/* ============================================================================
   PROTOPORA · public index
   ----------------------------------------------------------------------------
   Grammar: continuous world. One canvas is fixed for the whole page and the
   scroll travels through it. There are no sections and no acts: the copy
   arrives at waypoints inside a fixed layer, the only element in document
   flow is a spacer, and the navigation is a map of the journey rather than a
   bar over it.

   The page is a readable document first. Without script, and under reduced
   motion, every waypoint is a normal block in a normal column and the canvas
   is not drawn at all. The class `pp-ready` is what turns that document into
   the world, and it is only ever added once the world is actually running.

   Route scoped: linked from the index route's head and nowhere else.
   ========================================================================== */

/* ------------------------------------------------------------------ tokens */
/* Taken from src/styles.css so the landing page and the product are the same
   brand rather than two that resemble each other. The accent is bronze at two
   lightnesses, one hue: the page ends on a warm white ground and spends the
   rest of its length on near black, and a single stop cannot clear 4.5:1 on
   both. That is the one exception the taste floor licenses, and this brand
   shipped the two stops before this page existed. */
:root {
  --pp-void: oklch(0.145 0.021 258);
  --pp-ink: oklch(0.962 0.008 86);
  --pp-ink-soft: oklch(0.771 0.012 250);
  --pp-ink-dim: oklch(0.612 0.014 252);
  --pp-accent: oklch(0.769 0.094 79);
  --pp-accent-ink: oklch(0.155 0.023 258);
  --pp-hairline: oklch(0.322 0.018 254);

  --pp-day: oklch(0.968 0.007 86);
  --pp-day-ink: oklch(0.232 0.014 258);
  --pp-day-soft: oklch(0.438 0.014 254);
  --pp-day-accent: oklch(0.472 0.072 70);

  --pp-ui: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --pp-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;
  --pp-display: "Source Serif 4", "Iowan Old Style", Georgia, serif;

  --pp-ease: cubic-bezier(0.23, 1, 0.32, 1);
  --pp-gut: clamp(1.25rem, 4.5vw, 4.5rem);

  /* Written by the page. Declared here so a no-script render is valid. */
  --pp-p: 0;
  --pp-day-mix: 0;
}

/* --------------------------------------------------------------- the sheet */
.pp {
  position: relative;
  min-height: 100svh;
  background: var(--pp-void);
  color: var(--pp-ink);
  font-family: var(--pp-ui);
  -webkit-font-smoothing: antialiased;
}

.pp ::selection {
  background: var(--pp-accent);
  color: var(--pp-accent-ink);
}

.pp :focus-visible {
  outline: 2px solid var(--pp-accent);
  outline-offset: 3px;
  border-radius: 2px;
}

/* The world. Hidden until it is proven to be running, so a device that cannot
   give us WebGL never shows an empty black rectangle where a page should be. */
.pp-canvas {
  display: none;
}

.pp-ready .pp-canvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* The only element in document flow. Its height is the length of the journey,
   and nothing else on the page scrolls at all. */
.pp-spacer {
  display: none;
}

.pp-veil {
  display: none;
}

.pp-ready .pp-veil {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: block;
  pointer-events: none;
  /* Density where the copy is, and it has to reach as far as the copy does.
     The widest panel on the page carries a right hand column of delivery
     times, and that column sits over the lit part of the machine: measured
     against the pixels actually behind it, one of those labels was at 1.2 to
     1 where it needs 4.5. The falloff holds longer and lets go further out,
     which covers the whole column and still leaves the machine its own side
     of the frame. */
  background: radial-gradient(
    146% 132% at 2% 74%,
    oklch(0.145 0.021 258 / 0.93) 0%,
    oklch(0.145 0.021 258 / 0.86) 32%,
    oklch(0.145 0.021 258 / 0.7) 52%,
    oklch(0.145 0.021 258 / 0.3) 70%,
    transparent 88%
  );
  opacity: calc(1 - clamp(0, calc((var(--pp-p) - 0.9) / 0.05), 1));
}

/* The arrival is lit by the world itself, which washes its own ground to this
   warm white. This layer is only the guarantee that the copy has a light
   surface under it on a machine whose ramp runs behind the page's. It used to
   be a tight, nearly opaque ellipse over the middle of the frame, which
   bleached the top half of the mark and left the bottom half saturated: the
   figure read as half finished. It is now even, edge to edge, and gentle
   enough that the mark keeps its own weight. */
.pp-ready .pp-veil--day {
  /* Half a screen of white laid over the whole frame is the one thing that
     cannot be tuned back: it lifted the ground, and it lifted the mark with
     it, so the figure the page spends its length making arrived as flat
     taupe. The world already washes its own ground to warm white and holds
     the figure out of that wash, so this is now a lift and not a cover. */
  background: radial-gradient(
    150% 130% at 50% 44%,
    oklch(0.968 0.007 86 / 0.1) 0%,
    oklch(0.968 0.007 86 / 0.08) 60%,
    oklch(0.968 0.007 86 / 0.05) 100%
  );
  opacity: clamp(0, calc((var(--pp-p) - 0.94) / 0.045), 1);
}

.pp-ready .pp-spacer {
  display: block;
  height: 1000svh;
}

/* --------------------------------------------------------------- the copy */
/* A document by default: one readable column, everything present, nothing
   hidden. This is what a search engine, a screen reader with motion off, and
   a browser with no script all get. */
.pp-copy {
  position: relative;
  z-index: 2;
  display: grid;
  gap: clamp(4rem, 12vh, 9rem);
  max-width: 46rem;
  margin: 0 auto;
  padding: clamp(4rem, 12vh, 8rem) var(--pp-gut);
}

.pp-ready .pp-copy {
  position: fixed;
  inset: 0;
  z-index: 2;
  display: block;
  max-width: none;
  margin: 0;
  padding: 0;
  pointer-events: none;
}

/* One waypoint. In the world it is a fixed panel that arrives, holds and
   leaves; in the document it is a block. */
.pp-way {
  position: relative;
}

.pp-ready .pp-way {
  position: absolute;
  inset: auto 0 0 0;
  display: grid;
  align-content: end;
  min-height: 100svh;
  padding: clamp(4.25rem, 10vh, 6.5rem) var(--pp-gut) clamp(5.5rem, 14vh, 9rem);
  /* Arrival and departure are one interpolation each, computed from the page
     progress the driver writes. Opacity and transform only. */
  --pp-t: clamp(0, calc((var(--pp-p) - var(--pp-at)) / 0.026), 1);
  --pp-u: clamp(0, calc((var(--pp-p) - var(--pp-until)) / 0.024), 1);
  --pp-in: calc(var(--pp-t) * var(--pp-t) * (3 - 2 * var(--pp-t)));
  --pp-out: calc(var(--pp-u) * var(--pp-u) * (3 - 2 * var(--pp-u)));
  /* The arrival belongs to the lines inside, which fade in on their own
     staggered curve. Putting it here as well multiplied the two: a panel its
     own curve said was at a third was on screen at a tenth, and the copy
     spent most of its window arriving instead of being read. The panel owns
     the departure and the lift; the lines own the arrival. */
  opacity: calc(1 - var(--pp-out));
  transform: translate3d(0, calc(1.5rem * (1 - var(--pp-in)) - 1.2rem * var(--pp-out)), 0);
  will-change: opacity, transform;
}

/* Only the waypoint the reader is actually on may be clicked or tabbed into.
   Without this every link on the page is in the tab order at once and a
   keyboard user lands on an invisible control. */
.pp-ready .pp-way[data-pp-live] {
  pointer-events: auto;
}

/* Visibility is set inline by the driver from the same numbers the opacity
   is computed from. It cannot be expressed here, because a hidden panel has
   to stay hidden only while its opacity is genuinely zero, and CSS has no way
   to branch on a calc. */

/* The lines arrive in sequence. The ramp is deliberately short: a waypoint
   holds the screen for about seven hundredths of the journey, and a line that
   spends a third of that fading in is a line the reader watches instead of
   reads. Short enough to be read, staggered enough to be a sequence. */
.pp-ready .pp-panel > * {
  --pp-lag: calc(var(--pp-i, 0) * 0.004);
  --pp-ct: clamp(0, calc((var(--pp-p) - var(--pp-at) - var(--pp-lag)) / 0.011), 1);
  --pp-ce: calc(var(--pp-ct) * (2 - var(--pp-ct)));
  opacity: var(--pp-ce);
  transform: translate3d(0, calc(1.1rem * (1 - var(--pp-ce))), 0);
}

.pp-ready .pp-panel > :nth-child(2) {
  --pp-i: 1;
}
.pp-ready .pp-panel > :nth-child(3) {
  --pp-i: 2;
}
.pp-ready .pp-panel > :nth-child(4) {
  --pp-i: 3;
}
.pp-ready .pp-panel > :nth-child(5) {
  --pp-i: 4;
}
.pp-ready .pp-panel > :nth-child(6) {
  --pp-i: 5;
}

/* Rows inside a listing arrive in sequence too, so a seven item list reads
   as filling in rather than as appearing. */
.pp-ready .pp-rows li {
  --pp-rl: calc(var(--pp-r, 0) * 0.003);
  --pp-rt: clamp(0, calc((var(--pp-p) - var(--pp-at) - 0.004 - var(--pp-rl)) / 0.011), 1);
  --pp-re: calc(var(--pp-rt) * (2 - var(--pp-rt)));
  opacity: calc(0.06 + 0.94 * var(--pp-re));
  transform: translate3d(calc(0.7rem * (1 - var(--pp-re))), 0, 0);
}

.pp-ready .pp-rows li:nth-child(2) {
  --pp-r: 1;
}
.pp-ready .pp-rows li:nth-child(3) {
  --pp-r: 2;
}
.pp-ready .pp-rows li:nth-child(4) {
  --pp-r: 3;
}
.pp-ready .pp-rows li:nth-child(5) {
  --pp-r: 4;
}
.pp-ready .pp-rows li:nth-child(6) {
  --pp-r: 5;
}
.pp-ready .pp-rows li:nth-child(7) {
  --pp-r: 6;
}

.pp-panel {
  display: grid;
  gap: clamp(0.9rem, 1.6vh, 1.5rem);
  max-width: 42rem;
}

.pp-panel--wide {
  max-width: 58rem;
}

/* The last waypoint arrives on a warm white ground, so it carries its own ink
   and restates `color`. Redefining a token on a subtree does not re-ink the
   text under it: colour inherits as a computed value, and the failure is
   silent. */
.pp-way--day {
  --pp-ink: var(--pp-day-ink);
  --pp-ink-soft: var(--pp-day-soft);
  --pp-ink-dim: var(--pp-day-soft);
  --pp-accent: var(--pp-day-accent);
  --pp-accent-ink: var(--pp-day);
  --pp-hairline: oklch(0.862 0.008 258);
  color: var(--pp-ink);
}

.pp-ready .pp-way--day {
  align-content: center;
  padding-bottom: clamp(7rem, 18vh, 11rem);
  text-align: center;
  justify-items: center;
}

/* The arrival is the one screen where the words and the mark share a frame,
   so above a height that can hold both it is composed rather than centred:
   the copy takes the upper field and the mark is seated below it. Centred,
   the last line sat straight through the mark's top ring at every height
   tried. Below that the copy is centred again, because on a window too short
   to seat them apart, anchoring the copy to the top pushes the closing links
   off the bottom, and a link you cannot reach is worse than one the mark
   passes behind. */
@media (min-height: 640px) {
  .pp-ready .pp-way--day {
    align-content: start;
    padding-top: clamp(3.5rem, 9vh, 6rem);
  }
}

.pp-ready .pp-way--day .pp-panel {
  justify-items: center;
}

/* ---------------------------------------------------------------- the type */
/* The headings carry the page's authority, and a neutral grotesque cannot:
   it is the most set face on the web and it reads as a default rather than a
   decision. This is the serif the rest of the site already sets its headings
   in, it has a properly drawn Greek, and it is already in the bundle, so the
   page gains a voice without gaining a family or a byte. Serif here is not a
   shortcut to premium, it is what a Greek reader recognises as official. */
.pp-h1,
.pp-h2 {
  margin: 0;
  font-family: var(--pp-display);
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -0.014em;
  text-wrap: balance;
}

.pp-h1 {
  font-size: clamp(2.3rem, 1.35rem + 3.5vw, 4.6rem);
  max-width: 17ch;
}

.pp-h2 {
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 3.4rem);
  letter-spacing: -0.018em;
}

.pp-lede {
  margin: 0;
  font-size: clamp(1.02rem, 0.96rem + 0.4vw, 1.32rem);
  line-height: 1.5;
  max-width: 40ch;
  text-wrap: pretty;
  color: var(--pp-ink-soft);
}

.pp-say {
  margin: 0;
  font-size: clamp(0.94rem, 0.9rem + 0.22vw, 1.06rem);
  line-height: 1.62;
  max-width: 48ch;
  text-wrap: pretty;
  color: var(--pp-ink-soft);
}

/* Mono is for labels and figures, which is what it is for. */
.pp-tag {
  margin: 0;
  font-family: var(--pp-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--pp-accent);
}

.pp-num {
  font-family: var(--pp-mono);
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  color: var(--pp-accent);
}

/* ---------------------------------------------------------------- listings */
/* Rows on hairlines, never a grid of identical tiles. A card grid is the most
   recognisable machine made page there is and there are none here. */
.pp-rows {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pp-rows li {
  display: grid;
  grid-template-columns: 2.1rem minmax(0, 1fr);
  gap: 0.9rem;
  align-items: baseline;
  padding-block: clamp(0.5rem, 1.1vh, 0.85rem);
  border-top: 1px solid var(--pp-hairline);
}

.pp-rows li:first-child {
  border-top: 0;
}

.pp-rows h3 {
  margin: 0;
  font-size: clamp(0.95rem, 0.9rem + 0.24vw, 1.1rem);
  font-weight: 500;
  line-height: 1.28;
  letter-spacing: -0.008em;
}

.pp-rows p {
  margin: 0.28rem 0 0;
  font-size: 0.86rem;
  line-height: 1.5;
  max-width: 46ch;
  color: var(--pp-ink-soft);
}

/* The one list whose length is data rather than copy, and the only panel that
   was taller than the frame it has to live in. Three things were doing it: the
   timing column held nearly two fifths of the row for a four word phrase, the
   sentence beside it was capped at a measure meant for a narrower column, and
   the rhythm was set for a list half this long. A waypoint is a fixed frame,
   so anything past its height is content nobody can reach. */
/* The delivery time sits in its own column, and that column has to stay on the
   copy's ground rather than reaching out over the lit part of the machine.
   Stacking it under each row fixed the contrast and cost six extra lines,
   which pushed the panel past the viewport at every desktop height. So the
   column stays, and it is held inside the panel's measure instead: the row is
   capped so its right edge lands where the veil is still dense, and the label
   carries the soft tone rather than the dimmest one. The narrow layout still
   stacks, where there is no room for a second column at all. */
.pp-rows--split li {
  grid-template-columns: 2.1rem minmax(0, 1fr) max-content;
  max-width: 54rem;
  padding-block: clamp(0.32rem, 0.7vh, 0.56rem);
}

@media (max-width: 720px) {
  .pp-rows--split li {
    grid-template-columns: 2.1rem minmax(0, 1fr);
    max-width: none;
  }
  .pp-rows--split li > :last-child {
    grid-column: 2;
  }
}

.pp-rows--split p {
  max-width: 64ch;
}

.pp-side {
  margin-top: 0.1rem;
  font-size: 0.78rem;
  letter-spacing: 0.01em;
  /* The dimmest tone on the page could not carry small text over a lit ground
     on a phone: measured, it sat just under the level it needs. Size and
     position already say this line is secondary, so the colour does not have
     to say it a third time. */
  color: var(--pp-ink-soft);
}

/* ---------------------------------------------------------------- the map */
/* The navigation of a world is a map, not a bar. It names the stations, marks
   where the reader is, and every station is a real destination. */
.pp-map {
  display: none;
}

.pp-ready .pp-map {
  position: fixed;
  top: 50%;
  right: clamp(0.75rem, 2vw, 2rem);
  z-index: 4;
  display: grid;
  gap: 0.55rem;
  transform: translateY(-50%);
  opacity: calc(1 - clamp(0, calc((var(--pp-p) - 0.9) / 0.05), 1));
}

/* The map is fixed over whatever the world is doing behind it, and the world
   is bright now: measured over a lit spar, these labels were the least legible
   text on the page, and they had never been measured because they sit outside
   the waypoint the checks read from. A narrow corner of density, sized to the
   column and no wider, so the world keeps the rest of the frame. */
.pp-ready .pp-map::before,
.pp-ready .pp-depth::before,
.pp-ready .pp-mark::before {
  content: "";
  position: absolute;
  inset: -1.1rem -2.1rem;
  z-index: -1;
  pointer-events: none;
  /* Density held flat across the labels and released only past their far edge.
     A gradient that starts falling off inside the text protects the short
     labels and abandons the long ones, which is exactly what happened: the
     station named with the most letters was the least legible on the page. */
  /* Feathered on every edge, not just the one the text runs off. A gradient
     with a hard top and bottom reads as a rectangle laid on the picture the
     moment the world behind it is dark, which is most of the page. */
  background: radial-gradient(
    185% 132% at 100% 50%,
    oklch(0.145 0.021 258 / 0.94) 0%,
    oklch(0.145 0.021 258 / 0.84) 48%,
    oklch(0.145 0.021 258 / 0.38) 76%,
    transparent 100%
  );
}

.pp-ready .pp-depth::before,
.pp-ready .pp-mark::before {
  background: radial-gradient(
    185% 132% at 0% 50%,
    oklch(0.145 0.021 258 / 0.94) 0%,
    oklch(0.145 0.021 258 / 0.84) 48%,
    oklch(0.145 0.021 258 / 0.38) 76%,
    transparent 100%
  );
}

.pp-ready .pp-mark {
  position: relative;
  z-index: 4;
}

.pp-map button {
  display: grid;
  grid-template-columns: auto 1.9rem;
  min-width: 0;
  gap: 0.55rem;
  align-items: center;
  justify-content: end;
  padding: 0.18rem 0.2rem;
  border: 0;
  background: none;
  color: var(--pp-ink-dim);
  font-family: var(--pp-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
  transition: color 160ms var(--pp-ease);
}

/* The rule fills across the station's own span, so this is a gauge rather
   than a list with one row highlighted. No transition on the transform: it is
   driven continuously by the page's progress, and easing a value that is
   already being eased reads as lag. */
.pp-map button::after {
  content: "";
  height: 1px;
  background: currentColor;
  transform: scaleX(
    calc(0.2 + 0.8 * clamp(0, calc((var(--pp-p) - var(--pp-s0, 0)) * var(--pp-si, 1)), 1))
  );
  transform-origin: left;
}

.pp-map button[aria-current="true"],
.pp-map button:hover {
  color: var(--pp-accent);
}

/* The depth readout. Real position in a real journey, not a section counter. */
.pp-depth {
  display: none;
}

.pp-ready .pp-depth {
  position: fixed;
  left: var(--pp-gut);
  bottom: clamp(1rem, 3vh, 2rem);
  z-index: 4;
  display: flex;
  gap: 0.6rem;
  align-items: center;
  font-family: var(--pp-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pp-ink-dim);
  opacity: calc(1 - clamp(0, calc((var(--pp-p) - 0.9) / 0.05), 1));
}

/* Graduated, because depth is measured here rather than asserted. The track
   is a hairline with a tick every fifth of the journey; the fill runs along
   the same line. */
.pp-depth__bar {
  position: relative;
  width: clamp(3rem, 9vw, 7rem);
  height: 5px;
  background:
    repeating-linear-gradient(to right, var(--pp-hairline) 0 1px, transparent 1px 20%) left top /
      100% 3px no-repeat,
    linear-gradient(var(--pp-hairline), var(--pp-hairline)) left bottom / 100% 1px no-repeat;
}

.pp-depth__bar::after {
  content: "";
  position: absolute;
  inset: auto auto 0 0;
  width: 100%;
  height: 1px;
  background: var(--pp-accent);
  transform: scaleX(var(--pp-p));
  transform-origin: left;
}

/* --------------------------------------------------------------- the brand */
.pp-mark {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  text-decoration: none;
  color: inherit;
}

.pp-ready .pp-mark {
  opacity: calc(1 - clamp(0, calc((var(--pp-p) - 0.9) / 0.05), 1));
  position: fixed;
  top: clamp(1rem, 3vh, 2rem);
  left: var(--pp-gut);
  z-index: 4;
  pointer-events: auto;
}

.pp-mark img {
  width: 1.35rem;
  height: 1.35rem;
}

.pp-mark span {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}

/* -------------------------------------------------------------- the action */
.pp-go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.95rem;
  padding: 0 1.5rem;
  border: 0;
  border-radius: 3px;
  background: var(--pp-accent);
  color: var(--pp-accent-ink);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  transition:
    background 150ms var(--pp-ease),
    transform 130ms var(--pp-ease);
}

@media (hover: hover) and (pointer: fine) {
  .pp-go:hover {
    background: oklch(0.842 0.096 82);
  }
  .pp-way--day .pp-go:hover {
    background: oklch(0.398 0.062 68);
  }
}

.pp-go:active {
  transform: translateY(1px);
}

.pp-go--quiet {
  background: none;
  color: var(--pp-ink);
  border: 1px solid var(--pp-hairline);
}

@media (hover: hover) and (pointer: fine) {
  .pp-go--quiet:hover {
    background: none;
    color: var(--pp-accent);
    border-color: var(--pp-accent);
  }
}

.pp-in {
  align-self: center;
  margin-left: 0.35rem;
  padding: 0.35rem 0.15rem;
  color: var(--pp-ink-soft);
  font-size: 0.8125rem;
  text-decoration: none;
  text-underline-offset: 0.3em;
  transition: color 150ms var(--pp-ease);
}

.pp-in:hover {
  color: var(--pp-accent);
  text-decoration: underline;
}

.pp-note {
  margin: 0;
  font-size: 0.82rem;
  color: var(--pp-ink-dim);
}

.pp-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-top: 0.4rem;
}

/* ---------------------------------------------------------------- arrival */
.pp-ident {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.3rem 1rem;
  margin: 0;
  font-size: 0.85rem;
}

.pp-ident dt {
  font-family: var(--pp-mono);
  font-size: 0.625rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--pp-ink-dim);
  padding-top: 0.16rem;
}

.pp-ident dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.pp-reach {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  font-size: 0.95rem;
}

.pp-reach a,
.pp-links a {
  color: inherit;
  text-decoration: none;
  text-underline-offset: 0.25em;
}

.pp-reach a:hover,
.pp-links a:hover {
  color: var(--pp-accent);
  text-decoration: underline;
}

.pp-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.35rem;
  justify-content: center;
  font-size: 0.82rem;
  color: var(--pp-ink-soft);
}

/* The housekeeping row. Separated by size rather than by colour: the tone is
   already at the floor the contrast check holds it to, and a rank the eye can
   read is worth more here than another step of grey. */
.pp-links--fine {
  gap: 0.35rem 1.05rem;
  font-size: 0.72rem;
}

.pp-fine {
  font-size: 0.7rem;
  color: var(--pp-ink-dim);
}

/* ------------------------------------------------------------------ phone */
@media (max-width: 720px) {
  .pp-ready .pp-way {
    padding-bottom: clamp(6.5rem, 17vh, 9rem);
  }
  /* On a wide frame the copy holds one side and the world holds the other, so
     a corner of density is enough. A portrait frame has no other side: the
     copy spans it, and the machine stands behind every line of it. Measured,
     a lit spar was running straight through two rows. So the veil becomes a
     band here, clear across the top where the world is worth seeing and solid
     underneath where the words are. The world is the ground the copy is read
     on, and it never gets to compete with it.

     Scoped away from the day veil deliberately: that one carries this class
     too, and at equal specificity the later rule wins, so without the guard
     the arrival's light wash was being replaced by this dark band. Measured,
     it was taking a canvas at 227 down to 27 across the bottom of a phone. */
  .pp-ready .pp-veil:not(.pp-veil--day) {
    background: linear-gradient(
      to bottom,
      transparent 0%,
      oklch(0.145 0.021 258 / 0.30) 15%,
      oklch(0.145 0.021 258 / 0.90) 30%,
      oklch(0.145 0.021 258 / 0.94) 47%,
      oklch(0.145 0.021 258 / 0.95) 100%
    );
  }
  /* A map that cannot be read is not a map. Six two pixel rules down the edge
     of a phone say nothing at all, so the map becomes a readout: the station
     you are standing in, named, on the same line as the depth. The others stay
     in the document and stay tabbable, and step forward when focused. */
  .pp-ready .pp-map {
    top: auto;
    right: var(--pp-gut);
    bottom: 0.7rem;
    gap: 0;
    transform: none;
  }
  .pp-map button {
    position: absolute;
    right: 0;
    bottom: 0;
    grid-template-columns: auto;
    gap: 0;
    padding: 0;
    opacity: 0;
    pointer-events: none;
    /* Stations cross fade into one another rather than cutting, so passing a
       boundary reads as the ground changing under you. */
    transition:
      color 160ms var(--pp-ease),
      opacity 220ms var(--pp-ease);
  }
  .pp-map button[aria-current="true"] {
    position: relative;
    opacity: 1;
    pointer-events: auto;
  }
  /* One name at a time: while a station is being tabbed to, it is the one
     shown, and the one you are standing in steps back out of its way. */
  .pp-map:focus-within button[aria-current="true"]:not(:focus-visible) {
    opacity: 0;
  }
  .pp-map button:focus-visible {
    opacity: 1;
    pointer-events: auto;
  }
  .pp-map button::after {
    display: none;
  }
  .pp-ready .pp-depth {
    bottom: 0.7rem;
  }
}

/* ---------------------------------------------------------- reduced motion */
/* Fewer and gentler, not zero. The world is not drawn at all, because a
   scroll driven camera is the one thing this preference exists to refuse, and
   the page falls back to the document it already is. */
@media (prefers-reduced-motion: reduce) {
  .pp-canvas,
  .pp-ready .pp-canvas {
    display: none;
  }
  .pp-ready .pp-spacer {
    display: none;
  }
  .pp-ready .pp-copy {
    position: relative;
    display: grid;
    gap: clamp(4rem, 12vh, 9rem);
    max-width: 46rem;
    margin: 0 auto;
    padding: clamp(4rem, 12vh, 8rem) var(--pp-gut);
    pointer-events: auto;
  }
  .pp-ready .pp-way,
  .pp-ready .pp-way:not([data-pp-live]) {
    position: relative;
    inset: auto;
    min-height: 0;
    padding: 0;
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
  }
  .pp-ready .pp-map,
  .pp-ready .pp-depth {
    display: none;
  }
  .pp-ready .pp-mark {
    position: relative;
    top: auto;
    left: auto;
  }
}

@media (max-width: 720px), (max-height: 760px) {
  .pp-ready .pp-rows--split li > div > p {
    display: none;
  }
}

/* ==========================================================================
   THE GATE
   The world needs a moment to compile a shader and reach its first frame, and
   without something in front of it that moment is a black rectangle followed
   by objects appearing out of order, which reads as a broken page rather than
   a loading one. So the wait is given something to say.

   It says the name. Protopora is the one who goes first, so the gate draws the
   first form: a point, then six corners placed around it, then the edges
   closing between them. Every object in the world past this screen is built
   from that hexagon, so the reader watches the site's first shape being made
   before they are let into the place it made.

   No WebGL here, on purpose. This has to paint on the first frame, before the
   thing it is covering exists, so it is one inline SVG and a handful of
   keyframes on transform and opacity. It costs nothing and it can never be the
   reason the page is slow.
   ========================================================================== */
.pp-gate {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  background: radial-gradient(
    120% 90% at 50% 42%,
    oklch(0.196 0.026 258) 0%,
    oklch(0.145 0.021 258) 55%,
    oklch(0.118 0.018 258) 100%
  );
  transition:
    opacity 900ms cubic-bezier(0.32, 0.72, 0, 1),
    visibility 0s linear 900ms;
}

.pp-gate--out {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.pp-gate__stage {
  display: grid;
  justify-items: center;
  gap: clamp(1.5rem, 4vh, 2.4rem);
  padding: var(--pp-gut);
  transition: transform 1100ms cubic-bezier(0.32, 0.72, 0, 1);
}

.pp-gate--out .pp-gate__stage {
  transform: scale(1.07);
}

.pp-gate__form {
  width: clamp(8.5rem, 20vh, 13rem);
  height: auto;
  overflow: visible;
}

/* The seed. One point of light before there is any form around it. */
.pp-gate__seed {
  transform-origin: center;
  animation: pp-seed 900ms cubic-bezier(0.32, 0.72, 0, 1) both;
}

/* The corners, placed one at a time. Six is the whole vocabulary of this
   brand, so they are counted out rather than faded in together. */
.pp-gate__pt {
  transform-origin: center;
  animation: pp-pt 620ms cubic-bezier(0.32, 0.72, 0, 1) both;
}
.pp-gate__pt:nth-child(1) {
  animation-delay: 300ms;
}
.pp-gate__pt:nth-child(2) {
  animation-delay: 392ms;
}
.pp-gate__pt:nth-child(3) {
  animation-delay: 484ms;
}
.pp-gate__pt:nth-child(4) {
  animation-delay: 576ms;
}
.pp-gate__pt:nth-child(5) {
  animation-delay: 668ms;
}
.pp-gate__pt:nth-child(6) {
  animation-delay: 760ms;
}

/* The edges close between the corners once they are all standing. */
.pp-gate__edge {
  stroke-dasharray: 276;
  stroke-dashoffset: 276;
  animation: pp-edge 1250ms cubic-bezier(0.36, 0.66, 0.04, 1) 760ms both;
}

.pp-gate__inner {
  opacity: 0;
  transform-origin: center;
  animation: pp-inner 1000ms cubic-bezier(0.32, 0.72, 0, 1) 1320ms both;
}

.pp-gate__word {
  margin: 0;
  font-family: var(--pp-mono);
  font-size: clamp(0.72rem, 0.62rem + 0.5vw, 0.92rem);
  font-weight: 600;
  color: var(--pp-accent);
  animation: pp-track 1250ms cubic-bezier(0.32, 0.72, 0, 1) 1120ms both;
}

.pp-gate__line {
  margin: 0;
  max-width: 26ch;
  font-family: var(--pp-display);
  font-size: clamp(1rem, 0.92rem + 0.42vw, 1.3rem);
  line-height: 1.45;
  text-align: center;
  text-wrap: balance;
  color: var(--pp-ink-soft);
  animation: pp-rise 1100ms cubic-bezier(0.32, 0.72, 0, 1) 1620ms both;
}

@keyframes pp-seed {
  from {
    opacity: 0;
    transform: scale(0.2);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
@keyframes pp-pt {
  from {
    opacity: 0;
    transform: scale(0.35);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
@keyframes pp-edge {
  to {
    stroke-dashoffset: 0;
  }
}
@keyframes pp-inner {
  from {
    opacity: 0;
    transform: scale(0.86);
  }
  to {
    opacity: 0.42;
    transform: scale(1);
  }
}
@keyframes pp-track {
  from {
    opacity: 0;
    letter-spacing: 0.06em;
  }
  to {
    opacity: 1;
    letter-spacing: 0.66em;
  }
}
@keyframes pp-rise {
  from {
    opacity: 0;
    transform: translate3d(0, 0.8rem, 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Fewer and gentler, not zero: the sequence is what explains the wait, so it
   keeps its order and loses its travel. */
@media (prefers-reduced-motion: reduce) {
  .pp-gate__seed,
  .pp-gate__pt,
  .pp-gate__inner,
  .pp-gate__word,
  .pp-gate__line {
    animation-duration: 1ms;
    animation-delay: 0ms;
  }
  .pp-gate__edge {
    animation: none;
    stroke-dashoffset: 0;
  }
  .pp-gate__stage,
  .pp-gate--out .pp-gate__stage {
    transform: none;
  }
}
