/* ==========================================================================
   The desk (`desk` in crt-config.js) — the screen runs on a real CRT: a
   photo of a sunlit 90s bedroom with a beige monitor on the desk
   (assts/desk-crt-*.webp, made from retro-bedroom-4k-marker-label_upscayl_2x_digital-art-4x.png). The photo
   covers the window, and our screen is placed on the photo's glass in the
   photo's own coordinates, clipped to the glass's outline (traced from the
   photo), so the real bezel frames it at any window shape. The room shows
   in the glass over the picture, and the bezel shades the glass's edge.

   Landscape windows with room for it only (phones keep the bare screen);
   JS drops it in fullscreen. Decoration, click-through, but for the
   stickers that link to projects.
   ========================================================================== */

.desk,
.monitor__glare,
.monitor__rim {
  display: none;
}

/* Room for the desk. room.js's DESK_ROOM is this same query: the tube's
   effects run only here; change both together. */
@media (orientation: landscape) and (min-width: 700px) and (min-height: 450px) {
  body.crt--desk {
    /* The photo (7680 × 4320) and its glass's box, as fractions of it. */
    --desk-ratio: calc(7680 / 4320);
    --desk-glass-x: 0.35091;
    --desk-glass-y: 0.16561;
    --desk-glass-w: 0.29809;
    --desk-glass-h: 0.43597;
    /* How tall the glass is drawn: at least 55% of the window's height, and
       at least 610px so a full screen of text stays readable (~14px, see the
       font size below) — zooming into the photo on smaller windows, cropping
       the room — but never taller or wider than the window. With the
       keyboard on the desk, small enough to show it too (--desk-fit-px). */
    --desk-glass-ratio: calc(var(--desk-glass-w) * var(--desk-ratio) / var(--desk-glass-h));
    --desk-glass-px: min(
      clamp(55vh, 610px, 94vh),
      calc(96vw / var(--desk-glass-ratio)),
      var(--desk-fit-px, 100vh)
    );
    /* The photo, scaled to that, and always covering the window. */
    --desk-h: max(
      100vh,
      calc(100vw / var(--desk-ratio)),
      calc(var(--desk-glass-px) / var(--desk-glass-h))
    );
    --desk-w: calc(var(--desk-h) * var(--desk-ratio));
    /* The screen is the glass. */
    --crt-screen-width: calc(var(--desk-glass-w) * var(--desk-w));
    --crt-screen-height: calc(var(--desk-glass-h) * var(--desk-h));
    /* The glass centred across, and a little above the middle. When it's
       tall (zoomed in), lower: far enough to keep the bezel above it in view
       (it's 0.133 of the glass's height, so the glass's centre sits at least
       0.633 of its height down), but never so far its bottom leaves the
       window — nor the keyboard, when it's there (--desk-fit-cy). All as far
       as the photo can move without uncovering the window's edges. */
    --desk-glass-cy: min(
      max(46vh, calc(var(--crt-screen-height) * 0.633)),
      calc(100vh - var(--crt-screen-height) / 2 - 1vh),
      var(--desk-fit-cy, 100vh)
    );
    --desk-x: clamp(
      calc(100vw - var(--desk-w)),
      calc(50vw - (var(--desk-glass-x) + var(--desk-glass-w) / 2) * var(--desk-w)),
      0px
    );
    --desk-y: clamp(
      calc(100vh - var(--desk-h)),
      calc(var(--desk-glass-cy) - (var(--desk-glass-y) + var(--desk-glass-h) / 2) * var(--desk-h)),
      0px
    );
    /* The picture runs on past the glass, under the bezel, by more than the
       curve pulls its edges in (--crt-curve-reach, set by JS), so the curved
       edge never shows. The terminal's padding grows to match, plus a margin
       inside the glass. */
    --desk-overscan: calc(var(--crt-curve-reach, 35px) + 4px);
    --desk-inner: calc(var(--crt-screen-width) * 0.032);
    /* Like a real terminal, the screen is a fixed grid — 80 columns by 27
       rows, enough for the longest output — and the text is sized to
       fit it on the glass, so nothing scrolls or rewraps as the window
       changes. 0.6em is a monospace character's width. */
    font-size: min(
      calc((var(--crt-screen-width) - 2 * var(--desk-inner)) / (80 * 0.6)),
      calc((var(--crt-screen-height) - 2 * var(--desk-inner)) / (27 * 1.5))
    );
  }

  /* With the keyboard on the desk (keyboard.css), the view runs from the
     bezel above the glass down to --desk-view-bottom in the photo: the glass
     is drawn small enough for that to fit the window's height, and no lower
     than lets it. Text shrinks with it on smaller windows. */
  body.crt--desk.crt--keyboard {
    --desk-view-top: calc(var(--desk-glass-y) - 0.133 * var(--desk-glass-h));
    --desk-fit-px: calc(
      100vh * var(--desk-glass-h) / (var(--desk-view-bottom) - var(--desk-view-top))
    );
    --desk-fit-cy: calc(
      100vh -
      (var(--desk-view-bottom) - var(--desk-glass-y) - var(--desk-glass-h) / 2) * var(--desk-h)
    );
  }

  body.crt--desk .desk,
  body.crt--desk .monitor__glare,
  body.crt--desk .monitor__rim {
    display: block;
  }

  /* The screen, on the photo's glass and clipped to its outline. The clip
     sits here, around the glass, so it applies after the curve. overflow:
     clip keeps the glare's full-photo box (and the overscan) from making the
     page scrollable — clip-path alone doesn't; clip, unlike hidden, doesn't
     make this a scroll container the terminal's scrollIntoView could move. */
  body.crt--desk .monitor {
    position: absolute;
    top: calc(var(--desk-y) + var(--desk-glass-y) * var(--desk-h));
    left: calc(var(--desk-x) + var(--desk-glass-x) * var(--desk-w));
    margin: 0;
    overflow: clip;
    clip-path: polygon(
      -0.18% 49.53%, -0.17% 47.44%, -0.14% 45.36%, -0.12% 43.27%, -0.09% 41.19%, -0.03% 39.10%,
      0.02% 37.02%, 0.10% 34.94%, 0.18% 32.85%, 0.27% 30.77%, 0.38% 28.69%, 0.49% 26.61%,
      0.58% 24.53%, 0.70% 22.45%, 0.84% 20.37%, 0.96% 18.29%, 1.09% 16.21%, 1.23% 14.13%,
      1.38% 12.05%, 1.52% 9.98%, 1.65% 7.90%, 1.98% 5.85%, 2.99% 4.17%, 4.54% 3.37%,
      6.24% 3.13%, 7.94% 2.91%, 9.65% 2.65%, 11.35% 2.43%, 13.06% 2.22%, 14.77% 2.00%,
      16.47% 1.76%, 18.17% 1.54%, 19.88% 1.36%, 21.60% 1.22%, 23.30% 1.03%, 25.01% 0.87%,
      26.73% 0.77%, 28.44% 0.66%, 30.15% 0.55%, 31.87% 0.44%, 33.58% 0.37%, 35.29% 0.28%,
      37.01% 0.19%, 38.72% 0.13%, 40.44% 0.06%, 42.15% 0.05%, 43.87% 0.01%, 45.58% 0.00%,
      47.30% 0.01%, 49.01% 0.02%, 50.73% 0.02%, 52.44% 0.02%, 54.16% 0.03%, 55.88% 0.06%,
      57.59% 0.13%, 59.30% 0.16%, 61.02% 0.24%, 62.73% 0.29%, 64.45% 0.39%, 66.16% 0.48%,
      67.87% 0.58%, 69.59% 0.67%, 71.30% 0.79%, 73.01% 0.92%, 74.73% 1.02%, 76.44% 1.20%,
      78.14% 1.41%, 79.86% 1.52%, 81.57% 1.68%, 83.27% 1.88%, 84.98% 2.08%, 86.69% 2.29%,
      88.40% 2.47%, 90.10% 2.70%, 91.80% 2.97%, 93.51% 3.22%, 95.21% 3.43%, 96.82% 4.15%,
      97.82% 5.80%, 98.21% 7.81%, 98.36% 9.89%, 98.49% 11.97%, 98.62% 14.05%, 98.74% 16.13%,
      98.87% 18.21%, 99.00% 20.29%, 99.13% 22.36%, 99.24% 24.45%, 99.33% 26.53%, 99.41% 28.61%,
      99.49% 30.69%, 99.56% 32.78%, 99.63% 34.86%, 99.69% 36.94%, 99.73% 39.03%, 99.77% 41.11%,
      99.80% 43.20%, 99.84% 45.28%, 99.88% 47.37%, 99.90% 49.45%, 99.91% 51.54%, 99.89% 53.62%,
      99.86% 55.71%, 99.84% 57.79%, 99.82% 59.88%, 99.81% 61.96%, 99.79% 64.05%, 99.74% 66.13%,
      99.68% 68.22%, 99.63% 70.30%, 99.57% 72.38%, 99.49% 74.47%, 99.39% 76.55%, 99.27% 78.63%,
      99.14% 80.71%, 99.01% 82.79%, 98.87% 84.86%, 98.70% 86.94%, 98.52% 89.01%, 98.36% 91.09%,
      98.30% 93.17%, 98.01% 95.22%, 96.97% 96.84%, 95.37% 97.48%, 93.66% 97.61%, 91.94% 97.72%,
      90.23% 97.88%, 88.52% 98.05%, 86.81% 98.17%, 85.10% 98.28%, 83.39% 98.39%, 81.67% 98.50%,
      79.96% 98.64%, 78.25% 98.76%, 76.53% 98.80%, 74.82% 98.88%, 73.11% 98.98%, 71.39% 99.07%,
      69.68% 99.14%, 67.96% 99.24%, 66.25% 99.30%, 64.54% 99.34%, 62.82% 99.43%, 61.11% 99.47%,
      59.39% 99.49%, 57.68% 99.50%, 55.96% 99.53%, 54.25% 99.56%, 52.53% 99.57%, 50.81% 99.57%,
      49.10% 99.57%, 47.38% 99.57%, 45.67% 99.57%, 43.95% 99.57%, 42.24% 99.57%, 40.52% 99.59%,
      38.81% 99.58%, 37.09% 99.54%, 35.38% 99.43%, 33.66% 99.40%, 31.95% 99.33%, 30.23% 99.25%,
      28.52% 99.17%, 26.81% 99.06%, 25.09% 98.94%, 23.38% 98.85%, 21.67% 98.79%, 19.95% 98.66%,
      18.24% 98.52%, 16.53% 98.41%, 14.82% 98.30%, 13.10% 98.16%, 11.39% 98.03%, 9.69% 97.82%,
      7.98% 97.63%, 6.26% 97.56%, 4.55% 97.47%, 2.90% 96.93%, 1.86% 95.28%, 1.51% 93.24%,
      1.43% 91.16%, 1.26% 89.09%, 1.08% 87.01%, 0.93% 84.93%, 0.80% 82.86%, 0.67% 80.78%,
      0.53% 78.70%, 0.40% 76.62%, 0.30% 74.54%, 0.21% 72.46%, 0.12% 70.37%, 0.08% 68.29%,
      0.02% 66.20%, -0.06% 64.12%, -0.10% 62.04%, -0.13% 59.95%, -0.15% 57.87%, -0.16% 55.78%,
      -0.15% 53.70%, -0.16% 51.61%
    );
  }

  body.crt--desk .monitor__screen {
    inset: calc(-1 * var(--desk-overscan));
  }

  /* The glass's outline gives the corners their shape; rounding the tube as
     well would cut into them. */
  body.crt-on.crt--desk #crt-tube {
    border-radius: 0;
  }

  /* The clip above replaces the curve's own edge trim (crt.css). */
  body.crt--desk.crt--curve .monitor__screen {
    clip-path: none;
  }

  /* XP fills the glass, inset a little from its edges, as a CRT's picture
     sat inside the tube with a dark margin: its corners (Start, the clock)
     clear the glass's rounded ones. */
  body.crt--desk .screen {
    inset: calc(var(--desk-overscan) + var(--desk-inner) * 0.6);
  }

  /* Browsers without overflow: clip (Safari before 16) would let the glare's
     photo-sized box scroll the page: the page doesn't scroll at all. */
  @supports not (overflow: clip) {
    body.crt--desk {
      overflow: hidden;
    }
  }
}

/* The photo, in a window-sized frame that crops it. Clip, where the browser
   has it: hidden would let a sticker reached with Tab scroll the photo. */
.desk {
  position: absolute;
  inset: 0;
  overflow: hidden;
  overflow: clip;
  pointer-events: none;
}

.desk__photo,
.monitor__glare {
  background: url("assts/desk-crt-1600.webp") center / 100% 100% no-repeat;
}

@media (min-width: 1300px), (min-resolution: 1.5dppx) {
  .desk__photo,
  .monitor__glare {
    background-image: url("assts/desk-crt-2752.webp");
  }
}

@media (min-width: 2200px), (min-width: 1300px) and (min-resolution: 1.5dppx) {
  .desk__photo,
  .monitor__glare {
    background-image: url("assts/desk-crt-4000.webp");
  }
}

.desk__photo,
.desk__stickers {
  position: absolute;
  top: var(--desk-y);
  left: var(--desk-x);
  width: var(--desk-w);
  height: var(--desk-h);
}

/* Stickers on the monitor's casing (index.html), in the photo's
   coordinates, so they stay stuck to it. They're in the casing's light:
   dim, from the room in front (the SVG filter), warmer along the bottom
   edge where the sunlit desk bounces onto it (--low), a touch brighter or
   darker where the casing is (--lit); and a hair of shadow at the edge.
   The tilt and the light are both on the picture: on separate elements,
   the light would soften it. */
.desk__sticker {
  --sticker-edge: drop-shadow(0 0 0.5px rgba(0, 0, 0, 0.5));
  position: absolute;
  left: calc(var(--x) * 100%);
  top: calc(var(--y) * 100%);
  width: calc(var(--w) * 100%);
  transform: translate(-50%, -50%);
}

.desk__sticker img {
  display: block;
  width: 100%;
  height: auto;
  transform: rotate(var(--r));
  filter:
    url(#desk-sticker-light)
    brightness(calc(var(--lit, 1) * var(--glow, 1)))
    var(--sticker-edge);
}

.desk__sticker--low img {
  filter:
    url(#desk-sticker-light-low)
    brightness(calc(var(--lit, 1) * var(--glow, 1)))
    var(--sticker-edge);
}

/* The projects' stickers are links. Under the pointer, or reached with Tab,
   one catches the light: a glint glances across its gloss, and it stays a
   touch brighter (--glow). The glint is turned with the sticker, masked to
   its die-cut shape (its own picture, --art, from app.js), and screened on
   outside the casing's light, which would dim it. It's only there while
   it's needed (its blending would re-render the sticker, a touch softer),
   starting off the sticker, to the left. Tab also rings it in the
   terminal's green: shadows after the casing's light, as an outline would
   be dimmed too. */
a.desk__sticker {
  pointer-events: auto;
}

a.desk__sticker::after {
  position: absolute;
  inset: 0;
  transform: rotate(var(--r));
  background: linear-gradient(
      115deg,
      transparent 40%,
      rgba(150, 205, 255, 0.1) 45%,
      rgba(255, 246, 232, 0.5) 50%,
      rgba(255, 170, 215, 0.1) 55%,
      transparent 60%
    )
    100% 0 / 300% 100% no-repeat;
  -webkit-mask: var(--art) center / 100% 100% no-repeat;
  mask: var(--art) center / 100% 100% no-repeat;
  mix-blend-mode: screen;
  pointer-events: none;
}

a.desk__sticker:hover,
a.desk__sticker:focus-visible {
  --glow: 1.12;
}

a.desk__sticker:hover::after,
a.desk__sticker:focus-visible::after {
  content: "";
  animation: desk-sticker-glint 0.9s cubic-bezier(0.3, 0, 0.2, 1);
}

@keyframes desk-sticker-glint {
  to {
    background-position: 0 0;
  }
}

a.desk__sticker:focus-visible {
  --sticker-edge:
    drop-shadow(1.5px 0 0 var(--accent))
    drop-shadow(-1.5px 0 0 var(--accent))
    drop-shadow(0 1.5px 0 var(--accent))
    drop-shadow(0 -1.5px 0 var(--accent))
    drop-shadow(0 0 0.5px rgba(0, 0, 0, 0.5));
  outline: none;
}

/* Reduced motion: no glint, just the light. */
@media (prefers-reduced-motion: reduce) {
  a.desk__sticker:hover::after,
  a.desk__sticker:focus-visible::after {
    animation: none;
  }
}

/* The room reflected in the glass, over the picture: the photo again, lined
   up with itself (offsets are the glass's box in the photo, as % of the
   glass), screened on so only its light adds — the window, the sunlit desk
   along the bottom. The contrast filter drops the glass's own grey first,
   so the picture's blacks stay deep. Off until the tube has warmed up. */
.monitor__glare {
  position: absolute;
  top: calc(-100% * var(--desk-glass-y) / var(--desk-glass-h));
  left: calc(-100% * var(--desk-glass-x) / var(--desk-glass-w));
  width: calc(100% / var(--desk-glass-w));
  height: calc(100% / var(--desk-glass-h));
  filter: brightness(0.9) contrast(1.8);
  mix-blend-mode: screen;
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 0.6s ease-out;
}

body.crt-powering .monitor__glare {
  opacity: 0;
}

/* The bezel's shadow on the glass's edge: the traced outline, stroked and
   blurred; the clip keeps only its inner half. */
.monitor__rim {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  filter: blur(5px);
  pointer-events: none;
}

.monitor__rim path {
  fill: none;
  stroke: rgba(0, 0, 0, 0.75);
  stroke-width: 12px;
  vector-effect: non-scaling-stroke;
}
