/* ==========================================================================
   lain.css — the layer that makes this a Lain machine rather than a NeXT
   machine standing in front of a Lain wallpaper.

   Loaded after system.css and desktop.css, so it overrides the palette and
   adds everything on top. Five things do most of the work:

     1. a dirtier, more limited palette — sodium orange, dull viridian, bone
     2. visible colour banding, because the show's digital gradients step
     3. blind shadows raked across the room
     4. halation: every light source bleeds
     5. chromatic aberration on the chrome, never on body text

   All of it sits behind `body.no-fx`, which the View menu toggles.
   ========================================================================== */

:root {
  /* Lain's palette is warm, dirty and desaturated. The old values were too
     clean — sodium vapour is closer to #e08640 than to #ff9b3f. */
  --amber:     #e58a3c;
  --amber-dim: #96552a;
  --amber-hot: #ffb05e;
  --teal:      #4e8a80;   /* dull viridian, not cyan */
  --bone:      #ded8c8;
  --rust:      #8f3a26;
  --indigo:    #171a29;
  --olive:     #4a4630;

  /* The chrome itself goes warm. A blend layer cannot do this convincingly
     on light values, so the workstation gray becomes an actual taupe — which
     is what the show's interface panels are, once you stop assuming NeXT
     neutral. This is the single change that stops the windows reading as
     cold rectangles pasted over a sunset. */
  --gray:      #b0a79a;
  --gray-lt:   #c8c0b2;
  --gray-dk:   #948a7c;
  --hi:        #f6f0e4;
  --lo:        #574e44;
  --paper:     #f3ece0;   /* documents are paper, not screens */
}

/* ---------------------------------------------------------------- wallpaper
   Dusk, banded. The banding is the point: the show's gradients step because
   they were composited at low bit depth, and that stepping is half the look. */
#wallpaper {
  background:
    repeating-linear-gradient(180deg,
      rgba(0,0,0,.055) 0 3px, rgba(255,255,255,.012) 3px 16px),
    radial-gradient(64% 30% at 47% 113%, rgba(229,138,60,.46), rgba(229,138,60,0) 64%),
    radial-gradient(150% 62% at 50% 116%, rgba(180,86,40,.26), rgba(180,86,40,0) 62%),
    radial-gradient(90% 52% at 14% -14%, rgba(78,138,128,.10), transparent 64%),
    linear-gradient(180deg,
      #030408 0%, #07090f 26%, #0d0e15 50%, #171318 74%, #2a1810 100%);
}

/* blinds — the shadow that falls across almost every interior in the show.
   Every --fx-* below is written onto <html> by js/fx.js out of
   config/effects.js; the fallback is what this machine looked like before
   there was a config file, so deleting it changes nothing. */
#blinds {
  position: absolute; inset: -10%; z-index: 1; pointer-events: none;
  background: repeating-linear-gradient(94deg,
    rgba(0,0,0,.62) 0 30px,
    transparent 30px 104px);
  -webkit-mask-image: linear-gradient(158deg, #000 0%, rgba(0,0,0,.55) 46%, transparent 88%);
          mask-image: linear-gradient(158deg, #000 0%, rgba(0,0,0,.55) 46%, transparent 88%);
  opacity: var(--fx-blinds, 1);
}

/* a second, slower set from the other side, so the room has two windows */
#blinds::after {
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(-86deg,
    rgba(0,0,0,.42) 0 18px, transparent 18px 122px);
  -webkit-mask-image: linear-gradient(20deg, #000 4%, transparent 62%);
          mask-image: linear-gradient(20deg, #000 4%, transparent 62%);
}

#poles line { stroke: rgba(222,216,200,.20); }
#poles .wire { stroke: rgba(222,216,200,.13); }

/* halation — the horizon bleeds up into the room */
#bloom {
  position: absolute; left: 0; right: 0; bottom: -18%; height: 58%;
  z-index: 3; pointer-events: none;
  background: radial-gradient(58% 100% at 47% 100%, rgba(255,150,66,.20), transparent 70%);
  filter: blur(var(--fx-bloom-blur, 34px));
  opacity: var(--fx-bloom, 1);
}

/* --------------------------------------------------------------------- HUD
   Copland OS was never a desktop full of icons. It was readouts floating in
   the dark, most of which told you nothing you could act on.               */
#hud {
  position: absolute; top: 12px; right: 66px; z-index: 4;   /* behind windows */
  pointer-events: none;
  font-family: var(--mono); font-size: 10px; line-height: 1.75;
  letter-spacing: .22em; text-align: right;
  color: rgba(229,138,60,.62);
  text-shadow: 0 0 9px rgba(229,138,60,.35);
}
#hud .hot { color: var(--amber-hot); }
#hud .cool { color: rgba(78,138,128,.68); }
#hud b { font-weight: 400; opacity: .5; }

/* corner brackets — the frame the Navi draws around the world */
#brackets { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
#brackets i {
  position: absolute; width: 18px; height: 18px;
  border: 1px solid rgba(229,138,60,.34);
}
#brackets i:nth-child(1) { top: 6px;    left: 6px;    border-right: 0; border-bottom: 0; }
#brackets i:nth-child(2) { top: 6px;    right: 6px;   border-left: 0;  border-bottom: 0; }
#brackets i:nth-child(3) { bottom: 6px; left: 6px;    border-right: 0; border-top: 0; }
#brackets i:nth-child(4) { bottom: 6px; right: 6px;   border-left: 0;  border-top: 0; }

/* --------------------------------------------------------- chromatic chrome
   Aberration on the chrome only. Window titles, menus, the dock and the HUD
   shimmer; anything you actually read stays clean.                          */
.win-title .tb-label,
#menu .m-item, #menu .m-title,
#dock .d-app .tip,
.shelf label, .statusline {
  text-shadow:
    var(--fx-ab, .55px) 0 rgba(255,32,72,.30),
    calc(-1 * var(--fx-ab, .55px)) 0 rgba(0,214,255,.24);
}
.win.key .win-title {
  background:
    linear-gradient(180deg, rgba(229,138,60,.10), transparent 62%),
    var(--gray);
}

/* windows pick up the light in the room */
.win {
  box-shadow:
    0 0 0 1px rgba(0,0,0,.8),
    0 0 22px rgba(229,138,60,.05),
    14px 18px 34px rgba(0,0,0,.62),
    4px 5px 0 rgba(0,0,0,.3);
}
.win.key { box-shadow:
    0 0 0 1px rgba(0,0,0,.85),
    0 0 30px rgba(229,138,60,.11),
    16px 20px 40px rgba(0,0,0,.68),
    4px 5px 0 rgba(0,0,0,.3); }

/* ============================ INTERFERENCE ================================
   Not scanlines. This is a small glitch-art kit, and the pieces are the ones
   that actually appear in datamoshed video and corrupted bitmaps:

     · displacement — the frame smeared sideways along horizontal bands,
       driven by an SVG feTurbulence/feDisplacementMap pipeline with an
       anisotropic noise field, so it tears in strips rather than wobbling
     · channel separation — R and B offset hard, not the half-pixel shimmer
       the chrome wears all the time
     · block corruption — rectangles of the frame inverted, hue-rotated,
       crushed to black, or posterised, in staggered waves
     · slice shear — full-width strips translated by different amounts
     · smear — one band blurred and over-saturated downward, the way a
       pixel-sorted row bleeds
     · dropout — the whole frame gone except the readouts

   All of it is applied to a fixed overlay or to #desktop for 100-500ms and
   then removed, so nothing here is composited during ordinary use.
   ========================================================================== */

/* the SVG filter host — invisible, just carries the <defs> */
#fx { position: absolute; width: 0; height: 0; overflow: hidden; }

/* displacement is applied to the desktop itself for the length of a burst */
#desktop.mosh { filter: url(#mosh); }
#desktop.mosh-hard { filter: url(#mosh) contrast(1.25) saturate(1.4); }

/* the layer every block, slice and smear is spawned into */
#glitch { position: fixed; inset: 0; z-index: 705; pointer-events: none; }
#glitch > * { position: absolute; }

/* block corruption. Five flavours, all sampling what is behind them. */
.g-block { mix-blend-mode: normal; }
.g-invert  { backdrop-filter: invert(1) hue-rotate(150deg) saturate(.5);
             -webkit-backdrop-filter: invert(1) hue-rotate(150deg) saturate(.5); }
.g-burn    { backdrop-filter: brightness(.06) contrast(2.4);
             -webkit-backdrop-filter: brightness(.06) contrast(2.4); }
.g-poster  { backdrop-filter: contrast(3.4) saturate(3) hue-rotate(-24deg) brightness(1.15);
             -webkit-backdrop-filter: contrast(3.4) saturate(3) hue-rotate(-24deg) brightness(1.15); }
.g-bleed   { backdrop-filter: blur(7px) saturate(4) contrast(1.6);
             -webkit-backdrop-filter: blur(7px) saturate(4) contrast(1.6); }
.g-dead    { background: #05060a; box-shadow: inset 0 1px 0 rgba(229,138,60,.22); }

/* slice shear — a strip translated sideways, with its own channel split */
.g-slice {
  left: -6%; right: -6%;
  backdrop-filter: saturate(1.8) contrast(1.3);
  -webkit-backdrop-filter: saturate(1.8) contrast(1.3);
  box-shadow: inset 0 1px 0 rgba(255,40,60,.5), inset 0 -1px 0 rgba(0,200,255,.42);
}

/* smear — a pixel-sorted row bleeding downward */
.g-smear {
  left: 0; right: 0;
  backdrop-filter: blur(14px) saturate(3.2) contrast(1.5) brightness(.9);
  -webkit-backdrop-filter: blur(14px) saturate(3.2) contrast(1.5) brightness(.9);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0,0,0,.35) 55%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 0%, rgba(0,0,0,.35) 55%, transparent 100%);
}

/* dropout — everything gone but the readouts, for two frames */
#flash {
  position: fixed; inset: 0; z-index: 706; pointer-events: none; opacity: 0;
  background: #04050a;
}
#flash.run { animation: dropout .19s steps(2); }
@keyframes dropout { 0% { opacity: 0 } 30% { opacity: .96 } 60% { opacity: .1 } 100% { opacity: 0 } }

/* the old tear band, kept as the mildest event in the set */
#tear {
  position: fixed; left: 0; right: 0; top: -14%; height: 2.6%;
  z-index: 705; pointer-events: none; opacity: 0;
  backdrop-filter: invert(1) hue-rotate(184deg) saturate(.6);
  -webkit-backdrop-filter: invert(1) hue-rotate(184deg) saturate(.6);
  box-shadow: 0 0 24px rgba(0,0,0,.6);
}
#tear.run { animation: sweep var(--tear-dur, 1.1s) linear; }
@keyframes sweep {
  0%   { opacity: 0;   transform: translateY(0) scaleY(.4) }
  6%   { opacity: .95 }
  50%  { transform: translateY(52vh) scaleY(1.5) }
  94%  { opacity: .9 }
  100% { opacity: 0;   transform: translateY(112vh) scaleY(.5) }
}

/* whole-desktop displacement, very short */
@keyframes slip {
  0%, 100% { transform: translateX(0) }
  18% { transform: translateX(-13px) }
  36% { transform: translateX(8px) }
  55% { transform: translateX(-4px) }
  74% { transform: translateX(2px) }
}
#desktop.slip { animation: slip .26s steps(5); }

/* ------------------------------------------------------------------- dirt
   The machine is not clean. Coarse noise, a few vertical scratches in the
   phosphor, and three dead rows that never move.                          */
#dirt {
  position: fixed; inset: 0; z-index: 700; pointer-events: none;
  opacity: var(--fx-dirt, .3); mix-blend-mode: multiply;
  background-image:
    /* scratches */
    linear-gradient(90deg, transparent 0 12.4%, rgba(0,0,0,.34) 12.4% 12.46%, transparent 12.46%),
    linear-gradient(90deg, transparent 0 63.2%, rgba(0,0,0,.26) 63.2% 63.25%, transparent 63.25%),
    linear-gradient(90deg, transparent 0 88.7%, rgba(0,0,0,.2) 88.7% 88.74%, transparent 88.74%),
    /* dead rows */
    linear-gradient(180deg, transparent 0 31.2%, rgba(0,0,0,.42) 31.2% 31.28%, transparent 31.28%),
    linear-gradient(180deg, transparent 0 74.6%, rgba(0,0,0,.34) 74.6% 74.66%, transparent 74.66%),
    /* coarse dirt — redrawn by js/fx.js when the grime is asked to be finer */
    var(--fx-dirt-noise, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='d'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 .09 0 0 0 0 .07 0 0 0 0 .06 0 0 0 -1.5 .82'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23d)'/%3E%3C/svg%3E"));
}

/* grain and its crawl are set in desktop.css; the dial for both is here */
#grain {
  opacity: var(--fx-grain, .16);
  animation-duration: var(--fx-grain-ms, .9s);
}

/* --------------------------------------------------------------- the drift
   The longer you stay, the less stable the machine is. Three levels, set on
   <body> by drift.js. Nothing here ever touches legibility of body text.   */
body[data-drift="1"] #grain { opacity: var(--fx-grain-1, .21); }
body[data-drift="2"] #grain { opacity: var(--fx-grain-2, .27); }
body[data-drift="2"] #scanlines { opacity: var(--fx-scan-2, .27); }
body[data-drift="3"] #grain { opacity: var(--fx-grain-3, .33); }
body[data-drift="3"] #scanlines { opacity: var(--fx-scan-3, .32); }
body[data-drift="1"] #dirt { opacity: var(--fx-dirt-1, .4); }
body[data-drift="2"] #dirt { opacity: var(--fx-dirt-2, .52); }
body[data-drift="3"] #dirt { opacity: var(--fx-dirt-3, .66); }
body[data-drift="3"] #wallpaper { filter: saturate(1.3) contrast(1.12) brightness(.92); }
body[data-drift="3"] #hud { color: rgba(229,138,60,.9); }

/* ------------------------------------------------------------------- audio
   The hum toggle lives in the status bar next to the observer light.       */
#hum {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 9px;
  font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  color: rgba(222,216,200,.62);
  background: rgba(0,0,0,.5); border: 1px solid rgba(222,216,200,.22);
  cursor: pointer;
}
#hum:hover { background: rgba(222,216,200,.10); }
#hum[aria-pressed="true"] { color: var(--amber); border-color: rgba(229,138,60,.44); }
#hum i {
  width: 5px; height: 5px; background: currentColor;
  box-shadow: 7px 0 0 currentColor, 14px 0 0 currentColor;
  opacity: .35;
}
#hum[aria-pressed="true"] i { opacity: 1; animation: hum 1.9s ease-in-out infinite; }
@keyframes hum { 0%,100% { opacity: .4 } 50% { opacity: 1 } }

/* ------------------------------------------------------------- screensaver */
#saver { background: #04050a; }
.saver-text .s1, .saver-text .s2 {
  text-shadow: 2px 0 rgba(255,40,40,.5), -2px 0 rgba(78,180,170,.45), 0 0 26px rgba(222,216,200,.2);
}

/* -------------------------------------------------------- file metadata rows
   The browser gains a right-hand gutter of dates. Timestamps are where the
   story actually lives — a file changed at 03:12 says more than its content. */
.browser .row .when {
  margin-left: auto; padding-left: 8px;
  font-family: var(--mono); font-size: 9.5px;
  color: var(--lo); opacity: .8;
  white-space: nowrap;
}
.browser .row.sel .when { color: rgba(255,255,255,.75); }
.browser .row.stale .nm { opacity: .62; }
.browser .row.locked .nm::after { content: " ·"; }

.inspector .facts {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 12px;
  font-family: var(--mono); font-size: 11px;
  margin: 8px 0 4px;
}
.inspector .facts dt { color: var(--lo); }
.inspector .facts dd { margin: 0; }

/* the lock panel */
.lockbox { padding: 18px 16px; max-width: 42ch; }
.lockbox h2 { margin: 0 0 6px; }
.lockbox p { margin: 0 0 10px; line-height: 1.5; font-size: 12px; }
.lockbox .row { display: flex; gap: 6px; margin-top: 10px; }
.lockbox .bad { color: var(--rust); font-size: 11px; min-height: 14px; }

/* trash */
.browser .row.trashed .nm { text-decoration: line-through; text-decoration-color: rgba(0,0,0,.35); }

/* --------------------------------------------------------------- the stack
   Downloads is not a folder, it is a deposit. New things land on top and
   press everything else down. The year rules are the bedding planes; the
   further you scroll the older the thing you are looking at, and the less
   its owner remembers putting it there. */
.browser .stratum {
  position: sticky; top: 0; z-index: 2;
  padding: 1px 8px 1px 9px;
  font-family: var(--mono); font-size: 9px; letter-spacing: .18em;
  color: var(--lo);
  background:
    linear-gradient(var(--gray-lt), var(--gray-lt)) padding-box,
    repeating-linear-gradient(90deg, rgba(0,0,0,.22) 0 1px, transparent 1px 4px);
  border-top: 1px solid var(--lo);
  box-shadow: 0 1px 0 var(--hi), 0 3px 5px -3px rgba(0,0,0,.4);
}
.browser .col > .stratum:first-child { border-top: 0; }
.browser .col.stack-col { flex-basis: 268px; }

/* the core sample, in the inspector */
.core { margin-top: 10px; }
.core h3 { margin: 0 0 6px; }
.core .layer {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: 10px; color: var(--lo);
  padding: 1px 0; cursor: default;
}
.core .layer:hover { color: var(--ink); }
.core .layer:hover .bar { background: var(--amber); }
.core .yr, .wired-spine .yr { width: 4ch; flex: 0 0 auto; }
.core .ct, .wired-spine .ct { width: 5ch; flex: 0 0 auto; text-align: right; opacity: .7; }
.core .bar, .wired-spine .bar {
  flex: 0 1 auto; height: 7px; min-width: 2px;
  background: var(--ink);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.25);
}

/* ---------------------------------------------------------------- the Wired
   An archive of a network the machine can no longer reach. The year spine is
   the Downloads stack again, turned on its side: one thick layer in 2011 and
   then a decade of somebody leaving. */
.browser .col.wired-spine { flex: 0 0 138px; }
.wired-spine .row.layer {
  gap: 7px; font-family: var(--mono); font-size: 10px; color: var(--lo);
}
.wired-spine .row.layer.sel { color: var(--paper); }
.wired-spine .row.layer.sel .bar { background: var(--amber); }
.wired-spine .row.layer:hover .bar { background: var(--amber-dim); }

.w-post {
  margin: 0 0 14px; padding-bottom: 11px;
  border-bottom: 1px solid rgba(87,78,68,.35);
  max-width: 58ch;
}
.w-post p { margin: 0; font-size: 12.5px; line-height: 1.55; white-space: pre-wrap; }
.w-head {
  display: flex; gap: 9px; align-items: baseline; margin-bottom: 4px;
  font-family: var(--mono); font-size: 9.5px; color: var(--lo);
}
.w-re { opacity: .65; }
.w-eng { margin-left: auto; opacity: .7; }

/* ------------------------------------------------------------------ no-fx
   One switch turns off everything in this file that moves or interferes.
   The palette stays; the machine stops misbehaving.                        */
body.no-fx #blinds,
body.no-fx #bloom,
body.no-fx #tear,
body.no-fx #flash,
body.no-fx #hud,
body.no-fx #brackets { display: none; }
body.no-fx .win-title .tb-label,
body.no-fx #menu .m-item,
body.no-fx #menu .m-title { text-shadow: none; }
body.no-fx #desktop.slip { animation: none; }

/* A visitor who has asked their machine to hold still gets a machine that
   holds still. `respectReducedMotion: false` in config/effects.js is the only
   thing that puts body.fx-force here and overrules them. */
@media (prefers-reduced-motion: reduce) {
  body:not(.fx-force) #tear,
  body:not(.fx-force) #flash,
  body:not(.fx-force) #glitch { display: none; }
  body:not(.fx-force) #desktop.mosh,
  body:not(.fx-force) #desktop.mosh-hard { filter: none; }
  body:not(.fx-force) #desktop.slip { animation: none; }
  body:not(.fx-force) #hum i { animation: none; }
  body.fx-force #grain { animation: shift var(--fx-grain-ms, .9s) steps(3) infinite; }
}


/* ------------------------------------------------------------------- light
   The single most Lain thing available: one wash of sodium light over the
   WHOLE screen, windows included, so the gray chrome sits in the same room
   as the horizon instead of floating in front of a picture of one.

   soft-light keeps the chrome's contrast intact while shifting its hue —
   the windows go warm without the text going muddy.                       */
#tint {
  position: fixed; inset: 0; z-index: 702; pointer-events: none;
  mix-blend-mode: soft-light; opacity: var(--fx-tint, .78);
  background:
    radial-gradient(120% 86% at 50% 112%,
      rgba(var(--fx-tint-warm, 255,150,58), .92),
      rgba(var(--fx-tint-warm, 255,150,58), 0) 58%),
    radial-gradient(90% 70% at 12% -8%,  rgba(96,150,190,.34), transparent 62%),
    linear-gradient(184deg, rgba(20,26,72,.72) 0%, rgba(90,60,52,.24) 54%,
      rgba(var(--fx-tint-warm, 255,150,58), .40) 100%);
}
#vignette {
  position: fixed; inset: 0; z-index: 703; pointer-events: none;
  mix-blend-mode: multiply; opacity: var(--fx-vignette, 1);
  background: radial-gradient(110% 94% at 50% 44%,
    rgba(255,255,255,1) 30%, rgba(132,102,94,.9) 66%, rgba(22,16,26,.94) 100%);
}
/* the scanline field returns to the top of the stack, faintly, so the whole
   frame reads as one screen rather than as windows over a photograph */
#scanlines { position: fixed; z-index: 704; opacity: var(--fx-scan, .11); }

body[data-drift="2"] #tint { opacity: var(--fx-tint-2, .88); }
body[data-drift="3"] #tint { opacity: var(--fx-tint-3, .96); }
body[data-drift="3"] #vignette { mix-blend-mode: multiply; }

body.no-fx #tint, body.no-fx #vignette, body.no-fx #dirt, body.no-fx #glitch { display: none; }
body.no-fx #desktop { filter: none !important; }


/* an unfocused window sits further back in the room */
.win:not(.key) .win-body { filter: saturate(.86) brightness(.97); }

/* white surfaces take the light too */
.doc, .eliza-log { background: var(--paper); }
.inspector pre[style*="background:#fff"] { background: var(--paper) !important; }


/* ========================== HAUNTOLOGY ====================================
   Fisher's term, and the reason it belongs here: this machine is a future
   that did not arrive. Three cheap things carry most of that, and none of
   them are effects — they are evidence that something was here before you.
   ========================================================================== */

/* --- the photograph ------------------------------------------------------
   Hauntology's actual subject is not the past, it is a future that did not
   arrive — and its texture is generational loss: the copy of the copy. A
   drawn gradient cannot do that. A wrecked photograph can.

   assets/wallpaper.jpg has already been through the wringer offline
   (desaturated, cold-shifted, posterised to 4 bits, blended with a 1/5
   scale version of itself, saved at quality 32). What is left here is the
   part that has to happen live: it sits UNDER the sodium wash, so the room
   still lights it.                                                        */
#photo {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: url("../assets/wallpaper.jpg") center/cover no-repeat;
  opacity: 0;
  filter: grayscale(.28) contrast(1.1) brightness(.72) saturate(.85);
  transition: opacity .6s ease;
}
/* when the photograph is the ground, the drawn dusk gets out of its way */
body.photo #wallpaper {
  background:
    repeating-linear-gradient(180deg, rgba(0,0,0,.05) 0 3px, rgba(255,255,255,.01) 3px 16px),
    linear-gradient(180deg, #04050a 0%, #0a0b12 55%, #1d1310 100%);
}
body.photo #photo { opacity: .95; }
body.photo #poles { opacity: .3; }
body.photo #bloom { opacity: var(--fx-bloom-photo, .55); }
/* the room's light has to back off or it eats the photograph */
body.photo #tint { opacity: var(--fx-tint-photo, .5); }
body.photo #vignette { opacity: var(--fx-vignette-photo, .45); }
body.photo #dirt { opacity: var(--fx-dirt-photo, .22); }
body.photo[data-drift="3"] #photo { filter: grayscale(.12) contrast(1.4) brightness(.9) saturate(1.1); }
body.no-fx #photo { display: none; }

/* --- burn-in -------------------------------------------------------------
   A phosphor ghost of the menu column and of a window that sat in the same
   place for years. Baked into the wallpaper, so windows cover it and the
   desktop never quite forgets.                                            */
#burn {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  opacity: var(--fx-burn, .5);
}
#burn::before {
  content: ""; position: absolute;
  left: 10px; top: 10px; width: 150px; height: 372px;
  background: rgba(206,196,178,.045);
  box-shadow:
    inset 0 0 0 1px rgba(206,196,178,.05),
    inset 0 45px 0 -44px rgba(206,196,178,.05),
    inset 0 90px 0 -89px rgba(206,196,178,.05),
    inset 0 135px 0 -134px rgba(206,196,178,.05),
    inset 0 180px 0 -179px rgba(206,196,178,.05),
    inset 0 225px 0 -224px rgba(206,196,178,.05),
    inset 0 270px 0 -269px rgba(206,196,178,.05);
}
#burn::after {
  content: ""; position: absolute;
  left: 214px; top: 28px; width: 640px; height: 396px;
  border: 1px solid rgba(206,196,178,.05);
  background: linear-gradient(180deg,
    rgba(206,196,178,.055) 0 22px, rgba(206,196,178,.022) 22px 100%);
}

/* --- registration marks --------------------------------------------------
   Crop and calibration marks from a print process nobody ran. They float
   over the windows because they belong to the reproduction, not the scene. */
#marks { position: fixed; inset: 0; z-index: 701; pointer-events: none; }
#marks i {
  position: absolute; width: 26px; height: 26px;
  opacity: var(--fx-marks, .3); mix-blend-mode: overlay;
}
#marks i::before, #marks i::after {
  content: ""; position: absolute; background: rgba(232,226,210,.85);
}
#marks i::before { left: 0; right: 0; top: 50%; height: 1px; }
#marks i::after  { top: 0; bottom: 0; left: 50%; width: 1px; }
#marks i.x { transform: rotate(45deg); opacity: var(--fx-marks-x, .22); }

#marks i:nth-child(1)  { left: 23%;  top: 4%; }
#marks i:nth-child(2)  { left: 61%;  top: 11%; }
#marks i:nth-child(3)  { left: 8%;   top: 47%; }
#marks i:nth-child(4)  { left: 88%;  top: 33%; }
#marks i:nth-child(5)  { left: 44%;  top: 71%; }
#marks i:nth-child(6)  { left: 74%;  top: 88%; }
#marks i:nth-child(7)  { left: 35%;  top: 26%; }
#marks i:nth-child(8)  { left: 92%;  top: 62%; }
#marks i:nth-child(9)  { left: 15%;  top: 79%; }
#marks i:nth-child(10) { left: 55%;  top: 41%; }
#marks i:nth-child(11) { left: 68%;  top: 57%; }
#marks i:nth-child(12) { left: 30%;  top: 94%; }

body[data-drift="2"] #marks i { opacity: var(--fx-marks-2, .42); }
body[data-drift="3"] #marks i { opacity: var(--fx-marks-3, .55); }

/* --- ghost windows -------------------------------------------------------
   Close a window and its outline stays. It fades most of the way out and
   then stops, because the machine does not entirely let go of anything.   */
#ghosts { position: absolute; inset: 0; z-index: 5; pointer-events: none; }
#ghosts .ghost {
  position: absolute;
  border: 1px solid rgba(206,196,178,.22);
  background: linear-gradient(180deg,
    rgba(206,196,178,.05) 0 22px, rgba(206,196,178,.014) 22px 100%);
  animation: settle var(--fx-ghost-ms, 26s) ease-out forwards;
}
#ghosts .ghost .g-title {
  position: absolute; left: 0; right: 0; top: 3px;
  text-align: center;
  font: 700 12px/1 var(--ui);
  color: rgba(206,196,178,.3);
  letter-spacing: .02em;
}
@keyframes settle {
  from { opacity: var(--fx-ghost-start, .85) }
  to   { opacity: var(--fx-ghost, .16) }
}
@media (prefers-reduced-motion: reduce) {
  body:not(.fx-force) #ghosts .ghost { animation: none; opacity: var(--fx-ghost, .16); }
}

body.no-fx #marks, body.no-fx #burn, body.no-fx #ghosts { display: none; }

/* ==========================================================================
   The outbound panel. A system alert, not an application window: it sits
   above everything, it cannot be moved, and it is the only thing on this
   machine that can start a connection to another one.
   ========================================================================== */
.ob-shade {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8, 7, 5, .62);
  backdrop-filter: saturate(.5) blur(1px);
  -webkit-backdrop-filter: saturate(.5) blur(1px);
  padding: 16px;
}
.ob-panel {
  width: min(470px, 100%); max-height: 88vh; overflow: auto;
  background: var(--gray); color: var(--ink);
  border: 1px solid var(--lo);
  box-shadow: inset 1px 1px 0 var(--hi), 0 14px 42px rgba(0,0,0,.65);
  font-size: 12px; line-height: 1.5;
}
.ob-bar {
  display: flex; align-items: center; gap: 7px;
  padding: 3px 8px; background: var(--gray-lt);
  border-bottom: 1px solid var(--lo); box-shadow: inset 0 1px 0 var(--hi);
}
.ob-glyph { color: #8a5a10; font-size: 11px; }
.ob-title { font-weight: 700; font-size: 11px; letter-spacing: .05em; text-transform: uppercase; }
.ob-body { padding: 12px 14px 8px; }
.ob-lede { margin: 0 0 8px; }
.ob-lede b { font-family: var(--mono); background: var(--gray-lt); padding: 0 3px; }
.ob-who { margin: 0 0 10px; color: #3d3a34; }
.ob-body h3 {
  margin: 0 0 4px; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .08em; color: #55524a;
}
.ob-list { margin: 0 0 10px; padding-left: 17px; }
.ob-list li { margin-bottom: 2px; }
.ob-note {
  margin: 0 0 10px; padding: 7px 9px; font-size: 11px; color: #3d3a34;
  background: var(--gray-lt);
  border: 1px solid var(--lo); box-shadow: inset 1px 1px 0 var(--hi);
}
.ob-remember { display: flex; align-items: center; gap: 5px; font-size: 11px; }
.ob-foot {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 14px 12px;
}
.ob-foot .spacer { flex: 1 1 auto; }
body.no-fx .ob-shade { backdrop-filter: none; -webkit-backdrop-filter: none; }
