/* ntvrli design system — components
   Rule: no component may draw a border, a card edge, or a divider line.
   Separation is done by ground colour, scale, and space. Nothing else. */

/* ── Band ─────────────────────────────────────────────────────────────
   The only structural primitive. Full-bleed, edge to edge, no margin.
   A change of band IS the section break. */
.band {
  background: var(--ground);
  color: var(--fg);
  padding: var(--band-pad) var(--gut);
  position: relative;
}
.band-tight { padding-block: calc(var(--band-pad) * .55); }
.band-flush { padding-inline: 0; }

/* Corner anchoring. The empty middle is the composition, not a mistake. */
.anchor {
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  align-items: baseline;
}

/* ── Nav ──────────────────────────────────────────────────────────────
   Wordmark left, links right. No bar, no background, no bottom edge. */
.nav {
  position: absolute; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s-3);
  height: var(--nav-h);
  padding-inline: var(--gut);
  color: #fff;
}
/* The nav crosses whatever the scene happens to be doing up there. A
   difference blend reads as nothing against mid-tones, and the forms are
   glossy — full of them. This is a scrim that feathers to zero over twice the
   nav's height, so it guarantees contrast without drawing an edge anywhere. */
.nav::before {
  content: ""; position: absolute; inset: 0 0 auto 0;
  height: calc(var(--nav-h) * 2.4);
  background: linear-gradient(to bottom,
    rgba(10, 8, 7, .62) 0%,
    rgba(10, 8, 7, .34) 42%,
    rgba(10, 8, 7, 0) 100%);
  pointer-events: none;
  z-index: -1;
}
.nav a { text-decoration: none; }
.nav-id { display: flex; align-items: baseline; gap: var(--s-2); min-width: 0; }
.nav-mark {
  font-family: var(--face-mass);
  font-size: .95rem;
  letter-spacing: -.02em;
  text-transform: lowercase;
  flex: none;
}
/* The nav says what the site is on every page, so no other block has to. */
.nav-what {
  /* This is the line that tells a first-time visitor what the site is, so it
     is secondary to the mark but must not be the faintest thing on the page. */
  opacity: .85;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nav-what::before {
  content: ""; display: inline-block;
  width: 1.6em; height: 1px; margin-right: .7em;
  vertical-align: .28em;
  background: currentColor; opacity: .55;
}
@media (max-width: 780px) { .nav-what { display: none; } }
.nav-links { display: flex; gap: var(--s-3); }
.nav-links a { position: relative; }
.nav-links a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -.25em;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--fast) var(--ease);
}
.nav-links a:hover::after,
.nav-links a:focus-visible::after { transform: scaleX(1); }

/* ── Cropped statement ────────────────────────────────────────────────
   Set large enough that the viewport cuts it. The eye completes the rest. */
.crop { overflow: hidden; }
.crop > span { display: block; white-space: nowrap; }

/* ── The wipe ─────────────────────────────────────────────────────────
   The one orchestrated moment. An oxblood field rises and swallows the
   statement; the covered letters invert to bone. Two stacked copies of the
   same text, the inner one counter-translated so it stays registered.
   One transform per frame — no layout, no paint beyond the composite. */
.stage {
  position: relative;
  height: 190svh;
  /* The wipe ends full-oxblood. The stage carries that colour underneath so
     the sticky can release straight into the oxblood band with no flash. */
  background: var(--oxblood);
}
.stage-fix {
  position: sticky; top: 0; height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  overflow: hidden;
}
.wipe {
  position: absolute; inset: 0;
  background: var(--oxblood);
  overflow: hidden;
  transform: translate3d(0, var(--wipe, 100%), 0);
  will-change: transform;
}
.wipe-in {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: center;
  transform: translate3d(0, calc(var(--wipe, 100%) * -1), 0);
  color: var(--bone);
}

/* ── Work index ───────────────────────────────────────────────────────
   Bands separate SECTIONS, not items. Everything here sits on one ground and
   is separated by space and scale alone — no fills, no rules, no rows of
   colour. Reads as a masthead, not as three stacked slabs. */
.index { display: grid; }
.row {
  display: grid;
  row-gap: var(--s-2);
  padding-block: var(--s-5);
  text-decoration: none;
  color: var(--fg);
}
.row-title { transition: transform var(--fast) var(--ease); }
.row-lead  { max-width: var(--measure); margin-top: var(--s-2); }

.row:hover .row-title,
.row:focus-visible .row-title { transform: translateX(.14em); }

/* ── Specimen ─────────────────────────────────────────────────────────
   How a case study shows work when there is no photography: the deliverables
   themselves. Swatches sit flush with no gaps so the palette reads as one
   continuous strip rather than a row of cards. A colour block here is content
   — it is the thing being shown — not structure. */
.specimen { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); }
.specimen i { display: block; height: clamp(6rem, 14vh, 10rem); }
.specimen b { display: block; font-weight: 400; padding-top: var(--s-2); color: var(--fg); }
.specimen span { display: block; color: var(--fg-quiet); }

/* Plain fact list. Term and value, separated by space only. */
.facts { display: grid; gap: var(--s-3); margin-top: var(--s-4); }
.fact { display: grid; grid-template-columns: 9rem minmax(0, 1fr); gap: var(--s-4); align-items: baseline; }
.fact dt { color: var(--fg-quiet); }
.fact dd { color: var(--fg); max-width: var(--measure); }

/* ── Tiers ────────────────────────────────────────────────────────────
   Stacked, separated by space alone. */
.tier { padding-block: var(--s-4); }
.tier + .tier { margin-top: var(--s-4); }
.tier-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: var(--s-4);
  align-items: start;
}
.tier ul { list-style: none; display: grid; gap: var(--s-2); }
.tier li { color: var(--fg-quiet); }

/* ── Action ───────────────────────────────────────────────────────────
   Solid fill, no border, no radius. There is one primary action per page. */
.action {
  display: inline-block;
  background: var(--fg); color: var(--ground);
  font: inherit; font-size: var(--t-micro);
  letter-spacing: var(--track-micro); text-transform: uppercase;
  padding: var(--s-3) var(--s-4);
  border: none; cursor: pointer; text-decoration: none;
  transition: background var(--fast) var(--ease);
}
.action:hover, .action:focus-visible { background: var(--fg-accent); color: var(--ground); }

.field {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  max-width: 34rem; margin-top: var(--s-4);
}
.field input {
  flex: 1 1 14rem; min-width: 0;
  background: var(--ground); color: var(--fg);
  font: inherit; font-size: var(--t-body);
  padding: var(--s-3);
  border: none;
  box-shadow: inset 0 -2px 0 var(--fg);
}
.field input::placeholder { color: var(--fg-quiet); }
.field input:focus { outline: none; box-shadow: inset 0 -2px 0 var(--fg-accent); }

/* ── Footer ───────────────────────────────────────────────────────────*/
.foot {
  display: flex; justify-content: space-between; gap: var(--s-3);
  flex-wrap: wrap; align-items: baseline;
}

@media (max-width: 760px) {
  .tier-grid { grid-template-columns: 1fr; gap: var(--s-3); }
  .stage { height: 170svh; }
  .row { row-gap: var(--s-1); padding-block: var(--s-4); }
  .fact { grid-template-columns: 1fr; gap: var(--s-1); }
}

@media (prefers-reduced-motion: reduce) {
  /* No wipe means no travel to reserve. */
  .stage { height: 100svh; }
  .wipe { display: none; }
}

/* ── Audit result ─────────────────────────────────────────────────────
   The agent returns twelve keys. Showing four of them wastes the most
   persuasive parts — the score, the evidence, and the questions the visitor
   was about to ask anyway. No panels: the score is mass, the issues reuse the
   index, and everything else is space. */
.audit { display: grid; gap: var(--s-5); }

.audit-top { display: grid; gap: var(--s-4); align-items: end; }
@media (min-width: 900px) { .audit-top { grid-template-columns: auto minmax(0, 1fr); gap: var(--s-6); } }

/* The score is the one number worth setting at display scale. */
.score { display: flex; align-items: baseline; gap: .1em; line-height: .8; }
.score b { font-family: var(--face-mass); font-weight: 400; font-size: clamp(4.5rem, 13vw, 11rem); letter-spacing: -.04em; }
.score i { font-style: normal; font-size: var(--t-micro); letter-spacing: var(--track-micro); text-transform: uppercase; color: var(--fg-quiet); align-self: flex-start; padding-top: .8em; }
.score-note { margin-top: var(--s-2); max-width: 26ch; }

.audit-lede { max-width: 46ch; }

.finding { display: grid; gap: var(--s-2); padding-block: var(--s-4); }
.finding-h { display: flex; gap: var(--s-3); align-items: baseline; flex-wrap: wrap; }
.finding-h b { font-family: var(--face-mass); font-weight: 400; text-transform: uppercase; font-size: clamp(1.3rem, 2.6vw, 2rem); line-height: .9; letter-spacing: -.02em; }
.finding dl { display: grid; gap: var(--s-2); margin-top: var(--s-2); max-width: var(--measure); }
.finding dt { color: var(--fg-quiet); }
.finding dd { color: var(--fg); }

.asks { display: grid; gap: var(--s-2); margin-top: var(--s-3); }
.asks button {
  text-align: left; background: none; border: none; padding: var(--s-2) 0;
  font: inherit; color: var(--fg); cursor: pointer;
}
.asks button::before { content: "→ "; color: var(--fg-quiet); }
.asks button:hover, .asks button:focus-visible { color: var(--fg-accent); }

.audit-foot { display: grid; gap: var(--s-2); }

/* ── Glass ────────────────────────────────────────────────────────────
   A panel that hovers over the space rather than sitting on it.

   This is a deliberate exception to "nothing draws a box". The rule exists
   because flat rectangles stacked on a flat ground read as panels — but there
   is a live 3D scene behind this one, so the blur is actual refraction of
   actual geometry and the shadow is a real lift off a real surface. It reads
   as depth, which is the opposite of the failure the rule guards against.

   Everything is keyed to --ground, so the panel re-tints itself as the space
   changes colour underneath it and never needs a per-band override. */
.glass {
  position: relative;
  background:
    linear-gradient(
      158deg,
      color-mix(in srgb, var(--fg) 13%, transparent) 0%,
      color-mix(in srgb, var(--fg) 5%, transparent) 46%,
      color-mix(in srgb, var(--fg) 9%, transparent) 100%
    ),
    color-mix(in srgb, var(--ground) 52%, transparent);
  backdrop-filter: blur(26px) saturate(1.45) brightness(1.06);
  -webkit-backdrop-filter: blur(26px) saturate(1.45) brightness(1.06);
  padding: clamp(1.6rem, 2.6vw, 2.9rem);
  box-shadow:
    /* the top edge catching light, and the bottom falling away */
    inset 0 1px 0 rgb(255 255 255 / 22%),
    inset 1px 0 0 rgb(255 255 255 / 8%),
    inset 0 -1px 0 rgb(0 0 0 / 22%),
    /* the lift — a contact shadow close in, and a soft one further out */
    0 2px 8px -2px rgb(0 0 0 / 34%),
    0 34px 70px -26px rgb(0 0 0 / 54%);
  transition: transform var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}

/* A hairline that reads as the edge of a sheet, not as a drawn border. */
.glass::before {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  background: linear-gradient(
    150deg,
    rgb(255 255 255 / 10%) 0%,
    rgb(255 255 255 / 2%) 32%,
    transparent 58%
  );
}

/* It rises a touch when the visitor is actually working in it. */
.glass:focus-within {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 18%),
    inset 0 -1px 0 rgb(0 0 0 / 16%),
    0 3px 8px -2px rgb(0 0 0 / 30%),
    0 36px 74px -26px rgb(0 0 0 / 52%);
}

/* Fallback: without backdrop-filter the panel would be a flat wash, so it
   leans on the ground colour more heavily instead of looking broken. */
@supports not (backdrop-filter: blur(1px)) {
  .glass { background: color-mix(in srgb, var(--ground) 88%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .glass, .glass:focus-within { transition: none; transform: none; }
}

/* ── Fields inside the glass ──────────────────────────────────────────
   Bigger than the old 15px underlines, which read as a prototype. The rule
   under each field thickens and takes the accent on focus, so the affordance
   is unmistakable without adding a box inside a box. */
.field-row { display: grid; gap: clamp(1rem, 2vw, 1.5rem); }
@media (min-width: 720px) { .field-row-2 { grid-template-columns: 1fr 1fr; } }

.field-unit { display: grid; gap: .45rem; }
.field-unit > label { color: var(--fg-quiet); }

.field-unit input,
.field-unit textarea {
  width: 100%;
  background: none;
  color: var(--fg);
  font: inherit;
  font-size: var(--t-lead);
  line-height: 1.4;
  padding: .5rem 0 .65rem;
  border: none;
  box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--fg) 32%, transparent);
  transition: box-shadow var(--fast) var(--ease);
  resize: vertical;
}
.field-unit input::placeholder,
.field-unit textarea::placeholder { color: color-mix(in srgb, var(--fg) 34%, transparent); }

.field-unit input:hover,
.field-unit textarea:hover { box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--fg) 52%, transparent); }

.field-unit input:focus,
.field-unit textarea:focus {
  outline: none;
  box-shadow: inset 0 -2px 0 var(--fg);
}

/* The submit sits on the panel as the one solid thing in it. */
.glass .action {
  font-size: var(--t-micro);
  padding: var(--s-3) var(--s-5);
}

/* ── The audit result ─────────────────────────────────────────────────
   The sleeve is glass — apparatus. The card is solid — the thing the
   apparatus made. That contrast is the image: the one opaque object on a
   page built out of transparency.

   It is also the cheap choice. An opaque card needs no backdrop-filter, so
   it animates as a plain transform rather than re-compositing the live WebGL
   scene behind the glass on every frame. */
.sleeve {
  position: relative;
  display: grid;
  gap: var(--s-3);
  padding: clamp(1.1rem, 1.8vw, 1.8rem);
  background:
    linear-gradient(158deg,
      color-mix(in srgb, var(--fg) 13%, transparent) 0%,
      color-mix(in srgb, var(--fg) 5%, transparent) 46%,
      color-mix(in srgb, var(--fg) 9%, transparent) 100%),
    color-mix(in srgb, var(--ground) 52%, transparent);
  backdrop-filter: blur(26px) saturate(1.45) brightness(1.06);
  -webkit-backdrop-filter: blur(26px) saturate(1.45) brightness(1.06);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 22%),
    inset 0 -1px 0 rgb(0 0 0 / 22%),
    0 2px 8px -2px rgb(0 0 0 / 34%),
    0 34px 70px -26px rgb(0 0 0 / 54%);
}

/* The lip the card passes behind. Without something in FRONT of the card's
   origin edge it emerges from nothing — it is just a rectangle moving. */
.sleeve-lip {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: clamp(2.2rem, 4vh, 3.4rem);
  z-index: 2;
  pointer-events: none;
  /* Short and fully feathered. At a third of the card's height this drew a
     horizontal seam across it — the exact panel edge the system rejects. */
  background: linear-gradient(to top,
    color-mix(in srgb, var(--ground) 46%, transparent) 0%,
    color-mix(in srgb, var(--ground) 18%, transparent) 58%,
    transparent 100%);
  backdrop-filter: blur(10px) saturate(1.15);
  -webkit-backdrop-filter: blur(10px) saturate(1.15);
}

.card {
  position: relative;
  z-index: 1;
  /* Ink, not bone. The sleeve is glass over a bone ground, so a bone card
     disappeared into it — the whole point is one dense object against
     transparency. Setting the ink scheme locally means everything inside,
     .action included, resolves its own colours correctly. */
  --ground: var(--ink);
  --fg: var(--bone);
  --fg-quiet: var(--raw);
  --fg-accent: var(--raw);
  background: var(--ink);
  color: var(--bone);
  padding: clamp(1.5rem, 2.4vw, 2.6rem);
  display: grid;
  gap: var(--s-3);
  min-height: 15rem;
  align-content: start;
  box-shadow:
    0 1px 2px rgb(0 0 0 / 32%),
    0 18px 40px -12px rgb(0 0 0 / 46%),
    0 48px 92px -30px rgb(0 0 0 / 42%);
  transform: translate3d(0, 0, 0);
  transition: transform 760ms cubic-bezier(.16, .84, .28, 1);
}

/* Phase one: tucked inside the sleeve, before it rises. */
.card-tucked { transform: translate3d(0, 22%, 0) scale(.965); }

/* Blank stock. The plate mark is what makes an empty card read as waiting to
   be written rather than as failed to load — it is the one mark that is
   struck before anything else is. */
.card-blank::after {
  content: "";
  position: absolute;
  left: clamp(1.5rem, 2.4vw, 2.6rem); top: clamp(1.5rem, 2.4vw, 2.6rem);
  width: 2.4rem; height: 2px;
  background: var(--bone); opacity: .28;
  animation: plate-breathe 2.4s var(--ease) infinite;
}
@keyframes plate-breathe { 0%, 100% { opacity: .16 } 50% { opacity: .42 } }

/* Phase two: the content strikes in, line by line. */
.card > * {
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}
.card-struck > * { opacity: 1; transform: none; }

.card-score { display: flex; align-items: baseline; gap: .5rem; }
.card-score b {
  font-family: var(--face-mass); font-weight: 400;
  font-size: clamp(3.4rem, 8.5vw, 6rem);
  line-height: .82; letter-spacing: -.045em;
}
.card-score i { font-style: normal; font-size: var(--t-micro); opacity: .55; }
.card-title { font-size: clamp(1.2rem, 2.3vw, 1.8rem); line-height: 1.14; }
.card-read  { color: var(--raw); }
.card-move  { font-size: var(--t-say); line-height: 1.45; }
.card-next  { font-size: var(--t-body); line-height: 1.5;
              color: var(--raw); }

/* A failure is the same object in a lower register, not an alert. */
.card-quiet { background: color-mix(in srgb, var(--ink) 88%, var(--slate)); }

.findings-toggle { justify-self: start; }
.findings { display: grid; gap: var(--s-4); }
/* A display value beats the [hidden] attribute's UA rule, so without this the
   findings render open however many times hidden is set. */
.findings[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
  .card-tucked { transform: none; }
  .card > * { transition: opacity 200ms linear; transform: none; }
  .card-blank::after { animation: none; opacity: .28; }
}
