/* ============================================================================
   shackleton · the page's own layer

   scrollcraft.css is the floor and the mechanism. Nothing here restyles
   .sc-world, .sc-world__seg, .sc-world__copy or any [data-sc-*] selector: the
   theme is six colours and two families, and everything else is this page's own
   markup.
   ========================================================================== */

:root {
  /* --- the six roles. One continuous grade for the whole travel, because the
         continuous-world grammar forbids drift stops: the ground change is
         authored into the film, not interpolated between legs. ------------- */
  --sc-canvas:     #0B0C10;   /* cold shadow black. Never #000. */
  --sc-surface:    #15171C;
  --sc-ink:        #F1EDE4;   /* bone, warm: the colour of a sunlit suit */
  --sc-ink-soft:   #A39C90;   /* tinted from the ink, not flat grey */
  --sc-accent:     #E8672C;   /* hazard orange: what a working colony paints on
                                 things. The one colour here that is not a
                                 natural colour of the place. */
  --sc-accent-ink: #100C08;

  --sc-font-display: "Newsreader", Georgia, serif;
  --sc-font-text:    "Geist", system-ui, sans-serif;
  --sc-font-mono:    "Geist Mono", ui-monospace, monospace;

  /* One radius for the page, and it is nothing. A bolted plate has corners. */
  --sc-r-sm: 0; --sc-r-md: 0; --sc-r-lg: 0; --sc-r-pill: 0;

  /* --- the sun. page.js writes these every frame; the values here are the
         state at the top of the page, so the first paint is already correct
         even before script runs. ------------------------------------------ */
  --sun-i: 1;        /* direct sunlight, 1 on the rim, 0 below it */
  --earth-i: 0;      /* earthlight, the inverse, arriving over the descent */
  --sun-skew: 15deg; /* which way a shadow leans */
  --sun-len: 1.4;    /* how many plate-heights long it is */
  --pr: 0;           /* position along the whole track */

  /* the plate, lit by whatever light there is */
  --plate: color-mix(in oklab, #F4EFE3, #93A3BA calc(var(--earth-i) * 62%));
  --plate-ink: #14110C;
  /* Secondary ink is tinted to whatever light there is, and that is not only a
     taste rule here, it is a contrast one. #5B5347 is warm and correct on the
     sunlit plate at 6.6:1, and it falls to 4.12:1 once the plate goes cold at
     the descent, which is under the floor for the labels, the footnotes and the
     colophon. The harness cannot see it: it grades the [data-sc-copy] element's
     own colour, which is --plate-ink at 16:1, not its children's. Measured, not
     eyeballed: warm end 6.60:1, cold end 5.68:1. */
  --plate-ink-soft: color-mix(in oklab, #5B5347, #3C4046 calc(var(--earth-i) * 100%));
  /* The accent on a plate is the documented two-stop escape from taste.md: ONE
     hue, two lightnesses, keyed to the ground. --sc-accent is #E8672C and it is
     right on the canvas at 5.96:1, but on bone it is 2.86:1 and on the cold
     plate 1.78:1, so the CTA's own indicator would vanish exactly where the CTA
     lives. This pair reads 4.32:1 warm and 3.51:1 cold, both clear of the 3:1
     floor for a control. Still one hue for the whole page. */
  --accent-on-plate: color-mix(in oklab, #C2481A, #A63B10 calc(var(--earth-i) * 100%));
  --plate-rule: color-mix(in oklab, var(--plate-ink) 16%, transparent);

  --folio-w: 132px;
}

/* ------------------------------------------------------------- the folio --
   Nav, position readout and map in one object in the margin. The grammar wants
   a map and it wants it clickable, because a world you cannot skip around in is
   a video. Light type on a dark ground, so it takes the three-axis
   compensation: more leading, more tracking, one step more weight. */
.folio {
  position: absolute;
  left: var(--sc-gutter); top: 50%;
  transform: translateY(-50%);
  width: var(--folio-w);
  pointer-events: auto;           /* the copy layer above is inert by default */
  font-family: var(--sc-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  line-height: 1.5;
  font-weight: 500;
  color: color-mix(in oklab, var(--sc-ink) 78%, transparent);
  display: grid;
  grid-template-columns: 44px 1fr;
  grid-template-areas: "title title" "profile legs" "read read";
  column-gap: 10px;
  align-items: start;
}
/* A column of density under the folio, not a full-frame overlay. The folio sits
   over sunlit regolith for most of the page, which is the brightest ground this
   build has, and mono type at 0.66rem loses against it completely. taste.md's
   third shape: put the density where the text is and leave the photograph
   alone. It is a pseudo-element rather than a sibling on purpose: the folio is
   not a [data-sc-copy] block, so the verification pass never hides it and the
   scrim is always part of what gets composited. */
.folio::before {
  content: "";
  position: absolute;
  /* Full height, and starting exactly at the viewport edge: a column, not a
     rectangle floating in the frame, so it has no top or bottom edge to read as
     a panel. */
  inset-block: -100vh;
  left: calc(-1 * var(--sc-gutter));
  right: -52px;
  background: linear-gradient(to right,
    rgb(11 12 16 / 0.9) 0%,
    rgb(11 12 16 / 0.86) 46%,
    rgb(11 12 16 / 0.66) 76%,
    rgb(11 12 16 / 0) 100%);
  z-index: -1;
  pointer-events: none;
}
.folio__title {
  grid-area: title;
  margin: 0 0 var(--sc-4);
  font-family: var(--sc-font-display);
  font-size: 0.9rem;
  letter-spacing: 0.01em;
  color: var(--sc-ink);
}
.folio__profile { grid-area: profile; width: 44px; height: 300px; overflow: visible; }
.folio__datum { stroke: color-mix(in oklab, var(--sc-ink) 24%, transparent); stroke-width: 1; stroke-dasharray: 2 3; }
.folio__route { fill: none; stroke: color-mix(in oklab, var(--sc-ink) 30%, transparent); stroke-width: 1.5; }
.folio__tick { stroke: color-mix(in oklab, var(--sc-ink) 26%, transparent); stroke-width: 1; }
.folio__mark {
  fill: var(--sc-accent);
  /* offset-path would be the elegant version; a point sampled off the same path
     in script is the one that cannot drift from the drawn line. */
}

.folio__legs { grid-area: legs; list-style: none; margin: 0; padding: 0; display: grid; }
.folio__legs li { min-height: 50px; display: flex; align-items: flex-start; }
.folio__legs button {
  appearance: none; background: none; border: 0; padding: 2px 0 6px;
  font: inherit; letter-spacing: inherit; text-align: left;
  color: color-mix(in oklab, var(--sc-ink) 64%, transparent);
  cursor: pointer;
  transition: color var(--sc-d-fast) var(--sc-ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .folio__legs button:hover { color: var(--sc-ink); }
}
.folio__legs button:active { transform: translateY(1px); }
.folio__legs button[aria-current="true"] { color: var(--sc-ink); font-weight: 500; }
.folio__legs button[aria-current="true"] span::before {
  content: ""; display: inline-block; vertical-align: 0.18em;
  width: 10px; height: 1.5px; margin-right: 6px;
  background: var(--sc-accent);
}
.folio__legs button span { display: block; max-width: 13ch; line-height: 1.45; }

.folio__read {
  grid-area: read; margin: var(--sc-5) 0 0;
  display: grid; grid-template-columns: auto auto; gap: 1px var(--sc-3);
  border-top: 1px solid color-mix(in oklab, var(--sc-ink) 14%, transparent);
  padding-top: var(--sc-3);
}
.folio__read dt { color: color-mix(in oklab, var(--sc-ink) 56%, transparent); font-size: 0.6rem; }
/* On a wide screen the waypoint is already named six times in the list beside
   this, so naming it again is noise. It earns its place only on a phone, where
   the list has no room for words. */
.folio__wp, .folio__wp-dt { display: none; }
.folio__read dd { margin: 0; color: var(--sc-ink); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- a plate --
   Three elements in one box: the cast shadow, the plate, and the type. Shadow
   and plate are SIBLINGS of the copy block, never children of it, because the
   verification pass hides [data-sc-copy] and its whole subtree to photograph
   the frame underneath. A plate written as a pseudo-element on the copy is
   hidden along with the copy and never measured, and the tell is that
   strengthening it does not move the reported numbers by a thousandth.

   page.js keeps all three registered by publishing the engine's own transform
   and opacity as --engine-t and --vis on the wrapper. */
.mark {
  position: absolute;
  width: min(38ch, 32vw);
  --vis: 0;
  --engine-t: translate3d(0, 0, 0);
}
.mark__shadow, .mark__plate { position: absolute; inset: 0; will-change: transform, opacity; }

.mark__shadow {
  background: #04050A;
  /* Near-opaque, not a 60% wash. A cast shadow on regolith is BLACK: there is
     no atmosphere to scatter light into it, which is the same fact that makes
     the edge hard. At 0.6 over sunlit ground it read as a grey panel floating
     in the frame, which is exactly the "glass and blur as decoration" failure,
     and it looked nothing like the jet-black shadows in the film beside it. */
  opacity: calc(clamp(0, calc(var(--vis) * 40 - 1.2), 1) * 0.92 * var(--sun-i));
  transform-origin: 50% 0%;
  /* A cast shadow at a grazing sun is a projection, not an offset copy: it
     hangs off the bottom edge of the object, leans away from the light and
     stretches with the inverse of the sun's height. An offset rectangle with
     zero blur is the neobrutalist decoration taste.md bans; this is the same
     zero blur for the opposite reason, because there is no air here to make a
     penumbra. */
  transform: var(--engine-t) translateY(100%) skewX(var(--sun-skew)) scaleY(var(--sun-len));
}

.mark__plate {
  background: var(--plate);
  /* The plate arrives BEFORE its ink and leaves after it. A printed sign does
     not dissolve, and an opaque plate caught at half opacity over a bright
     frame reads as a ghost rather than as a fade, which the contact sheet
     catches at any sample that lands inside the ramp. Steepening the curve
     against the engine's own window means the object is solid by the time the
     type is legible on it, and the type never sits on a see-through plate.

     Two attempts at a gentler curve (2.4x, then 5x) both still got caught: with
     eight samples across a 2.15vh leg the gap between positions is wider than
     the ramp itself, so a translucent plate is not a sampling artefact, it is
     something a reader can stop on. So the plate is a hard switch: solid by
     window opacity 0.055, which is a few pixels of scroll. The reader sees the
     sign arrive, and then the ink comes up on it over the engine's whole
     window. That is also what the object would do. A sign is placed; it does
     not dissolve into existence. */
  opacity: clamp(0, calc(var(--vis) * 40 - 1.2), 1);
  transform: var(--engine-t);
  border: 1px solid color-mix(in oklab, var(--plate-ink) 22%, transparent);
  /* Edge light, and it is directional while there is a sun: a real lip catches
     the light on the side it comes from. Under earthlight the source is a whole
     hemisphere, so the directional edge goes and a flat one stays. */
  box-shadow:
    inset 2px 0 0 rgb(255 252 244 / calc(0.5 * var(--sun-i))),
    inset -1px 0 0 rgb(10 8 6 / 0.16),
    inset 0 1px 0 rgb(255 252 244 / calc(0.22 + 0.2 * var(--earth-i))),
    /* Earthlight casts too. Earth is about two degrees wide from here, so it
       throws a short shadow with a real penumbra, which is the opposite of the
       sun's knife edge and the reason this one has blur and an offset. Without
       it the plates float unattached the moment the sun goes, which is exactly
       when the page most needs them to still be objects. */
    0 calc(7px * var(--earth-i)) calc(20px * var(--earth-i))
      rgb(3 5 12 / calc(0.55 * var(--earth-i)));
}

.mark__text {
  position: relative;             /* sizes the box that plate and shadow fill */
  padding: var(--sc-5) var(--sc-5) calc(var(--sc-5) + 2px);
  color: var(--plate-ink);
  font-family: var(--sc-font-text);
  font-weight: 400;
  font-size: 0.95rem;
  line-height: 1.55;
  letter-spacing: var(--sc-track-normal);
  hyphens: auto;
  text-wrap: pretty;
}
.mark__text > * { margin: 0; }
.mark__text > * + * { margin-top: var(--sc-3); }
.mark__text p { max-width: 42ch; }

/* the register mark: the accent owns this one role and nothing else on a plate */
.label {
  font-family: var(--sc-font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--plate-ink-soft);
  padding-bottom: var(--sc-2);
  border-bottom: 1px solid var(--plate-rule);
}
.label::before {
  content: ""; display: inline-block; vertical-align: 0.1em;
  width: 14px; height: 2px; margin-right: 8px;
  background: var(--accent-on-plate);
}

.mark__text h1, .mark__text h2 {
  font-family: var(--sc-font-display);
  font-weight: 400;
  letter-spacing: -0.018em;
  line-height: 1.02;
  text-wrap: balance;
  margin-top: var(--sc-4);
}
.mark__text h1 { font-size: clamp(2.3rem, 1.5rem + 3.2vw, 4rem); hyphens: manual; }
.mark__text h2 {
  font-size: clamp(1.55rem, 1.1rem + 1.5vw, 2.4rem); line-height: 1.06;
  /* Auto-hyphenation belongs to body copy, not to a serif at 2.4rem. German
     hyphenation is legal but it broke the close on "Wasser-eis" and
     "Ar-beitslicht", which reads as an accident rather than as typesetting. */
  hyphens: manual;
}
.lede { font-size: 1.02rem; line-height: 1.58; }

/* A footnote is the one thing an editorial page can give somebody for stopping,
   and it is never needed to understand the entry. page.js opens it after the
   scroll has been still for a moment. */
.mark__note {
  font-family: var(--sc-font-display);
  font-style: italic;
  font-size: 0.92rem;
  line-height: 1.5;
  padding-top: var(--sc-3);
  border-top: 1px solid var(--plate-rule);
  /* The rule and the space stay: an empty compartment under a hairline reads as
     a blank field on a printed plate, which is deliberate, where an empty band
     with no rule reads as padding somebody got wrong. Only the text arrives.
     Fading colour rather than opacity is what keeps the rule visible. */
  color: transparent;
  transition: color 320ms var(--sc-ease-out);
}
.mark[data-dwell="on"] .mark__note { color: var(--plate-ink-soft); }

/* ------------------------------------------------------------ the anchors --
   Vary where the copy sits. Four different anchors and eight different heights,
   because centred copy in every act is the tell. */
.mark--lead   { left: calc(var(--sc-gutter) + var(--folio-w) + var(--sc-6)); }
.mark--trail  { right: var(--sc-gutter); }
.mark--centre { left: 0; right: 0; margin-inline: auto; width: min(46ch, 40vw); }
.mark--hero   { width: min(44ch, 38vw); }
/* The close carries the most copy on the page: a heading, a paragraph, a
   four-row rota and a colophon. At 38ch German auto-hyphenation started
   breaking "Warteliste" across a line, so the plate gets the measure it needs
   instead of the type getting a worse break. */
.mark--close  { width: min(46ch, 35vw); }

/* The hero plate is present on the very first paint, before script has had a
   frame to publish --vis. Without this the landing view shows dark plate type
   over the film for one frame, which is the one screen every visitor sees. */
.mark:nth-of-type(1) { bottom: 13vh; --vis: 1; }
.mark:nth-of-type(2) { top: 17vh; }
.mark:nth-of-type(3) { bottom: 15vh; }
.mark:nth-of-type(4) { top: 21vh; }
.mark:nth-of-type(5) { bottom: 19vh; }
.mark:nth-of-type(6) { top: 32vh; }
.mark:nth-of-type(7) { top: 24vh; }
.mark:nth-of-type(8) { bottom: 12vh; }

/* --------------------------------------------------------------- the ask --
   Arrival, and the ask is an object in the place. The label is the same one it
   would be anywhere else on the page, and asking for it produces the thing
   itself rather than a form. */
.rota { margin-top: var(--sc-5); }
.rota summary {
  display: inline-flex; align-items: center; gap: 10px;
  min-height: 44px; padding: 0 2px;
  list-style: none; cursor: pointer;
  font-family: var(--sc-font-mono);
  font-size: 0.74rem; font-weight: 500; letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--plate-ink);
  border-bottom: 2px solid var(--accent-on-plate);
  transition: gap var(--sc-d-fast) var(--sc-ease-out);
}
.rota summary::-webkit-details-marker { display: none; }
.rota summary::after {
  content: ""; width: 9px; height: 9px;
  border-right: 2px solid var(--accent-on-plate); border-bottom: 2px solid var(--accent-on-plate);
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--sc-d-base) var(--sc-ease-out);
}
.rota[open] summary::after { transform: rotate(225deg) translateY(-2px); }
@media (hover: hover) and (pointer: fine) { .rota summary:hover { gap: 14px; } }
.rota summary:active { transform: translateY(1px); }
.rota__body { padding-top: var(--sc-4); }
.rota__body dl {
  margin: 0 0 var(--sc-4);
  display: grid; grid-template-columns: auto 1fr; gap: var(--sc-2) var(--sc-4);
  font-size: 0.9rem;
}
.rota__body dt { font-family: var(--sc-font-mono); font-variant-numeric: tabular-nums; color: var(--plate-ink-soft); }
.rota__body dd { margin: 0; }

.colophon {
  margin-top: var(--sc-5);
  padding-top: var(--sc-3);
  border-top: 1px solid var(--plate-rule);
  font-family: var(--sc-font-mono);
  font-size: 0.6rem;
  line-height: 1.55;
  letter-spacing: 0.05em;
  color: var(--plate-ink-soft);
  max-width: 46ch;
}

/* --------------------------------------------------------- the portrait --
   A 16:9 move covering a 9:16 viewport keeps the middle third, and this world
   puts its subject left of centre and low: the default centre crop lands on the
   crater interior, which is the one part of every frame with nothing in it.
   Biasing the crop is the cheapest of the three fixes in assets.md, and the
   only one that does not mean generating a second flight.

   These are my own elements, not engine selectors: the class is on the img and
   video I wrote, so nothing here reaches into the mechanism. */
@media (max-width: 860px) {
  /* With `cover`, a 16:9 source in a 9:16 box is already scaled to fill the
     height, so only the horizontal value does anything: this picks WHICH
     vertical slice of the frame survives. 58% keeps the rovers, the cargo and
     the crater edge; the default 50% lands on the shadowed ground between
     them. */
  .world__frame { object-position: 58% 50%; }
}

/* ------------------------------------------------------------------ focus --
   Themed to the accent, with offset, and it has to stay visible against both
   grounds this page has: the plate and the world. */
:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--sc-accent);
  outline-offset: 3px;
}
/* A focus ring has to be visible against every ground it crosses, and this page
   has two: the canvas, where the accent reads 5.96:1, and a bone-to-cold plate,
   where it reads 2.86:1 falling to 1.78:1. Inside a plate the ring is the plate's
   own ink, 16.4:1 warm and 10.3:1 cold. */
.mark__text :focus-visible { outline-color: var(--plate-ink); }

/* ------------------------------------------------------------ the phone --
   The folio moves to the top as a horizontal strip and the plates take the
   bottom band, because a corner anchor becomes a full-width band below 860px
   anyway and two objects fighting for the same corner is worse than either. */
@media (max-width: 860px) {
  :root { --folio-w: 0px; }
  .folio {
    top: 0; left: 0; right: 0; width: auto;
    transform: none;
    padding: calc(env(safe-area-inset-top) + var(--sc-3)) var(--sc-gutter) var(--sc-3);
    background: linear-gradient(to bottom, rgb(11 12 16 / 0.82), rgb(11 12 16 / 0));
    grid-template-columns: 1fr auto;
    grid-template-areas: "title read" "legs legs";
    column-gap: var(--sc-4);
    align-items: center;
  }
  .folio__profile { display: none; }
  /* The waypoint name squeezes the title into two lines at 390px. The title is
     a folio, not a headline: it holds one line or it is not doing its job. */
  .folio__title { white-space: nowrap; font-size: 0.82rem; margin-bottom: 0; }
  /* Six German waypoint names will not fit across 390px at any size worth
     reading, so the map becomes six rules with the active one named once, in
     the readout. The names stay in the accessibility tree. */
  .folio__legs { grid-auto-flow: column; grid-auto-columns: 1fr; margin-top: var(--sc-3); gap: 5px; }
  .folio__legs li { min-height: 0; min-width: 0; }
  .folio__legs button { min-width: 0; width: 100%; padding: 0; }
  .folio__legs button span {
    max-width: none;
    height: 32px;                       /* with the row gap this is a 44px target */
    border-top: 2px solid color-mix(in oklab, var(--sc-ink) 20%, transparent);
    color: transparent; font-size: 0; overflow: hidden;
  }
  .folio__legs button[aria-current="true"] span { border-top-color: var(--sc-accent); }
  .folio__legs button[aria-current="true"] span::before { display: none; }
  .folio__read {
    margin: 0; border: 0; padding: 0;
    grid-template-columns: auto; grid-auto-flow: column; gap: 0 var(--sc-4);
    justify-items: end; text-align: right;
  }
  .folio__read dt { display: none; }
  .folio__wp { display: block; font-family: var(--sc-font-display); font-size: 0.82rem; letter-spacing: 0; }
  .folio__read dd[data-read="sun"]::before { content: "☉ "; color: var(--sc-ink-soft); }
  .folio__read dd[data-read="depth"]::before { content: "↓ "; color: var(--sc-ink-soft); }

  .mark,
  .mark--hero,
  .mark--centre { left: var(--sc-gutter); right: var(--sc-gutter); width: auto; margin-inline: 0; }
  .mark:nth-of-type(n) { top: auto; bottom: calc(env(safe-area-inset-bottom) + 8vh); }
  .mark:nth-of-type(6) { top: 34vh; bottom: auto; }
  .mark__text { padding: var(--sc-4); font-size: 0.92rem; }
  /* --sc-t-4xl floors at a DESKTOP floor. A hero headline at that size wraps to
     six lines at 390px, so the hero steps down a rung here. */
  .mark__text h1 { font-size: clamp(1.9rem, 1.3rem + 3vw, 2.6rem); }
  .mark__text h2 { font-size: clamp(1.3rem, 1rem + 2vw, 1.8rem); }
  .lede { font-size: 0.96rem; }
  /* A shadow long enough to read on a desktop plate runs off a phone screen. */
  .mark__shadow { opacity: calc(var(--vis) * 0.5 * var(--sun-i)); }
}

/* --------------------------------------------------------- reduced motion --
   Fewer and gentler, not zero. The clips are never fetched, the posters hold
   and cross-dissolve through the same seams, the copy windows still open and
   close, and the shadow still goes out at the descent, because that IS the
   meaning of this page and deleting it would delete the story. What goes is the
   continuous interpolation: page.js snaps the sun per leg instead. */
@media (prefers-reduced-motion: reduce) {
  .mark__note { transition: none; color: var(--plate-ink-soft); }
  .rota summary, .rota summary::after { transition: none; }
  .folio__legs button { transition: none; }
}
