/* Rub-a-Dub Duck interface tokens.
 *
 * From brand/BRAND.md, and the rules are constraints rather than suggestions:
 *
 *  - The interface is greyscale until something is an exception. Colour carries meaning
 *    or it is not used.
 *  - Duck yellow means exactly one thing: "your turn". It is permitted in two places --
 *    the mark itself, and the 1px rule along the top of the header. Nowhere else.
 *  - There is no amber status colour. The warning tier is #e8873a, roughly 35 degrees
 *    away in hue, and never placed next to the mark.
 *  - Low confidence carries no hue at all: a dotted underline and a hatch. Absence of
 *    colour is the signal.
 *  - Rows are 28px. Radius never exceeds 3px. There is one shadow token.
 *  - The duck and a number never occupy the same screen region.
 */
:root {
  --ink: #e7e9ec;
  --ink-muted: #8a9099;
  --ink-dim: #5b626b;
  --bg: #0b0d10;
  --surface: #12151a;
  --raised: #171b21;
  --line: #23272e;
  --line-strong: #333840;
  --duck: #f2c21b;
  --warn: #e8873a;
  --bad: #cf5a54;
  --good: #7c8b7a;
  --row: 28px;
  --radius: 3px;
  --shadow: 0 1px 0 rgba(0, 0, 0, 0.45);
  --gap: 16px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

html { color-scheme: dark; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 13px/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
}

/* Numbers are tabular everywhere. A column of figures that shifts is unreadable. */
.num, td.num, .mono, time, code { font-family: var(--mono); font-variant-numeric: tabular-nums; }

a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--line-strong); }
a:hover { border-bottom-color: var(--ink-muted); }

/* ------------------------------------------------------------------- the shell
 *
 * A sidebar rather than a row of links, because the sections are not peers: the work a
 * customer does, the tools they reach for occasionally, and the administration of the
 * service are three different jobs and a flat nav made them look like one.
 */

.shell { display: grid; grid-template-columns: 210px minmax(0, 1fr); min-height: 100vh; }

header.app {
  grid-column: 1 / -1;
  border-top: 1px solid var(--duck); /* one of exactly two permitted uses */
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  display: flex; align-items: center; gap: 12px;
  padding: 0 14px; height: 42px;
  position: sticky; top: 0; z-index: 20;
}
header.app .mark { display: flex; align-items: center; gap: 8px; border: 0; }
header.app .mark img { width: 20px; height: 20px; display: block; }
header.app .mark b { font-weight: 600; font-size: 12px; letter-spacing: 0.01em; }
header.app .spacer { flex: 1; }
header.app .badge {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.04em;
  color: var(--ink-dim); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 2px 6px; text-transform: uppercase;
}
header.app .badge.warn { color: var(--warn); border-color: var(--warn); }

nav.rail {
  border-right: 1px solid var(--line); background: var(--surface);
  padding: 14px 0 40px; position: sticky; top: 42px; align-self: start;
  height: calc(100vh - 42px); overflow-y: auto;
}
nav.rail .group { margin: 0 0 18px; }
nav.rail .group > span {
  display: block; padding: 0 14px 6px; font-size: 10px; color: var(--ink-dim);
  text-transform: uppercase; letter-spacing: 0.09em;
}
nav.rail a {
  display: flex; align-items: center; justify-content: space-between;
  height: var(--row); padding: 0 14px; border: 0;
  color: var(--ink-muted); font-size: 12px;
  border-left: 2px solid transparent;
}
nav.rail a:hover { color: var(--ink); background: var(--raised); }
nav.rail a[aria-current="page"] {
  color: var(--ink); background: var(--raised); border-left-color: var(--ink-muted);
}
nav.rail a .count { font-family: var(--mono); font-size: 11px; color: var(--ink-dim); }
/* A number a person has to act on wears the "your turn" colour. */
nav.rail a .count.turn { color: var(--duck); }

.content { min-width: 0; padding: 0 20px 64px; }

/* --------------------------------------------------------------- page furniture */

.crumbs { display: flex; align-items: center; gap: 7px; height: 34px;
          font-size: 11px; color: var(--ink-dim); font-family: var(--mono); }
.crumbs a { border: 0; color: var(--ink-dim); }
.crumbs a:hover { color: var(--ink-muted); }
.crumbs .sep { opacity: 0.5; }

.page-head {
  display: flex; align-items: flex-start; gap: var(--gap);
  padding: 4px 0 18px; border-bottom: 1px solid var(--line); margin-bottom: 20px;
}
.page-head .who { flex: 1; min-width: 0; }
.page-head h1 { font-size: 17px; font-weight: 600; margin: 0 0 3px; letter-spacing: -0.01em; }
.page-head p { margin: 0; color: var(--ink-muted); max-width: 72ch; font-size: 12px; }
.page-head .action { flex-shrink: 0; padding-top: 2px; }

/* A row of figures. Never placed in the same region as the duck. */
.stats { display: flex; gap: 0; border: 1px solid var(--line); border-radius: var(--radius);
         background: var(--surface); overflow: hidden; margin: 0 0 var(--gap); }
.stats > div { padding: 9px 14px; border-right: 1px solid var(--line); min-width: 92px; }
.stats > div:last-child { border-right: 0; }
.stats dt { display: block; font-size: 10px; color: var(--ink-dim);
            text-transform: uppercase; letter-spacing: 0.07em; margin-bottom: 3px; }
.stats dd { margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums;
            font-size: 16px; letter-spacing: -0.01em; }
.stats dd.turn { color: var(--duck); }
.stats dd.bad { color: var(--bad); }

/* ----------------------------------------------------------------- surfaces */

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.panel > .head {
  display: flex; align-items: center; gap: 8px;
  padding: 0 12px; height: 32px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-muted); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.07em;
}
.panel > .body { padding: 12px; }

/* -------------------------------------------------------------------- tables */

table { width: 100%; border-collapse: collapse; }
thead th {
  text-align: left; font-weight: 500; font-size: 11px; color: var(--ink-dim);
  text-transform: uppercase; letter-spacing: 0.06em;
  padding: 0 12px; height: var(--row); border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
tbody td { padding: 0 12px; height: var(--row); border-bottom: 1px solid var(--line); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--raised); }
td.num, th.num { text-align: right; }
td.wrap { height: auto; padding-top: 6px; padding-bottom: 6px; }
.scroll { overflow-x: auto; }

/* ------------------------------------------------------------------- status */

.state { font-family: var(--mono); font-size: 11px; letter-spacing: 0.02em; }
.state.done { color: var(--good); }
.state.skipped { color: var(--ink-dim); }
.state.pending, .state.ready { color: var(--ink-muted); }
.state.failed { color: var(--bad); }
/* Stale must be visually distinct from pending: approving a stale render is the silent
   bug that ships the wrong audio. No hue, a hatch, so it reads as "not trustworthy". */
.state.stale {
  color: var(--ink-muted);
  background-image: repeating-linear-gradient(45deg, transparent, transparent 3px,
    rgba(255,255,255,.07) 3px, rgba(255,255,255,.07) 6px);
  padding: 1px 4px; border-radius: var(--radius);
}

.sev { font-family: var(--mono); font-size: 11px; }
.sev.warning { color: var(--warn); }
.sev.error { color: var(--bad); }

/* A claim the product does not yet support carries a dotted underline, never a colour. */
.unproven { border-bottom: 1px dotted var(--line-strong); }

/* -------------------------------------------------------------------- forms */

label { display: block; font-size: 11px; color: var(--ink-muted); margin: 0 0 4px;
        text-transform: uppercase; letter-spacing: 0.06em; }
input[type="text"], select, textarea {
  width: 100%; background: var(--bg); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 6px 8px; font: 13px/1.4 var(--mono);
}
input:focus, select:focus, textarea:focus { outline: 1px solid var(--ink-muted); outline-offset: -1px; }
.field { margin-bottom: 14px; }
.field .hint { color: var(--ink-dim); font-size: 11px; margin-top: 4px; }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); }

button {
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 7px 14px; font: 500 12px var(--sans); cursor: pointer;
}
button:hover { border-color: var(--ink-muted); }
/* Yellow means "your turn", so the one action a person is being asked to take wears it. */
button.turn { border-color: var(--duck); color: var(--duck); }
button.turn:hover { background: rgba(242, 194, 27, 0.07); }

/* ------------------------------------------------------------- empty states */

.empty { display: flex; align-items: center; gap: 18px; padding: 28px 12px; }
.empty img { width: 56px; height: 56px; opacity: 0.9; }
.empty .said { color: var(--ink-muted); max-width: 52ch; }
.empty .said strong { color: var(--ink); font-weight: 600; display: block; margin-bottom: 3px; }

/* ------------------------------------------------------------------ notices */

.notice {
  border: 1px solid var(--line-strong); border-left-width: 2px;
  border-radius: var(--radius); padding: 10px 12px; margin: 0 0 var(--gap);
  color: var(--ink-muted); font-size: 12px;
}
.notice.caveat { border-left-color: var(--warn); }
.notice.bad { border-left-color: var(--bad); color: var(--ink); }
.notice strong { color: var(--ink); }

dl.facts { display: grid; grid-template-columns: max-content 1fr; gap: 3px 18px; margin: 0; }
dl.facts dt { color: var(--ink-dim); font-size: 11px; text-transform: uppercase;
              letter-spacing: 0.06em; align-self: center; }
dl.facts dd { margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums;
              overflow-wrap: anywhere; }

footer.app { border-top: 1px solid var(--line); margin-top: 40px; padding: 14px var(--gap);
             color: var(--ink-dim); font-size: 11px; display: flex; gap: var(--gap);
             max-width: 1120px; margin-left: auto; margin-right: auto; }
footer.app a { border: 0; color: var(--ink-dim); }
footer.app a:hover { color: var(--ink-muted); }

@media (max-width: 720px) {
  .row2 { grid-template-columns: 1fr; }
  header.app nav { gap: 10px; }
}


/* ------------------------------------------------------------------ the sidebar shell
 * Narrow viewports collapse the rail to a scrolling strip rather than hiding it: an
 * administrator on a laptop still needs to reach every section.
 */
@media (max-width: 860px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  nav.rail { position: static; height: auto; border-right: 0;
             border-bottom: 1px solid var(--line); display: flex; gap: 0;
             overflow-x: auto; padding: 0; }
  nav.rail .group { display: flex; margin: 0; }
  nav.rail .group > span { display: none; }
  nav.rail a { border-left: 0; border-bottom: 2px solid transparent; white-space: nowrap; }
  nav.rail a[aria-current="page"] { border-left: 0; border-bottom-color: var(--ink-muted); }
  .content { padding: 0 14px 48px; }
}

/* =============================================================================
 * The public face: landing page and the sign-in pages.
 *
 * A different job from the working interface. The tool is dense and greyscale because
 * a mixer scanning 1,500 rows needs it to be. The landing page has to place the thing
 * for somebody who has never seen it, so it is given room: larger type, real
 * whitespace, and figures set big enough to read as claims rather than as table cells.
 *
 * Same tokens throughout, so the two read as one product. The duck yellow is used as a
 * hairline accent here rather than as the "your turn" signal it is inside the app; that
 * is a deliberate extension of the brand rule for the public pages, where there is no
 * queue and nothing is waiting on anyone.
 * ========================================================================== */

.public { --measure: 1160px; }

.public header.site {
  border-top: 1px solid var(--duck);
  border-bottom: 1px solid var(--line);
  background: rgba(11, 13, 16, 0.86);
  backdrop-filter: saturate(140%) blur(10px);
  position: sticky; top: 0; z-index: 30;
}
.public header.site .inner {
  max-width: var(--measure); margin: 0 auto;
  display: flex; align-items: center; gap: 22px;
  height: 54px; padding: 0 22px;
}
.public header.site .mark { display: flex; align-items: center; gap: 9px; border: 0; }
.public header.site .mark img { width: 24px; height: 24px; }
.public header.site .mark b {
  font-weight: 600; font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase;
}
.public header.site nav { display: flex; gap: 20px; margin-left: auto; }
.public header.site nav a {
  border: 0; color: var(--ink-muted); font-size: 11px;
  letter-spacing: 0.1em; text-transform: uppercase;
}
.public header.site nav a:hover { color: var(--ink); }
.public header.site .enter {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 5px 13px; font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink);
}
.public header.site .enter:hover { border-color: var(--duck); color: var(--duck); }

.band { border-bottom: 1px solid var(--line); }
.band > .inner { max-width: var(--measure); margin: 0 auto; padding: 0 22px; }
.band.alt { background: var(--surface); }

/* ------------------------------------------------------------------------ hero */

.hero { padding: 92px 0 76px; }
.hero .kicker {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--duck); margin: 0 0 22px;
}
.hero h1 {
  font-size: clamp(34px, 6vw, 62px); line-height: 1.03; margin: 0 0 22px;
  letter-spacing: -0.028em; font-weight: 600; max-width: 20ch;
}
.hero h1 span { color: var(--ink-dim); }
.hero p.stand {
  font-size: clamp(15px, 1.7vw, 19px); line-height: 1.55; color: var(--ink-muted);
  max-width: 64ch; margin: 0 0 30px;
}
.hero .actions { display: flex; flex-wrap: wrap; gap: 11px; align-items: center; }
.hero .actions a { border: 0; }
.cta {
  display: inline-block; border: 1px solid var(--duck); color: var(--duck);
  border-radius: var(--radius); padding: 10px 20px; font-size: 12px;
  letter-spacing: 0.1em; text-transform: uppercase;
}
.cta:hover { background: rgba(242, 194, 27, 0.08); }
.cta.quiet { border-color: var(--line-strong); color: var(--ink-muted); }
.cta.quiet:hover { border-color: var(--ink-muted); color: var(--ink); background: none; }

/* ------------------------------------------------------------------- headings */

.band h2.section {
  font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ink-dim); margin: 0 0 26px; font-weight: 500;
}
.band h3 {
  font-size: clamp(21px, 2.6vw, 29px); line-height: 1.2; letter-spacing: -0.018em;
  margin: 0 0 14px; font-weight: 600; max-width: 30ch;
}
.band p.body { color: var(--ink-muted); max-width: 70ch; margin: 0 0 14px; font-size: 14px;
               line-height: 1.65; }
.band p.body strong { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------------------- figures */

.figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1px;
           background: var(--line); border: 1px solid var(--line);
           border-radius: var(--radius); overflow: hidden; }
.figures > div { background: var(--bg); padding: 20px 20px 18px; }
.figures .n {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: clamp(26px, 3.4vw, 38px); letter-spacing: -0.03em; line-height: 1;
  margin: 0 0 8px;
}
.figures .n.accent { color: var(--duck); }
.figures .what { font-size: 12px; color: var(--ink-muted); line-height: 1.45; }

/* ----------------------------------------------------------------------- grids */

.cols { display: grid; gap: 30px; }
.cols.two { grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); }
.cols.three { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }

.step { border-left: 1px solid var(--line-strong); padding: 0 0 0 18px; }
.step .no {
  font-family: var(--mono); font-size: 11px; color: var(--duck);
  letter-spacing: 0.14em; margin: 0 0 7px;
}
.step h4 { margin: 0 0 7px; font-size: 14px; font-weight: 600; letter-spacing: -0.008em; }
.step p { margin: 0; color: var(--ink-muted); font-size: 13px; line-height: 1.6; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chips span {
  font-family: var(--mono); font-size: 11px; color: var(--ink-muted);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 3px 8px;
}
.chips span.pack { color: var(--ink-dim); border-style: dashed; }

.plainlist { margin: 0; padding: 0; list-style: none; }
.plainlist li {
  padding: 11px 0; border-bottom: 1px solid var(--line);
  color: var(--ink-muted); font-size: 13px; line-height: 1.6;
}
.plainlist li:last-child { border-bottom: 0; }
.plainlist li strong { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------------------- honesty */


/* ----------------------------------------------------------------- auth screens */

.auth { min-height: 100vh; display: grid; place-items: center; padding: 40px 20px 72px; }
.auth .card { width: 100%; max-width: 396px; }
.auth h1 { font-size: 19px; margin: 0 0 5px; letter-spacing: -0.012em; text-align: center; }
.auth .sub { color: var(--ink-muted); font-size: 12.5px; margin: 0 0 22px; text-align: center; }
.auth .panel { box-shadow: var(--shadow); }
.auth button[type="submit"] { width: 100%; padding: 10px; margin-top: 4px; }
.auth .row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 0 14px;
}
.auth .row label { margin: 0; text-transform: none; letter-spacing: 0; font-size: 12px;
                   color: var(--ink-muted); display: flex; align-items: center; gap: 7px; }
.auth .row input[type="checkbox"] { width: auto; }
.auth .row a { font-size: 12px; border: 0; color: var(--ink-muted); }
.auth .row a:hover { color: var(--ink); }
.auth .below { margin: 16px 0 0; text-align: center; font-size: 12.5px; color: var(--ink-muted); }
.auth .below a { border-bottom-color: var(--line-strong); }
.auth .aside {
  margin-top: 13px; border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px; font-size: 11.5px; color: var(--ink-dim); line-height: 1.6;
}
.auth .aside strong { color: var(--ink-muted); }

footer.site { border-top: 1px solid var(--line); background: var(--surface); }
footer.site .inner {
  max-width: var(--measure); margin: 0 auto; padding: 28px 22px;
  display: flex; flex-wrap: wrap; gap: 20px; align-items: center;
  color: var(--ink-dim); font-size: 11.5px;
}
footer.site a { border: 0; color: var(--ink-dim); }
footer.site a:hover { color: var(--ink-muted); }
footer.site .spacer { flex: 1; }

@media (max-width: 720px) {
  .public header.site nav { display: none; }
  .hero { padding: 58px 0 46px; }
  .band > .inner { padding-top: 44px; padding-bottom: 44px; }
}

/* =============================================================================
 * Water, depth, and film furniture. Landing page only.
 *
 * Pure CSS and inline SVG. No library, nothing to audit, nothing to download.
 *
 * On the brand rule: BRAND.md forbids animating the mark inside the working interface,
 * and that stands. The ban exists so a mascot never appears next to a rejection or a
 * meter. Out here there is no queue, no number and nothing waiting on anyone, so the
 * duck is allowed to float. Inside the app it still does not move.
 *
 * Every animation is switched off under prefers-reduced-motion. A moving background is
 * a genuine accessibility problem for some people, and a decorative one for everyone.
 * ========================================================================== */

.tank {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 14% -10%, rgba(242, 194, 27, 0.09), transparent 58%),
    radial-gradient(90% 70% at 88% 4%, rgba(60, 120, 170, 0.14), transparent 62%),
    linear-gradient(180deg, #0c1016 0%, #0a0d12 58%, var(--bg) 100%);
}

/* Caustics: two slow, offset gradient sheets. Reads as light through moving water and
 * costs nothing, where a canvas simulation would cost a frame budget on every scroll. */
.tank::before,
.tank::after {
  content: "";
  position: absolute;
  inset: -26% -18% auto -18%;
  height: 150%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(38% 24% at 22% 30%, rgba(120, 190, 235, 0.10), transparent 70%),
    radial-gradient(30% 20% at 68% 58%, rgba(242, 194, 27, 0.07), transparent 72%),
    radial-gradient(26% 18% at 45% 80%, rgba(120, 190, 235, 0.06), transparent 74%);
  filter: blur(26px);
  animation: drift 34s ease-in-out infinite alternate;
}
.tank::after {
  animation-duration: 47s;
  animation-direction: alternate-reverse;
  opacity: 0.68;
  transform: scaleX(-1);
}


/* The waterline: a hairline with a slow sheen crossing it. */
.waterline {
  position: relative;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong) 12%,
              var(--line-strong) 88%, transparent);
  overflow: hidden;
}
.waterline::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(242, 194, 27, 0.55), transparent);
  width: 34%;
  animation: sheen 11s linear infinite;
}
@keyframes sheen {
  from { transform: translateX(-120%); }
  to   { transform: translateX(420%); }
}

/* ------------------------------------------------------------------ the duck, afloat */

.float { position: relative; width: 236px; height: 236px; perspective: 700px; }
.float .duck {
  width: 100%; height: 100%; display: block;
  /* Depth without a 3D asset: a warm key from upper left, a cool bounce from below, and
   * a contact shadow that stays put while the duck moves over it. */
  filter:
    drop-shadow(-7px -5px 10px rgba(255, 228, 150, 0.16))
    drop-shadow(9px 14px 20px rgba(0, 0, 0, 0.62))
    drop-shadow(0 2px 2px rgba(120, 190, 235, 0.22));
  transform-style: preserve-3d;
  animation: bob 6.4s ease-in-out infinite;
  will-change: transform;
}

/* Concentric rings leaving the waterline where the duck sits. */
.float .ripple {
  position: absolute; left: 50%; bottom: 14px;
  width: 116px; height: 24px; margin-left: -58px;
  border: 1px solid rgba(140, 200, 240, 0.30);
  border-radius: 50%;
  animation: spread 5.2s ease-out infinite;
}
.float .ripple:nth-of-type(2) { animation-delay: 1.75s; }
.float .ripple:nth-of-type(3) { animation-delay: 3.5s; }
@keyframes spread {
  0%   { transform: scale(0.42); opacity: 0; }
  18%  { opacity: 0.75; }
  100% { transform: scale(1.85); opacity: 0; }
}

/* A squashed, dimmed, blurred copy of the mark, sitting under the waterline. */
.float .reflection {
  position: absolute; left: 0; top: 78%;
  width: 100%; height: 100%;
  transform: scaleY(-0.42);
  opacity: 0.16;
  filter: blur(3px);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 62%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 62%);
  pointer-events: none;
}

.hero .withduck {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 44px; align-items: center;
}

/* ------------------------------------------------------------------ film furniture */

/* Sprocket perforations, as a repeating gradient. No image request. */

.tcstrip {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em;
  color: var(--ink-dim); text-transform: uppercase;
}
.tcstrip .tc {
  color: var(--duck); font-variant-numeric: tabular-nums;
  border: 1px solid rgba(242, 194, 27, 0.32); border-radius: var(--radius);
  padding: 3px 9px;
}
.tcstrip .sep { opacity: 0.4; }

/* A waveform, drawn from bars. Heights are fixed so it reads as one specific signal
 * rather than as decoration that redraws differently on every load. */
.wave { display: flex; align-items: center; gap: 2px; height: 30px; }
.wave i {
  display: block; width: 2px; border-radius: 1px;
  background: linear-gradient(180deg, rgba(242,194,27,0.55), rgba(120,190,235,0.35));
  animation: pulse 2.6s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { transform: scaleY(1); opacity: 0.55; }
  50%      { transform: scaleY(1.5); opacity: 0.95; }
}

.slate {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); padding: 14px 16px;
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-muted);
  display: grid; grid-template-columns: max-content 1fr; gap: 5px 16px;
}
.slate dt { color: var(--ink-dim); text-transform: uppercase; letter-spacing: 0.08em; }
.slate dd { margin: 0; color: var(--ink); font-variant-numeric: tabular-nums; }

/* Depth on the figure panels: a hairline top edge catching the light. */
.figures > div { position: relative; }
.figures > div::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.07), transparent);
}


/* A moving background is a real accessibility problem for some people. Everything above
 * is decoration, so all of it stops. */
@media (prefers-reduced-motion: reduce) {
  .tank::before, .tank::after,
  .waterline::after,
  .float .duck, .float .ripple, .wave i {
    animation: none !important;
  }
  .float .duck { transform: none; }
  .float .ripple { opacity: 0.28; }
}

/* The auth screens share the landing page's shell, so the duck floats there too, smaller
 * and centred above the card. */
.auth { min-height: calc(100vh - 54px - 86px); }
.float.small { width: 84px; height: 84px; margin: 0 auto 18px; }
.float.small .ripple { width: 58px; height: 13px; margin-left: -29px; bottom: 8px; }
.auth .card > h1 { margin-top: 0; }

/* =============================================================================
 * Atmosphere: grain, vignette, and a second accent temperature.
 *
 * Grain is the cheapest cinematic cue there is and the most effective. A fixed SVG
 * turbulence tile at very low opacity, sitting over everything, stops large flat fields
 * of near-black looking like a web page and makes them look like a projected image.
 * It does not move, because moving grain over text costs legibility and battery.
 * ========================================================================== */

:root {
  --cool: #6fb4dc;          /* water, the second accent. Never a status colour. */
  --cool-dim: rgba(111, 180, 220, 0.16);
  --warm-dim: rgba(242, 194, 27, 0.14);
}

body.public::after {
  content: "";
  position: fixed; inset: 0; z-index: 9999;
  pointer-events: none;
  opacity: 0.042;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");
}

/* A vignette on the atmospheric bands, so the eye lands in the middle of the frame. */
.tank::after {
  box-shadow: inset 0 0 200px 60px rgba(0, 0, 0, 0.55);
}

/* --------------------------------------------------------------------- the hero */

.hero { padding: 116px 0 88px; }
.hero .kicker {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.3em;
  text-transform: uppercase; color: var(--duck); margin: 0 0 28px;
}
.hero .kicker::before {
  content: ""; width: 26px; height: 1px; background: var(--duck); opacity: 0.75;
}
.hero h1 {
  font-size: clamp(38px, 7.2vw, 78px);
  line-height: 0.98;
  letter-spacing: -0.036em;
  font-weight: 650;
  margin: 0 0 26px;
  max-width: 17ch;
  /* A vertical wash so the top of the type catches the light and the base sits back. */
  background: linear-gradient(176deg, #ffffff 4%, #cfd4da 46%, #8d949c 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero p.stand { font-size: clamp(15px, 1.75vw, 20px); line-height: 1.5; max-width: 58ch; }

.float .duck {
  filter:
    drop-shadow(-9px -7px 13px rgba(255, 228, 150, 0.20))
    drop-shadow(12px 20px 30px rgba(0, 0, 0, 0.68))
    drop-shadow(0 3px 3px rgba(111, 180, 220, 0.26));
}
/* The pool the duck sits in: an elliptical pane of cool light under the waterline. */
.float::after {
  content: "";
  position: absolute; left: 50%; bottom: 6px; margin-left: -88px;
  width: 176px; height: 30px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(111, 180, 220, 0.20), transparent 72%);
  filter: blur(5px);
}

/* --------------------------------------------------------------- section rhythm */

.band h2.section {
  display: flex; align-items: baseline; gap: 14px;
  font-size: 10.5px; letter-spacing: 0.26em; margin-bottom: 30px;
}
.band h2.section::after {
  content: ""; flex: 1; height: 1px;
  background: linear-gradient(90deg, var(--line-strong), transparent);
}
.band h3 {
  font-size: clamp(23px, 3vw, 34px); line-height: 1.14; letter-spacing: -0.024em;
  margin-bottom: 16px;
}

/* --------------------------------------------------------------------- figures */

.figures > div { padding: 26px 22px 22px; }
.figures .n {
  font-size: clamp(32px, 4.4vw, 52px); letter-spacing: -0.045em; font-weight: 500;
}
.figures .n.accent { color: var(--duck); }
.figures .what {
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-dim); line-height: 1.5;
}

/* =============================================================================
 * One subtitle file, many language versions. The diagram, not a paragraph.
 * ========================================================================== */

.fanout { display: block; }

.srtfile {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: linear-gradient(180deg, #11151b, #0d1116);
  box-shadow: 0 18px 40px -22px rgba(0, 0, 0, 0.9);
  overflow: hidden;
}
.srtfile .name {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 13px; border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 11px; color: var(--ink-muted);
  background: rgba(255, 255, 255, 0.015);
}
.srtfile .name b { color: var(--ink); font-weight: 500; }
.srtfile .name .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--duck); }
.srtfile pre {
  margin: 0; padding: 14px 13px; font-family: var(--mono);
  font-size: 11.5px; line-height: 1.75; color: var(--ink-muted);
  overflow-x: auto;
}
.srtfile pre .n  { color: var(--ink-dim); }
.srtfile pre .tc { color: var(--cool); }
.srtfile pre .tx { color: var(--ink); }

.versions { display: flex; flex-direction: column; gap: 8px; }

.illustration {
  margin: 18px 0 0; padding: 11px 13px;
  border: 1px dashed var(--line-strong); border-radius: var(--radius);
  font-size: 11.5px; color: var(--ink-dim); line-height: 1.6;
}
.illustration strong { color: var(--ink-muted); }

/* Two-up explanation blocks under the diagram. */
.pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
        gap: 26px; margin-top: 38px; }
.pair .one { border-top: 1px solid var(--line-strong); padding-top: 16px; }
.pair .one h4 {
  margin: 0 0 8px; font-size: 13px; font-weight: 600; letter-spacing: -0.006em;
}
.pair .one p { margin: 0; color: var(--ink-muted); font-size: 13px; line-height: 1.65; }

/* Eleven steps do not divide evenly into three columns, and an orphan on its own row
 * looks like a mistake. The last step is the one a customer cares about most, so it gets
 * the full width and the accent instead of being tucked into a corner. */
.cols.three > .step:last-child {
  grid-column: 1 / -1;
  border-left-color: var(--duck);
  background: linear-gradient(90deg, var(--warm-dim), transparent 42%);
  padding: 14px 0 14px 18px;
}
.cols.three > .step:last-child p { max-width: 78ch; }

/* =============================================================================
 * Depth, and a look at the instruments.
 *
 * The temptation with "make it 3D" is to add shine. What actually reads as a serious
 * film-technology company is showing the tools: a pitch contour, a null-test residual, a
 * channel matrix. So the depth here is in service of six panels that each show a real
 * mechanism, tilted in perspective and lit from one direction.
 *
 * All of it is inline SVG and CSS transforms. No model files, no WebGL, no library.
 * ========================================================================== */

.stage3d { perspective: 1400px; perspective-origin: 50% 40%; }

/* ---------------------------------------------------------- the instrument panels */

.rig {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(276px, 1fr));
  gap: 18px;
  transform-style: preserve-3d;
}

.gauge {
  position: relative;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.035), transparent 34%),
    linear-gradient(180deg, #12161c, #0c1015);
  box-shadow:
    0 26px 48px -28px rgba(0, 0, 0, 0.95),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
  padding: 15px 16px 13px;
  transform: rotateX(5deg) rotateY(-3deg) translateZ(0);
  transform-style: preserve-3d;
  transition: transform 340ms cubic-bezier(.2,.7,.2,1), border-color 340ms;
  will-change: transform;
}
.rig > .gauge:nth-child(even) { transform: rotateX(5deg) rotateY(3deg); }
.gauge:hover {
  transform: rotateX(0deg) rotateY(0deg) translateZ(26px);
  border-color: var(--cool);
}

.gauge .cap {
  display: flex; align-items: baseline; gap: 8px; margin: 0 0 11px;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-dim);
}
.gauge .cap b { color: var(--cool); font-weight: 500; }
.gauge .cap .tag { margin-left: auto; color: var(--duck); }
.gauge svg { display: block; width: 100%; height: auto; overflow: visible; }
.gauge .note {
  margin: 11px 0 0; font-size: 11.5px; line-height: 1.55; color: var(--ink-muted);
}
.gauge .note b { color: var(--ink); font-weight: 600; }

/* Stroke roles, so the SVGs stay readable and consistent. */
.ink-line   { stroke: var(--ink-muted); }
.cool-line  { stroke: var(--cool); }
.duck-line  { stroke: var(--duck); }
.dim-line   { stroke: var(--line-strong); }
.grid-line  { stroke: rgba(255,255,255,0.055); }
.cool-fill  { fill: var(--cool); }
.duck-fill  { fill: var(--duck); }
.dim-fill   { fill: var(--line-strong); }

/* One pass of light travelling along a path, so a still diagram reads as live. */
.trace { stroke-dasharray: 4 210; animation: crawl 5.4s linear infinite; }
@keyframes crawl { to { stroke-dashoffset: -214; } }

.blip { animation: blip 3.2s ease-in-out infinite; }
@keyframes blip { 0%,100% { opacity: .35 } 50% { opacity: 1 } }

/* ----------------------------------------------------------- 3D film strip */

.stripwrap { perspective: 900px; overflow: hidden; padding: 30px 0; }
.filmstrip {
  display: flex; gap: 10px; width: max-content;
  transform: rotateX(28deg) rotateZ(-2.4deg);
  transform-origin: center;
  animation: run 46s linear infinite;
}
.filmstrip .frame {
  width: 154px; height: 86px; flex: none;
  border: 1px solid var(--line-strong); border-radius: 2px;
  background: linear-gradient(150deg, #151a21, #0b0e13);
  box-shadow: 0 16px 26px -18px rgba(0,0,0,0.95), inset 0 1px 0 rgba(255,255,255,0.04);
  position: relative; overflow: hidden;
  display: grid; place-items: center;
}
.filmstrip .frame::before,
.filmstrip .frame::after {
  content: ""; position: absolute; left: 0; right: 0; height: 7px;
  background-image: linear-gradient(90deg, var(--line) 0 7px, transparent 7px 17px);
  background-size: 17px 7px; opacity: 0.6;
}
.filmstrip .frame::before { top: 3px; }
.filmstrip .frame::after { bottom: 3px; }
.filmstrip .frame span {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em;
  color: var(--ink-dim); text-transform: uppercase;
}
.filmstrip .frame.lit { border-color: rgba(242,194,27,0.5); }
.filmstrip .frame.lit span { color: var(--duck); }

/* -------------------------------------------------------------- hero parallax */

.float { transform-style: preserve-3d; transition: transform 240ms ease-out; }

/* A specular pass across the duck, so the surface reads as vinyl rather than as flat art. */
.float .gloss {
  position: absolute; inset: 6% 10% 22% 10%;
  border-radius: 50% 50% 42% 42%;
  background: linear-gradient(112deg, transparent 34%, rgba(255,255,255,0.20) 47%,
              transparent 58%);
  mix-blend-mode: screen; pointer-events: none;
  animation: sweep 9s ease-in-out infinite;
}
@keyframes sweep {
  0%, 100% { opacity: 0.30; transform: translateX(-11%) rotate(-3deg); }
  50%      { opacity: 0.70; transform: translateX(11%) rotate(3deg); }
}

@media (max-width: 900px) {
  .gauge, .rig > .gauge:nth-child(even) { transform: none; }
  .filmstrip { transform: rotateX(18deg); }
}

@media (prefers-reduced-motion: reduce) {
  .trace, .blip, .filmstrip, .float .gloss { animation: none !important; }
  .gauge { transition: none; }
  .float .gloss { opacity: 0.4; }
}

/* =============================================================================
 * Editorial layer: a display serif, asymmetry, and room to breathe.
 *
 * The single biggest move toward looking like a film company rather than a SaaS product
 * is the typeface. A refined serif for display against a mono for data is the house style
 * of nearly every distributor worth copying. ui-serif resolves to New York on Apple
 * platforms and to a decent old-style face elsewhere, so there is no font to download and
 * nothing to block first paint.
 * ========================================================================== */

:root {
  --display: ui-serif, "New York", "Iowan Old Style", "Palatino Linotype", Palatino,
             Georgia, serif;
}

.public .hero h1,
.public .band h3,
.public .auth h1 {
  font-family: var(--display);
  font-weight: 500;
  font-feature-settings: "liga" 1, "kern" 1;
}

.public .hero h1 {
  font-size: clamp(40px, 7.6vw, 86px);
  line-height: 0.96;
  letter-spacing: -0.028em;
  max-width: 19ch;
}
.public .band h3 {
  font-size: clamp(25px, 3.4vw, 40px);
  line-height: 1.1;
  letter-spacing: -0.018em;
  max-width: 26ch;
}

/* Section labels get a number, the way a call sheet or a spec document does. */
.band h2.section { counter-increment: sect; }
.public main, .public body { counter-reset: sect; }
.band h2.section::before {
  content: counter(sect, decimal-leading-zero);
  color: var(--duck); opacity: 0.85; letter-spacing: 0.08em;
}

/* --------------------------------------------------------------- asymmetry */

.hero .withduck { grid-template-columns: minmax(0, 1.25fr) auto; gap: 56px; }

/* Lists in body copy read better with a hanging marker than with bullets. */
.plainlist li { padding: 13px 0; font-size: 13.5px; }
.band p.body { font-size: 14.5px; line-height: 1.7; }
.step p { font-size: 13px; line-height: 1.62; }


/* =============================================================================
 * Liquid dividers, and the duck carried down the page.
 *
 * The flat 1px waterline was too quiet once grain and vignette went in, and the duck
 * appeared exactly once. Both fixed here: two wave paths sliding at different speeds so
 * the crests never repeat on a fixed beat, and a small duck riding the surface between
 * bands.
 *
 * On the brand rule again: the duck stays out of the instrument panels and away from the
 * figure blocks, because that rule exists so a mascot never turns up beside a number. It
 * rides the water between sections, where there are no numbers to undercut.
 * ========================================================================== */



/* Translating by the path's own wavelength keeps the loop seamless. */

/* A band of cool light along the crest, so the surface reads as wet. */


/* Caustics were too faint once grain and the vignette went over them. Brought back up,
 * with a third sheet so the pattern does not read as two shapes crossing. */
.tank::before,
.tank::after {
  background:
    radial-gradient(40% 26% at 20% 28%, rgba(120, 190, 235, 0.19), transparent 68%),
    radial-gradient(32% 22% at 70% 56%, rgba(242, 194, 27, 0.13), transparent 70%),
    radial-gradient(26% 18% at 44% 82%, rgba(120, 190, 235, 0.12), transparent 72%),
    radial-gradient(22% 15% at 86% 22%, rgba(150, 210, 245, 0.10), transparent 74%);
  filter: blur(30px);
}
.tank::after { opacity: 0.8; }

/* A cool wash along the top of every water band, as though light is entering from above. */
.band.tank > .inner { position: relative; }
.band.tank::marker { content: none; }
.tank {
  background:
    linear-gradient(180deg, rgba(111, 180, 220, 0.07), transparent 22%),
    radial-gradient(120% 90% at 14% -10%, rgba(242, 194, 27, 0.10), transparent 58%),
    radial-gradient(90% 70% at 88% 4%, rgba(60, 120, 170, 0.17), transparent 62%),
    linear-gradient(180deg, #0c1016 0%, #0a0d12 58%, var(--bg) 100%);
}



/* =============================================================================
 * One body of water, and a frame around the picture.
 *
 * The dividers made water look like a motif applied seven times. This makes it a medium
 * the whole page sits inside: a fixed layer of caustics, light shafts and rising bubbles
 * behind everything, with the bands themselves turned semi-transparent so it shows
 * through. Scroll the page and the light stays put while the content moves through it,
 * which is what being underwater actually looks like.
 *
 * The filmic half is framing rather than decoration: a 2.39:1 letterbox on the hero,
 * slate marks in the corners, a running frame counter, and a split-tone grade over the
 * whole page. Those are the things that read as a picture rather than as a web page.
 * ========================================================================== */

/* ------------------------------------------------------------- the body of water */

.deep {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
}
.deep .caustic {
  position: absolute; inset: -20%;
  background:
    radial-gradient(38% 24% at 22% 26%, rgba(120, 190, 235, 0.16), transparent 66%),
    radial-gradient(30% 20% at 68% 54%, rgba(242, 194, 27, 0.10), transparent 70%),
    radial-gradient(26% 18% at 44% 80%, rgba(120, 190, 235, 0.11), transparent 72%),
    radial-gradient(22% 16% at 86% 18%, rgba(150, 210, 245, 0.09), transparent 74%);
  filter: blur(34px);
  animation: drift 38s ease-in-out infinite alternate;
}
.deep .caustic.two {
  animation-duration: 53s; animation-direction: alternate-reverse;
  transform: scaleX(-1); opacity: 0.7;
}

/* Light shafts from the surface. Skewed, soft, and slowly breathing. */

/* Bubbles. Few, slow and small, so they read as atmosphere and never as a screensaver. */
@keyframes rise {
  0%   { transform: translateY(0) translateX(0); opacity: 0; }
  12%  { opacity: 0.8; }
  85%  { opacity: 0.5; }
  100% { transform: translateY(-118vh) translateX(3vw); opacity: 0; }
}

/* Every band now floats in that water rather than painting over it. */
.public .band { position: relative; z-index: 1; }
.public .tank { background: rgba(10, 13, 18, 0.34); }
.public .tank::before, .public .tank::after { display: none; }
.public header.site, .public footer.site { position: relative; z-index: 20; }

/* A split-tone grade over the lot: cool in the shadows, warm in the highlights. The single
 * cheapest thing that makes a page look graded rather than rendered. */

/* ------------------------------------------------------------------ the frame */

.gate { position: relative; }
/* 2.39:1 bars, the widescreen ratio, drawn as real mattes on the hero. */
.gate::before, .gate::after {
  content: ""; position: absolute; left: 0; right: 0; height: 26px; z-index: 3;
  background: #05070a; pointer-events: none;
}
.gate::before { top: 0; box-shadow: 0 1px 0 rgba(255,255,255,0.05); }
.gate::after  { bottom: 0; box-shadow: 0 -1px 0 rgba(255,255,255,0.05); }
.gate > .inner { padding-top: 96px; padding-bottom: 92px; }

/* Slate furniture in the corners, the way a viewer or a scope overlays its readout. */
.marks { position: absolute; inset: 26px 0; z-index: 4; pointer-events: none; }
.marks span {
  position: absolute; font-family: var(--mono); font-size: 9.5px;
  letter-spacing: 0.2em; text-transform: uppercase; color: rgba(255,255,255,0.30);
}
.marks .tl { top: 14px; left: 22px; }
.marks .tr { top: 14px; right: 22px; color: rgba(242,194,27,0.55); }
.marks .bl { bottom: 14px; left: 22px; }
.marks .br { bottom: 14px; right: 22px; }
/* Corner crop marks. */
.marks i {
  position: absolute; width: 15px; height: 15px;
  border: 1px solid rgba(255,255,255,0.16);
}
.marks i.a { top: 10px;    left: 10px;  border-right: 0; border-bottom: 0; }
.marks i.b { top: 10px;    right: 10px; border-left: 0;  border-bottom: 0; }
.marks i.c { bottom: 10px; left: 10px;  border-right: 0; border-top: 0; }
.marks i.d { bottom: 10px; right: 10px; border-left: 0;  border-top: 0; }

/* ------------------------------------------------------- the language crawl */

.crawl {
  position: relative; z-index: 1; overflow: hidden;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: rgba(5, 7, 10, 0.72); padding: 17px 0;
}
.crawl .run {
  display: flex; gap: 40px; width: max-content;
  animation: crawlx 52s linear infinite;
}
.crawl .run span {
  font-family: var(--display); font-size: clamp(17px, 2.1vw, 25px);
  letter-spacing: -0.012em; color: var(--ink-dim); white-space: nowrap;
}
.crawl .run span.on { color: var(--ink); }
.crawl .run span.pack { color: var(--ink-dim); opacity: 0.45; font-style: italic; }
@keyframes crawlx { to { transform: translateX(-50%); } }
/* Feathered edges, so the crawl leaves the frame instead of stopping at it. */
.crawl::before, .crawl::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 16%; z-index: 2;
  pointer-events: none;
}
.crawl::before { left: 0;  background: linear-gradient(90deg, #05070a, transparent); }
.crawl::after  { right: 0; background: linear-gradient(270deg, #05070a, transparent); }

/* --------------------------------------------------------------- scroll reveal */

.rise { opacity: 0; transform: translateY(19px); }
.rise.seen {
  opacity: 1; transform: none;
  transition: opacity 760ms cubic-bezier(.2,.7,.2,1), transform 760ms cubic-bezier(.2,.7,.2,1);
}

@media (max-width: 720px) {
  .gate::before, .gate::after { height: 15px; }
  .marks { inset: 15px 0; }
  .marks span { font-size: 8.5px; letter-spacing: 0.14em; }
}

@media (prefers-reduced-motion: reduce) {
  .rise { opacity: 1; transform: none; }
}

/* =============================================================================
 * Flow, not stripes.
 *
 * The page read as chunks because it was built as chunks: every band carried a bottom
 * border, alternating bands carried a different background, and seven hard-edged wave
 * dividers sat between them. Three separate ways of saying "this section has ended".
 *
 * All three go. The water layer behind the page is now the only background, sections are
 * separated by space alone, and the few remaining surfaces overlap their neighbours so the
 * waterline passes through the content instead of cutting it in half.
 * ========================================================================== */

.public .band { border-bottom: 0; background: none; backdrop-filter: none; }
.public .tank { background: none; }

/* A very soft vertical rhythm instead of a rule: the eye still gets a beat, but nothing
 * draws a line across the page. */
.public .band + .band > .inner::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.045) 30%,
              rgba(255, 255, 255, 0.045) 70%, transparent);
}

/* The surfaces overlap what is above and below, so the water passes through rather than
 * separating two blocks. */

/* Panels keep an edge, because a table of figures does need a container. Everything else
 * floats. */
.public .panel { background: rgba(18, 21, 26, 0.62); backdrop-filter: blur(4px); }
.public .gauge {
  background:
    linear-gradient(180deg, rgba(255,255,255,0.035), transparent 34%),
    rgba(14, 18, 23, 0.72);
  backdrop-filter: blur(5px);
}
.public .crawl { background: rgba(5, 7, 10, 0.55); }
.public .figures { background: rgba(255,255,255,0.055); }
.public .figures > div { background: rgba(12, 15, 20, 0.66); backdrop-filter: blur(4px); }

/* The whole page sits on one graded ground rather than on stacked panels. */
body.public {
  background:
    radial-gradient(130% 70% at 12% 0%, rgba(242, 194, 27, 0.055), transparent 56%),
    radial-gradient(110% 80% at 88% 14%, rgba(60, 120, 170, 0.13), transparent 60%),
    linear-gradient(180deg, #0b0f14 0%, #090c11 46%, #07090d 100%);
  background-attachment: fixed;
}

/* Stronger, slower caustics. The old ones were too tight to read as moving water. */
.deep .caustic { filter: blur(52px); animation-duration: 44s; }
.deep .caustic.two { animation-duration: 61s; }
@keyframes drift {
  0%   { transform: translate3d(-5%, -2%, 0) scale(1.06) rotate(-1.2deg); }
  50%  { transform: translate3d(4%, 4%, 0)  scale(1.16) rotate(1deg); }
  100% { transform: translate3d(6%, -3%, 0) scale(1.08) rotate(1.8deg); }
}

/* Two events, four languages, and the windows they have to fit. The earlier version showed
 * two events in the file and translated only one of them, which made the example contradict
 * its own source. */



/* =============================================================================
 * The caustic field.
 *
 * This is the layer that makes the background read as water. The drifting gradients stay
 * underneath it as colour; the movement comes from an animated turbulence filter, because
 * a blurred blob crossing the screen over forty seconds is not something an eye registers
 * as flowing.
 *
 * Blend mode matters here: screen on the ridges means they add light rather than paint over
 * the page, so text stays legible and the ridges read as being cast onto the content.
 * ========================================================================== */

.deep .caustics {
  position: absolute; inset: -8%;
  width: 116%; height: 116%;
  mix-blend-mode: screen;
  opacity: 0.30;
  will-change: filter;
}
.deep .caustics .ridge   { opacity: 0.55; }

/* A second pass, mirrored and slower, so the ridges never look like one repeating tile. */
.deep::after {
  content: ""; position: absolute; inset: 0;
  background: inherit;
}

/* The gradient sheets drop back to being colour, since the filter now carries the motion. */
.deep .caustic { opacity: 0.55; filter: blur(60px); }
.deep .caustic.two { opacity: 0.4; }

/* Surface light: a bright band that travels across the top of the viewport, as though the
 * water's surface is moving above the camera. */
.deep .skin {
  position: absolute; top: -6%; left: -20%; width: 140%; height: 34%;
  background: linear-gradient(180deg, rgba(160, 215, 245, 0.16), transparent 74%);
  filter: blur(18px);
  animation: skin 13s ease-in-out infinite alternate;
}

@media (prefers-reduced-motion: reduce) {
  /* SMIL cannot be stopped from CSS, so the whole filtered layer goes and the static
   * gradients carry the look on their own. */
  .deep .caustics { display: none; }
  .deep .skin { animation: none; }
  .deep .caustic { opacity: 0.8; }
}

/* =============================================================================
 * Water: flowing, and considerably darker.
 *
 * Two corrections to the first attempt. It jumped because `seed` was animated, and an
 * integer seed regenerates the whole noise field on every step, so the pattern popped
 * rather than moved. Seed is fixed now; the motion is a slow spline-eased drift in
 * baseFrequency plus a lateral translate of the layer, both of which are continuous.
 *
 * And it was far too bright. Caustics are a suggestion on a dark floor, not a light show,
 * and every one of these pixels sits behind body text.
 * ========================================================================== */

.deep .caustics { opacity: 0.16; mix-blend-mode: screen; }
.deep .caustics .ridge   { opacity: 0.42; }

/* The lateral drift. Long, linear and seamless, so nothing ever snaps back. */
.deep .caustics .flow { animation: flow 96s linear infinite; }
@keyframes flow {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-9%, -3%, 0); }
}

/* The colour sheets come down too. Together with the filter they were reading as daylight. */
.deep .caustic { opacity: 0.34; filter: blur(66px); }
.deep .caustic.two { opacity: 0.24; }
.deep .skin {
  background: linear-gradient(180deg, rgba(150, 210, 245, 0.075), transparent 72%);
}
@keyframes shimmer {
  0%, 100% { opacity: 0.22; transform: skewX(-11deg) translateX(0); }
  50%      { opacity: 0.5;  transform: skewX(-9.5deg) translateX(1.4vw); }
}
@keyframes skin {
  from { transform: translateX(-3%) scaleY(1); opacity: 0.35; }
  to   { transform: translateX(3%) scaleY(1.14); opacity: 0.7; }
}

/* Bubbles were reading as sparkles. Fewer highlights, thinner rims. */

@media (prefers-reduced-motion: reduce) {
  .deep .caustics .flow { animation: none !important; }
}

/* =============================================================================
 * Type scale for the public pages.
 *
 * The base is 13px because the working interface is a dense table surface: 28px rows,
 * tabular figures, a mixer scanning fifteen hundred lines. The landing page inherited that
 * and it was far too small for prose somebody reads once, standing up, deciding whether to
 * bother with us.
 *
 * So the public surface gets its own scale. The app keeps 13px, which is correct for it.
 * ========================================================================== */

body.public { font-size: 16px; line-height: 1.6; }

.public .band p.body { font-size: 17px; line-height: 1.65; max-width: 68ch; }
.public .hero p.stand { font-size: clamp(17px, 2vw, 22px); line-height: 1.5; max-width: 56ch; }
.public .step p { font-size: 15px; line-height: 1.6; }
.public .step h4 { font-size: 15.5px; }
.public .plainlist li { font-size: 15.5px; line-height: 1.6; padding: 14px 0; }
.public .pair .one p { font-size: 15.5px; line-height: 1.65; }
.public .pair .one h4 { font-size: 15.5px; }
.public .gauge .note { font-size: 13.5px; line-height: 1.6; }
.public .gauge .cap { font-size: 11px; }
.public .illustration { font-size: 13.5px; line-height: 1.65; padding: 14px 16px; }
.public .figures .what { font-size: 12px; }
.public .srtfile pre { font-size: 13px; line-height: 1.8; }
.public .srtfile .name { font-size: 12.5px; }
.public .slate { font-size: 13px; }
.public .chips span { font-size: 12.5px; padding: 4px 10px; }
.public header.site nav a { font-size: 12px; }
.public header.site .enter { font-size: 12px; padding: 6px 15px; }
.public header.site .mark b { font-size: 13px; }
.public footer.site .inner { font-size: 12.5px; }
.public .band h2.section { font-size: 11.5px; }

/* The sign-in screens are prose too, so they scale with the rest of the public surface. */
.public .auth h1 { font-size: 23px; }
.public .auth .sub { font-size: 14.5px; }
.public .auth label { font-size: 12px; }
.public .auth .field .hint { font-size: 12.5px; line-height: 1.55; }
.public .auth input, .public .auth select { font-size: 15px; padding: 9px 11px; }
.public .auth button[type="submit"] { font-size: 14px; padding: 12px; }
.public .auth .below { font-size: 14px; }
.public .auth .aside { font-size: 12.5px; line-height: 1.65; padding: 14px 16px; }
.public .auth .row label { font-size: 13.5px; }
.public .auth .row a { font-size: 13.5px; }
.public .notice { font-size: 14px; line-height: 1.6; padding: 12px 15px; }

@media (max-width: 720px) {
  body.public { font-size: 15.5px; }
  .public .band p.body { font-size: 16px; }
}

/* The source card as a dubbing script: timecode, character, line, intent, window. Reads the
 * way a spotting list does, and it makes the per-character voice casting obvious. */

/* Locale families. One card per language name that is really several delivery targets,
 * because "we support Spanish" is the claim that gets a track rejected on arrival. */
.families {
  display: grid; gap: 14px; margin: 26px 0 30px;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
}
.family {
  padding: 18px 20px 19px; border: 1px solid var(--line); border-radius: 14px;
  background: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0.012));
}
.family .lang {
  margin: 0; font-family: var(--display); font-size: 19px; color: var(--ink);
}
.family .vars { display: flex; flex-wrap: wrap; gap: 7px; margin: 11px 0 12px; }
.family .var {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 4px 9px; border-radius: 999px; border: 1px solid var(--line);
  font-size: 12px; color: var(--ink-dim); background: rgba(0,0,0,0.16);
}
.family .var b {
  font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.04em; color: var(--duck);
}
.family .why { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink-dim); }

/* Sections separate by space, not by rules. The horizontal borders plus a duck divider
 * between every band read as a stack of separate boxes rather than one continuous page,
 * which is the opposite of the flowing feel this thing is meant to have. So: no band
 * borders on the public page, softer tinting, and considerably more air. */
/* The first band sits under the hero and needs less above it. */

@media (max-width: 760px) {
  .public .band > .inner { padding-top: 66px; padding-bottom: 66px; }
}

/* The source card. The cue block is byte-for-byte what an SRT holds, spans added only for
 * colour: sequence number, a timecode pair with a comma and milliseconds, the speaker in
 * capitals on its own line, then the text. People in this trade read these files daily and
 * spot an invented shape instantly, so the format is copied from a real script. The one
 * omission is the sequence number: every subtitle editor hides it, so printing it made
 * readers doubt the example against their own files. */
.srtfile .cues { padding: 16px 15px 4px; }
.srtfile .cues .tc { color: var(--cool); }
.srtfile .cues .sp { color: var(--duck); letter-spacing: 0.04em; }
.srtfile .cues .tx { color: var(--ink); }


/* The output side is three more SRT files, not a table of statistics. Character counts and
 * durations are our annotations, so printing them inside something shaped like a file was
 * the same mistake as inventing the timecode format. The numbers live in the prose below;
 * the cards hold only what an SRT holds. */
.versions { display: flex; flex-direction: column; gap: 10px; }
.srtfile.out .cues { padding: 13px 15px; font-size: 11px; line-height: 1.7; }
.srtfile.out .name { font-size: 10.5px; }
.srtfile.out .name b {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.srtfile.out .name span:last-child { flex: 0 0 auto; color: var(--duck); }
.versions .more {
  margin: 2px 0 0; padding: 0 4px; font-size: 12.5px; line-height: 1.5; color: var(--ink-dim);
}
.public .srtfile.out .cues { font-size: 11.5px; }
.public .versions .more { font-size: 13px; }

/* One file in, three files out, read top to bottom. Side by side with a spine between them,
 * the master was the same size as the conversions and the direction of travel was guesswork.
 * So: the master alone and dominant, a labelled split, then the outputs as equals in a row. */
.fanout .step { position: relative; }
.fanout .tag {
  margin: 0 0 12px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-dim);
}
/* The master is centred over the row it feeds, so the fan reads as symmetrical rather than
 * as three files hanging off the left edge. */
.fanout .step.master { max-width: 560px; margin-inline: auto; }
.fanout .step.master .tag { text-align: center; }
.fanout .step.master .tag { color: var(--duck); }
.fanout .step.master .srtfile { border-color: rgba(247, 201, 72, 0.32); }
.fanout .step.master .cues { font-size: 12.5px; }

/* The split: one line down out of the master, fanning into three. Decorative, so it carries
 * aria-hidden and no meaning the text does not already give. */
.split { height: 58px; display: grid; grid-template-columns: repeat(3, 1fr); position: relative; }
.split::before {
  content: ""; position: absolute; left: 50%; top: 0; width: 1px; height: 22px;
  background: linear-gradient(180deg, var(--line-strong), transparent);
}
.split i {
  position: relative; opacity: 0.7;
}
.split i::before {
  content: ""; position: absolute; left: 50%; bottom: 0; width: 1px; height: 22px;
  background: linear-gradient(180deg, transparent, var(--line-strong));
}
.split i::after {
  content: ""; position: absolute; left: 50%; bottom: 20px; width: 5px; height: 5px;
  margin-left: -2px; border-radius: 50%; background: var(--duck); opacity: 0.55;
  animation: drip 3.4s ease-in-out infinite;
}
.split i:nth-child(2)::after { animation-delay: 1.1s; }
.split i:nth-child(3)::after { animation-delay: 2.2s; }
@keyframes drip {
  0%, 100% { transform: translateY(-14px); opacity: 0; }
  40%      { opacity: 0.7; }
  70%      { transform: translateY(6px); opacity: 0; }
}

.versions {
  display: grid; gap: 12px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.versions .srtfile.out { min-width: 0; }
.fanout .more {
  margin: 14px 0 0; font-size: 13px; line-height: 1.5; color: var(--ink-dim);
}

@media (max-width: 760px) {
  .split { height: 44px; grid-template-columns: 1fr; }
  .split i:nth-child(n + 2) { display: none; }
}

/* Grey reads as "skip this", and none of the marketing copy is skippable.
 *
 * The two muted inks were picked for the application interface, where a dim label sits beside
 * the live figure it annotates and the contrast difference is the hierarchy. Measured against
 * the page ground, --ink-dim is 3.15:1, which is below the 4.5:1 floor for body text and is
 * why it looks disabled rather than secondary. On the public pages the same tokens carry real
 * sentences, so they are lifted here and only here: the application keeps its density.
 *
 * Hierarchy survives, at 16.0 / 12.8 / 8.7 to one. Nothing reads switched off. This only
 * touches the greys; the duck, the cool blue and the state colours are unchanged. */

/* The eligibility notice on the access request. Stated before the form rather than after it,
 * because somebody who cannot use the service should find that out before typing a password.
 * The service needs stems from a mix stage, which is a hard boundary and not a preference. */
.eligibility {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); padding: 18px 20px 19px; margin: 0 0 20px;
}
.eligibility .who {
  margin: 0 0 10px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--duck);
}
.eligibility ul { margin: 0; padding: 0; list-style: none; }
.eligibility li {
  position: relative; padding: 0 0 7px 17px; font-size: 14px; line-height: 1.5;
  color: var(--ink);
}
.eligibility li::before {
  content: ""; position: absolute; left: 3px; top: 9px; width: 5px; height: 5px;
  border-radius: 50%; background: var(--duck); opacity: 0.6;
}
.eligibility .not {
  margin: 13px 0 0; padding-top: 13px; border-top: 1px solid var(--line);
  font-size: 13.5px; line-height: 1.55; color: var(--ink-muted);
}
.eligibility .not strong { color: var(--ink); }

/* Who the service is for, said in the hero rather than discovered at the access form. The
 * boundary is the assets: stems come off a mix stage, so a project without them cannot be
 * served whatever its subject matter. Saying that here saves everybody the round trip. */
.hero .audience {
  margin: 0 0 22px; max-width: 62ch; font-size: 15px; line-height: 1.6;
  color: var(--ink-muted); padding-left: 14px;
  border-left: 2px solid rgba(242, 194, 27, 0.35);
}
.hero .gatenote {
  margin: 14px 0 0; font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.02em;
  color: var(--ink-dim);
}

/* =============================================================================
 * RESPONSIVE REPAIR
 *
 * Two bugs, both invisible on a desktop and both total on a phone.
 *
 * The hero never collapsed. Its one-column rule sat inside a max-width block that
 * appeared BEFORE an unqualified rule of equal specificity, so the later rule won at
 * every width. A media query adds no specificity, which is the trap. At 360px the text
 * column was left about 150px wide with an 86px display serif inside it.
 *
 * And the site navigation was set to display:none below 720px with nothing put in its
 * place, so a phone visitor had no way to reach any section.
 *
 * Both rules live here, at the end of the file, where nothing can out-order them.
 * ========================================================================== */
@media (max-width: 900px) {
  .hero .withduck { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .hero .float { width: 150px; height: 150px; margin: 0; }
  /* The duck leads on a narrow screen rather than being stranded under the copy. */
  .hero .withduck > .float { order: -1; }
}

@media (max-width: 720px) {
  /* Navigation becomes a scrolling strip instead of disappearing. */
  .public header.site nav {
    display: flex; gap: 18px; margin-left: auto;
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
    max-width: 58vw; mask-image: linear-gradient(90deg, #000 86%, transparent);
  }
  .public header.site nav::-webkit-scrollbar { display: none; }
  .public header.site nav a { flex: 0 0 auto; }
  .hero .float { width: 118px; height: 118px; }
}

/* Every auto-fit grid floors its track at its own minimum, so a 290px minimum overflows
 * a 276px column. min() lets the track shrink below the ideal on a narrow screen. */
.cols.two    { grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr)); }
.cols.three  { grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); }
.rig         { grid-template-columns: repeat(auto-fit, minmax(min(276px, 100%), 1fr)); }
.pair        { grid-template-columns: repeat(auto-fit, minmax(min(270px, 100%), 1fr)); }
.families    { grid-template-columns: repeat(auto-fit, minmax(min(270px, 100%), 1fr)); }
.versions    { grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); }
/* A grid item defaults to min-width:auto, which refuses to shrink below its content. */
.cols > div, .rig > .gauge, .family, .pair .one, .versions .srtfile { min-width: 0; }
.slate dd { overflow-wrap: anywhere; }

/* =============================================================================
 * RHYTHM AND WIDTH
 *
 * The page read as one repeated stripe because nine of eleven bands resolved to the
 * same 104px top and bottom and the same 1160px column. Vertical rhythm was a constant,
 * so scroll velocity never changed and nothing signalled which section mattered.
 *
 * Three widths and four vertical steps, assigned so the page opens wide, narrows to
 * state the case, widens for the evidence, and narrows again to close. The background
 * tints are also pushed apart: the old three were within 3% luminance of each other,
 * which is a distinction the eye cannot make.
 * ========================================================================== */
:root {
  --v-xl: 128px;  --v-l: 96px;  --v-m: 64px;  --v-s: 32px;
  --w-wide: 1240px;  --w-text: 760px;
  --gap-label: 24px;   /* section label to heading */
  --gap-heading: 16px; /* heading to body */
}

/* One declaration owns band padding. Variants opt in by class. */
.public .band { border-bottom: 0; }
.public .band > .inner { padding-top: var(--v-l); padding-bottom: var(--v-l); }
.public .band.v-xl > .inner { padding-top: var(--v-xl); padding-bottom: var(--v-xl); }
.public .band.v-m  > .inner { padding-top: var(--v-m);  padding-bottom: var(--v-m); }
.public .band.v-s  > .inner { padding-top: var(--v-s);  padding-bottom: var(--v-s); }

/* Width tiers. Narrow bands centre their copy and drop the 68ch cap, so a thesis
 * paragraph is a measured column rather than a ribbon pinned to the left of a wide box. */
.public .band.wide > .inner { max-width: var(--w-wide); }
.public .band.text > .inner { max-width: var(--w-text); }
.public .band.text h3,
.public .band.text p.body { max-width: none; }
.public .band.text h3 { font-weight: 600; }   /* the only heading tier above a section label */

/* Backgrounds, far enough apart to read as alternation. `alt` and `tank` were also
 * colliding: a band carrying both silently got whichever rule came last in the file. */
.public .band.alt:not(.tank) { background: rgba(255, 255, 255, 0.032); }
.public .band.tank { background: rgba(4, 12, 22, 0.55); }

/* Heading to body, from two tokens instead of sixteen ad hoc values. */
.public .band h2.section { margin-bottom: var(--gap-label); }
.public .band h3 { margin-bottom: var(--gap-heading); }
.public .band p.body + p.body { margin-top: var(--gap-heading); }

/* Staggered reveal. One shared timing with no offset meant eleven sections arrived
 * identically; a small per-item delay makes a section land rather than appear. */
.public .rise { transition-delay: calc(var(--i, 0) * 70ms); }

@media (max-width: 760px) {
  :root { --v-xl: 80px; --v-l: 64px; --v-m: 48px; --v-s: 24px; }
}

/* =============================================================================
 * DEPTH
 *
 * The page read flat because everything sat on one of two depth values: a fixed
 * backdrop at zero parallax, and the content at one. Two values is not a depth field,
 * and six earlier passes had spent their effort turning the background down rather than
 * pulling it apart, which is how it ended up an even grey fog.
 *
 * ONE LIGHT, stated once here and obeyed by everything: key from the upper left, warm.
 * One cool fill bouncing up off the water. No third source, and no light ever moves. The
 * duck mark already carried this direction, so everything else is brought to the duck.
 * ========================================================================== */
:root {
  --key-warm: 255 242 207;
  --fill-cool: 111 180 220;

  /* Derived from the key direction. Never hand-tuned per component. */
  --lit-x: 1px;  --lit-y: 1px;    /* highlight on the top-left edge */
  --occ-x: -1px; --occ-y: -1px;   /* occlusion on the bottom-right edge */
  --cast-x: 6px; --cast-y: 12px;  /* cast shadows fall down and right */

  /* A raise is three terms: contact, near-field occlusion, cast. One soft blob under an
   * element is how a sticker with a smudge behind it looks, which is what we had. */
  --raise-1:
    0 1px 1px rgba(0, 0, 0, 0.55),
    0 2px 4px -2px rgba(0, 0, 0, 0.42),
    var(--cast-x) var(--cast-y) 22px -12px rgba(0, 0, 0, 0.70),
    inset var(--lit-x) var(--lit-y) 0 rgb(var(--key-warm) / 0.055),
    inset var(--occ-x) var(--occ-y) 0 rgba(0, 0, 0, 0.42);
  --raise-2:
    0 1px 1px rgba(0, 0, 0, 0.6),
    0 3px 6px -2px rgba(0, 0, 0, 0.5),
    calc(var(--cast-x) * 2) calc(var(--cast-y) * 2) 42px -16px rgba(0, 0, 0, 0.82),
    inset var(--lit-x) var(--lit-y) 0 rgb(var(--key-warm) / 0.085),
    inset var(--occ-x) var(--occ-y) 0 rgba(0, 0, 0, 0.5);
}

/* ------------------------------------------------------------------ the planes */
.deep { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.deep > div { position: absolute; inset: -40% -12%; will-change: transform; }

/* Blur is set once per plane and never animated, so each one rasterises a single time
 * and only its transform moves afterwards. This is the whole depth-of-field budget.
 * Farther planes are larger and blurrier: that pairing is the atmospheric cue. */
.deep .p1 { filter: blur(56px); opacity: 0.22; }
.deep .p2 { opacity: 0.30; mix-blend-mode: screen; }
.deep .p3 { filter: blur(1.5px); opacity: 0.28; }
.deep .p4 { opacity: 0.18; mix-blend-mode: screen; }
.deep .p1 .caustic { position: absolute; inset: 0; filter: none; opacity: 1; }
.deep .p1 .caustic.two { transform: scaleX(-1); opacity: 0.62; }

/* The grade, now BEHIND the content instead of over it. */
.deep::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg,
              rgb(90 150 190 / 0.10) 0%, transparent 42%, rgb(255 196 90 / 0.06) 94%);
  mix-blend-mode: soft-light;
}

/* Parallax. Progressive: where scroll timelines are unsupported the planes keep the
 * autonomous drift they already have, so nothing breaks and nothing jumps. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .deep > div {
      animation-timeline: scroll(root block);
      animation-duration: auto; animation-timing-function: linear;
      animation-fill-mode: both;
    }
    .deep .p1 { animation-name: pan-p1; }
    .deep .p2 { animation-name: pan-p2; }
    .deep .p3 { animation-name: pan-p3; }
    .deep .p4 { animation-name: pan-p4; }
  }
}
@keyframes pan-p1 { from { transform: translate3d(0,0,0) scale(1.20); }
                    to   { transform: translate3d(0,-28vh,0) scale(1.20); } }
@keyframes pan-p2 { from { transform: translate3d(0,0,0) scale(1.06); }
                    to   { transform: translate3d(0,-84vh,0) scale(1.06); } }
@keyframes pan-p3 { from { transform: translate3d(0,0,0); }
                    to   { transform: translate3d(0,-140vh,0); } }
@keyframes pan-p4 { from { transform: translate3d(0,0,0); }
                    to   { transform: translate3d(0,-210vh,0); } }

/* Caustics live at the edges of frame and are masked out of the middle, where all the
 * copy is. This is the readability rule that lets the rest of the system be this strong. */
.deep .p2, .deep .p4 {
  -webkit-mask-image: radial-gradient(130% 92% at 50% 42%, transparent 0 32%, #000 64%);
  mask-image: radial-gradient(130% 92% at 50% 42%, transparent 0 32%, #000 64%);
}

/* Silt: two tiled point fields rather than dozens of elements. Gives the empty middle
 * distance something to occupy it. */
.deep .p3 .silt {
  position: absolute; inset: -30%;
  background-image:
    radial-gradient(1px 1px at 17% 23%, rgb(var(--fill-cool) / 0.55), transparent),
    radial-gradient(1px 1px at 63% 11%, rgb(var(--key-warm) / 0.40), transparent),
    radial-gradient(1px 1px at 38% 71%, rgb(var(--fill-cool) / 0.45), transparent),
    radial-gradient(1px 1px at 84% 52%, rgb(var(--fill-cool) / 0.38), transparent),
    radial-gradient(1px 1px at 8% 86%, rgb(var(--key-warm) / 0.30), transparent),
    radial-gradient(1px 1px at 52% 40%, rgb(var(--fill-cool) / 0.42), transparent);
  background-size: 340px 340px;
  animation: silt 190s linear infinite;
}
.deep .p3 .silt.b {
  background-size: 520px 520px; opacity: 0.6;
  animation-duration: 260s; animation-direction: reverse;
}
@keyframes silt { to { transform: translate3d(-340px, -170px, 0); } }

.deep .rays { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.deep .rays .far { animation: raybreath 29s ease-in-out infinite alternate; }
.deep .rays .near {
  animation: raybreath 41s ease-in-out infinite alternate-reverse;
  transform-origin: 50% 0;
}
@keyframes raybreath {
  from { transform: translate3d(-1.1%, 0, 0) scaleX(0.97); opacity: 0.62; }
  to   { transform: translate3d(1.4%, 0, 0) scaleX(1.05); opacity: 1; }
}

/* ------------------------------------------------- the near plane, in front of content */
.near {
  position: fixed; inset: 0; z-index: 3; pointer-events: none; overflow: hidden;
  filter: blur(5px); opacity: 0.13; mix-blend-mode: screen;
}
.near .mote {
  position: absolute; left: var(--x); bottom: -14vh;
  width: var(--w); height: var(--w); border-radius: 50%;
  background: radial-gradient(circle at 36% 30%,
              rgb(var(--key-warm) / 0.5) 0%, rgb(var(--fill-cool) / 0.3) 46%, transparent 70%);
  animation: mote var(--t) linear infinite; animation-delay: var(--d);
  will-change: transform;
}
@keyframes mote {
  from { transform: translate3d(0, 0, 0) scale(0.9); }
  to   { transform: translate3d(3.5vw, -128vh, 0) scale(1.15); }
}

/* ------------------------------------------------------- occlusion on raised surfaces */
.public .gauge, .public .panel, .public .srtfile,
.public .family, .public .slate, .public .eligibility {
  box-shadow: var(--raise-1);
  /* No backdrop-filter. A flat translucent fill occludes more than a 4px blur does, and
   * does not force a readback of the animated layer behind it on every frame. */
  backdrop-filter: none; -webkit-backdrop-filter: none;
  background:
    linear-gradient(168deg, rgb(var(--key-warm) / 0.030) 0%, transparent 38%),
    linear-gradient(180deg, rgba(16, 20, 26, 0.88), rgba(11, 14, 19, 0.92));
}
.public .gauge, .public .panel, .public .srtfile, .public .family { position: relative; }
.public .gauge::after, .public .panel::after,
.public .srtfile::after, .public .family::after {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 5%; right: -3%; bottom: -13px; height: 26px;
  background: radial-gradient(58% 100% at 47% 0%, rgba(0, 0, 0, 0.66), transparent 74%);
}
.public .figures > div {
  position: relative;
  background: linear-gradient(166deg, rgb(var(--key-warm) / 0.022), transparent 44%),
              rgba(12, 15, 20, 0.80);
  backdrop-filter: none;
  box-shadow: inset 1px 1px 0 rgb(var(--key-warm) / 0.05),
              inset -1px -1px 0 rgba(0, 0, 0, 0.5);
}
.public .figures > div::before { display: none; }

/* --------------------------------------------------------------------- one camera */
.stage3d, .stripwrap { perspective: 1500px; perspective-origin: 50% -8%; }
.public .rig > .gauge, .public .rig > .gauge:nth-child(even) {
  /* One rotation. Mirroring rotateY on alternate children destroyed the vanishing point,
   * which is why the rig read as tilted at random rather than seen from a position. */
  transform: rotateX(6deg); transform-style: preserve-3d;
  transition: transform 340ms cubic-bezier(.2,.7,.2,1),
              box-shadow 340ms cubic-bezier(.2,.7,.2,1), border-color 340ms;
}
.public .rig > .gauge:hover {
  transform: rotateX(0deg) translateZ(30px);
  box-shadow: var(--raise-2); border-color: var(--cool);
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .public .rig {
      transform-style: preserve-3d; animation: rig-settle linear both;
      animation-timeline: view(); animation-range: entry 10% cover 52%;
    }
  }
}
@keyframes rig-settle {
  from { transform: rotateX(9deg) translateY(2.2vh) scale(0.985); }
  to   { transform: rotateX(0deg) translateY(0) scale(1); }
}
.public .filmstrip { transform: rotateX(24deg) rotateZ(-2.4deg); }
.public .filmstrip .frame {
  box-shadow: var(--raise-1);
  background: linear-gradient(166deg, #171c24, #0a0d12);
}
@keyframes run { to { transform: rotateX(24deg) rotateZ(-2.4deg) translateX(-50%); } }

/* ------------------------------------------------------------------------- the duck */
/* In-plane bob only. The old keyframe carried rotateY, which foreshortens the mark at
 * this perspective, and BRAND.md forbids distorting it. */
@keyframes bob {
  0%   { transform: translateY(0) rotateZ(-2.0deg); }
  30%  { transform: translateY(-7px) rotateZ(1.4deg); }
  58%  { transform: translateY(-2px) rotateZ(2.2deg); }
  80%  { transform: translateY(-9px) rotateZ(-0.6deg); }
  100% { transform: translateY(0) rotateZ(-2.0deg); }
}
/* A static specular, masked to the mark's own silhouette so no rectangle of light leaks
 * onto the water. The bob moves the duck under a fixed highlight, which is what actually
 * happens. A highlight that travels is a second sun. */
.float .gloss {
  position: absolute; inset: 0;
  background: linear-gradient(145deg, rgb(var(--key-warm) / 0.34) 0%,
              rgb(var(--key-warm) / 0.10) 22%, transparent 44%);
  -webkit-mask-image: url("/static/duck-mark.svg"),
                      linear-gradient(145deg, #000 0%, transparent 58%);
  mask-image: url("/static/duck-mark.svg"),
              linear-gradient(145deg, #000 0%, transparent 58%);
  -webkit-mask-size: 100% 100%, 100% 100%; mask-size: 100% 100%, 100% 100%;
  -webkit-mask-repeat: no-repeat, no-repeat; mask-repeat: no-repeat, no-repeat;
  mask-composite: intersect; -webkit-mask-composite: source-in;
  mix-blend-mode: screen; pointer-events: none; animation: none;
}
.float::before {
  content: ""; position: absolute; left: 50%; bottom: 13px; margin-left: -46px;
  width: 92px; height: 13px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.72), transparent 78%);
  z-index: -1;
}
/* The duck rides slightly faster than the page while its shadow stays with the surface,
 * so a gap opens between object and shadow on scroll. That gap is the depth cue. The old
 * JS did the opposite: it moved the nearest object slower, which reads as receding. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero .float .duck, .hero .float .gloss {
      animation-timeline: view(); animation-range: cover 0% cover 100%;
    }
    .hero .float .duck { animation: duck-ride 1s linear both, bob 6.4s ease-in-out infinite; }
    .hero .float .gloss { animation: duck-ride 1s linear both; }
  }
}
@keyframes duck-ride { from { translate: 0 5vh; } to { translate: 0 -7vh; } }

/* Vignette and a touch of edge aberration, at the frame only. Both rings stop well short
 * of the measure, so no glyph is ever touched by either. */
body.public::before {
  content: ""; position: fixed; inset: 0; z-index: 8; pointer-events: none;
  box-shadow: inset 0 0 190px 34px rgba(0, 0, 0, 0.52);
  background:
    linear-gradient(90deg, rgb(120 190 255 / 0.055) 0, transparent 2.2%),
    linear-gradient(270deg, rgb(255 176 120 / 0.050) 0, transparent 2.2%),
    linear-gradient(180deg, rgb(120 190 255 / 0.035) 0, transparent 1.4%),
    linear-gradient(0deg, rgb(255 176 120 / 0.035) 0, transparent 1.4%);
  mix-blend-mode: screen;
}
body.public::after { opacity: 0.035; z-index: 9999; }

/* =============================================================================
 * REDUCED MOTION, in one place
 *
 * Depth here is built from grade, blur, scale and occlusion, none of which need movement.
 * So a reduced-motion visitor loses the animation and keeps the volume, rather than
 * getting a flat page. The motes in particular stay: out-of-focus points in front of the
 * content are the strongest cue in the system, and they work standing still.
 * ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .deep > div { animation: none !important; }
  .deep .p1 { opacity: 0.30; }
  .deep .p2 { opacity: 0.26; }
  .deep .p3 { opacity: 0.20; }
  .deep .p3 .silt, .deep .skin { animation: none !important; }
  .deep .rays .far, .deep .rays .near { animation: none !important; opacity: 0.8; }
  /* SMIL cannot be stopped from CSS, so the animated caustic layer is removed rather than
   * frozen. The gradient sheets carry the water on their own. */
  .deep .p4 { display: none; }

  .near { opacity: 0.11; }
  .near .mote {
    animation: none !important; bottom: auto; top: calc(18% + var(--w));
  }
  .near .mote:nth-child(2n) { top: 54%; }
  .near .mote:nth-child(3n) { top: 76%; }

  /* The camera holds its FINAL pose. A reduced-motion visitor must never be left looking
   * at the pre-animation state of something that was meant to settle. */
  .public .rig { animation: none !important; transform: rotateX(0deg); }
  .public .rig > .gauge,
  .public .rig > .gauge:nth-child(even) { transform: rotateX(4deg); transition: none; }
  .public .rig > .gauge:hover { transform: rotateX(4deg); box-shadow: var(--raise-2); }
  .public .filmstrip { animation: none !important; transform: rotateX(14deg); }

  .float .duck { animation: none !important; transform: rotateZ(-1.5deg); translate: 0; }
  .float .gloss { animation: none !important; translate: 0; opacity: 1; }
  .float .ripple { animation: none !important; opacity: 0.26; }

  .trace, .blip, .crawl .run, .wave i, .split i::after { animation: none !important; }
  .rise { opacity: 1; transform: none; transition-delay: 0s; }
}

/* The greys can come back down now. They were raised earlier to compensate for a
 * soft-light sheet painted over every paragraph; with that gone, the copy is legible at
 * values much closer to the application's own, so the two do not drift apart. Measured
 * against the page ground: 16.0 / 9.7 / 6.5 to one. */

/* The legal documents, on the public layer. Previously they extended the application
 * shell, so counsel read the terms at 13px in a sidebar interface. A 760px measure and
 * the marketing type scale make them documents rather than screens. */
.public .legalhead {
  margin: 40px 0 12px; font-family: var(--display); font-weight: 600;
  font-size: 20px; letter-spacing: -0.01em; color: var(--ink);
  padding-top: 18px; border-top: 1px solid var(--line);
}
.public .band.text .legalhead:first-of-type { border-top: 0; padding-top: 0; }
.public table.legaltable { width: 100%; border-collapse: collapse; margin: 16px 0 8px; }
.public table.legaltable th {
  text-align: left; padding: 9px 12px 9px 0; font-family: var(--mono);
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-dim); border-bottom: 1px solid var(--line-strong); font-weight: 400;
}
.public table.legaltable td {
  padding: 11px 12px 11px 0; font-size: 14px; line-height: 1.5;
  color: var(--ink-muted); border-bottom: 1px solid var(--line);
  vertical-align: top; overflow-wrap: anywhere;
}
.public table.legaltable td:first-child { color: var(--ink); }
.public .notice.caveat { margin: 20px 0 8px; }

/* =============================================================================
 * AUTH PAGES, WIDER
 *
 * The card was capped at 396px and centred, so on any real screen a four-field form sat
 * in a narrow ribbon down the middle with the qualifying copy stacked underneath it and
 * pushed below the fold. The form is short and the context beside it is long, which is a
 * two-column problem, not a one-column one.
 * ========================================================================== */
.auth .card { max-width: 460px; }
.auth .card.wide { max-width: 1040px; }
/* Centred headings read as a login box. Left-aligned, a wide card reads as a page. */
.auth .card.wide h1, .auth .card.wide .sub { text-align: left; max-width: 62ch; }

.authgrid {
  display: grid; gap: 34px; align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
}
.authgrid.tight { grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr); gap: 40px; }
.authgrid > .col { min-width: 0; }

/* Fields sit two across where they pair naturally, which halves the height of the form. */
.authgrid .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.authgrid .hint.spanner { margin: 2px 0 16px; }
.authgrid form .field { margin-bottom: 16px; }
.authgrid .below { text-align: left; }
.authgrid .col.side .eligibility { margin-bottom: 18px; }
.authgrid .aside.compact { margin-top: 0; }

/* The rights statement. Denser than body copy because it is reference material, and set
 * on its own surface so it reads as terms rather than as marketing. */
.rights {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: rgba(8, 11, 15, 0.55); padding: 18px 20px 16px; margin: 0 0 18px;
}
.rights .who {
  margin: 0 0 10px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--duck);
}
.rights p { margin: 0 0 11px; font-size: 13px; line-height: 1.6; color: var(--ink-muted); }
.rights p:last-child { margin-bottom: 0; }
.rights .fine {
  margin-top: 13px; padding-top: 11px; border-top: 1px solid var(--line);
  font-size: 12px; color: var(--ink-dim);
}
.rights a { color: var(--ink-muted); border-bottom-color: var(--line-strong); }

@media (max-width: 900px) {
  .authgrid, .authgrid.tight { grid-template-columns: minmax(0, 1fr); gap: 26px; }
  .auth .card.wide h1, .auth .card.wide .sub { text-align: center; }
}
@media (max-width: 560px) {
  .authgrid .row2 { grid-template-columns: 1fr; }
}

/* The qualifying information comes first in the source, so that when the two columns
 * collapse on a narrow screen a visitor reads who this is for and what we require before
 * they start filling in a password. On a wide screen the form belongs on the left, where
 * the eye starts, so it is moved there visually and only visually. */
@media (min-width: 901px) {
  .authgrid > .col.form { order: -1; }
}

/* =============================================================================
 * THE APPLICATION INTERFACE: BIGGER, AND NOT GREY
 *
 * The 13px base came from wanting a lot of rows on screen, and the two muted inks came
 * from the same instinct. On a real dashboard it reads as small and switched off, and the
 * muted ink was being used for whole sentences rather than for labels beside a live figure.
 *
 * So: the base goes to 15px, and the greys go up to where prose is properly legible.
 * Measured against the app ground, #0b0d10: 16.0 for primary, 12.0 for secondary, 8.4 for
 * the quietest labels. Nothing in the interface now sits below the 4.5:1 body-text floor.
 *
 * Tables keep their density. A row is still a row; it is the type inside it that grew.
 * ========================================================================== */
body {
  font-size: 15px;
  line-height: 1.55;
}

/* Prose in the application was the worst of it: notices, empty states and hints carried
 * whole sentences in the dimmest ink on the page. They read as primary text now. */
.notice, .empty .said, .field .hint, .page-head .lede {
  color: var(--ink);
}
.notice { font-size: 14px; line-height: 1.6; }
.empty .said { font-size: 14.5px; line-height: 1.6; }
.field .hint { font-size: 12.5px; color: var(--ink-dim); }
.page-head h1 { font-size: 22px; letter-spacing: -0.015em; margin: 0 0 5px; }
.page-head .lede { font-size: 14.5px; line-height: 1.55; max-width: 74ch; }

/* Tables and rows: same geometry, readable type. */
table { font-size: 14px; }
thead th { font-size: 11px; color: var(--ink-dim); }
tbody td { color: var(--ink); }
tbody td.mono, .mono { font-size: 13px; }
.panel > .head { font-size: 12.5px; color: var(--ink); }

/* The figure strips. The number was already loud; the label under it was not. */
.stats dt { font-size: 11px; color: var(--ink-dim); }
.stats dd { font-size: 17px; }
dl.facts dt { color: var(--ink-dim); font-size: 12.5px; }
dl.facts dd { color: var(--ink); font-size: 14px; }

/* Sidebar and header. */
nav.rail a { font-size: 13.5px; color: var(--ink-muted); }
nav.rail a[aria-current="page"] { color: var(--ink); }
nav.rail .group { font-size: 11px; color: var(--ink-dim); }
header.app .badge { font-size: 12px; color: var(--ink-muted); }
label { font-size: 12px; color: var(--ink-dim); }
input, select, textarea { font-size: 14px; }
button { font-size: 13px; }

h2 { font-size: 16px; }

/* The access request as one centred column. Two columns put a four-paragraph rights
 * statement in a narrow gutter, which is the wrong shape for reading terms. The form is
 * short so it sits centred at a comfortable width; the qualifying information and the
 * rights position run the full width of the card underneath it. */
.authstack { display: block; }
.authstack .formwrap { max-width: 560px; margin: 0 auto 8px; }
.authstack .formwrap .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.authstack .formwrap .field { margin-bottom: 16px; }
.authstack .formwrap .hint.spanner { margin: 2px 0 16px; }
.authstack .formwrap .below { text-align: center; }

.authstack .qualify {
  margin-top: 34px; padding-top: 30px; border-top: 1px solid var(--line);
}
.authstack .qualify .eligibility { margin-bottom: 18px; }
/* Four bullets read better two across at this width than as one tall list. */
.authstack .qualify .eligibility ul {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: 0 26px;
}
/* The rights text keeps a measure so the lines do not run to 140 characters. */
.authstack .qualify .rights { margin-bottom: 18px; }
.authstack .qualify .rights p { max-width: 92ch; }

@media (max-width: 560px) {
  .authstack .formwrap .row2 { grid-template-columns: 1fr; }
}

/* =============================================================================
 * NO GREY TEXT. ANYWHERE.
 *
 * The owner's instruction, twice: grey reads as unimportant and none of this text is
 * unimportant. Every other colour stays exactly as it is.
 *
 * The two muted inks were the interface's way of building hierarchy out of contrast, which
 * is a legitimate technique and the wrong one here: it was applied to whole sentences, not
 * just to labels sitting beside a live figure. So both tokens now resolve to the primary
 * ink, and hierarchy comes from size, weight, letter-spacing and case instead. That is a
 * sturdier basis anyway, since it survives a change of background.
 *
 * Defined last and on :root, so it wins over every earlier layer without needing !important
 * and without editing the forty-odd rules that reference these tokens by name.
 *
 * Untouched, deliberately: --duck, --warn, --bad, --good and --cool. Status colour still
 * means what it meant, and the duck still means "your turn".
 * ========================================================================== */
:root,
body,
body.public {
  --ink-muted: #e7e9ec;
  --ink-dim: #e7e9ec;
}

/* Two places kept a deliberate step down, because they are not prose and they sit directly
 * against text that must win: the film-strip frame numbers and the timecode furniture in the
 * hero corners. Both are set from the ink at reduced opacity rather than from a grey, so they
 * track the palette instead of drifting away from it. */
.marks span, .filmstrip .frame span { color: var(--ink); opacity: 0.55; }

/* =============================================================================
 * THE SETUP REPORT
 *
 * A checklist whose state is measured, so the colour carries real information: green means
 * a directory was stat'ed and a permission was checked, not that somebody typed a path.
 * Three states, and the middle one matters most: outstanding items let the service run
 * while making a weaker promise than the public pages do.
 * ========================================================================== */
.checklist { display: block; }
.checklist .item {
  display: grid; grid-template-columns: 14px minmax(0, 1fr) 96px 110px;
  gap: 14px; align-items: start; padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.checklist .item:last-child { border-bottom: 0; }
.checklist .dot {
  width: 8px; height: 8px; border-radius: 50%; margin-top: 6px;
  background: var(--line-strong);
}
.checklist .item.ok .dot { background: var(--good); }
.checklist .item.blocking .dot { background: var(--bad); }
.checklist .item.degraded .dot { background: var(--warn); }
.checklist .name { margin: 0; font-weight: 600; font-size: 14.5px; color: var(--ink); }
.checklist .detail {
  margin: 4px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--ink); max-width: 84ch;
}
.checklist .verdict, .checklist .act { padding-top: 2px; }
.checklist .act { text-align: right; }
.checklist .act button { width: 100%; }

/* Navigation cards, for the pages the report sends an administrator to. */
.cards {
  display: grid; gap: 12px; margin-top: 4px;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
}
.cards .card {
  display: block; padding: 16px 18px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
  text-decoration: none; border-bottom-width: 1px;
  transition: border-color 140ms, background 140ms;
}
.cards .card:hover { border-color: var(--line-strong); background: var(--raised); }
.cards .card b { display: block; font-size: 14.5px; color: var(--ink); margin-bottom: 5px; }
.cards .card span { display: block; font-size: 13.5px; line-height: 1.5; color: var(--ink); }

.notice.good { border-left: 2px solid var(--good); }
ul.tight { margin: 6px 0 0; padding-left: 18px; }
ul.tight li { margin-bottom: 3px; }

@media (max-width: 760px) {
  .checklist .item { grid-template-columns: 14px minmax(0, 1fr); }
  .checklist .verdict, .checklist .act { grid-column: 2; text-align: left; padding-top: 0; }
  .checklist .act button { width: auto; }
}

/* Rights verification. An approval carries a note, and a note does not fit in a table cell
 * beside a toggle, so the approve control is a small form rather than a button. */
table.rights td { vertical-align: top; }
table.rights .when {
  display: block; margin-top: 3px; font-size: 12px; color: var(--ink); opacity: 0.75;
}
form.approve { display: flex; gap: 8px; align-items: center; margin: 0; }
form.approve input[type="text"] { min-width: 280px; }
form.inline { display: inline; margin: 0; }
@media (max-width: 900px) {
  form.approve { flex-direction: column; align-items: stretch; }
  form.approve input[type="text"] { min-width: 0; }
}

/* =============================================================================
 * THE PROJECT PAGE: FOUR STEPS, TOP TO BOTTOM
 *
 * Connect, order, run, save. That is the product, and it is one page per title with the four
 * steps down it in order. Each step shows its own state and its next action in place, so a
 * customer never leaves the project to do something to the project. The colour on the left
 * edge is the state: grey to do, amber partly done, green done.
 * ========================================================================== */
.flow { display: block; }
.stage {
  display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: 0 18px;
  padding: 22px 0 26px; border-top: 1px solid var(--line);
}
.stage:first-child { border-top: 0; padding-top: 6px; }
.stage .mark {
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  font-family: var(--mono); font-weight: 600; font-size: 14px;
  border: 2px solid var(--line-strong); color: var(--ink);
}
.stage.part .mark { border-color: var(--warn); }
.stage.ok .mark { border-color: var(--good); background: var(--good); color: var(--bg); }
.stage .what h2 { margin: 4px 0 8px; font-size: 17px; }
.stage .what > p { margin: 0 0 12px; max-width: 78ch; }
.stage details { margin-top: 6px; }
.stage summary { cursor: pointer; font-weight: 600; color: var(--ink); }
.stage form.stack { margin-top: 12px; }
.dim { color: var(--ink); opacity: 0.6; }
table.grid td.cell { white-space: nowrap; }
table.grid td.cell .state { margin-right: 6px; }


/* Pricing rows carry their compute tier. Rented-GPU rows are the ones that cost us before they
 * cost a customer, so they get the warm state colour rather than the failure red: a fact to
 * price around, not something wrong. */
tr.tier-rented_gpu .state.failed { background: var(--warn); color: var(--bg); }

/* ---------------------------------------------------------------- app text size and colour
 * Inside the app shell nothing is grey and nothing is small. Status chips, labels, hints, table
 * heads and rail entries all sit at 13px in full ink. The public pages keep their own scale. */
.shell .state, .shell label, .shell .field .hint, .shell th, .shell nav.rail a,
.shell nav.rail .group > span, .shell .crumbs, .shell .page-head p, .shell dl.facts dt,
.shell .sev, .shell button, .shell .badge, .shell nav.rail a .count { font-size: 13px; }
.shell .dim { opacity: 1; }
.shell .field .hint, .shell label, .shell th, .shell nav.rail .group > span,
.shell dl.facts dt, .shell .state.skipped { color: var(--ink); }
