/* ==========================================================================
   Eaon — monochrome, framed, and grounded in the real app
   ==========================================================================

   Structural decisions, unchanged from the previous pass:

   1. A CENTRED BORDERED COLUMN. `.frame` is a fixed-width column with a hairline
      down each side, centred on a slightly darker page, rails unbroken from the
      header through the footer, so the page reads as a sheet laid on the page
      rather than a layout that merely has a max-width.

   2. LEFT-ALIGNED INSIDE. The column is centred; its contents are not.

   3. Colour stays two backgrounds, two text weights, one hairline, per theme.
      Dark is the default and does NOT follow the OS; light is opt-in.

   Typography changed from monospace-everywhere to a three-role split, restoring
   this site's own documented historical system (Space Grotesk display / DM Sans
   body / JetBrains Mono chrome) rather than the opencode.ai-style all-mono
   treatment tried previously. Two independent reasons converged on the same
   answer: it is what this project used before the monochrome rebuild, AND it is
   what the real Eaon app itself defaults to (`FontPreferenceStore` ships Space
   Grotesk as the fresh-install default, used for both prose and — unusually —
   code). Matching the app's own typographic choice is a small, honest signal
   that this page describes a real, considered product rather than a template.

   Mono is kept, deliberately, for exactly the roles the real app itself uses it
   for: small chrome (sidebar labels, model-picker text, nav, buttons, stat
   captions) rather than general reading text. Headlines and prose read in Space
   Grotesk / DM Sans, which is both more legible at paragraph length and closer
   to what a general audience — not only developers — expects from a product
   they are about to trust with a download.

   The one accent colour on the whole page is the real brand mark's own coral
   (#F17455, `AquaBrand.accent` in the app's own theme file) — used only where
   the actual app uses it: the logo. Every other pixel stays monochrome.
   ========================================================================== */

:root {
  /* Dark — the default. */
  --bg-page: #000000;
  --bg: #0d0d0d;
  --bg-sunken: #060606;
  --fg: #e9e9e9;
  --muted: #8b8b8b;
  --line: #262626;
  --inverse-bg: #f2f2f2;
  --inverse-fg: #0d0d0d;

  /* The one real accent: the brand mark's actual colour, nowhere else. */
  --brand: #f17455;

  --frame: 1180px;
  --measure: 66ch;

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

  /* Three roles, not one. See the note above for why. */
  --display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  --body-font: "DM Sans", ui-sans-serif, system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  color-scheme: dark;
}

[data-theme="light"] {
  --bg-page: #eeeeee;
  --bg: #ffffff;
  --bg-sunken: #fafafa;
  --fg: #111111;
  --muted: #6a6a6a;
  --line: #e3e3e3;
  --inverse-bg: #111111;
  --inverse-fg: #ffffff;

  color-scheme: light;
}

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

html {
  -webkit-text-size-adjust: 100%;
  /* The header is sticky, so an in-page anchor would land underneath it. */
  scroll-padding-top: 88px;
  background: var(--bg-page);
}

body {
  margin: 0;
  background: var(--bg-page);
  color: var(--fg);
  font-family: var(--body-font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

/* --------------------------------------------------------------------------
   The frame — the centred column with visible rails
   -------------------------------------------------------------------------- */

.frame {
  max-width: var(--frame);
  margin-inline: auto;
  background: var(--bg);
  border-inline: 1px solid var(--line);
  /* Keeps the rails running to the bottom of the viewport on a short page, so
     they never stop mid-air. */
  min-height: 100vh;
}

/* Inner padding lives here, not on .frame, so a section can span the full column
   width for its own hairline while its text still lines up with everything else. */
.pad {
  padding-inline: clamp(var(--s4), 5vw, var(--s7));
}

.section {
  padding-block: var(--s8);
  border-top: 1px solid var(--line);
}

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

/* A band sitting slightly deeper than the column, for rhythm without colour. */
.section--sunken {
  background: var(--bg-sunken);
}

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

h1,
h2,
h3 {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

h1 {
  /* Capped well under the 6-8 word poster range: the headline here is longer
     than that, so a smaller ceiling keeps it to two lines instead of forcing a
     font-size error into a line-count problem. */
  font-size: clamp(2rem, 4vw, 3rem);
}

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

h3 {
  font-family: var(--body-font);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

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

/* The chrome layer: anything that is UI furniture rather than reading text stays
   in mono, matching how the real app itself uses its font (sidebar labels,
   model-picker text, stat captions — never body prose). */
.mono {
  font-family: var(--mono);
}

/* Underlines are a hairline border rather than text-decoration, so they sit on
   the same 1px grid as every other rule on the page. */
a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}

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

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

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

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

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

.site-head {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}

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

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

.brand__mark {
  width: 22px;
  height: 22px;
  flex: none;
}

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

.site-nav a {
  font-family: var(--mono);
  font-size: 0.875rem;
  color: var(--muted);
  border-bottom: 0;
}

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

/* Square hairline button. Shows the theme you would get by pressing it. */
.theme-toggle {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--line);
  background: none;
  color: var(--muted);
  cursor: pointer;
}

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

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

/* Only one of the two icons is ever shown. Swapped in CSS off the theme
   attribute so it cannot disagree with the palette, even before JS runs. */
.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
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 10px 20px;
  border: 1px solid var(--line);
  background: none;
  color: var(--fg);
  font-family: var(--mono);
  font-size: 0.875rem;
  cursor: pointer;
  transition: background-color 120ms linear, color 120ms linear, border-color 120ms linear;
}

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

/* The only filled element on the page, reserved for the primary action so the
   fill actually means something. */
.btn--solid {
  background: var(--inverse-bg);
  border-color: var(--inverse-bg);
  color: var(--inverse-fg);
}

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

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

/* --------------------------------------------------------------------------
   Hero — one headline, then a card for each of the two products.

   The previous hero was an asymmetric split selling one app. Eaon is two apps,
   and a hero that only showed the desktop one left half the product invisible
   below the fold. It is also the wrong shape for the problem: the two apps have
   genuinely different audiences (someone who wants to chat with a model, and
   someone who wants to hand a repo to agents), so one set of hero copy could
   only ever be vague enough to cover both badly.

   So: a short shared headline for the idea they have in common, then two cards
   that each get to make their own case in their own language, side by side and
   at equal weight. Neither is the "other" product.

   Each card leads with its own real visual rather than an icon, because what
   these apps look like IS the pitch — one is a chat window, one is a grid of
   terminals, and a glance tells them apart faster than any paragraph.

   Top padding stays capped low: hero content floating halfway down the viewport
   reads as a layout bug, not as intentional space.
   -------------------------------------------------------------------------- */

.hero {
  padding-block: var(--s5) var(--s8);
}

.hero__intro {
  margin-bottom: var(--s6);
}

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

.hero p {
  margin-top: var(--s4);
  color: var(--muted);
  max-width: 56ch;
  font-size: 1.0625rem;
}

/* The two product cards. Hairline-separated rather than boxed: a border on each
   would make two heavy rectangles fight the frame's own rails. */
.duo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s6);
}

.duo__card {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}

/* Both windows are pinned to one height and made to flex internally, so the two
   cards' copy starts on the same line. Left to their natural heights the chat
   window is ~40px taller than the terminal grid, which reads as a mistake rather
   than as two different products. */
.duo__visual {
  order: -1;
  height: 400px;
}

.duo__visual .appwindow,
.duo__visual .adewin {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.duo__visual .appwindow__body,
.duo__visual .adewin__grid {
  height: auto;
  flex: 1;
  min-height: 0;
}

.duo__kicker {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* .duo__name is an h2, so it needs to beat h2's own clamp() without shouting:
   these are product names inside a hero, not section headings. */
.duo .duo__name {
  margin-top: var(--s3);
  font-size: 1.5rem;
}

/* Scoped under .duo for the same reason .notice is scoped under .hero: these are
   <p> elements inside .hero, so the bare class would lose to `.hero p`. */
.duo .duo__what {
  margin-top: var(--s3);
  max-width: 42ch;
  font-size: 1rem;
}

.duo__facts {
  margin: var(--s4) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s2);
  font-size: 0.875rem;
}

.duo__facts li {
  display: grid;
  grid-template-columns: 9ch 1fr;
  gap: var(--s3);
}

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

.duo .btn-row {
  margin-top: var(--s5);
}

.duo .duo__alt {
  margin-top: var(--s3);
  max-width: none;
  font-size: 0.8125rem;
}

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

/* --------------------------------------------------------------------------
   The ADE window — a grid of terminals, which is what ADE literally is.

   Not a screenshot and not an invented dashboard. The shape is taken from the
   real app: panes top out at twelve (its LAYOUTS list is 1, 2, 4, 6, 8, 10, 12),
   every pane is a real pseudo-terminal running your login shell with a CLI agent
   inside it, the agent names are the five ADE actually supports (Claude Code,
   Codex, Gemini CLI, Aider, plain shell), and the accent edge is its real signal
   — colour is how ADE tells you which agents are working and which are waiting
   on you. ⌘J is the real Conductor shortcut for broadcasting one prompt.

   Six panes are drawn rather than twelve so the labels stay readable at this
   size; the titlebar says so rather than implying six is the maximum.

   Same isolated --a-* tokens as .appwindow: a real app window does not restyle
   itself to match the website showing it.
   -------------------------------------------------------------------------- */

.adewin {
  --a-bg: #121212;
  --a-side: #131313;
  --a-input: #1c1c1c;
  --a-text: #ececec;
  --a-text2: #b4b4b4;
  --a-text3: #8e8e9c;
  --a-border: rgb(255 255 255 / 0.1);
  --a-accent: #f17455;

  border-radius: 14px;
  overflow: hidden;
  background: var(--a-bg);
  border: 1px solid rgb(255 255 255 / 0.08);
  box-shadow: 0 30px 80px -20px rgb(0 0 0 / 0.55);
  font-family: var(--mono);
  font-size: 12px;
}

.adewin__tag {
  margin-left: auto;
  color: var(--a-text3);
  font-size: 10px;
}

.adewin__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  padding: 1px;
  background: var(--a-border);
  height: 300px;
}

.adepane {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 9px;
  background: var(--a-bg);
  border-top: 2px solid transparent;
  overflow: hidden;
}

/* Working: accent edge. Waiting on you: a brighter, colder edge. Idle: nothing.
   Three states, told by one border, exactly as the app does it. */
.adepane--busy {
  border-top-color: var(--a-accent);
}

.adepane--wait {
  border-top-color: var(--a-text2);
}

.adepane__bar {
  color: var(--a-text2);
  font-size: 9.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.adepane__ln {
  height: 5px;
  border-radius: 2px;
  background: rgb(255 255 255 / 0.09);
}

.adepane__ln--m {
  width: 70%;
}

.adepane__ln--s {
  width: 42%;
}

.adewin__conductor {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--a-input);
  border-top: 1px solid var(--a-border);
  color: var(--a-text3);
  font-size: 10.5px;
}

.adewin__key {
  margin-left: auto;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgb(255 255 255 / 0.08);
  color: var(--a-text2);
}

/* One small filled tag plus a line of text, for a single announcement. */
/* Scoped to .hero deliberately. `.notice` alone (0,1,0) loses to `.hero p`
   (0,1,1), so the 56ch hero measure kept applying and pushed the trailing link
   onto its own line — measured as a 470.4px cap, not the 68ch it looked like. */
.hero .notice {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s6);
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
  flex-wrap: wrap;
  max-width: none;
}

.tag {
  flex: none;
  padding: 2px 8px;
  background: var(--inverse-bg);
  color: var(--inverse-fg);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 700;
}

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

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

.install {
  margin-top: var(--s6);
  max-width: 680px;
  border: 1px solid var(--line);
}

.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."
   -------------------------------------------------------------------------- */

.trust {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s6);
  padding-block: var(--s4);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

.trust__item {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* 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;
}

/* --------------------------------------------------------------------------
   The app window — a real recreation, not an invented mockup.

   Every colour, label and layout relationship below is sourced from the actual
   Eaon-desktop Swift source (Theme/ThemeColors.swift, Views/SidebarView.swift,
   Services/EaonMode.swift, Views/ChatComposer.swift, Views/ModelPickerPopover.swift):
   dark-theme hex values, real sidebar copy ("Pinned", "Chats", the ⌘-shortcuts),
   the real mode names ("Chat" / "Eaon Work"), a real bundled model name ("Claude
   Sonnet 5"), the real composer placeholder pattern ("Ask {model} anything"),
   and the real rule that assistant turns get NO bubble and NO avatar — only a
   small model-attribution header — while user turns get a right-aligned filled
   bubble. The one thing genuinely composed rather than sourced is the sample
   conversation text itself: the app ships no seed/preview chat data, so there
   was nothing to lift verbatim for that one piece.

   Isolated custom properties (--a-*) rather than the page's own --bg/--fg: the
   real app does not restyle itself to match whatever theme the website showing
   it happens to be in, so this mockup renders in the app's actual dark theme
   even when the page itself is switched to light.
   -------------------------------------------------------------------------- */

.appwindow {
  --a-bg: #121212;
  --a-side: #131313;
  --a-input: #1c1c1c;
  --a-input2: #262626;
  --a-text: #ececec;
  --a-text2: #b4b4b4;
  --a-text3: #8e8e9c;
  --a-border: rgb(255 255 255 / 0.1);
  --a-accent: #f17455;

  border-radius: 14px;
  overflow: hidden;
  background: var(--a-bg);
  border: 1px solid rgb(255 255 255 / 0.08);
  box-shadow: 0 30px 80px -20px rgb(0 0 0 / 0.55);
  font-family: var(--mono);
  font-size: 12px;
}

.appwindow__titlebar {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding-inline: 14px;
  background: var(--a-side);
}

.appwindow__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.appwindow__dot:nth-child(1) {
  background: #ff5f57;
}

.appwindow__dot:nth-child(2) {
  background: #febc2e;
}

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

.appwindow__body {
  display: flex;
  height: 380px;
}

.appwindow__sidebar {
  width: 40%;
  flex: none;
  background: var(--a-side);
  border-right: 1px solid var(--a-border);
  padding: 12px 10px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: hidden;
}

.appwindow__modes {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--a-input);
  border-radius: 7px;
}

.appwindow__mode {
  flex: 1;
  text-align: center;
  padding: 5px 3px;
  border-radius: 5px;
  color: var(--a-text3);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.appwindow__mode--active {
  background: var(--a-input2);
  color: var(--a-text);
}

.appwindow__nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.appwindow__navrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 5px 6px;
  border-radius: 6px;
  color: var(--a-text2);
}

.appwindow__navrow span:last-child {
  color: var(--a-text3);
  font-size: 10px;
}

.appwindow__seclabel {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--a-text3);
  padding: 0 6px;
  margin-top: 2px;
}

.appwindow__chat {
  padding: 6px 6px;
  border-radius: 6px;
  color: var(--a-text2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.appwindow__chat--active {
  background: rgb(255 255 255 / 0.11);
  color: var(--a-text);
}

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

.appwindow__topbar {
  display: flex;
  align-items: center;
  height: 38px;
  padding-inline: 14px;
  border-bottom: 1px solid var(--a-border);
}

.appwindow__model {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--a-input2);
  color: var(--a-text);
  font-weight: 500;
}

.appwindow__model svg {
  width: 13px;
  height: 13px;
  flex: none;
}

.appwindow__model .chev {
  color: var(--a-text3);
  margin-left: 2px;
}

.appwindow__thread {
  flex: 1;
  padding: 16px 16px 8px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  overflow: hidden;
}

.appwindow__msg--user {
  align-self: flex-end;
  max-width: 78%;
  padding: 8px 12px;
  border-radius: 12px;
  background: var(--a-input);
  color: var(--a-text);
  font-family: var(--body-font);
  line-height: 1.5;
}

.appwindow__msg--assistant {
  max-width: 88%;
}

.appwindow__attribution {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
  color: var(--a-text3);
  font-size: 10.5px;
  font-weight: 500;
}

.appwindow__attribution svg {
  width: 12px;
  height: 12px;
  flex: none;
}

.appwindow__msg--assistant p {
  color: var(--a-text);
  font-family: var(--body-font);
  line-height: 1.6;
  max-width: none;
}

.appwindow__composer {
  margin: 0 16px 16px;
  padding: 10px 12px;
  border-radius: 20px;
  background: var(--a-input);
  border: 1px solid var(--a-border);
  display: flex;
  align-items: center;
  gap: 10px;
}

.appwindow__composer span {
  flex: 1;
  color: var(--a-text3);
  font-family: var(--body-font);
}

.appwindow__send {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--a-text);
  color: var(--a-bg);
  display: grid;
  place-items: center;
  flex: none;
}

.appwindow__send svg {
  width: 13px;
  height: 13px;
}

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

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

.head p {
  margin-top: var(--s4);
  color: var(--muted);
}

.rows {
  border-top: 1px solid var(--line);
}

.row {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: var(--s5);
  padding-block: var(--s5);
  border-bottom: 1px solid var(--line);
  align-items: baseline;
}

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

/* 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. */
.features {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s7) var(--s6);
}

.features h3 {
  margin-bottom: var(--s2);
}

.features p {
  color: var(--muted);
  max-width: 40ch;
}

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

.faq {
  border-top: 1px solid var(--line);
  max-width: var(--measure);
}

.faq details {
  border-bottom: 1px solid var(--line);
}

.faq summary {
  display: flex;
  justify-content: space-between;
  gap: var(--s4);
  padding-block: var(--s4);
  cursor: pointer;
  list-style: none;
}

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

.faq summary::after {
  content: "+";
  color: var(--muted);
}

.faq details[open] summary::after {
  content: "-";
}

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

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

.site-foot {
  border-top: 1px solid var(--line);
  padding-block: var(--s7) var(--s6);
}

.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.75rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.foot-cols ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s3);
}

.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-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

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

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

.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 square hairline button as the theme toggle in the header. */
.iconbtn {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  color: var(--muted);
}

.iconbtn:hover {
  border-color: var(--fg);
  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);
  }

  .row {
    grid-template-columns: 1fr;
    gap: var(--s2);
  }

  /* The two product cards stack before the features grid does: each card holds a
     window mockup, which gets cramped long before a text column would. */
  .duo {
    grid-template-columns: 1fr;
    gap: var(--s7);
  }

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

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

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

  /* Below this the rails cost more than they add — the gutters would be a few
     wasted pixels either side. */
  .frame {
    border-inline: 0;
  }

  .site-nav a[data-secondary] {
    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;
  }

  .appwindow__body {
    height: 400px;
  }
}

@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`,
      `.milestone__card`, 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; the launch promo works inversely and gets
      `hidden` once the real total passes its cap. 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
   -------------------------------------------------------------------------- */

.phead {
  padding-block: var(--s7) var(--s6);
  border-bottom: 1px solid var(--line);
}

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

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

/* 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: 6px 14px;
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

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

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

.prod {
  padding-block: var(--s8);
  border-top: 1px solid var(--line);
}

.prod:first-of-type {
  border-top: 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: 60px;
  height: 60px;
  flex: none;
  border-radius: 14px;
  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(--s5);
  max-width: 62ch;
  color: var(--muted);
  font-size: 1.0625rem;
}

/* A definition list, so the facts are marked up as the pairs they are. */
.prod__facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s4);
  margin: var(--s6) 0 0;
  padding-block: var(--s4);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

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

.prod__facts dd {
  margin: var(--s2) 0 0;
  font-size: 0.9375rem;
}

.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. */
.prod__warn {
  margin-top: var(--s4);
  padding-left: var(--s3);
  border-left: 2px solid var(--muted);
  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(--s6);
  border-top: 1px solid var(--line);
}

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

.compare__row--head {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  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: var(--s8);
  border-top: 1px solid var(--line);
}

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

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

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

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

/* --------------------------------------------------------------------------
   The launch promo and announcement bar.

   Inverse of the counters: these start VISIBLE and main.js sets `hidden` on
   them once the real download total passes the offer's cap, so the offer
   disappears when it stops being true rather than when nobody is looking.
   -------------------------------------------------------------------------- */

.annbar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding-block: var(--s3);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

.annbar[hidden],
.promo[hidden] {
  display: none;
}

.promo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s5);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  font-size: 0.875rem;
  color: var(--muted);
}

.promo__badge {
  flex: none;
  padding: 2px 8px;
  background: var(--inverse-bg);
  color: var(--inverse-fg);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   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;
}

/* --------------------------------------------------------------------------
   The download milestone dialog (features.html).

   main.js drives .is-open (display) then .is-visible (transition) on the next
   frame, and puts .no-scroll on <body>. It focuses .milestone__card, which
   therefore needs a tabindex in the markup.
   -------------------------------------------------------------------------- */

.milestone {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100;
  place-items: center;
  padding: var(--s4);
}

.milestone.is-open {
  display: grid;
}

.milestone__scrim {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 0.6);
  opacity: 0;
  transition: opacity 200ms linear;
}

.milestone.is-visible .milestone__scrim {
  opacity: 1;
}

.milestone__card {
  position: relative;
  width: min(440px, 100%);
  padding: var(--s6) var(--s5) var(--s5);
  background: var(--bg);
  border: 1px solid var(--line);
  text-align: center;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 200ms linear, transform 200ms ease;
}

.milestone.is-visible .milestone__card {
  opacity: 1;
  transform: none;
}

.milestone__close {
  position: absolute;
  top: var(--s3);
  right: var(--s3);
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

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

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

.milestone__mark {
  width: 40px;
  height: 40px;
  margin: 0 auto var(--s4);
}

.milestone__eyebrow {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.milestone__num-wrap {
  margin-top: var(--s4);
}

.milestone__num-wrap[hidden] {
  display: none;
}

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

.milestone__num {
  display: block;
  font-family: var(--display);
  font-size: 2.5rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.milestone__title {
  margin-top: var(--s4);
}

.milestone__sub {
  margin: var(--s3) auto 0;
  max-width: 34ch;
  color: var(--muted);
  font-size: 0.9375rem;
}

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

body.no-scroll {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   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);
  background: var(--bg-sunken);
  color: var(--muted);
  font-size: 0.9375rem;
}

.docs__note p {
  margin-top: 0;
}

.docs__note--warn {
  border-left: 2px solid var(--fg);
}

.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(--bg-sunken);
  border: 1px solid var(--line);
}

.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;
}

.nf__code {
  display: block;
  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: 1px solid var(--line);
  background: var(--bg-sunken);
  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: 1px solid var(--line);
  background: var(--bg-sunken);
  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: 1px solid var(--line);
}

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

.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: 1px solid var(--line);
  background: var(--bg-sunken);
}

.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: var(--s7);
  border-top: 1px solid var(--line);
}

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

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

.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. */
.dlrow {
  display: grid;
  grid-template-columns: 3ch minmax(0, 1fr) auto;
  gap: var(--s4);
  align-items: center;
  padding-block: var(--s4);
  border-top: 1px solid var(--line);
}

.dlrow:last-child {
  border-bottom: 1px solid var(--line);
}

.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-left: var(--s3);
  border-left: 2px solid var(--line);
  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);
}

/* Each product card carries its own download count as one more fact row.

   A single count under both cards was ambiguous: it read as the desktop app, as
   ADE, or as the pair added up, and it was only ever the first. The two numbers
   also come from different places, so they could never have been one figure —
   eaon.dev counts the desktop app, GitHub counts ADE's installers.

   `.js-dl.is-on` and `.js-adedl.is-on` set display to flex and inline-flex, which
   would drop these rows out of the label/value grid the other facts sit on.
   Higher specificity puts them back on it. */
.duo__facts .js-dl.is-on,
.duo__facts .js-adedl.is-on {
  display: grid;
}

.duo__facts b {
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
