/* ============================================================
   XRONE — flawed audio by design
   Dependency-free stylesheet. Swiss grid · editorial layout.

   System
     · Palette      1 ground · 1 nuance · 2 inks — no pure black, no pure white
     · Spacing      8px scale, --s1 … --s10 (no arbitrary values)
     · Grid         12 columns, --maxw, --gutter
     · Type         6 steps · Space Grotesk (400/700) + IBM Plex Mono (400)
   ============================================================ */

/* ---------- Fonts ----------
   Self-hosted. No third-party request, no visitor IP leaked to a CDN.
   Both families: SIL Open Font License 1.1 — see assets/fonts/OFL-*.txt
   Subsets: latin + latin-ext. Space Grotesk is variable (300–700). */

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(fonts/space-grotesk-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(fonts/space-grotesk-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/ibm-plex-mono-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/ibm-plex-mono-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
ul, ol { list-style: none; }
table { width: 100%; border-collapse: collapse; }

/* ---------- Tokens ---------- */
:root {
  /* colour — one ground, one nuance, two inks. no gradient, no shadow, no accent */
  --ink:       #111111;   /* the ground: every section sits on it */
  --line:      #222222;   /* the nuance: hairlines, separations, raised plates */
  --stone:     #82817C;   /* secondary text: labels, meta, descriptions */
  --alabaster: #f2f0e6;   /* primary text, titles, active states */

  /* spacing — 8px base, used everywhere */
  --s1: 0.25rem;  /*  4 */
  --s2: 0.5rem;   /*  8 */
  --s3: 0.75rem;  /* 12 */
  --s4: 1rem;     /* 16 */
  --s5: 1.5rem;   /* 24 */
  --s6: 2rem;     /* 32 */
  --s7: 3rem;     /* 48 */
  --s8: 4rem;     /* 64 */
  --s9: 6rem;     /* 96 */
  --s10: 8rem;    /* 128 */

  /* rhythm derived from the scale */
  --pad-y: clamp(var(--s8), 8vw, var(--s10));
  --pad-x: clamp(var(--s4), 4vw, var(--s7));

  /* grid */
  --maxw:   1120px;
  --gutter: clamp(var(--s4), 2vw, var(--s5));
  --header-h: 72px;

  /* type */
  --font: 'Space Grotesk', system-ui, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;
  --fs-mast:    clamp(2.75rem, 11vw, 7rem);      /* plugin masthead */
  --fs-display: clamp(1.75rem, 5vw, 3.25rem);    /* hero */
  --fs-title:   clamp(1.4rem, 3.4vw, 2rem);      /* section titles, menu */
  --fs-lead:    clamp(1rem, 1.4vw, 1.125rem);    /* lede */
  --fs-body:    0.875rem;                        /* body, lists, tables */
  --fs-label:   0.75rem;                         /* mono micro-labels */
  --ls-tight:  -0.03em;
  --ls-mono:    0.08em;   /* the one tracking of the mono voice */

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}
@media (max-width: 639px) { :root { --header-h: 60px; } }

/* ---------- Base ---------- */
body {
  font-family: var(--font);
  font-size: var(--fs-body);
  background: var(--ink);
  color: var(--alabaster);
  line-height: 1.5;
  margin-top: var(--header-h);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
[id] { scroll-margin-top: var(--header-h); }

/* ---------- Primitives ---------- */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--pad-x); }
.grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gutter); }
.section { padding-block: var(--pad-y); }
.rule { height: 1px; background: var(--line); border: 0; }
.divider { height: 1px; background: var(--line); }

/* mono micro-label — lowercase, set apart by tracking rather than by case */
.label {
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-mono);
  text-transform: lowercase;
  color: var(--alabaster);
}
.label-dim { color: var(--stone); }

.title { font-size: var(--fs-title); font-weight: 700; letter-spacing: var(--ls-tight); color: var(--alabaster); line-height: 1.1; }
.lede { font-size: var(--fs-lead); color: var(--alabaster); }

/* le seul texte encadré du site : un fait qu'on veut lire au premier coup d'œil
   (« free »). Il se compose avec .label — la voix mono, en bas de casse, encre
   principale — et n'ajoute que le filet. Aucune couleur d'accent : c'est le
   cadre qui fait l'accent. */
.tag { display: inline-block; border: 1px solid var(--line); padding: var(--s2) var(--s3); }

/* ---------- Links ----------
   one rule for every clickable text on the site: --stone at rest, --alabaster on hover.
   no underline anywhere — the colour shift is the whole affordance.
   the two image links (brand, coffee) follow the same states through `filter`, since
   there is no text to colour. exempt by design, each carrying its own state: the menu
   sitemap (.menu-link, .menu-sublink) and the plugin cards, whose title is content and
   whose hover lights the rule. The submit button and the gallery arrows are not <a>. */
a:not(.menu-link, .menu-sublink, .plugin-card, .brand) {
  color: var(--stone);
  transition: color 0.15s var(--ease);
}
a:not(.menu-link, .menu-sublink, .plugin-card, .brand):hover { color: var(--alabaster); }

/* ---------- Loading screen ---------- */
#loading-screen { position: fixed; inset: 0; z-index: 60; background: var(--ink); transition: opacity 0.5s var(--ease); }
#loading-screen.hide { opacity: 0; pointer-events: none; }

/* ---------- Header (identical on every page) ---------- */
#header {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--header-h);
  z-index: 50;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--gutter);
  padding-inline: var(--pad-x);
  background: var(--ink);
  border-bottom: 1px solid var(--line);
}
#header .brand img { height: 32px; filter: invert(1) brightness(0.54); transition: filter 0.15s var(--ease); }
#header .brand:hover img { filter: invert(1); }

.icon-btn { width: 44px; height: 44px; display: grid; place-items: center; margin-right: calc(var(--s3) * -1); }
.icon-btn svg { width: 24px; height: 24px; stroke: var(--stone); transition: stroke 0.15s var(--ease); }
.icon-btn:hover svg { stroke: var(--alabaster); }
.icon-btn .ico-close { display: none; }
body.menu-open .icon-btn .ico-open { display: none; }
body.menu-open .icon-btn .ico-close { display: block; }

/* ---------- Menu (full-screen sitemap, same grid) ---------- */
#menu {
  position: fixed;
  inset: 0;
  z-index: 40;                 /* under the header (50) so the cross stays on top */
  background: var(--ink);
  padding-top: var(--header-h);
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-0.5rem);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease), visibility 0.35s;
  overflow-y: auto;
}
#menu.open { opacity: 1; visibility: visible; transform: none; }

.menu-map { flex: 1; align-content: center; padding-block: var(--s8); }
.menu-col { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s2); grid-column: span 12; }
.menu-col + .menu-col { padding-top: var(--s7); border-top: 1px solid var(--line); margin-top: var(--s5); }
@media (min-width: 900px) {
  .menu-col:nth-child(1) { grid-column: 1 / span 7; }
  .menu-col:nth-child(2) { grid-column: 9 / span 4; padding-top: 0; margin-top: 0; border-top: 0; border-left: 1px solid var(--line); padding-left: var(--s6); }
}
.menu-cat { margin-bottom: var(--s5); }
.menu-link {
  font-size: var(--fs-title);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: var(--ls-tight);
  color: var(--alabaster);
  transition: color 0.15s var(--ease), transform 0.2s var(--ease);
}
.menu-link:hover { transform: translateX(var(--s2)); }
.menu-subs { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s5); margin: var(--s2) 0 var(--s2) var(--s5); }
.menu-sublink { font-family: var(--mono); font-size: var(--fs-body); color: var(--stone); transition: color 0.15s var(--ease); }
.menu-sublink::before { content: "→ "; }
.menu-sublink:hover { color: var(--alabaster); }
/* menu foot — keeps the sitemap and the footer telling the same story */
.menu-foot { border-top: 1px solid var(--line); padding-block: var(--s5); display: flex; flex-wrap: wrap; gap: var(--s3) var(--s6); justify-content: space-between; }

/* ---------- Home (centred) ---------- */
#home {
  position: relative;
  min-height: calc(100svh - var(--header-h));
  display: grid;
  place-items: center;
  background: var(--ink);
  padding-block: var(--s9);
}
.hero-inner { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; text-align: center; }
#home h1 { font-size: var(--fs-display); font-weight: 700; letter-spacing: var(--ls-tight); line-height: 1.02; color: var(--alabaster); margin-block: var(--s5) var(--s5); }
#home .lede { max-width: 52ch; font-style: italic; }
.logo-anim { width: clamp(120px, 18vw, 200px); margin-bottom: var(--s7); filter: invert(1); transform-style: preserve-3d; animation: spinY 7s linear infinite; }
@keyframes spinY { to { transform: rotateY(360deg); } }
.scroll-cue { display: inline-flex; align-items: center; gap: var(--s3); margin-top: var(--s8); }
.scroll-cue .arrow { animation: bob 1.8s var(--ease) infinite; }
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5px); } }

/* ---------- Broadcast (page dédiée : /broadcast/) ----------
   Le son a sa page, et cette page n'est que ça : le header, la scène, rien
   dessous. Quatre pièces — le bandeau qui y mène depuis l'accueil, la scène,
   la visualisation, et la commande posée en marge.

   Le bandeau : une bande d'une cinquantaine de pixels entre les plugins et la
   newsletter. Pas de filet en bas — c'est celui de #newsletter qui le ferme,
   sinon les deux se doubleraient. */
.ticker {
  display: block;
  overflow: hidden;
  background: var(--ink);
  border-top: 1px solid var(--line);
  padding-block: var(--s4);
}
.ticker-track { display: flex; width: max-content; animation: ticker 45s linear infinite; }
/* La série fait au moins la largeur de l'écran : en deçà, la seconde ne
   suffirait pas à couvrir le cadre et un vide passerait à chaque tour. Les deux
   séries sont identiques, donc de même largeur — le glissement de 50 % tombe
   toujours juste. */
.ticker-row {
  flex: none;
  min-width: 100vw;
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  gap: var(--s6);
  font-family: var(--mono);
  font-size: var(--fs-lead);
  letter-spacing: var(--ls-mono);
  line-height: 1;
  color: var(--stone);
  transition: color 0.2s var(--ease);
}
.ticker-dot { color: var(--line); transition: color 0.2s var(--ease); }
@keyframes ticker { to { transform: translateX(-50%); } }
/* Sous la main la bande s'arrête et s'éclaire : le mot devient une cible, pas
   un décor qui passe. */
.ticker:hover .ticker-track { animation-play-state: paused; }
.ticker:hover .ticker-row { color: var(--alabaster); }
.ticker:hover .ticker-dot { color: var(--stone); }

/* La visualisation : une couche de fond fixe, à l'écran entier, sous tout le
   reste. Les anneaux sont tracés en --line, comme tous les filets du site — assez
   sourds pour qu'un titre se lise par-dessus sans qu'on ait à les voiler. */
.bc-viz { position: fixed; inset: 0; z-index: 0; display: block; width: 100%; height: 100%; pointer-events: none; }

/* Le contenu passe au-dessus, et le fil comme le masthead rendent leur fond :
   opaques, ils découperaient un rectangle mort dans la visualisation. Rien ne
   sépare le contenu du fond — pas de filet, pas de cadre, pas de plaque. */
.bc-page { position: relative; z-index: 1; }
.bc-page .crumb,
.bc-page .mast { background: transparent; }

/* ---------- La commande ----------
   Elle occupe la colonne réglée d'une page plugin — celle des métadonnées. Là où
   un plugin annonce sa version et ses formats, le broadcast met son play/stop et
   son niveau : c'est tout ce qu'il a à déclarer. Même colonne, même filet, même
   voix mono. */
.bc-deck { display: flex; flex-direction: column; gap: var(--s5); }

/* Le play/stop est une ligne de la colonne, lue comme les autres : le mot à
   gauche, le signe à droite. */
.bc-play {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  width: 100%;
  padding-bottom: var(--s3);
}
.bc-play svg { width: var(--s4); height: var(--s4); fill: var(--stone); transition: fill 0.15s var(--ease); }
.bc-play .ico-stop { display: none; }
.bc-play[data-state="on"] .ico-play { display: none; }
.bc-play[data-state="on"] .ico-stop { display: block; }
/* Un mot par état, montré par le CSS : le module ne pose qu'un data-state, et
   motor.js peut poser `unavailable` sans rien savoir du texte. */
.bc-word { display: none; color: var(--stone); transition: color 0.15s var(--ease); }
.bc-play[data-state="off"] .bc-word[data-state="off"],
.bc-play[data-state="loading"] .bc-word[data-state="loading"],
.bc-play[data-state="on"] .bc-word[data-state="on"],
.bc-play[data-state="unavailable"] .bc-word[data-state="unavailable"] { display: inline; }
.bc-play[data-state="on"] svg,
.bc-play:hover:not(:disabled) svg { fill: var(--alabaster); }
.bc-play[data-state="on"] .bc-word,
.bc-play:hover:not(:disabled) .bc-word { color: var(--alabaster); }
/* Le module n'a pas pu prendre la main : page ouverte en fichier local, ou son
   introuvable. La commande reste là, elle n'est simplement pas gréée, et le mot
   le dit au lieu de le laisser deviner. */
.bc-play:disabled { cursor: default; }

/* Le niveau : un rail d'un pixel — le filet du site — et pour curseur le carré
   de 8 px. La zone de saisie, elle, fait toute la hauteur d'une ligne : un rail
   d'un pixel serait une cible impossible à la souris. Le rail est peint dans la
   piste, centré, ce qui laisse le curseur se caler dessus sans marge négative.
   La part déjà atteinte passe en --stone : le niveau se lit sans chiffre. */
.bc-level { display: flex; flex-direction: column; gap: var(--s3); }
.bc-level input {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--s5);
  background: none;
  cursor: pointer;
}
.bc-level input::-webkit-slider-runnable-track {
  height: var(--s5);
  background:
    linear-gradient(var(--stone), var(--stone)) 0 50% / var(--fill, 70%) 1px no-repeat,
    linear-gradient(var(--line), var(--line))   0 50% / 100% 1px no-repeat;
}
.bc-level input::-moz-range-track {
  height: var(--s5);
  background:
    linear-gradient(var(--stone), var(--stone)) 0 50% / var(--fill, 70%) 1px no-repeat,
    linear-gradient(var(--line), var(--line))   0 50% / 100% 1px no-repeat;
}
.bc-level input::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--s2); height: var(--s2);
  margin-top: calc((var(--s5) - var(--s2)) / 2);
  background: var(--stone);
  transition: background 0.15s var(--ease);
}
.bc-level input::-moz-range-thumb {
  width: var(--s2); height: var(--s2);
  border: 0; border-radius: 0;
  background: var(--stone);
  transition: background 0.15s var(--ease);
}
.bc-level input:hover::-webkit-slider-thumb,
.bc-level input:focus-visible::-webkit-slider-thumb { background: var(--alabaster); }
.bc-level input:hover::-moz-range-thumb,
.bc-level input:focus-visible::-moz-range-thumb { background: var(--alabaster); }

/* ---------- Section heading (shared) ---------- */
.head { margin-bottom: var(--s7); display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s3); padding-bottom: var(--s4); }

/* ---------- Sigil (shared: plugin card + masthead) ----------
   source files: plugins/<plugin>/<plugin>-sigil.svg — inlined in the HTML so the
   mark loads without a server, and painted here in --alabaster. No animation.
   The mark is centred on its plate and never cropped (preserveAspectRatio).

   Deux emplois, deux mesures. Sur une carte de l'accueil il occupe 64 % de sa
   plaque. Dans le masthead d'une page plugin il n'a pas de plaque : il tient au
   nom, et se mesure donc sur lui. */
.sigil { width: 64%; height: 64%; fill: var(--alabaster); }

/* Le sigil et le nom sur une seule ligne : la marque précède le mot, comme une
   initiale. Le sigil est plus petit que le corps du titre — à hauteur de
   capitale, sinon il domine les lettres au lieu de les accompagner. */
.mast-mark { display: flex; align-items: center; gap: var(--s5); margin-block: var(--s4); }
.mast-mark .mast-title { margin-block: 0; }
.mast-mark .sigil { flex: none; width: auto; height: calc(var(--fs-mast) * 0.72); }

/* ---------- Plugin index (home) ---------- */
#plugins { background: var(--ink); border-top: 1px solid var(--line); }
.plugin-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gutter) var(--gutter); }
.plugin-card {
  grid-column: span 12;
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--line);
  padding-top: var(--s4);
  transition: border-color 0.25s var(--ease);
}
/* hover lights the rule the card hangs from — the grid itself reacts */
a.plugin-card:hover { border-top-color: var(--alabaster); }
@media (min-width: 700px) { .plugin-card { grid-column: span 6; } }
.plugin-card .art { position: relative; width: 100%; aspect-ratio: 16 / 10; background: var(--line); overflow: hidden; display: grid; place-items: center; transition: background 0.25s var(--ease); }
.plugin-card .idx { position: absolute; top: var(--s4); left: var(--s4); z-index: 1; }
/* stacked and flush left: a right-aligned description ragged badly at card width */
.plugin-card .meta { display: flex; flex-direction: column; gap: var(--s2); padding-top: var(--s4); }
.plugin-card .meta h5 { font-size: var(--fs-title); font-weight: 700; letter-spacing: var(--ls-tight); color: var(--alabaster); line-height: 1; }
.plugin-card .meta p { color: var(--stone); max-width: 32ch; transition: color 0.15s var(--ease); }
a.plugin-card:hover .meta p { color: var(--alabaster); }
.plugin-card.empty .art { background: transparent; border: 1px dashed var(--line); }

/* ============================================================
   PLUGIN PAGE
   masthead → media → ruled spec rows → footer
   ============================================================ */

/* breadcrumb bar, flush under the fixed header */
.crumb { border-bottom: 1px solid var(--line); background: var(--ink); }
.crumb .wrap { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); height: var(--s7); }
.back-link { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: var(--ls-mono); text-transform: lowercase; }
.back-link::before { content: "← "; }

/* masthead */
.mast { background: var(--ink); padding-block: clamp(var(--s7), 6vw, var(--s9)) var(--s8); }
.mast-id { grid-column: span 12; }
.mast-title { font-size: var(--fs-mast); font-weight: 700; letter-spacing: var(--ls-tight); line-height: 0.92; color: var(--alabaster); margin-block: var(--s4) var(--s4); }
.mast-sub { font-size: var(--fs-lead); color: var(--alabaster); max-width: 34ch; }
.mast-id .tag { margin-top: var(--s5); }
/* .bc-deck est la colonne de la page broadcast : mêmes règles de place que les
   métadonnées d'un plugin, pour que les deux mastheads se superposent. */
.mast-meta, .bc-deck { grid-column: span 12; margin-top: var(--s7); align-self: end; }
@media (min-width: 900px) {
  .mast-id   { grid-column: 1 / span 8; }
  .mast-meta,
  .bc-deck   { grid-column: 10 / span 3; margin-top: 0; border-left: 1px solid var(--line); padding-left: var(--s5); }
}
.mast-meta .row { display: flex; justify-content: space-between; gap: var(--s4); padding-block: var(--s2); }
.mast-meta .row:first-child { border-top: 0; padding-top: 0; }
.mast-meta dt { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: var(--ls-mono); text-transform: lowercase; color: var(--stone); }
.mast-meta dd { font-family: var(--mono); font-size: var(--fs-label); color: var(--alabaster); text-align: right; }

/* media — full-bleed gallery */
.media { background: var(--ink); border-block: 1px solid var(--line); }
/* portrait frame on phones so 1:3 mockups stay readable,
   fixed short height from tablet up so the gallery never dominates the page */
.viewport { position: relative; width: 100%; aspect-ratio: 4 / 5; max-height: 62svh; overflow: hidden; background-color: #222222; }
@media (min-width: 700px) { .viewport { aspect-ratio: auto; height: clamp(340px, 54svh, 520px); max-height: none; } }
#slider { display: flex; height: 100%; transition: transform 0.5s var(--ease); touch-action: pan-y; }
/* each slide is a grid: multiple visuals share the frame evenly,
   side by side on wide screens, stacked on small ones */
/* visuals sit next to each other, centred as a group — never spread apart */
#slider > * {
  flex: 0 0 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(var(--s3), 3vw, var(--s6));
  padding: clamp(var(--s4), 3vw, var(--s6));
}
#slider > * > * { min-width: 0; min-height: 0; }
#slider img { max-height: 100%; max-width: 100%; width: auto; object-fit: contain; }
/* placeholder frame */
.mock {
  width: min(60%, 380px); aspect-ratio: 3 / 4;
  display: grid; place-items: center;
  background: var(--line);
  font-family: var(--mono); font-size: var(--fs-label);
  letter-spacing: var(--ls-mono); text-transform: lowercase;
  color: var(--stone);
}
.mock.wide { aspect-ratio: 4 / 3; }

/* media control bar */
.media-bar { border-top: 1px solid var(--line); }
.media-bar .wrap { display: flex; align-items: center; gap: var(--s4); height: var(--s7); }
.segs { flex: 1; display: flex; gap: var(--s2); }
.seg { flex: 1; height: 1px; background: var(--line); transition: background 0.3s var(--ease); }
.seg.on { background: var(--alabaster); }
.media-nav { display: flex; }
.slider-btn {
  width: var(--s7); height: var(--s7);
  display: grid; place-items: center;
  color: var(--alabaster);
  border-left: 1px solid var(--line);
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.slider-btn:hover { color: var(--ink); background: var(--alabaster); }
.counter { min-width: 5ch; }

/* spec rows — the editorial core */
.spec { background: var(--ink); padding-block: var(--pad-y); }
/* rules separate the rows only — no line closing the block above the first or below the last */
.spec-row { padding-block: var(--s7); row-gap: var(--s5); }
.spec-row + .spec-row { border-top: 1px solid var(--line); }
.spec-key { grid-column: span 12; display: flex; align-items: baseline; gap: var(--s3); }
.spec-val { grid-column: span 12; }
@media (min-width: 900px) {
  .spec-key { grid-column: 1 / span 3; position: sticky; top: calc(var(--header-h) + var(--s5)); align-self: start; flex-direction: column; gap: var(--s2); }
  .spec-val { grid-column: 5 / span 8; }
}
.spec-key h2 { font-family: var(--mono); font-size: var(--fs-body); font-weight: 400; text-transform: lowercase; letter-spacing: var(--ls-mono); color: var(--alabaster); }
.spec-key .n { font-family: var(--mono); font-size: var(--fs-label); color: var(--stone); }

/* numbered feature list */
.list-num { counter-reset: n; }
.list-num li {
  display: grid;
  grid-template-columns: 2rem 1fr;
  gap: var(--s3);
  padding-block: var(--s4);
  color: var(--alabaster);
}
.list-num li:first-child { border-top: 0; padding-top: 0; }
.list-num li::before {
  counter-increment: n;
  content: counter(n, decimal-leading-zero);
  font-family: var(--mono); font-size: var(--fs-label);
  color: var(--stone);
  padding-top: 0.15em;
}
@media (min-width: 900px) { .list-num li { grid-template-columns: 3rem 1fr; gap: var(--s4); } }

/* parameter definition rows */
.params .row { padding-block: var(--s4); display: grid; grid-template-columns: 1fr; gap: var(--s2); }
.params .row:first-child { border-top: 0; padding-top: 0; }
@media (min-width: 640px) { .params .row { grid-template-columns: minmax(9rem, 3fr) 8fr; gap: var(--gutter); } }
.params dt { font-family: var(--mono); letter-spacing: var(--ls-mono); color: var(--alabaster); text-transform: lowercase; }
.params dd { color: var(--alabaster); }

/* download table */
.tbl { font-size: var(--fs-body); }
.tbl th, .tbl td { text-align: left; padding-block: var(--s3); color: var(--alabaster); vertical-align: baseline; }
.tbl thead th { border-top: 0; padding-top: 0; font-family: var(--mono); font-weight: 400; font-size: var(--fs-label); letter-spacing: var(--ls-mono); text-transform: lowercase; color: var(--stone); }
.tbl .soon, .soon { color: var(--stone); }
/* the table collapses into stacked rows on small screens */
@media (max-width: 639px) {
  .tbl thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .tbl tr { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2) var(--s4); padding-block: var(--s4); border-top: 1px solid var(--line); }
  .tbl td { border: 0; padding: 0; }
  .tbl td::before { content: attr(data-th) " "; font-family: var(--mono); font-size: var(--fs-label); text-transform: lowercase; letter-spacing: var(--ls-mono); color: var(--stone); display: block; }
}
.note { font-size: var(--fs-label); color: var(--stone); margin-top: var(--s4); }

/* ---------- Newsletter (centred) ---------- */
#newsletter { background: var(--ink); border-top: 1px solid var(--line); padding-block: var(--pad-y); }
#newsletter .wrap { display: flex; flex-direction: column; align-items: center; text-align: center; }
.inf-copy { margin-bottom: var(--s7); }
.inf-form { width: 100%; max-width: 520px; }
.inf-card { padding: 0; }
.inf-input { margin-bottom: var(--s3); }
.inf-input input {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  color: var(--alabaster);
  padding: var(--s3) 0;
  font: inherit;
  transition: border-color 0.15s var(--ease);
}
.inf-input input { text-align: center; }
.inf-input input::placeholder { color: var(--stone); }
.inf-input input:focus { outline: none; border-color: var(--alabaster); }
.inf-rgpd { font-size: var(--fs-label); line-height: 1.6; color: var(--stone); margin: var(--s5) auto; max-width: 46ch; }
.inf-submit { display: flex; justify-content: center; margin-top: var(--s6); }
/* a plugin control: stone outline, stone label. only the label lights up. */
.inf-submit input {
  background: transparent; color: var(--stone);
  border: 1px solid var(--stone);
  padding: var(--s3) var(--s7);
  font-family: var(--font); font-size: var(--fs-body);
  text-transform: lowercase;
  cursor: pointer;
  transition: color 0.15s var(--ease);
}
.inf-submit input:hover { color: var(--alabaster); }
.inf-success h4 { font-weight: 700; color: var(--alabaster); margin-bottom: var(--s2); }
/* third-party widget: the iframe is cross-origin, so its stylesheet is out of reach —
   it paints black text and blue links, and its document carries no background of its own. */
#mcaptcha__widget-container {
  display: flex; justify-content: center;
  width: fit-content; margin: var(--s5) auto 0;
  padding: var(--s2);
}
/* the way round it: filter the frame itself. the widget ground is transparent, so inverting
   flips only its ink — the --line plate behind it is untouched. grayscale drops the blue of
   the mCaptcha links, and sepia + brightness land the white on --alabaster:
   #000 → invert #fff → sepia(0.8) → brightness(0.95) → rgb(242,242,230) ≈ #f2f0e6.
   the trade: the widget turns monochrome, so its green "Verified!" and red error read as
   alabaster too — the wording still carries the state. */
#mcaptcha__widget-container iframe {
  width: 340px; max-width: 100%;
  filter: invert(1) grayscale(1) brightness(0.95);
}

/* ---------- Footer (identical on every page) ---------- */
#footer { background: var(--ink); border-top: 1px solid var(--line); padding-block: var(--s8); }
.foot-grid { row-gap: var(--s7); }
/* order: coffee · contact — stacked and centred on small screens,
   two halves side by side from 640px up, each centred in its half */
.foot-col { grid-column: span 12; display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--s3); }
@media (min-width: 640px) {
  .foot-col { grid-column: span 6; }
}
.foot-email { color: var(--alabaster); }
/* the cup's ink is centred in its square viewBox, so no optical offset is needed here */
.foot-coffee { --coffee: 112px; }
.foot-coffee img { margin-top: -16px; height: var(--coffee); width: var(--coffee); max-width: none; filter: invert(1) brightness(0.54); transition: filter 0.15s var(--ease); }
.foot-coffee:hover img { filter: invert(1); }

.copyright { background: var(--ink); border-top: 1px solid var(--line); padding-block: var(--s5); }
/* centred: one line while both statements fit, two centred lines once they don't — never three */
.copyright .wrap { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center; gap: var(--s2) var(--s5); text-align: center; }
/* each statement holds its own line: the type steps down just enough to stay unbroken at 320px */
.copyright p { font-family: var(--mono); font-size: clamp(0.6875rem, 3.4vw, var(--fs-label)); color: var(--stone); white-space: nowrap; }

/* ============================================================
   PLAIN DOCUMENT (legal pages)
   a notepad: one family, one size, one weight. hierarchy comes from
   blank lines, indentation and --stone — never from type.
   ============================================================ */
.doc {
  font-family: var(--mono);
  font-size: var(--fs-body);
  line-height: 1.9;
  max-width: 76ch;
  padding-block: var(--s7) var(--s10);
}
.doc > * + * { margin-top: var(--s4); }
/* every heading level renders at body size: only the blank line above them,
   and their --stone colour, say that a section starts */
.doc h1, .doc h2, .doc h3 { font: inherit; letter-spacing: var(--ls-mono); text-transform: lowercase; }
.doc h2, .doc h3 { color: var(--stone); margin-top: var(--s7); }
.doc h3 { margin-top: var(--s5); }
/* two markers, one per level — the only hierarchy signal besides the blank line */
.doc h2::before { content: "— "; }
.doc h3::before { content: "· "; }
.doc .doc-meta { color: var(--stone); }
/* lists: the marker sits in the gutter so wrapped lines stay aligned */
.doc ul, .doc ol { padding-left: 3ch; }
.doc ol { counter-reset: n; }
.doc li { position: relative; margin-top: var(--s3); }
.doc li::before { position: absolute; left: -3ch; color: var(--stone); }
.doc ul > li::before { content: "-"; }
.doc ol > li { counter-increment: n; }
.doc ol > li::before { content: counter(n) "."; }
.doc li > ul { margin-top: var(--s3); }
/* Extensions pour la documentation technique — les pages légales n'en ont pas
   besoin, un manuel de plugin si. Aucune nouvelle couleur, aucune graisse :
   c'est la plaque --line qui distingue, comme partout ailleurs sur le site.
   · code  : un nom de paramètre ou de commande, tel qu'il s'affiche à l'écran
   · pre   : un schéma en caractères. Interligne resserré, sinon le dessin se
             disloque, et défilement propre plutôt qu'un débordement de page. */
.doc code { background: var(--line); padding: 0 0.4ch; }
.doc pre { background: var(--line); padding: var(--s4); line-height: 1.5; overflow-x: auto; }
.doc pre code { background: none; padding: 0; }
.doc .tbl { margin-top: var(--s5); }
.doc .tbl th, .doc .tbl td { padding-right: var(--s4); }

/* ---------- Scroll reveal ---------- */
.reveal { opacity: 0; transform: translateY(20px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
.reveal.d1 { transition-delay: 0.08s; }
.reveal.d2 { transition-delay: 0.16s; }
.reveal.d3 { transition-delay: 0.24s; }

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; }
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  #slider { transition: none; }
}
