/* ============================================================
   STATIONS MODULE - the three workstation openings.

   Each `.poste-header` (Computer, Camera, Workbench) becomes a full
   viewport panel that js/stations.js pins and drives on scroll
   progress. Two states, one DOM:

   1. AT REST (no JS, no GSAP, prefers-reduced-motion, or a viewport
      too short to hold a scene): nothing here applies. The banner
      keeps the styles.css layout, app.js keeps its on-enter number,
      typewriter and SVG tracing. Everything is visible.

   2. SCENE (html.stn + .poste-header.is-scene, both set by
      js/stations.js before the first frame): the banner is a panel
      exactly one screen tall, composed vertically: giant number as a
      watermark, the blueprint drawing as the hero of the screen, then
      a cartouche band (eyebrow + title | lede + tooling).

   GEOMETRY CONTRACT WITH THE ANCHORS. The panel is 100svh and is
   pinned at `top top`, so it covers the screen edge to edge, the
   sticky topbar included; its own padding-top keeps the content clear
   of the bar. app.js scrollToAnchor() aims at the SECTION box minus
   the topbar offset, so for a nav link to land exactly on progress 0
   the section box must sit one topbar offset BELOW the panel top:

     .stn-poste       margin-top:  +var(--topbar-h)   (and no padding-top)
     .poste-header    margin-top:  -var(--topbar-h)   (moved onto the
                                    pin spacer by ScrollTrigger, which
                                    copies the margins of the pinned
                                    element onto its spacer)

   The two cancel out, so the panel starts exactly where the section
   used to start and the block above keeps its spacing; only the box
   the anchor measures has moved. `display: flow-root` stops the two
   margins from collapsing into each other.

   SINCE THE HEADER WAS REMOVED, --topbar-h is 0 (declared in
   css/shell.css) and scrollToAnchor() subtracts nothing: the two
   margins below are 0, the section box and the panel top are the same
   line, and the contract above holds trivially. The rules are kept as
   they are so the geometry survives if any chrome ever comes back.

   Only transform, opacity, clip-path, stroke-dashoffset and colour
   move. Individual `translate` / `rotate` properties drive the
   printer head and spool so the `transform` based hover
   micro-animations of styles.css keep composing on top.
   ============================================================ */

/* ------------------------------------------------------------
   1. Panel shell
   ------------------------------------------------------------ */

html.stn .poste.stn-poste {
  display: flow-root;
  margin-top: var(--topbar-h, 88px);
  padding-top: 0;
  border-top: 0;
  scroll-margin-top: 0;
}

/* The pin spacer inserted by ScrollTrigger is a direct child of
   `.poste`, so it inherits `.poste > *` (capped at --maxw, padded).
   The panel must go edge to edge. Scoped to OUR spacers only: the
   terminal has its own spacer in the same section and relies on the
   styles.css width. `:has` applies from the first frame; the class is
   set by the module for engines without it. */
html.stn .poste > div.pin-spacer:has(> .poste-header.is-scene),
html.stn .poste > div.stn-spacer {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
}

html.stn .poste-header.is-scene {
  --stn-pad-x: clamp(1.25rem, 4vw, 3rem);
  --stn-pad-top: calc(var(--topbar-h, 88px) + clamp(0.25rem, 1.4vh, 1.25rem));

  position: relative;
  z-index: 1;
  box-sizing: border-box;
  max-width: none;
  width: 100%;
  margin: calc(-1 * var(--topbar-h, 88px)) 0 0;
  padding: var(--stn-pad-top) var(--stn-pad-x) clamp(1.1rem, 3.2vh, 2.6rem);

  /* A minimum of one screen, never less: the next block must not peek
     at the bottom edge while the scene is at rest. It may grow past a
     screen if the composition needs it (very narrow phones, very long
     chips); js/stations.js then plays the scene without pinning it, so
     nothing is ever held off screen. */
  min-height: 100vh;
  min-height: 100svh;

  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(1rem, 3.6vh, 3rem);
  overflow: hidden;

  /* Opaque: the section's own `::before` wash would otherwise drift
     behind the pinned panel. The panel paints its own, on progress. */
  background: var(--paper);
}

/* Beat (a): the accent wash settles. */
html.stn .poste-header.is-scene::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: var(--stn-wash, 0);
  background:
    radial-gradient(115% 72% at 50% 6%,
      color-mix(in srgb, var(--poste-accent) 11%, transparent), transparent 68%);
}

/* Until the module has armed the scene (one frame after load), the
   pieces it drives stay hidden: no flash of a finished banner. */
html.stn .poste-header.is-scene:not(.stn-armed) .poste-figure,
html.stn .poste-header.is-scene:not(.stn-armed) .poste-mark,
html.stn .poste-header.is-scene:not(.stn-armed) .poste-intro > * {
  opacity: 0;
}

/* ------------------------------------------------------------
   2. Giant number, watermark of the figure row
   ------------------------------------------------------------ */

/* PHASE 1 of the scene belongs to this number alone, so it is not a
   watermark yet: `--stn-num` is 1 while it holds the screen and
   js/stations.js takes it to 0 as the drawing is laid over it. The
   colour is the only thing that changes, so nothing here can reflow. */
html.stn .poste-header.is-scene .poste-mark {
  position: relative;
  inset: auto;
  grid-row: 1;
  grid-column: 1;
  justify-self: center;
  align-self: center;
  z-index: 0;

  /* Zero height so the watermark never sizes the row: the text is
     centred on its own box and overflows symmetrically. */
  height: 0;
  display: grid;
  place-content: center;
  overflow: visible;

  font-size: min(58vh, 42vw);
  line-height: 0.78;
  letter-spacing: -0.045em;
  color: color-mix(in srgb,
    var(--poste-accent) calc(10% + var(--stn-num, 1) * 32%), transparent);
  transform: none;
}

/* color-mix with a calc() percentage is not universal; the plain
   watermark is the honest fallback (the scene then opens on the number
   it has in the finished panel, a little faint but alone). */
@supports not (color: color-mix(in srgb, red calc(10% + 1 * 32%), transparent)) {
  html.stn .poste-header.is-scene .poste-mark {
    color: color-mix(in srgb, var(--poste-accent) 18%, transparent);
  }
}

/* ------------------------------------------------------------
   3. The drawing, hero of the scene
   ------------------------------------------------------------ */

html.stn .poste-header.is-scene .poste-figure {
  grid-row: 1;
  grid-column: 1;
  order: 0;
  z-index: 1;
  position: relative;
  width: min(100%, 84rem);
  max-width: none;
  /* Stretched by the 1fr row, not by a percentage height: the panel is
     sized by `min-height`, so its block size is indefinite and a
     percentage height on a child would collapse to auto (the drawing
     would then size itself from its own width and blow the panel past
     one screen). The row gives the box its height, the SVG is laid out
     inside it absolutely. */
  align-self: stretch;
  height: auto;
  /* The 1fr row may not squeeze the drawing away: below this the panel
     grows past one screen instead. */
  min-height: clamp(150px, 26svh, 420px);
  margin-inline: auto;
  justify-self: center;
}

/* viewBox + the default preserveAspectRatio do the "contain" work:
   the drawing fills the box and stays centred at any panel height. */
html.stn .poste-header.is-scene .poste-figure > svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  max-height: none;
}

/* PHASE 2: the finished drawing, a stripped clone (no ids, no markers,
   no defs) built by the module. It is nothing at all while the number
   holds the screen, then it is laid over the number out of blur, then
   the real line inks it in and it decays to nothing. Opacity and blur
   are written by js/stations.js as one continuous function of scroll
   progress. */
html.stn .poste-header.is-scene .stn-ghost {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
}
html.stn .poste-header.is-scene .stn-ghost:not(.is-spent) {
  will-change: opacity, filter;
}
html.stn .poste-header.is-scene .stn-ghost .draw {
  stroke-dasharray: none !important;
  stroke-dashoffset: 0 !important;
}
html.stn .poste-header.is-scene .stn-ghost.is-spent {
  visibility: hidden;
  filter: none;
}

/* Shutter flash of the Camera scene: same element and keyframes as the
   original contact-sheet trigger (styles.css §7), fired once when the
   scene crosses its last beat. */
html.stn .poste-header.is-scene > .shutter-flash {
  z-index: 6;
}

/* Signature loops, driven by a class so the `transform` based hover
   micro-animations of styles.css keep working untouched. */
html.stn .poste-svg.is-live #pc-caret-b { animation: pc-blink 1s steps(1) infinite; }
/* The hover blink of styles.css is an animation, and an animation beats the
   inline opacity the scene writes: with the pointer over the figure the caret
   blinked on an empty page during the approach. */
html.stn .poste-header.is-scene .poste-svg:not(.is-live) #pc-caret-b { animation: none !important; }
/* The blink is the sign of life of the scene that is LIVE. During the push
   into the screen and during the pull back out the page is otherwise
   perfectly still, and a caret going on and off on a motionless frame is the
   only thing moving: it reads as a fault, not as a machine waiting. It picks
   up again as soon as the drawing is the scene again. */
html.term-zoom .poste-svg.is-live #pc-caret-b,
html.term-dark .poste-svg.is-live #pc-caret-b { animation: none; }
html.stn .poste-svg.is-live .obj-rec-b { animation: rec-blink 1.1s steps(1) infinite; }

/* Computer signature: the laptop screen lights up in the ink of the
   terminal that follows in the same section. One custom property,
   mixed here, drives the three colours; js/stations.js only tweens the
   number, so a ScrollTrigger refresh has nothing to re-record. */
html.stn .poste-svg-pc { --stn-lit: 0; }
html.stn .poste-svg-pc #pc-code-b {
  stroke: color-mix(in srgb, #a9c6d9 calc(var(--stn-lit) * 100%), var(--poste-accent));
}
html.stn .poste-svg-pc #pc-caret-b {
  stroke: color-mix(in srgb, #c79b56 calc(var(--stn-lit) * 100%), var(--poste-accent));
}
html.stn .poste-svg-pc #pc-screen-b g[fill="currentColor"] {
  fill: color-mix(in srgb, #a9c6d9 calc(var(--stn-lit) * 100%), var(--poste-accent));
}

/* Printer: the head slides and the spool turns on individual transform
   properties, which compose with the hover `transform` instead of
   fighting it. */
html.stn .poste-svg-printer #printer-head-b { translate: var(--stn-head, 0px) 0; }
html.stn .poste-svg-printer #printer-spool-b { rotate: var(--stn-spool, 0deg); }

/* ------------------------------------------------------------
   4. Cartouche band (eyebrow + title | lede + tooling)
   ------------------------------------------------------------ */

html.stn .poste-header.is-scene .poste-intro {
  grid-row: 2;
  grid-column: 1;
  order: 0;
  z-index: 2;
  position: relative;
  width: min(100%, var(--maxw));
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  column-gap: clamp(1.5rem, 4vw, 3.5rem);
  row-gap: clamp(0.5rem, 1.6vh, 1.1rem);
  align-items: start;
  padding-top: clamp(0.9rem, 2.2vh, 1.7rem);
  /* The sheet rule arrives with the cartouche, deep in phase 3: before
     that the panel is paper, the giant number, then the drawing. */
  border-top: 1px solid
    color-mix(in srgb, var(--line) calc(var(--stn-rule, 0) * 100%), transparent);
}

/* Accent segment that opens the cartouche, same language as the
   sub-station heads of styles.css. Grows with beat (c). */
html.stn .poste-header.is-scene .poste-intro::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 4.5rem;
  height: 3px;
  background: var(--poste-accent);
  transform: scaleX(var(--stn-rule, 0));
  transform-origin: left center;
}

html.stn .poste-header.is-scene .poste-eyebrow { grid-column: 1; grid-row: 1; margin-bottom: 0; }
html.stn .poste-header.is-scene .poste-intro h2 { grid-column: 1; grid-row: 2; margin: 0; }
html.stn .poste-header.is-scene .poste-lede { grid-column: 2; grid-row: 1; margin: 0; }
html.stn .poste-header.is-scene .poste-tooling { grid-column: 2; grid-row: 2; }

html.stn .poste-header.is-scene .poste-intro h2 {
  font-size: clamp(2.2rem, 4.4vw, 3.4rem);
}

html.stn .poste-header.is-scene .poste-lede {
  font-size: clamp(1rem, 1.3vw, 1.15rem);
  max-width: 46em;
}

html.stn .poste-header.is-scene .poste-tooling {
  border-top: 0;
  padding-top: 0;
}

/* A chip whose typing has not started yet keeps its width (the ghost
   reserves it) but must not show as an empty outline. */
html.stn .poste-header.is-scene .poste-typed.is-pending { opacity: 0; }

html.stn .poste-header.is-scene .poste-eyebrow,
html.stn .poste-header.is-scene .poste-lede {
  will-change: clip-path, opacity, transform;
}

/* The title arrives word by word (BRIEF-MOTION §2: stagger 0.16,
   blur to focus). The spans are built by the module; without it the
   heading is a plain text node. */
html.stn .poste-header.is-scene .poste-intro h2 .stn-word {
  display: inline-block;
  will-change: transform, opacity, filter;
}

/* ------------------------------------------------------------
   5. Composition for short and narrow viewports
   ------------------------------------------------------------ */

/* Phones and narrow tablets, portrait: one column, drawing on top. */
@media (max-width: 900px) {
  html.stn .poste-header.is-scene {
    gap: clamp(0.6rem, 2vh, 1.4rem);
    /* Content-sized rows centred as one block, rather than a drawing
       floating in a stretched row: in portrait the drawing is limited
       by the width, so a 1fr row would leave a dead band above and
       below it. */
    grid-template-rows: auto auto;
    align-content: center;
  }
  html.stn .poste-header.is-scene .poste-intro {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0.5rem;
  }
  html.stn .poste-header.is-scene .poste-eyebrow,
  html.stn .poste-header.is-scene .poste-intro h2,
  html.stn .poste-header.is-scene .poste-lede,
  html.stn .poste-header.is-scene .poste-tooling {
    grid-column: 1;
    grid-row: auto;
  }
  html.stn .poste-header.is-scene .poste-figure {
    width: 100%;
    max-width: 36rem;
    /* The box is the drawing, so there is nothing to letterbox. */
    aspect-ratio: 4 / 3;
    align-self: center;
    height: auto;
    min-height: 0;
    max-height: 40svh;
  }
  html.stn .poste-header.is-scene .poste-mark {
    font-size: min(34vh, 56vw);
  }
}

@media (max-width: 640px) {
  html.stn .poste-header.is-scene {
    padding-bottom: clamp(0.9rem, 2.6vh, 1.8rem);
  }
  html.stn .poste-header.is-scene .poste-intro h2 {
    font-size: clamp(1.85rem, 8.4vw, 2.4rem);
  }
  html.stn .poste-header.is-scene .poste-lede {
    font-size: 0.92rem;
    line-height: 1.42;
  }
  html.stn .poste-header.is-scene .poste-tooling-label {
    font-size: 0.62rem;
    margin-bottom: 0.45rem;
  }
  html.stn .poste-header.is-scene .poste-tooling li {
    font-size: 0.68rem;
    padding: 0.24rem 0.62rem;
  }
  html.stn .poste-header.is-scene .poste-tooling ul {
    gap: 0.35rem 0.4rem;
  }
}

/* Compression pass. js/stations.js sets this when the measured panel
   would grow past one screen, so the scene stays pinnable instead of
   holding its own foot off screen. */
html.stn .poste-header.is-scene.stn-tight {
  gap: 0.5rem;
  padding-bottom: 0.7rem;
}
html.stn .poste-header.is-scene.stn-tight .poste-figure {
  min-height: 110px;
  max-height: 26svh;
}
html.stn .poste-header.is-scene.stn-tight .poste-intro {
  padding-top: 0.6rem;
  row-gap: 0.3rem;
}
html.stn .poste-header.is-scene.stn-tight .poste-intro h2 {
  font-size: clamp(1.6rem, 6.4vw, 2rem);
}
html.stn .poste-header.is-scene.stn-tight .poste-lede {
  font-size: 0.85rem;
  line-height: 1.38;
}
html.stn .poste-header.is-scene.stn-tight .poste-tooling li {
  font-size: 0.63rem;
  padding: 0.2rem 0.5rem;
}

/* Very narrow phones: the five chips must still fit under the lede. */
@media (max-width: 380px) {
  html.stn .poste-header.is-scene .poste-lede { font-size: 0.88rem; }
  html.stn .poste-header.is-scene .poste-tooling li { font-size: 0.65rem; }
}

/* Landscape phones: too short to stack. Two columns, small drawing,
   and the module shortens the scroll budget to match. */
@media (max-height: 620px) and (orientation: landscape) {
  html.stn .poste-header.is-scene {
    --stn-pad-top: calc(var(--topbar-h, 88px) + 0.2rem);
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
    grid-template-rows: minmax(0, 1fr);
    column-gap: clamp(1rem, 3vw, 2rem);
    padding-bottom: clamp(0.5rem, 2.4vh, 1rem);
  }
  html.stn .poste-header.is-scene .poste-figure {
    grid-row: 1;
    grid-column: 1;
    max-width: none;
  }
  html.stn .poste-header.is-scene .poste-mark {
    grid-row: 1;
    grid-column: 1;
    font-size: min(52vh, 26vw);
  }
  html.stn .poste-header.is-scene .poste-intro {
    grid-row: 1;
    grid-column: 2;
    grid-template-columns: minmax(0, 1fr);
    align-content: center;
    align-self: center;
    border-top: 0;
    padding-top: 0;
    row-gap: 0.3rem;
  }
  html.stn .poste-header.is-scene .poste-intro::before { display: none; }
  html.stn .poste-header.is-scene .poste-eyebrow,
  html.stn .poste-header.is-scene .poste-intro h2,
  html.stn .poste-header.is-scene .poste-lede,
  html.stn .poste-header.is-scene .poste-tooling {
    grid-column: 1;
    grid-row: auto;
  }
  html.stn .poste-header.is-scene .poste-intro h2 { font-size: clamp(1.5rem, 5.4vh, 2.1rem); }
  html.stn .poste-header.is-scene .poste-lede { font-size: 0.84rem; line-height: 1.38; }
  html.stn .poste-header.is-scene .poste-tooling-label { margin-bottom: 0.35rem; }
  html.stn .poste-header.is-scene .poste-tooling li { font-size: 0.63rem; padding: 0.18rem 0.5rem; }
}

/* ------------------------------------------------------------
   6. Fallbacks
   ------------------------------------------------------------
   The module never sets `html.stn` under prefers-reduced-motion, with
   GSAP missing or with no JS at all, so none of the rules above apply
   and styles.css keeps the banner in normal flow, fully visible. The
   rules below are the belt and braces for a reduce setting that lands
   after the scene was armed (an OS switch mid-session). */

@media (prefers-reduced-motion: reduce) {
  html.stn .poste-header.is-scene .stn-ghost { display: none; }
  html.stn .poste-svg.is-live #pc-caret-b,
  html.stn .poste-svg.is-live .obj-rec-b { animation: none; }
  html.stn .poste-header.is-scene > .shutter-flash { display: none !important; }
}

/* ------------------------------------------------------------
   3bis. Short screens: the band gives its room back to the drawing

   The drawing is 4:3 and takes the height the band leaves it, so on a
   laptop screen (1600x780, say) it came out small between two empty
   margins while the band spread over four lines. Under 900px of height
   the band becomes compact: the tooling chips sit on one scrolling-free
   row, the lede keeps its measure, and the room saved goes to the 1fr
   row above. Felix: "before the PC was big, now it is small".
   ------------------------------------------------------------ */
@media (min-width: 900px) and (max-height: 900px) {
  html.stn .poste-header.is-scene {
    gap: clamp(0.6rem, 1.6vh, 1.2rem);
    padding-bottom: clamp(0.8rem, 2vh, 1.6rem);
  }

  html.stn .poste-header.is-scene .poste-intro {
    align-items: start;
  }

  html.stn .poste-header.is-scene .poste-lede {
    font-size: clamp(0.95rem, 1.6vh, 1.05rem);
    line-height: 1.45;
  }

  html.stn .poste-header.is-scene .poste-tooling {
    margin-top: clamp(0.4rem, 1.1vh, 0.8rem);
  }

  html.stn .poste-header.is-scene .poste-tooling ul {
    gap: 0.35rem 0.4rem;
  }

  html.stn .poste-header.is-scene .poste-typed {
    padding-block: 0.22rem;
    padding-inline: 0.6rem;
    font-size: 0.68rem;
  }

  /* The bench label is the one line the scene can do without here: the
     chips say what the bench is. */
  html.stn .poste-header.is-scene .poste-tooling-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  html.stn .poste-header.is-scene h2 {
    font-size: clamp(2.2rem, 5.6vh, 3.2rem);
    line-height: 1.02;
  }
}
