/* PropIntel landing page.
   Page-level layout only. Colour, type, space and radius come from duroob tokens.

   THEME: the page is locked to light by data-theme="light" on the page wrapper, so a
   viewer's dark OS or a host that stamps data-theme="dark" on the root cannot flip it.
   Two bands opt back into dark deliberately: the hero and the close. That is the brand
   book's own split, Drenched for hero surfaces and Restrained for content, not a
   mid-page theme flip. */

/* ---------- PropIntel accent: indigo ----------
   PropIntel's own accent, not a third duroob accent. The brand book allows two
   chromatic accents in the system; an endorsed venture keeps its own go-to-market,
   so this belongs to the product. It is sampled out of the fluted-glass texture,
   whose signature tone is #1D1E30, and drifted toward duroob purple, so the dark
   end IS the glass and the light end reads as family with purple. Hue 248 to 262. */
.page {
  --indigo-50:   #F7F4FA;
  --indigo-100:  #EBE5F5;
  --indigo-200:  #D2C6EB;
  --indigo-300:  #B29EE0;
  --indigo-400:  #8D70D7;
  --indigo-500:  #6642CD;
  --indigo-600:  #4D2EAD;
  --indigo-700:  #3B2687;
  --indigo-800:  #2D2065;
  --indigo-900:  #201943;
  --indigo-950:  #18132F;
  --indigo-1000: #0D0B19;

   /* Cool neutral ground. A near-white with the faintest lavender cast, so indigo
      is the only chroma in the frame and the page blends with the dark bands. */
   --bg:             #F4F4F7;
   --surface:        color-mix(in srgb, var(--periwinkle-100) 30%, #E9E9EE);
   --border:         color-mix(in srgb, var(--neutral-200) 62%, #E9E9EE);
   --border-control: color-mix(in srgb, var(--neutral-500) 88%, #E9E9EE);
   --divider:        color-mix(in srgb, var(--neutral-100) 55%, #F4F4F7);

  /* Semantic, light ground */
  --pi-accent:      var(--indigo-600);   /* 8.1:1 on cream */
  --pi-accent-deep: var(--indigo-800);
  --pi-line:        var(--indigo-600);
  --pi-ground:      var(--indigo-950);
  --pi-cut: 8px;            /* softened panel corner: enough curve to stop
                              reading as a wireframe, short of a rounded UI */
  /* A flat scrim laid over the finished glass band, the way an overlay sits over
     a modal. The texture and its gradient are untouched underneath; this one value
     is the whole dial. 0.50 is the setting: secondary text clears 4.5:1 against the
     brightest rib of the texture at 0.45, and 0.50 leaves a margin. Lower it for
     more glass, raise it for more contrast. */
  --glass-modal:    0.40;
}
/* Semantic, the Drenched bands */
[data-theme="dark"] {
  --pi-accent:      var(--indigo-300);   /* 7.6:1 on the band */
  --pi-accent-deep: var(--indigo-200);
  --pi-line:        var(--indigo-300);
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* Jumped-to sections start clear of the top edge instead of flush against it. */
#watch, #demo, #modules, #how, #problem { scroll-margin-top: var(--space-8); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body { margin: 0; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }

/* Surfaces the browser draws, themed rather than left at defaults. */
.page ::selection { background: color-mix(in srgb, var(--pi-accent) 24%, transparent); color: var(--ink); }
.page { caret-color: var(--pi-accent); scrollbar-color: var(--border-control) transparent; scrollbar-width: thin; }
.page a { text-underline-offset: 0.18em; text-decoration-thickness: from-font; }
.page :focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--pi-accent); border-radius: var(--radius-sm); }
[data-theme="dark"] ::selection { background: color-mix(in srgb, var(--indigo-300) 32%, transparent); color: var(--ink); }
.page { background: var(--bg); color: var(--ink); font: var(--text-body); }

/* Full-bleed sections carry no inset of their own; .shell is the single element
   that owns the page gutter, so a background can run edge to edge while its content
   stays on the measure. A padding detector reads those wrappers as flush; that is
   the pattern working, not a defect. */
.shell { max-width: 1200px; margin: 0 auto; padding-inline: var(--space-6); }
@media (min-width: 900px) { .shell { padding-inline: var(--space-8); } }

section { padding-block: var(--space-8); }
@media (min-width: 900px) { section { padding-block: var(--space-16); } }

/* One rhythm for the gap between a section heading and the block it introduces,
   so every section steps down at the same interval. --pi-optical is the one
   sub-step nudge on the page: it drops a standfirst onto the cap line of the
   title beside it, which is optical alignment rather than spacing rhythm, so
   it is named once here instead of being retyped as a magic number. */
.page { --pi-head-gap: var(--space-8); --pi-optical: .35rem; }

/* Controls take the panel cut too, so nothing on the page is a pill. */
.page .dr-btn { border-radius: var(--pi-cut); }

/* A hairline delimits each section. No eyebrow: the heading carries itself. */

/* Sections divide on a full-bleed hairline rather than an inset one, so the
   break reads across the whole page and every section opens the same way. */
main > section:not(.modules):not(.drenched) { border-top: 1px solid var(--divider); }
.modules + section { border-top: 0; }

/* One section head everywhere: the title against its standfirst. */
.sechead { margin: 0 0 var(--pi-head-gap); }
.sechead .sec { margin: 0 0 var(--space-4); }
.sechead .lede { margin: 0; color: var(--ink-secondary); font: var(--text-body-large); }
@media (min-width: 900px) {
  .sechead__in { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: var(--space-8) var(--space-16); align-items: start; }
  .sechead .sec { margin: 0; max-width: 15ch; }
  /* The walkthrough opens with its title over the standfirst, not beside it. */
  .sechead--stack .sechead__in { display: block; }
  .sechead--stack .sec { margin: 0 0 var(--space-4); max-width: 22ch; }
  .sechead--stack .lede { max-width: 40ch; padding-top: 0; }
  .sechead .lede { max-width: 54ch; padding-top: var(--pi-optical); }
  /* With no standfirst beside it a title has no reason to be squeezed into the
     left column of a two-column head, and the 15ch cap breaks it over two
     lines for nothing. */
  .sechead__in:not(:has(.lede)) { display: block; }
  .sechead__in:not(:has(.lede)) .sec { max-width: 26ch; }
}

/* ---------- Shared type ---------- */
h2.sec { font: var(--text-h1); letter-spacing: -0.025em; margin: 0 0 var(--space-6);
  max-width: 22ch; text-wrap: balance; }
@media (max-width: 700px) { h2.sec { font: var(--text-h2); letter-spacing: -0.015em; } }
h2.sec + p.lede { margin-top: calc(var(--space-3) * -1); }
p.lede { font: var(--text-body-large); color: var(--ink-secondary); margin: 0; max-width: 68ch; }

/* ---------- The ground, shared by hero and close ----------
   Three layers, bottom up: the fluted glass photograph on the element itself,
   the grain plate over it in overlay, then one dim so type sits clear of both.
   --glass-modal is the whole dial for that dim. */
.drenched { position: relative; isolation: isolate; overflow: hidden;
  color: var(--ink);
  background-image: url("../assets/duroob-texture-fluted-glass.jpg");
  background-size: cover; background-position: center;
  /* The grain lifts the ground, so the band lifts its secondary text one step
     to match: periwinkle-400 measured 3.1:1 up in the nav, indigo-200 clears
     4.5:1 and keeps the tint. Scoped here, not in the design system. */
  --ink-secondary: var(--indigo-200); }

.drenched::before { content: ""; position: absolute; inset: 0; z-index: -2;
  background-image: url("../assets/duroob-texture-grain.jpg");
  background-size: cover; background-position: center;
  mix-blend-mode: overlay; opacity: .62; pointer-events: none; }

.drenched::after { content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(0deg,
      color-mix(in srgb, var(--indigo-1000) calc(var(--glass-modal) * 100%), transparent),
      color-mix(in srgb, var(--indigo-1000) calc(var(--glass-modal) * 100%), transparent)),
    linear-gradient(175deg,
      color-mix(in srgb, var(--pi-ground) 42%, transparent) 0%,
      color-mix(in srgb, var(--pi-ground) 58%, transparent) 55%,
      color-mix(in srgb, var(--bg) 78%, transparent) 100%); }

/* The hero is the foreground layer at the first section transition. */
.page > .drenched:first-child { z-index: 4; border-radius: 0; }

/* ---------- Navigation, inside the hero band ---------- */
.nav { padding-block: var(--space-4); border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); }
.nav__in { display: flex; align-items: center; gap: var(--space-8); }
.brand { display: flex; align-items: baseline; gap: var(--space-1); text-decoration: none; color: var(--ink); }
.brand__name { font: var(--text-h3); letter-spacing: -0.02em; }
.brand__by { font: var(--text-caption); color: var(--ink-secondary); white-space: nowrap; }
.nav__links { display: none; gap: var(--space-6); margin-inline-start: auto; }
@media (min-width: 900px) { .nav__links { display: flex; } }
.nav__links a { font: var(--text-body); color: var(--ink-secondary); text-decoration: none; }
.nav__links a:hover { color: var(--ink); }
.nav__links a:focus-visible, .brand:focus-visible { outline: none; border-radius: var(--radius-sm);
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--focus-ring); }
/* The button label is long by design-system rule, so on narrow screens the
   nav drops it rather than forcing the bar wider than the viewport. The hero
   carries the same call to action a screen-height below. */
.nav__cta { display: none; margin-inline-start: auto; }
@media (min-width: 620px) { .nav__cta { display: inline-flex; } }
@media (min-width: 900px) { .nav__cta { margin-inline-start: 0; } }
.nav__in { flex-wrap: wrap; row-gap: var(--space-3); }

/* ---------- Hero ---------- */
.hero { position: relative; padding-top: var(--space-16);
  padding-bottom: var(--space-16); }
.hero__grid { display: grid; gap: var(--space-16) var(--space-8); position: relative; z-index: 2;
  align-items: center; }
/* The window peeks: it runs off the bottom edge of the band instead of sitting
   whole inside it. What made every earlier attempt read as broken was WHERE the
   cut landed — straight through the card's own content, so the product surface
   looked sliced rather than continuing. The card is not what gets cut now. The
   frame is given a deep bottom inset and the band cuts through THAT, so the
   card is always whole and what runs off the edge is empty window.

   The relationship that has to hold: --peek < the frame's bottom inset. Keep
   the peek under it and the cut can never reach the card, however the copy
   above reflows, because both move together. */
.hero__media { position: relative; }
@media (min-width: 1000px) {
  .hero__grid { grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.8fr);
    align-items: start; }
  .hero { --peek: var(--space-8); padding-bottom: 0; }
  .hero__media .viewport { padding-bottom: var(--space-16); }
  /* Both columns open on the same line, and the copy sits centred against the
     window rather than hanging from the top of it. Top-aligning the copy under
     a window half again its height left 212px of empty band beneath the call
     to action — the column read as unfinished rather than as composed. */
  .hero__media { margin-top: 0; margin-bottom: calc(var(--peek) * -1); }
  .hero__copy { align-self: center; }
}

.hero h1 { font: var(--text-display); letter-spacing: -0.04em; margin: 0 0 var(--space-6);
  max-width: 17ch; text-wrap: balance; }
@media (min-width: 1100px) { .hero h1 { font-size: 62px; line-height: 1.0; } }
@media (max-width: 900px) { .hero h1 { font: var(--text-h1); letter-spacing: -0.025em; } }
.hero__sub { font: var(--text-body-large); color: var(--ink-secondary);
  margin: 0 0 var(--space-8); max-width: 48ch; }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* ---------- The viewport: window chrome around the instrument ----------
   The frame was frosted white, which worked over the dark band and then
   vanished over the light section the viewport now straddles. Solid charcoal
   chrome reads on both grounds and behaves like an actual application window:
   dark frame, light content surface. */
.viewport { position: relative; border-radius: var(--pi-cut); overflow: hidden;
  background: var(--indigo-950);
  border: 1px solid color-mix(in srgb, var(--indigo-300) 22%, transparent);
  box-shadow: 0 22px 52px rgba(13, 11, 25, 0.38); padding: var(--space-2); padding-top: 0; }
.viewport__bar { display: flex; align-items: center; gap: var(--space-3);
  height: 30px; padding: 0 var(--space-2); }
.viewport__dots { display: flex; gap: var(--space-1); }
.viewport__dots i { width: 6px; height: 6px; border-radius: 1px;
  background: color-mix(in srgb, var(--indigo-300) 46%, transparent); }
.viewport__addr { font-family: var(--font-mono); font-size: .68rem; line-height: 1rem;
  letter-spacing: 0; color: color-mix(in srgb, var(--indigo-200) 72%, transparent); }

/* ---------- The terminal ----------
   A light card on the dark glass: the product standing on the brand ground.
   It forces the light token set with data-theme so it keeps its own palette
   inside the dark band. Every figure in it is arithmetically consistent:
   1,392 collected minus 208 duplicates is 1,184 unique, which is 85%, which
   is where the meter fills. */
/* The card fills its window. It used to be capped at 430px and pushed right
   with margin-inline-start:auto, which left 22px of dead frame down the left
   and 8px down the right — the card sat visibly off-centre inside its own
   chrome. The window's column already sizes the card; it needs no cap. */
.terminal { width: 100%;
  display: flex; flex-direction: column; gap: var(--space-4);
  padding: var(--space-6); border-radius: calc(var(--pi-cut) - 1px);
  background: var(--bg); color: var(--ink);
  border: 0; box-shadow: none; }


/* Top row: context pills, and the menu affordance. */
.tm__top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.tm__pills { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.tm__pill { font: var(--text-caption); color: var(--ink-secondary); white-space: nowrap;
  padding: var(--space-1) var(--space-3); border-radius: var(--pi-cut);
  border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent); }
.tm__menu { display: flex; gap: var(--space-1); flex: none; padding-top: var(--space-2); }
.tm__menu i { width: 3px; height: 3px; border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--ink) 34%, transparent); }

.tm__title { font: var(--text-h2); letter-spacing: -0.02em; margin: 0; }
.tm__rule { height: 1px; border: 0; margin: 0; background: var(--divider); }
.tm__say { font: var(--text-body); color: var(--ink-secondary); margin: 0; }

/* The one dominant figure, with its qualifier alongside. */
.tm__stat { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-3); }
.tm__v { font: var(--text-h1); letter-spacing: -0.035em; color: var(--ink);
  font-variant-numeric: tabular-nums; }
.tm__unit { font: var(--text-body); color: var(--ink-secondary); letter-spacing: 0; }
.tm__badge { display: inline-flex; align-items: center; gap: var(--space-2);
  font: var(--text-caption); font-weight: 600; color: var(--pi-accent);
  padding: var(--space-1) var(--space-3); border-radius: var(--pi-cut);
  border: 1px solid color-mix(in srgb, var(--pi-accent) 38%, transparent); }
.tm__since { font: var(--text-caption); color: var(--ink-secondary); }

/* Deduplication as a shape: bright bars are unique units, dim the collapsed duplicates. */
/* A progress bar, not a chart: 28 segments at 8px tall, each filling from
   its own left edge so the row reads as reconciliation running left to
   right. The 24/28 split still carries the unique-to-collected ratio. */
.tm__meter { display: flex; align-items: stretch; gap: 2px; height: 8px; }
.tm__meter i { flex: 1 1 0; border-radius: 999px; background: var(--pi-accent);
  transform-origin: 0% 50%; }
.tm__meter i.is-dupe { background: color-mix(in srgb, var(--ink) 13%, transparent); }

/* The live prompt. A blinking block cursor is the universal signal for awaiting
   input, which is the one thing a still image of a tool cannot say. */
.tm__prompt { display: flex; align-items: center; gap: var(--space-2); margin: 0;
  padding-top: var(--space-3); border-top: 1px solid var(--divider);
  font: var(--text-body); color: var(--ink-secondary); }
.tm__caret { color: var(--pi-accent); font-weight: 600; flex: none; }
.hero__note { font: var(--text-caption); color: var(--ink-secondary);
  margin: var(--space-6) 0 0; font-family: var(--font-mono); letter-spacing: 0; }

@media (min-width: 820px) {
  .page > .drenched:first-child { border-radius: 0; }
}

@media (max-width: 819px) {
  .page > .drenched:first-child { border-radius: 0; }
}

/* ---------- Funnel: scattered inputs, one reconciled answer ----------
   Five columns: chips, wire, core, wire, chips. Each wire SVG owns exactly the
   gap it spans and uses a 0-100 viewBox with preserveAspectRatio="none", so a
   curve's endpoints are simply percentages of that gap. The chip columns are
   equal-fraction grid rows, which puts chip i's centre at (i+.5)/n of the
   height, the same number the curve starts from. Nothing measures anything at
   runtime; the two halves agree because they are computed from the same count. */
/* model-cube.svg carries 56px of empty board on each side before the solid
   corner, at the size it is drawn here. Both the feeder legs and the output
   run-in have to cross it to actually touch the model, so the distance is
   named once rather than typed twice. */
.funnel { background: var(--bg); --iso-inset: 56px; }
.funnel__head { max-width: 42rem; }
.funnel__head h2.sec { margin-bottom: var(--space-4); }
.funnel__head p { margin: 0; color: var(--ink-secondary); font: var(--text-body-large); }

.funnel__fig { display: grid; gap: var(--space-6); margin-top: var(--pi-head-gap); }
.funnel__col { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
.fchip { display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-4); border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--ink) 26%, transparent);
  background: transparent;
  font: var(--text-caption); color: var(--ink-secondary); }
/* a register tick, not a friendly dot */
/* Status indicators: the market's gaps are failing, the model is not. Uses the
   design system's own error/success icon tokens rather than invented reds and
   greens, and both clear 3:1 on the page ground (7.57 and 6.66). */
.fchip__dot { flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--error-icon); }

.funnel__wire { display: none; }
/* The core box is the cube box and nothing else: the name is lifted out of
   the flow so that centring the column centres the cube, which is what puts
   the pinch, both stems and the output spine on one horizontal axis. */
.funnel__core { position: relative; display: grid; justify-items: center; gap: var(--space-3); }
/* Dashed guides down each side of the model, the same construction line the
   layer stack uses. */
.funnel__core::after { content: ""; position: absolute; inset: -40px -18px -44px; pointer-events: none;
  --d: color-mix(in srgb, var(--pi-accent) 30%, transparent);
  background:
    repeating-linear-gradient(to bottom, var(--d) 0 2px, transparent 2px 8px) 0 0 / 1px 100% no-repeat,
    repeating-linear-gradient(to bottom, var(--d) 0 2px, transparent 2px 8px) 100% 0 / 1px 100% no-repeat; }
.funnel__iso { width: min(22rem, 56vw); height: auto; }
/* Eight curves in, four right angles out. The left column is scattered and
   organic; the output is a single spine with four nodes on an even pitch, and
   the whole argument of the diagram is the difference between those two
   drawings. Nothing here is a pill. */
.outs { position: relative; align-self: center; --out-node: 10px; }
.outs__list { display: grid; gap: var(--space-4);
  margin: 0; padding: 0; list-style: none; --out-h: 1.25rem; }
/* One bracket bus instead of a spine plus per-row rings. Everything the right
   side draws now lives in a single SVG, so the bar, the arms and the terminals
   share one coordinate system: the rings used to be laid out by the grid and
   sat 8px off the spine they were supposed to be sitting on. */
.outs { position: relative; align-self: center; padding-left: var(--space-8); }
/* Reaches --iso-inset further left so the dotted run-in starts on the model's
   right edge instead of in the air beside it. The viewBox grows by the same
   amount on its left only, so the bar, the arms and the terminals keep the
   exact pixels they had. */
.outs__bus { position: absolute; top: 0;
  left: calc((var(--space-16) + var(--iso-inset)) * -1);
  width: calc(var(--space-16) + var(--space-8) + var(--iso-inset));
  height: 100%; overflow: visible; }
.outs__bus path { fill: none; stroke: var(--ink); stroke-width: 1;
  vector-effect: non-scaling-stroke; }
/* The run in from the model is dotted; the bracket itself is solid, because
   the bracket is the structure and the run is just how it got there. */
.ob-in { stroke-dasharray: 3 4; }
/* A junction, not a bullet. Solid ink at 9px read as a blob against 1px
   hairlines; a ring on the page ground interrupts the bar where the arm meets
   it, which is how a line drawing shows a connection. non-scaling-stroke keeps
   the outline at 1px despite the bus being stretched to its column, and the
   node is an <ellipse> with rx pre-divided by that same stretch so it comes
   out round. */
.ob-dot { fill: var(--bg); stroke: var(--ink); stroke-width: 1;
  vector-effect: non-scaling-stroke; }
/* Every piece of the bus grows rather than draws: .ob-in carries its own dash
   pattern, and draw-on works by overwriting stroke-dasharray, so scaling is
   the only way to extend a dotted line without flattening it. */
.ob-in, .ob-arm { transform-box: fill-box; transform-origin: left center; }
.ob-bar, .ob-dot { transform-box: fill-box; transform-origin: center; }
.out { display: grid; align-items: center; min-height: var(--out-h); }
/* Four outputs at body size against eight inputs at caption: the result is
   calmer and larger than the noise it came from, which is the hierarchy the
   diagram is arguing for. 20px line height is also exactly --out-h, so the
   spine still starts and ends on a node centre. */
.out__t { font: var(--text-body); color: var(--ink); }

/* The object is named, in the product's technical voice. */
.funnel__name { display: flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-mono); font-size: .82rem; line-height: 1.25rem;
  font-weight: 600; letter-spacing: 0; color: var(--ink); }
.funnel__name::before { content: ""; flex: none; width: 6px; height: 6px;
  border-radius: 50%; background: var(--success-icon); }
/* Everything on a schematic carries a label and a number. */
.funnel__cap { margin: var(--space-8) 0 0; font-family: var(--font-mono);
  font-size: .75rem; line-height: 1rem; font-weight: 500; letter-spacing: 0;
  color: var(--ink-secondary); }

@media (min-width: 960px) {
  /* Chip columns take their content width and the wires take everything left
     over, so the diagram spans the measure instead of huddling in the middle.
     Chips sit flush against the wires, which puts every curve's start on the
     same x. */
  /* Four terms now the outbound connector is gone: pills, the run, the model,
     the record. A fifth track would sit empty. */
  /* The figure pins to the middle of the viewport and the stage underneath it
     supplies the scroll. --runway is how far you scroll while it is pinned, so
     the whole sequence has to play out before the page moves on. Without this
     the diagram had only the ~440px between entering the viewport and reaching
     centre, which is not enough for eight routes to read one at a time. */
  /* The heading pins with the figure: it is the sentence the diagram is
     illustrating, so losing it off the top while the diagram plays leaves the
     reader watching an unlabelled animation. */
  /* A pinned stage is its content plus a runway.

     The runway is an ::after spacer, not padding and not a margin on the pin.
     A sticky element's range is its parent's CONTENT box, so padding adds no
     range at all and the pin slides straight past; a margin on the pin
     collapses out of an auto-height parent so the stage never grows. A
     pseudo-element is a real box in the flow and does neither.

     Centring is a real `top`, never `top: 50vh` plus translateY(-50%): a
     transform applies the whole time, not only while stuck, so the figure
     renders half its own height too high before it ever pins — floating up
     into the band above and finishing its sequence before it reaches the
     middle of the screen.

     --pinH is the one number CSS cannot measure for itself. The value here is
     the no-JS fallback; the motion module overwrites it with the figure's real
     height on every build, so it cannot drift the way a hand-written 38rem did
     when it was 61px short and left the stack 30px off centre. */
  .funnel__stage  { --runway: 115vh; --pinH: 28rem; }
  .modules__stage { --runway: 85vh;  --pinH: 42rem; }
  .funnel__stage::after, .modules__stage::after {
    content: ""; display: block; height: var(--runway); }
  .funnel__pin, .modules__pin {
    position: sticky; top: calc((100vh - var(--pinH)) / 2); }
  .funnel__pin .sechead, .modules__pin .sechead { margin-bottom: var(--space-8); }
  .funnel__fig { grid-template-columns: auto minmax(4rem, 1fr) auto auto;
    align-items: center; gap: 0; min-height: 23rem; }
  .outs { margin-left: var(--space-16); }
  .funnel__col { align-self: stretch; gap: 0; }
  .funnel__col--in  { grid-template-rows: repeat(8, 1fr); justify-items: end; }

  .fchip { align-self: center; }

  /* The wire's own box ends where the model's column begins, but the model
     is an SVG with 55.6px of its own left padding before the solid corner, so
     the legs converged into empty space. Reaching past the column edge by that
     exact distance lands the pinch on the corner itself. The model is later in
     the DOM, so it paints over anything that overshoots. */
  .funnel__wire { display: block; align-self: stretch;
    width: calc(100% + var(--iso-inset)); height: 100%;
    color: var(--pi-accent); overflow: visible; }
  .funnel__wire path { fill: none; stroke: currentColor; vector-effect: non-scaling-stroke;
    shape-rendering: crispEdges; }
  /* Feeders are quiet, the trunk carries weight, and the line into the model
     is the one that matters. Three weights instead of one flat tangle. */
  /* Solid charcoal throughout, weighted: feeders quiet, trunk firmer, the
     run into the model firmest. */
  /* One weight, curved: eight sources converging on the model. */
  .funnel__wire path { fill: none; stroke: var(--ink); stroke-width: 1; opacity: .3; }
  /* Every feeder ends on one point and the point is marked, so the cube reads
     as somewhere the lines arrive rather than where they happen to stop. A
     zero-length subpath with a round cap draws a true circle even though this
     SVG is stretched to the column. */
  /* Lifting the name out of the flow makes the core box the cube box, which
     is what puts the cube's midline on the figure's centre line. Stacked, the
     name stays in the flow or it lands on the first output. */
  .funnel__core { gap: 0; }
  .funnel__name { position: absolute; top: 100%; left: 50%;
    transform: translateX(-50%); margin-top: var(--space-3); white-space: nowrap; }
    /* Cube edge to spine, closing the axis across the column gap. */
  

}

/* ---------- Process: three cards, the last inverted ---------- */
.steps { display: grid; gap: var(--space-4); margin: var(--pi-head-gap) 0 0;
  padding: 0; list-style: none; }
.step { display: flex; flex-direction: column; gap: var(--space-6); height: 100%;
  padding: var(--space-6); border-radius: var(--pi-cut);
  border: 1px solid var(--border); background: transparent; }
.step__head { display: flex; align-items: baseline; gap: var(--space-3); }
.step__n { font-family: var(--font-mono); font-size: .75rem; line-height: 1rem;
  font-weight: 600; letter-spacing: 0; color: var(--pi-accent); }
.step h3 { font: var(--text-body); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; margin: 0; color: var(--ink); }
/* Icons are assets now: the file carries its own paint, so nothing here
   styles their innards. Only size and placement live in CSS. */
.step__glyph { width: 4rem; height: 4rem; display: block; }
.step p { font: var(--text-body); color: var(--ink-secondary); margin: 0; }
/* All three cards are built the same. The answer is the one you can act on,
   so it earns a hover state rather than a permanent inverted fill. */
.step { transition: background-color .18s ease, border-color .18s ease; }
/* Gated on a fine pointer: touch fires a false hover on tap and would leave
   the card stuck in the hovered state until the next tap elsewhere. The
   colour transition stays unconditional, including under reduced motion,
   because a colour fade is not the kind of movement reduce is asking about. */
@media (hover: hover) and (pointer: fine) {
  .step--answer:hover {
    background: color-mix(in srgb, var(--pi-accent) 6%, transparent);
    border-color: color-mix(in srgb, var(--pi-accent) 38%, transparent); }
}

@media (min-width: 720px) {
  .steps { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-6); }
  .step { padding: var(--space-8); }
}

/* ---------- Modules: the exploded stack beside its list ----------
   Full-bleed and dark, but flat: texture behind a line drawing fights it.
   The glass is reserved for the two bands that bookend the page, which is
   what keeps the set cohesive while this one still divides cleanly.
   Flat, not a gradient: a large soft purple wash is the generic look, and
   the only purple in here should be the one lit plate. */
.modules { background: var(--indigo-1000); color: var(--neutral-50); }
.modules .sec { color: var(--neutral-50); }
.modules .lede { color: var(--ink-secondary); }

/* indigo-200 measured 11.37:1 here and still read muted against the near
   black; indigo-100 lifts the body copy without closing on the headings. */
.modules { --ink-secondary: var(--indigo-100); }
.layers__grid { display: grid; gap: var(--space-16) var(--space-8); align-items: center; }
.layers__figure { display: flex; justify-content: center; }
.iso { display: block; width: 100%; max-width: 400px; height: auto; }

/* The stack reads as depth, not as four repeats. Bottom to top: the lowest
   layer is only an implied footprint, dashed, filled with the band itself so
   nothing but its edges register. Above it the slabs step up in light, each
   one a heavier tint of indigo over the same ground, so the top plate is the
   one the eye lands on. Side faces are derived from each slab's own top fill,
   which keeps the whole ramp on one hue. */
.iso path { vector-effect: non-scaling-stroke; shape-rendering: geometricPrecision;
  stroke-width: 1; stroke-linejoin: miter; }

.lv0 { --sl-tint: 52%; --sl-edge: 92%; }
.lv1 { --sl-tint: 34%; --sl-edge: 82%; }
.lv2 { --sl-tint: 19%; --sl-edge: 74%; }
.lv3 { --sl-tint:  0%; --sl-edge: 66%; stroke-dasharray: 4 4; }

.sl-top { fill: color-mix(in srgb, var(--indigo-400) var(--sl-tint), var(--indigo-1000)); }
.sl-left { fill: color-mix(in srgb, #000 32%,
  color-mix(in srgb, var(--indigo-400) var(--sl-tint), var(--indigo-1000))); }
.sl-right { fill: color-mix(in srgb, #000 56%,
  color-mix(in srgb, var(--indigo-400) var(--sl-tint), var(--indigo-1000))); }

/* Depth is carried by the fills, so the edges only need a light falloff.
   Flattening it keeps every face of every slab above the 3:1 non-text bar,
   which is what the lower two slabs were failing. */
.sl-top { stroke: color-mix(in srgb, var(--indigo-200) var(--sl-edge), transparent); }
.sl-left { stroke: color-mix(in srgb, var(--indigo-200)
  calc(var(--sl-edge) * 0.82), transparent); }
.sl-right { stroke: color-mix(in srgb, var(--indigo-200)
  calc(var(--sl-edge) * 0.70), transparent); }

/* The footprint has no body: every face is the band, only the dashes read. */
.lv3.sl-top, .lv3.sl-left, .lv3.sl-right { fill: var(--indigo-1000); }
/* It is an outline, not a lit object, so its three edges match. */
.lv3.sl-left, .lv3.sl-right {
  stroke: color-mix(in srgb, var(--indigo-200) var(--sl-edge), transparent); }

.sl-dot { fill: var(--indigo-200); stroke: none; }
/* Connectors run in the gaps only, never through a slab. */
.sl-guide { fill: none; stroke: color-mix(in srgb, var(--indigo-300) 30%, transparent);
  stroke-dasharray: 2 6; }

.layers__list { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.lay { display: grid; gap: var(--space-1) var(--space-6); padding: var(--space-6) 0;
  border-top: 1px solid color-mix(in srgb, var(--indigo-200) 20%, transparent); }
.lay:last-child { border-bottom: 1px solid color-mix(in srgb, var(--indigo-200) 20%, transparent); }
.lay__n { font-family: var(--font-mono); font-size: .75rem; line-height: 1rem;
  font-weight: 600; letter-spacing: 0; color: var(--indigo-300); }
.lay h3 { font: var(--text-h3); color: var(--neutral-50); margin: 0; }
.lay p { font: var(--text-body); color: var(--ink-secondary); margin: 0; max-width: 56ch; }

@media (min-width: 900px) {
  .layers__grid { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr);
    gap: var(--space-16); }
  .lay { grid-template-columns: 3rem minmax(0, 1fr); align-items: start; }
  .lay__n { grid-row: span 2; }
}

/* ---------- Assurance: four specimens on a single rule ----------
   Content sets the height; no card floor. The vertical rules run the full
   column and stop where the copy stops, plus one tail of air. The title box
   is held at two lines so every body paragraph starts on the same line,
   whether its heading wrapped or not. */
.trust { background: var(--bg); }
.trust__head { display: grid; gap: var(--space-8); align-items: start; }
.trust__head h2.sec { max-width: 17ch; margin-bottom: 0; }

/* Not cards. Four columns of one row, told apart by a hairline rather than by
   a box, so the marks carry the section and nothing competes with them. The
   ordinals are gone: four parallel guarantees are a set, not a sequence. */
.trust__grid { display: grid; gap: 0; margin-top: var(--pi-head-gap); }
.tr { display: grid; align-content: start; justify-items: center;
  text-align: center; padding: var(--space-8) var(--space-6);
  border: 0; background: transparent; }
.tr + .tr { border-top: 1px solid var(--divider); }
/* The mark is the section's largest element; it can afford the room now that
   it is not sharing a card with a border and a number. */
.tr__icon { width: 6rem; height: 6rem; justify-self: center;
  margin: 0 0 var(--space-6);
  color: var(--pi-accent); }
.tr h3 { font: var(--text-h3); margin: 0 0 var(--space-3); text-wrap: balance; }
.tr p { font: var(--text-body); color: var(--ink-secondary); margin: 0; }
.fmt { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2);
  margin: var(--space-4) 0 0; padding: 0; list-style: none; }
.fmt li { padding: var(--space-1) var(--space-3); border-radius: var(--pi-cut);
  border: 1px solid color-mix(in srgb, var(--pi-accent) 38%, transparent);
  background: color-mix(in srgb, var(--pi-accent) 7%, transparent);
  font-family: var(--font-mono); font-size: .69rem; line-height: 1rem;
  font-weight: 600; letter-spacing: 0; color: var(--pi-accent); }

@media (min-width: 720px) {
  .trust__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Two up: rule between the columns, and again between the rows. */
  .tr + .tr { border-top: 0; }
  .tr:nth-child(even) { border-left: 1px solid var(--divider); }
  .tr:nth-child(n+3) { border-top: 1px solid var(--divider); }
}
@media (min-width: 1100px) {
  /* Four across, one rule between each pair and none on the outer edges. */
  .trust__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .tr:nth-child(n+3) { border-top: 0; }
  .tr + .tr { border-left: 1px solid var(--divider); }
}


/* ---------- Walkthrough: a placeholder port in the same window chrome ----------
   It reuses .viewport so the video sits in the same frame as the hero's
   terminal; only the stage inside changes. */
/* One third copy, two thirds port. The section head lives inside the left
   column rather than above the grid, so the port's top edge starts on the same
   line as the title instead of below it. */
.watch__grid { display: grid; gap: var(--space-8); }
.watch__copy .sec { margin: 0 0 var(--space-4); max-width: 18ch; }
.watch__copy p { margin: 0; color: var(--ink-secondary); font: var(--text-body-large); }
.player { margin: 0; }
@media (min-width: 960px) {
  .watch__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    gap: var(--space-16); align-items: start; }
  /* The title stays on the port's top line, and the standfirst drops to the
     foot of the column instead of hanging directly under it. A 136px copy
     block top-aligned beside a 432px port left 296px of empty column; this
     spans the same height and reads as composed rather than unfinished. */
  .watch__copy { align-self: stretch; display: flex; flex-direction: column;
    justify-content: space-between; }
  .watch__copy p { max-width: 34ch; }
}
.viewport--player { background: var(--indigo-950); }
.player__stage { position: relative; display: grid; place-items: center;
  aspect-ratio: 1520 / 1068; overflow: hidden; border-radius: calc(var(--pi-cut) - 1px);
  background:
    repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--indigo-200) 4%, transparent) 0 10px,
      transparent 10px 20px),
    var(--indigo-1000); }
.player__video { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; display: block; }
.player__play { position: relative; display: block; width: 4.5rem; height: 4.5rem;
  padding: 0; border: 0; background: none; cursor: pointer; border-radius: 50%; }
.player__play img { display: block; width: 100%; height: 100%; }
.player__play:focus-visible { outline: 2px solid var(--indigo-200); outline-offset: 4px; }
.player__meta, .player__time { z-index: 1; }
.player__stage.is-playing .player__play,
.player__stage.is-playing .player__meta,
.player__stage.is-playing .player__time { display: none; }
.player__meta, .player__time { position: absolute; bottom: var(--space-4);
  font-family: var(--font-mono); font-size: .69rem; line-height: 1rem; letter-spacing: 0;
  color: color-mix(in srgb, var(--indigo-200) 62%, transparent); }
.player__meta { left: var(--space-4); }
.player__time { right: var(--space-4); }
.player__cap { margin: var(--space-4) 0 0; font-family: var(--font-mono);
  font-size: .75rem; line-height: 1rem; letter-spacing: 0; color: var(--ink-secondary); }

/* ---------- Close ---------- */
.close { text-align: center; }
/* With the standfirst gone the title carries straight to the button, so it
   takes over the gap the paragraph used to hold open. */
.close h2 { font: var(--text-display); letter-spacing: -0.04em; margin: 0 0 var(--space-8); text-wrap: balance; }
@media (max-width: 900px) { .close h2 { font: var(--text-h1); letter-spacing: -0.025em; } }
.close p { font: var(--text-body-large); color: var(--ink-secondary);
  margin: 0 auto var(--space-8); max-width: 46ch; }

/* ---------- Footer ---------- */
/* One row, two columns. There was a second row under a divider holding only the
   copyright: it was built for policy links that were never written, so a
   space-between row ran with a single child and left 800px of nothing beside it,
   at a cost of 65px of height. The copyright sits with the tagline instead. */
.foot__copy { font: var(--text-caption); color: var(--ink-secondary); margin: 0;
  font-family: var(--font-mono); letter-spacing: 0; }
.foot__meta { display: flex; flex-direction: column; gap: var(--space-1);
  align-items: flex-end; text-align: right; }
.foot { padding-block: var(--space-8); border-top: 1px solid var(--divider); }
.foot__in { display: flex; flex-wrap: wrap; gap: var(--space-4);
  align-items: center; justify-content: space-between; }
.foot__mark { display: flex; flex-direction: column; gap: var(--space-1); }
.foot__lockup { display: block; height: 26px; width: auto; }
.foot__label { font: var(--text-caption); color: var(--ink-secondary); margin: 0;
  text-transform: uppercase; letter-spacing: 0.14em; }
.foot p.foot__note { font: var(--text-caption); color: var(--ink-secondary); margin: 0; }
/* Below this the row would wrap on its own at an awkward width, leaving the
   right column still on the right but ragged against the gutter. Stack it
   deliberately instead, so there is no in-between state. */
@media (max-width: 620px) {
  .foot__in { flex-direction: column; align-items: flex-start; gap: var(--space-6); }
  .foot__meta { align-items: flex-start; text-align: left; }
}

/* Tactile press. Asymmetric on purpose: the press answers instantly, because
   a press that waits feels broken, while the release is the button recovering
   rather than the user acting, so it eases back. transition-duration: 0s on
   :active is what splits the two directions. */
.dr-btn { transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1); }
.dr-btn:active { transform: translateY(1px); transition-duration: 0s; }
@media (prefers-reduced-motion: reduce) {
  .dr-btn { transition: none; }
  .dr-btn:active { transform: none; }
}


/* ================= Motion =================
   duroob has not locked UI motion: the brand book offers the film's language as
   a reference to start from, routes that draw on, travel, retract, branch and
   wipe on, and says state changes stay instant until motion is decided. This
   page starts from that reference rather than treating it as a rule.

   The page has three verbs and no more, all defined in js/motion.js:
     reveal  opacity + a 10px rise    text, cards, rows, marks
     draw    stroke-dashoffset        line art, against pathLength="1"
     travel  offset-path / translate  the funnel's dots and pulse

   Almost nothing is animated from this stylesheet any more. Motion is authored
   as data in js/motion.js and played through the Web Animations API, because
   Figma's motion model is per-node keyframe tracks and a stylesheet has no node
   identity to hang one on. What stays here: the resting states, the cursor
   blink, and cube-bloom, whose filter maps to a Figma effect track rather than
   a transform field. */



/* ---------- The first section, animated ----------
   The argument the diagram makes, played once: a cluttered market on the left,
   every source running into one point, the model taking it, and a single
   ordered result coming out the other side.

   Nothing is hidden unless the script has confirmed it can animate it back.
   data-motion="on" is set only when IntersectionObserver and offset-path are
   both available and the reader has not asked for reduced motion, so with no
   JS, an old browser, or reduce set, the diagram renders finished.

   The chips tilt about their RIGHT edge, which is the edge the wires meet. The
   join stays fixed and only the far end moves: that is the difference between
   cluttered and janky. */
@media (min-width: 960px) {
  .fchip { transform: rotate(var(--tilt, 0deg)); transform-origin: 100% 50%; }
}
.fchip:nth-child(1) { --tilt: -0.8deg; }
.fchip:nth-child(2) { --tilt: 0.55deg; }
.fchip:nth-child(3) { --tilt: -0.4deg; }
.fchip:nth-child(4) { --tilt: 0.9deg; }
.fchip:nth-child(5) { --tilt: -0.65deg; }
.fchip:nth-child(6) { --tilt: 0.45deg; }
.fchip:nth-child(7) { --tilt: -0.9deg; }
.fchip:nth-child(8) { --tilt: 0.35deg; }

.fw-dot { fill: var(--ink); opacity: 0;
  transform-box: fill-box; transform-origin: center;
  offset-rotate: 0deg; offset-distance: 0%; }
.fw-dot:nth-of-type(1) { offset-path: path("M0 6.25 C55 6.25 46 50 100 50"); }
.fw-dot:nth-of-type(2) { offset-path: path("M0 18.75 C55 18.75 46 50 100 50"); }
.fw-dot:nth-of-type(3) { offset-path: path("M0 31.25 C55 31.25 46 50 100 50"); }
.fw-dot:nth-of-type(4) { offset-path: path("M0 43.75 C55 43.75 46 50 100 50"); }
.fw-dot:nth-of-type(5) { offset-path: path("M0 56.25 C55 56.25 46 50 100 50"); }
.fw-dot:nth-of-type(6) { offset-path: path("M0 68.75 C55 68.75 46 50 100 50"); }
.fw-dot:nth-of-type(7) { offset-path: path("M0 81.25 C55 81.25 46 50 100 50"); }
.fw-dot:nth-of-type(8) { offset-path: path("M0 93.75 C55 93.75 46 50 100 50"); }

/* The run across to the spine is straight, so a translate is enough and there
   is no second copy of the path to keep in sync. */
/* Stacked, there is no cube to run out of. */

                        40%  { filter: drop-shadow(0 0 16px rgba(77, 46, 173, .40)); }

/* Resting states. Nothing is hidden unless the script has confirmed it can
   animate it back: data-motion="on" is set only when the Web Animations API,
   IntersectionObserver and offset-path are all present and reduced motion is
   not requested. With no JS, an old browser, or reduce set, none of this
   applies and the page renders finished. */
[data-motion="on"] .fchip,
[data-motion="on"] .out,
[data-motion="on"] .terminal,
[data-motion="on"] #how .sec, [data-motion="on"] #how .lede, [data-motion="on"] .step,
[data-motion="on"] .watch .sec, [data-motion="on"] .watch__copy p, [data-motion="on"] .player,
[data-motion="on"] .modules .sec, [data-motion="on"] .modules .lede, [data-motion="on"] .lay,
[data-motion="on"] .lv2-g, [data-motion="on"] .lv1-g, [data-motion="on"] .lv0-g,
[data-motion="on"] .trust .sec, [data-motion="on"] .trust .lede,
[data-motion="on"] .tr h3, [data-motion="on"] .tr p, [data-motion="on"] .fmt,
[data-motion="on"] .tr__icon .tr__mark,
[data-motion="on"] .close h2, [data-motion="on"] .close p,
[data-motion="on"] .close .dr-btn          { opacity: 0; }
[data-motion="on"] .tm__meter i            { transform: scaleX(0); }
[data-motion="on"] .ob-in,
[data-motion="on"] .ob-arm                 { transform: scaleX(0); }
[data-motion="on"] .ob-bar                 { transform: scaleY(0); }
[data-motion="on"] .ob-dot                 { opacity: 0; }
/* pathLength="1" makes every one of these exactly one unit long whatever its
   real geometry, so a single dash value draws any of them, and the same 0..1
   number is what Figma's PATH_TRIM_END takes. */
/* Draw-on works by overwriting stroke-dasharray, so it can only be pointed at
   strokes that are not already dashed. The stack's footprint (4 4), its
   connectors (2 6) and the icons' .tr__dash members (3 5) carry dashes as part
   of the drawing and are revealed by opacity instead. */
/* The marks are revealed whole. Drawing them on meant overwriting
   stroke-dasharray, which chopped the artwork: these are dense technical
   glyphs with dashed members of their own, not single continuous routes. */
[data-motion="on"] .tr__icon,
[data-motion="on"] .lv3-g                  { opacity: 0; }
/* The legs are solid single curves, which is what draw-on is actually for.
   A dash far longer than any leg holds them fully undrawn until the script
   measures each one's real on-screen length and takes over. pathLength cannot
   do this job: non-scaling-stroke makes dashes screen-space, so pathLength is
   ignored and a value of 1 would render a 1px dotted line instead. */
[data-motion="on"] .fw-leg                 { stroke-dasharray: 4000px; stroke-dashoffset: 4000px; }
/* Dashed lines extend by scaling, never by dasharray, which is already
   carrying their dashes. Origin is the end they grow from. */
[data-motion="on"] .lv-guides path         { transform: scaleY(0); }
.lv-guides path                            { transform-origin: 50% 0%; }

/* ---------- Request a demo ----------
   The ask is a modal, not a band: the page makes its argument and the form
   arrives on top of it over a scrim, so nothing competes with the closing
   line. <dialog> is the element for this — the browser owns the top layer,
   the focus trap, Esc and inert-ing the page behind, and none of that has to
   be reimplemented badly.

   The panel is the light surface, not an indigo one. Indigo is the accent and
   the two drenched bands; a whole form built out of it turns the product's one
   colour into wallpaper and the fields stop reading as fields. This is the same
   move the hero terminal makes — a light card carrying the product, with the
   accent spent only on the submit and on what the user has chosen. */
.modal { position: relative; width: min(46rem, calc(100vw - var(--space-8)));
  max-height: calc(100dvh - var(--space-16));
  margin: auto; padding: 0; border: 0; background: transparent; overflow: visible;
  color: var(--ink); }
.modal::backdrop { background: rgba(6, 6, 6, 0.8); }
@supports (backdrop-filter: blur(1px)) {
  .modal::backdrop { backdrop-filter: blur(3px); }
}
/* The page cannot scroll behind an open modal. :has() does it without a
   script, so the lock cannot desynchronise from the dialog's own state. */
html:has(.modal[open]) { overflow: hidden; }

.modal__panel { position: relative; max-height: calc(100dvh - var(--space-16));
  overflow-y: auto; padding: var(--space-8); border-radius: var(--pi-cut);
  background: var(--bg); border: 1px solid var(--border);
  box-shadow: 0 24px 64px rgba(13, 11, 25, 0.28); }
@media (max-width: 719px) { .modal__panel { padding: var(--space-6); } }

/* The dismiss is a child of the dialog rather than of the panel, because on a
   short screen the panel is the scroll container and an absolute box inside it
   would scroll the dismiss off the top. Anchored to the dialog it holds the
   corner while the form moves under it. */
.modal__x { position: absolute; z-index: 1; top: var(--space-4); inset-inline-end: var(--space-4);
  display: grid; place-items: center; width: var(--space-8); height: var(--space-8);
  padding: 0; border-radius: var(--pi-cut); cursor: pointer;
  background: transparent; border: 1px solid transparent; color: var(--ink-secondary);
  transition: color .16s ease, border-color .16s ease; }
.modal__x:hover { color: var(--ink); border-color: var(--border); }
.modal__x:focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--focus-ring); }
.modal__x svg { width: 16px; height: 16px; display: block; }

.modal__title { font: var(--text-h1); letter-spacing: -0.03em;
  margin: 0 var(--space-8) var(--space-3) 0; text-wrap: balance; }
/* Same step-down the closing band takes: at display size the title alone
   fills a phone screen and pushes the first field below the fold. */
@media (max-width: 719px) {
  .modal__title { font: var(--text-h2); letter-spacing: -0.02em; }
}
.modal__lede { font: var(--text-body); margin: 0; max-width: 52ch;
  color: var(--ink-secondary); }

.demo { margin: var(--space-8) 0 0; text-align: left; }
/* Three across rather than two: a 2-up grid leaves the third field beside a
   hole, and these are all short entries that do not need half a modal each. */
.demo__grid { display: grid; gap: var(--space-4); }
@media (min-width: 720px) {
  .demo__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4) var(--space-4); }
}
.fld { display: grid; gap: var(--space-2); }
.fld--note { margin-top: 0; }
/* Times and Comments share the right column so it is not left empty beside a
   tall calendar. */
.demo__side { display: grid; gap: var(--space-6); align-content: start; }
@media (min-width: 720px) {
  /* Six stacked chips come to 268px against the calendar's 274 — the two
     columns end on the same line instead of one trailing the other. They fill
     the column's width so the right of it is not left empty; at this size they
     read as selectable rows, which is what picking several of them is. */
  .demo__side .slots__row { flex-direction: column; align-items: stretch; }
  .demo__side .slot { width: 100%; }
  .demo__side .slot span { width: 100%; justify-content: center; }
}
/* Sentence case. The uppercase-plus-tracking treatment is the page's eyebrow
   style and belongs to section furniture, not to the label on a text field. */
.fld__l { font: var(--text-caption); color: var(--ink-secondary); }
/* Required marker in full ink: the label is secondary, the obligation is not. */
.req { color: var(--ink); margin-inline-start: 2px; }

/* Dialling code and number read as one control. */
.fld__combo { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-2); }
.demo .fld__combo select {
  font: var(--text-body); color: var(--ink);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  border: 1px solid var(--border); border-radius: var(--pi-cut);
  padding: var(--space-3) var(--space-2) var(--space-3) var(--space-3);
  cursor: pointer; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-secondary) 50%),
                    linear-gradient(135deg, var(--ink-secondary) 50%, transparent 50%);
  background-position: right 14px top 50%, right 9px top 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: var(--space-6); }
.demo .fld__combo select:hover { border-color: color-mix(in srgb, var(--ink) 34%, transparent); }
.demo .fld__combo select:focus-visible { outline: none; border-color: var(--pi-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pi-accent) 18%, transparent); }
/* A neutral 4% wash rather than --surface: that token is periwinkle-100, and
   six lavender fields on a light panel put more colour on this form than the
   whole page spends. The well reads as recessed on tone alone. */
.demo input, .demo textarea {
  width: 100%; font: var(--text-body); color: var(--ink);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  border: 1px solid var(--border); border-radius: var(--pi-cut);
  padding: var(--space-3) var(--space-4);
  transition: border-color .16s ease, box-shadow .16s ease; }
.demo textarea { resize: vertical; min-height: 5.5rem; }
.demo input::placeholder, .demo textarea::placeholder { color: var(--ink-tertiary, var(--ink-secondary)); opacity: .7; }
.demo input:hover, .demo textarea:hover { border-color: color-mix(in srgb, var(--ink) 34%, transparent); }
.demo input:focus-visible, .demo textarea:focus-visible { outline: none;
  border-color: var(--pi-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pi-accent) 18%, transparent); }
.demo input[aria-invalid="true"] { border-color: var(--error-icon); }

/* Days and times are both offered as slots, so choosing is picking from what is
   available rather than typing a date into a box and hoping. */
.slots { border: 0; padding: 0; margin: var(--space-6) 0 0; min-inline-size: 0; }
/* The UA gives <legend> its own 2px inline padding, which pushes this one
   label off the grid every other label in the form sits on. */
.slots legend.fld__l { padding: 0; }
.slots__row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }

/* ---------- The day picker ----------
   A month grid, because "which day" is a date question. Sized so the whole
   month fits without the panel growing a scrollbar on a laptop. */
/* Day and time sit side by side: stacked, the pair pushed the submit below
   the fold on a laptop and the panel grew a scrollbar for no reason. */
.demo__when { display: grid; gap: var(--space-6); margin-top: var(--space-6); }
@media (min-width: 720px) {
  .demo__when { grid-template-columns: auto minmax(0, 1fr); gap: var(--space-6); align-items: start; }
}
.demo__when .slots { margin-top: 0; }

/* Capped, or seven equal columns across the panel give 93px cells and the
   month alone fills the modal. */
/* Selection is lilac, not the deep accent. A chosen day is a light state the
   eye should land on, not a dark block that outweighs everything around it;
   indigo-200 carries the brand without the weight, and the label stays ink so
   it keeps its contrast. */
.demo { --pi-select: var(--indigo-200); --pi-select-edge: var(--indigo-300); }
.cal { width: 20rem; max-width: 100%; margin-top: var(--space-2); padding: var(--space-3);
  border: 1px solid var(--border); border-radius: var(--pi-cut);
  background: color-mix(in srgb, var(--ink) 3%, transparent); }
.cal__head { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-2); margin-bottom: var(--space-2); }
.cal__month { font: var(--text-body); font-weight: 600; color: var(--ink); }
.cal__nav { display: grid; place-items: center; width: var(--space-6); height: var(--space-6);
  padding: 0; cursor: pointer; border-radius: var(--pi-cut);
  background: transparent; border: 1px solid transparent; color: var(--ink-secondary);
  transition: color .16s ease, border-color .16s ease; }
.cal__nav svg { width: 16px; height: 16px; display: block; }
.cal__nav:hover:not(:disabled) { color: var(--ink); border-color: var(--border); }
.cal__nav:disabled { opacity: var(--opacity-disabled); cursor: default; }
.cal__nav:focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--focus-ring); }

.cal__dow, .cal__grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-1); }
.cal__dow { margin-bottom: var(--space-1); }
.cal__dow span { text-align: center; font: var(--text-caption);
  color: var(--ink-secondary); letter-spacing: .04em; }

/* A true 36x36 square, centred in its column rather than stretched to fill it.
   Filling made the selected day 39x36 — a rounded rectangle pretending to be a
   square, and the only boxed element in the grid, so the mismatch showed. */
.cal__day { width: 36px; height: 36px; justify-self: center; padding: 0; cursor: pointer;
  font: var(--text-body); font-variant-numeric: tabular-nums;
  color: var(--ink); background: transparent;
  border: 1px solid transparent; border-radius: var(--pi-cut);
  transition: background-color .16s ease, border-color .16s ease, color .16s ease; }
.cal__day:hover:not(:disabled) { border-color: color-mix(in srgb, var(--ink) 30%, transparent); }
/* Unofferable days stay legible but plainly inert — a weekend the team does not
   run sessions on is information, not an error. */
.cal__day:disabled { color: var(--ink-secondary); opacity: var(--opacity-disabled); cursor: default; }
.cal__day[aria-pressed="true"] { background: var(--pi-select);
  border-color: var(--pi-select-edge); color: var(--ink); font-weight: 600; }
.cal__day:focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--focus-ring); }
.cal__pad { display: block; }
@media (prefers-reduced-motion: reduce) { .cal__day, .cal__nav { transition: none; } }
.slots__tz { font: var(--text-caption); color: var(--ink-secondary);
  margin: var(--space-2) 0 0; }
/* The same line carries the shortfall when too few times are picked. */
.slots__tz[data-warn] { color: var(--error-text, #8D2C2B); font-weight: 600; }
.slot { position: relative; display: inline-flex; }
.slot input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.slot span { display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4); border-radius: var(--pi-cut);
  font: var(--text-body); color: var(--ink-secondary);
  border: 1px solid var(--border); background: transparent;
  transition: border-color .16s ease, background-color .16s ease, color .16s ease; }
.slot span b { font-weight: 600; color: inherit; }
.slot span em { font-style: normal; color: var(--ink-secondary); opacity: .8; }
.slot input:hover + span { border-color: color-mix(in srgb, var(--ink) 34%, transparent); }
/* The accent lands only on the chosen slot, which is the one place on this
   panel where colour is carrying information. */
.slot input:checked + span { background: var(--pi-select);
  border-color: var(--pi-select-edge); color: var(--ink); font-weight: 600; }
.slot input:checked + span em { color: var(--ink); opacity: 1; }
.slot input:focus-visible + span { outline: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--focus-ring); }

/* Deep rather than mid indigo: at --pi-accent a solid fill this size is the
   loudest thing on a light panel, and the chips already carry that value as a
   hairline. Same hue, lower voice. */
.modal .dr-btn--primary { background: var(--indigo-800); border-color: var(--indigo-800);
  color: var(--neutral-50); }
.modal .dr-btn--primary:hover { background: var(--indigo-700); border-color: var(--indigo-700); }

.demo__foot { display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-3) var(--space-6); margin-top: var(--space-8); }
.demo__foot .dr-btn { flex: none; }
.demo__note { margin: 0; max-width: 30ch; font: var(--text-caption); color: var(--ink-secondary); }
@media (prefers-reduced-motion: reduce) {
  .demo input, .demo textarea, .slot span, .modal__x { transition: none; }
}
