/* os/desktop.css — the surface the windows sit on.
 *
 * The previous desktop was a printed page wearing a desktop metaphor: a framed
 * rectangle floating in a gradient, ten icons centred in a five-column grid, and a
 * mouse cursor drawn in SVG. That reads as a poster about a computer. This file
 * replaces the poster with the surface — a screen that runs to its edges, icons
 * that live in the top-left corner the way icons do on every desktop ever
 * shipped, and one place where the stacking order of the whole OS is written down.
 *
 * The illustration survives. The wallpaper is still violet hills under drifting
 * clouds; the icons are still hand-drawn objects with drop shadows; the labels
 * are still the site's own serif. What changes is that the geometry is now a
 * screen's geometry rather than a page's: full-bleed, corner-anchored, and
 * clipped at the bezel.
 *
 * ── contract (site-v4/CONTRACTS.md) ─────────────────────────────────────────
 *   .os      the OS root, and the stacking context every z-index is scoped to
 *   .frame   the screen: wallpaper, icon layer, system note
 *   .grid    the icon layer; .desktop is the desktop module's own hook
 *   .icon    one desktop icon: .icon__art .icon__loop .icon__label
 *            (.icon__name, .icon__trade), states .is-in .is-selected
 *            .is-dragging .is-drop .is-open, accent as an inline --accent
 *
 *   This file owns presentation. It owns NO marquee, drag or reveal behaviour —
 *   those belong to the desktop module's own sheet, and two rules for one
 *   selection rectangle is exactly the duplicated-truth bug the contract exists to
 *   prevent.
 *
 *   Load order: this file, then wm.css, then chrome.css.
 * ────────────────────────────────────────────────────────────────────────── */


/* ══ the stacking scale ═══════════════════════════════════════════════════
 * One table for the whole OS, in one place, so no two files have to agree on a
 * magic number. Numbers are the ones pinned in CONTRACTS.md: windows sit in
 * 10–100 so ten windows can each have a distinct layer, and every chrome surface
 * sits in the hundreds where it cannot be interleaved with a window by accident. */

.os {
  --z-win: 10;             /* an unfocused window */
  --z-win-focus: 90;       /* the focused window, near the top of the window band */
  --z-chrome: 500;         /* taskbar + tray */
  --z-peek: 600;           /* hover preview */
  --z-mobile-switch: 700;
  --z-search: 700;
  --z-start: 800;
  --z-menu: 900;           /* context menu */
  --z-dialog: 950;         /* case study and any modal above a context menu */
}


/* ══ the screen ══════════════════════════════════════════════════════════════
 * position:relative + isolation makes .os a stacking context, so a window at
 * z-index 90 cannot escape above the taskbar's 500 no matter what the window
 * manager feels like writing. That containment is the whole reason the scale can
 * be small numbers.
 *
 * The screen fills the viewport rather than sitting in the middle of the page as
 * a card. The previous shell centred the frame with page padding and called the
 * result a desktop; it was a poster with a wallpaper printed on it. */

.os {
  position: relative;
  isolation: isolate;
  /* the screen never scrolls: the icons and the windows are arranged inside it,
     and a scrollbar on a desktop is a page asking to be a page again */
  overflow: clip;
  display: block;
  padding: 0;
  /* the viewport minus the site header above it. A fixed height breaks the moment
     the header wraps at a narrow width, so this is a floor. */
  min-height: calc(100dvh - 6rem);
  /* a violet floor behind the bezel, so a window dragged to the very edge still
     has the OS behind it rather than the body background */
  background: #3A2F63;
}

.frame {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-height: calc(100dvh - 6rem);
  /* the bezel. A screen has an edge, and the edge is what tells you the icons and
     the windows are inside something rather than on the page behind it. */
  border: 2px solid var(--ink, #17151F);
  border-radius: 16px;
  background: var(--ink, #17151F);
  box-shadow: 0 24px 60px -30px rgba(23, 21, 31, .7);
  overflow: clip;
}

/* While a window is open the screen recedes — brightness, not opacity, so the
   wallpaper's colour survives and the desktop stays legible underneath. */
.frame.is-behind { filter: brightness(.86) saturate(.92); }


/* ══ wallpaper ══════════════════════════════════════════════════════════════
 * Full-bleed to the bezel. --accent is written onto this element by the theme
 * module, so the sky answers the window sitting on it. */

.wallpaper {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(120% 92% at 50% 4%, #F3EBFF 0%, #E2D7FB 30%, #CDBCEC 62%, #B3A3DC 100%);
  transition: background .5s ease;
}

/* The accent, laid over the sky at low opacity. The layer is unconditional and
   its colour defaults to transparent, so the default sky is not darkened by an
   absent accent — which is what a `[style*=--accent]` guard would have to
   re-check on every repaint. */
.wallpaper::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(90% 70% at 50% 0%, var(--accent, transparent) 0%, transparent 68%);
  opacity: .22;
  transition: opacity .5s ease;
}

/* The hills are a horizon, not a floor. Two arcs together occupy the lower third
   and leave the top two thirds as sky; stacked any higher they read as a dark
   slab the icons and the taskbar are sitting on top of rather than a landscape
   behind them. */
.wall__hill {
  position: absolute;
  left: -6%;
  width: 112%;
  border-radius: 50% 50% 0 0 / 100% 100% 0 0;
}
.wall__hill--far  { height: 30%; bottom: 12%; opacity: .3;  background: #6A5A9E; }
.wall__hill--near { height: 17%; bottom: 0;  opacity: .34; background: #3A2F63; }

.wall__spark {
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #FFF3D6;
  box-shadow: 0 0 0 3px rgba(255, 243, 214, .28);
  animation: wall-pulse 3.4s ease-in-out infinite;
}
.wall__spark--1 { top: 16%; left: 22%; }
.wall__spark--2 { top: 34%; right: 18%; animation-delay: 1.4s; }
.wall__spark--3 { top: 58%; left: 62%; animation-delay: 2.7s; }
@keyframes wall-pulse {
  50% { opacity: .25; scale: .7; }
}

/* Clouds drift behind the screen, as they did — clipped to the desktop root now
   rather than floating over the page margins. */
.os__clouds {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: clip;
}
.cloud {
  position: absolute;
  display: block;
  width: 220px;
  height: 54px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .5);
}
.cloud::before, .cloud::after {
  content: "";
  position: absolute;
  border-radius: 999px;
  background: inherit;
}
.cloud::before { width: 110px; height: 74px; left: 26px; bottom: 0; }
.cloud::after  { width: 78px;  height: 58px; right: 24px; bottom: 0; }
.cloud--1 { top: 12%; left: -60px;  animation: cloud-drift 46s linear infinite; }
.cloud--2 { top: 38%; right: -80px; scale: .72; animation: cloud-drift 62s linear infinite reverse; }
.cloud--3 { bottom: 14%; left: 12%; scale: .55; animation: cloud-drift 78s linear infinite; }
@keyframes cloud-drift { to { translate: 62vw 0; } }


/* ══ the icon layer ════════════════════════════════════════════════════════
 * The icon block lives in the top-left corner and is a FIXED width, not a
 * fraction of the screen. Ten icons filling 1440px in one row is a toolbar; ten
 * icons spread across twelve auto-columns is wallpaper with confetti on it. A
 * desktop shows a compact block in one corner and leaves the rest of the screen
 * for windows — so the track size is fixed and the block wraps downward. */

.grid {
  position: relative;
  z-index: 2;
  flex: 1;
  min-height: 0;
  margin: 0;
  padding: clamp(.6rem, 1.6vh, 1.1rem);
  list-style: none;
  display: grid;
  /* A FIXED track, and a block of bounded width. Fixed because an icon grid
     whose icons change size with the screen is not a grid; bounded because
     `auto-fill` alone resolves against the FULL width of the screen — measured
     at 1440px it produced eleven columns and a single row of ten icons, which is
     a toolbar with the rest of the wallpaper empty. The cap is what makes it a
     block in one corner, and it is in rem so it does not need a media query at
     every width between here and a phone. */
  grid-template-columns: repeat(auto-fill, 108px);
  grid-auto-rows: min-content;
  max-width: 31rem;
  justify-content: start;
  align-content: start;
  gap: clamp(.3rem, .8vh, .6rem) clamp(.25rem, .7vw, .55rem);
}

.slot {
  display: grid;
  place-items: start center;
  min-width: 0;
}

/* An icon is a fixed object: the art on top, two lines of label under it, and a
   hit area the size of both. Fixed rather than fluid, because an icon grid whose
   icons change size with the screen is not a grid. */
.icon {
  position: relative;
  width: 100%;
  max-width: 108px;
  padding: .3rem .15rem .25rem;
  border: 0;
  border-radius: 10px;
  background: none;
  display: grid;
  justify-items: center;
  gap: .3rem;
  font: inherit;
  color: inherit;
  cursor: default;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.icon__art {
  position: relative;
  display: block;
  width: 56px;
  aspect-ratio: 96 / 112;
  filter: drop-shadow(1px 3px 0 rgba(23, 21, 31, .28));
  transition: scale .18s cubic-bezier(.2, .9, .25, 1);
}
.icon__art svg { width: 100%; height: 100%; display: block; }

/* Selection. A drawn loop, not a filled box: the same hand-cut outline the icon
   art already uses, in the same cream, so "selected" belongs to this illustration
   rather than being borrowed from another operating system. */
.icon__loop {
  position: absolute;
  inset: -8px -6px 44% -6px;
  border: 2.5px solid transparent;
  border-radius: 46% 54% 50% 50% / 52% 46% 54% 48%;
  rotate: -4deg;
  pointer-events: none;
  transition: border-color .16s ease, scale .16s ease, background-color .16s ease;
}
.icon.is-selected .icon__loop {
  border-color: #FFF3D6;
  background: rgba(255, 243, 214, .16);
  scale: 1.05;
}

/* A window is open for this world — the small underline every desktop draws under
   a running application. .is-open is the state the desktop module sets. */
.icon.is-open::after {
  content: "";
  position: absolute;
  bottom: -.1rem;
  left: 50%;
  translate: -50% 0;
  width: 18px;
  height: 3px;
  border-radius: 2px;
  background: #FFF3D6;
}

.icon__label {
  display: grid;
  text-align: center;
  max-width: 100%;
  /* A label plate, not a text-shadow. The wallpaper is a four-stop gradient
     running from near-white at the top of the sky to plum at the bottom, and
     cream serif at .74rem is unreadable over the pale end of it however tight a
     shadow is put under it. A translucent ink plate holds at both ends, and it is
     what every desktop has used for exactly this reason. */
  padding: .1rem .3rem .12rem;
  border-radius: 5px;
  background: rgba(23, 21, 31, .58);
}
.icon__name {
  font-family: var(--serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif);
  font-size: .74rem;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.01em;
  color: #FFF8EA;
  overflow-wrap: anywhere;
}
.icon__trade {
  font-size: .6rem;
  line-height: 1.2;
  color: rgba(255, 248, 234, .82);
  overflow-wrap: anywhere;
}

@media (hover: hover) {
  .icon:hover .icon__art { scale: 1.07; }
  .icon:hover .icon__loop { border-color: rgba(255, 243, 214, .55); }
}

/* Icon focus. 3px solid per the contract, drawn around the whole icon — art and
   label together — because a ring around the art alone leaves the thing the
   focus is actually about outside it. */
.icon:focus-visible {
  outline: 3px solid #FFF3D6;
  outline-offset: 3px;
  border-radius: 10px;
}
.icon:focus-visible .icon__loop { border-color: #FFF3D6; }

/* The entrance: each icon drops in with a slightly different arrival. The desktop
   module sets .is-in; before it lands the icon is invisible rather than sitting
   at zero opacity on top of the ones that already have. */
.icon:not(.is-in) { opacity: 0; translate: 0 -12px; }
.icon.is-in {
  opacity: 1;
  translate: 0 0;
  transition:
    opacity .5s ease,
    translate .5s cubic-bezier(.2, .9, .25, 1),
    scale .18s cubic-bezier(.2, .9, .25, 1);
  transition-delay: calc(var(--i, 0) * 55ms);
}

/* Being dragged, and the drop it landed in. */
.icon.is-dragging { opacity: .5; scale: .94; }
.icon.is-drop { scale: 1.08; }


/* ══ system note ════════════════════════════════════════════════════════════
 * A plate pinned near the screen's bottom-left, where a status line lives on a
 * real desktop, rather than a sticker floating in the middle of the wallpaper. */

.note {
  position: relative;
  z-index: 2;
  flex: none;
  align-self: flex-start;
  margin: 0 0 .6rem clamp(.6rem, 1.6vh, 1.1rem);
  padding: .3rem .65rem;
  border: 1.5px solid var(--ink, #17151F);
  border-radius: 6px;
  background: #FFF3D6;
  color: var(--ink, #17151F);
  font-family: var(--mono, ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace);
  font-size: .66rem;
  line-height: 1.35;
}
.note b { font-weight: 700; }

/* A file is over the desktop. The inset ring is the whole message: accept it
   here. */
.os.is-dropping .frame {
  box-shadow:
    inset 0 0 0 4px var(--accent, var(--violet, #6D5BD0)),
    0 24px 60px -30px rgba(23, 21, 31, .7);
}
.os.is-dropping .wallpaper { filter: brightness(1.08); }


/* ══ 390 ═══════════════════════════════════════════════════════════════════
 * A phone has no room for a bezel and no use for a wallpaper you can see past.
 * The screen becomes the page: no frame, no radius, full-bleed, and the icon
 * block tightens to two columns of 84px so all ten stay reachable. */

@media (max-width: 760px) {
  .frame {
    border: 0;
    border-radius: 0;
    box-shadow: none;
    min-height: calc(100dvh - 6rem);
  }
  .grid {
    grid-template-columns: repeat(2, minmax(0, 84px));
    justify-content: start;
    gap: .4rem .3rem;
    padding: .5rem .6rem;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .icon { max-width: 84px; }
  .icon__art { width: 46px; }
  .icon__name { font-size: .72rem; }
  .icon__trade { font-size: .58rem; }
  .note { font-size: .62rem; }
  /* no hover on a phone — the loop is what marks a tap */
  .icon:active .icon__loop { border-color: rgba(255, 243, 214, .8); }
  .icon:active .icon__art { scale: 1.05; }
}

@media (max-width: 760px) and (max-height: 560px) {
  .grid { grid-template-columns: repeat(3, minmax(0, 76px)); }
  .icon__art { width: 40px; }
  .icon__trade { display: none; }
}


/* ══ reduced motion ═════════════════════════════════════════════════════════
 * The wallpaper keeps its colour and loses its movement: no drift, no pulse, no
 * icon arrival. The scene is still a scene when nothing in it moves.
 *
 * Correctness must not depend on JS setting an attribute, so the media query is
 * the authority and the data attribute is honoured as a mirror. */

@media (prefers-reduced-motion: reduce) {
  .cloud, .wall__spark, .icon__art { animation: none !important; }
  .icon, .icon__art, .icon__loop, .wallpaper, .wallpaper::after, .frame { transition: none !important; }
  .wall__spark { opacity: .8; }
  .icon:not(.is-in) { opacity: 1; translate: none; }
  .icon.is-in { transition-delay: 0ms; }
  .frame.is-behind { filter: none; }
}

[data-reduce-motion] .cloud,
[data-reduce-motion] .wall__spark,
[data-reduce-motion] .icon__art { animation: none !important; }
[data-reduce-motion] .icon,
[data-reduce-motion] .icon__art,
[data-reduce-motion] .icon__loop,
[data-reduce-motion] .wallpaper,
[data-reduce-motion] .wallpaper::after,
[data-reduce-motion] .frame { transition: none !important; }
[data-reduce-motion] .icon:not(.is-in) { opacity: 1; translate: none; }
[data-reduce-motion] .frame.is-behind { filter: none; }


/* ══ forced colours ═════════════════════════════════════════════════════════
 * The wallpaper, the icon shadows and the ink labels are all colour. Under a
 * forced palette every one of them collapses to the system background, so the
 * surface is given its own background and the labels a plate — otherwise ten
   icons float on a void with unreadable names. */

@media (forced-colors: active) {
  .frame { border-color: CanvasText; background: Canvas; }
  .os { background: Canvas; }
  .wallpaper { background: Canvas; }
  .wallpaper::after { display: none; }
  .wall__hill, .wall__spark, .cloud { display: none; }
  .icon__label { background: Canvas; border: 1px solid CanvasText; }
  .icon__name, .icon__trade { color: CanvasText; }
  .icon__loop { border-color: Highlight; }
  .icon.is-selected .icon__loop { background: Highlight; }
  .icon.is-open::after { background: Highlight; }
  .note { border-color: CanvasText; background: Canvas; color: CanvasText; }
}