/* ==========================================================================
   TSUL AI Hub — tsulai.uz
   The front door to the five OXFORDER AI products.

   The palette, spacing, radii, type scale and motion below are the OXFORDER
   tokens from D:/OXFORDER/shared/brand/tokens.css, inlined because this site
   is buildless and independent of the four product repos. Keep them in sync by
   hand; if a value here disagrees with tokens.css, tokens.css wins.

   The hub is deliberately NOT styled like the products. They are working tools
   with light app chrome; this is a front door, so it leads and closes on a deep
   indigo ground with the girih field showing, and the cubs — pale silver-grey —
   read at their best against it.
   ========================================================================== */

/* ── Tokens ──────────────────────────────────────────────────────────────── */

:root {
  color-scheme: light;

  /* Brand */
  --indigo: #2a4b8d;
  --indigo-dark: #1e3766;
  --indigo-soft: #e7ecf7;
  --glaze: #0e7c86;
  --glaze-soft: #ddf0f1;
  --clay: #c8783c;

  /* Neutrals, biased toward the indigo so they sit with the brand. */
  --ink: #12161f;
  --ink-2: #3b4453;
  --ink-3: #6b7484;
  --rule: #dce1e9;
  --rule-strong: #c6cdd8;
  --surface: #ffffff;
  --surface-2: #eff2f6;
  --paper: #f7f8fa;
  /* The ground for .section--alt: between the page and a card, so a card on it
     still reads as a card. */
  --section-alt: #edf1f7;

  /* The dark ground. Constant across themes: the hero, the agent and the
     footer are dark panels in BOTH modes, the way a printed cover is dark on
     white paper and on a dark shelf alike. */
  /* Constant across themes. In dark mode the page ground is #080b12, so this
     stays clearly bluer and lighter — that difference is the only thing making
     the hero, the assistant and the footer read as panels rather than as more
     page. */
  --ground: #0d1a2e;
  --ground-2: #12233c;
  --on-ground: #f2f5fa;
  --on-ground-2: rgba(242, 245, 250, 0.66);
  --on-ground-3: rgba(242, 245, 250, 0.44);
  --ground-rule: rgba(255, 255, 255, 0.1);

  /* Semantic — deliberately separate from the brand accent. */
  --ok: #15803d;
  --warn: #8a5a06;
  --danger: #b3261e;

  /* Product hues, assigned in FIXED ORDER from the validated six-step
     categorical palette in shared/knowledge-ui/palette.ts. A product's colour
     is part of how it is recognised, so it never depends on position in a list
     and is never recycled. Lawify takes step 5 (green) because it is the fifth
     product, not because green means anything about legal advice — none of the
     other four are semantic either.

     Two values each: `-ink` for a light surface, plain for the dark ground,
     where the token values go muddy at low alpha over #0b1626. */
  /* Lawify's hue is NOT ours to choose. It is a live product with its own
     brand — deep violet #5B21B6 to #7C3AED with gold eyes as the single point
     of contrast — and the hub had been assigning it a green from the shared
     categorical palette, which is the hub deciding what another product looks
     like. lawify.uz/app/globals.css is the authority.
     The gold rides alongside as --hue-lawify-accent, because violet alone sits
     close to Final Exam's violet in a row of five; the gold and the owl are
     what separate them at a glance. */
  --hue-lawify: #9d6cf5;
  --hue-lawify-ink: #6d28d9;
  --hue-lawify-accent: #f5b82f;
  --hue-learnify: #5b83d6;
  --hue-learnify-ink: #2a4b8d;
  --hue-casify: #2bb3bd;
  --hue-casify-ink: #0e7c86;
  --hue-midterm: #e0904a;
  --hue-midterm-ink: #a85f22;
  --hue-finalexam: #9285d1;
  --hue-finalexam-ink: #5a4b96;

  /* Spacing (4px base) */
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.25rem;
  --s6: 1.5rem;
  --s8: 2rem;
  --s12: 3rem;
  --s16: 4rem;

  /* Radii */
  --r-sm: 0.5rem;
  --r: 0.75rem;
  --r-lg: 1rem;
  --r-xl: 1.5rem;
  --r-2xl: 1.75rem;
  --r-full: 9999px;

  /* Elevation */
  --shadow-sm: 0 1px 2px rgba(18, 22, 31, 0.05);
  --shadow: 0 1px 2px rgba(18, 22, 31, 0.05), 0 8px 24px -12px rgba(18, 22, 31, 0.14);
  --shadow-lg: 0 2px 4px rgba(18, 22, 31, 0.06), 0 16px 40px -16px rgba(18, 22, 31, 0.2);

  /* Type. Fluid between the mobile and desktop ends of each step so nothing
     has to be re-declared at a breakpoint. */
  --font-body: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-display: 'Playfair Display', ui-serif, Georgia, 'Times New Roman', serif;

  --t-xs: 0.75rem;
  --t-sm: 0.875rem;
  --t-base: 1rem;
  --t-lg: clamp(1.0625rem, 0.5vw + 0.95rem, 1.125rem);
  --t-xl: clamp(1.15rem, 0.9vw + 0.98rem, 1.375rem);
  --t-2xl: clamp(1.35rem, 1.6vw + 1.05rem, 1.75rem);
  --t-3xl: clamp(1.65rem, 2.6vw + 1.15rem, 2.25rem);
  --t-4xl: clamp(2rem, 4.2vw + 1.1rem, 3rem);
  /* 3.3rem, not 4.25. At the larger size the headline ran to three lines and
     on its own pushed the five mascots off the bottom of the screen. They are
     the argument this page makes; the type is not. */
  --t-hero: clamp(2.15rem, 3.4vw + 0.9rem, 3.3rem);

  --leading-tight: 1.15;
  --leading: 1.6;
  --tracking-wide: 0.11em;

  /* Motion — two durations, one easing. */
  --fast: 150ms;
  --dur: 250ms;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  /* Layout */
  --touch: 44px;
  --measure: 68ch;
  --container: 1200px;
  --header-h: 68px;

  /* The khatim — two squares, one rotated 45°, with quarter-diamonds at the
     corners that close a second rosette at every junction. It is the only
     square-symmetric star that tiles a square grid without a seam, and it is
     the evidence for the claim that this palette comes from Samarkand
     tilework. Two strokes: ink for light surfaces, white for the dark ground. */
  --girih-ink: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2260%22%20height%3D%2260%22%20viewBox%3D%220%200%2060%2060%22%3E%3Cg%20fill%3D%22none%22%20stroke%3D%22%23122036%22%20stroke-width%3D%221%22%3E%3Crect%20x%3D%229%22%20y%3D%229%22%20width%3D%2242%22%20height%3D%2242%22%2F%3E%3Cpath%20d%3D%22M30%202%2058%2030%2030%2058%202%2030Z%22%2F%3E%3Cpath%20d%3D%22M0%209%209%200M51%200%2060%209M60%2051%2051%2060M9%2060%200%2051%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --girih-light: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2260%22%20height%3D%2260%22%20viewBox%3D%220%200%2060%2060%22%3E%3Cg%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%221%22%3E%3Crect%20x%3D%229%22%20y%3D%229%22%20width%3D%2242%22%20height%3D%2242%22%2F%3E%3Cpath%20d%3D%22M30%202%2058%2030%2030%2058%202%2030Z%22%2F%3E%3Cpath%20d%3D%22M0%209%209%200M51%200%2060%209M60%2051%2051%2060M9%2060%200%2051%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}

/* Dark mode changes ONLY the tokens. No component below branches on theme. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;

    /* Dark mode is not "the light palette, darker". The first version put paper
       #0d1017 against surface #141922 — seven steps of luminance apart — so the
       cards, the section bands and the page collapsed into one flat grey mass
       with the rules too dim to draw an edge. The page ground now goes DOWN and
       the card surfaces UP until the separation is visible.

       THIS BLOCK AND :root[data-theme='dark'] BELOW MUST STAY IDENTICAL.
       scripts/build-html.mjs fails the build if they diverge. */
    --indigo: #8faee6;
    --indigo-dark: #b9cdf1;
    --indigo-soft: #1a2438;
    --glaze: #4fc4cc;
    --glaze-soft: #0f2a2c;
    --clay: #e0a06a;

    --ink: #eef2f8;
    --ink-2: #b6c0d0;
    --ink-3: #8894a8;
    --rule: #29344a;
    --rule-strong: #3d4b66;
    --surface: #141b28;
    --surface-2: #1c2536;
    --paper: #080b12;
    --section-alt: #0e131e;

    --ok: #6fcf8f;
    --warn: #e0ac5c;
    --danger: #f2877e;

    --hue-lawify-ink: #9d6cf5;
    --hue-learnify-ink: #5b83d6;
    --hue-casify-ink: #2bb3bd;
    --hue-midterm-ink: #e0904a;
    --hue-finalexam-ink: #9285d1;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
    --shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.35), 0 16px 40px -16px rgba(0, 0, 0, 0.7);
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;
  --indigo: #8faee6;
  --indigo-dark: #b9cdf1;
  --indigo-soft: #1a2438;
  --glaze: #4fc4cc;
  --glaze-soft: #0f2a2c;
  --clay: #e0a06a;

  --ink: #eef2f8;
  --ink-2: #b6c0d0;
  --ink-3: #8894a8;
  --rule: #29344a;
  --rule-strong: #3d4b66;
  --surface: #141b28;
  --surface-2: #1c2536;
  --paper: #080b12;
  --section-alt: #0e131e;

  --ok: #6fcf8f;
  --warn: #e0ac5c;
  --danger: #f2877e;

  --hue-lawify-ink: #9d6cf5;
  --hue-learnify-ink: #5b83d6;
  --hue-casify-ink: #2bb3bd;
  --hue-midterm-ink: #e0904a;
  --hue-finalexam-ink: #9285d1;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
  --shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.35), 0 16px 40px -16px rgba(0, 0, 0, 0.7);
}

/* ── Reset ───────────────────────────────────────────────────────────────── */

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored sections must clear the sticky header. */
  scroll-padding-top: calc(var(--header-h) + var(--s4));

  /* The sideways clip belongs on <html>, not on <body>.
     `overflow-x: hidden` on body does not clip anything: while html's overflow
     is `visible` the value PROPAGATES to the viewport and body's own used value
     becomes visible again. The comparison table is 720px wide inside its own
     scroller, and with the clip on body that width still reached the initial
     containing block — on a phone the layout viewport grew to 693px, so every
     `position: fixed` element (the menu sheet, the back-to-top button) was
     sized and placed against a viewport nearly twice the height of the screen.
     Measured at 360x780: 693x1502 before, 360x780 after.
     `clip` rather than `hidden` because hidden would force overflow-y to auto
     and make <html> a scroll container. */
  overflow-x: clip;
}

body {
  font-family: var(--font-body);
  font-size: var(--t-base);
  line-height: var(--leading);
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Fallback for Safari before 16, which does not know `clip` and would leave
     <html> visible. There it behaves as it always did — no sideways scroll,
     even though the clip does not take effect. */
  overflow-x: hidden;
}

img,
svg,
video {
  display: block;
  max-width: 100%;
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

:focus-visible {
  outline: 2px solid var(--glaze);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* On the dark ground the glaze ring is too dim to find. */
.ground :focus-visible,
.ground-panel :focus-visible {
  outline-color: rgba(255, 255, 255, 0.85);
}

::selection {
  background: color-mix(in srgb, var(--glaze) 30%, transparent);
}

/* ── Primitives ──────────────────────────────────────────────────────────── */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--s5);
}

@media (min-width: 768px) {
  .container {
    padding-inline: var(--s8);
  }
}

.section {
  padding-block: clamp(3.5rem, 7vw, 6.5rem);
  position: relative;
}

.section--tight {
  padding-block: clamp(2.5rem, 5vw, 4.5rem);
}

/* The alternating band needs its OWN ground, not the card colour.
   It used to be `background: var(--surface)` — the exact value every card
   inside it uses. In light mode the borders were just strong enough to hide
   the problem; in dark they were not, so the glossary terms and the
   comparison table read as faint outlines floating on nothing. Three steps
   now: page, band, card. */
.section--alt {
  background: var(--section-alt);
  border-block: 1px solid var(--rule);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--s2);
  left: var(--s2);
  z-index: 200;
  padding: var(--s3) var(--s4);
  border-radius: var(--r);
  background: var(--indigo);
  color: #fff;
  font-weight: 600;
  transform: translateY(-200%);
  transition: transform var(--dur) var(--ease);
}

.skip-link:focus-visible {
  transform: none;
}

/* Section headers. The eyebrow is a small caps label with a rule running off
   it, rather than a bare word floating above a grid. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--glaze);
}

.eyebrow::before {
  content: '';
  width: 18px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  flex: none;
}

.section-head {
  max-width: 46rem;
  margin-bottom: clamp(2rem, 4vw, 3.25rem);
}

.section-head--center {
  margin-inline: auto;
  text-align: center;
}

.section-head--center .eyebrow::before {
  display: none;
}

.section-title {
  font-family: var(--font-display);
  font-size: var(--t-4xl);
  line-height: var(--leading-tight);
  letter-spacing: -0.015em;
  font-weight: 700;
  margin-top: var(--s3);
  text-wrap: balance;
}

.section-sub {
  margin-top: var(--s4);
  font-size: var(--t-lg);
  color: var(--ink-2);
  max-width: var(--measure);
  text-wrap: pretty;
}

.section-head--center .section-sub {
  margin-inline: auto;
}

/* Icons. One sprite, `currentColor`, sized by the box it sits in.
   fill/stroke are set here as well as on each <symbol>: CSS on the referencing
   element inherits into the shadow tree that <use> builds, which the sprite's
   own root attributes do not. Either alone works; both means a hand-edited
   sprite cannot silently turn every icon into a black blob. */
.icon {
  width: 1.25em;
  height: 1.25em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon--sm {
  width: 1em;
  height: 1em;
}

.icon--lg {
  width: 1.5em;
  height: 1.5em;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: var(--touch);
  padding: 0.7rem 1.35rem;
  border-radius: var(--r-full);
  font-size: var(--t-sm);
  font-weight: 650;
  letter-spacing: 0.005em;
  border: 1px solid transparent;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
    color var(--dur) var(--ease), transform var(--fast) var(--ease), box-shadow var(--dur) var(--ease);
  white-space: nowrap;
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background: var(--indigo);
  color: #fff;
  box-shadow: var(--shadow);
}

:root[data-theme='dark'] .btn--primary,
.ground .btn--primary {
  color: var(--ground);
}

.btn--primary:hover {
  background: var(--indigo-dark);
}

.btn--glaze {
  background: var(--glaze);
  color: #fff;
}

.btn--ghost {
  border-color: var(--rule-strong);
  color: var(--ink);
  background: var(--surface);
}

.btn--ghost:hover {
  border-color: var(--indigo);
  color: var(--indigo);
}

/* On the dark ground the neutral tokens invert, so these are stated in terms
   of the ground rather than the theme. */
.on-ground .btn--primary {
  background: var(--on-ground);
  color: var(--ground);
}

.on-ground .btn--primary:hover {
  background: #fff;
}

.on-ground .btn--ghost {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.22);
  color: var(--on-ground);
}

.on-ground .btn--ghost:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.4);
  color: #fff;
}

.btn--block {
  width: 100%;
}

.btn .icon {
  transition: transform var(--dur) var(--ease);
}

.btn:hover .icon--go {
  transform: translateX(3px);
}

/* Chips */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 0.3rem 0.7rem;
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  font-weight: 600;
  border: 1px solid var(--rule);
  background: var(--surface-2);
  color: var(--ink-2);
}

.on-ground .chip {
  border-color: var(--ground-rule);
  background: rgba(255, 255, 255, 0.05);
  color: var(--on-ground-2);
}

/* The girih field. Always decorative and always behind content. */
.girih {
  position: absolute;
  inset: 0;
  background-image: var(--girih-light);
  background-size: 60px 60px;
  opacity: 0.05;
  pointer-events: none;
}

.girih--ink {
  background-image: var(--girih-ink);
  opacity: 0.035;
}

/* ── Dark ground panels ──────────────────────────────────────────────────── */

.ground {
  position: relative;
  isolation: isolate;
  background: var(--ground);
  color: var(--on-ground);
  overflow: hidden;
}

.ground::before {
  /* A wash of the two brand hues so the ground is never a flat black. */
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(70% 85% at 12% 0%, rgba(42, 75, 141, 0.55), transparent 62%),
    radial-gradient(60% 75% at 92% 12%, rgba(14, 124, 134, 0.32), transparent 60%);
  pointer-events: none;
  z-index: -1;
}

.ground .section-sub,
.on-ground .section-sub {
  color: var(--on-ground-2);
}

.ground .eyebrow,
.on-ground .eyebrow {
  color: var(--glaze);
}

:root:not([data-theme='dark']) .ground .eyebrow,
:root:not([data-theme='dark']) .on-ground .eyebrow {
  color: #4fc4cc;
}

/* ── Header ──────────────────────────────────────────────────────────────── */

.header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: saturate(1.6) blur(14px);
  -webkit-backdrop-filter: saturate(1.6) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.header[data-scrolled='true'] {
  border-bottom-color: var(--rule);
  background: color-mix(in srgb, var(--paper) 92%, transparent);
}

.header__bar {
  display: flex;
  align-items: center;
  gap: var(--s4);
  height: var(--header-h);
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: none;
  min-width: 0;
}

.brand__mark {
  width: 38px;
  height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 11px;
  background: linear-gradient(160deg, var(--indigo-dark), var(--indigo));
  border: 1px solid rgba(255, 255, 255, 0.14);
  overflow: hidden;
}

.brand__mark img {
  width: 34px;
  height: auto;
  transform: translateY(2px);
}

.brand__text {
  display: flex;
  flex-direction: column;
  line-height: 1.05;
  min-width: 0;
}

.brand__name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.06rem;
  letter-spacing: 0.005em;
  white-space: nowrap;
}

.brand__sub {
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--clay);
  white-space: nowrap;
  margin-top: 2px;
}

/* "By OXFORDER" is four characters in English and eighteen in Karakalpak
   ("OXFORDER TÁREPINEN"). Being nowrap, it widened the brand block until the
   menu button was pushed past the right edge of a 390px screen — a header that
   was fine in the language it was designed in and broken in one of the five it
   ships in. It is secondary, it is on screen again in the footer, and the
   product name beside it says the same thing. Below the small breakpoint it
   goes. */
@media (max-width: 419px) {
  .brand__sub {
    display: none;
  }
}

/* The header at small widths.
   Measured before this block: the row needed ~378px in EVERY language, so on a
   360px phone the menu button sat 17px past the right edge and on a 320px one
   45px past it. `overflow-x: clip` hid the symptom — the page did not scroll
   sideways, the button was simply not there. Three changes and it fits at 320:
   the language control drops to its code alone, the gaps and padding tighten,
   and the brand becomes the thing that gives way if anything still does not
   fit. */
@media (max-width: 419px) {
  .container {
    padding-inline: 1rem;
  }

  .header__bar {
    gap: 0.5rem;
  }

  .lang__btn {
    padding-inline: 0.5rem;
  }

  /* The globe and the caret are affordances, not information. The code stays. */
  .lang__btn .icon {
    display: none;
  }

  .brand__mark {
    width: 34px;
    height: 34px;
  }

  .brand__mark img {
    width: 30px;
  }

  .brand__name {
    font-size: 0.98rem;
  }
}

/* The last resort, at any width: the brand truncates and the controls never do.
   A header is a row of fixed controls plus one variable-length label, and the
   label is the only part that can give. */
.brand {
  min-width: 0;
}

.brand__name,
.brand__sub {
  overflow: hidden;
  text-overflow: ellipsis;
}

.header__tools {
  flex: none;
}

.nav {
  display: none;
  align-items: center;
  gap: 0.15rem;
  margin-inline: auto;
}

.nav__link {
  padding: 0.5rem 0.7rem;
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  font-weight: 550;
  color: var(--ink-2);
  transition: color var(--fast) var(--ease), background-color var(--fast) var(--ease);
  white-space: nowrap;
}

.nav__link:hover {
  color: var(--ink);
  background: var(--surface-2);
}

.nav__link[aria-current='true'] {
  color: var(--indigo);
}

.header__tools {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-left: auto;
}

/* Language switcher. A native <select> on small screens — it gets the
   platform's own picker, which is the best mobile control there is — and a
   button menu from `md` up where there is room to show the current choice. */
.lang {
  position: relative;
}

.lang__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  height: 38px;
  padding-inline: 0.7rem;
  border-radius: var(--r-full);
  border: 1px solid var(--rule);
  background: var(--surface);
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--ink-2);
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}

.lang__btn:hover {
  border-color: var(--rule-strong);
  color: var(--ink);
}

.lang__btn .icon {
  width: 1rem;
  height: 1rem;
}

.lang__caret {
  transition: transform var(--dur) var(--ease);
}

.lang__btn[aria-expanded='true'] .lang__caret {
  transform: rotate(180deg);
}

.lang__menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 190px;
  padding: var(--s2);
  border-radius: var(--r-lg);
  border: 1px solid var(--rule);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  display: grid;
  gap: 2px;
  z-index: 120;
}

.lang__menu[hidden] {
  display: none;
}

.lang__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  width: 100%;
  min-height: var(--touch);
  padding: 0.5rem 0.7rem;
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  font-weight: 550;
  text-align: left;
  color: var(--ink-2);
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease);
}

.lang__item:hover {
  background: var(--surface-2);
  color: var(--ink);
}

.lang__item[aria-selected='true'] {
  color: var(--indigo);
  font-weight: 650;
}

.lang__item .icon {
  opacity: 0;
  color: var(--glaze);
}

.lang__item[aria-selected='true'] .icon {
  opacity: 1;
}

.icon-btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: var(--r-full);
  border: 1px solid var(--rule);
  background: var(--surface);
  color: var(--ink-2);
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease),
    background-color var(--fast) var(--ease);
}

.icon-btn:hover {
  border-color: var(--rule-strong);
  color: var(--ink);
}

.icon-btn--menu {
  display: grid;
}

@media (min-width: 1024px) {
  .nav {
    display: flex;
  }

  .icon-btn--menu {
    display: none;
  }
}

/* The theme toggle shows the state it will switch TO, which is the convention
   every OS settings pane uses. */
.theme-toggle [data-theme-icon='dark'] {
  display: none;
}

:root[data-theme='dark'] .theme-toggle [data-theme-icon='dark'] {
  display: block;
}

:root[data-theme='dark'] .theme-toggle [data-theme-icon='light'] {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .theme-toggle [data-theme-icon='dark'] {
    display: block;
  }

  :root:not([data-theme='light']) .theme-toggle [data-theme-icon='light'] {
    display: none;
  }
}

/* Mobile menu — a sheet, not a full-screen takeover, so the page stays visible
   behind it and the gesture reads as reversible. */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 150;
  display: grid;
  grid-template-rows: 1fr auto;
}

.sheet[hidden] {
  display: none;
}

.sheet__scrim {
  position: absolute;
  inset: 0;
  background: rgba(6, 12, 22, 0.55);
  backdrop-filter: blur(2px);
  animation: fade-in var(--dur) var(--ease);
}

.sheet__panel {
  position: relative;
  /* Explicit, so the panel cannot end up under the scrim if either ever gains
     a transform or a filter and starts its own stacking context. */
  z-index: 1;
  grid-row: 2;
  background: var(--surface);
  border-top-left-radius: var(--r-2xl);
  border-top-right-radius: var(--r-2xl);
  border-top: 1px solid var(--rule);
  padding: var(--s3) var(--s4) max(var(--s6), env(safe-area-inset-bottom));
  max-height: 86vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  box-shadow: 0 -20px 60px -20px rgba(0, 0, 0, 0.45);
  animation: sheet-up var(--dur) var(--ease);
}

.sheet__grab {
  width: 42px;
  height: 4px;
  border-radius: var(--r-full);
  background: var(--rule-strong);
  margin: 0 auto var(--s4);
}

.sheet__link {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: var(--touch);
  padding: 0.7rem 0.6rem;
  border-radius: var(--r);
  font-size: var(--t-lg);
  font-weight: 600;
  color: var(--ink);
  transition: background-color var(--fast) var(--ease);
}

.sheet__link:active {
  background: var(--surface-2);
}

.sheet__link .icon {
  color: var(--ink-3);
}

.sheet__rule {
  height: 1px;
  background: var(--rule);
  margin-block: var(--s3);
}

/* On a site in five languages the switcher is one of the two controls people
   actually reach for, so on a phone it goes FIRST in the sheet and stays on
   one row. Buried under eight nav links it sat past the panel's own scroll
   edge, which made changing language a scroll-inside-a-sheet gesture. */
.sheet__langs {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--s2);
}

.lang-chip {
  min-height: var(--touch);
  padding: 0.5rem 0.25rem;
  border-radius: var(--r);
  border: 1px solid var(--rule);
  background: var(--surface-2);
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--ink-2);
  transition: border-color var(--fast) var(--ease), background-color var(--fast) var(--ease),
    color var(--fast) var(--ease);
}

.lang-chip[aria-selected='true'] {
  border-color: var(--indigo);
  background: var(--indigo-soft);
  color: var(--indigo);
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@keyframes sheet-up {
  from {
    transform: translateY(12px);
    opacity: 0;
  }
}

/* ── Hero ────────────────────────────────────────────────────────────────
   The first version was a headline beside a grid of five tiles on a flat
   gradient — correct, and completely anonymous. It now opens on an
   architectural night plate in the brand palette with our own AI figure
   standing in it, and the five platforms run full width underneath as the
   first thing below the fold.

   Everything here is ours and static: one WebP for the plate, one for the
   figure. The version this replaces embedded a third-party Spline scene that
   wrote another company's product name across the middle of the page, carried
   its own "Built with" badge, and could not be composited onto a dark ground
   at all — its robot is near-black on opaque light grey.
   ────────────────────────────────────────────────────────────────────────── */

.hero {
  padding-block: clamp(2rem, 4vw, 3rem) 0;
}

/* The plate sits behind everything, anchored to the top-right so the building
   recedes away from the headline rather than under it. */
.hero__plate {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  pointer-events: none;
}

.hero__plateimg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 68% 28%;
  opacity: 0.55;
}

@media (min-width: 1024px) {
  .hero__plateimg {
    object-position: 62% 30%;
  }
}

/* The loop lies on top of the still and fades in once it can play through, so
   there is never a moment of empty plate. Added by script, never in the
   markup — see initHeroLoop in app.js for who does and does not get it. */
.hero__loop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 62% 30%;
  opacity: 0;
  transition: opacity 900ms var(--ease);
  pointer-events: none;
}

.hero__loop[data-ready='true'] {
  opacity: 0.55;
}

/* Text has to win. The veil is a two-axis wash rather than a flat scrim: dark
   from the left where the headline is, dark from the bottom where the family
   strip sits, and thinnest at the top right where the building is worth
   seeing. */
.hero__veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(100deg, var(--ground) 18%, color-mix(in srgb, var(--ground) 72%, transparent) 48%, transparent 78%),
    linear-gradient(0deg, var(--ground) 4%, color-mix(in srgb, var(--ground) 45%, transparent) 34%, transparent 62%);
}

.hero__grid {
  position: relative;
  display: grid;
  gap: clamp(2rem, 4vw, 3rem);
  align-items: center;
}

@media (min-width: 1024px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  }
}

.hero__copy {
  position: relative;
  z-index: 1;
}

.hero__badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 0.4rem 0.9rem 0.4rem 0.55rem;
  border-radius: var(--r-full);
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(11, 22, 38, 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--on-ground-2);
}

.hero__badge-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: #4fc4cc;
  box-shadow: 0 0 0 3px rgba(79, 196, 204, 0.22);
  flex: none;
}

.hero__title {
  font-family: var(--font-display);
  font-size: var(--t-hero);
  line-height: 1.03;
  letter-spacing: -0.022em;
  font-weight: 700;
  margin-top: var(--s5);
  text-wrap: balance;
  text-shadow: 0 2px 24px rgba(6, 12, 22, 0.55);
}

.hero__title em {
  font-style: normal;
  display: block;
  background: linear-gradient(96deg, #f0b45f, #e08c3f 55%, #d97b57);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero__sub {
  margin-top: var(--s5);
  font-size: var(--t-lg);
  color: var(--on-ground-2);
  max-width: 34rem;
  text-wrap: pretty;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s6);
}

/* Below the wrap point they stack, and two stacked pills at two different
   widths read as an accident rather than a pair. */
@media (max-width: 419px) {
  .hero__actions {
    display: grid;
  }
}

.hero__stats {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.5rem, 5vw, 3rem);
  margin-top: clamp(1.25rem, 2.5vw, 1.75rem);
  padding-top: clamp(1rem, 2vw, 1.35rem);
  border-top: 1px solid var(--ground-rule);
}

.stat__n {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 3.5vw, 2.35rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
}

.stat__l {
  margin-top: 0.4rem;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--on-ground-3);
}

/* ── The figure ──────────────────────────────────────────────────────────── */

/* The figure's HEIGHT is set here, not inherited from the artwork.
   The robot is 663x1115, so left to itself it made the hero grid 774px tall
   while the copy beside it needed 454 — 320px of nothing, which is what pushed
   the five mascots off the bottom of a 950px screen. The box leads; the image
   fits inside it. */
.hero__figure {
  position: relative;
  display: grid;
  place-items: end center;
  justify-self: center;
  width: 100%;
  max-width: 520px;
  height: clamp(360px, 46vh, 500px);
}

/* A cool pool of light behind it, so the figure is standing in the scene
   rather than pasted over it. */
.hero__glow {
  position: absolute;
  left: 50%;
  top: 46%;
  width: 108%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(79, 196, 204, 0.2), rgba(42, 75, 141, 0.14) 55%, transparent 72%);
  filter: blur(14px);
  pointer-events: none;
}

/* ── The 3D scene ────────────────────────────────────────────────────────
   Sits over the still and fades in once the frame has loaded. Added by script;
   see initHeroScene in app.js for who gets it and what each part of the
   treatment below is hiding. */
.hero__scene {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  opacity: 0;
  transition: opacity 900ms var(--ease);
  pointer-events: none;
  /* Fade every edge into the ground. This does two jobs: it kills the hard
     rectangle a cropped iframe would otherwise leave, and it takes out the
     faint bands at the left and right that are all that survives of the
     scene's own wordmark after the inversion. */
  -webkit-mask-image: radial-gradient(96% 80% at 50% 40%, #000 26%, transparent 74%);
  mask-image: radial-gradient(96% 80% at 50% 40%, #000 26%, transparent 74%);
  /* The blend belongs HERE, not on the iframe.
     A mask makes this element its own stacking context, so a mix-blend-mode
     set on the iframe inside it blends against this box's own transparent
     backdrop — which does nothing — and the whole thing then composites over
     the hero as an opaque black rectangle. On the masked element it blends
     with the hero ground, which is the point: the scene's inverted background
     is near-black, and screen makes near-black disappear. */
  mix-blend-mode: screen;
}

.hero__scene[data-ready='true'] {
  opacity: 1;
}

/* Once the scene is up, the still underneath is redundant. */
.hero__scene[data-ready='true'] ~ .hero__robot,
.hero__figure:has(.hero__scene[data-ready='true']) .hero__robot {
  opacity: 0;
}

.hero__scene iframe {
  position: absolute;
  /* Scaled up and offset so the "Built with Spline" badge in the scene's
     bottom-right corner falls outside the visible box entirely. */
  left: -44%;
  top: -26%;
  width: 188%;
  height: 176%;
  border: 0;
  /* invert: the scene's light ground goes dark and its near-black robot goes
     white. hue-rotate undoes the colour flip the invert causes. screen: the
     now-dark ground drops out against ours, leaving only the robot — and it
     takes the inverted wordmark with it. */
  /* brightness stays at 1: the scene's robot is already near-white after the
     invert, and pushing it further blows the modelling off the chest. */
  filter: invert(1) hue-rotate(180deg) contrast(1.06);
}

.hero__robot {
  position: relative;
  transition: opacity 700ms var(--ease);
  width: auto;
  max-width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 30px 50px rgba(0, 0, 0, 0.55));
  animation: hero-float 7s var(--ease) infinite;
}

/* Enough to feel alive, not enough to distract from the words beside it. */
@keyframes hero-float {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-10px);
  }
}

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

/* A standing figure has to end somewhere. Cut off square by the section edge it
   reads as a mistake, so the last stretch dissolves into the ground. */
.hero__robot {
  -webkit-mask-image: linear-gradient(180deg, #000 78%, transparent 99%);
  mask-image: linear-gradient(180deg, #000 78%, transparent 99%);
}

/* On a phone the figure becomes the BACKDROP, not the act.
   Stacked above the copy it took 437px of an 844px viewport, so the fold was a
   robot and the headline was somewhere below it. Behind the text at low
   opacity it still sets the scene and the first thing read is the sentence
   that says what this is. */
@media (max-width: 1023px) {
  .hero__grid {
    position: relative;
  }

  .hero__figure {
    position: absolute;
    height: auto;
    right: -14%;
    top: 46%;
    transform: translateY(-50%);
    width: 66%;
    max-width: 360px;
    z-index: 0;
    opacity: 0.32;
    pointer-events: none;
    justify-self: end;
  }

  .hero__robot {
    width: 100%;
    height: auto;
    animation: none;
  }

  .hero__glow {
    opacity: 0.6;
  }

  .hero__copy {
    z-index: 1;
  }
}

/* ── The family band ─────────────────────────────────────────────────────── */

.hero__family {
  position: relative;
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  padding-bottom: clamp(1.75rem, 3vw, 2.5rem);
}

.family {
  position: relative;
  border-radius: var(--r-2xl);
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(11, 22, 38, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: clamp(1rem, 2.5vw, 1.5rem);
  overflow: hidden;
  box-shadow: 0 24px 60px -34px rgba(0, 0, 0, 0.9);
}

.family__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.5rem, 1.5vw, 0.75rem);
}

/* Five items do not divide into two or four columns, so the first tile spans
   the row on small screens and leads the set instead of leaving a hole. */
.family__item:first-child {
  grid-column: 1 / -1;
}

@media (min-width: 560px) {
  .family__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .family__item:first-child {
    grid-column: auto;
  }
}

@media (min-width: 900px) {
  .family__grid {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

.family__card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  height: 100%;
  padding: clamp(0.6rem, 1.4vw, 0.8rem) 0.5rem 0.7rem;
  border-radius: var(--r-xl);
  border: 1px solid rgba(255, 255, 255, 0.09);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--hue) 14%, transparent),
    color-mix(in srgb, var(--hue) 4%, transparent));
  overflow: hidden;
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* A hairline of the product's own colour along the top edge. Five of them in a
   row is the palette, which is half the point of the strip. */
.family__card::after {
  content: '';
  position: absolute;
  inset-inline: 18%;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--hue), transparent);
}

.family__card:hover,
.family__card:focus-visible {
  transform: translateY(-4px);
  border-color: var(--hue);
}

/* The hue bloom answers "is this a link" and "which product is this" in the
   same gesture. */
.family__bloom {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 50% 118%, var(--hue) 0%, transparent 62%);
  opacity: 0.22;
  transition: opacity var(--dur) var(--ease);
  pointer-events: none;
}

.family__card:hover .family__bloom,
.family__card:focus-visible .family__bloom {
  opacity: 0.44;
}

.family__cub {
  position: relative;
  width: auto;
  height: clamp(56px, 6vw, 72px);
  object-fit: contain;
  filter: saturate(0.94) drop-shadow(0 8px 12px rgba(0, 0, 0, 0.42));
  transition: transform var(--dur) var(--ease), filter var(--dur) var(--ease);
}

.family__card:hover .family__cub,
.family__card:focus-visible .family__cub {
  transform: translateY(-3px) scale(1.06);
  filter: saturate(1.06) drop-shadow(0 12px 16px rgba(0, 0, 0, 0.5));
}

.family__name {
  position: relative;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  text-align: center;
  color: var(--on-ground);
  line-height: 1.2;
}

.family__role {
  position: relative;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-align: center;
  color: var(--hue);
  line-height: 1.25;
}

.family__caption {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-top: clamp(0.7rem, 1.6vw, 0.9rem);
  padding-top: clamp(0.7rem, 1.6vw, 0.9rem);
  border-top: 1px solid var(--ground-rule);
  font-size: var(--t-xs);
  color: var(--on-ground-3);
  line-height: 1.45;
}

.family__caption .icon {
  color: var(--on-ground-3);
  flex: none;
}

/* ── Trust strip ─────────────────────────────────────────────────────────── */

.trust {
  border-top: 1px solid var(--ground-rule);
  background: rgba(0, 0, 0, 0.22);
}

.trust__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 4vw, 3rem);
  padding-block: var(--s5);
}

.trust__item {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--on-ground-2);
  text-align: center;
}

.trust__item .icon {
  color: #4fc4cc;
  width: 1rem;
  height: 1rem;
}

/* ── Platform cards ──────────────────────────────────────────────────────── */

.platforms {
  display: grid;
  gap: clamp(1rem, 2.5vw, 1.5rem);
}

@media (min-width: 700px) {
  .platforms {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Five cards over three columns leave a hole in the second row.
   The fix is six columns with every card spanning two, so the trailing pair
   can start at column 2 and 4 and sit centred. Doing it with margins and a
   width over 100% — which is the trick that first shipped here — makes each
   card wider than its own grid cell, so cards 4 and 5 overlapped card 3 and
   ran off the right edge. A card that spans grid tracks never overflows. */
@media (min-width: 1080px) {
  .platforms {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  .platforms > * {
    grid-column: span 2;
  }

  .platforms > :nth-child(4) {
    grid-column: 2 / span 2;
  }

  .platforms > :nth-child(5) {
    grid-column: 4 / span 2;
  }
}

.pcard {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--r-2xl);
  border: 1px solid var(--rule);
  background: var(--surface);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

/* In dark mode a drop shadow does nothing — there is no lighter ground for it
   to fall on. A one-pixel inset highlight along the top edge is what actually
   lifts a surface off a dark page, the way a real edge catches light. */
:root[data-theme='dark'] .pcard,
:root[data-theme='dark'] .step,
:root[data-theme='dark'] .role,
:root[data-theme='dark'] .milestone,
:root[data-theme='dark'] .table-wrap,
:root[data-theme='dark'] .definition,
:root[data-theme='dark'] .role-result,
:root[data-theme='dark'] .about__figure {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .pcard,
  :root:not([data-theme='light']) .step,
  :root:not([data-theme='light']) .role,
  :root:not([data-theme='light']) .milestone,
  :root:not([data-theme='light']) .table-wrap,
  :root:not([data-theme='light']) .definition,
  :root:not([data-theme='light']) .role-result,
  :root:not([data-theme='light']) .about__figure {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  }
}

.pcard:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: color-mix(in srgb, var(--hue-ink) 55%, var(--rule));
}

/* ── The header band ──────────────────────────────────────────────────────
   The mascot used to sit in a 76px box in the corner, at the same weight as
   the tag beside it, which made five cards read as five identical layouts with
   a small picture in them. It is the strongest thing any of these products
   owns, so it leads: a band in the product's own hue, the girih behind it, and
   the character breaking the band's lower edge so it reads as standing in
   front of the card rather than pasted onto it. */
.pcard__head {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: var(--s4);
  min-height: 132px;
  padding: var(--s5) var(--s6) 0;
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--hue-ink) 24%, transparent),
    color-mix(in srgb, var(--hue-ink) 8%, transparent) 62%,
    transparent);
}

/* No border along the bottom of the band. The character overlaps that edge on
   purpose, and a hairline running across its body reads as the mascot having
   been sliced rather than as it standing in front. The gradient fading to
   nothing is the edge. */

.pcard__wash {
  position: absolute;
  inset: 0;
  background-image: var(--girih-ink);
  background-size: 60px 60px;
  opacity: 0.05;
  pointer-events: none;
}

:root[data-theme='dark'] .pcard__wash {
  background-image: var(--girih-light);
  opacity: 0.045;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .pcard__wash {
    background-image: var(--girih-light);
    opacity: 0.045;
  }
}

/* A hairline of the product's own colour along the top edge. */
.pcard::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  z-index: 2;
  background: linear-gradient(90deg,
    var(--hue-ink),
    var(--hue-accent, var(--hue-ink)) 55%,
    color-mix(in srgb, var(--hue-ink) 20%, transparent));
}

.pcard__figure {
  position: relative;
  flex: none;
  display: grid;
  place-items: end center;
  /* 96, not 116: the text column beside it is only about 180px on a
     three-across grid, and "TSUL Finalizer" in the display face needs room to
     stay on one line. */
  width: 96px;
  /* The character stands ON the band's lower edge and overlaps the body below
     it. Negative margin rather than absolute positioning, so the band still
     reserves the height and nothing overlaps on a narrow card. */
  margin-bottom: -18px;
}

/* A soft ellipse under the character, so it is standing rather than floating. */
.pcard__figure::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 16px;
  width: 64px;
  height: 13px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--hue-ink) 42%, transparent), transparent);
  filter: blur(3px);
  z-index: -1;
}

.pcard__cub {
  position: relative;
  width: auto;
  height: 100px;
  object-fit: contain;
  filter: drop-shadow(0 8px 14px rgba(10, 14, 22, 0.22));
  transition: transform var(--dur) var(--ease);
}

.pcard:hover .pcard__cub {
  transform: translateY(-5px) scale(1.04);
}

.pcard__id {
  position: relative;
  min-width: 0;
  flex: 1;
  padding-bottom: var(--s5);
}

.pcard__tag {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.24rem 0.62rem;
  border-radius: var(--r-full);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--hue-ink);
  background: color-mix(in srgb, var(--hue-ink) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--hue-ink) 30%, transparent);
}

.pcard__tag .icon {
  width: 0.85rem;
  height: 0.85rem;
}

.pcard__name {
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  font-weight: 700;
  line-height: 1.1;
  margin-top: 0.5rem;
  letter-spacing: -0.015em;
}

.pcard__domain {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.25rem;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.pcard__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--s6) var(--s6) var(--s6);
}

.pcard__desc {
  color: var(--ink-2);
  font-size: var(--t-sm);
  line-height: 1.65;
}

.pcard__features {
  display: grid;
  gap: 0.55rem;
  margin-top: var(--s5);
}

.pcard__feature {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-size: var(--t-sm);
  color: var(--ink-2);
  line-height: 1.5;
}

.pcard__feature .icon {
  width: 1.05rem;
  height: 1.05rem;
  margin-top: 0.18rem;
  color: var(--hue-ink);
}

.pcard__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
}

.pcard__cta {
  /* auto, not a fixed gap: the descriptions are different lengths, and a row
     of cards whose buttons sit at five different heights reads as five
     unrelated boxes rather than one set. */
  margin-top: auto;
  padding-top: 0;
  background: var(--hue-ink);
  color: #fff;
  border-color: transparent;
}

:root[data-theme='dark'] .pcard__cta {
  color: var(--ground);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .pcard__cta {
    color: var(--ground);
  }
}

.pcard__cta:hover {
  background: color-mix(in srgb, var(--hue-ink) 84%, var(--ink));
}

/* The whole card is clickable, with the visible button as the accessible
   target — a stretched pseudo-element rather than a wrapping <a>, so the
   feature list stays selectable text. */
.pcard__cta::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* ── Steps ───────────────────────────────────────────────────────────────── */

.steps {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2rem);
  counter-reset: step;
}

@media (min-width: 800px) {
  .steps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.step {
  position: relative;
  padding: var(--s6);
  border-radius: var(--r-xl);
  border: 1px solid var(--rule);
  background: var(--surface);
}

.step__n {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r);
  background: var(--indigo-soft);
  color: var(--indigo);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
}

.step__t {
  margin-top: var(--s4);
  font-size: var(--t-xl);
  font-weight: 650;
  letter-spacing: -0.005em;
}

.step__d {
  margin-top: var(--s3);
  color: var(--ink-2);
  font-size: var(--t-sm);
  line-height: 1.65;
}

/* ── Role finder ─────────────────────────────────────────────────────────
   The one genuinely interactive thing on the page, and the first version
   rendered its answer as three grey rows of text. It now switches: the picked
   role lifts and takes its hue, the answer panel slides up, and the three
   recommendations arrive one after another with their mascots. Every bit of it
   is decoration — the same three links are there before and after the motion —
   so all of it is dropped under prefers-reduced-motion.
   ────────────────────────────────────────────────────────────────────────── */

.roles {
  display: grid;
  gap: var(--s4);
}

@media (min-width: 720px) {
  .roles {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.role {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s4);
  width: 100%;
  min-height: var(--touch);
  padding: var(--s5);
  border-radius: var(--r-xl);
  border: 1px solid var(--rule);
  background: var(--surface);
  text-align: left;
  overflow: hidden;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

/* A glaze wash that only exists once the role is chosen, so "picked" is a
   state you can see from across the room rather than a one-pixel border. */
.role::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--glaze) 20%, transparent),
    color-mix(in srgb, var(--glaze) 5%, transparent) 70%,
    transparent);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
  pointer-events: none;
}

.role:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--glaze) 55%, var(--rule));
  box-shadow: var(--shadow);
}

.role[aria-pressed='true'] {
  border-color: var(--glaze);
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}

.role[aria-pressed='true']::before {
  opacity: 1;
}

.role__icon {
  position: relative;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: var(--r);
  background: var(--glaze-soft);
  color: var(--glaze);
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.role[aria-pressed='true'] .role__icon {
  transform: scale(1.06);
  background: color-mix(in srgb, var(--glaze) 22%, var(--surface));
}

.role__text {
  position: relative;
  min-width: 0;
}

.role__t {
  display: block;
  font-weight: 650;
  font-size: var(--t-base);
}

.role__d {
  display: block;
  font-size: var(--t-sm);
  color: var(--ink-3);
  margin-top: 0.1rem;
  line-height: 1.45;
}

/* The answer. */
.role-result {
  margin-top: var(--s6);
  padding: var(--s6);
  border-radius: var(--r-2xl);
  border: 1px solid var(--rule);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.role-result[hidden] {
  display: none;
}

.role-result__t {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-weight: 650;
  font-size: var(--t-lg);
}

.role-result__t .icon {
  color: var(--glaze);
}

.role-result__list {
  display: grid;
  gap: var(--s3);
  margin-top: var(--s5);
}

@media (min-width: 720px) {
  .role-result__list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Each recommendation is a small version of the platform card: the mascot on
   its own hue, the name, and one clause saying why it was picked. */
.role-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s4);
  min-height: var(--touch);
  padding: 0.85rem 1rem;
  border-radius: var(--r-lg);
  border: 1px solid var(--rule);
  background: var(--paper);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
    background-color var(--dur) var(--ease);
}

.role-link:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--hue-ink) 55%, var(--rule));
  background: var(--surface-2);
}

.role-link__plinth {
  position: relative;
  display: grid;
  place-items: end center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: var(--r);
  border: 1px solid color-mix(in srgb, var(--hue-ink) 24%, transparent);
  background: radial-gradient(circle at 50% 122%,
    color-mix(in srgb, var(--hue-ink) 48%, transparent),
    color-mix(in srgb, var(--hue-ink) 10%, transparent) 74%);
  overflow: hidden;
}

.role-link__plinth img {
  width: auto;
  height: 42px;
  object-fit: contain;
  transition: transform var(--dur) var(--ease);
}

.role-link:hover .role-link__plinth img {
  transform: translateY(-2px) scale(1.06);
}

.role-link__body {
  min-width: 0;
  flex: 1;
}

.role-link__name {
  display: block;
  font-size: var(--t-sm);
  font-weight: 700;
}

.role-link__why {
  display: block;
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--ink-3);
  margin-top: 0.15rem;
  line-height: 1.45;
}

.role-link > .icon {
  flex: none;
  color: var(--hue-ink);
  transition: transform var(--dur) var(--ease);
}

.role-link:hover > .icon {
  transform: translate(2px, -2px);
}

/* The switch. Added by script after the nodes are in the document, so a
   failure leaves them plainly visible rather than stuck at opacity 0. */
.role-result--enter {
  animation: role-panel 380ms var(--ease) both;
}

.role-link--enter {
  animation: role-item 420ms var(--ease) both;
  animation-delay: calc(var(--i, 0) * 70ms + 60ms);
}

@keyframes role-panel {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}

@keyframes role-item {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.97);
  }
}

@media (prefers-reduced-motion: reduce) {
  .role-result--enter,
  .role-link--enter {
    animation: none;
  }
}

/* ── Comparison table ────────────────────────────────────────────────────── */

/* Wide content scrolls inside its own container; the page never scrolls
   sideways. */
.table-wrap {
  overflow-x: auto;
  border-radius: var(--r-xl);
  border: 1px solid var(--rule);
  background: var(--surface);
  -webkit-overflow-scrolling: touch;
}

.cmp {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  font-size: var(--t-sm);
}

.cmp th,
.cmp td {
  padding: 0.85rem 1rem;
  text-align: center;
  border-bottom: 1px solid var(--rule);
}

.cmp thead th {
  position: sticky;
  top: 0;
  background: var(--surface-2);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-2);
  z-index: 1;
}

.cmp tbody tr:last-child th,
.cmp tbody tr:last-child td {
  border-bottom: 0;
}

.cmp tbody tr:hover td,
.cmp tbody tr:hover th {
  background: var(--surface-2);
}

.cmp th[scope='row'] {
  text-align: left;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

.cmp__product {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
}

.cmp__product img {
  width: 30px;
  height: auto;
}

.cmp__yes {
  color: var(--ok);
}

.cmp__no {
  color: var(--ink-3);
  opacity: 0.5;
}

/* Colour alone never carries the verdict: the cell also has an icon, and the
   screen-reader text spells it out. */
.cmp td .icon {
  width: 1.15rem;
  height: 1.15rem;
}

/* ── Roadmap ─────────────────────────────────────────────────────────────── */

.roadmap {
  position: relative;
  display: grid;
  gap: var(--s6);
  padding-left: 2.25rem;
}

.roadmap::before {
  content: '';
  position: absolute;
  left: 13px;
  top: 8px;
  bottom: 8px;
  width: 2px;
  background: linear-gradient(180deg, var(--glaze), var(--rule) 65%);
  border-radius: 2px;
}

.milestone {
  position: relative;
  padding: var(--s5) var(--s6);
  border-radius: var(--r-xl);
  border: 1px solid var(--rule);
  background: var(--surface);
}

.milestone::before {
  content: '';
  position: absolute;
  left: -2.25rem;
  top: 1.6rem;
  width: 12px;
  height: 12px;
  margin-left: 8px;
  border-radius: var(--r-full);
  background: var(--surface);
  border: 3px solid var(--rule-strong);
}

.milestone[data-state='done']::before {
  border-color: var(--ok);
  background: var(--ok);
}

.milestone[data-state='active']::before {
  border-color: var(--glaze);
  background: var(--surface);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--glaze) 22%, transparent);
}

.milestone__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
}

.milestone__state {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.6rem;
  border-radius: var(--r-full);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid;
}

.milestone[data-state='done'] .milestone__state {
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 35%, transparent);
  background: color-mix(in srgb, var(--ok) 10%, transparent);
}

.milestone[data-state='active'] .milestone__state {
  color: var(--glaze);
  border-color: color-mix(in srgb, var(--glaze) 35%, transparent);
  background: var(--glaze-soft);
}

.milestone[data-state='planned'] .milestone__state {
  color: var(--ink-3);
  border-color: var(--rule);
  background: var(--surface-2);
}

.milestone__when {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.milestone__t {
  margin-top: var(--s3);
  font-size: var(--t-xl);
  font-weight: 650;
  letter-spacing: -0.005em;
}

.milestone__d {
  margin-top: var(--s2);
  color: var(--ink-2);
  font-size: var(--t-sm);
  line-height: 1.65;
}

/* ── Glossary ────────────────────────────────────────────────────────────── */

.glossary {
  display: grid;
  gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

.term {
  min-height: var(--touch);
  padding: 0.7rem 0.9rem;
  border-radius: var(--r);
  border: 1px solid var(--rule);
  background: var(--surface);
  font-size: var(--t-sm);
  font-weight: 600;
  text-align: center;
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease),
    background-color var(--fast) var(--ease);
}

.term:hover {
  border-color: var(--glaze);
  color: var(--glaze);
}

.term[aria-pressed='true'] {
  border-color: var(--glaze);
  background: var(--glaze-soft);
  color: var(--glaze);
}

.definition {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s4);
  align-items: start;
  margin-top: var(--s6);
  padding: var(--s6);
  border-radius: var(--r-xl);
  border: 1px solid var(--rule);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.definition[hidden] {
  display: none;
}

.definition img {
  width: 56px;
  height: auto;
}

.definition__t {
  font-family: var(--font-display);
  font-size: var(--t-xl);
  font-weight: 700;
}

.definition__d {
  margin-top: var(--s2);
  color: var(--ink-2);
  line-height: 1.7;
}

/* ── The assistant ───────────────────────────────────────────────────────
   Three things were wrong with the first version. The panel was a murky
   near-black box on a near-black ground; the log was a 21rem void with one
   sentence at the top of it; and an assistant on a page whose whole subject is
   five products answered every question in flat grey prose, never once showing
   one. All three are addressed here — the last by rendering the platforms an
   answer names as real cards under it (see agent.js).
   ────────────────────────────────────────────────────────────────────────── */

.agent {
  position: relative;
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  align-items: start;
}

@media (min-width: 980px) {
  .agent {
    grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
  }
}

.agent__intro {
  position: relative;
}

.agent__avatar {
  position: relative;
  width: min(190px, 46vw);
  margin-bottom: var(--s5);
}

.agent__avatar img {
  width: 100%;
  height: auto;
  filter: drop-shadow(0 18px 26px rgba(0, 0, 0, 0.5));
}

/* The pose is the status indicator. A thinking cub while a request is in
   flight and an apologetic one on failure say more, faster, than a spinner. */
.agent__avatar img[hidden] {
  display: none;
}

.agent__halo {
  position: absolute;
  left: 50%;
  bottom: 4%;
  width: 84%;
  aspect-ratio: 2 / 1;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(79, 196, 204, 0.42), transparent);
  filter: blur(8px);
  z-index: -1;
}

.agent__pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: var(--s5);
}

/* The panel is LIGHTER than the ground, not darker. A dark box on a dark
   ground is a hole; a lifted surface is a device. */
.agent__panel {
  display: flex;
  flex-direction: column;
  border-radius: var(--r-2xl);
  border: 1px solid rgba(255, 255, 255, 0.13);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.075), rgba(255, 255, 255, 0.035));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09), 0 24px 60px -30px rgba(0, 0, 0, 0.9);
  overflow: hidden;
  min-height: 25rem;
}

.agent__bar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 0.8rem var(--s5);
  border-bottom: 1px solid var(--ground-rule);
  background: rgba(255, 255, 255, 0.04);
}

.agent__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--t-sm);
  font-weight: 650;
  color: var(--on-ground);
}

.agent__brand img {
  width: 26px;
  height: auto;
}

.agent__status {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-left: auto;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--on-ground-2);
}

.agent__dot {
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background: #55d38b;
  flex: none;
  box-shadow: 0 0 0 3px rgba(85, 211, 139, 0.18);
}

.agent__reset {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 32px;
  padding-inline: 0.6rem;
  border-radius: var(--r-full);
  border: 1px solid var(--ground-rule);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--on-ground-2);
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease);
}

.agent__reset:hover {
  background: rgba(255, 255, 255, 0.1);
  color: var(--on-ground);
}

.agent__log {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s5);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  max-height: 24rem;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}

.msg {
  display: flex;
  gap: 0.7rem;
  max-width: 100%;
  animation: msg-in var(--dur) var(--ease);
}

.msg__avatar {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: var(--r-full);
  display: grid;
  place-items: center;
  overflow: hidden;
  background: radial-gradient(circle at 50% 118%, rgba(91, 131, 214, 0.9), rgba(30, 55, 102, 0.9));
  border: 1px solid rgba(255, 255, 255, 0.16);
}

.msg__avatar img {
  width: 30px;
  height: auto;
  transform: translateY(2px);
}

.msg__col {
  min-width: 0;
  flex: 1;
}

.msg__bubble {
  padding: 0.75rem 1rem;
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.09);
  font-size: var(--t-sm);
  line-height: 1.65;
  color: var(--on-ground);
  min-width: 0;
  overflow-wrap: anywhere;
}

.msg--user {
  flex-direction: row-reverse;
}

.msg--user .msg__col {
  display: flex;
  justify-content: flex-end;
}

.msg--user .msg__avatar {
  background: rgba(255, 255, 255, 0.12);
  color: var(--on-ground-2);
  font-size: var(--t-xs);
  font-weight: 700;
}

.msg--user .msg__bubble {
  background: linear-gradient(160deg, rgba(58, 96, 176, 0.85), rgba(35, 62, 118, 0.85));
  border-color: rgba(255, 255, 255, 0.18);
  max-width: 85%;
}

.msg--error .msg__bubble {
  background: rgba(179, 38, 30, 0.18);
  border-color: rgba(242, 135, 126, 0.42);
}

.msg__bubble p + p {
  margin-top: 0.55rem;
}

.msg__bubble strong {
  font-weight: 650;
  color: #fff;
}

.msg__bubble ul {
  margin-top: 0.45rem;
  display: grid;
  gap: 0.3rem;
}

.msg__bubble li {
  padding-left: 0.9rem;
  position: relative;
}

.msg__bubble li::before {
  content: '';
  position: absolute;
  left: 0.15rem;
  top: 0.62em;
  width: 4px;
  height: 4px;
  border-radius: var(--r-full);
  background: #4fc4cc;
}

.msg__bubble a {
  color: #7fd9e0;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── Answers that show the product ───────────────────────────────────────
   When an answer names a platform, that platform appears under it as a real
   card: its mascot, on its hue, linking to it. The assistant does not choose
   this markup — the client matches names it already knows against the text, so
   nothing the model writes can put an element on the page. */
.msg__products {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.msg__product {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.75rem 0.4rem 0.4rem;
  border-radius: var(--r-full);
  border: 1px solid color-mix(in srgb, var(--hue) 42%, transparent);
  background: color-mix(in srgb, var(--hue) 14%, transparent);
  font-size: var(--t-xs);
  font-weight: 650;
  color: var(--on-ground);
  transition: background-color var(--fast) var(--ease), border-color var(--fast) var(--ease),
    transform var(--fast) var(--ease);
  animation: role-item 380ms var(--ease) both;
  animation-delay: calc(var(--i, 0) * 60ms);
}

.msg__product:hover {
  transform: translateY(-2px);
  background: color-mix(in srgb, var(--hue) 24%, transparent);
  border-color: var(--hue);
}

.msg__productcub {
  width: auto;
  height: 30px;
  flex: none;
  object-fit: contain;
}

.msg__product .icon {
  width: 0.85rem;
  height: 0.85rem;
  color: var(--hue);
}

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

@keyframes msg-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

.typing {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  height: 1.2em;
}

.typing span {
  width: 6px;
  height: 6px;
  border-radius: var(--r-full);
  background: var(--on-ground-3);
  animation: blink 1.2s infinite;
}

.typing span:nth-child(2) {
  animation-delay: 0.18s;
}

.typing span:nth-child(3) {
  animation-delay: 0.36s;
}

@keyframes blink {
  0%,
  60%,
  100% {
    opacity: 0.25;
  }
  30% {
    opacity: 1;
  }
}

.agent__suggestions {
  display: flex;
  gap: 0.45rem;
  padding: 0 var(--s5) var(--s3);
  overflow-x: auto;
  scrollbar-width: none;
  /* Fade the trailing edge, so a row that continues past the panel reads as
     scrollable rather than as a chip that got cut in half. */
  -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent);
  mask-image: linear-gradient(90deg, #000 88%, transparent);
}

.agent__suggestions::-webkit-scrollbar {
  display: none;
}

.suggestion {
  flex: none;
  min-height: 36px;
  padding: 0.4rem 0.85rem;
  border-radius: var(--r-full);
  border: 1px solid var(--ground-rule);
  background: rgba(255, 255, 255, 0.06);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--on-ground-2);
  white-space: nowrap;
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease),
    border-color var(--fast) var(--ease);
}

.suggestion:hover {
  background: rgba(255, 255, 255, 0.13);
  border-color: rgba(255, 255, 255, 0.3);
  color: var(--on-ground);
}

.agent__form {
  display: flex;
  align-items: flex-end;
  gap: var(--s2);
  padding: var(--s3) var(--s4) var(--s4);
  border-top: 1px solid var(--ground-rule);
  background: rgba(0, 0, 0, 0.16);
}

.agent__input {
  flex: 1;
  min-width: 0;
  min-height: var(--touch);
  max-height: 8rem;
  padding: 0.7rem 1rem;
  border-radius: var(--r-lg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: var(--on-ground);
  /* 16px minimum, or iOS Safari zooms the page on focus. */
  font-size: 16px;
  line-height: 1.5;
  resize: none;
  overflow-y: auto;
}

.agent__input::placeholder {
  color: var(--on-ground-3);
}

.agent__input:focus {
  outline: none;
  border-color: rgba(79, 196, 204, 0.65);
  background: rgba(255, 255, 255, 0.1);
}

.agent__send {
  display: grid;
  place-items: center;
  width: var(--touch);
  height: var(--touch);
  flex: none;
  border-radius: var(--r-lg);
  background: var(--glaze);
  color: #fff;
  transition: background-color var(--fast) var(--ease), opacity var(--fast) var(--ease);
}

.agent__send:hover:not(:disabled) {
  background: #12a0ac;
}

.agent__send:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.agent__note {
  margin-top: var(--s3);
  font-size: var(--t-xs);
  color: var(--on-ground-2);
  line-height: 1.5;
}

/* ── About ───────────────────────────────────────────────────────────────── */

.about {
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: center;
}

@media (min-width: 900px) {
  .about {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  }
}

.about__body p + p {
  margin-top: var(--s4);
}

.about__body p {
  color: var(--ink-2);
  line-height: 1.75;
  max-width: var(--measure);
}

.about__points {
  display: grid;
  gap: var(--s4);
  margin-top: var(--s6);
}

@media (min-width: 560px) {
  .about__points {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.point {
  display: flex;
  gap: var(--s3);
  align-items: flex-start;
}

.point__icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: var(--r-sm);
  background: var(--indigo-soft);
  color: var(--indigo);
}

.point__t {
  display: block;
  font-weight: 650;
  font-size: var(--t-sm);
}

.point__d {
  display: block;
  font-size: var(--t-sm);
  color: var(--ink-3);
  margin-top: 0.15rem;
  line-height: 1.55;
}

.about__figure {
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  border-radius: var(--r-2xl);
  border: 1px solid var(--rule);
  background: var(--surface);
  overflow: hidden;
}

.about__figure img {
  position: relative;
  width: min(240px, 60vw);
  height: auto;
}

.about__caption {
  position: relative;
  margin-top: var(--s4);
  font-size: var(--t-xs);
  color: var(--ink-3);
  text-align: center;
  line-height: 1.6;
  max-width: 30ch;
}

/* ── Contact ─────────────────────────────────────────────────────────────── */

.contact {
  display: grid;
  gap: var(--s4);
}

@media (min-width: 720px) {
  .contact {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.contact__card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: var(--s6);
  border-radius: var(--r-xl);
  border: 1px solid var(--ground-rule);
  background: rgba(255, 255, 255, 0.04);
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

a.contact__card:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.24);
}

.contact__icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r);
  background: rgba(79, 196, 204, 0.14);
  color: #4fc4cc;
  margin-bottom: var(--s3);
}

.contact__l {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--on-ground-3);
}

.contact__v {
  font-size: var(--t-lg);
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* ── Footer ──────────────────────────────────────────────────────────────
   The version this replaces ended in a copyright line beside three grey pills
   reading "5 platforms · 5 languages · Built for TSUL" — three claims already
   made twice higher up the page, set as UI chips, in the one place a reader
   expects an address and a name. This has a shape instead: a brand block that
   carries weight, four real columns, the family closing the argument it
   opened, and a quiet bottom rule.
   ────────────────────────────────────────────────────────────────────────── */

.footer {
  padding-top: clamp(3rem, 6vw, 4.5rem);
  padding-bottom: max(var(--s8), env(safe-area-inset-bottom));
  border-top: 1px solid var(--ground-rule);
}

/* Brand block and columns are one grid, so the columns line up with the
   description above them rather than floating beside it. */
.footer__top {
  max-width: 34rem;
  margin-bottom: clamp(2.5rem, 5vw, 3.5rem);
}

.footer__brand {
  display: flex;
  align-items: center;
  gap: var(--s4);
}

/* The mark sits on the house plinth, the same shape the header uses, so the
   page opens and closes on the same object. */
.footer__mark {
  display: grid;
  place-items: end center;
  width: 62px;
  height: 62px;
  flex: none;
  border-radius: var(--r-lg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: radial-gradient(circle at 50% 122%, rgba(91, 131, 214, 0.85), rgba(42, 75, 141, 0.16) 74%);
  overflow: hidden;
}

.footer__mark img {
  width: 58px;
  height: auto;
}

.footer__brandtext {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.footer__brandname {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.35rem;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.footer__desc {
  margin-top: var(--s5);
  font-size: var(--t-sm);
  color: var(--on-ground-2);
  line-height: 1.7;
}

/* The five, as faces. Each on its own hue, at the size of an avatar. */
.footer__family {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: var(--s6);
}

.footer__face {
  display: grid;
  place-items: end center;
  width: var(--touch);
  height: var(--touch);
  border-radius: var(--r);
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--hue) 24%, transparent),
    color-mix(in srgb, var(--hue) 7%, transparent));
  overflow: hidden;
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease),
    background-color var(--dur) var(--ease);
}

.footer__face:hover,
.footer__face:focus-visible {
  transform: translateY(-3px);
  border-color: var(--hue);
}

.footer__facecub {
  width: auto;
  height: 34px;
  object-fit: contain;
  transition: transform var(--dur) var(--ease);
}

.footer__face:hover .footer__facecub {
  transform: translateY(-2px) scale(1.06);
}

.footer__grid {
  display: grid;
  gap: clamp(2rem, 4vw, 2.5rem) clamp(1.5rem, 3vw, 3rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding-top: clamp(2rem, 4vw, 2.75rem);
  border-top: 1px solid var(--ground-rule);
}

@media (min-width: 760px) {
  .footer__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.footer__h {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--on-ground-3);
  margin-bottom: var(--s4);
}

.footer__list {
  display: grid;
  gap: 0.1rem;
}

.footer__link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 34px;
  font-size: var(--t-sm);
  color: var(--on-ground-2);
  transition: color var(--fast) var(--ease), transform var(--fast) var(--ease);
}

.footer__link:hover {
  color: var(--on-ground);
  transform: translateX(2px);
}

.footer__link .icon {
  width: 0.85rem;
  height: 0.85rem;
  opacity: 0.45;
  transition: opacity var(--fast) var(--ease);
}

.footer__link:hover .icon {
  opacity: 0.9;
}

/* The language row repeats the sheet's compact chips. A visitor who scrolled
   past the header should not have to scroll back to change language. */
.footer__langs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.footer__langs .lang-chip {
  min-height: 36px;
  padding: 0.4rem 0.6rem;
  border-color: var(--ground-rule);
  background: rgba(255, 255, 255, 0.05);
  color: var(--on-ground-2);
  font-size: var(--t-xs);
}

.footer__langs .lang-chip:hover {
  background: rgba(255, 255, 255, 0.1);
  color: var(--on-ground);
}

.footer__langs .lang-chip[aria-selected='true'] {
  border-color: rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
}

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding-top: var(--s6);
  border-top: 1px solid var(--ground-rule);
  font-size: var(--t-xs);
  color: var(--on-ground-3);
}

.footer__place {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* ── Back to top ─────────────────────────────────────────────────────────── */

.to-top {
  position: fixed;
  right: max(var(--s4), env(safe-area-inset-right));
  bottom: max(var(--s4), env(safe-area-inset-bottom));
  z-index: 90;
  display: grid;
  place-items: center;
  width: var(--touch);
  height: var(--touch);
  border-radius: var(--r-full);
  background: var(--indigo);
  color: #fff;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(10px) scale(0.92);
  pointer-events: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.to-top[data-visible='true'] {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

/* ── Reveal on scroll ────────────────────────────────────────────────────── */

/* Applied by script only when the browser supports IntersectionObserver AND
   the visitor has not asked for reduced motion — so content is never hidden by
   a stylesheet that JavaScript might fail to un-hide. */
[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 520ms var(--ease), transform 520ms var(--ease);
}

[data-reveal='in'] {
  opacity: 1;
  transform: none;
}

/* ── Motion ──────────────────────────────────────────────────────────────── */

/* Every piece of information on this page is in the words, the colour and the
   icons. Motion is decoration throughout, so under a reduced-motion preference
   it all goes and nothing is lost. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .pcard:hover,
  .family__card:hover,
  .role:hover,
  .btn:active {
    transform: none;
  }

  [data-reveal] {
    opacity: 1;
    transform: none;
  }
}

/* ── Print ───────────────────────────────────────────────────────────────── */

@media print {
  .header,
  .to-top,
  .agent__form,
  .agent__suggestions,
  .sheet {
    display: none !important;
  }

  .ground,
  .ground::before {
    background: none !important;
    color: #000 !important;
  }

  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
}
