/* graphyt.in — full-viewport hero */

/* Colours come from theme.css, the one palette shared with app.graphyt.in (a copy of
   panel/theme.css; tests/test_theme.py keeps the two identical). Only what the site alone
   needs is set here. */
:root {
  --paper-clear: rgba(0, 0, 0, 0);
  /* The scans are stored as negatives on white and shown as true negatives, black ground and
     bright tissue: inverted in full, so their white is the black paper and their tissue keeps
     its whole range. */
  --scan-filter: invert(1);
  --scan-ground: #FFFFFF;
  --gutter: 20px;
  --reserve: 420px;   /* portrait: the height the text block needs below the scan */
  --ease: cubic-bezier(.65, 0, .35, 1);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Humant', ui-sans-serif, system-ui, sans-serif;
  font-weight: 500;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, p { margin: 0; }
img, svg { display: block; }
a { color: inherit; }
::selection { background: var(--ink); color: var(--paper); }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 2px; }

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

.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  padding: var(--gutter);
  overflow: hidden;
  background: var(--paper);
}

/* Background layer: the scan sits behind the text, its white ground melts into the page. */
.hero__media {
  transform-origin: 50% 30%;
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* Portrait: the scan fills the space above the text. */
.hero__stage {
  --stage: clamp(190px, calc((100vh - var(--reserve)) * 1.15), 108vw);
  --stage: clamp(190px, calc((100svh - var(--reserve)) * 1.15), 108vw);
  position: absolute;
  width: var(--stage);
  height: var(--stage);
  left: 50%;
  top: calc(var(--gutter) + 26px + clamp(0px, (100vh - 780px) / 6, 12px));
  top: calc(var(--gutter) + 26px + clamp(0px, (100svh - 780px) / 6, 12px));
  transform: translateX(-50%);
  /* The head sits inside 76% of the radius; beyond that the video's own white fades into the page. */
  -webkit-mask-image: radial-gradient(closest-side, #000 80%, transparent 99%);
  mask-image: radial-gradient(closest-side, #000 80%, transparent 99%);
}

.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: var(--scan-ground);
  filter: var(--scan-filter);
  z-index: 1;
  opacity: 1;
  transition: opacity 500ms linear;
}

/* No play glyph, ever. Video here is scenery, not a player: it is muted, inline and decorative,
   and the tap target belongs to the page underneath. iOS paints a start-playback button over any
   video it refuses to autoplay (Low Power Mode, low battery) — a button that would do nothing,
   because the element takes no pointer events. Hide every native control on every video, and let
   app.js keep asking for playback until it starts. One selector per rule on purpose: a browser
   that does not know one of these pseudo-elements throws out the whole selector list, and the
   glyph we are hiding lives in the browser most likely to disagree. */
video { pointer-events: none; }
video::-webkit-media-controls { display: none !important; -webkit-appearance: none !important; }
video::-webkit-media-controls-enclosure { display: none !important; -webkit-appearance: none !important; }
video::-webkit-media-controls-panel { display: none !important; -webkit-appearance: none !important; }
video::-webkit-media-controls-overlay-play-button { display: none !important; -webkit-appearance: none !important; }
video::-webkit-media-controls-start-playback-button { display: none !important; -webkit-appearance: none !important; }

/* The loop dissolve: a twin video waits hidden on frame 0 and fades in over the ending one. */
.hero__video.is-spare { opacity: 0; z-index: 0; transition: none; }
.hero__video.is-incoming { z-index: 2; }

.hero__top,
.hero__text { position: relative; z-index: 1; }

.hero__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}
.brand__mark { height: 28px; width: auto; flex: none; color: var(--ink); }

/* The mark's three segments part and rejoin, but only when the visitor asks:
   hover, keyboard focus, or a tap on touch screens. */
.brand__mark .seg { transform-box: view-box; }
@keyframes brand-part {
  0%, 100% { transform: translate(0, 0); }
  42%, 58% { transform: translate(var(--dx), var(--dy)); }
}
.brand__mark .s1 { --dx: 34px; --dy: -58px; }
.brand__mark .s2 { --dx: -66px; --dy: 0px; }
.brand__mark .s3 { --dx: 34px; --dy: 58px; }

@media (hover: hover) {
  .brand:hover .brand__mark .seg,
  .brand:focus-visible .brand__mark .seg { animation: brand-part 2.4s cubic-bezier(.65, 0, .35, 1) infinite; }
  .brand:hover .brand__mark .s2,
  .brand:focus-visible .brand__mark .s2 { animation-delay: .1s; }
  .brand:hover .brand__mark .s3,
  .brand:focus-visible .brand__mark .s3 { animation-delay: .2s; }
}

/* Same loop, driven by a class app.js owns. By the time a mouseleave handler runs, :hover has
   already stopped matching and the segments read as home, with nothing to ease back from. This
   class outlives the pointer by one frame, which is long enough to catch the pose they were in. */
.brand.is-parting .brand__mark .seg { animation: brand-part 2.4s cubic-bezier(.65, 0, .35, 1) infinite; }
.brand.is-parting .brand__mark .s2 { animation-delay: .1s; }
.brand.is-parting .brand__mark .s3 { animation-delay: .2s; }

/* Leaving mid-flight used to snap the segments home. app.js freezes them where they are and
   adds this class, so they glide back on the same curve they left on. */
.brand__mark .seg.is-settling { transition: transform 560ms var(--ease); }

.brand.is-tapped .brand__mark .seg { animation: brand-part 2.4s cubic-bezier(.65, 0, .35, 1) 1; }
.brand.is-tapped .brand__mark .s2 { animation-delay: .1s; }
.brand.is-tapped .brand__mark .s3 { animation-delay: .2s; }

@media (prefers-reduced-motion: reduce) {
  .brand__mark .seg { animation: none !important; }
}
.brand__word { font-weight: 700; font-size: 1.0625rem; letter-spacing: -0.02em; line-height: 1; }
/* A product's page puts its name after the wordmark, same size, same font, a lighter weight:
   graphyt vault. */
.brand__product { margin-left: -2px; font-weight: 400; font-size: 1.0625rem; letter-spacing: -0.02em; line-height: 1; color: var(--ink); }

/* ---------- sound toggle ---------- */

.sound {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 0 14px 0 12px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 120ms var(--ease);
}
.sound[hidden] { display: none; }
.sound__glyph { width: 18px; height: 18px; flex: none; }
.sound__on { opacity: 0; transition: opacity 200ms var(--ease); }
.sound__off { opacity: 1; transition: opacity 200ms var(--ease); }
.sound[aria-pressed="true"] { border-color: var(--ink); }
.sound[aria-pressed="true"] .sound__on { opacity: 1; }
.sound[aria-pressed="true"] .sound__off { opacity: 0; }
@media (hover: hover) { .sound:hover { border-color: var(--ink); } }

/* ---------- log in ---------- */

/* The way into app.graphyt.in for invited radiologists. It wears the sound toggle's 40px hairline
   pill rather than the 52px ink one, so the top row reads as one family and "Book a call" stays
   the loudest thing on the page: most visitors are meeting Graphyt, only the invited few sign in. */
.hero__tools { display: flex; align-items: center; gap: 8px; flex: none; }

.login {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 16px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  transition: border-color 120ms var(--ease);
}
@media (hover: hover) { .login:hover { border-color: var(--ink); } }

/* The top row sits over the graph paper (70) and under the cursor (80). */
.hero__top { z-index: 75; }
/* Narrow phones: Sound keeps its glyph and drops its word, so the logo, Sound and Log in fit. */
@media (max-width: 389px) {
  .sound { padding: 0 11px; }
  .sound > span { display: none; }
}
@media (max-width: 359px) {
  .hero__top { gap: 8px; }
}

/* ---------- text ---------- */

.hero__text {
  margin-top: auto;
  padding-top: 48px;
  max-width: 720px;
}

/* A soft white lift so the headline never sits on the scan's edge. */
.hero__text::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: calc(var(--gutter) * -1);
  right: calc(var(--gutter) * -1);
  top: 16px;
  bottom: calc(var(--gutter) * -1);
  background: linear-gradient(to bottom, var(--paper-clear), var(--paper) 32px);
  pointer-events: none;
}

.eyebrow {
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.eyebrow > span { display: block; }
.eyebrow__dot { display: none !important; }

/* The text arrives once, in order, then holds still. (It used to drift and one word used to
   breathe, but text that moves by a fraction of a pixel is redrawn soft.) */
@keyframes hero-rise {
  from { opacity: 0; transform: translate3d(0, 16px, 0); }
  to { opacity: 1; transform: none; }
}
.headline__beat { display: inline-block; }

.hero__text .eyebrow { animation: hero-rise 820ms cubic-bezier(.2, .7, .2, 1) 120ms both; }
.hero__text .headline { animation: hero-rise 1000ms cubic-bezier(.2, .7, .2, 1) 220ms both; }
.hero__text .actions { animation: hero-rise 860ms cubic-bezier(.2, .7, .2, 1) 430ms both; }

@media (prefers-reduced-motion: reduce) {
  .hero__text .eyebrow,
  .hero__text .headline,
  .hero__text .actions { animation: none; }
}

.headline {
  margin-top: 16px;
  font-weight: 700;
  font-size: clamp(2.5rem, 11vw, 5.5rem);
  line-height: 1;
  letter-spacing: -0.035em;
  text-wrap: balance;
}


/* ---------- pills ---------- */

.actions { margin-top: 32px; display: flex; flex-direction: column; gap: 12px; }

.pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 52px;
  padding-inline: 26px;
  border-radius: 999px;
  border: 1px solid var(--ink);
  font-weight: 700;
  font-size: 1.0625rem;
  line-height: 1;
  text-decoration: none;
  transition: background-color 120ms var(--ease), color 120ms var(--ease), border-color 120ms var(--ease);
}
.pill__glyph { width: 17px; height: 17px; flex: none; }
.pill--ink { background: var(--ink); color: var(--paper); }
.pill--paper { background: var(--paper); color: var(--ink); border-color: var(--hairline); }

@media (hover: hover) {
  .pill--ink:hover { background: var(--paper); color: var(--ink); }
  .pill--paper:hover { border-color: var(--ink); }
}

/* ---------- breakpoints ---------- */

/* Short phones: tighten the text so the scan keeps its size. */
@media (max-height: 720px) and (max-aspect-ratio: 1/1) {
  :root { --reserve: 380px; }
  .hero__text { padding-top: 32px; }
  .headline { font-size: clamp(2.25rem, 10vw, 5.5rem); }
  .actions { margin-top: 24px; }
  .pill { min-height: 48px; }
}

@media (min-width: 480px) {
  .actions { flex-direction: row; }
  .eyebrow > span { display: inline; }
  .eyebrow__dot { display: inline !important; }
}

@media (min-width: 768px) {
  :root { --gutter: 40px; --reserve: 420px; }
  .brand__mark { height: 32px; }
  .brand__word, .brand__product { font-size: 1.25rem; }
}

/* Landscape: text on the left, the scan on the right, both vertically centred. */
@media (min-width: 768px) and (min-aspect-ratio: 1/1) {
  .hero__stage {
    --stage: min(90vh, 56vw);
    --stage: min(90svh, 56vw);
    left: calc(75vw - var(--stage) / 2);
    top: 50%;
    transform: translateY(-50%);
  }
  .hero__text {
    margin-block: auto;
    padding-top: 0;
    max-width: 44vw;
  }
  .hero__text::before { display: none; }
  .headline { font-size: clamp(2.75rem, 5.4vw, 5.5rem); }
}

@media (min-width: 1280px) {
  :root { --gutter: 64px; }
}

/* ---------- features ---------- */

html { scrollbar-color: var(--ash) var(--paper); }

.features { padding: 88px var(--gutter) 96px; }

/* The statement. The h2 is read by everyone; on wide screens a visual copy sits beside the scan. */
.features__lead,
.features__title {
  margin: 0;
  font-weight: 700;
  font-size: clamp(2.25rem, 8.6vw, 4.25rem);
  line-height: 1.04;
  letter-spacing: -0.035em;
}
.features__lead > span,
.features__title > span { display: block; }
.features__lead > span:not(:last-child),
.features__title > span:not(:last-child) { color: var(--statement); }
.nw { white-space: nowrap; }
.lit { transition: none; }
.features__lead { max-width: 1240px; margin: 0 auto 32px; }
.features__title { display: none; }

.features__inner { max-width: 1240px; margin-inline: auto; }
.features__aside { margin-bottom: 40px; }

/* ---------- scan panel ---------- */

.scan { margin: 0; }
.scan__frame {
  position: relative;
  width: min(100%, 520px);
  aspect-ratio: 1 / 1;
  border-radius: 22px;
  overflow: hidden;
}

.scan__canvas { position: relative; display: block; width: 100%; height: 100%; filter: var(--scan-filter); }

/* Annotation overlay: leader lines out to blank label pills, the suggestion of a labelled plate.
   Ink with a paper outline, so a pill stays readable whether ink or paper sits under it. */
.scan__labels { position: absolute; inset: 0; z-index: 3; pointer-events: none; overflow: visible; }
/* Stroke widths live in viewBox units so the dashed draw-on maths stays exact at any size. */
.scan__labels .lead-halo { fill: none; stroke: var(--paper); stroke-width: .62; stroke-linecap: round; stroke-linejoin: round; }
.scan__labels .lead { fill: none; stroke: var(--ink); stroke-width: .26; stroke-linecap: round; stroke-linejoin: round; }
.scan__labels .pill { fill: var(--ink); stroke: var(--paper); stroke-width: .44; paint-order: stroke; transform-box: fill-box; }
.scan__labels .dot { fill: var(--ink); stroke: var(--paper); stroke-width: .34; paint-order: stroke; }

/* Smaller plates need proportionally heavier lines to stay crisp. */
@media (max-width: 1099px) {
  .scan__labels .lead-halo { stroke-width: .9; }
  .scan__labels .lead { stroke-width: .4; }
  .scan__labels .pill { stroke-width: .6; }
  .scan__labels .dot { stroke-width: .45; }
}
/* A still slice stands in before the sequence loads, and for visitors without JavaScript.
   It sits on its own layer so dark mode can turn it into a negative without touching the labels. */
.scan__frame::before {
  content: "";
  position: absolute;
  inset: 0;
  background: #FFFFFF url("media/slices/m024.webp?v=2") center / contain no-repeat;
  filter: var(--scan-filter);
}

/* ---------- cards ---------- */

.features__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.feature {
  padding: 26px 24px 28px;
  border: 1px solid var(--hairline);
  border-radius: 22px;
  background: var(--paper);
}

.feature__icon {
  --cut: var(--paper);
  display: block;
  width: 52px;
  height: 52px;
  margin-bottom: 26px;
  color: var(--ink);
  overflow: visible;
}

.feature__title {
  margin: 0 0 8px;
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.25;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.feature__text {
  margin: 0;
  max-width: 42ch;
  font-size: 1.0625rem;
  line-height: 1.55;
  color: var(--ink-muted);
}

.feature--ink {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.feature--ink .feature__icon { --cut: var(--ink); color: var(--paper); }
.feature--ink .feature__text { color: var(--ash); }

.feature__badges {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}
.feature__badges li {
  padding: 5px 12px;
  border: 1px solid currentColor;
  border-radius: 999px;
  font-size: 0.875rem;
  line-height: 1.4;
  opacity: 0.8;
}

@media (min-width: 640px) {
  .features__grid { grid-template-columns: 1fr 1fr; gap: 16px; }
  .feature:not(.feature--wide) { display: grid; grid-row: span 3; grid-template-rows: subgrid; row-gap: 0; }
  .feature:not(.feature--wide) .feature__body { display: contents; }
  .feature--wide { grid-column: 1 / -1; }
  .feature--wide {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 28px;
    align-items: center;
  }
  .feature--wide .feature__icon { margin-bottom: 0; width: 64px; height: 64px; }
  .feature--wide .feature__text { max-width: 60ch; }
}

@media (min-width: 768px) {
  .features { padding-block: 120px 128px; }
  .features__lead { margin-bottom: 48px; }
  .feature { padding: 30px 30px 32px; }
}

/* Wide screens without the scroll story: statement and scan pinned beside the grid. */
@media (min-width: 1100px) {
  .features__lead {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .features__title { display: block; font-size: clamp(2.25rem, 3.4vw, 3.05rem); }
  .features__title > span { white-space: nowrap; }
  .features__inner {
    display: grid;
    grid-template-columns: minmax(0, 11fr) minmax(0, 13fr);
    column-gap: 72px;
    align-items: start;
  }
  .features__aside { position: sticky; top: 96px; margin: 0; }
  .features__aside .scan { margin-top: 36px; }
}

/* ---------- scroll story (switched on by app.js) ---------- */

.features__progress { display: none; }
.features__progress i { display: block; height: 3px; border-radius: 2px; background: var(--hairline); overflow: hidden; }
.features__progress i::after {
  content: ""; display: block; height: 100%;
  background: var(--ink);
  transform-origin: left center;
  transform: scaleX(var(--fill, 0));
}

/* Version sentinel: app.js only enables the scroll story when this stylesheet is the matching one. */
.features__track { --story-css: 4; }

/* The cards scroll natively, in flow. The scan and the rail are sticky, so they hold still while
   the column passes them. Nothing here is positioned by script, which is what keeps it smooth. */
.features.is-story { padding: 0; }
.is-story .features__track { position: relative; }
.is-story .features__progress { display: grid; grid-template-columns: repeat(var(--segs, 6), 1fr); gap: 6px; }
.is-story .features__progress i::after { transition: transform 420ms var(--ease); }

/* Only the card the column has arrived at is fully inked; the rest wait their turn. */
.is-story .feature { transition: opacity 340ms var(--ease); will-change: opacity; }
.is-story .feature:not(.is-live) { opacity: .16; }
.is-story .feature--ink:not(.is-live) { opacity: .1; }

/* Phones and tablets: the scan held at the top, the rail at the foot, the cards running between. */
@media (max-width: 1099px) {
  .is-story .features__lead { margin: 0 auto; padding: 88px var(--gutter) 8px; }
  .is-story .features__pin { padding-inline: var(--gutter); }
  .is-story .features__inner { width: 100%; max-width: 560px; margin-inline: auto; }
  .is-story .features__aside {
    position: sticky; top: 0; z-index: 40; margin: 0;
    padding: max(12px, 2.4svh) 0 12px;
    background: var(--paper);
    box-shadow: 0 14px 18px -8px var(--paper);
  }
  .is-story .scan__frame { width: min(72vw, 34svh); margin-inline: auto; }
  .features.is-story .features__grid {
    display: flex; flex-direction: column; gap: 22px;
    padding-block: 24px 28px;
    -webkit-mask-image: none; mask-image: none;
  }
  .features.is-story .feature { display: block; }
  .features.is-story .feature__body { display: block; }
  .features.is-story .feature--wide { display: block; }
  .features.is-story .feature__icon { margin-bottom: 22px; }
  .features.is-story .feature--wide .feature__icon { width: 52px; height: 52px; margin-bottom: 22px; }
  /* Short and centred rather than edge to edge, riding the foot of the screen on its own white band. */
  .is-story .features__progress {
    position: sticky; z-index: 60;
    bottom: max(14px, calc(env(safe-area-inset-bottom, 0px) + 10px));
    width: 100%; margin: 0; padding: 14px 0 10px;
    background: var(--paper);
    box-shadow: 0 -14px 18px -8px var(--paper);
    grid-template-columns: repeat(var(--segs, 6), minmax(0, 16px));
    justify-content: center;
    gap: 6px;
  }
}

/* The page goes on past the story (to the voice invitation), so the end of the section carries
   the scan up with it. A sticky box keeps its margin box inside its parent: this margin halts the
   scan above the rail, where the rail's white band would otherwise clip its rounded foot. The
   grid takes the same distance back, so nothing moves while the story runs. */
@media (max-width: 1099px) {
  .is-story .features__aside { margin-bottom: 40px; }
  .features.is-story .features__grid { margin-top: -40px; }
}

/* Wide screens: the statement and the scan stand still on the left, the rail beside them, the
   cards scrolling past on the right. */
@media (min-width: 1100px) {
  .is-story .features__pin { padding-inline: var(--gutter); }
  .is-story .features__inner {
    width: 100%; align-items: start;
    grid-template-columns: minmax(0, 11fr) auto minmax(0, 13fr);
    column-gap: 36px;
  }
  .is-story .features__aside {
    grid-column: 1; grid-row: 1; margin: 0;
    position: sticky; top: 0;
    height: 100vh; height: 100svh;
    display: flex; flex-direction: column; justify-content: center;
  }
  .is-story .features__aside .scan { margin-top: clamp(14px, 2.6vh, 30px); }
  .is-story .scan__frame { width: min(100%, min(58vh, 560px)); }
  .is-story .features__progress {
    grid-column: 2; grid-row: 1; margin: 0;
    position: sticky; top: 0;
    height: 100vh; height: 100svh;
    align-content: center;
    grid-template-columns: 3px;
    grid-template-rows: repeat(var(--segs, 6), clamp(12px, 2vh, 20px));
    gap: 8px;
  }
  .is-story .features__progress i { width: 3px; height: auto; }
  .is-story .features__progress i::after { transform-origin: center top; transform: scaleY(var(--fill, 0)); }
  .features.is-story .features__grid {
    grid-column: 3; grid-row: 1;
    display: flex; flex-direction: column; gap: 18px;
    padding-block: calc(50svh - 104px);
  }
  .features.is-story .feature {
    display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 30px; align-items: start;
    padding: 34px 38px 36px;
  }
  .features.is-story .feature__body { display: block; }
  .features.is-story .feature__icon { margin: 0; width: 64px; height: 64px; }
  .features.is-story .feature__title { font-size: 1.5rem; margin-bottom: 10px; }
  .features.is-story .feature__text { max-width: 52ch; font-size: 1.125rem; }
  .features.is-story .feature--wide { align-items: center; }
  .features.is-story .feature--wide .feature__icon { width: 64px; height: 64px; }
}

/* Shorter laptop screens: tighten the cards so a whole one still fits the stage. */
@media (min-width: 1100px) and (max-height: 840px) {
  .is-story .features__title { font-size: clamp(1.875rem, min(3.4vw, 4.8vh), 3.05rem); }
  .features.is-story .feature { padding: 22px 26px 24px; }
}

/* ---------- icon marks ---------- */

.feature__icon * { transform-box: fill-box; transform-origin: center; }
.i-think path { transform-box: view-box; transform-origin: 0 0; }
.feature__icon .k { fill: var(--cut); }
.feature__icon .ks { fill: none; stroke: var(--cut); stroke-linecap: round; stroke-linejoin: round; }
.feature__icon .is { fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; }
.feature__icon .g { stroke: var(--cut); stroke-width: 5px; paint-order: stroke; }

/* Resting poses are the finished state. Every loop starts and ends on that pose, so nothing
   jumps when a card scrolls into view, and motion only runs while the card is on screen. */
.i-keys .slash, .i-error .squig { stroke-dasharray: 1 1; stroke-dashoffset: 0; }
.i-correct .old { opacity: 0; }
.i-correct .strike { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.i-scan .sweep { opacity: 0; }
.i-context .l, .i-report .r { transform-origin: left center; }

.is-live .feature__icon * { animation-timing-function: cubic-bezier(.65, 0, .35, 1); animation-iteration-count: infinite; animation-fill-mode: both; }

/* Speak naturally, and the dictation half of the report mark: voice bars */
.is-live .i-speak .b, .is-live .i-report .w { animation-name: mark-voice; animation-duration: 1.1s; }
.is-live .i-speak .b:nth-of-type(2) { animation-delay: .28s; }
.is-live .i-speak .b:nth-of-type(3) { animation-delay: .12s; }
.is-live .i-speak .b:nth-of-type(4) { animation-delay: .4s; }
.is-live .i-speak .b:nth-of-type(5) { animation-delay: .2s; }
.is-live .i-report .w:nth-of-type(2) { animation-delay: .25s; }
.is-live .i-report .w:nth-of-type(3) { animation-delay: .5s; }
@keyframes mark-voice { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(.3); } }

/* Never touch the keyboard: the slash erases from its start and redraws from its start.
   The dash pattern repeats every 2, so -2 is the resting pose again. */
.is-live .i-keys .slash { animation-name: mark-slash; animation-duration: 4s; }
@keyframes mark-slash { 0%, 38% { stroke-dashoffset: 0; opacity: 1; } 47% { opacity: 1; } 53%, 62% { stroke-dashoffset: -1; opacity: 0; } 64% { stroke-dashoffset: -1.14; opacity: 0; } 67% { stroke-dashoffset: -1.24; opacity: 1; } 84%, 100% { stroke-dashoffset: -2; opacity: 1; } }

/* Context and report lines clear, then build back line by line */
.is-live .i-context .l, .is-live .i-report .r { animation-name: mark-line; animation-duration: 4.2s; }
.is-live .i-context .l:nth-of-type(3) { animation-delay: .15s; }
.is-live .i-context .l:nth-of-type(4) { animation-delay: .3s; }
.is-live .i-report .r:nth-of-type(6) { animation-delay: .12s; }
.is-live .i-report .r:nth-of-type(7) { animation-delay: .24s; }
.is-live .i-report .r:nth-of-type(8) { animation-delay: .36s; }
.is-live .i-report .r:nth-of-type(9) { animation-delay: .48s; }
@keyframes mark-line { 0%, 42% { transform: scaleX(1); } 56%, 62% { transform: scaleX(0); } 80%, 100% { transform: scaleX(1); } }
.is-live .i-context .c2 { animation-name: mark-float; animation-duration: 4.2s; animation-delay: .15s; }
.is-live .i-context .c3 { animation-name: mark-float; animation-duration: 4.2s; animation-delay: .3s; }
@keyframes mark-float { 0%, 100% { transform: translate(0, 0); } 30%, 70% { transform: translate(2px, -2px); } }
.is-live .i-report .arrow { animation-name: mark-nudge; animation-duration: 1.6s; }
@keyframes mark-nudge { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(2.5px); } }

/* It catches what you miss: the lens leaves the finding, sweeps back and finds it again */
.is-live .i-miss .lens { animation-name: mark-lens; animation-duration: 4.6s; }
.is-live .i-miss .spot { animation-name: mark-spot; animation-duration: 4.6s; }
@keyframes mark-lens { 0%, 36% { transform: translate(0, 0); } 50%, 58% { transform: translate(-10px, 0); } 82%, 100% { transform: translate(0, 0); } }
@keyframes mark-spot { 0%, 36% { transform: scale(1); } 42%, 80% { transform: scale(0); } 86% { transform: scale(1.5); } 92%, 100% { transform: scale(1); } }

/* Errors in real time: the underline and flag clear, the underline redraws, the flag pops */
.is-live .i-error .squig { animation-name: mark-squiggle; animation-duration: 4.4s; }
@keyframes mark-squiggle { 0%, 36% { stroke-dashoffset: 0; opacity: 1; } 45%, 62% { opacity: 0; } 53%, 62% { stroke-dashoffset: -1; } 64% { stroke-dashoffset: -1.14; opacity: 0; } 67% { stroke-dashoffset: -1.24; opacity: 1; } 84%, 100% { stroke-dashoffset: -2; opacity: 1; } }
.is-live .i-error .badge { animation-name: mark-badge; animation-duration: 4.4s; }
@keyframes mark-badge { 0%, 34% { transform: scale(1); opacity: 1; } 40%, 80% { transform: scale(0); opacity: 0; } 86% { transform: scale(1.12); opacity: 1; } 92%, 100% { transform: scale(1); opacity: 1; } }

/* Correct yourself: the corrected phrase rests; the old phrase returns, is struck,
   holds struck for a beat, slides out, and the correction slides back in */
.is-live .i-correct .new { animation-name: mark-new; animation-duration: 5.6s; }
.is-live .i-correct .old { animation-name: mark-old; animation-duration: 5.6s; }
.is-live .i-correct .strike { animation-name: mark-strike; animation-duration: 5.6s; }
@keyframes mark-new { 0%, 24% { opacity: 1; transform: translateX(0); } 30%, 75% { opacity: 0; transform: translateX(5px); } 86%, 100% { opacity: 1; transform: translateX(0); } }
@keyframes mark-old { 0%, 28% { opacity: 0; transform: translateX(0); } 34%, 72% { opacity: 1; transform: translateX(0); } 80%, 100% { opacity: 0; transform: translateX(-5px); } }
@keyframes mark-strike { 0%, 40% { stroke-dashoffset: 1; } 52%, 100% { stroke-dashoffset: 0; } }

/* Templates: blocks clear, then snap back into the layout one after another */
.is-live .i-template .t { transform-origin: center; animation-name: mark-snap; animation-duration: 4.6s; }
.is-live .i-template .t:nth-of-type(3) { animation-delay: .1s; }
.is-live .i-template .t:nth-of-type(4) { animation-delay: .2s; }
.is-live .i-template .t:nth-of-type(5) { animation-delay: .3s; }
.is-live .i-template .t:nth-of-type(6) { animation-delay: .4s; }
@keyframes mark-snap { 0%, 44% { opacity: 1; transform: scale(1); } 54%, 60% { opacity: 0; transform: scale(1); } 61% { opacity: 0; transform: scale(.55); } 74%, 100% { opacity: 1; transform: scale(1); } }

/* Radiology: a scan line travels the brain, fading in and out inside the skull */
.is-live .i-scan .sweep { animation-name: mark-sweep; animation-duration: 2.8s; animation-direction: alternate; }
@keyframes mark-sweep { 0% { opacity: 0; transform: translateY(0); } 14%, 86% { opacity: 1; } 100% { opacity: 0; transform: translateY(30px); } }

/* Intelligence: the logo's three segments part and rejoin */
.is-live .i-think .seg { animation-name: mark-part; animation-duration: 3.2s; }
.is-live .i-think .s1 { --dx: 1.3px; --dy: -2.2px; }
.is-live .i-think .s2 { --dx: -2.5px; --dy: 0px; animation-delay: .12s; }
.is-live .i-think .s3 { --dx: 1.3px; --dy: 2.2px; animation-delay: .24s; }
@keyframes mark-part { 0%, 100% { transform: translate(0, 0); } 40%, 60% { transform: translate(var(--dx), var(--dy)); } }

/* Data stays: the lock lifts open, holds, and drops shut with a small settle */
.is-live .i-vault .shackle { animation-name: mark-lock; animation-duration: 4.2s; }
.is-live .i-vault .led { animation-name: mark-led; animation-duration: 1.6s; }
.is-live .i-vault .led:nth-of-type(2) { animation-delay: .4s; }
.is-live .i-vault .led:nth-of-type(3) { animation-delay: .8s; }
.is-live .i-vault .led:nth-of-type(4) { animation-delay: 1.2s; }
@keyframes mark-lock { 0%, 30% { transform: translateY(0); } 40%, 62% { transform: translateY(-5px); } 70% { transform: translateY(.8px); } 74%, 100% { transform: translateY(0); } }
@keyframes mark-led { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

@media (prefers-reduced-motion: reduce) {
  .is-live .feature__icon * { animation: none !important; }
}

/* ---------- voice ---------- */

/* The invitation to lend a voice at record.graphyt.in. It speaks in the statement's type and,
   on wide screens, stands on the feature section's columns (heading where the statement stood,
   copy and pill where the cards ran), so it reads as the page's last word, not a banner. */
.voice { padding: 0 var(--gutter) 56px; }
/* The scroll story drops the section's own bottom padding; give the same room back here. */
.features.is-story + .voice { padding-top: 96px; }
.voice__inner { max-width: 1240px; margin-inline: auto; }

.voice__title {
  margin: 0;
  font-weight: 700;
  font-size: clamp(2.25rem, 8.6vw, 4.25rem);
  line-height: 1.04;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

.voice__text {
  margin-top: 16px;
  max-width: 42ch;
  font-size: 1.0625rem;
  line-height: 1.55;
  color: var(--ink-muted);
  text-wrap: pretty;
}

@media (min-width: 768px) {
  .voice { padding-bottom: 72px; }
  .features.is-story + .voice { padding-top: 128px; }
  .voice__text { margin-top: 20px; font-size: 1.125rem; }
}

@media (min-width: 1100px) {
  .voice__inner {
    display: grid;
    grid-template-columns: minmax(0, 11fr) minmax(0, 13fr);
    column-gap: 72px;
    align-items: baseline;
  }
  .voice__title { font-size: clamp(2.25rem, 3.4vw, 3.05rem); }
  .voice__text { margin-top: 0; max-width: 52ch; }
}

/* ---------- close ---------- */

.close {
  padding: 28px var(--gutter) 40px;
}
.close__inner {
  max-width: 1240px;
  margin-inline: auto;
  padding-top: 28px;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.close .actions { margin-top: 0; width: 100%; }
@media (min-width: 480px) { .close .actions { width: auto; } }

/* ---------- graphyt.in: the studio's front page ---------- */

/* Graphyt is a venture studio: this page names the domains it works in and the products in each,
   and every product lives on its own site. Until Aditya's studio film lands, the Graphyt mark
   stands in for it, large, its three segments drifting apart and back together. */

/* Portrait: the mark sits above the text. Landscape: on the right, the text on the left. */
.studio__stage {
  --stage: clamp(220px, calc(100svh - 470px), 92vw);
  position: absolute;
  left: 50%;
  top: calc(var(--gutter) + 72px);
  width: var(--stage);
  height: var(--stage);
  transform: translateX(-50%);
  pointer-events: none;
}
.studio__mark { width: 100%; height: 100%; overflow: visible; }
.studio__mark .seg { transform-box: view-box; }
.studio__mark path {
  fill: #0E0E0D;
  stroke: rgba(var(--pulse), .22);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
/* Each segment leaves along its own gap and comes home, one after another, then rests. */
@keyframes studio-part {
  0%, 18%, 100% { transform: translate(0, 0); }
  50%, 64% { transform: translate(var(--dx), var(--dy)); }
}
.studio__mark .s1 { --dx: 60px; --dy: -104px; animation: studio-part 9s var(--ease) 1.2s infinite; }
.studio__mark .s2 { --dx: -120px; --dy: 0px; animation: studio-part 9s var(--ease) 1.35s infinite; }
.studio__mark .s3 { --dx: 60px; --dy: 104px; animation: studio-part 9s var(--ease) 1.5s infinite; }
/* The mark arrives once, from nothing, before the text. */
@keyframes studio-arrive {
  from { opacity: 0; transform: scale(.94); }
  to { opacity: 1; transform: none; }
}
.studio__mark { animation: studio-arrive 1400ms cubic-bezier(.2, .7, .2, 1) both; }

@media (min-width: 768px) and (min-aspect-ratio: 1/1) {
  .studio__stage {
    --stage: min(74svh, 44vw);
    left: auto;
    right: calc(var(--gutter) + 2vw);
    top: 50%;
    transform: translateY(-46%);
  }
  .hero--studio .hero__text { max-width: 48vw; }
}

/* ---------- domains and products ---------- */

.studio { padding-bottom: 120px; }
.domain + .domain { margin-top: 72px; }
.domain__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 16px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--hairline);
}
.domain__name { margin: 0; font-weight: 700; font-size: 1.5rem; line-height: 1.2; letter-spacing: -0.02em; }
.domain__count { margin: 0; font-size: 0.9375rem; color: var(--ink-muted); }

.products { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.product {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 1px solid var(--hairline);
  border-radius: 22px;
  overflow: hidden;
  color: var(--ink);
  text-decoration: none;
  transition: border-color 120ms var(--ease);
}
@media (hover: hover) { .product:hover { border-color: var(--ink); } }
.product__media {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--paper);
}
.product__media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The foot of each film melts into the card. */
.product__media::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 40%;
  background: linear-gradient(to bottom, var(--paper-clear), var(--paper));
}
/* Square films stand whole in the middle of the card, on its black. */
.product__media--square video {
  left: 50%;
  width: auto;
  aspect-ratio: 1;
  transform: translateX(-50%);
}
/* Squire's scan is a negative on white, shown as a true negative and dissolved at its edge, as on
   its own page. */
.product__media--scan video {
  filter: var(--scan-filter);
  -webkit-mask-image: radial-gradient(closest-side, #000 72%, transparent 98%);
  mask-image: radial-gradient(closest-side, #000 72%, transparent 98%);
}
.product__body { display: flex; flex-direction: column; flex: 1; padding: 4px 26px 26px; }
.product__title { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; }
/* The card is named the way the product's logo reads: graphyt squire, graphyt vault. */
.product__name {
  margin: 0;
  font-weight: 700;
  font-size: clamp(1.75rem, 5vw, 2.25rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
}
.product__name .brand__word,
.product__name .brand__product { font-size: inherit; letter-spacing: inherit; }
.product__name .brand__product { margin-left: .26em; }
.product__soon {
  padding: 5px 10px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--ink-muted);
}
.product__note { margin: 10px 0 0; max-width: 34ch; font-size: 1.0625rem; line-height: 1.5; color: var(--ink-muted); }
.product__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: auto 0 0;
  padding-top: 28px;
  font-size: 0.9375rem;
  color: var(--ink-muted);
}
.product__arrow { width: 18px; height: 18px; flex: none; color: var(--ink); transition: transform 200ms var(--ease); }
@media (hover: hover) { .product:hover .product__arrow { transform: translate(2px, -2px); } }

/* Domains the studio is studying next: names only, no promises. */
.next {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 16px;
}
.next li {
  padding: 16px 0;
  border-bottom: 1px solid var(--hairline);
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: -0.015em;
  color: var(--statement);
}

@media (min-width: 768px) {
  .products { grid-template-columns: 1fr 1fr; }
  .product__body { padding: 6px 32px 32px; }
  .next { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .domain + .domain { margin-top: 96px; }
}

@media (prefers-reduced-motion: reduce) {
  .studio__mark, .studio__mark .seg { animation: none; }
}

/* ---------- Vault (graphyt.in/vault/) ---------- */

/* The hero is Aditya's film of a sphere of points turning slowly on black. It stands on a square
   stage where Squire's scan stands on its page: above the words on a phone, to the right
   of them on a wide screen. The film's black is the page's black, so the stage has no edge. */
.hero--vault { background: var(--paper); }
.vault__media { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.vault__stage {
  --stage: clamp(190px, calc((100vh - var(--reserve)) * 1.15), 108vw);
  --stage: clamp(190px, calc((100svh - var(--reserve)) * 1.15), 108vw);
  position: absolute;
  width: var(--stage);
  height: var(--stage);
  left: calc(50% - var(--stage) / 2);
  top: calc(var(--gutter) + 26px + clamp(0px, (100vh - 780px) / 6, 12px));
  top: calc(var(--gutter) + 26px + clamp(0px, (100svh - 780px) / 6, 12px));
}
.vault__video { display: block; width: 100%; height: 100%; background: var(--paper); }
@media (min-width: 768px) and (min-aspect-ratio: 1/1) {
  .vault__stage {
    --stage: min(90vh, 56vw);
    --stage: min(90svh, 56vw);
    left: calc(75vw - var(--stage) / 2);
    top: calc(50% - var(--stage) / 2);
  }
}

/* Scan labels: made-up patients' scans, each card pinned to a point of the sphere. app.js moves a
   label with the film, frame by frame, along a track measured from the footage, so it turns with
   the sphere instead of floating over it. A label lands as a dot, draws its leader, then shows
   its card, beside its point or above or below it, wherever it stays clear for its whole run; it
   lets go before its point turns away. Without JavaScript none shows.
   Sharp text: app.js puts every label on whole device pixels and the card is painted in place,
   with no blur behind it, so its words sit on the screen's own pixel grid. */
.vault__tags { position: absolute; inset: 0; z-index: 2; }
.tag { position: absolute; left: 0; top: 0; }
.tag__dot {
  position: absolute;
  left: -4px;
  top: -4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 3px rgba(var(--pulse), .16), 0 0 14px rgba(var(--pulse), .45);
  opacity: 0;
  transform: scale(.4);
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
/* One ring goes out as the label lands, like a point being picked. */
.tag__dot::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1px solid rgba(var(--pulse), .7);
  opacity: 0;
}
/* The leader runs from the dot to the card; app.js draws it for the side the card hangs on. */
.tag__lead {
  position: absolute;
  left: 0;
  top: 0;
  overflow: visible;
  fill: none;
  stroke: rgba(var(--pulse), .6);
  stroke-width: 1;
  stroke-dasharray: 1 1;
  stroke-dashoffset: 1;
  opacity: 0;
  transition: stroke-dashoffset .45s var(--ease), opacity .4s var(--ease);
}
.tag__card {
  position: absolute;
  left: 46px;
  top: -78px;
  box-sizing: border-box;
  width: 232px;
  padding: 12px;
  font-size: .8125rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--ink);
  background: rgba(6, 6, 8, .86);
  border: 1px solid rgba(var(--pulse), .16);
  border-radius: 14px;
  box-shadow: inset 0 1px 0 rgba(var(--pulse), .12), 0 16px 40px rgba(0, 0, 0, .45);
  opacity: 0;
  transform: translate(var(--from-x, -6px), var(--from-y, 0px));
  transition: opacity .45s var(--ease), transform .45s var(--ease);
}
.tag__card p, .tag__card dl, .tag__card dd { margin: 0; }
.tag__head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: .9375rem; }
.tag__year, .tag__where { color: var(--ink-muted); }
.tag__where { margin-top: 2px; }
/* The private part: who the scan belongs to, shown only as a row of boxes, one per letter or
   digit. app.js brings the boxes in one by one, shuffling, and keeps them changing, so the row
   reads as something locked rather than something blank. */
.tag__private { display: grid; gap: 4px; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(var(--pulse), .12); }
.tag__private div { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.tag__private dt { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-muted); }
.tag__lock { flex: none; width: 10px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.2; }
.tag__cipher { display: inline-flex; align-items: center; gap: 2px; height: 17px; }
.tag__box {
  display: block;
  flex: none;
  width: 6px;
  height: 10px;
  border-radius: 1.5px;
  background: rgba(var(--pulse), .2);
  opacity: 0;
  transition: background-color .24s var(--ease), opacity .3s var(--ease);
}
.tag__box.is-mid { background: rgba(var(--pulse), .5); }
.tag__box.is-on { background: rgba(var(--pulse), .92); }
.tag__box.is-in { opacity: 1; }
.tag__space { display: block; flex: none; width: 4px; }
.tag.is-left .tag__card { --from-x: 6px; }
.tag.is-up .tag__card { --from-x: 0px; --from-y: 6px; }
.tag.is-down .tag__card { --from-x: 0px; --from-y: -6px; }
.tag.is-on .tag__dot { opacity: 1; transform: none; }
.tag.is-on .tag__dot::after { animation: tag-ring 1.2s var(--ease) .1s both; }
.tag.is-on .tag__lead { opacity: 1; stroke-dashoffset: 0; transition-delay: .15s; }
.tag.is-on .tag__card { opacity: 1; transform: none; transition-delay: .4s; }
@keyframes tag-ring {
  from { transform: scale(1); opacity: .9; }
  to { transform: scale(4); opacity: 0; }
}

/* Locks here and there: a small lock settles on a point of the sphere, rides it for a couple of
   seconds and lets go. app.js moves the outer span; the badge inside grows in and fades out. */
.vault__lock { position: absolute; left: 0; top: 0; }
.vault__lock-glyph {
  position: absolute;
  left: -12px;
  top: -12px;
  overflow: visible;
  opacity: 0;
  transform: scale(.5);
  transition: opacity .4s var(--ease), transform .5s var(--ease);
}
.vault__lock-ground { fill: rgba(6, 6, 8, .86); stroke: rgba(var(--pulse), .3); stroke-width: 1; }
.vault__lock-line { fill: none; stroke: var(--ink); stroke-width: 1.3; stroke-linejoin: round; }
.vault__lock.is-on .vault__lock-glyph { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .vault__tags { display: none; } }

@media (min-width: 768px) and (min-aspect-ratio: 1/1) {
  .hero--vault .hero__text { max-width: min(44vw, 640px); }
}

/* Sections below the hero: a statement, then cards on the site's grid. */
.block { padding: 88px var(--gutter) 0; }
.block__inner { max-width: 1240px; margin-inline: auto; }

.statement {
  margin: 0 0 32px;
  font-weight: 700;
  font-size: clamp(2.25rem, 8.6vw, 4.25rem);
  line-height: 1.04;
  letter-spacing: -0.035em;
  text-wrap: balance;
}
.statement > span { display: block; }
.statement > span:not(:last-child) { color: var(--statement); }

.cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.plan__name { margin: 0; font-weight: 700; font-size: 1.25rem; line-height: 1.25; letter-spacing: -0.015em; }
.plan__for { margin: 6px 0 22px; font-size: 1.0625rem; line-height: 1.45; color: var(--ink-muted); }
.plan__list { list-style: none; margin: 0; padding: 0; }
.plan__list li {
  padding: 10px 0;
  border-top: 1px solid var(--hairline);
  font-size: 1.0625rem;
  line-height: 1.45;
  color: var(--ink-muted);
}
.plan__list li:first-child { color: var(--ink); }

.block + .voice { padding-top: 104px; }

@media (min-width: 640px) {
  .cards { grid-template-columns: 1fr 1fr; gap: 16px; }
}
@media (min-width: 768px) {
  .block { padding-top: 128px; }
  .statement { margin-bottom: 48px; }
  .block + .voice { padding-top: 144px; }
}
@media (min-width: 1100px) {
  .statement { font-size: clamp(2.25rem, 3.6vw, 3.4rem); }
  .cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The new marks move like the old ones: only while their card is on screen, always back to rest. */
.i-disc .slash, .i-change .line { stroke-dasharray: 1 1; stroke-dashoffset: 0; }
.is-live .i-record .c2 { animation-name: mark-float; animation-duration: 4.2s; animation-delay: .15s; }
.is-live .i-record .c3 { animation-name: mark-float; animation-duration: 4.2s; animation-delay: .3s; }
.is-live .i-disc .slash { animation-name: mark-slash; animation-duration: 4s; }
.is-live .i-allow .badge, .is-live .i-change .badge { animation-name: mark-badge; animation-duration: 4.4s; }
.is-live .i-compare .spot { animation-name: mark-spot; animation-duration: 4.6s; }
.is-live .i-centres .node { animation-name: mark-led; animation-duration: 1.6s; }
.is-live .i-centres .node:nth-of-type(2) { animation-delay: .4s; }
.is-live .i-centres .node:nth-of-type(3) { animation-delay: .8s; }
.is-live .i-change .line { animation-name: mark-squiggle; animation-duration: 4.4s; }

/* ---------- graph paper under the page (grid.js) ---------- */

/* One canvas over everything. It lightens, so its squares show on the black paper and disappear
   over text, buttons and the scan. */
.grid-reveal {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 70;
  pointer-events: none;
  mix-blend-mode: lighten;
}

/* With a mouse: a small dot on the pointer and a ring that follows it a moment behind. Both
   invert what is under them, so they read on the paper, on ink buttons and on the scan alike. */
.cursor { display: none; }
@media (hover: hover) and (pointer: fine) {
  .has-cursor, .has-cursor * { cursor: none !important; }
  .has-cursor .cursor {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 80;
    pointer-events: none;
    mix-blend-mode: difference;
    opacity: 0;
    transition: opacity 200ms var(--ease);
  }
  .has-cursor .cursor.is-on { opacity: 1; }
  .cursor__dot, .cursor__ring {
    position: absolute;
    left: 0;
    top: 0;
  }
  .cursor__dot {
    width: 6px;
    height: 6px;
    margin: -3px 0 0 -3px;
    border-radius: 50%;
    background: #FFFFFF;
    transform: translate3d(var(--x, -40px), var(--y, -40px), 0);
    transition: opacity 160ms var(--ease);
  }
  .cursor__ring { transform: translate3d(var(--rx, -40px), var(--ry, -40px), 0); }
  .cursor__ring::before {
    content: "";
    position: absolute;
    left: -16px;
    top: -16px;
    width: 32px;
    height: 32px;
    border: 1px solid #FFFFFF;
    border-radius: 50%;
    opacity: .5;
    transition: transform 220ms var(--ease), opacity 220ms var(--ease);
  }
  .cursor.is-link .cursor__dot { opacity: 0; }
  .cursor.is-link .cursor__ring::before { transform: scale(1.5); opacity: .9; }
  .cursor.is-down .cursor__ring::before { transform: scale(.8); }
}
