/*
  shift.cx — the instrument, then the marking.
  --------------------------------------------------------------------------------------------
  The page is in two acts and the whole design is the join between them. Act one is Lumify's own
  waveform running at the size of a room, in the blue it wears when it is playing. As you leave it,
  the water flattens and its colour tweens to the red of a marking pen, so the instrument becomes the
  ruled line the marking is written under. That handover is one interpolation and it is the argument.

  The letters still carry no colour coding. A red C and a green B turn honesty into a scoreboard.
*/
:root {
  --ink:        #ece7e0;
  --ink-soft:   #a7a49f;
  /* The fourth tier. It was #6f6c68, which measures 3.72 against the floor — under the 4.5 that
     small text needs. It carries the tally, the captions, the footer, and the label on the only
     control in act two, so the whole of the quiet half of this page sat under the line. #807d78
     measures 4.74 and is still plainly a step below --ink-soft's 7.83. */
  --ink-faint:  #807d78;
  --floor:      #0b0d11;   /* measured off the real player: its room is blue-cast, not neutral */
  --loom-rgb:   88, 128, 168;
  --pen:        #d1503c;
  --measure:    36rem;
  --ease:       cubic-bezier(0.16, 1, 0.3, 1);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--floor);
  color: var(--ink);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Inter, Roboto, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.55;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: rgb(var(--loom-rgb)); text-underline-offset: 3px; }
/* `summary` belongs in this list. Left out, the sixteen disclosures — every keyboard-reachable
   control in act two — were the only focusable things on the page wearing the browser's own ring
   instead of the pen, and so the only ones that sat out the blue-to-red handover. */
a:focus-visible, .skip:focus, summary:focus-visible, [tabindex]:focus-visible { outline: 2px solid rgb(var(--loom-rgb)); outline-offset: 4px; }
.skip { position: absolute; left: -9999px; background: var(--ink); color: var(--floor); padding: .6rem 1rem; }
.skip:focus { left: 1rem; top: 1rem; z-index: 9; }
.quiet { color: var(--ink-faint); font-size: .93rem; max-width: var(--measure); }

/* ═══ ACT ONE ═══════════════════════════════════════════════════════════════ */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(1.25rem, 4vw, 3rem);
  overflow: hidden;
  isolation: isolate;
}

/* the room's light — three slow blobs on non-harmonic periods, so the composite never repeats.
   Pure CSS, so it is there even when the canvas is not. */
.hero::before {
  content: "";
  position: absolute; inset: -20%;
  z-index: -2;
  background:
    radial-gradient(46vmax 40vmax at 18% 78%, rgba(var(--loom-rgb), .34), transparent 65%),
    radial-gradient(52vmax 44vmax at 82% 62%, rgba(var(--loom-rgb), .26), transparent 68%),
    radial-gradient(38vmax 38vmax at 55% 96%, rgba(var(--loom-rgb), .30), transparent 70%);
  animation: drift 47s ease-in-out infinite alternate;
  /* The hero clips at its own bottom, and the third blob is centred BELOW that line, so the light was
     at its brightest exactly where it got cut — a ruled horizontal edge across the full width,
     measured at 25 to 42 of 255.
     The stop is 83%, not the 85.7% the untransformed geometry suggests. A mask is painted in the
     element's own coordinates and the transform is applied to the result, and this element is NEVER
     untransformed — `drift` below runs infinite alternate, so it is always somewhere between a
     +1vmax nudge and a -2vmax nudge at scale 1.12. That envelope moves the hero's bottom edge between
     83.9% and 84.6% of the box on a 1440x900 screen and 83.2% to 85.0% on a 390x844 one, so a fade
     that ends at 85.7% is still ~10-15% opaque where it gets cut for most of the 47s cycle. Ending at
     83% clears the whole envelope at both sizes. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0 72%, transparent 83%);
          mask-image: linear-gradient(to bottom, #000 0 72%, transparent 83%);
}
@keyframes drift {
  from { transform: translate3d(-2vmax, 1vmax, 0) scale(1); }
  to   { transform: translate3d(3vmax, -2vmax, 0) scale(1.12); }
}

#loom { position: absolute; inset: 0; width: 100%; height: 100%; z-index: -1; display: block; }

.rail {
  position: absolute; top: clamp(1.25rem, 4vw, 3rem); left: clamp(1.25rem, 4vw, 3rem);
  right: clamp(1.25rem, 4vw, 3rem);
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: .78rem; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-soft);
}
.dot { display: inline-block; width: 4px; height: 4px; margin-left: .55em; vertical-align: .18em; background: var(--pen); border-radius: 50%; }

/* `ch` and `rem` both scale with the reader's font size, which is right for prose and wrong for a
   display face: on a browser whose default is 24px this column became narrow AND the type became
   168px, so a four-word line broke into three. Measured, not guessed. So the MINIMUM stays in rem —
   somebody who needs large text still gets it — and the MAXIMUM is in pixels, because past a point a
   headline is a picture rather than text. */
.hero-type { position: relative; max-width: min(92vw, 880px); padding-bottom: 6svh; }

h1 {
  margin: 0;
  font-size: clamp(2.1rem, 6.2vw, 88px);
  line-height: .94;
  letter-spacing: -0.042em;
  font-weight: 620;
  text-wrap: balance;
}
h1 span { display: block; }
h1 span:nth-child(1) { color: var(--ink-soft); font-weight: 520; }
h1 span:nth-child(2) { color: var(--ink-soft); font-weight: 520; }
.h1-turn { color: var(--ink); }

.sub {
  margin: clamp(1rem, 2.4svh, 1.6rem) 0 0;
  max-width: min(88vw, 560px);
  color: var(--ink-soft);
  font-size: clamp(.95rem, 1.25vw, 19px);
}

.play-hint {
  margin: 2.2rem 0 0;
  font-size: .72rem; letter-spacing: .2em; text-transform: uppercase; color: var(--ink-faint);
}
@media (hover: none) { .play-hint { display: none; } }

/* ═══ ACT ONE AND A HALF ════════════════════════════════════════════════════ */

.proof { padding: clamp(4rem, 14svh, 9rem) clamp(1.25rem, 4vw, 3rem) 0; }
.proof figure { margin: 0 auto; max-width: 1180px; }
.proof img {
  display: block; width: 100%; height: auto;
  border-radius: 10px;
  border: 1px solid rgba(var(--loom-rgb), .22);
  box-shadow: 0 40px 120px -40px rgba(var(--loom-rgb), .45), 0 2px 0 rgba(255,255,255,.03) inset;
}
.proof figcaption {
  margin: 1.1rem auto 0; max-width: var(--measure);
  color: var(--ink-faint); font-size: .93rem;
}

/* ═══ ACT TWO ═══════════════════════════════════════════════════════════════ */

.mark { padding: clamp(5rem, 18svh, 11rem) clamp(1.25rem, 4vw, 3rem) 0; }
.mark:focus { outline: none; }
.mark-head { max-width: calc(5.5rem + var(--measure)); margin: 0 auto; }

.mark-head h2 {
  margin: 0;
  font-size: clamp(1.9rem, 4.4vw, 3.4rem);
  line-height: 1.02; letter-spacing: -0.03em; font-weight: 620;
}
/* the rule the ribbon flattened into */
.mark-head h2::after {
  content: ""; display: block;
  height: 2px; margin: clamp(1.4rem, 3vh, 2.2rem) 0 0;
  background: linear-gradient(90deg, var(--pen) 0 22%, #241f1c 22%);
  transform-origin: left;
}
.mark-sub { max-width: var(--measure); margin: 1.4rem 0 0; color: var(--ink-soft); }

.board { max-width: calc(5.5rem + var(--measure)); margin: clamp(2.5rem, 7svh, 4rem) auto 0; }
.board-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem 1rem; padding-bottom: 1.2rem; }
.board-title { margin: 0; font-size: 1.35rem; font-weight: 620; letter-spacing: -.02em; }
.board-what { margin: 0; color: var(--ink-faint); font-size: .95rem; }

.part {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  align-items: start;
  gap: 0 1.25rem;
  padding: clamp(1.1rem, 2.6svh, 1.6rem) 0;
  border-top: 1px solid #1d2126;
}
.part:last-of-type { border-bottom: 1px solid #1d2126; }

.grade {
  grid-row: span 2;
  font-size: clamp(2.8rem, 6vw, 4.2rem);
  line-height: .82; font-weight: 620; letter-spacing: -.045em;
  color: var(--ink);
}
.grade.none { color: var(--ink-faint); font-weight: 400; }

.part-name { margin: .2rem 0 0; font-size: 1.05rem; font-weight: 620; letter-spacing: -.012em; }
.part-promise { margin: .35rem 0 0; color: var(--ink-soft); max-width: var(--measure); font-size: .97rem; }

/* THE HONEST DETAIL IS AVAILABLE, NOT SHOUTED. The old page led every row with what was broken,
   which read as a list of faults rather than as rigour. It is one click away and it is still here. */
.part-more { grid-column: 2; margin: .55rem 0 0; }
.part-more summary {
  display: inline-flex; align-items: center; gap: .45rem;
  cursor: pointer; list-style: none;
  font-size: .82rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-faint);
}
.part-more summary::-webkit-details-marker { display: none; }
.part-more summary::before {
  content: ""; width: 14px; height: 1px; background: var(--pen); transition: width .3s var(--ease);
}
.part-more[open] summary::before { width: 26px; }
.part-more summary:hover { color: var(--ink-soft); }
.part-more p { margin: .6rem 0 0; color: var(--ink-soft); max-width: var(--measure); font-size: .97rem; }
/* `.part-more p` is a class AND a type, so it outranks a bare class and was quietly winning the
   colour here — an abstaining row read exactly like a row that had something to report. */
.part-more p.part-abstain { color: var(--ink-faint); font-style: italic; }

.tally { max-width: calc(5.5rem + var(--measure)); margin: 0 auto; padding-top: 1.6rem; color: var(--ink-faint); font-size: .9rem; }

/* The gutter has to live OUTSIDE the measure, not inside it. .mark puts its padding on the section
   and centres a 5.5rem+measure box within it; .foot is a sibling of .mark rather than a child, so it
   needs a gutter of its own — but adding one inside the same max-width narrowed the footer's text to
   568px and stepped it 48px in from the tally and the last row rule directly above it, both on
   screen at the same time. Widen the box by the gutter it now carries and the edges line up again. */
.foot { max-width: calc(5.5rem + var(--measure) + 2 * clamp(1.25rem, 4vw, 3rem)); margin: 0 auto; padding: clamp(5rem, 14svh, 9rem) clamp(1.25rem, 4vw, 3rem) 5rem; }
.sign { margin-top: 2.4rem; color: var(--ink-soft); font-size: .93rem; }

/* ═══ entrance ══════════════════════════════════════════════════════════════
   Hidden only by the code that has promised to show it again — see app.js. */
.js .hero-type h1 span, .js .hero-type .sub, .js .hero-type .play-hint { opacity: 0; animation: rise .9s var(--ease) forwards; }
.js .hero-type h1 span:nth-child(1) { animation-delay: .05s; }
.js .hero-type h1 span:nth-child(2) { animation-delay: .14s; }
.js .hero-type h1 span:nth-child(3) { animation-delay: .23s; }
.js .hero-type .sub { animation-delay: .38s; }
.js .hero-type .play-hint { animation-delay: .8s; }
@keyframes rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }

.part.will-reveal:not(.in) { opacity: 0; transform: translateY(16px); }
.part.in { opacity: 1; transform: none; transition: opacity .55s var(--ease), transform .55s var(--ease); }
.part.will-reveal:not(.in) .grade { opacity: 0; transform: translateY(6px) scale(.94); }
.part.in .grade { opacity: 1; transform: none; transition: opacity .5s var(--ease) .12s, transform .6s var(--ease) .12s; }

@media (prefers-reduced-motion: reduce) {
  .hero::before { animation: none; }
  .js .hero-type h1 span, .js .hero-type .sub, .js .hero-type .play-hint,
  .part, .part .grade, .part.will-reveal:not(.in), .part.will-reveal:not(.in) .grade {
    animation: none !important; transition: none !important; opacity: 1 !important; transform: none !important;
  }
}

@media (max-width: 34rem) {
  :root { --measure: 21rem; }
  body { font-size: 1rem; }
  .part { grid-template-columns: 3.4rem 1fr; }
  .grade { font-size: 2.5rem; }
  .hero-type { max-width: none; }
}

@media (prefers-contrast: more) {
  :root { --ink-soft: #d6cfc7; --ink-faint: #b3aaa1; }
}

/* ═══ on paper ══════════════════════════════════════════════════════════════
   Browsers drop the background and keep the ink, so without this the page prints as near-white text
   on white — nothing at all — behind a first sheet that is a blank full-height hero. The letters and
   the promises print; the disclosures print exactly as the reader left them, because a page that
   quietly opens all sixteen faults the moment it meets a printer is not the page they were reading. */
@media print {
  :root { --ink: #14171c; --ink-soft: #494e56; --ink-faint: #6b7079; }
  body { background: #fff; font-size: 11pt; }
  .hero { min-height: auto; padding-block: 0 1rem; }   /* padding-block, so the gutter survives and
                                                          the hero lines up with everything below */
  .hero-type { padding-bottom: 0; }
  .hero::before, #loom, .play-hint, .skip { display: none; }
  .rail { position: static; }
  h1 { font-size: 22pt; }
  .mark-head h2 { font-size: 16pt; }   /* h1 is pinned at 22pt, and h2's clamp floor is 30px — without
                                          this the second-level heading prints larger than the first */
  .proof, .mark, .foot { padding-block: 1.2rem 0; }
  /* These three are painted as backgrounds, and the print dialog's background box is off by default,
     so the pen rule, the dot and the sixteen dashes would silently not print. */
  .mark-head h2::after, .dot, .part-more summary::before {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
  .proof img { box-shadow: none; border-color: #d8d8d8; }
  .grade { font-size: 28pt; }
  /* Anything the script hides has to be forced back for paper, and that is TWO places, not one. The
     rows are revealed as they are scrolled to and paper does not scroll. The hero type is hidden until
     its entrance animation fills forwards, which is 1.28s after load — measured by printing at 250ms:
     "One player." came out at 0.02 opacity and the paragraph at 0. A print-to-PDF or a quick Ctrl+P
     lands inside that window and loses the headline. CSS rather than a beforeprint handler, because it
     must hold whether or not the script ran at all. */
  .part, .part .grade { opacity: 1 !important; transform: none !important; }
  .js .hero-type h1 span, .js .hero-type .sub {
    opacity: 1 !important; transform: none !important; animation: none !important;
  }
  .part { break-inside: avoid; border-top-color: #ddd; }
  .part:last-of-type { border-bottom-color: #ddd; }
  .mark-head h2::after { background: linear-gradient(90deg, var(--pen) 0 22%, #ddd 22%); }
  a { color: var(--pen); }
}
