/* ==========================================================================
   Eaon — built on superset.sh's system
   ==========================================================================

   Fourth pass, and the brief finally has one name on it. The user asked for
   paseo / emdash / telemetry / superset, twice got an average of them, then
   picked paseo, then said "wrong — make it like superset.sh". So: superset,
   specifically, read off its shipped CSS and markup rather than eyeballed.

   WHAT SUPERSET ACTUALLY IS, and how it differs from paseo (the previous pass):

   1. MONO IS THE VOICE, NOT A GARNISH. `font-mono` appears 82 times on its
      home page. Section labels, window chrome, data captions, the
      announcement pill, table headers — all IBM Plex Mono, mostly uppercase
      with wide tracking. paseo has essentially none. This is the single
      biggest difference between the two sites and the thing that makes
      superset read as a tool rather than as a product page.

   2. THE SECTION LABEL IS BRAND-COLOURED AND BIG.
      `text-sm font-mono uppercase tracking-widest text-brand` — 14px, not the
      10–11px a normal eyebrow gets, and set in the brand colour. It is the
      main place the accent appears.

   3. SQUARE. `rounded-[2px]` 36 times, `rounded-sm` 26, `rounded-none` on the
      nav items, and the hero's primary button carries no radius class at all.
      Only the product-window mockups get `rounded-lg` (8px). paseo's 12–16px
      was the opposite end of the same axis.

   4. A NEUTRAL GROUND. `#111` flat — no tint at all, where paseo's is
      green-black. Cards are `#171717`, or `bg-foreground/[0.03]` when they
      need to sit on top of one.

   5. CORNER BRACKETS. Eight tiny brand-coloured slabs pinned to the four
      corners of an element, drawn entirely in `background-image`. It marks one
      phrase of the hero headline like a crop mark on a plate. Reproduced
      verbatim below as `.brackets`.

   6. BIG, EVEN SECTION RHYTHM. `py-24 sm:py-32` (96/128px) per section, and
      `space-y-24 sm:space-y-32` between feature rows inside one. Feature rows
      are a plain two-column grid, `gap-12 lg:gap-16`, `items-center`, sides
      alternating — no cards around them.

   7. A 5%-OPACITY BRAND RADIAL BEHIND EACH MOCKUP, and nothing else:
      `radial-gradient(ellipse 55% 45% at 50% 40%, rgba(232,128,74,0.05),
      transparent 75%)`. Not the glow that was reverted here months ago — two
      orders of magnitude fainter, and it sits behind a window that has its own
      border, so it reads as air rather than as light.

   HELD. Dark is the default and does not follow the OS; light is opt-in. The
   coral stays `#F17455` — and it lands almost exactly between superset's own
   `--brand: #d25611` and `--brand-light: #e8804a`, so it belongs to this
   system without being adjusted. The real app mockups keep their own palette.
   No invented social proof: superset runs "Trusted by builders from" and a
   testimonial wall, and neither is available honestly here.
   ========================================================================== */

:root {
  /* superset's own dark tokens, verbatim where they apply. Flat neutral — the
     warm tint from the paseo pass is gone, because superset has none. */
  /* superset's #111 with one point of red: paseo tints its ground toward its
     brand (green-black under green), superset does not tint at all. One point
     is the whole compromise, and it reads as neutral until the coral sits on
     it. */
  --bg-page: #121111;
  --bg: #121111;
  --bg-band: #151414;

  /* paseo's fills, not superset's. White at 3/5/8% instead of opaque greys,
     so a tile inside a panel inside the page compounds correctly and hover
     can lift a card one step without a fourth token. */
  --card: rgba(255, 255, 255, 0.03);
  --card-2: rgba(255, 255, 255, 0.05);
  --card-3: rgba(255, 255, 255, 0.08);
  --panel: rgba(255, 255, 255, 0.02);

  --surface: var(--card);
  --surface-2: var(--card-2);
  --surface-3: var(--card-3);
  --bg-sunken: var(--bg-band);

  --fg: #fafafa;
  --fg-2: #d4d4d4;
  /* superset ships `--muted-foreground: #a1a1a1` and then overrides it to
     `#bebebe` later in the same sheet. The override is the value in force and
     the only one that clears 4.5:1 on `#171717`, so it is the one taken. */
  --muted: #bebebe;

  --line: rgba(255, 255, 255, 0.1);
  --line-2: rgba(255, 255, 255, 0.15);
  --line-3: rgba(255, 255, 255, 0.26);

  --inverse-bg: #fafafa;
  --inverse-fg: #171717;

  /* Eaon's coral sits between superset's --brand (#d25611) and --brand-light
     (#e8804a), so it needs no adjustment to belong here. */
  --brand: #f17455;
  --brand-2: #ffa07f;
  --brand-dim: rgba(241, 116, 85, 0.14);
  --brand-wash: rgba(241, 116, 85, 0.05);

  --frame: 1280px; /* max-w-7xl */
  --measure: 68ch;

  --s1: 4px;
  --s2: 8px;
  --s3: 16px;
  --s4: 24px;
  --s5: 40px;
  --s6: 64px;
  --s7: 96px;  /* py-24  */
  --s8: 128px; /* sm:py-32 */

  /* The radius rule that makes this a combination rather than an average:
     LABELS are superset's (2px), SURFACES are paseo's (8/12/16px). If you can
     click it or it holds content, it is a surface. If it names something, it
     is a label. Nothing is set to a value neither site uses. */
  /* Measured off onorca.dev, which ships `rounded-md` (6px), `rounded-lg`
     (8px) and `rounded-xl` (12px) and reads noticeably softer than the
     near-square ramp this file carried before. Hard corners plus hairline
     rules everywhere is what made the page feel like a form to fill in rather
     than something a person made. */
  --r-xs: 6px;   /* tags, mono chips, the notice pill */
  --r-sm: 8px;   /* buttons, inputs, anything you press */
  --r: 12px;     /* content boxes, tiles, tables, quote blocks */
  --r-lg: 12px;  /* product windows */

  --sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --display: var(--sans);
  --body-font: var(--sans);
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Stronger than the CSS keywords. --ease-out is for anything responding to
     the user (hover, press, enter); --ease-in-out for things already on
     screen moving from A to B. */
  --ease: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  color-scheme: dark;
}

[data-theme="light"] {
  /* superset's light theme is plain white with an #e5e5e5 rule — no warm
     off-white, no tint. Cards are also white, so on light the border is doing
     100% of the separating. */
  --bg-page: #ffffff;
  --bg: #ffffff;
  --bg-band: #fafafa;

  /* Light inverts the fill rather than lightening it: white-at-3% is
     invisible on white, so the ladder becomes black at the same steps. */
  --card: rgba(10, 10, 10, 0.03);
  --card-2: rgba(10, 10, 10, 0.05);
  --card-3: rgba(10, 10, 10, 0.08);
  --panel: rgba(10, 10, 10, 0.02);

  --fg: #0a0a0a;
  --fg-2: #404040;
  --muted: #616161;

  --line: #e5e5e5;
  --line-2: #d4d4d4;
  --line-3: #a3a3a3;

  --inverse-bg: #171717;
  --inverse-fg: #fafafa;

  /* Coral on white is a 3.1:1 smudge at label sizes, and the section labels
     ARE coral here — `.eyebrow` is 14px brand-coloured text, which makes this
     token load-bearing rather than decorative. `#b8421a` is the first value
     that clears 4.5:1 on the darkest surface it ever lands on (`--card-3`,
     #ebebeb) at 4.6:1, and it reaches 5.5:1 on plain white.
     superset's own `--brand-dark: #9e400a` would also work; this stays closer
     to Eaon's coral. */
  --brand: #ad3e18;
  --brand-2: #9e400a;
  --brand-dim: rgba(173, 62, 24, 0.1);
  --brand-wash: rgba(173, 62, 24, 0.04);

  color-scheme: light;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: 80px;
  background: var(--bg);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--body-font);
  font-size: 1rem;
  line-height: 1.6;
  font-feature-settings: "cv05" 1, "ss03" 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

::selection {
  background: var(--brand);
  color: #fff;
}

/* --------------------------------------------------------------------------
   Frame and rhythm
   -------------------------------------------------------------------------- */

/* The page never scrolls sideways. `clip`, not `hidden`: it cuts off anything
   that strays past the edge without making .frame a scroll container, so the
   sticky header still sticks. A backstop, not the fix: the cause of the last
   sideways scroll was the hero canvas, and that is fixed at its source. */
.frame {
  background: var(--bg);
  min-height: 100vh;
  overflow-x: clip;
}

/* max-w-7xl + px-6 sm:px-8. */
.pad {
  padding-inline: max(clamp(var(--s4), 4vw, var(--s5)), calc((100% - var(--frame)) / 2));
}

/* `py-24 sm:py-32`. Sections carry no border and no background — superset
   separates them with this much space and nothing else. `.section--band` and
   `.section--sunken` stay as no-ops so eight HTML files need no edit. */
/* HALF the gap, per section.

   superset's rhythm is `py-24 sm:py-32` on ONE section that then holds several
   feature rows at `space-y-24 sm:space-y-32` — so the distance between two
   consecutive rows is 96/128px total. Eaon's page is many sibling `<section>`
   elements instead, so giving each the full 96/128px of padding stacks two of
   them and doubles the gap to 256px. Each section carries half. */
.section {
  padding-block: var(--s5);
  position: relative;
}

@media (min-width: 640px) {
  .section {
    padding-block: var(--s6);
  }
}

.section--tight {
  padding-block: var(--s6);
}

.section--sunken,
.section--band {
  background: transparent;
}

/* --------------------------------------------------------------------------
   The ruled grid
   -------------------------------------------------------------------------- */

/* All four reference sites organise the page with rules the eye can actually
   see, and that — not their palettes — is what makes them read as the work of
   a company rather than a stack of floating cards. emdash is the clearest
   about it: two vertical hairlines run the full height of the document, and
   every section is a stripe between two horizontal ones. Content sits IN the
   grid instead of on top of it.

   The mechanism is two classes. A `.band` is a full-bleed horizontal stripe
   carrying the horizontal rule. The `.rail` inside it is the centred column
   carrying the two vertical ones. Every band holds the same rail, so the
   vertical lines meet across band boundaries and read as continuous.

   This replaces the panel system on the homepage. Panels gave every section
   the same rounded translucent shell, and when every block is a card nothing
   is primary. Rules cost no fill and still group. */

.band,
.site-head,
.site-foot {
  padding-inline: clamp(0px, 3vw, 56px);
}

/* No rule by default, and no side rails.

   The first cut of this system drew a hairline under every band and two more
   down the full height of the page, which is emdash's structure and it does
   make a page look deliberate. It also puts a line on screen every eighty
   pixels. onorca.dev gets the same sense of order out of alignment and space
   alone: its sections carry no borders at all, and what holds the page
   together is that everything starts on the same left edge and every gap is
   the same size.

   So the rail stays (it is still what everything aligns to) and its border
   goes. A rule now has to be asked for, with `.band--ruled`, and the page
   spends about three of them rather than one per section. */
.rail {
  width: 100%;
  max-width: var(--frame);
  margin-inline: auto;
}

.band--ruled {
  border-bottom: 1px solid var(--line);
}

/* The rail has no border of its own any more, so nothing needs holding off
   it, and content can start flush at the alignment edge. The class is kept
   because all eight pages use it as their content wrapper. */
.rail > .gut {
  padding-inline: 0;
}

/* Vertical size lives on the band, not on the section, so two neighbours
   cannot stack their padding into a 256px canyon the way sibling sections
   did. --s5/--s6 is 40/64px of air above and below the content. */
/* onorca runs `py-20`/`py-28`, so 80px climbing to 112px. That is roughly
   double what this file used, and the extra air is doing the job the removed
   hairlines used to do: it is what tells you one section has ended.

   It sits on the rail rather than on `.band__body`, because the homepage's
   bands hold their content directly while the other seven pages wrap theirs in
   a body div. One rule covers both, and nothing stacks two paddings.

   Each band carries HALF the gap, because two neighbours both contribute: at
   the full 112px the distance between two sections came out at 224px and the
   page read as empty rather than as calm. 40-72px each puts consecutive
   sections 80-144px apart, which is where onorca's actually land. */
.band > .rail {
  padding-block: clamp(var(--s5), 4.5vw, 72px);
}

.band__body {
  padding-block: 0;
}

/* A band whose content is entirely ruled cells needs no block padding: the
   cells' own padding is the air, and the rules should meet the band edges. */
.band__body--flush {
  padding-block: 0;
}

/* The band label: emdash's bracketed mono tag, set in its own ruled strip
   across the top of the band rather than floating above a headline. It is a
   divider that happens to carry a word, which is why it can appear more often
   than a free-standing eyebrow without turning into a tic. Rationed anyway —
   only the bands that genuinely change subject get one. */
/* A plain word, not a ruled strip.

   This began as emdash's bracketed tag sitting in its own bordered band across
   the top of a section. Three of them on one page is three more horizontal
   rules and six more bracket glyphs, and they were a large part of why the
   page felt like machinery. The label keeps its job (naming the section in the
   brand colour) and loses the box it came in. */
.band__label {
  max-width: none;
  margin: 0 0 var(--s3);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand);
}

/* The band headline block. One focused message, stacked: headline, then body
   under it at a readable measure. Not the left-headline/right-floating-
   paragraph split — that pattern leaves the paragraph aligned to nothing. */
.band__head {
  padding-block: 0 var(--s6);
}

/* 20ch was set when the band head sat in a padded cell inside a ruled grid and
   needed to stay clear of the column beside it. In the open layout it just
   forces a break: "One picker. Every major model." fits on one line at 1280px
   and was being split after "Every". */
.band__head > h2 {
  max-width: 34ch;
}

.band__head > p {
  margin: var(--s3) 0 0;
  max-width: 58ch;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1.6;
}

/* --------------------------------------------------------------------------
   Ruled cells
   -------------------------------------------------------------------------- */

/* Grouped by space, not by lines.

   These cells used to draw their own hairlines, which gave the page a visible
   grid but also meant a three-column row put four more rules on screen. onorca
   groups the same kind of content with nothing but a 32-48px gap and no border
   at all, and it reads as calmer without reading as looser, because the
   columns still line up.

   Dropping the lines also drops a whole class of problem: a grid no longer has
   to care whether its item count divides by its column count, because a
   partial last row is just two blocks of text with space after them. */
.cells {
  display: grid;
  grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
  gap: var(--s5) var(--s6);
}

.cells--3 {
  --cols: 3;
}

.cell {
  min-width: 0;
}

/* A cell that leads with a visual: the mockup sits at the top, the words
   underneath, and the tag link pins to the bottom so a row of cells with
   different amounts of copy still ends on a straight line. */
.cell--feature {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.cell--feature .cell__art {
  margin: calc(clamp(22px, 2.6vw, 34px) * -1) calc(clamp(22px, 2.6vw, 34px) * -1) 0;
  padding: clamp(22px, 2.6vw, 34px);
  border-bottom: 1px solid var(--line);
  background: var(--bg-band);
  overflow: hidden;
}

.cell--feature h3 {
  margin: 0;
}

.cell--feature p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9375rem;
  line-height: 1.6;
}

.cell--feature .cell__tag {
  margin-top: auto;
  padding-top: var(--s3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--brand);
  text-decoration: none;
}

.cell--feature .cell__tag::before {
  content: "[ " attr(data-tag) " ]";
}

.cell--feature .cell__tag::after {
  content: "→";
  color: var(--fg-2);
  transition: transform 180ms var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .cell--feature .cell__tag:hover::after {
    transform: translateX(4px);
  }
}

/* A caption introducing a cell group, set on the same rule as the cells so it
   reads as their header rather than as a stray line of body text. */
.cells__cap {
  margin: var(--s6) 0 var(--s4);
  max-width: none;
  padding: 0;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.cell > h3 {
  margin: 0 0 var(--s2);
}

.cell > p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9375rem;
  line-height: 1.6;
}

/* --------------------------------------------------------------------------
   The provider wall
   -------------------------------------------------------------------------- */

/* Every reference runs a wall of marks: superset's customer logos, paseo's
   "Works with your tools", emdash's agent row under a ghosted count. Eaon has
   no customer logos and inventing them would make the site LESS credible, not
   more. What it does have is six providers whose marks already ship in
   vendors/ and which the app genuinely connects to — so the wall is built out
   of those, labelled as what it is. The ghosted figure is emdash's device and
   the number is the real one. */
.wall {
  display: grid;
  grid-template-columns: repeat(var(--wall-cols, 6), minmax(0, 1fr));
  gap: var(--s4);
}

/* A plain row of marks, the way onorca sets its "Used by engineers at" strip:
   no boxes, no dividers, nothing but the logos and the space around them. */
.wall__cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: var(--s4) 12px;
  min-width: 0;
}

.wall__cell img {
  height: 30px;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  /* Every mark in vendors/ is light-on-transparent, so one rule per theme is
     enough: flatten to grey on dark, flip to ink on light. No per-logo hacks. */
  filter: grayscale(1) opacity(0.78);
}

[data-theme="light"] .wall__cell img {
  filter: grayscale(1) invert(1) opacity(0.72);
}

.wall__cell span {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-align: center;
}

/* The ghosted count sitting behind the right edge of the wall's headline. */
/* The outlined numeral that emdash sets behind its "Use any agent" row was
   tried here and removed. It is pure decoration, and on a page being stripped
   back to alignment and space it read as an artifact sitting above the
   headline rather than as emphasis behind it. */

/* --------------------------------------------------------------------------
   Numbered steps and ruled figures
   -------------------------------------------------------------------------- */

/* superset's 01/02/03 columns. The number is a mono caption in the brand, the
   step is an h3, and nothing is boxed. */
.steps {
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
  gap: var(--s5) var(--s6);
}

.step {
  min-width: 0;
}

.step__n {
  display: block;
  margin-bottom: var(--s3);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--brand);
}

.step h3 {
  margin: 0 0 var(--s2);
}

.step p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9375rem;
  line-height: 1.6;
}

/* The figures strip. Four numbers as ruled columns rather than four floating
   tiles — the tiles read as four unrelated objects, the strip reads as one
   statement with four parts. Mono on the number, per the density rule that
   numbers in a dense layout are always mono. */
.figures {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s5) var(--s4);
  text-align: center;
}

/* Equal shares of the row, centred as a group, however many figures it
   holds. */
.figure {
  flex: 1 1 0;
  min-width: 0;
}

.figure__n {
  display: block;
  font-family: var(--mono);
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 400;
  letter-spacing: -0.03em;
  color: var(--fg);
  line-height: 1.1;
}

.figure__l {
  display: block;
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   The split row
   -------------------------------------------------------------------------- */

/* A feature and its evidence, side by side, divided by the same hairline as
   everything else. The asymmetry is deliberate: an even 1fr 1fr split reads as
   a comparison, and these rows are not comparisons — one side argues, the
   other shows. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--s6) clamp(var(--s5), 5vw, 88px);
  align-items: center;
}

/* The copy column is always first in the markup, so a screen reader and the
   keyboard always meet the sentence before the picture it explains. A flipped
   row only moves where the two are painted: `order` sends the copy to the
   second track, and the track ratio swaps with it so the picture keeps the
   wide side rather than inheriting the narrow one. */
.split--flip {
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
}

.split--flip .split__say {
  order: 2;
}

.split > * {
  min-width: 0;
}

.split__say {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s3);
}

.split__say > h2 {
  margin: 0;
}

.split__say > p {
  margin: 0;
  color: var(--muted);
  line-height: 1.65;
  max-width: 46ch;
}

/* No fill. An earlier pass tinted this column to `--bg-band`, but the mockup
   inside already has its own dark ground and its own border, so the tint added
   a third near-identical dark and the whole row went muddy. The hairline
   between the two columns is the separation; the column needs nothing else. */
.split__show {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* --------------------------------------------------------------------------
   The question band
   -------------------------------------------------------------------------- */

.faqgrid {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: var(--s5) clamp(var(--s5), 5vw, 88px);
}

.faqgrid > * {
  min-width: 0;
}

.faqgrid__head h2 {
  margin: var(--s3) 0 0;
  max-width: 12ch;
}

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

/* --------------------------------------------------------------------------
   Ruled rows
   -------------------------------------------------------------------------- */

/* For lists that are genuinely rows of the same kind of thing — the FAQ, the
   platform table. One hairline between rows, none above the first and none
   below the last, because the band's own rules already close the group.

   Named `.ruled`, not `.rows`: features.html has owned a `.rows` grid since
   long before this system existed, and a bare `.rows > * + *` here put a line
   across the top of every card in it. */
.ruled > * + * {
  border-top: 1px solid var(--line);
}

@media (max-width: 900px) {
  .split,
  .split--flip {
    grid-template-columns: minmax(0, 1fr);
  }

  .cells,
  .cells--3,
  .steps {
    --cols: 2;
  }

  .wall {
    --wall-cols: 3;
  }
}

@media (max-width: 620px) {
  .cells,
  .cells--3,
  .steps {
    --cols: 1;
  }

  .figures {
    flex-direction: column;
  }

  .wall {
    --wall-cols: 2;
  }

}

/* --------------------------------------------------------------------------
   Band variants and the pieces that sit in them
   -------------------------------------------------------------------------- */

/* The product band. Generous above and below because the window is the page's
   one hero object, and it should not look wedged between two rules. */
/* The product window's band carries the rail padding like any other, so the
   stage itself adds nothing. */
.band--product .stage {
  margin: 0;
  padding: 0;
}

/* A product window given the full rail rather than a column of a split. Both
   app recreations use it: at half width ADE's six panes came out ~140px each
   and the agent output clipped mid-word. */
.stage--wide {
  margin: 0;
}


/* The qualifying line under a product window. ADE needs an agent CLI already
   installed, which is the one thing a visitor has to know before downloading
   it, so it sits with the window rather than three pages away. */
.stage__note {
  margin: var(--s4) 0 0;
  max-width: none;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--muted);
}


/* The hero and the window it introduces are one moment, so the gap between
   them is a gap, not a section break. */
.hero + .band--product > .rail {
  padding-top: 0;
}

/* Inside a band, vertical air is the band's job, so the stage stops carrying
   its own top margin. */
.band .split .stage,
.band .band__body .stage {
  margin: 0;
}

/* The shape budget, stated once. Buttons and product windows take 8px;
   labels, tags and the notice pill take 2px; structure takes none at all.
   Two radii and a rule for which gets which, rather than four radii applied
   by feel. The 16px panel radius went with the panels. */
.stage .appwindow,
.stage .adewin,
.stage .term,
.stage .install {
  border-radius: var(--r-sm);
}

/* The closing call to action. Centred deliberately and only here: it is the
   one block on the page with nothing to align to and nothing following it. */
/* Left-aligned like everything above it. Centring the last block was the
   instinct, but on a page this rigorously ranged-left one centred section
   reads as a mistake rather than as a full stop. The rule above it and the
   footer below already close the page. */
.close {
  padding-block: var(--s6);
  text-align: left;
}

.close h2 {
  margin: 0;
  max-width: 18ch;
}

.close p {
  margin: var(--s4) 0 0;
  max-width: 52ch;
  color: var(--muted);
  line-height: 1.6;
}

.close .btn-row {
  margin-top: var(--s5);
  justify-content: flex-start;
}

/* The fact list inside a split. Mono, ruled, no fill — the same object as the
   figures strip, turned on its side. */
.split__say .split__facts {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The install box sits in a `.split__show`, which already paints the sunken
   ground and the padding, so the box itself only needs its border. */
.split__show .install {
  margin: 0;
}

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

/* `font-medium tracking-tight leading-[1.1]`. Note leading 1.1, not the 0.98
   of the paseo pass — superset's headlines breathe more between lines and set
   `word-spacing: 0.15em` on the h1, which is unusual and is part of why its
   hero reads as spaced-out and deliberate rather than tight and punchy. */
h1,
h2,
h3,
h4 {
  margin: 0;
  font-family: var(--display);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.025em;
  text-wrap: balance;
}

.hero h1 {
  line-height: 1.12;
}

/* superset sets `word-spacing: 0.15em` on its h1 and it is a real part of why
   that page reads as deliberate. Copied here it fought the calmer direction:
   spaced-out words are a mannerism, and the brief for this pass was to take
   mannerisms out. Normal spacing, and the size does the work. */
h1 {
  font-size: clamp(2.375rem, 5vw, 4.125rem);
}

/* 28/40px. The previous pass copied superset's `text-2xl sm:text-3xl` literally
   and the result read as timid, because superset sets those 24/30px headings
   inside an 1100px column while this page runs to 1280 — the same type in a
   wider frame leaves the heading stranded in its own gutter. emdash and
   telemetry both sit nearer 40px at this width, which is where this lands. */
h2 {
  font-size: clamp(1.75rem, 3.2vw, 2.5rem);
}

h3 {
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.015em;
}

h4 {
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.006em;
}

/* superset colours no headline text. The accent lives in the mono section
   labels and in the corner brackets instead. `.hl` is kept as a no-op because
   it sits on a headline in all eight HTML files. */
.hl {
  color: inherit;
}

/* --------------------------------------------------------------------------
   The typing headline
   -------------------------------------------------------------------------- */

/* superset types its h1 out behind a brand-coloured block cursor. The part
   worth copying is not the typing, it is how the layout is held still:

   - the REAL headline stays in the markup and reserves the box;
   - JS sets `visibility: hidden` on it and paints an absolutely positioned
     copy over the top, one character at a time;
   - on completion the overlay is removed and the real headline comes back.

   Because the reserved box is the finished headline, nothing below the h1 ever
   moves, and a two-line headline cannot briefly become one line and shove the
   whole page up. superset does this with an `.invisible` duplicate plus an
   `.sr-only` copy; keeping the real element and hiding it gets the same result
   with one copy instead of three, and leaves correct text for screen readers
   and for select-all without an aria dance.

   No JS, or reduced motion: `.type__real` is simply the headline. Nothing
   here is required for the page to read correctly. */
.type {
  position: relative;
}

.type__out {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* `w-3 h-[0.72em] bg-brand` on superset, sized in em here so it tracks the
   clamped h1 rather than staying 12px at every breakpoint. The negative right
   margin is theirs too: it keeps the cursor from adding width to the line
   while it sits at the end of it. */
.type__cursor {
  display: inline-block;
  width: 0.16em;
  height: 0.72em;
  margin-left: 0.06em;
  margin-right: -0.19em;
  background: var(--brand);
  transform: translateY(0.04em);
  animation: type-blink 1s steps(1, end) infinite;
}

/* While characters are still arriving the cursor holds steady — a caret that
   blinks *and* moves reads as two animations fighting. It only starts blinking
   once the line is finished. */
.type--typing .type__cursor {
  animation: none;
}

@keyframes type-blink {
  0%,
  50% {
    opacity: 1;
  }
  50.01%,
  100% {
    opacity: 0;
  }
}

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

/* The corner brackets, reproduced from superset's own rule: eight slabs in
   `background-image`, two per corner, sized `.15em × 4px` and `4px × .15em`.
   Drawn this way they scale with the font size and cost no extra elements.
   Used on one phrase of the h1 and nowhere else. */
/* superset's corner-bracket device, kept in the sheet because it is a genuinely
   nice trick (eight slabs drawn in `background-image`, sized in em so they track
   the font) but no longer used on the homepage headline. With the rules, the
   ruled labels and the boxed cards all gone, four small coral marks around one
   phrase were the only machinery left on the hero and they read as artifacts
   rather than as emphasis. */
.brackets {
  padding: 0.12em 0.22em;
  background-image: linear-gradient(var(--brand), var(--brand)),
    linear-gradient(var(--brand), var(--brand)), linear-gradient(var(--brand), var(--brand)),
    linear-gradient(var(--brand), var(--brand)), linear-gradient(var(--brand), var(--brand)),
    linear-gradient(var(--brand), var(--brand)), linear-gradient(var(--brand), var(--brand)),
    linear-gradient(var(--brand), var(--brand));
  background-position: 0 0, 0 0, 100% 0, 100% 0, 0 100%, 0 100%, 100% 100%, 100% 100%;
  background-repeat: no-repeat;
  background-size: 0.15em 4px, 4px 0.15em, 0.15em 4px, 4px 0.15em, 0.15em 4px, 4px 0.15em,
    0.15em 4px, 4px 0.15em;
  white-space: nowrap;
}

p {
  margin: 0;
  max-width: var(--measure);
}

.mono {
  font-family: var(--mono);
  font-variant-ligatures: none;
}

a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--line-2);
  transition: border-color 140ms var(--ease), color 140ms var(--ease);
}

a:hover {
  border-bottom-color: var(--brand);
}

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
}

.muted {
  color: var(--muted);
}

/* Visible to screen readers only. The app window's composer has a visual
   placeholder but no visible label, so its <label> uses this. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* THE section label: `text-sm font-mono uppercase tracking-widest text-brand`.
   14px and brand-coloured, which is bigger and louder than an eyebrow usually
   gets — on superset it is the element that opens every section and it does
   the job a coloured headline does elsewhere. */
.eyebrow {
  display: block;
  font-family: var(--mono);
  font-size: 0.875rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand);
  margin: 0 0 var(--s3);
}

.eyebrow--accent {
  color: var(--brand);
}

/* The smaller mono caption, for data labels and table headers:
   `text-[10px] font-medium uppercase tracking-[0.1em] text-muted-foreground/65`. */
.label {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.rule {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--s5) 0;
}

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */

/* `bg-card border border-border rounded-[2px]`, or `bg-foreground/[0.03]` for
   one sitting on top of another surface. Square, flat, no shadow. */
.card {
  display: flex;
  flex-direction: column;
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  transition: border-color 160ms var(--ease-out), background-color 160ms var(--ease-out);
}

/* Hover is gated to a real pointer. On touch, :hover fires on tap and sticks,
   so a card would stay lifted after you pressed it. */
@media (hover: hover) and (pointer: fine) {
  a.card:hover,
  .card--link:hover {
    border-color: var(--line-2);
    background: var(--card-2);
  }
}

/* paseo's section-as-panel: `overflow-hidden rounded-2xl border border-white/10
   bg-white/[0.02]`, a header row divided off by a hairline, a grid inside. It
   is what makes its page read as a set of objects. Used here for Models, How
   it works and the FAQ; feature splits stay open, as superset's are. */
.panel {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
  overflow: hidden;
}

/* Two columns inside a panel body, for paired groups like hosted/local. */
.panel__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5);
}

@media (max-width: 720px) {
  .panel__cols {
    grid-template-columns: minmax(0, 1fr);
  }
}

.panel__head {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  padding: var(--s4);
  border-bottom: 1px solid var(--line);
}

@media (min-width: 640px) {
  .panel__head {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }

  .panel__head,
  .panel__body {
    padding: var(--s5);
  }
}

.panel__head h2 {
  font-size: clamp(1.25rem, 2vw, 1.5rem);
}

.panel__head p {
  margin: var(--s2) 0 0;
  color: var(--muted);
  font-size: 0.9375rem;
  max-width: 44ch;
}

@media (min-width: 640px) {
  .panel__head > p {
    margin-top: 0;
    text-align: right;
  }
}

.panel__body {
  padding: var(--s4);
}

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */

/* `sticky top-0 z-50 border-b bg-background/80 backdrop-blur-sm`, `h-16`. */
.site-head {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}

@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .site-head {
    background: var(--bg);
  }
}

.site-head__inner {
  display: flex;
  align-items: center;
  gap: var(--s4);
  height: 64px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
  border-bottom: 0;
  margin-right: auto;
}

.brand__mark {
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: var(--r-xs);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

@media (max-width: 880px) {
  .site-nav a[data-secondary] {
    display: none;
  }
}

@media (max-width: 560px) {
  .site-head__inner {
    flex-wrap: wrap;
    height: auto;
    padding-block: var(--s3);
    row-gap: var(--s3);
  }

  .site-nav {
    order: 3;
    flex-basis: 100%;
    gap: var(--s2);
    margin-inline: -12px;
  }
}

/* `h-8 rounded-none bg-transparent px-4 text-[13px] tracking-[0.01em]
   font-normal text-muted-foreground hover:text-foreground`. Sans, not mono —
   superset's nav is one of the few places it is not. */
.site-nav a {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding-inline: 12px;
  font-family: var(--body-font);
  font-size: 0.8125rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--muted);
  border-bottom: 0;
  border-radius: 0;
  transition: color 140ms var(--ease);
}

.site-nav a:hover {
  color: var(--fg);
}

.site-nav a[aria-current="page"] {
  color: var(--fg);
}

.theme-toggle {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 140ms var(--ease), color 140ms var(--ease);
}

.theme-toggle:hover {
  color: var(--fg);
  border-color: var(--line-2);
}

.theme-toggle svg {
  width: 15px;
  height: 15px;
}

.theme-toggle .icon-sun {
  display: block;
}

.theme-toggle .icon-moon {
  display: none;
}

[data-theme="light"] .theme-toggle .icon-sun {
  display: none;
}

[data-theme="light"] .theme-toggle .icon-moon {
  display: block;
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

/* `bg-foreground text-background px-6 py-3 text-base font-normal
   hover:bg-brand hover:text-white` — and no radius class at all, so the
   primary is a true square block. The hover takes the brand, which is
   superset's move and the reason the accent needs to appear on a fill
   somewhere. */
/* paseo's geometry (`rounded-lg`, 8px), superset's weight and hover. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: 11px 20px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--fg);
  font-family: var(--body-font);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: -0.006em;
  line-height: 1.3;
  white-space: nowrap;
  cursor: pointer;
  touch-action: manipulation;
  transition: background-color 140ms var(--ease-out), border-color 140ms var(--ease-out),
    color 140ms var(--ease-out), transform 160ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    border-color: var(--line-3);
    background: var(--card-2);
  }
}

/* Press feedback. A 3% scale-down with the fast curve says the interface
   heard the click before anything else happens. Layout bounds do not change,
   so neighbouring buttons do not shift. */
.btn:active {
  transform: scale(0.97);
}

.btn--solid {
  background: var(--inverse-bg);
  border-color: var(--inverse-bg);
  color: var(--inverse-fg);
}

/* The hover label cannot be one colour in both themes, because the fill is not
   one colour in both themes.

   Dark: the fill is `#f17455`, and superset's own `hover:text-white` gives
   2.9:1 on it — under the 3:1 floor even for large text. Near-black is 6.7:1.
   Light: the fill is the darkened `#b8421a`, where near-black collapses to
   3.8:1 and white is 5.5:1 — so light takes superset's white after all.
   One rule each, rather than one token that is wrong in one theme. */
@media (hover: hover) and (pointer: fine) {
  .btn--solid:hover {
    background: var(--brand);
    border-color: var(--brand);
    color: #180c07;
  }

  [data-theme="light"] .btn--solid:hover {
    color: #ffffff;
  }
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s5);
}

/* --------------------------------------------------------------------------
   Hero — centred, mono pill, bracketed phrase

   superset's shape: an announcement pill in mono with a trailing arrow, a big
   headline at leading 1.1 with `word-spacing: 0.15em` and one phrase inside
   corner brackets, a LIGHT-weight subheadline, then two square buttons.
   -------------------------------------------------------------------------- */

/* Left-aligned, occupying eight of twelve columns, with the right third left
   deliberately empty. A centred hero is the safe choice and it is why the page
   read as a blog: centred type in a 1280px frame has nothing to align to, so
   every section below it starts its own private alignment. Anchoring the
   headline to the rail gives the whole document one left edge.

   The empty right third is not dead space. The ambient canvas field runs the
   full width of the band, so it shows through there and becomes the hero's
   right-hand visual, which is the shape emdash uses. */
.hero {
  position: relative;
  padding-block: 0;
  text-align: left;
}

.hero__copy {
  max-width: 60ch;
}

@media (min-width: 900px) {
  .hero__copy {
    width: 66.66%;
    max-width: 44rem;
  }
}

/* The ambient field behind the hero — superset's device. A sparse grid of
   faint mono characters, each fading in and out on its own clock, drawn on
   one canvas by main.js.

   Canvas rather than DOM because the grid is ~1,500 cells and a few hundred
   of them are mid-fade at any moment; that many elements transitioning
   opacity would be a style-recalc storm, while one canvas is a single
   composited layer. `inset: 0` inside the hero's own positioning context
   means it cannot overhang and cause horizontal scroll. `pointer-events:
   none` keeps it out of hit-testing; `aria-hidden` is set by the script.

   The colour is read off this element by the script, so the field follows
   the theme without a second colour token: near-white ink on dark, near-black
   on light, both at very low alpha. The script also fades the field toward
   the centre so it never sits under the headline at full strength — that
   falloff lives in the draw loop, not in a CSS mask, so it needs no
   `mask-composite` compatibility dance. */
/* Flush with the hero, and it has to stay that way: main.js sizes the canvas
   to the hero's own width in pixels. An inset here used to push its left edge
   in by the page gutter, and with the width fixed the right edge went past the
   window by the same amount, which gave the whole page a horizontal scroll a
   trackpad swipe would pan. */
.codefield {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  color: var(--fg);
}

/* Everything the hero actually says sits above the field. */
.hero__intro,
.hero .stage {
  position: relative;
  z-index: 1;
}

@media (min-width: 640px) {
  /* The rail owns vertical rhythm now; the hero adds none of its own at any
     width. Left in, this put the old 128px on top of the rail's 72 and opened
     a screen of nothing above the announcement pill. */
  .hero {
    padding-block: 0;
  }
}

.hero__intro {
  display: block;
}

.hero__copy {
  max-width: none;
}

.hero h1 {
  max-width: 24ch;
}

/* `text-base sm:text-xl font-light text-muted-foreground max-w-4xl mx-auto`.
   Weight 300 is the part that matters — superset's subheadline is genuinely
   LIGHT, which is what stops a 20px centred paragraph under a 72px headline
   from reading as a second headline. */
.hero__copy p,
.hero p {
  margin: var(--s4) 0 0;
  color: var(--muted);
  max-width: 48ch;
  font-size: 1.125rem;
  font-weight: 300;
  line-height: 1.55;
}

.hero .btn-row {
  margin-top: var(--s5);
  justify-content: flex-start;
}

/* `rounded-[2px] border border-border bg-background/80 px-3 py-1.5 text-xs
   font-mono text-muted-foreground`, with a `→` that slides on hover. */
.hero .notice {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  width: auto;
  max-width: 100%;
  margin: 0 0 var(--s5);
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--muted);
  text-align: left;
  transition: border-color 140ms var(--ease), color 140ms var(--ease);
}

.hero .notice:hover {
  border-color: var(--line-2);
  color: var(--fg);
}

/* The pill is an <a> now (superset's whole chip is the link target), so it has
   to opt out of the global link underline explicitly. */
a.notice {
  border-bottom: 1px solid var(--line);
}

a.notice:hover {
  border-bottom-color: var(--line-2);
}

.hero .notice a {
  color: inherit;
  border-bottom: 0;
  white-space: nowrap;
}

/* The arrow is generated rather than authored into eight files, and it is the
   only thing on the pill that moves. */
.hero .notice::after {
  content: "\2192";
  flex: none;
  transition: transform 160ms var(--ease);
}

.hero .notice:hover::after {
  transform: translateX(2px);
}

.tag {
  flex: none;
  padding: 2px 7px;
  border-radius: var(--r-xs);
  background: var(--brand-dim);
  color: var(--brand);
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Facts under the actions, as mono captions divided by thin rules rather than
   as pills — superset's own quiet way of stating specs. */
.hero__aside {
  display: contents;
}

.hero__spec {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0;
  margin: var(--s6) 0 0;
  padding: 0;
}

.hero__spec div {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 0 var(--s3);
  border-left: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.6875rem;
}

.hero__spec div:first-child {
  border-left: 0;
}

.hero__spec dt {
  color: var(--muted);
  opacity: 0.7;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.hero__spec dd {
  margin: 0;
  color: var(--fg-2);
}

.hero__spec .js-dl {
  display: none;
}

.hero__spec .js-dl.is-on {
  display: inline-flex;
}

@media (max-width: 560px) {
  /* Vertical rules between wrapped rows read as stray marks, so the divider
     goes and the gap does the separating instead. */
  .hero__spec {
    gap: var(--s2) var(--s3);
  }

  .hero__spec div {
    border-left: 0;
    padding: 0;
  }
}

/* The hand-drawn ADE window that lived here is gone: the homepage now shows
   ADE's own stylesheets and markup in demo/ade/, loaded in a frame. */

/* --------------------------------------------------------------------------
   Install block — tabs over one command
   -------------------------------------------------------------------------- */

.install {
  margin-top: var(--s5);
  max-width: 680px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  overflow: hidden;
}

.install__tabs {
  display: flex;
  border-bottom: 1px solid var(--line);
}

.install__tab {
  padding: 12px 18px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.875rem;
  cursor: pointer;
}

.install__tab:hover {
  color: var(--fg);
}

.install__tab[aria-selected="true"] {
  color: var(--fg);
  border-bottom-color: var(--fg);
}

.install__body {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4);
  font-size: 0.875rem;
  overflow-x: auto;
}

.install__body code {
  font-family: var(--mono);
  white-space: nowrap;
  color: var(--muted);
}

/* The part of the command that matters, lifted out of the muted surround. */
.install__body code b {
  color: var(--fg);
  font-weight: 400;
}

.copy {
  flex: none;
  border: 0;
  background: none;
  padding: 0;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}

.copy:hover {
  color: var(--fg);
}

/* --------------------------------------------------------------------------
   Trust strip — real, checkable facts, directly under the hero.

   Not badges, not seals, not a star rating. Four things a visitor can verify
   for themselves within a minute of landing: the source is public, the binary
   is actually signed, there is no account wall, and the download count is a
   real number pulled live from api.eaon.dev (main.js already contains this
   exact fetch-and-reveal pattern for products.html — this reuses the same
   `.js-dl[data-dlcount]` markup so it plugs into that code unchanged, rather
   than duplicating the logic). The count starts hidden and only appears once a
   real number has loaded — see main.js's own comment: "honest: no number, no
   claim."
   -------------------------------------------------------------------------- */

/* Each fact is its own filled tile, the way the reference puts every customer
   logo in its own rounded card rather than stringing them along one row. Three
   claims a visitor can check, each given its own object to sit in. */
.trust {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  padding-block: 0 var(--s6);
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
}

.trust__item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  border-bottom: 1px solid var(--line);
  transition: border-color 140ms var(--ease), color 140ms var(--ease),
    background-color 140ms var(--ease);
}

a.trust__item:hover {
  border-color: var(--line-2);
  background: var(--card-2);
  color: var(--fg);
}

/* Centred variant, for the strip that spans the two hero cards on the homepage.
   The page's own rule is that the column is centred and its contents are not, so
   this is the exception rather than the default: with the download counts moved
   into the product cards, three items left-aligned across the full frame left a
   long empty right-hand side. The mastheads on features and download keep the
   left alignment, since their strip sits directly under left-aligned copy. */
.trust--center {
  justify-content: center;
}

.trust__item svg {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--fg);
}

/* Hidden until the real total arrives — ported verbatim from styles.css so it
   plugs into main.js's existing fetch without any change to that script. */
.js-dl {
  display: none;
  align-items: center;
  gap: 8px;
}

.js-dl.is-on {
  display: flex;
}

.js-dl b {
  color: var(--fg);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   Eaon Desktop, rebuilt from its own source
   --------------------------------------------------------------------------

   Not a drawing of the app and not a screenshot: this is the app's own design
   system, lifted out of
   `Eaon Desktop/src/renderer/src/styles/{tokens,app,chat}.css` and reapplied to
   static markup. Every colour below is computed by the same `color-mix` chain
   the shipping build runs, from the same four user-editable values the
   Appearance settings page exposes (accent, background, foreground, contrast),
   so the surfaces here are the values the real window paints, not a match made
   by eye.

   It is also still a working chat client. main.js finds `[data-app]`, posts to
   api.eaon.dev's Instant tier and streams a reply, and it generates
   `.appwindow__msg--user` / `--assistant` / `__notice` / `__chat` / `__thinking`
   / `__caret` nodes. Those class names are therefore fixed: renaming them here
   silently unstyles everything the script creates.

   The app's accent is BLUE. `#f17455` is the brand and icon colour and stays
   the website's accent; `#0169cc` is the app's own dark default, one of the
   eight palettes 5.0 ships. Recolouring the window to the site's accent would
   make it a picture of a product that does not exist, and the difference is
   useful: it separates "app" from "page" at a glance.
   -------------------------------------------------------------------------- */

.appwindow {
  /* The four editable values, verbatim from the app's dark palette. */
  --bg: #111111;
  --fg: #fcfcfc;
  --accent: #0169cc;
  --contrast: 60;

  --lift-color: #ffffff;
  --sink-color: #000000;
  --u: calc(var(--contrast) * 0.055%);

  /* Surfaces, mixed exactly as tokens.css mixes them. */
  --canvas: color-mix(in srgb, var(--bg), var(--sink-color) 22%);
  --surface-1: color-mix(in srgb, var(--bg), var(--lift-color) calc(var(--u) * 1.6));
  --surface-2: color-mix(in srgb, var(--bg), var(--lift-color) calc(var(--u) * 2.6));
  --surface-3: color-mix(in srgb, var(--bg), var(--lift-color) calc(var(--u) * 4.2));
  --sunken: color-mix(in srgb, var(--bg), var(--sink-color) 34%);

  --a-hover: rgba(255, 255, 255, 0.055);
  --a-hover-strong: rgba(255, 255, 255, 0.09);
  --a-border-soft: rgba(255, 255, 255, 0.06);
  --a-border-strong: rgba(255, 255, 255, 0.14);

  --text: var(--fg);
  --text-2: color-mix(in srgb, var(--fg), var(--bg) 38%);
  --text-3: color-mix(in srgb, var(--fg), var(--bg) 58%);
  --text-4: color-mix(in srgb, var(--fg), var(--bg) 72%);

  --send-bg: #b4b4b4;
  --send-fg: #1a1a1a;

  /* Metrics, measured from the design frames and copied out of tokens.css. */
  --sidebar-w: 252px;
  --sidebar-gap: 8px;
  --titlebar-h: 36px;
  --nav-row-h: 27px;
  --composer-chat-w: 640px;
  --r-panel: 14px;
  --r-composer: 18px;
  --a-r-sm: 8px;
  --a-r-lg: 12px;

  --a-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", system-ui, sans-serif;
  --a-ease: cubic-bezier(0.32, 0.72, 0, 1);
  --a-dur: 160ms;

  position: relative;
  display: flex;
  overflow: hidden;
  /* `body` paints the window once in the real app, for the reason its own
     comment gives: any surface painting its own background shows up as a seam
     down the edge of the sidebar. Same here. */
  background: var(--canvas);
  color: var(--text);
  font-family: var(--a-font);
  font-size: 14px;
  line-height: 1.45;
  letter-spacing: -0.01em;
  text-align: left;
  -webkit-font-smoothing: antialiased;
}

/* macOS draws the traffic lights itself, over the page, at
   trafficLightPosition.x = 20 — three 12pt buttons 20pt apart. There is no OS
   here to draw them, so they are drawn at the coordinates the OS would use:
   x 20, and vertically centred in the panel's 36px titlebar, which itself
   starts --sidebar-gap down from the window edge. */
.appwindow__lights {
  position: absolute;
  top: calc(var(--sidebar-gap) + (var(--titlebar-h) - 12px) / 2);
  left: 20px;
  z-index: 5;
  display: flex;
  gap: 8px;
  pointer-events: none;
}

.appwindow__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex: none;
}

.appwindow__dot:nth-child(1) { background: #ff5f57; }
.appwindow__dot:nth-child(2) { background: #febc2e; }
.appwindow__dot:nth-child(3) { background: #28c840; }

/* ---- sidebar ---- */

/* A floating rounded panel, not a flush column. `--sidebar-gap: 8px` plus
   `--r-panel: 14px` is the mechanical proof of it. */
.appwindow__sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  display: flex;
  flex-direction: column;
  padding: var(--sidebar-gap);
  overflow: hidden;
  user-select: none;
  transition: flex-basis 240ms var(--a-ease), width 240ms var(--a-ease),
    padding 240ms var(--a-ease);
}

.appwindow.is-collapsed .appwindow__sidebar {
  flex-basis: 0;
  width: 0;
  min-width: 0;
  padding: 0;
}

.appwindow__panel {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* `--surface-1`, not `--sidebar`: in the dark theme that token lands three
     values off the canvas and the panel's curve disappears. The app's own
     comment makes the same point. */
  background: var(--surface-1);
  border: 1px solid var(--a-border-soft);
  border-radius: var(--r-panel);
  overflow: hidden;
  width: calc(var(--sidebar-w) - var(--sidebar-gap) * 2);
  min-width: calc(var(--sidebar-w) - var(--sidebar-gap) * 2);
  transition: opacity 160ms var(--a-ease), transform 240ms var(--a-ease);
}

.appwindow.is-collapsed .appwindow__panel {
  opacity: 0;
  transform: translateX(-24px);
  pointer-events: none;
}

/* Right-aligned, so the buttons sit at the far edge of the panel rather than
   crowding the traffic lights. 82px of window space is reserved on the left
   (the lights end at 72 plus 10px of air), less the gutter the panel already
   has. */
.appwindow__sidehead {
  height: var(--titlebar-h);
  flex: 0 0 var(--titlebar-h);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 2px;
  padding: 0 8px 0 max(8px, calc(82px - var(--sidebar-gap)));
}

.appwindow__ico {
  position: relative;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--a-r-sm);
  color: var(--text-3);
  flex: none;
  background: none;
  border: 0;
  cursor: pointer;
  transition: background var(--a-dur) var(--a-ease), color var(--a-dur) var(--a-ease);
}

.appwindow__ico svg {
  width: 16px;
  height: 16px;
  stroke-width: 1.9;
}

@media (hover: hover) and (pointer: fine) {
  .appwindow__ico:hover {
    background: var(--a-hover);
    color: var(--text);
  }
}

.appwindow__body {
  flex: 1;
  min-height: 0;
  padding: 0 7px;
  overflow-y: auto;
  scrollbar-width: none;
}

.appwindow__body::-webkit-scrollbar {
  width: 0;
}

.appwindow__navrow {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  height: var(--nav-row-h);
  padding: 0 8px 0 6px;
  border: 0;
  border-radius: var(--a-r-sm);
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: background var(--a-dur) var(--a-ease);
}

.appwindow__navrow svg {
  width: 16px;
  height: 16px;
  flex: none;
  stroke-width: 1.9;
  color: var(--text-3);
}

@media (hover: hover) and (pointer: fine) {
  .appwindow__navrow:hover {
    background: var(--a-hover);
  }
}

/* The one place this window deviates from the app.

   `--text-4` is Eaon Desktop's most de-emphasised tier and these three labels
   genuinely use it, but it lands at 2.2:1 and this window is not a screenshot:
   it is a live chat client sitting on a public page, and everything you can
   read in it should be readable. They move one tier, to `--text-2`. Structure,
   metrics, layout and the rest of the palette are untouched, and the ADE window
   below keeps its own dim tiers exactly because it is a static illustration
   rather than a widget. */
.appwindow__seclabel {
  padding: 14px 8px 4px 6px;
  color: var(--text-2);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.01em;
}

.appwindow__empty {
  padding: 0 6px 4px;
  color: var(--text-2);
  font-size: 13px;
}

.appwindow__recents {
  display: flex;
  flex-direction: column;
}

/* Generated by main.js — the class name is part of that contract. */
.appwindow__chat {
  display: block;
  width: 100%;
  height: var(--nav-row-h);
  padding: 0 8px 0 6px;
  border: 0;
  border-radius: var(--a-r-sm);
  background: none;
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  line-height: var(--nav-row-h);
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  transition: background var(--a-dur) var(--a-ease), color var(--a-dur) var(--a-ease);
}

@media (hover: hover) and (pointer: fine) {
  .appwindow__chat:hover {
    background: var(--a-hover);
    color: var(--text);
  }
}

.appwindow__chat--active {
  background: var(--a-hover-strong);
  color: var(--text);
}

.appwindow__sidefoot {
  flex: none;
  display: flex;
  align-items: center;
  padding: 6px 7px;
}

/* ---- main pane ---- */

.appwindow__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* Offset by the sidebar gutter so this row lands on the same baseline as the
   floating panel's titlebar, rather than sitting 8px higher against the window
   edge. The app does the same arithmetic. */
.appwindow__topbar {
  flex: none;
  height: calc(var(--titlebar-h) + var(--sidebar-gap));
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px 0 16px;
}

/* Chat and Work, in a sunken track so the raised option reads as selected
   without an accent fill — the accent is already spent on primary actions. */
.appwindow__mode {
  flex: none;
  display: flex;
  gap: 2px;
  padding: 3px;
  background: var(--sunken);
  border: 1px solid var(--a-border-soft);
  border-radius: var(--a-r-lg);
}

.appwindow__modeopt {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 28px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: calc(var(--a-r-lg) - 4px);
  background: none;
  color: var(--text-2);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--a-dur) var(--a-ease), color var(--a-dur) var(--a-ease),
    border-color var(--a-dur) var(--a-ease);
}

.appwindow__modeopt svg {
  width: 15px;
  height: 15px;
  flex: none;
  stroke-width: 1.9;
  color: var(--text-3);
  transition: color var(--a-dur) var(--a-ease);
}

.appwindow__modeopt[data-active] {
  background: var(--surface-3);
  border-color: var(--a-border-strong);
  color: var(--text);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
}

.appwindow__modeopt[data-active] svg {
  color: var(--text);
}

.appwindow__title {
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 30%;
}

.appwindow__spacer {
  flex: 1;
}

.appwindow__topacts {
  display: flex;
  align-items: center;
  gap: 2px;
}

.appwindow__topbtn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 9px;
  border: 0;
  border-radius: var(--a-r-sm);
  background: none;
  color: var(--text-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: background var(--a-dur) var(--a-ease), color var(--a-dur) var(--a-ease);
}

.appwindow__topbtn svg {
  width: 14px;
  height: 14px;
  stroke-width: 1.9;
}

@media (hover: hover) and (pointer: fine) {
  .appwindow__topbtn:hover {
    background: var(--a-hover);
    color: var(--text);
  }
}

/* ---- thread ---- */

.appwindow__thread {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 24px 0 12px;
  scrollbar-width: none;
}

.appwindow__thread::-webkit-scrollbar {
  width: 0;
}

/* The app wraps the messages in `.thread__inner`; here the thread element is
   the scroller main.js appends into, so the measure and the 22px rhythm sit on
   its children instead. Same numbers. */
.appwindow__thread > * {
  width: var(--composer-chat-w);
  max-width: calc(100% - 48px);
  flex: none;
}

.appwindow__thread > * + * {
  margin-top: 22px;
}

.appwindow__msg--user {
  align-self: flex-end;
  width: auto !important;
  max-width: min(78%, calc(var(--composer-chat-w) * 0.78)) !important;
  margin-right: max(24px, calc((100% - var(--composer-chat-w)) / 2));
  background: var(--surface-2);
  border-radius: 18px;
  padding: 8px 15px;
  font-size: 14px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.appwindow__msg--assistant {
  font-size: 14px;
  line-height: 1.62;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The site's own `.hero p { font-size: 1.125rem }` and friends are descendant
   rules at (0,1,1), so a bare `p` inside the window inherits page type and
   blows the window open. This guard outranks them at (0,2,0) — `:where()`
   contributes nothing, `:not([class])` contributes one class' worth.

   The `:not([class])` is the load-bearing half. The first version of this rule
   was a plain `.appwindow p, .appwindow span, .appwindow div, .appwindow
   button`, which is (0,1,1) and therefore beat every single-class component
   rule in this file: `.appwindow__seclabel`'s 11px, `.adepane__meta`'s 10px
   and every other size silently became 14px. Restricting it to elements that
   carry no class of their own leaves the components alone. */
.appwindow :where(p, span, div, button):not([class]),
.adewin :where(p, span, div, button):not([class]) {
  font-size: inherit;
}

.appwindow__notice {
  font-size: 13px;
  color: var(--text-3);
  text-align: center;
}

.appwindow__thinking {
  color: var(--text-3);
}

.appwindow__caret {
  display: inline-block;
  width: 7px;
  height: 1em;
  margin-left: 1px;
  vertical-align: text-bottom;
  background: var(--text-3);
  animation: appcaret 1s steps(1) infinite;
}

@keyframes appcaret {
  50% {
    opacity: 0;
  }
}

/* ---- composer ---- */

.appwindow__dock {
  flex: none;
  display: flex;
  justify-content: center;
  padding: 8px 0 13px;
}

.appwindow__composer {
  position: relative;
  z-index: 2;
  width: var(--composer-chat-w);
  max-width: calc(100% - 48px);
  background: var(--surface-2);
  border-radius: var(--r-composer);
  padding: 0 5px 6px;
  display: flex;
  flex-direction: column;
  box-shadow: 0 0 0 1px var(--a-border-soft);
  transition: box-shadow var(--a-dur) var(--a-ease);
}

.appwindow__composer:focus-within {
  box-shadow: 0 0 0 1px var(--a-border-strong);
}

.appwindow__input {
  width: 100%;
  border: 0;
  background: none;
  resize: none;
  outline: none;
  padding: 13px 11px 8px;
  min-height: 46px;
  max-height: 160px;
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.45;
}

.appwindow__input::placeholder {
  color: var(--text-3);
}

.appwindow__composer-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 6px 2px;
}

.appwindow__model {
  margin-left: auto;
  color: var(--text-2);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 45%;
}

.appwindow__send {
  width: 28px;
  height: 28px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: var(--send-bg);
  color: var(--send-fg);
  cursor: pointer;
  transition: opacity var(--a-dur) var(--a-ease);
}

.appwindow__send svg {
  width: 16px;
  height: 16px;
  stroke-width: 2.2;
}

.appwindow__send:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ---- responsive ---- */

@media (max-width: 860px) {
  .appwindow {
    font-size: 13px;
  }

  .appwindow__sidebar {
    display: none;
  }

  .appwindow__lights {
    top: 14px;
    left: 14px;
  }

  .appwindow__topbar {
    padding-left: 76px;
  }

  .appwindow__mode {
    display: none;
  }
}

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

/* --------------------------------------------------------------------------
   Content
   -------------------------------------------------------------------------- */

.head {
  max-width: var(--measure);
  margin-bottom: var(--s5);
}

.head p {
  margin-top: var(--s3);
  color: var(--fg-2);
  font-size: 1rem;
}

/* A rule per cell rather than an enclosing bordered panel.

   The panel treatment was tried first and is wrong for this component: these
   grids hold 4, 5 and 3 items on features.html, so a fixed 3-column panel left
   two blank cells inside a drawn rectangle on the first one and one on the
   second — and a blank cell inside a border reads as content that failed to
   load, where a blank cell between gaps reads as nothing at all. Only grids
   whose item count always fills the rectangle (`.stats`, `.prod__facts`) get
   the panel; everything else gets top rules, which is also what `.features`
   and `.steps` use, so the three scanning grids on the site now agree. */
/* paseo: `flex min-h-48 flex-col rounded-xl border border-white/10
   bg-white/[0.025] p-5`. The min-height is the part worth copying — it is what
   keeps a row of three tiles with unequal copy from reading as a row that
   failed to align, and it is why an incomplete final row still looks
   deliberate rather than broken. */
/* Ruled cells, like every other grid on the site. These used to be filled,
   bordered, rounded cards floating on a 16px gap, which is the pattern the
   whole rebuild moved away from: when every block is a card, none of them is
   emphasised.

   The column count follows the item count so a group never ends with one cell
   marooned in a row of three. `:has()` reads it off the markup, so adding a
   feature to a section does not mean remembering to retune a class. Browsers
   without `:has()` get the three-column default, which still lays out. */
.rows {
  --cols: 3;
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--s5) var(--s6);
}

.rows:has(> :nth-child(2):last-child),
.rows:has(> :nth-child(4):last-child) {
  --cols: 2;
}

.row {
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

/* No hover state: there is nothing to press. These were cards once, and a
   card invites a click it never honours. */

.row h3 {
  margin-bottom: var(--s3);
}

.row p {
  color: var(--muted);
  margin: 0;
  max-width: none;
}

/* A 2x2 grid rather than the 3-across "equal feature cards" default — no card
   styling at all (no border, no fill), just paired headline/body blocks with
   room between them. Deliberately a different rhythm from the trust strip and
   from the hairline .rows above it, so three sections in a row don't share one
   layout family. */
/* The 2x2 stays unboxed on purpose. This section is the one that inverts, so
   it already reads as a distinct plane; putting cards inside an inverted block
   would stack two separation devices on one section. Space does the work. */
/* Each cell opens with its own rule and the grid tightens. Unboxed blocks
   separated by 88px of nothing were the loosest thing on the features page —
   a reader could not tell whether two neighbouring paragraphs belonged to the
   same idea. A rule per cell answers that without adding a card. */
.features {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5) var(--s6);
}

.features > * {
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.features h3 {
  margin-bottom: var(--s2);
  font-size: 1.0625rem;
}

.features p {
  color: var(--muted);
  max-width: 46ch;
  font-size: 0.9375rem;
}

/* A small filled square ahead of each headline, so the grid can be scanned by
   shape before it is read. Monochrome: this section's accent budget is already
   spent on its headline. */
.feature-icon {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  margin-bottom: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--card-2);
  color: var(--brand);
}

.feature-icon svg {
  width: 15px;
  height: 15px;
}

/* --------------------------------------------------------------------------
   FAQ — native disclosure, works without JavaScript
   -------------------------------------------------------------------------- */

/* Each question is its own filled tile, stacked with a small gap, rather than
   rows divided by hairlines. Open and closed both read cleanly because the
   fill defines the item either way. */
/* A ruled list rather than a stack of filled pills with gaps. An FAQ is a
   table of contents for objections; every reference site draws it as rows
   divided by hairlines, and the gaps between filled pills were the single
   loosest-looking thing on the old page. */
/* Ruled rows, not a stack of bordered cards. Four separately-boxed questions
   read as four objects the reader has to assess one at a time; one ruled list
   reads as a single list, which is what it is. Borders come from `.rows`, so
   there is no line above the first row or below the last — the band's own
   rules already close the group. */
.faq {
  display: block;
  max-width: none;
  border-top: 0;
}

.panel__body.faq {
  max-width: none;
}

.faq details {
  border: 0;
  border-radius: 0;
  background: transparent;
  padding-inline: clamp(20px, 3vw, 40px);
  transition: background-color 160ms var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .faq details:hover {
    background: var(--card);
  }
}

.faq details[open] {
  background: var(--card);
}

.faq summary {
  display: flex;
  justify-content: space-between;
  gap: var(--s4);
  padding-block: var(--s4);
  cursor: pointer;
  list-style: none;
  font-weight: 500;
  font-size: 0.9375rem;
  letter-spacing: -0.011em;
  transition: color 140ms var(--ease);
}

.faq summary:hover {
  color: var(--brand);
}

.faq summary::-webkit-details-marker {
  display: none;
}

/* A rotating plus rather than a swapped glyph. Swapping "+" for "-" changes
   the width of the marker, which nudges the whole row; rotating one arm does
   not, and it is the only animation in the component. */
.faq summary::after {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  margin-top: 7px;
  background:
    linear-gradient(currentColor, currentColor) center / 100% 1px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1px 100% no-repeat;
  color: var(--muted);
  transition: transform 180ms var(--ease);
}

.faq details[open] summary::after {
  transform: rotate(90deg);
  background:
    linear-gradient(currentColor, currentColor) center / 100% 1px no-repeat;
}

.faq details p {
  padding-bottom: var(--s4);
  color: var(--muted);
  font-size: 0.9375rem;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

/* The one hairline left in the layout, and the one place that earns it.

   Every other boundary on this page is a change of background, which works
   because both sides of it are known. The footer is the exception: each page
   ends on whatever section it happens to end on, so a tone chosen here is
   right on some pages and invisible on others. Banding it collides on index
   and products, which end banded; leaving it on the base tone collides on
   features, download, docs, changelog and privacy, which end on base, and
   there it is genuinely bad — the page just runs into a tall region of
   identical black with no boundary at all.

   A rule does not care what precedes it. This is also the only boundary on the
   page where the KIND of content changes rather than just the topic, which is
   what a rule is for. So: base tone plus one hairline, correct on all eight
   pages, no per-page tuning to keep in sync. */
.site-foot {
  background: var(--bg);
  border-top: 1px solid var(--line);
  padding-block: var(--s6) var(--s5);
  position: relative;
}

.foot-cols {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: var(--s6) var(--s5);
}

.foot-cols h4 {
  margin: 0 0 var(--s4);
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.foot-cols ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 10px;
}

.foot-cols a {
  font-size: 0.875rem;
  color: var(--muted);
  border-bottom: 0;
}

.foot-cols a:hover {
  color: var(--fg);
}

.foot-tagline {
  margin-top: var(--s3);
  max-width: 30ch;
  font-size: 0.875rem;
  color: var(--muted);
}

.foot-brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* Live Discord counts. Hidden until main.js has real numbers — the .is-on class
   it adds is what reveals this, so an unreachable Discord shows nothing rather
   than an em-dash placeholder or an invented figure. */
.dwidget {
  display: none;
  margin-top: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  overflow: hidden;
  border-bottom: 0;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

.dwidget.is-on {
  display: flex;
}

.dwidget:hover {
  background: var(--surface-2);
}

.dwidget__seg {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px;
}

/* A hairline divider between the two halves, not a second box. */
.dwidget__seg + .dwidget__seg {
  border-left: 1px solid var(--line);
}

.dwidget b {
  color: var(--fg);
  font-weight: 500;
}

/* The one other place a colour is allowed: a presence dot that means "online"
   reads as wrong in monochrome. */
.dwidget__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #4ec26a;
  flex: none;
}

.foot-socials {
  display: flex;
  gap: var(--s2);
  margin-top: var(--s4);
}

/* Same filled square as the theme toggle in the header. */
.iconbtn {
  display: inline-grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 0;
  border-bottom: 0;
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--muted);
  transition: background-color 140ms ease, color 140ms ease;
}

.iconbtn:hover {
  background: var(--surface-2);
  color: var(--fg);
}

.iconbtn svg {
  width: 16px;
  height: 16px;
}

.foot-base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  justify-content: space-between;
  align-items: baseline;
  margin-top: var(--s7);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
}

.foot-ghost {
  color: var(--muted);
  border-bottom: 0;
}

.foot-ghost:hover {
  color: var(--fg);
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1000px) {
  :root {
    --s8: 88px;
    --s7: 64px;
  }

  .foot-cols {
    grid-template-columns: repeat(2, 1fr);
  }

  /* Three tiles across is too tight here, but one is too loose: the middle
     tile spans both columns so the row still reads as a group. */
  .rows {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .rows .row:last-child {
    grid-column: 1 / -1;
  }

  .hero h1,
  .hero p {
    max-width: none;
  }
}

@media (max-width: 720px) {
  .features {
    grid-template-columns: 1fr;
    gap: var(--s6);
  }

  .rows {
    grid-template-columns: 1fr;
  }

  .rows .row:last-child {
    grid-column: auto;
  }
}

@media (max-width: 640px) {
  body {
    font-size: 15px;
  }

  .hero {
    padding-block: 0;
  }

  /* Full-width buttons in a stacked hero: two half-width pills centred on a
     narrow screen look like an accident. Same for the pair beside each feature
     split, which are too long to sit side by side at this width. */
  .hero .btn-row,
  .split__say .btn-row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s2);
  }

  .hero .btn-row .btn,
  .split__say .btn-row .btn {
    justify-content: center;
  }

  /* The announcement stops being a pill here. At full width the sentence wraps
     to two or three lines, and a 999px radius on a tall box bulges into a
     lozenge; the trailing link also breaks one word per line. So: normal card
     radius, and the link is dropped, since the sentence already says what
     changed and the whole strip sits directly above the hero. */
  .hero .notice {
    width: auto;
    border-radius: var(--r);
    padding: var(--s3);
    align-items: flex-start;
    gap: var(--s2);
  }

  .hero .notice a {
    display: none;
  }

  .foot-cols {
    grid-template-columns: 1fr;
  }

  .install__body {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s3);
  }

  .trust {
    gap: var(--s4);
    font-size: 0.75rem;
  }

  .appwindow {
    font-size: 11px;
  }
}

/* ==========================================================================
   PRODUCT-SITE LAYER
   ==========================================================================

   Added for the pass that took superset.sh, paseo.sh, emdash.com and
   telemetry.dev as references. What actually separates those from a hobby
   page is not colour or type — Eaon already had a considered palette and a
   real type scale. It is three things, in order of how much they matter:

   1. THE MOCKUP IS THE PAGE. All four give a picture of the running product
      enormous space: full width or near it, high in the page, before any
      feature copy. Eaon was showing two mockups at ~340px wide, side by side,
      each cramped enough that none of the detail inside could be read. `.stage`
      exists to give one mockup the whole column.

   2. SHOWN MID-WORK, NOT AT REST. Their realism comes from real timings, real
      diff counts, real branch and file names — "worked for 7s", "+46 −1". A
      product caught doing something reads as a product other people use. See
      the .appwindow note above for how Eaon's own numbers were sourced.

   3. ALTERNATING FEATURE SPLITS. A mono eyebrow, a short headline, two or
      three lines of copy, and a picture — repeated, sides swapping. `.split`.

   Deliberately NOT taken: the customer-logo walls (Google, Vercel, Cloudflare),
   the testimonial grids, "Backed by Y Combinator", "over 1M downloads". Eaon
   has no customers to name and no testimonials, and this site's own rule is
   that a number it cannot check does not go on the page. The honest
   substitutes are here instead: `.logos` for the providers Eaon genuinely
   routes to, and `.stats` for figures that come from a live API.
   ========================================================================== */

/* An accent eyebrow. The plain .eyebrow stays muted; this one is for section
   openers where the reference sites put a coloured mono label. */
.eyebrow--accent {
  color: var(--brand);
}

/* --------------------------------------------------------------------------
   The stage — one mockup, given the whole column
   -------------------------------------------------------------------------- */

.stage {
  margin-top: var(--s7);
  position: relative;
  text-align: left;
}

/* superset puts a 5%-opacity brand ellipse behind every mockup and nothing
   else: `radial-gradient(ellipse 55% 45% at 50% 40%, rgba(232,128,74,0.05),
   transparent 75%)`. This is NOT the glow that got reverted here — that one
   was a saturated mesh at full strength; this is two orders of magnitude
   fainter, sits behind a window that has its own border, and reads as air
   rather than as light. `inset: 0` so it cannot overhang and cause scroll. */
.stage::before {
  content: "";
  position: absolute;
  inset: -6% 0 -6%;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 55% 45% at 50% 40%, var(--brand-wash), transparent 75%);
}

/* `rounded-lg border border-border bg-background
   shadow-[0_1px_1px_rgba(0,0,0,0.4),0_24px_70px_-16px_rgba(0,0,0,0.75)]` plus
   an inset white ring at 6% — the ring is what gives the window a lit top edge
   without a gradient. The windows are the one thing on the page that is not
   square: superset gives them 8px. */
/* paseo's frame (`rounded-2xl ring-1 ring-white/10`) with superset's shadow
   and lit top edge. A ring rather than a border so the 1px sits inside the
   16px corner instead of fighting it. */
.stage .appwindow,
.stage .adewin {
  position: relative;
  z-index: 1;
  border: 0;
  border-radius: var(--r-lg);
  box-shadow: 0 0 0 1px var(--line), 0 1px 1px rgb(0 0 0 / 0.4),
    0 24px 70px -16px rgb(0 0 0 / 0.75), inset 0 1px 0 rgb(255 255 255 / 0.06);
}

.stage--hero {
  margin: var(--s7) 0 0;
}

/* 1280 x 700 is roughly 1.83:1, which is what a Mac window actually looks
   like. At 440 the window was 2.9:1 and read as a letterboxed strip of an app
   rather than the app. */
.stage--hero .appwindow {
  height: 700px;
}

.stage--split .appwindow,
.stage--split .adewin {
  height: 420px;
}

/* The gap-and-fill split that used to live here has been replaced by the ruled
   one defined with the grid system above. It set `gap: 40px 64px` with no
   track background, so the two columns were separated by empty space rather
   than by a rule, and the row did not belong to the same structure as the rest
   of the page. `.split__copy` and `.split--top` went with it; nothing in the
   eight pages referenced either after the rebuild. */

/* The same spec table the hero masthead uses, so a fact reads identically
   wherever it appears: hairline per row, mono label left, value right. */
.split__facts {
  margin: var(--s5) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0;
  max-width: 34rem;
}

.split__facts li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: 8px 0;
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.6875rem;
}

.split__facts span {
  flex: none;
  color: var(--muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.split__facts b {
  min-width: 0;
  text-align: right;
  font-weight: 400;
  color: var(--fg-2);
  font-variant-numeric: tabular-nums;
}

/* The counters keep owning `display`, so they stay hidden until a real number
   lands. Restoring the grid needs to beat their own .is-on rule. */
.split__facts .js-dl.is-on,
.split__facts .js-adedl.is-on {
  display: flex;
}

/* --------------------------------------------------------------------------
   Provider logos — honest social proof
   -------------------------------------------------------------------------- */

.logos {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
  margin-top: var(--s3);
}

@media (min-width: 640px) {
  .logos {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s3);
  }
}

.logos__label {
  margin: 0;
  color: var(--muted);
  opacity: 0.75;
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.logos + .logos__label {
  margin-top: var(--s5);
}

/* paseo's "Works with your tools": `flex items-center justify-center gap-2
   rounded-xl border border-white/10 bg-white/[0.03] px-3 py-3` in a
   `grid grid-cols-2 sm:grid-cols-3` — real tiles in a grid, not small chips in
   a wrapped row. */
.logos__item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  color: var(--fg-2);
  font-family: var(--mono);
  font-size: 0.8125rem;
  transition: border-color 160ms var(--ease), background-color 160ms var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .logos__item:hover {
    border-color: var(--line-2);
    background: var(--card-2);
  }
}

.logos__item img {
  width: 16px;
  height: 16px;
  object-fit: contain;
  flex: none;
  /* The provider marks arrive in their own brand colours and several are pure
     black, which disappears here. Desaturating them puts the row on one level
     and stops it reading as a sponsor wall. */
  filter: grayscale(1) brightness(1.9);
  opacity: 0.85;
}

/* The dark-theme filter lifts every mark towards white, which on the light
   theme's near-white chip leaves an empty box. Light has to push the other
   way — down to a mid grey — rather than reuse one filter for both. */
[data-theme="light"] .logos__item img {
  filter: grayscale(1) brightness(0.45);
  opacity: 0.75;
}

/* --------------------------------------------------------------------------
   Stats — every figure here is fetched live or checkable
   -------------------------------------------------------------------------- */

/* One strip divided by rules rather than four separate filled cards with gaps
   between them. Cards of unequal content looked like a row that had failed to
   align; a divided strip is a single object and cannot. The gap collapses to
   1px and the divider IS that pixel. */
/* `grid-auto-flow: column` rather than a fixed `repeat(4, ...)`, because the
   count is not fixed: `.stat--live` stays hidden until its figure actually
   arrives, so an unreachable GitHub leaves three children, not four. A fixed
   four-track panel would then draw a quarter of itself empty — with the gap
   showing through as the border colour, which is worse than a gap. Auto
   columns give exactly as many tracks as there are visible cells. */
/* Separate rounded tiles with a real gap, which is how paseo lays out every
   grid it has. The hairline-divided panel this replaces was emdash's
   construction. Auto columns are kept: `.stat--live` stays hidden until its
   figure arrives, so the count is 3 or 4 and a fixed `repeat(4, ...)` would
   leave a gap where the fourth should be. */
.stats {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--s2);
}

.stat {
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}

.stat__num {
  font-family: var(--mono);
  font-size: clamp(1.5rem, 2.4vw, 1.875rem);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.stat__label {
  margin-top: var(--s3);
  color: var(--muted);
  opacity: 0.75;
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Same honesty rule as the counters: a stat whose number has not arrived is
   not shown at all rather than shown as a zero. main.js adds .is-on.

   Three classes, not two. A live stat also carries .js-dl or .js-adedl so the
   counter script finds it, and those set display to flex and inline-flex when
   they turn on — .js-adedl's rule sits later in this sheet than a two-class
   selector here, so it won, and the ADE card laid its number and its label out
   side by side in one inline row. Winning on specificity instead of on order
   keeps that independent of where either block ends up in the file. */
.stat--live {
  display: none;
}

.stat.stat--live.is-on {
  display: block;
}

/* --------------------------------------------------------------------------
   The closing call to action
   -------------------------------------------------------------------------- */

/* `.close` is defined once, with the band variants further up. The centred
   button row that used to live here went with the centring. */

/* The boxed `.steps` grid that used to live here was overriding the open one
   defined with the grid system above, so the three steps kept their borders
   and fills through the declutter pass. One definition, up top. */

/* --------------------------------------------------------------------------
   The hero stage's caption row
   -------------------------------------------------------------------------- */

/* Same facts list as a split's, but reading as a caption under the hero
   window rather than as a column beside it: one centred line instead of a
   stack of label/value pairs. It carries both classes so the counter rules
   above (`.split__facts .js-dl.is-on`) still reach it. */
.stage__facts {
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: center;
  gap: var(--s5);
  margin-top: var(--s5);
}

.stage__facts li {
  grid-template-columns: none;
  grid-auto-flow: column;
  align-items: baseline;
  gap: var(--s2);
}

/* --------------------------------------------------------------------------
   Scroll reveal

   main.js has shipped the observer for this all along — it collects `.reveal`,
   staggers siblings, adds `.in` on intersection, and short-circuits under
   reduced motion. What it never had was any CSS to drive, because the rules
   stayed behind in the old styles.css when this sheet replaced it. So every
   `.reveal` in the markup did nothing. These are those rules.

   The opacity floor is scoped to `.js` — added by main.js on the first line it
   runs — so the page can never be left blank by a script that failed to load.
   Reduced motion is already handled by the global rule further down, which
   collapses the duration to 0.01ms; the `both` fill still lands on the final
   frame, and main.js has added `.in` to everything by then anyway.
   -------------------------------------------------------------------------- */

.js .reveal {
  opacity: 0;
}

.js .reveal.in {
  animation: reveal-rise 520ms var(--ease-out) both;
}

@keyframes reveal-rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1000px) {
  /* minmax(0, 1fr), not 1fr. A bare `1fr` floors the track at the widest
     min-content in it, and a terminal mockup's longest unbroken line is wider
     than a phone — which pushed the whole grid, copy included, past the
     viewport and gave the page a horizontal scrollbar. */
  .split,
  .split--flip {
    grid-template-columns: minmax(0, 1fr);
  }



  /* Order is reset so the copy always leads once the columns stack — a picture
     arriving before the sentence that explains it is the wrong way round. */
  .split--flip .split__say {
    order: 0;
  }

  /* `grid-auto-flow` has to be reset here, or the template below is ignored
     and the strip stays on one long row. */
  .stats {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .steps {
    grid-template-columns: 1fr;
  }

  .stage--hero .appwindow {
    height: 600px;
  }
}

@media (max-width: 640px) {
  .stats {
    grid-auto-flow: row;
    grid-template-columns: minmax(0, 1fr);
  }

  .stage {
    margin-top: var(--s6);
  }

  /* One line of facts under the hero wraps badly at this width; let it stack. */
  .stage__facts {
    grid-auto-flow: row;
    grid-auto-columns: auto;
    justify-content: start;
    gap: var(--s2);
  }

  /* Taller than the 380px the splits get. With the sidebar hidden the hero
     window is one column, and at 380px the thread was cut off mid-bubble —
     losing the local-model reply, which is the one exchange the whole page is
     arguing for. The splits can stay short; this one cannot. */
  .stage--hero .appwindow {
    height: 560px;
  }

  .stage--split .appwindow,
  .stage--split .adewin {
    height: 380px;
  }

  /* The conversation sidebar goes, and the thread takes the whole window.

     At this width the two panes were sharing ~350px, which left the model
     picker wrapping onto two lines and every message three or four words wide.
     The sidebar is the expendable half: it shows what the app's navigation
     looks like, while the thread is what the page is actually claiming — that
     one window switches from a hosted model to a local one mid-conversation.
     A mockup too cramped to read is decoration, so the half that carries the
     argument is the half that stays. */
  .appwindow__sidebar {
    display: none;
  }

  .appwindow__main {
    border-radius: var(--r);
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* ==========================================================================
   INNER PAGES
   ==========================================================================

   products / features / docs / changelog / privacy / 404 were previously on
   styles.css, the older design, so the site read as two different websites
   depending on which link you followed. They are now on this stylesheet.

   Three rules held while porting them:

   1. EVERY JS HOOK SURVIVES. main.js binds to class names as well as data
      attributes — `.js-dl [data-dlnum]`, `.download__req [data-dlnum]`,
      `.docs__nav-link` + `.is-current`, `.cmd__btn`/`.cmd__label`/`.cmd__text`,
      and eleven `.clog__*` classes the changelog renderer emits at runtime and
      which appear in no HTML file. Renaming any of those breaks a feature
      silently, so the names are kept even where they no longer match this
      file's own conventions.

   2. THE HONESTY PATTERN IS PRESERVED. The download counters and the Discord
      widget are display:none here and revealed only when main.js adds `.is-on`
      after real data arrives. Making any of them visible by
      default would print "0 downloads" and an expired offer.

   3. PROSE STAYS ELEMENT-SCOPED. docs, changelog notes and the privacy policy
      are hundreds of unclassed <p>/<h2>/<li> elements — the changelog's markdown
      renderer *cannot* emit classes. So those three get scoped element blocks
      rather than a class per element.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Shared page header — the masthead every inner page opens with
   -------------------------------------------------------------------------- */

/* Inner-page mastheads stay left-aligned. Only the homepage hero centres. */
/* Inner-page mastheads open the page and close with a rule, so every page has
   the same first two moves as the homepage: a title block, then a boundary. */
/* Vertical rhythm moved onto `.band__body` when these pages came onto the
   ruled grid. Left as it was, each block's own padding stacked with the
   band's and opened a canyon at the top of every page. The masthead keeps
   a little extra above it and nothing below. */
.phead {
  padding-block: var(--s4) 0;
}

/* A rule under the masthead makes the first section's own top border a double
   line, so the section drops it. */
.phead + .section,
.phead + .fsec,
.phead + .prod,
.phead + .dlsec {
  border-top: 0;
}

.phead h1 {
  max-width: 17ch;
}

.phead__lead {
  margin-top: var(--s4);
  color: var(--fg-2);
  font-size: 1.0625rem;
  line-height: 1.55;
  max-width: 58ch;
}

/* Small anchor pills, for jumping within a long page. */
.jump {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s5);
}

.jump a {
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
  transition: border-color 140ms var(--ease), color 140ms var(--ease),
    background-color 140ms var(--ease);
}

.jump a:hover {
  border-color: var(--line-2);
  background: var(--card-2);
  color: var(--fg);
}

/* --------------------------------------------------------------------------
   products.html — one block per app, then a comparison
   -------------------------------------------------------------------------- */

/* Alternating bands carry the section rhythm here too, so no rules. The HTML
   adds .section--band to every other .prod. */
.prod {
  padding-block: 0;
}

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

/* outline rather than border, so the 1px hairline sits inside the icon's own
   box and the lockup's baseline does not shift by a pixel. */
.prod__icon {
  width: 48px;
  height: 48px;
  flex: none;
  border-radius: var(--r-lg);
  outline: 1px solid var(--line);
  outline-offset: -1px;
}

.prod__name {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
}

.prod__what {
  margin-top: 2px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.875rem;
}

.prod__lead {
  margin-top: var(--s4);
  max-width: 62ch;
  color: var(--fg-2);
  font-size: 1rem;
}

/* A definition list, so the facts are marked up as the pairs they are. */
/* One divided panel, same construction as `.stats` and `.rows`. */
.prod__facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s2);
  margin: var(--s5) 0 0;
  padding-block: 0;
}

.prod__facts > div,
.prod__facts__cell {
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}

.prod__facts dt {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.prod__facts dd {
  margin: 10px 0 0;
  font-size: 0.875rem;
  color: var(--fg-2);
}

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

.prod__fine {
  margin-top: var(--s3);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  max-width: none;
}

.prod__fine b {
  color: var(--fg);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* The one caveat callout on this page. A left rule rather than a tinted box:
   this is a real limitation worth reading, not a warning banner. */
/* `border-l-2` is one of superset's few decorative flourishes — a 2px accent
   edge on a callout, used seven times on its own page. */
.prod__warn {
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-left: 2px solid var(--brand);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--muted);
  font-size: 0.875rem;
  max-width: 66ch;
}

/* The comparison table. Deliberately NOT called .pick__* — styles.css used that
   name for two unrelated components (a fake model picker and this table), which
   is exactly the collision worth not reproducing. */
.compare {
  margin-top: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  overflow: hidden;
  padding-inline: var(--s5);
}

.compare__row {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  gap: var(--s5);
  padding-block: 11px;
  border-bottom: 1px solid var(--line);
  align-items: baseline;
  font-size: 0.9375rem;
}

.compare__row:last-child {
  border-bottom: 0;
}

.compare__row--head {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.compare__q {
  color: var(--muted);
}

.compare__row:not(.compare__row--head) span:not(.compare__q) {
  color: var(--fg);
}

/* --------------------------------------------------------------------------
   features.html — long-form, alternating prose rows and small grids
   -------------------------------------------------------------------------- */

.fsec {
  padding-block: 0;
}

.fsec--sunken {
  background: transparent;
}

.subhead {
  max-width: var(--measure);
  margin-bottom: var(--s5);
}

.subhead h2 {
  margin-top: var(--s2);
}

.subhead p {
  margin-top: var(--s3);
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   The split download button (features.html).

   `.dlsplit__warn` is display:none and revealed only by
   [data-dl-active="windows"], an attribute main.js writes after OS detection.
   That attribute name and that value are a CSS-to-JS contract: keep both.
   -------------------------------------------------------------------------- */

.dlsplit {
  position: relative;
  display: inline-flex;
}

.dlsplit__main {
  display: flex;
}

.dlsplit__primary {
  border-right: 0;
}

.dlsplit__toggle {
  display: grid;
  place-items: center;
  width: 42px;
  padding: 0;
  border: 1px solid var(--inverse-bg);
  background: var(--inverse-bg);
  color: var(--inverse-fg);
  cursor: pointer;
}

.dlsplit__toggle:hover {
  background: none;
  border-color: var(--fg);
  color: var(--fg);
}

.dlsplit__toggle svg {
  width: 15px;
  height: 15px;
}

.dlsplit__menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 5;
  display: none;
  min-width: 100%;
  background: var(--bg);
  border: 1px solid var(--line);
}

.dlsplit.is-open .dlsplit__menu {
  display: block;
}

.dlsplit__item {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125rem;
  white-space: nowrap;
}

.dlsplit__item:last-child {
  border-bottom: 0;
}

.dlsplit__item:hover {
  background: var(--bg-sunken);
}

.dlsplit__size {
  margin-left: auto;
  color: var(--muted);
}

.dlsplit__warn {
  display: none;
  margin-top: var(--s3);
  color: var(--muted);
  font-size: 0.875rem;
  max-width: 60ch;
}

[data-dl-active="windows"] .dlsplit__warn {
  display: block;
}

.download__req {
  margin-top: var(--s4);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  max-width: none;
}

.download__req b {
  color: var(--fg);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   docs.html — sticky sidebar plus scoped prose

   `.docs__nav-link` and `.is-current` are main.js's scrollspy selectors, and
   each link's href must stay 1:1 with a section id. The observer band is
   rootMargin "-90px 0px -65% 0px", tuned to this header's height.
   -------------------------------------------------------------------------- */

.docs {
  display: grid;
  grid-template-columns: 216px minmax(0, 1fr);
  gap: var(--s7);
  padding-block: var(--s6) var(--s8);
}

/* Without this, a wide code block inside the body blows the grid out instead of
   scrolling inside its own column. */
.docs > * {
  min-width: 0;
}

.docs__side {
  position: sticky;
  top: 88px;
  align-self: start;
  max-height: calc(100vh - 110px);
  overflow-y: auto;
}

.docs__nav {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
}

.docs__nav-group {
  margin: var(--s4) 0 var(--s2);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.docs__nav-group:first-child {
  margin-top: 0;
}

.docs__nav-link {
  padding: 5px 0 5px var(--s3);
  border-bottom: 0;
  border-left: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.4;
}

.docs__nav-link:hover {
  border-left-color: var(--fg);
  color: var(--fg);
}

.docs__nav-link.is-current {
  border-left-color: var(--fg);
  color: var(--fg);
}

.docs__body {
  max-width: 74ch;
}

.docs__lead {
  margin-top: var(--s4);
  color: var(--muted);
  font-size: 1.0625rem;
}

.docs__section {
  padding-top: var(--s7);
}

/* Element-scoped: the docs body is almost entirely unclassed prose. */
.docs__section h2 {
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}

.docs__section h3 {
  margin-top: var(--s5);
}

.docs__section p {
  margin-top: var(--s4);
  color: var(--muted);
  max-width: none;
}

.docs__section b {
  color: var(--fg);
  font-weight: 500;
}

.docs__section a {
  color: var(--fg);
}

.docs__section code,
.legal code {
  padding: 1px 5px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.875em;
}

kbd {
  padding: 2px 6px;
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  font-family: var(--mono);
  font-size: 0.8125em;
}

.docs__ul,
.docs__ol {
  margin: var(--s4) 0 0;
  padding-left: 0;
  list-style: none;
  color: var(--muted);
}

.docs__ul li,
.docs__ol li {
  position: relative;
  padding-left: var(--s4);
  margin-bottom: var(--s3);
  line-height: 1.6;
}

.docs__ul li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.65em;
  width: 5px;
  height: 1px;
  background: var(--muted);
}

.docs__ol {
  counter-reset: docsol;
}

.docs__ol li {
  counter-increment: docsol;
}

.docs__ol li::before {
  content: counter(docsol) ".";
  position: absolute;
  left: 0;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

/* Numbered steps, as hairline rows rather than cards. */
.docs__steps {
  margin: var(--s5) 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}

.docs__step {
  display: grid;
  grid-template-columns: 3ch 1fr;
  gap: var(--s4);
  padding-block: var(--s4);
  border-bottom: 1px solid var(--line);
}

.docs__step-n {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

.docs__step h3 {
  margin-top: 0;
}

.docs__step p {
  margin-top: var(--s2);
}

.docs__note {
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-left: 2px solid var(--line-3);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--muted);
  font-size: 0.9375rem;
}

.docs__note p {
  margin-top: 0;
}

/* The one caveat marker that stays a rule: a left edge on a note is a real
   signal (read this one), not decoration. Coral, because a warning is exactly
   the kind of thing the accent should be spent on. */
.docs__note--warn {
  border-left: 2px solid var(--brand);
}

.docs__foot {
  margin-top: var(--s7);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

/* --------------------------------------------------------------------------
   changelog.html — a live feed, so most of this styles markup that only exists
   at runtime. The eleven .clog__* classes below appear in no HTML file:
   main.js's renderRelease() emits them, and its markdown renderer emits bare
   h3/h4/p/ul/li/pre/code/b/em/a with NO classes at all. Hence the descendant
   selectors under .clog__notes — they cannot be replaced with classes.
   -------------------------------------------------------------------------- */

.clog__meta {
  margin-top: var(--s3);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

.clog__list {
  padding-block: var(--s6) 0;
}

.clog__loading,
.clog__fallback {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.875rem;
}

.clog__fallback[hidden] {
  display: none;
}

/* One release. The left rule is the timeline; the dot marks the release itself. */
.clog__rel {
  position: relative;
  padding: 0 0 var(--s7) var(--s5);
  border-left: 1px solid var(--line);
}

.clog__list .clog__rel:last-child {
  border-left-color: transparent;
}

.clog__rel::before {
  content: "";
  position: absolute;
  left: -4px;
  top: 7px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg);
}

.clog__rel--old::before {
  background: var(--line);
}

.clog__rel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s3);
}

.clog__ver {
  font-size: 1.25rem;
}

.clog__badge {
  padding: 2px 8px;
  background: var(--inverse-bg);
  color: var(--inverse-fg);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.clog__badge--pre {
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
}

.clog__date {
  margin-left: auto;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

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

.clog__plat {
  padding: 2px 8px;
  border: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.6875rem;
}

.clog__notes {
  margin-top: var(--s4);
  max-width: var(--measure);
}

.clog__notes h3 {
  margin-top: var(--s4);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.clog__notes h4 {
  margin: var(--s4) 0 0;
  font-size: 0.9375rem;
}

.clog__notes p {
  margin-top: var(--s3);
  color: var(--muted);
  max-width: none;
}

.clog__notes ul {
  margin: var(--s3) 0 0;
  padding-left: 0;
  list-style: none;
}

.clog__notes li {
  position: relative;
  padding-left: var(--s4);
  margin-bottom: var(--s2);
  color: var(--muted);
  line-height: 1.6;
}

.clog__notes li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.65em;
  width: 5px;
  height: 1px;
  background: var(--muted);
}

.clog__notes li ul {
  margin-top: var(--s2);
}

.clog__notes li li::before {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  top: 0.55em;
}

.clog__notes code {
  padding: 1px 5px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.875em;
}

.clog__notes pre {
  margin-top: var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  overflow-x: auto;
}

.clog__notes pre code {
  padding: 0;
  border: 0;
  background: none;
}

.clog__notes b {
  color: var(--fg);
  font-weight: 500;
}

.clog__notes a {
  color: var(--fg);
}

.clog__dl {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s4);
}

.clog__dl a {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 6px 12px;
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

.clog__dl a:hover {
  border-color: var(--fg);
}

.clog__dl-size {
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   privacy.html — long-form legal prose, almost entirely unclassed
   -------------------------------------------------------------------------- */

.legal {
  padding-block: var(--s6) var(--s8);
}

.legal__inner {
  max-width: 74ch;
}

.legal__meta {
  margin-top: var(--s3);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

.legal h2 {
  margin-top: var(--s7);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
  font-size: 1.25rem;
}

.legal h3 {
  margin-top: var(--s5);
}

.legal p {
  margin-top: var(--s4);
  color: var(--muted);
  max-width: none;
}

.legal b {
  color: var(--fg);
  font-weight: 500;
}

.legal a {
  color: var(--fg);
}

.legal__list {
  margin: var(--s4) 0 0;
  padding-left: 0;
  list-style: none;
}

.legal__list li {
  position: relative;
  padding-left: var(--s4);
  margin-bottom: var(--s3);
  color: var(--muted);
  line-height: 1.6;
}

.legal__list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.65em;
  width: 5px;
  height: 1px;
  background: var(--muted);
}

/* The summary box sits inside .legal, so it would otherwise inherit the
   section-divider border-top and display font from `.legal h2`. Compound
   selector, deliberately — the same specificity fix the old stylesheet needed. */
.legal__summary {
  margin-top: var(--s5);
  padding: var(--s5);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
}

.legal h2.legal__summary-title {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
  font-family: var(--display);
  font-size: 1.0625rem;
}

.legal__lead {
  margin-top: var(--s5);
}

/* --------------------------------------------------------------------------
   404.html — no header, no footer; the link row IS the navigation
   -------------------------------------------------------------------------- */

.nf {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--s5);
}

.nf__inner {
  max-width: 52ch;
  text-align: center;
}

.nf__mark {
  display: inline-block;
  width: 34px;
  height: 34px;
  margin-bottom: var(--s5);
  border-bottom: 0;
}

/* .eyebrow is block now, so this only tightens the gap under it. */
.nf__code {
  margin-bottom: var(--s3);
}

.nf__lead {
  margin: var(--s4) auto 0;
  color: var(--muted);
}

.nf__acts {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s6);
}

.nf__links {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s3) var(--s5);
  margin-top: var(--s7);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125rem;
}

.nf__links a {
  color: var(--muted);
  border-bottom: 0;
}

.nf__links a:hover {
  color: var(--fg);
}

/* --------------------------------------------------------------------------
   Inner-page responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1000px) {
  .docs {
    grid-template-columns: 1fr;
    gap: var(--s5);
  }

  /* The sidebar becomes a horizontal scroller above the content. Group labels go
     away because they cannot read as groups in a single row. */
  .docs__side {
    position: static;
    max-height: none;
    overflow: visible;
  }

  .docs__nav {
    flex-direction: row;
    gap: var(--s4);
    overflow-x: auto;
    padding-bottom: var(--s3);
    border-bottom: 1px solid var(--line);
  }

  .docs__nav-group {
    display: none;
  }

  .docs__nav-link {
    padding: 0 0 var(--s2);
    border-left: 0;
    border-bottom: 1px solid transparent;
    white-space: nowrap;
  }

  .docs__nav-link:hover,
  .docs__nav-link.is-current {
    border-left: 0;
    border-bottom-color: var(--fg);
  }
}

@media (max-width: 940px) {
  .prod__facts {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 720px) {
  .compare__row {
    grid-template-columns: 1fr;
    gap: var(--s2);
  }

  /* The column headings are gone at this width, so each value has to say which
     app it belongs to. This is real content living in CSS — the old stylesheet
     did the same, and dropping it silently loses the labels. */
  .compare__row--head {
    display: none;
  }

  .compare__row > span:nth-child(2)::before {
    content: "Eaon: ";
    color: var(--muted);
  }

  .compare__row > span:nth-child(3)::before {
    content: "ADE: ";
    color: var(--muted);
  }

  .prod__id {
    gap: var(--s3);
  }

  .prod__icon {
    width: 46px;
    height: 46px;
  }
}

/* --------------------------------------------------------------------------
   Copy-a-command block.

   Class names are main.js's, not this file's: it binds `.cmd[data-copy]` and
   reaches inside for `.cmd__btn`, `.cmd__label` and `.cmd__text`, then flashes
   `.is-copied`. Renaming any of them breaks copy-to-clipboard with no error.
   -------------------------------------------------------------------------- */

.cmd {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border: 0;
  border-radius: var(--r);
  background: var(--surface);
  overflow-x: auto;
}

.cmd__text {
  flex: 1;
  min-width: 0;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.875rem;
  white-space: nowrap;
}

.cmd__btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  border: 0;
  padding: 0;
  background: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}

.cmd__btn:hover,
.cmd__btn.is-copied {
  color: var(--fg);
}

.cmd__ic {
  width: 14px;
  height: 14px;
}

/* --------------------------------------------------------------------------
   A terminal block. Real shell output, in the app's own mono.
   -------------------------------------------------------------------------- */

.term {
  margin-top: var(--s4);
  border: 0;
  border-radius: var(--r);
  background: var(--surface);
  overflow: hidden;
}

.term__bar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 9px var(--s3);
  border-bottom: 1px solid var(--line);
}

.term__title {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
}

.term__body {
  margin: 0;
  padding: var(--s3) var(--s4);
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.7;
  color: var(--fg);
}

.c-dim {
  color: var(--muted);
}

.c-key {
  color: var(--fg);
  font-weight: 500;
}

.c-ok {
  color: var(--muted);
}

/* The ADE download counter. Deliberately a separate class from .js-dl so the
   desktop app's total can never be printed beside the ADE button by accident —
   main.js keeps the two fetches and the two slots entirely apart. */
.js-adedl {
  display: none;
  align-items: center;
  gap: 8px;
}

.js-adedl.is-on {
  display: inline-flex;
}

.js-adedl b {
  color: var(--fg);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* Changelog page header (the feed items themselves are styled further up). */
.clog {
  padding-block: var(--s6) var(--s8);
}

.clog__inner {
  max-width: 74ch;
}

.clog__lead {
  margin-top: var(--s4);
  color: var(--muted);
  font-size: 1.0625rem;
}

/* ==========================================================================
   DOCS: code blocks, language tabs, endpoint headers, reference tables
   ==========================================================================
   Everything the API reference needs. Kept in one block because these only
   appear on docs.html and read as one component family.
   ========================================================================== */

/* --------------------------------------------------------------------------
   A multi-line code block.

   Deliberately built on `.cmd` rather than as a new component: main.js already
   binds copy-to-clipboard to `.cmd[data-copy]` and reaches inside for
   `.cmd__btn`, `.cmd__label` and `.cmd__text`. Reusing those names means code
   blocks get working copy buttons with no new JavaScript. The modifier only
   changes layout — a stacked block that wraps, instead of a single nowrap row.

   `data-copy` is left EMPTY on these: main.js falls back to the rendered text of
   .cmd__text, so a snippet can never drift from the attribute claiming to be it.
   -------------------------------------------------------------------------- */

.cmd--block {
  display: block;
  padding: 0;
  overflow: visible;
}

.cmd__head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 9px var(--s4);
  border-bottom: 1px solid var(--line);
}

.cmd__lang {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.cmd__head .cmd__btn {
  margin-left: auto;
}

.cmd--block .cmd__text {
  display: block;
  margin: 0;
  padding: var(--s4);
  overflow-x: auto;
  color: var(--fg);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.7;
  white-space: pre;
  tab-size: 2;
}

/* Minimal, hand-applied highlighting. Three roles only — there is no tokenizer
   here and inventing one for a docs page is not worth the weight. */
.t-c {
  color: var(--muted);
}

.t-s {
  color: var(--fg);
}

.t-k {
  color: var(--fg);
  font-weight: 500;
}

.t-p {
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Language tabs over a group of samples
   -------------------------------------------------------------------------- */

.samples {
  margin-top: var(--s4);
  border: 0;
  border-radius: var(--r);
  background: var(--surface);
  overflow: hidden;
}

.samples__tabs {
  display: flex;
  border-bottom: 1px solid var(--line);
}

.samples__tab {
  padding: 10px 16px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  cursor: pointer;
}

.samples__tab:hover {
  color: var(--fg);
}

.samples__tab[aria-selected="true"] {
  color: var(--fg);
  border-bottom-color: var(--fg);
}

/* The block inside a tab group already has the group's border. */
.samples .cmd {
  margin: 0;
  border: 0;
  background: none;
}

.samples .cmd__head {
  border-bottom: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
   An endpoint heading: METHOD + path
   -------------------------------------------------------------------------- */

.ep {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s5);
  padding: var(--s3) var(--s4);
  border: 0;
  border-radius: var(--r);
  background: var(--surface);
}

.ep__m {
  flex: none;
  padding: 3px 9px;
  background: var(--inverse-bg);
  color: var(--inverse-fg);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
}

/* GET is the safe, read-only one and gets the quieter treatment. */
.ep__m--get {
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
}

.ep__p {
  font-family: var(--mono);
  font-size: 0.9375rem;
  overflow-x: auto;
}

.ep__auth {
  margin-left: auto;
  flex: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   Reference tables — parameters, responses, error codes, plan limits
   -------------------------------------------------------------------------- */

.tablewrap {
  margin-top: var(--s4);
  overflow-x: auto;
}

.tablewrap table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

.tablewrap caption {
  margin-bottom: var(--s3);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-align: left;
  text-transform: uppercase;
}

.tablewrap th {
  padding: var(--s3) var(--s3) var(--s3) 0;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-align: left;
  text-transform: uppercase;
  white-space: nowrap;
}

.tablewrap td {
  padding: var(--s3) var(--s3) var(--s3) 0;
  border-bottom: 1px solid var(--line2, var(--line));
  color: var(--muted);
  vertical-align: top;
  line-height: 1.55;
}

.tablewrap tbody tr:last-child td {
  border-bottom: 0;
}

.tablewrap td:first-child {
  color: var(--fg);
  font-family: var(--mono);
  font-size: 0.8125rem;
  white-space: nowrap;
}

.tablewrap td code,
.tablewrap th code {
  padding: 1px 5px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125em;
  white-space: nowrap;
}

/* "required" / "optional" markers on a parameter row. */
.req,
.opt {
  display: inline-block;
  margin-left: var(--s2);
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: 1px;
}

.req {
  color: var(--fg);
}

.opt {
  color: var(--muted);
}

/* A row of small facts under a heading — base URL, auth scheme, and so on. */
.deflist {
  margin: var(--s4) 0 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--s3) var(--s5);
  font-size: 0.9375rem;
}

.deflist dt {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding-top: 2px;
}

.deflist dd {
  margin: 0;
  color: var(--fg);
}

.deflist dd code {
  padding: 1px 5px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.875em;
}

@media (max-width: 620px) {
  .deflist {
    grid-template-columns: 1fr;
    gap: var(--s1) 0;
  }

  .deflist dd {
    margin-bottom: var(--s3);
  }

  .ep__auth {
    margin-left: 0;
  }
}

/* ==========================================================================
   download.html — one page per way of getting Eaon
   ==========================================================================
   Numbered sections of equal-weight rows, modelled on the shape of
   opencode.ai/download: no "recommended" badge, no OS sniffing, no single giant
   button. Somebody arriving here already knows which machine they are on, and a
   flat list beats a hero button that guesses wrong.

   Eaon ships two apps and a CLI on three separate release tracks at different
   versions, so every row states its own version and size instead of inheriting
   one "latest" from the page.
   ========================================================================== */

.dlsec {
  padding-block: 0;
}

.dlsec__head {
  display: grid;
  grid-template-columns: 3ch 1fr;
  gap: var(--s4);
  align-items: baseline;
  margin-bottom: var(--s4);
}

/* The section index. It reads as a numbered clause in a document rather than
   as a decoration, so it takes the mono label treatment the rest of the page
   gives to chrome. */
.dlsec__n {
  color: var(--brand);
  font-family: var(--mono);
  font-size: 0.875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.dlsec__head p {
  margin-top: var(--s3);
  color: var(--muted);
}

/* One row per artefact. The grid keeps the platform, the metadata and the action
   on the same three columns down the whole list, so the eye scans one column
   rather than re-finding the button on every line. */
/* The rows become one bordered list divided by hairlines rather than a stack
   of separate filled tiles. A download list is a table of artefacts; a table
   has rules, not gaps. The wrapper gets its border from the first and last
   rows, so no extra element is needed in eight places of markup. */
.dlrow {
  display: grid;
  grid-template-columns: 3ch minmax(0, 1fr) auto;
  gap: var(--s4);
  align-items: center;
  padding: var(--s3) var(--s4);
  margin-bottom: var(--s2);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  transition: background-color 160ms var(--ease), border-color 160ms var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .dlrow:hover {
    background: var(--card-2);
    border-color: var(--line-2);
  }
}

.dlrow__plat {
  font-weight: 500;
}

.dlrow__meta {
  margin-top: 3px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
}

/* The action column keeps a fixed minimum so "Download" and "View" line up. */
.dlrow .btn {
  min-width: 11ch;
  justify-content: center;
}

.dlsec__note {
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-left: 2px solid var(--line-3);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--muted);
  font-size: 0.875rem;
  max-width: 68ch;
}

/* The CLI section stacks command blocks instead of rows. */
.dlsec .cmd {
  margin-top: var(--s3);
}

.dlsec .cmd:first-of-type {
  margin-top: 0;
}

@media (max-width: 620px) {
  .dlsec__head,
  .dlrow {
    grid-template-columns: 1fr;
    gap: var(--s2);
  }

  .dlrow {
    align-items: start;
  }

  .dlrow .btn {
    margin-top: var(--s2);
  }

  /* The leading number is decoration once the grid collapses; the heading and
     the platform name carry the structure on their own. */
  .dlsec__n,
  .dlrow > .dlsec__n {
    display: none;
  }
}

/* The per-product download count, sitting under that product's own rows.

   It used to be one number in the masthead, which on a page listing two apps
   belonged to neither: a visitor reading "127 downloads" could not tell whether
   that meant Eaon Desktop, ADE, or the two added together. Each section now
   carries its own, and they come from different places — eaon.dev counts the
   desktop app itself, while GitHub serves and counts ADE's installers.

   No `display` here on purpose. `.js-dl` and `.js-adedl` own that property to
   stay hidden until main.js has a real number, and setting it again would
   override the hidden state and print a zero. */
.dlsec__count {
  margin-top: var(--s4);
  align-items: center;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.75rem;
  max-width: none;
}

.dlsec__count b {
  color: var(--fg);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* A count directly above the caveat note would crowd it. */
.dlsec__count + .dlsec__note {
  margin-top: var(--s3);
}


/* ==========================================================================
   The demo stage: both apps, driven by a scripted cursor
   ==========================================================================

   onorca.dev's hero is its product, running: a tab strip over one big window,
   and each tab plays a short scene. This is the same idea built from Eaon's
   own parts. The windows are the source-accurate recreations defined above;
   demo.js moves a cursor over them, clicks, types and streams replies, then
   moves on to the next tab. The last Eaon tab swaps in the live window that
   actually talks to api.eaon.dev, so "try it yourself" means it.

   The scripted windows are pictures and are hidden from assistive tech; the
   caption under the stage says in words what each scene shows, and updates
   politely as the tabs change. */

/* ---- centred hero, onorca's shape ---- */

.hero--center {
  text-align: center;
}

.hero--center .hero__copy {
  width: auto;
  max-width: 46rem;
  margin-inline: auto;
}

.hero--center h1 {
  max-width: 18ch;
  margin-inline: auto;
}

.hero--center p {
  margin-inline: auto;
}

.hero--center .btn-row {
  justify-content: center;
}

.hero--center .notice {
  margin-inline: auto;
}

.hero__also {
  max-width: none !important;
  margin-top: var(--s3) !important;
  font-family: var(--mono);
  font-size: 12px !important;
  font-weight: 400 !important;
  color: var(--muted);
}

.hero__also a {
  color: inherit;
}

/* ---- tab strip ---- */

.demo {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.demo__tabs {
  display: flex;
  align-items: center;
  gap: 2px;
  max-width: 100%;
  margin: 0 auto var(--s4);
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  overflow-x: auto;
  scrollbar-width: none;
}

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

.demo__group {
  flex: none;
  padding: 0 8px 0 10px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}

.demo__sep {
  flex: none;
  width: 1px;
  height: 18px;
  margin: 0 6px;
  background: var(--line-2);
}

.demo__tab {
  position: relative;
  flex: none;
  height: 32px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--fg-2);
  font: inherit;
  font-size: 13.5px;
  white-space: nowrap;
  cursor: pointer;
  overflow: hidden;
  transition: background 160ms var(--ease), color 160ms var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .demo__tab:hover {
    color: var(--fg);
  }
}

.demo__tab[aria-pressed="true"] {
  background: var(--card-3);
  color: var(--fg);
}

/* How far through its scene the active tab is. Driven by demo.js through a
   custom property rather than a CSS duration, because scenes are sequences of
   real steps and their length is only known once they have run. */
.demo__tab::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 4px;
  height: 1px;
  background: var(--brand);
  transform: scaleX(var(--p, 0));
  transform-origin: left;
  opacity: 0;
}

.demo__tab[aria-pressed="true"]::after {
  opacity: 0.9;
}

/* ---- the frame the windows sit in ---- */

.demo__frame {
  position: relative;
  width: 100%;
  height: 720px;
}

.demo__screen {
  position: absolute;
  inset: 0;
}

.demo__screen[hidden] {
  display: none;
}

.demo__screen > .appwindow,
.demo__screen > .adewin {
  height: 100%;
  border-radius: var(--r-lg);
  box-shadow: 0 0 0 1px var(--line), 0 1px 1px rgb(0 0 0 / 0.4),
    0 30px 80px -20px rgb(0 0 0 / 0.75), inset 0 1px 0 rgb(255 255 255 / 0.06);
}

@media (max-width: 1100px) {
  .demo__frame {
    height: 620px;
  }
}

@media (max-width: 700px) {
  .demo__frame {
    height: 560px;
  }
}

.demo__caption {
  max-width: 60ch;
  min-height: 3.2em;
  margin: var(--s4) auto 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.9375rem;
  line-height: 1.6;
}

/* ---- the cursor ---- */

/* An arrow the size of the real macOS one, with a soft shadow so it reads on
   both the dark window and the light theme the ⌘K scene flips to. It moves on
   a single transform transition whose duration demo.js sets per move, from the
   distance travelled, so long moves take longer the way a hand does. */
.democursor {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 60;
  width: 22px;
  height: 22px;
  margin: -2px 0 0 -3px;
  pointer-events: none;
  opacity: 0;
  transform: translate(60%, 90%);
  transition-property: transform, opacity;
  transition-timing-function: cubic-bezier(0.33, 0.8, 0.25, 1), ease;
  transition-duration: 600ms, 200ms;
  filter: drop-shadow(0 2px 3px rgb(0 0 0 / 0.45));
}

.democursor.is-on {
  opacity: 1;
}

.democursor svg {
  width: 100%;
  height: 100%;
  transition: transform 120ms ease;
  transform-origin: 20% 10%;
}

.democursor.is-down svg {
  transform: scale(0.84);
}

/* The press, drawn where the click lands. */
.democursor__ring {
  position: absolute;
  top: 2px;
  left: 3px;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  border: 2px solid rgb(255 255 255 / 0.8);
  opacity: 0;
  transform: scale(0.3);
}

.democursor.is-down .democursor__ring {
  animation: demo-ring 420ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

@keyframes demo-ring {
  0% {
    opacity: 0.9;
    transform: scale(0.3);
  }
  100% {
    opacity: 0;
    transform: scale(1.3);
  }
}

/* A keyboard shortcut, shown at the foot of the window as it is pressed. */
.demokey {
  position: absolute;
  left: 50%;
  bottom: 22px;
  z-index: 61;
  padding: 6px 12px;
  border-radius: 10px;
  border: 1px solid rgb(255 255 255 / 0.18);
  background: rgb(20 20 20 / 0.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #fafafa;
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: 0.02em;
  opacity: 0;
  transform: translate(-50%, 8px) scale(0.96);
  transition: opacity 180ms ease, transform 240ms cubic-bezier(0.23, 1, 0.32, 1);
  pointer-events: none;
}

.demokey.is-on {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
}

/* ---- scripted Eaon Desktop: the pieces its scenes need ---- */

.appwindow__thread.is-home {
  justify-content: center;
}

/* The app's own empty state: `.home__title`, 28px at weight 400. */
.appwindow__home {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  text-align: center;
}

.appwindow__home h3 {
  margin: 0;
  font-size: 28px;
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--text);
}

.appwindow__home svg {
  width: 44px;
  height: 44px;
  color: var(--text-3);
  stroke-width: 1.3;
}

/* Eaon Work's four suggestion cards, with their real icon colours. */
.appwindow__sugg {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  width: min(552px, 100%);
}

.appwindow__sugg span {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border-radius: 12px;
  background: var(--surface-1);
  box-shadow: 0 0 0 1px var(--a-border-soft);
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1.35;
  text-align: left;
}

.appwindow__sugg svg {
  width: 18px;
  height: 18px;
  stroke-width: 1.8;
}

/* Tool calls, as ToolCall.tsx draws them: a 32px row on --surface-1. */
.dtool {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 10px;
  margin-bottom: 6px;
  border: 1px solid var(--a-border-soft);
  border-radius: 10px;
  background: var(--surface-1);
  white-space: normal;
  animation: dtool-in 220ms cubic-bezier(0.32, 0.72, 0, 1) both;
}

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

.dtool svg {
  width: 14px;
  height: 14px;
  flex: none;
  color: var(--text-3);
  stroke-width: 1.9;
}

.dtool__label {
  flex: none;
  font-size: 13px;
  color: var(--text);
}

.dtool__detail {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
  font-size: 12px;
  color: var(--text-3);
}

.dtool__add {
  color: #3fb950;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
}

.dtool__del {
  color: #f85149;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
}

/* Running: the glyph spins until the step lands. */
.dtool.is-running svg {
  animation: dtool-spin 900ms linear infinite;
}

@keyframes dtool-spin {
  to {
    transform: rotate(360deg);
  }
}

.dcode {
  margin: 10px 0;
  padding: 12px 14px;
  border-radius: 10px;
  background: #0d0d0d;
  box-shadow: 0 0 0 1px var(--a-border-soft);
  font-family: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
  font-size: 12.5px;
  line-height: 1.55;
  color: #e6edf3;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.dcode b {
  color: #79c0ff;
  font-weight: 400;
}

.appwindow__msg--assistant p {
  margin: 0;
  font-size: 14px;
}

.appwindow__msg--assistant p + p,
.appwindow__msg--assistant .dtool + p {
  margin-top: 8px;
}

/* The model control in the composer: a quiet button, as in the app. */
.appwindow__modelbtn {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 8px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text-2);
  font: inherit;
  font-size: 12.5px;
  white-space: nowrap;
  transition: background var(--a-dur) var(--a-ease);
}

.appwindow__modelbtn.is-hover {
  background: var(--a-hover);
  color: var(--text);
}

.appwindow__modelbtn svg {
  width: 12px;
  height: 12px;
  stroke-width: 2;
}

.appwindow__tag {
  padding: 1px 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent), transparent 80%);
  color: color-mix(in srgb, var(--accent), #ffffff 45%);
  font-size: 10.5px;
  font-weight: 500;
}

/* Menus: `--menu-bg` is translucent so the backdrop blur has something to
   frost, with the app's own menu shadow. */
.dmenu {
  position: absolute;
  z-index: 20;
  width: 280px;
  padding: 5px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--surface-3), transparent 12%);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 0 0 0.5px rgb(0 0 0 / 0.5), 0 10px 32px rgb(0 0 0 / 0.44),
    0 2px 8px rgb(0 0 0 / 0.3), inset 0 0 0 1px var(--a-border-soft);
  opacity: 0;
  transform: translateY(6px) scale(0.98);
  transform-origin: bottom right;
  pointer-events: none;
  transition: opacity 140ms ease, transform 200ms cubic-bezier(0.32, 0.72, 0, 1);
}

.dmenu.is-open {
  opacity: 1;
  transform: none;
}

.dmenu--model {
  right: max(24px, calc((100% - 640px) / 2) + 6px);
  bottom: 70px;
}

.dmenu__label {
  padding: 8px 8px 4px;
  color: var(--text-3);
  font-size: 11px;
  font-weight: 500;
}

.dmenu__row {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  padding: 0 8px;
  border-radius: 7px;
  color: var(--text);
  font-size: 13px;
}

.dmenu__row small {
  margin-left: auto;
  color: var(--text-3);
  font-size: 11.5px;
}

.dmenu__row.is-hover {
  background: var(--accent);
  color: #fff;
}

.dmenu__row.is-hover small {
  color: rgb(255 255 255 / 0.8);
}

.dmenu__row svg {
  width: 14px;
  height: 14px;
  flex: none;
  stroke-width: 2;
}

/* ⌘K: the command bar, centred near the top of the window. */
.dmenu--palette {
  left: 50%;
  top: 70px;
  width: min(520px, calc(100% - 48px));
  margin-left: calc(min(520px, 100% - 48px) / -2);
  transform-origin: top center;
}

.dmenu__search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--a-border-soft);
  color: var(--text);
  font-size: 14px;
}

.dmenu__search svg {
  width: 15px;
  height: 15px;
  color: var(--text-3);
  stroke-width: 1.9;
}

.dmenu__search:empty::before {
  content: none;
}

/* The approval modal, as ChatView's ApprovalPrompt: 440px, Deny and Approve. */
.dmodal {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: grid;
  place-items: center;
  background: rgb(0 0 0 / 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
}

.dmodal.is-open {
  opacity: 1;
}

.dmodal__card {
  width: min(440px, calc(100% - 48px));
  padding: 18px;
  border-radius: 14px;
  background: var(--surface-3);
  box-shadow: 0 24px 64px rgb(0 0 0 / 0.55), 0 2px 12px rgb(0 0 0 / 0.4),
    inset 0 0 0 1px var(--a-border-soft);
  transform: scale(0.97);
  transition: transform 220ms cubic-bezier(0.32, 0.72, 0, 1);
}

.dmodal.is-open .dmodal__card {
  transform: none;
}

.dmodal__title {
  margin: 0 0 12px;
  font-size: 15px;
  font-weight: 500;
  color: var(--text);
}

.dmodal__cmd {
  margin: 0;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--sunken);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12.5px;
  color: var(--text-2);
}

.dmodal__row {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

.dbtn {
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 14px;
  border: 0;
  border-radius: 8px;
  background: var(--a-hover-strong);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  transition: filter 120ms ease, transform 120ms ease;
}

.dbtn--danger {
  background: #ff453a;
  color: #fff;
}

.dbtn.is-press {
  transform: scale(0.96);
  filter: brightness(1.1);
}

/* The composer's own text while a scene types into it. */
.appwindow__typed {
  display: block;
  padding: 13px 11px 8px;
  min-height: 46px;
  color: var(--text);
  font-size: 14px;
  line-height: 1.45;
  white-space: pre-wrap;
}

.appwindow__typed:empty::before {
  content: attr(data-placeholder);
  color: var(--text-3);
}

.appwindow__typed.is-typing::after {
  content: "";
  display: inline-block;
  width: 1.5px;
  height: 1.05em;
  margin-left: 1px;
  vertical-align: text-bottom;
  background: var(--accent);
}

.appwindow__send.is-live {
  background: var(--text);
}

/* The ⌘K scene flips the window to the app's own light palette. Every surface
   above is a mix of these few values, so overriding them re-tones the whole
   window the way Appearance does in the app. */
.appwindow[data-look="light"] {
  --bg: #ffffff;
  --fg: #1a1c1f;
  --accent: #339cff;
  --contrast: 45;
  --lift-color: #000000;
  --canvas: #ffffff;
  --surface-1: color-mix(in srgb, #ffffff, #000000 calc(var(--u) * 1.5));
  --surface-2: color-mix(in srgb, #ffffff, #000000 calc(var(--u) * 2.4));
  --surface-3: #ffffff;
  --sunken: color-mix(in srgb, #ffffff, #000000 calc(var(--u) * 1.8));
  --a-hover: rgba(0, 0, 0, 0.045);
  --a-hover-strong: rgba(0, 0, 0, 0.07);
  --a-border-soft: rgba(0, 0, 0, 0.07);
  --a-border-strong: rgba(0, 0, 0, 0.16);
  --text-2: color-mix(in srgb, #1a1c1f, #ffffff 34%);
  --text-3: color-mix(in srgb, #1a1c1f, #ffffff 52%);
  --text-4: color-mix(in srgb, #1a1c1f, #ffffff 66%);
  --send-bg: #1a1a1a;
  --send-fg: #ffffff;
}

.appwindow--demo,
.appwindow--demo .appwindow__panel,
.appwindow--demo .appwindow__msg--user,
.appwindow--demo .appwindow__composer,
.appwindow--demo .appwindow__mode,
.appwindow--demo .appwindow__modeopt {
  transition: background-color 420ms ease, color 420ms ease, border-color 420ms ease,
    box-shadow 420ms ease;
}

.appwindow--demo button {
  cursor: default;
}

/* ==========================================================================
   Feature cards: each one a short looping clip of the thing it describes
   ========================================================================== */

/* onorca shows a grid of screen recordings. These are the same idea built as
   live HTML from each app's own palette rather than video files: sharper at
   any size, a few kilobytes instead of megabytes, and they can never drift
   out of date with a UI that has since changed without anyone re-recording.
   Each clip only runs while its card is on screen (demo.js toggles
   `.is-playing`) and none of them run under reduced motion, where each shows
   its most representative frame instead.

   `.fcard__art` is a drop-in slot: a real recording can replace the clip with
   a `<video autoplay muted loop playsinline>` and the card needs no other
   change. */

.fhead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s5);
}

.fhead h2 {
  margin: 0;
  max-width: 22ch;
}

.fhead p {
  margin: var(--s3) 0 0;
  max-width: 54ch;
  color: var(--muted);
  line-height: 1.6;
}

.fhead .btn {
  flex: none;
}

/* Two across, not three: each card is a real app window, and at a third of
   the rail its type would be too small to read. */
.fgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4);
}

.fcard {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--card);
  overflow: hidden;
  scroll-margin-top: 96px;
}

.fcard__art {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  /* The wallpaper each little window floats over. Tinted toward the app it
     belongs to: Desktop's blue, ADE's aqua. */
  background:
    radial-gradient(120% 90% at 15% 0%, rgb(1 105 204 / 0.38), transparent 60%),
    radial-gradient(90% 80% at 100% 100%, rgb(120 90 255 / 0.22), transparent 60%),
    #0b0d12;
}

.fgrid--ade .fcard__art {
  background:
    radial-gradient(120% 90% at 85% 0%, rgb(241 116 85 / 0.28), transparent 60%),
    radial-gradient(90% 80% at 0% 100%, rgb(242 193 78 / 0.12), transparent 60%),
    #0a0a0a;
}

.fcard__art video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.fcard__body {
  padding: var(--s4);
}

.fcard__body h3 {
  margin: 0 0 var(--s2);
}

.fcard__body p {
  margin: 0;
  color: var(--muted);
  font-size: 0.9375rem;
  line-height: 1.6;
}

.fcard__key {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg-2);
  vertical-align: 2px;
}

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

@media (max-width: 640px) {
  .fhead {
    display: block;
  }

  .fhead .btn {
    margin-top: var(--s4);
  }
}

/* Each card plays one feature in the real app. demo/desktop/ and demo/ade/
   are both apps rebuilt from their own stylesheets; a card loads one at a
   real window size, 1100 x 688, and demo.js scales it to fit (--fit), so the
   window keeps its true proportions at every width. */
.fcard__art iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 1100px;
  height: 688px;
  border: 0;
  transform-origin: 0 0;
  transform: scale(var(--fit, 0.5));
  pointer-events: none;
}

/* The hero and the demo under it are one moment: the pitch, then the product
   doing it. No section break between them. */
.hero + .band--demo > .rail {
  padding-top: var(--s2);
}

/* ---- the ADE screen ----
   ADE is drawn at a fixed 1440 x 810, the size of a real window, and scaled to
   the frame by demo.js, so it keeps its true proportions at every width: a
   phone sees the whole app, smaller, rather than a squeezed one. The frame
   takes the window's 16:9 shape while ADE is the tab in front. */
.demo__frame:has(.demo__screen--ade:not([hidden])) {
  height: auto;
  aspect-ratio: 16 / 9;
}

.demo__screen--ade {
  overflow: hidden;
  border-radius: var(--r-lg);
  background: #0a0a0a;
  box-shadow: 0 0 0 1px var(--line), 0 1px 1px rgb(0 0 0 / 0.4),
    0 30px 80px -20px rgb(0 0 0 / 0.75), inset 0 1px 0 rgb(255 255 255 / 0.06);
}

.demo__ade {
  display: block;
  width: 1440px;
  height: 810px;
  border: 0;
  transform-origin: 0 0;
  pointer-events: none;
}
