/*
 * results: the docket.
 *
 * A printed docket lying on a dark desk in real perspective. CSS 3D, no WebGL and
 * no canvas: the content is text and numbers, which is the one thing WebGL is worst
 * at and CSS 3D is best at, and it means every figure stays selectable, searchable
 * and readable by a screen reader, with the page's own grain over the top for free.
 *
 * Layers, in order:
 *   1. Base (phones, tablets, every static state): normal flow. Eyebrow, heading,
 *      plain words, docket, honesty strip, caveat, link.
 *   2. Desktop (1024 and up) with motion: a CSS sticky stage one screen tall at the
 *      least (it grows when the typed content needs more), words in cols 1 to 5, the
 *      scene clipped across the whole stage with the docket in cols 8 to 12.
 *   3. Reduced motion, no JS, and no preserve-3d: the flow layout, fully resolved.
 *
 * .rs-clip is the element that clips, and it is deliberately NOT a 3D context:
 * overflow on an element inside a preserve-3d chain flattens the whole scene in
 * Safari. Nothing uses position: fixed inside the 3D context (iOS).
 *
 * Only transform and opacity are animated. The shadow is a pre-blurred gradient
 * element whose opacity and scale change, never a box-shadow or a filter.
 */

.results {
  /* The heading gets its own size token: --fs-h2 at its 6.5rem cap would run to
     three lines in a five column measure and blow the stage height. */
  --rs-fs-h2: clamp(2.4rem, 1rem + 3.4vw, 4.5rem);
  --rs-fs-big: clamp(5.5rem, 2rem + 8vw, 9.5rem);
  --rs-fs-deep: clamp(5rem, 19vw, 20rem);

  --rs-paper: var(--night-2);
  --rs-edge: rgba(238, 237, 232, .13);
  --rs-desk-line: rgba(238, 237, 232, .055);
  --rs-desk-o: .5;

  --rs-top: 0px;
  --rs-head: 0px;
  --rs-gr: 0px;
  --rs-dw: 100%;
  /* A percentage-free twin of --rs-dw. A percentage inside a custom property is
     re-resolved by whatever property finally uses it (height against the block's
     height, font-size against the parent's font-size), so anything that is not a
     width or an inline offset reads this one instead. */
  --rs-dwf: min(460px, calc(100vw - 2 * var(--gx)));
  /* Everything printed on the docket is a fraction of the docket's own width, so the
     object is self similar at every size and its content always fits its proportion.
     The floors keep the smallest type readable on the smallest docket. */
  --rs-pad: calc(var(--rs-dwf) * .061);
  --rs-gap: calc(var(--rs-dwf) * .022);
  --sheen-x: 50%;

  position: relative;
  overflow-x: clip;
  padding-block: var(--section-y);
  /* No data-dock-guard anywhere in this section: it has no call button, so the
     bottom call dock should stay up here. The section reserves its height instead. */
  padding-bottom: calc(var(--section-y) + var(--dock-h));
}

/* ---------- the head row ---------- */

.results__head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px 24px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.results__meta {
  color: var(--fg-soft);
}
/* One line from 768 up, where the head row has the width for it. On a phone the row's
   own wrap takes a long platform and period to a second line instead, rather than
   running the date off the side of a 320px screen with no way to scroll to it. */
@media (min-width: 768px) {
  .results__meta { white-space: nowrap; }
}

/* ---------- the words ---------- */

.results__intro { margin-top: clamp(28px, 7vw, 48px); }
.results__words { grid-column: 1 / -1; }

.results__title {
  font-size: var(--rs-fs-h2);
  max-width: 14ch;
}
.results__plain,
.results__intro .visually-hidden + .results__plain {
  margin-top: clamp(18px, 4vw, 26px);
  font-size: var(--fs-body-l);
  line-height: 1.45;
  max-width: 34ch;
}

.rs-honesty { margin-top: clamp(26px, 7vw, 40px); }
.rs-honesty__row {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 4px 12px;
  align-items: baseline;
  padding: 11px 0;
  border-top: 1px solid var(--rule);
}
.rs-honesty__k { color: var(--fg-soft); padding-top: .18em; }
.rs-honesty__v { font-size: var(--fs-small); line-height: 1.4; margin: 0; }

.results__caveat {
  margin-top: clamp(22px, 6vw, 30px);
  color: var(--fg-soft);
  max-width: 40ch;
}
.results__cta { margin-top: 6px; }

/* ---------- the scene ---------- */

.rs-clip {
  position: relative;
  overflow: clip;
  margin-top: clamp(30px, 8vw, 46px);
  padding-inline: var(--gx);
}
.rs-stage {
  position: relative;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-bottom: 30px;
  perspective: 1000px;
  perspective-origin: 50% 42%;
  transform-style: preserve-3d;
}

/* The out-of-focus background is the receipt's own numbers at huge scale, outlined
   rather than blurred: depth without a filter, and it is the one thing in the room
   that is honestly the receipt's own content. Hidden under 768 (see below). */
.rs-deep {
  position: absolute;
  left: -80%;
  width: 260%;
  bottom: -36%;
  z-index: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2vw;
  transform: translateZ(-900px);
  font-family: var(--f-display);
  font-weight: 900;
  font-stretch: 125%;
  font-size: var(--rs-fs-deep);
  line-height: .82;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1px rgba(238, 237, 232, .07);
  user-select: none;
  pointer-events: none;
}

/* The desk: one element laid back into perspective, hairlines at 24px, masked so it
   fades out at the horizon. */
.rs-desk {
  position: absolute;
  z-index: 0;
  left: -90%;
  width: 280%;
  top: 42%;
  height: 116%;
  transform: translateZ(-520px) rotateX(74deg);
  transform-origin: 50% 0;
  opacity: var(--rs-desk-o);
  background:
    repeating-linear-gradient(0deg, var(--rs-desk-line) 0 1px, transparent 1px 24px),
    repeating-linear-gradient(90deg, var(--rs-desk-line) 0 1px, transparent 1px 24px);
  -webkit-mask-image: radial-gradient(118% 86% at 50% 100%, #000 4%, rgba(0, 0, 0, .5) 44%, transparent 82%);
  mask-image: radial-gradient(118% 86% at 50% 100%, #000 4%, rgba(0, 0, 0, .5) 44%, transparent 82%);
  pointer-events: none;
}

.rs-shadow {
  position: absolute;
  z-index: 1;
  inset: 0;
  transform: translateZ(-40px);
  opacity: .38;
  background: radial-gradient(46% 9% at 50% 94%, rgba(0, 0, 0, .9), rgba(0, 0, 0, .38) 56%, rgba(0, 0, 0, 0) 100%);
  pointer-events: none;
}

.rs-docket {
  position: relative;
  z-index: 2;
  width: var(--rs-dw);
  max-width: 460px;
  /* The designed proportion is a floor, not a cage: a long piece of fine print
     makes the docket taller rather than spilling out of it. In the normal case the
     height is known before the text lays out, so CLS stays 0. */
  min-height: calc(var(--rs-dwf) * 620 / 350);
  margin: 0 auto;
  transform-style: preserve-3d;
  /* A vertical scroll always wins over the drag tilt. */
  touch-action: pan-y;
}

.rs-sheen {
  position: absolute;
  z-index: 3;
  inset: -12%;
  transform: translateZ(90px);
  background: linear-gradient(104deg,
    transparent calc(var(--sheen-x) - 30%),
    rgba(238, 237, 232, .05) var(--sheen-x),
    transparent calc(var(--sheen-x) + 30%));
  pointer-events: none;
}

/* ---------- the docket faces ---------- */

.rs-face {
  display: flex;
  flex-direction: column;
  background: var(--rs-paper);
  border: 1px solid var(--rs-edge);
  border-radius: 3px 3px 0 0;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
}
/* The torn bottom edge: a sawtooth mask in the docket's own surface colour. No image. */
.rs-face::after {
  content: '';
  position: absolute;
  left: -1px;
  right: -1px;
  top: 100%;
  height: 9px;
  background: var(--rs-paper);
  -webkit-mask-image: conic-gradient(from -45deg at 50% 100%, #000 0 90deg, transparent 90deg);
  mask-image: conic-gradient(from -45deg at 50% 100%, #000 0 90deg, transparent 90deg);
  -webkit-mask-size: 12px 9px;
  mask-size: 12px 9px;
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
}
.rs-face--front {
  position: relative;
  padding: calc(var(--rs-pad) * .78) var(--rs-pad) calc(var(--rs-pad) * .54);
}
.rs-face--back {
  position: relative;
  margin-top: 22px;
  padding: calc(var(--rs-pad) * .7);
  gap: 12px;
}

/* The front face is the one in flow: it gives the docket its height, and the back
   face is laid over it, so the two sides are always exactly the same object. */
/* Column, so that the front face fills the docket's designed height when it is the only
   face, and so that a back face still in flow (no html.js) prints under it rather than
   beside it. */
html.js .rs-docket { display: flex; flex-direction: column; }
html.js .rs-face--front { position: relative; flex: 1 1 auto; min-width: 0; }

/*
 * With JS the two faces are one object that turns over. This is keyed to html.js, which
 * the inline script in index.php sets before first paint, and NOT to a class results.js
 * adds at runtime: results.js lands behind six deferred vendor scripts, so a runtime
 * class meant the back face was in normal flow at first paint and collapsed 200-300ms
 * later. That doubled the docket's height on first paint and scored 0.37 CLS on
 * /?only=results, against the zero this section's whole sticky strategy exists to hold
 * (see assets/js/core.js and the note at the top of this file).
 *
 * What the runtime class was trying to buy was reachability: both turn buttons used to
 * ship with the bare hidden attribute, so any failure before results.js ran (a 404 on a
 * vendor file after a partial deploy, a throw in its init) left the back face stacked and
 * invisible with nothing able to reveal it, and the uploaded screenshots unreachable. The
 * footer control now ships as one plain link per approved screenshot instead (see
 * includes/sections/results.php and .rs-turn-row below), visible and focusable at first
 * paint with no script at all, and results.js replaces the row with the turn button as
 * the last thing it does. The stacking can therefore be a first-paint decision again.
 * Measured in the two states that used to lose them, both with three approved shots at
 * 1440x900: with assets/vendor removed, and with a throw forced at the capability check
 * two statements after results.js builds that button, all three uploads are still
 * focusable links, each still pointing at its own file.
 */
html.js .rs-face {
  margin: 0;
  backface-visibility: hidden;
  transform-style: preserve-3d;
}
html.js .rs-face--back { position: absolute; inset: 0; transform: rotateY(180deg); }

/* ---------- the strip: six bands, the curl ---------- */

.rs-band { position: relative; }
.rs-band + .rs-band {
  margin-top: var(--rs-gap);
  padding-top: var(--rs-gap);
  border-top: 1px solid var(--rule);
}
/* The footer carries its own bordered control, so it needs no rule above it. Written
   against the adjacency so it actually wins: `.rs-band + .rs-band` is a class more
   specific than `.rs-band--foot` alone, so the flat selector was drawing the hairline
   and holding a gap's worth of padding above the button anyway. The :not keeps it off
   the empty footer below, which collapses to nothing and must not keep a margin. */
.rs-band + .rs-band--foot:not(.is-bare) {
  margin-top: 12px;
  padding-top: 0;
  border-top: 0;
}
.rs-band--big { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center; }
.rs-band--mark { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: flex-end; gap: calc(var(--rs-dwf) * .02); }
.rs-band--foot { flex: 0 0 auto; }
.rs-band.is-bare { display: none; }
.rs-band--foot.is-bare { display: block; margin: 0; padding: 0; border: 0; }

.rs-docket__meta { color: var(--fg-soft); font-size: max(11px, calc(var(--rs-dwf) * .0283)); }
.rs-docket__who {
  margin-top: calc(var(--rs-dwf) * .012);
  font-family: var(--f-display);
  font-weight: 700;
  font-stretch: 88%;
  font-size: max(14px, calc(var(--rs-dwf) * .037));
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.rs-big { display: flex; flex-direction: column; }
.rs-big__v {
  display: block;
  font-family: var(--f-display);
  font-weight: 900;
  font-stretch: 100%;
  /* Bounded by its own length as well as by the docket. --rs-chars is the character
     count of the typed figure, set inline by includes/sections/results.php, because
     only PHP knows how long the owner's number is. At this weight and tracking a
     digit costs about .62em and the paper gives the figure .878 of the docket's
     width, so the .32 cap pays for about four characters and anything longer has to
     divide: without the third term a six figure count ran off the paper, across the
     desk, and lost its last digit to the viewport edge.
     The 1.4 is calibrated on the tabular digit, which is the content this field is
     for, so it cannot be a guarantee on its own: a cap or a currency letter costs up
     to .95em, half again as much, and twelve of them still overflowed. overflow-wrap
     is the guarantee. It never fires for a figure the divisor already fits, and for
     anything wider the number breaks inside the paper instead of running across the
     desk, so no string the 12 character field accepts can escape the docket. */
  font-size: min(var(--rs-fs-big), calc(var(--rs-dwf) * .32), calc(var(--rs-dwf) * 1.4 / var(--rs-chars, 2)));
  line-height: .82;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
  color: var(--lime);
}
.rs-big__l {
  display: block;
  margin-top: calc(var(--rs-dwf) * .011);
  font-size: max(14px, calc(var(--rs-dwf) * .044));
  line-height: 1.1;
}
/* The lime underscore that draws under the headline. */
.rs-rule {
  display: block;
  width: 100%;
  height: 2px;
  margin-top: calc(var(--rs-dwf) * .024);
  background: var(--lime);
  transform-origin: 0 50%;
}

.rs-second { display: flex; flex-direction: column; }
.rs-second__v {
  font-family: var(--f-display);
  font-weight: 800;
  font-stretch: 100%;
  font-size: max(26px, calc(var(--rs-dwf) * .092));
  line-height: 1;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}
.rs-second__l {
  margin-top: calc(var(--rs-dwf) * .009);
  font-size: max(12px, calc(var(--rs-dwf) * .0304));
  line-height: 1.3;
  color: var(--fg-soft);
}

.rs-rows { display: block; }
.rs-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 14px;
  align-items: baseline;
  padding: calc(var(--rs-dwf) * .0115) 0;
}
.rs-row + .rs-row { border-top: 1px solid var(--rule); }
.rs-row__k { color: var(--fg-soft); padding-top: .1em; font-size: max(11px, calc(var(--rs-dwf) * .0283)); }
.rs-row__v {
  margin: 0;
  font-family: var(--f-display);
  font-weight: 800;
  font-stretch: 88%;
  font-size: max(14px, calc(var(--rs-dwf) * .037));
  line-height: 1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rs-stamp {
  align-self: flex-start;
  transform: rotate(-6deg);
  transform-origin: 0 50%;
}
.rs-stamp__ink {
  display: inline-block;
  padding: .34em .82em .4em;
  border: 1.5px solid currentColor;
  border-radius: 2px;
  font-family: var(--f-display);
  font-weight: 800;
  font-stretch: 75%;
  font-size: max(10px, calc(var(--rs-dwf) * .026));
  line-height: 1;
  letter-spacing: .13em;
  text-transform: uppercase;
}
.rs-fine {
  font-size: max(12px, calc(var(--rs-dwf) * .0304));
  line-height: 1.34;
  color: var(--fg-soft);
  max-width: 42ch;
}

/* The footer control's box. One 48px full width box in every state: with two or three
   approved screenshots the markup puts one plain link per screenshot in this row, which
   with no script of ours is the only way to the second and third (the tab strip that
   reaches them is on the stacked back face and is built by results.js), and results.js
   replaces the whole row with the single turn button. The row and that button have to
   measure the same, or the docket's height would change when the script runs, which is
   the layout shift the note at the top of this file is about. */
.rs-turn-row {
  display: flex;
  gap: 6px;
  min-height: 48px;
}
/* Several links sharing the one box: each takes an equal share of it, and a label too
   long for its share is cut with an ellipsis rather than wrapping, which would make the
   row taller than the button. The turn icon goes with it: at a third of the width there
   is no room for it beside the label. */
.rs-turn-row--many .rs-turn {
  flex: 1 1 0;
  min-width: 0;
  padding-inline: 8px;
  font-size: max(12px, calc(var(--rs-dwf) * .0283));
}
.rs-turn-row--many .rs-turn::after { display: none; }
.rs-turn-row--many .rs-turn__t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/*
 * With no script at all the two faces print in document order (spec 3.10) and every
 * screenshot carries its own link to the full upload directly under it, so this row
 * would be a control offering to show a screenshot that is already on screen. It is
 * only needed where the stylesheet has stacked the faces, and that is keyed to html.js,
 * which the inline script in index.php sets before first paint. The whole band goes, so
 * the footer leaves no gap behind it either.
 */
html:not(.js) .rs-band--foot { display: none; }

.rs-turn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 48px;
  padding: 8px 14px;
  border: 1px solid var(--rule);
  border-radius: 3px;
  font-family: var(--f-display);
  font-weight: 700;
  font-stretch: 88%;
  font-size: max(14px, calc(var(--rs-dwf) * .0326));
  letter-spacing: -0.005em;
  color: inherit;
  /* The front face's control ships as an anchor and becomes a button once results.js
     runs, so both forms are styled here and have to measure the same: an underline
     would be the one difference that moved the box. */
  text-decoration: none;
  transition: background-color .2s var(--ease-soft), border-color .2s var(--ease-soft);
}
.rs-turn::after {
  content: '';
  width: 13px;
  height: 13px;
  border: 1.5px solid currentColor;
  border-radius: 2px;
  transform: skewX(-18deg);
  opacity: .7;
}
.rs-turn:hover { background: rgba(238, 237, 232, .07); border-color: currentColor; }
.rs-turn[hidden] { display: none; }
.rs-turn--back { flex: none; margin-top: auto; }

/* ---------- the back face: the real screenshot ---------- */

.rs-tabs { flex: none; gap: 6px; }
.rs-tab {
  height: 28px;
  padding: 0 10px;
  color: var(--fg-soft);
  border-color: currentColor;
  transition: color .2s var(--ease-soft), background-color .2s var(--ease-soft);
}
.rs-tab[aria-selected='true'] { color: var(--ink); background: var(--lime); border-color: var(--lime); }

.rs-shots { position: relative; flex: 1 1 auto; min-height: 0; transform-style: preserve-3d; }
.rs-shot {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
}
.rs-shot + .rs-shot { margin-top: 18px; }
/* The author display above beats the UA rule for [hidden], so without this the hidden
   attribute changes nothing and the panel keeps its box and its focusable control.
   results.js sets it only where inert is missing (Safari before 15.5, Firefox before
   112, Chrome before 102); there, this is what takes the off panels out of the tab
   order and the accessibility tree. */
.rs-shot[hidden] { display: none; }
/* A screenshot uploaded but not yet ticked as checked. Never rendered for the public
   (see results.php): this is the in-place check for a logged-in admin. */
.rs-shot.is-sample { outline-offset: 4px; }
.rs-shot .sample-stamp { top: 4px; right: 4px; }

.rs-shot__media {
  position: relative;
  /* Ends the preserve-3d chain here: the frame inside clips, and a clipping element
     left inside the chain flattens the scene in Safari. */
  transform-style: flat;
}
.rs-shot__frame { border-radius: 4px; }
/* The shared frame renders its viewport as a span; give it a box so it can be sized
   (same adjustment cover and work make). The capture is pinned to the top of the
   window, so a tall full page export shows its head and the dialog shows the rest. */
.rs-shot__frame .frame-browser__view { display: block; aspect-ratio: 16 / 11; }

.rs-shot__open {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  align-self: flex-start;
  min-height: 40px;
  font-family: var(--f-display);
  font-weight: 700;
  font-stretch: 88%;
  font-size: .875rem;
  color: var(--link);
  text-decoration: none;
}
.rs-shot__open-t { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .2em; }
.rs-shot__cap { color: var(--fg-soft); }

/* Stacked panels on one turning face, so this is scoped the same way the faces are:
   with no html.js the shots print one under another, which is the fallback that keeps
   them reachable. The panels behind stay visible at 35% on purpose (spec 3.9), so
   opacity cannot be what takes them out of the tab order and the accessibility tree:
   results.js marks every panel but the selected one inert, or hides it where inert is
   missing (see .rs-shot[hidden] above). */
html.js .rs-shots { min-height: 1px; }
html.js .rs-shot {
  position: absolute;
  inset: 0;
  margin: 0;
  transform: translateZ(-90px);
  opacity: .35;
  pointer-events: none;
  transition: transform .4s var(--ease-out), opacity .4s var(--ease-out);
}
html.js .rs-shot.is-on { transform: translateZ(0); opacity: 1; pointer-events: auto; }
html.js .rs-shot__media {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  content-visibility: auto;
  contain-intrinsic-size: auto 240px;
}
html.js .rs-shot__frame { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
html.js .rs-shot__frame .frame-browser__bar { flex: none; }
html.js .rs-shot__frame .frame-browser__view { flex: 1 1 auto; aspect-ratio: auto; min-height: 0; }

/* ---------- the full size dialog ---------- */

.rs-dialog {
  width: min(96vw, 1180px);
  max-width: none;
  max-height: 92svh;
  padding: 0;
  border: 1px solid var(--rs-edge);
  border-radius: 4px;
  background: var(--night-2);
  color: var(--paper);
  --focus: var(--lime);
  overflow: hidden;
}
.rs-dialog::backdrop { background: rgba(10, 10, 9, .84); }
.rs-dialog__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 52px;
  padding: 0 16px;
  border-bottom: 1px solid var(--rs-edge);
}
.rs-dialog__title { color: var(--mist); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rs-dialog__close {
  min-height: 40px;
  padding: 0 14px;
  border: 1px solid rgba(238, 237, 232, .3);
  border-radius: 3px;
  font-family: var(--f-display);
  font-weight: 700;
  font-stretch: 88%;
  font-size: .875rem;
  color: var(--paper);
}
.rs-dialog__close:hover { background: rgba(238, 237, 232, .09); }
.rs-dialog__scroll {
  overflow: auto;
  max-height: calc(92svh - 52px);
  background: #fff;
  overscroll-behavior: contain;
}
.rs-dialog__scroll img { width: 100%; height: auto; }

/*
 * Which side is showing. results.js flips .is-back at the half way point of the turn,
 * when the docket is edge on, and immediately when the turn is an instant swap
 * (reduced motion, or no preserve-3d). backface-visibility on its own is not enough:
 * an element with transform-style: preserve-3d is not backface culled as one plane,
 * so the far side would print through mirrored. This also keeps the hidden side out
 * of the tab order and out of the accessibility tree.
 */
html.js .rs-face--back { visibility: hidden; }
html.js .results.is-back .rs-face--back { visibility: visible; }
html.js .results.is-back .rs-face--front { visibility: hidden; }
html.js .results.is-swap .rs-face--back { transform: none; }

/* ---------- hidden starting states (motion only) ---------- */

/*
 * Every rule below is scoped to .results:not(.motion-failed). core.js puts
 * .motion-failed on a section whose init threw, and motion-ready stays set because
 * the rest of the page is fine. Without the guard a throw anywhere in results.js
 * freezes the docket at rotateX(-35deg) inside its own opaque paper: the figures, the
 * fine print and the link to the uploaded screenshot are then invisible and not
 * clickable, which is the one failure this section is not allowed to have. Dropping
 * the guard puts them back in their finished state, animated by nothing.
 */
html.motion-ready .results:not(.motion-failed) .rs-band {
  transform: rotateX(calc(-7deg * var(--i))) translateZ(calc(-6px * var(--i)));
  transform-origin: 50% 0;
}
html.motion-ready .results:not(.motion-failed) .rs-shadow { opacity: .22; transform: translateZ(-40px) scale(1.14); }
html.motion-ready .results:not(.motion-failed) [data-rs-set],
html.motion-ready .results:not(.motion-failed) [data-rs-big],
html.motion-ready .results:not(.motion-failed) [data-rs-tier],
html.motion-ready .results:not(.motion-failed) [data-rs-mark],
html.motion-ready .results:not(.motion-failed) [data-rs-last],
html.motion-ready .results:not(.motion-failed) [data-rs-honesty] { opacity: 0; }
html.motion-ready .results:not(.motion-failed) [data-rs-ink] { opacity: .5; }
html.motion-ready .results:not(.motion-failed) [data-rs-rule] { transform: scaleX(0); }
/* Phones run the curl at 60% of the amounts, matching Depth Drift's convention. */
@media (max-width: 767px) {
  html.motion-ready .results:not(.motion-failed) .rs-band {
    transform: rotateX(calc(-4.2deg * var(--i))) translateZ(calc(-3.6px * var(--i)));
  }
  /* The deep figures are the four largest text nodes in the section, and the
     smallest device gains most from dropping them. */
  .rs-deep { display: none; }
}

/* ---------- tablets ---------- */

@media (min-width: 768px) and (max-width: 1023px) {
  .results__title { max-width: 16ch; }
  .rs-stage { max-width: 620px; }
  .rs-docket { min-height: calc(var(--rs-dwf) * 720 / 460); }
}

/* ---------- desktop with motion: the sticky stage ---------- */

@media (min-width: 1024px) {
  .results { --rs-desk-o: 1; }
  .results__words { grid-column: 1 / span 7; }
  .results__title { max-width: 12ch; }
  .rs-stage { max-width: 520px; }
  .rs-docket { min-height: calc(var(--rs-dwf) * 720 / 460); }
}

@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
  html.js .results {
    /* The sticky room: one screen tall at the least (see the stage below), held for
       one more screen. Sticky, not a GSAP pin: services right after this is pinned at
       +=400%, and a second pin before it would mean two pin orders to keep in document
       order for no gain. CLS stays 0 and nothing needs an mm.add condition. */
    --rs-top: 56px;
    --rs-head: 58px;
    /* The heading already has its own size token so that it cannot blow the stage
       height (see the top of this file). In the sticky room that height is the
       window's, not the width's, so the token takes a bound from the room as well:
       7.2svh is the 65px the designed 900px room already gives, so 1440x900 and
       1280x720 are untouched and only a short window asks the heading to give the
       column back its two lines. The 11ch measure scales with it, so it is still
       two lines. */
    --rs-fs-h2: clamp(2.4rem, min(1rem + 3.4vw, 0.5rem + 7.2svh), 4.5rem);
    --rs-gr: calc((100% - min(100%, var(--maxw))) / 2 + var(--gx));
    --rs-col5: calc((min(100%, var(--maxw)) - 2 * var(--gx) - 11 * var(--gutter)) / 12 * 5 + 4 * var(--gutter));
    /* The docket is as wide as five columns allow, capped at 460, and never so wide
       that its designed proportion plus the fixed-size extras under it outgrow the
       room below the head rule. Those extras are the gap above the turn button, the
       9px torn edge and the stage's own 12px of bottom padding. That gap is itself
       clamp(8px, 1.4svh, 12px) further down this block, so the three come to 33px on
       a window of about 857px or more and 29px on the shortest: 36 is the larger of
       those rounded up, not a sum, and the slack is deliberate because none of it
       scales with the docket while everything the ratio sizes does. The button's own
       48px is not in it: that buys back the air the ratio leaves under the designed
       content, and taking it off as well shrank the docket at every desktop size for
       a case the room now grows to cover. The room grows when the docket still needs
       more, so this only decides when that starts. */
    --rs-dw: min(460px, var(--rs-col5), calc((100svh - var(--rs-top) - var(--rs-head) - 36px) * .59));
    --rs-dwf: min(460px, calc((min(100vw, var(--maxw)) - 2 * var(--gx) - 11 * var(--gutter)) / 12 * 5 + 4 * var(--gutter)), calc((100svh - var(--rs-top) - var(--rs-head) - 36px) * .59));
    /* The roof: what the scene leaves clear above the docket, so the docket starts under
       the head rule instead of painting over it. --rs-head is a flat allowance for a head
       row whose own padding and margin are svh clamps, so on a short window it over
       reserves by about 15px of air the docket could have. On a 900px window there is
       46px of air and nothing to reclaim, so the term is zero there and only reaches its
       9px cap under about 670px of window, where it is the difference between the turn
       button's centre being on screen for the sticky hold and being below the fold. It is
       deliberately not folded into --rs-dw above: the docket's height is set by the typed
       rows and fine print, not by its designed proportion, so widening it would not have
       bought the room back. */
    --rs-roof: calc(var(--rs-top) + var(--rs-head) - clamp(0px, 60px - 7.6svh, 9px));
    min-height: 200svh;
    padding: 0;
    overflow: clip;
  }
  /*
   * One screen is the room's floor, not a cage. Everything in here is typed content:
   * the words column (heading, plain words, honesty strip, caveat, link) and the
   * docket both grow with what the owner writes, and a 1366x768 laptop gives about
   * 668px of window. As a fixed 100svh box with the scene absolutely placed inside
   * it, neither could push it: the caveat was cut at the fold and the link sat
   * outside the section's clip box at every scroll position. So the stage is a grid
   * whose rows size to their content, with the scene spanning all of them so the
   * docket's own height counts too, and the whole room gets taller when it has to.
   * The section's min-height: 200svh still sets the hold, and its own min-height
   * means it grows with the stage rather than clipping it.
   */
  html.js .results__stage {
    position: sticky;
    top: 0;
    min-height: 100svh;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto 1fr;
  }
  html.js .results__head { grid-area: 1 / 1; }
  html.js .results__intro { grid-area: 2 / 1; position: relative; z-index: 2; }
  html.js .results__after { grid-area: 3 / 1; align-self: start; position: relative; z-index: 2; }
  html.js .results__words { grid-column: 1 / span 5; pointer-events: auto; }
  html.js .results__title { max-width: 11ch; }
  /* The words occupy columns 1 to 5, but their blocks are as wide as the stage, so the
     empty right hand half of each lies over the docket and would take the pointer off
     it: with a tall docket that is the turn button. Only the words take the pointer. */
  html.js .results__intro,
  html.js .results__after { pointer-events: none; }

  /* The room's rhythm comes out of the gaps before it comes out of the type: every gap
     down the column is bounded by a fraction of the room as well as by a constant, so
     the words, the honesty strip, the caveat and the link still fit the 668px of window
     a 1366x768 laptop gives. Each constant is the value the designed 900px room already
     had, so nothing moves at 1440x900. */
  html.js .results__head { padding-top: calc(var(--rs-top) + clamp(6px, 2svh, 18px)); }
  html.js .results__head-row { margin-bottom: clamp(8px, 1.8svh, 16px); }
  html.js .results__intro { margin-top: clamp(14px, 3.6svh, 44px); }
  html.js .results__intro .results__plain { margin-top: clamp(12px, 2.9svh, 26px); }
  html.js .rs-honesty { margin-top: clamp(12px, 3.2svh, 36px); }
  html.js .rs-honesty__row { padding-block: clamp(6px, 1.25svh, 11px); }
  html.js .results__caveat { margin-top: clamp(13px, 3.4svh, 30px); }
  /* The gap above the turn button is a constant, not a fraction of the docket, so it is
     the one gap on the strip that can come down on a short window without touching the
     object's self similarity. 1.4svh is the 12px the designed 900px room already gives. */
  html.js .rs-band + .rs-band--foot:not(.is-bare) { margin-top: clamp(8px, 1.4svh, 12px); }

  /* The scene spans the whole room and is clipped to it. Pointer events pass
     through everything except the docket itself. It sits in every row of the grid
     and stays in flow, so the docket it holds is what makes the room taller when the
     docket outgrows one screen, instead of being cut off top and bottom inside it. */
  html.js .rs-clip {
    position: relative;
    grid-area: 1 / 1 / -1 / -1;
    z-index: 1;
    display: flex;
    margin: 0;
    padding: 0;
    pointer-events: none;
  }
  /* The docket is the scene's one in-flow child, centred in the room below the head
     rule by the flex box itself. Everything else is absolutely placed against the
     stage's padding box, so the background layers still cover the whole room. The
     bottom padding is the torn edge's, which is drawn below the docket's own box. */
  html.js .rs-stage {
    position: relative;
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    max-width: none;
    margin: 0;
    padding: var(--rs-roof) var(--rs-gr) 12px 0;
    perspective: 1100px;
  }
  html.js .rs-docket {
    position: relative;
    flex: none;
    /* auto inline margins would absorb the flex free space and centre it */
    margin-inline: 0;
    width: var(--rs-dw);
    max-width: none;
    pointer-events: auto;
  }
  /* The shadow fills the same vertical band the docket is centred in, and the
     ellipse is drawn near the foot of that band, where the docket's bottom edge lands
     whenever the room is the designed one screen. */
  html.js .rs-shadow {
    inset: var(--rs-roof) calc(var(--rs-gr) - var(--rs-dw) * .15) 0 auto;
    width: calc(var(--rs-dw) * 1.3);
  }
  html.js .rs-face--back { margin-top: 0; padding: calc(var(--rs-pad) * .6); }
}

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  /* No sticky stage, no curl, no tilt, no sheen travel. The docket is flat and face
     on, fully unrolled, every figure at its final state. The desk and the deep
     figures still render: they are a still background, not motion. */
  .rs-shot { transition: none; }
  .rs-turn { transition: none; }
}

/* ---------- no preserve-3d ---------- */

@supports not (transform-style: preserve-3d) {
  /* No 3D: the docket is a plain card in normal flow, and the room around it goes.
     The selectors are doubled because the sticky room is scoped to html.js. */
  html.js .results,
  .results {
    --rs-top: 0px;
    --rs-head: 0px;
    min-height: 0;
    padding-block: var(--section-y);
    padding-bottom: calc(var(--section-y) + var(--dock-h));
  }
  html.js .results__stage,
  .results__stage { position: static; display: block; min-height: 0; }
  .rs-deep,
  .rs-desk,
  .rs-shadow,
  .rs-sheen { display: none; }
  html.js .rs-clip,
  .rs-clip {
    position: relative;
    inset: auto;
    display: block;
    margin-top: clamp(30px, 8vw, 46px);
    padding-inline: var(--gx);
    pointer-events: auto;
  }
  html.js .rs-stage,
  .rs-stage {
    position: relative;
    inset: auto;
    display: block;
    max-width: 520px;
    margin-inline: auto;
    padding: 0;
    perspective: none;
  }
  html.js .rs-docket,
  .rs-docket {
    position: relative;
    right: auto;
    margin-inline: auto;
    width: 100%;
    max-width: 460px;
    height: auto;
  }
}
