/* Brand typography, per the brand manual (assets/branding/index.html) — which is the
   canonical source, and this file did not follow it. It loaded Inter + JetBrains Mono
   and called them "brand", justified as mirroring outreach/web: it aligned with a SIBLING
   SURFACE instead of with the manual, and a copy of a copy is how a brand drifts.
   The manual says, and now so does this file:
     · IBM Plex Mono 600 — the wordmark (already baked into the logo SVG)
     · IBM Plex Mono     — code, ids, NCL
     · IBM Plex Sans 500 — the tagline
     · system font stack — every other UI element and all body text (so NO webfont for
                           body: the site's --font-sans already resolves to the system
                           stack, which is what the manual asks for — Inter was the drift)
   The faces are SELF-HOSTED. The framework serves `style-src 'self' 'unsafe-inline';
   font-src 'self' data:`, so a fonts.googleapis.com stylesheet is blocked outright — the
   site loaded unstyled type in every browser that enforces it, and Brave never even sent
   the request. fonts-brand.css and styles/fonts/*.woff2 are generated by
   `just sync-brand-fonts`; the family list (Literata + Muli) lives in the sync-brand-fonts recipe.
   @import must precede every style rule (comments excepted), so it stays at the top. */
@import url('/styles/fonts-brand.css');

/* website-brand.css se importa DESDE AQUÍ, y no desde site/config/assets.ncl, porque
   ese fichero es INERTE en un sitio thin. Medido, no supuesto:

     rustelo/…/website-htmx-ssr/crates/server/build.rs:  src_css = fmt_slice(&site_cfg.assets.n)

   `source_css` se consume en build.rs — TIEMPO DE COMPILACIÓN. Un sitio thin no compila
   nada: corre la imagen compartida, cuya lista de hojas quedó horneada desde
   website-htmx-rustelo/code/site/config/assets.ncl (7 entradas, y la de marca no está
   entre ellas). Declarar la octava en el assets.ncl del sitio no la sirve: verificado en
   la página, document.styleSheets devolvía 8 hojas sin website-brand.css entre ellas.
   Es la misma familia que routes.ncl — un fichero que parece autoritativo y no lo es.

   custom.css SÍ va en esa lista horneada y es del sitio, así que es el único punto por
   el que la marca entra hoy sin reconstruir la imagen compartida. El día que la hoja
   suba a la capa de implementación, esta línea sobra; hasta entonces es lo que la hace
   existir. Va aquí arriba porque @import ha de preceder a toda regla de estilo. */
@import url('/styles/website-brand.css');

/* Custom CSS file for static file serving example */

/* Reset and base styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Esta regla llevaba la pila del sistema, `color:#333` y `background:#f8f9fa` a pelo —
   los tres valores de fábrica de la plantilla, y los tres INALCANZABLES desde un token:
   una declaración sobre el elemento `body` gana a cualquier redefinición de variable, de
   modo que ni la tipografía ni la superficie de marca podían llegar aquí. Se sustituyen
   por los tokens, que es lo que permite que website-brand.css tenga efecto y que el tema
   claro/oscuro cambie de verdad la página en vez de solo los bloques con clase. */
body {
  font-family: var(--font-sans);
  line-height: 1.6;
  color: var(--color-text);
  background-color: var(--color-surface);
}

/* Container */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
}

/* Typography — headings carry the brand, in the only way the brand allows.
 *
 * This was `color: #2c3e50` — a literal from the starter theme, with NO dark
 * variant: on the dark canvas the headings were slate-on-slate, all but invisible.
 *
 * The wordmark is `Onto` in SILVER #C0CCD8 + `ref` in AMBER #E8A838, and neither is
 * a heading colour on white: silver is 1.6:1 and amber 2.1:1 — amber fails even the
 * 3:1 floor for large text. So the brand is DERIVED, never copied: --onto-heading is
 * the wordmark silver on the dark canvas and its deep sibling on the light one (both
 * >11:1), and the amber survives where it CAN be read — as the h2 rule below, which
 * is the typographic echo of the `Onto|ref` seam. See --onto-* tokens further down;
 * they already flip on `html.light` / `[data-theme="light"]`, which is what <html>
 * actually carries. */
h1,
h2,
h3,
h4,
h5,
h6 {
  margin-bottom: 1rem;
  color: var(--onto-heading, #c0ccd8);
}

/* The element rule above cannot reach the page TITLE: `.ds-heading-xl` carries its own
 * `--color-neutral-900` in htmx-components.css, a class selector that loads AFTER this
 * file — so the h1 stayed framework-neutral while every h2 below it went brand, which
 * is the worst of both. `html .ds-page ...` (0,2,1) outranks both `.ds-heading-xl` and
 * its `.dark` twin, so ONE rule brands the title in BOTH themes — the token already
 * knows which theme it is in. */
html .ds-page .ds-heading-xl,
html .ds-page .ds-heading-lg {
  color: var(--onto-heading, #c0ccd8);
}

/* The amber seam. Scoped to page/prose bodies so it marks SECTIONS, never the
 * headings inside nav, cards or the hero — a rule everywhere is a rule nowhere. */
.ds-page-body h2,
.ds-prose h2 {
  padding-left: 0.75rem;
  border-left: 3px solid var(--onto-rule, #e8a838);
}

h1 {
  font-size: 2.5rem;
  font-weight: 700;
}

h2 {
  font-size: 2rem;
  font-weight: 600;
}

h3 {
  font-size: 1.5rem;
  font-weight: 500;
}

p {
  margin-bottom: 1rem;
}

/* Buttons */
.btn-icon {
  padding: 2px 4px !important;
}

.btn {
  display: inline-block;
  /* padding: 12px 24px; */
  font-size: 1rem;
  font-weight: 500;
  text-decoration: none;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.3s ease;
}

/* Dos literales de la plantilla de arranque, sin variante de tema y sin relación con esta
   marca. Encima, el azul de enlace se colaba como color de TEXTO y el botón salía azul
   sobre azul — medido en la página legal.

   Van aquí y no en website-brand.css porque esa hoja se importa en la CABECERA de este
   fichero: cualquier regla de aquí le gana por orden a igualdad de especificidad. Es la
   misma razón por la que la regla de `body` se corrigió en su sitio en vez de intentar
   sobrescribirla desde fuera.

   LA LETRA ES BLANCA EN LOS DOS TEMAS. Un primer intento la puso oscura en tema oscuro,
   porque el azul se aclara ahí para despegarse del fondo y sobre ese azul claro contrasta
   mejor lo oscuro (4.62 frente a 3.43 del papel). Medía bien y leía mal: un botón que
   cambia el color de su texto entre temas deja de reconocerse como el mismo botón.

   La solución es mover el FONDO, no la letra. Un botón trae su propia superficie, así que
   no necesita el azul aclarado que sí necesita un enlace sobre el lienzo: se queda con el
   azul profundo en ambos temas y la letra blanca aguanta en los dos.

     #fffcf4 sobre #0268d0   5.26  ✓   claro y oscuro, el mismo par */
.btn-primary {
  background-color: #0268d0;
  color: #fffcf4;
}
.dark .btn-primary { background-color: #0268d0; color: #fffcf4; }

.btn-primary:hover {
  background-color: #01509f;
  color: #fffcf4;
  transform: translateY(-2px);
}

.btn-secondary {
  background-color: #6c757d;
  color: white;
}

.btn-secondary:hover {
  background-color: #5a6268;
}

.btn-success {
  background-color: #28a745;
  color: white;
}

.btn-success:hover {
  background-color: #218838;
}

/* Cards */
.card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin-bottom: 20px;
  transition:
    transform 0.3s ease,
    box-shadow 0.3s ease;
}

.card:hover {
  transform: translateY(-5px);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}

.card-title {
  color: #2c3e50;
  margin-bottom: 10px;
}

.card-text {
  color: #666;
  line-height: 1.5;
}

/* Navigation */
.navbar {
  background-color: #2c3e50;
  padding: 1rem 0;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.navbar-brand {
  color: white;
  font-size: 1.5rem;
  font-weight: 700;
  text-decoration: none;
}

.navbar-nav {
  display: flex;
  list-style: none;
  gap: 2rem;
  margin-left: auto;
}

.nav-link {
  color: #ecf0f1;
  text-decoration: none;
  transition: color 0.3s ease;
}

.nav-link:hover {
  color: #3498db;
}

/* Grid system */
.row {
  display: flex;
  flex-wrap: wrap;
  margin: 0 -15px;
}

.col {
  flex: 1;
  padding: 0 15px;
}

.col-1 {
  flex: 0 0 8.333333%;
}
.col-2 {
  flex: 0 0 16.666667%;
}
.col-3 {
  flex: 0 0 25%;
}
.col-4 {
  flex: 0 0 33.333333%;
}
.col-6 {
  flex: 0 0 50%;
}
.col-8 {
  flex: 0 0 66.666667%;
}
.col-12 {
  flex: 0 0 100%;
}

/* Utilities */
.text-center {
  text-align: center;
}

.text-left {
  text-align: left;
}

.text-right {
  text-align: right;
}

.mt-1 {
  margin-top: 0.25rem;
}
.mt-2 {
  margin-top: 0.5rem;
}
.mt-3 {
  margin-top: 1rem;
}
.mt-4 {
  margin-top: 1.5rem;
}
.mt-5 {
  margin-top: 3rem;
}

.mb-1 {
  margin-bottom: 0.25rem;
}
.mb-2 {
  margin-bottom: 0.5rem;
}
.mb-3 {
  margin-bottom: 1rem;
}
.mb-4 {
  margin-bottom: 1.5rem;
}
.mb-5 {
  margin-bottom: 3rem;
}

.p-1 {
  padding: 0.25rem;
}
.p-2 {
  padding: 0.5rem;
}
.p-3 {
  padding: 1rem;
}
.p-4 {
  padding: 1.5rem;
}
.p-5 {
  padding: 3rem;
}

/* Alerts */
.alert {
  padding: 15px;
  margin-bottom: 20px;
  border: 1px solid transparent;
  border-radius: 6px;
}

.alert-info {
  color: #0c5460;
  background-color: #d1ecf1;
  border-color: #bee5eb;
}

.alert-success {
  color: #155724;
  background-color: #d4edda;
  border-color: #c3e6cb;
}

.alert-warning {
  color: #856404;
  background-color: #fff3cd;
  border-color: #ffeaa7;
}

.alert-danger {
  color: #721c24;
  background-color: #f8d7da;
  border-color: #f5c6cb;
}

/* Forms */
.form-group {
  margin-bottom: 1rem;
}

.form-label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: #333;
}

.form-control {
  display: block;
  width: 100%;
  padding: 0.75rem;
  font-size: 1rem;
  border: 1px solid #ced4da;
  border-radius: 6px;
  transition:
    border-color 0.3s ease,
    box-shadow 0.3s ease;
}

.form-control:focus {
  outline: none;
  border-color: #3498db;
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}

/* Responsive design */
@media (max-width: 768px) {
  .container {
    padding: 0 15px;
  }

  .row {
    flex-direction: column;
  }

  .col {
    flex: 1;
    margin-bottom: 1rem;
  }

  .navbar-nav {
    flex-direction: column;
    gap: 1rem;
  }

  h1 {
    font-size: 2rem;
  }

  h2 {
    font-size: 1.5rem;
  }
}

/* Animation utilities */
.fade-in {
  animation: fadeIn 0.5s ease-in;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.slide-up {
  animation: slideUp 0.6s ease-out;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Static file serving indicator */
.static-file-badge {
  position: fixed;
  bottom: 20px;
  right: 20px;
  background-color: #28a745;
  color: white;
  padding: 8px 12px;
  border-radius: 20px;
  font-size: 0.8rem;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  z-index: 1000;
}

.static-file-badge:before {
  content: "📁 ";
}

/* All images inside post body: never overflow the column. */
.post-content-body img {
  max-width: 100%;
  height: auto;
}

/* Post body: lead thumbnail image rendered from markdown (first paragraph, lone img) */
.post-content-body > p:first-child > img:only-child {
  width: 100%;
  max-height: 24rem;
  object-fit: contain;
  border-radius: 0.5rem;
  margin-bottom: 1rem;
}

/* Hide duplicate H1 title from markdown body — already shown in the header section.
   Covers two layouts:
   - thumbnail-first: <p><img></p> <h1>  → adjacent sibling after first p
   - no thumbnail:    <h1> as first child */
.post-content-body > p:first-child + h1,
.post-content-body > h1:first-child {
  display: none;
}

/* Content grid card thumbnail */
.grid-post-figure {
  max-height: 11rem;
}

.grid-post-img {
  height: 100%;
  max-height: 11rem;
  object-fit: contain;
  margin: 0 auto;
}

/* Theme-aware thumbnails: show light img on light theme, dark img on dark theme */
.grid-post-img-dark {
  display: none;
}

[data-theme="dark"] .grid-post-img-light {
  display: none;
}

[data-theme="dark"] .grid-post-img-dark {
  display: block;
}

/* ── Content graph mini ────────────────────────────────────────────────────── */

/* Constrain graph to sidebar width; prevent overflow on mobile */
.content-graph-mini-wrapper {
  max-width: 280px;
}

/* Preview: scales SVG to wrapper width, zoom-in cursor */
.content-graph-mini-preview {
  cursor: zoom-in;
  border-radius: 0.5rem;
  overflow: hidden;
  transition: opacity 0.15s;
}

.content-graph-mini-preview:hover {
  opacity: 0.85;
}

.content-graph-mini-preview svg {
  width: 100%;
  height: auto;
  display: block;
}

/* Dark-mode overrides for SVG CSS custom properties.
   The SVG embeds <style> using var(--cg-label) etc with light-mode fallbacks.
   Setting these on the wrapper propagates into the SVG through the cascade. */
[data-theme="dark"] .content-graph-mini-wrapper {
  --cg-label: #d1d5db;
  --cg-edge:  #4b5563;
  --cg-node:  #9ca3af;
}

/* Related content link styling — <A> from leptos_router renders a plain <a>.
   No underline; subtle hover; text matches surrounding content. */
.content-graph-related a {
  font-size: 0.875rem;
  line-height: 1.375;
  text-decoration: none;
  transition: opacity 0.15s;
}

.content-graph-related a:hover {
  opacity: 0.75;
}

/* ── ESPEJO DE PALETA RETIRADO ────────────────────────────────────────────────
 *
 * Aquí vivía una copia completa de la hoja de marca de ONTOREF —ámbar #E8A838 y plata
 * #C0CCD8—, inlineada por ese proyecto con esta justificación textual: «kept here so the
 * brand palette applies in source asset mode without requiring a framework rebuild».
 * Es el mismo apaño que este sitio necesita, resuelto allí copiando en vez de importando.
 *
 * Llegó con el fichero al extraer la plantilla, y era LA FUENTE REAL del color de la
 * página: al ir después del @import de la cabecera, ganaba siempre. Por eso los titulares
 * salían en plata y `--color-secondary` valía #C0CCD8 pese a estar declarado como terracota
 * en website-brand.css. Medido en el navegador, no deducido.
 *
 * Se retira en vez de reescribirse con los valores del libro: dos copias de una paleta en
 * dos ficheros es precisamente el modo en que una marca deriva, y aquí ya había derivado
 * una vez. La única fuente es /styles/website-brand.css, importada arriba. */
/* ADR reference links injected by linkify-adrs.nu */
.adr-ref {
  color: var(--color-brand-primary);
  text-decoration: none;
  border-bottom: 1px solid rgba(232, 168, 56, 0.35);
  font-family: 'IBM Plex Mono', ui-monospace, Consolas, monospace;
  font-size: 0.875em;
}
.adr-ref:hover {
  color: var(--color-primary-hover);
  border-bottom-color: var(--color-brand-primary);
}

/* (El bloque .dark del espejo se retira por la misma razón: ver la nota de arriba.) */

/* ══════════════════════════════════════════════════════════════════════════
   website document chrome (.doc-*) — port of outreach/web's hand-built page
   layout so the SSR site About matches it. Self-contained: defines the
   --onto-* token set and the .doc-* classes used by pages/about_project.j2.
   Lives here (custom.css is already in config/assets.ncl source_css) so the
   brand look ships with ZERO framework rebuild.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  /* REAPUNTADOS AL LIBRO. Estos tokens llegaron con el fichero del modelo de extracción y
   * son la marca de ONTOREF —plata #c0ccd8 y ámbar #e8a838, su logotipo—, no la de este
   * sitio. Sobrevivieron al saneado porque se llaman `--onto-*`, un nombre que ningún
   * grep de identidad reconoce, y desde ahí gobernaban titulares, filetes y bordes de
   * todas las páginas: el azul grisáceo de los <h1> venía de aquí.
   *
   * Se reapuntan en vez de borrarse: los usan las clases .doc-* de pages/about_project.j2,
   * así que quitarlos dejaría esa página sin color. Los nombres se conservan; los valores
   * pasan a ser los de la cubierta. */
  --onto-amber: #d4a853;
  --onto-amber-dark: #a75326;
  --onto-amber-light: #e0bd77;
  --onto-silver: #f5edd8;
  --onto-dark: #1a1208;
  --onto-gray: #c8a865;
  --onto-gray2: #d9c9a4;
  --onto-surface: #2c1810;
  --onto-border: #3d2c1c;
  --onto-amber-rgb: 212, 168, 83;
  --onto-silver-rgb: 245, 237, 216;

  /* Headings + the amber seam. Dark canvas: the wordmark silver itself (11.3:1 on
   * #0F1319), and the light amber the brand already reserves for dark surfaces. */
  --onto-heading: var(--color-secondary);
  --onto-rule: var(--color-secondary);

  /* The FRAMEWORK's font tokens, rebound to the manual. Restyling classes was not enough:
   * design-system.css and theme-default.css hand every token-driven component a font of
   * their own (--font-mono → ui-monospace…, --font-family-mono → JetBrains Mono). Leaving
   * them meant half the site asked for a face this file no longer loads and silently fell
   * back — the drift would have been invisible, which is the only kind that survives. */
  /* Redeclarar --font-sans aquí volvía a pisar Muli: este bloque va DESPUÉS del
   * @import de website-brand.css, así que ganaba por orden y el cuerpo seguía en la
   * pila del sistema pese a tener la fuente autoalojada. */
  --font-sans: 'Muli', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  --font-family-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Code', monospace;
}
html.light,
[data-theme="light"] {
  --onto-dark: #f5edd8;
  --onto-surface: #fffcf4;
  --onto-border: #e3d2a8;
  --onto-silver: #2c1810;
  --onto-gray: #5c3d28;
  --onto-gray2: #4a2e1d;

  /* Light canvas: the silver's deep sibling (13.2:1 on white) — same hue family as the
   * logo, legible where the logo's own value is not.
   * The rule takes the brand's DARK AMBER (#C88A20, declared in website-brand.css for
   * hover/pressed), not the base #E8A838: on white the base amber sits at 2.1:1 and
   * reads as a washed-out smudge. Same brand, the member of it that survives this canvas. */
  --onto-heading: var(--color-secondary);
  --onto-rule: var(--color-secondary);
}

/* Full-bleed dark canvas for the About body; the column centres inside it. */
.about-doc {
  background: var(--onto-dark);
  color: var(--onto-silver);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  border-radius: 0.75rem;
}
.doc-wrap {
  max-width: 52rem;
  margin: 0 auto;
  padding: 2.5rem 1.25rem 3.5rem;
}

.doc-head {
  border-bottom: 1px solid var(--onto-border);
  padding-bottom: 1.25rem;
  margin-bottom: 1.5rem;
}
.doc-eyebrow {
  font-family: 'IBM Plex Mono', Consolas, monospace;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--onto-amber);
  text-transform: uppercase;
  margin: 0 0 0.5rem;
}
.about-doc h1 {
  color: var(--onto-silver);
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0;
}
.doc-lead {
  color: var(--onto-gray2);
  font-size: 1.05rem;
  line-height: 1.7;
  margin: 0.85rem 0 0;
  max-width: 40rem;
}

.doc-toc {
  border: 1px solid var(--onto-border);
  border-radius: 0.5rem;
  padding: 0.75rem 1rem;
  margin: 1.5rem 0 2rem;
  background: var(--onto-surface);
}
.doc-toc p {
  font: 600 0.7rem/1 'IBM Plex Mono', monospace;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  opacity: 0.55;
  margin: 0 0 0.5rem;
}
.doc-toc ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}
.doc-toc a {
  font-size: 0.85rem;
  color: var(--onto-silver);
  text-decoration: none;
}
.doc-toc a:hover {
  color: var(--onto-amber);
}

.doc-section {
  margin: 2.25rem 0;
}
.doc-section > h2 {
  color: var(--onto-amber);
  font-size: 1.2rem;
  font-weight: 600;
  margin: 0 0 0.85rem;
  scroll-margin-top: 5rem;
}
.doc-section h3,
.doc-idx-h {
  color: var(--onto-silver);
  font-family: 'IBM Plex Mono', Consolas, monospace;
  font-size: 0.95rem;
  font-weight: 600;
  margin: 1.5rem 0 0.6rem;
}
.doc-idx-h .doc-count {
  color: var(--onto-gray);
  font-weight: 400;
}

.doc-prose {
  line-height: 1.75;
  color: var(--onto-gray2);
}
.doc-prose p {
  margin: 0 0 0.85rem;
}

.doc-mono {
  font-family: 'IBM Plex Mono', Consolas, monospace;
  color: var(--onto-gray);
}

/* Generic flat list (timeline, cards) */
.doc-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.doc-list > li {
  display: flex;
  gap: 0.75rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--onto-border);
  line-height: 1.6;
}
.doc-list > li:last-child {
  border-bottom: none;
}
.doc-list .doc-mono {
  flex-shrink: 0;
  white-space: nowrap;
}

/* Definition cards (axioms/tensions/practices) — block, not a wall. */
.doc-cards {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.75rem;
}
.doc-cards > li {
  border: 1px solid var(--onto-border);
  border-radius: 0.5rem;
  padding: 0.7rem 0.9rem;
  background: var(--onto-surface);
}
.doc-cards .doc-card-title {
  font-weight: 600;
  color: var(--onto-silver);
  margin: 0;
}
.doc-cards .doc-card-meta {
  font: 400 0.78rem/1.4 'IBM Plex Mono', monospace;
  color: var(--onto-gray);
}
.doc-cards .doc-card-body {
  color: var(--onto-gray2);
  font-size: 0.92rem;
  line-height: 1.55;
  margin: 0.35rem 0 0;
}

/* Index list (ADRs, catalog) — dense rows with mono code prefix. */
.doc-idx-list {
  list-style: none;
  padding: 0;
  margin: 0;
  columns: 2;
  column-gap: 1.5rem;
}
.doc-idx-list > li {
  break-inside: avoid;
  padding: 0.2rem 0;
}
.doc-idx-list a {
  color: var(--onto-silver);
  text-decoration: none;
  font-size: 0.9rem;
  line-height: 1.5;
}
.doc-idx-list a:hover {
  color: var(--onto-amber);
}
.doc-idx-list a .doc-mono {
  font-size: 0.82em;
}
@media (max-width: 640px) {
  .doc-idx-list {
    columns: 1;
  }
}

.doc-btn {
  display: inline-block;
  margin-top: 1rem;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--onto-border);
  border-radius: 0.45rem;
  color: var(--onto-silver);
  font-size: 0.88rem;
  text-decoration: none;
  background: var(--onto-surface);
  transition: border-color 0.15s, color 0.15s;
}
.doc-btn:hover {
  border-color: var(--onto-amber);
  color: var(--onto-amber);
}

.doc-graph {
  margin: 1rem auto;
  max-width: 24rem;
  border: 1px solid var(--onto-border);
  border-radius: 0.5rem;
  background: var(--onto-surface);
  padding: 0.5rem;
}
.doc-graph svg {
  width: 100%;
  height: auto;
  display: block;
}
/* The projected architecture diagram (gen-diagram.nu → /r/website-diagram.svg) is
   wide; widen the graph card when it holds the diagram image rather than a mini-svg. */
.doc-graph:has(.about-diagram) {
  max-width: 62rem;
}
.about-diagram {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 0.375rem;
}

.doc-kv {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.doc-kv td {
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--onto-border);
  vertical-align: top;
}
.doc-kv td:first-child {
  font-family: 'IBM Plex Mono', monospace;
  color: var(--onto-gray2);
  white-space: nowrap;
  width: 1%;
}

/* ── Tagline rotator (hero definition-phrases) ──────────────────────────────
   Mechanism only — phrases come from /r/taglines.json. Fade is the no-typed.js
   fallback; typed.js draws its own .typed-cursor. */
/* The manual assigns IBM Plex Sans 500 to exactly one thing — the tagline — and this is
   the tagline: the hero rotator draws the definition-phrases in the wordmark's own voice. */
[data-tagline-rotator] {
  display: inline;
  font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, sans-serif;
  font-weight: 500;
}
.tagline-rot {
  transition: opacity 0.35s ease;
  opacity: 0;
}
.tagline-rot.is-in {
  opacity: 1;
}
.typed-cursor {
  color: var(--onto-amber, #e8a838);
  animation: tagline-blink 1s infinite;
}
@keyframes tagline-blink {
  50% {
    opacity: 0;
  }
}
.tagline-focus {
  display: inline-block;
  margin-top: 0.4em;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.72em;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--onto-gray, #8090a4);
}
.tagline-focus:not(:empty)::before {
  content: '· ';
  color: var(--onto-amber, #e8a838);
}
@media (prefers-reduced-motion: reduce) {
  .tagline-rot {
    transition: none;
    opacity: 1;
  }
  .typed-cursor {
    animation: none;
  }
}
.tagline-doors {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em;
  justify-content: center;
  margin-top: 1.2em;
}
.tagline-door {
  font: inherit;
  font-size: 0.8em;
  padding: 0.35em 0.9em;
  border-radius: 999px;
  border: 1px solid rgba(var(--onto-silver-rgb), 0.28);
  background: transparent;
  color: var(--onto-gray, #8090a4);
  cursor: pointer;
  transition:
    color 0.2s ease,
    border-color 0.2s ease,
    background 0.2s ease;
}
.tagline-door:hover {
  color: var(--onto-silver, #c0ccd8);
  border-color: rgba(var(--onto-silver-rgb), 0.55);
}
.tagline-door.is-active {
  color: var(--onto-dark, #0f1319);
  background: var(--onto-amber, #e8a838);
  border-color: var(--onto-amber, #e8a838);
}
.tagline-panels {
  margin-top: 1rem;
}
.tagline-panel {
  display: none;
  border: 1px solid rgba(var(--onto-silver-rgb), 0.22);
  border-radius: 12px;
  padding: 1rem 1.25rem;
  background: rgba(var(--onto-silver-rgb), 0.05);
}
.tagline-panel.is-open {
  display: block;
  animation: tagline-panel-in 0.3s ease;
}
@keyframes tagline-panel-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.tagline-panel-line {
  margin: 0 0 0.75rem;
  font-size: 1.05em;
  color: var(--onto-silver, #c0ccd8);
}
.tagline-panel-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  align-items: center;
}
.tagline-panel-graph {
  font-size: 0.85em;
  color: var(--onto-amber, #e8a838);
  border-bottom: 1px solid rgba(var(--onto-amber-rgb), 0.4);
}
.tagline-panel-graph:hover {
  color: var(--onto-amber-light, #f0c265);
}
@media (prefers-reduced-motion: reduce) {
  .tagline-panel {
    animation: none;
  }
}
/* Header + footer logo — horizontal, 3rem (overrides framework h-8), nudged down. */
nav a.flex-shrink-0 img,
footer a.inline-block img {
  height: 3rem;
  width: auto;
  margin-top: 10px;
}

/* ──────────────────────────────────────────────────────────────────────────
   Brand mention: inline "website" links to /about in brand amber.
   Emitted by scripts/build/linkify-site.nu over blog markdown.
   ────────────────────────────────────────────────────────────────────────── */
.brand-site {
  color: var(--onto-amber, #e8a838);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid rgba(var(--onto-amber-rgb, 232, 168, 56), 0.35);
  transition: color 0.15s ease, border-color 0.15s ease;
}
.brand-site:hover,
.brand-site:focus-visible {
  color: var(--onto-amber-light, #f0c265);
  border-bottom-color: var(--onto-amber, #e8a838);
}

/* ── Per-post engagement (rating · comment · views) ───────────────────────── */
/* Layout lives here (not Tailwind utilities) so spacing is reliable regardless
   of the purged utility set. */
.pe-engagement {
  margin-top: 3rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--onto-border, #1e2a38);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}
.pe-block {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}
.pe-msg-block {
  justify-content: space-between;
}
.pe-cta-label {
  font-weight: 650;
  font-size: 0.95rem;
  color: var(--onto-silver, #c0ccd8); /* theme-aware: dark text in light, light in dark */
}
.pe-msg-copy {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  max-width: 34rem;
}
.pe-cta-sub {
  font-size: 0.85rem;
  color: var(--onto-gray2, #a8b4c8);
  line-height: 1.35;
}

/* Primary CTA button (comment, modal submit) */
.pe-cta-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 1.1rem;
  border: 0;
  border-radius: 0.6rem;
  background: var(--onto-amber, #e8a838);
  color: #1a1206; /* dark ink on amber — legible in both themes */
  font-weight: 650;
  font-size: 0.9rem;
  cursor: pointer;
  white-space: nowrap;
  transition: filter 0.15s ease, transform 0.1s ease;
}
.pe-cta-btn:hover { filter: brightness(1.08); }
.pe-cta-btn:active { transform: translateY(1px); }
.pe-cta-btn-block { width: 100%; justify-content: center; margin-top: 0.25rem; }

/* Stars */
.pe-rating { display: inline-flex; align-items: center; gap: 0.6rem; }
.pe-stars { display: inline-flex; gap: 0.1rem; }
.pe-star {
  background: none;
  border: 0;
  padding: 0 0.05rem;
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  color: rgba(128, 128, 128, 0.42); /* empty — neutral in both themes */
  transition: color 0.12s ease, transform 0.12s ease;
}
.pe-star:hover { transform: scale(1.15); color: var(--onto-amber, #e8a838); }
.pe-star-on { color: var(--onto-amber, #e8a838); }        /* part of the average */
.pe-star-mine { color: var(--onto-amber-light, #f0c265); } /* the caller's own vote */
.pe-rating-meta { font-variant-numeric: tabular-nums; color: var(--onto-gray, #8090a4); font-size: 0.85rem; }
.pe-rating-hint { font-size: 0.8rem; color: var(--onto-gray, #8090a4); font-style: italic; }

/* Views line (subtle) */
.pe-views-line { font-size: 0.78rem; color: var(--onto-gray, #8090a4); }
.pe-views { font-variant-numeric: tabular-nums; font-weight: 600; }

/* Grid card rating badge (only when the post has votes) */
.pe-card-rating {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--onto-amber, #e8a838);
  opacity: 0.9;
}

/* Comment modal */
.pe-modal-backdrop {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(0, 0, 0, 0.55);
  cursor: pointer;
}
.pe-modal-wrap {
  position: fixed; inset: 0; z-index: 85;
  display: flex; align-items: center; justify-content: center;
  padding: 1rem; pointer-events: none;
}
.pe-modal-card {
  position: relative;
  width: 100%; max-width: 30rem;
  background: var(--onto-surface, #141c24); /* white in light, dark in dark */
  color: var(--onto-silver, #c0ccd8);
  border: 1px solid var(--onto-border, #1e2a38);
  border-radius: 1rem;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
  padding: 1.5rem;
  pointer-events: auto;
}
.pe-modal-close {
  position: absolute; top: 0.6rem; right: 0.6rem;
  background: none; border: 0; cursor: pointer;
  color: var(--onto-gray, #8090a4);
  font-size: 1.1rem; line-height: 1; padding: 0.3rem;
}
.pe-modal-close:hover { color: var(--onto-silver, #c0ccd8); }
.pe-modal-title { font-size: 1.15rem; font-weight: 650; margin-bottom: 0.15rem; color: var(--onto-silver, #c0ccd8); }
.pe-modal-sub { font-size: 0.8rem; color: var(--onto-gray, #8090a4); margin-bottom: 0.75rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pe-modal-invite { font-size: 0.88rem; color: var(--onto-gray2, #a8b4c8); margin-bottom: 1rem; line-height: 1.4; }
.pe-msg-form { display: flex; flex-direction: column; gap: 0.75rem; }
.pe-field { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; color: var(--onto-gray2, #a8b4c8); }
.pe-field .ds-input,
.pe-field input,
.pe-field textarea {
  width: 100%;
  padding: 0.5rem 0.65rem;
  border-radius: 0.5rem;
  border: 1px solid var(--onto-border, #1e2a38);
  background: rgba(128, 128, 128, 0.1); /* subtle fill for contrast against the card */
  color: var(--onto-silver, #c0ccd8);
}
.pe-field input::placeholder,
.pe-field textarea::placeholder { color: var(--onto-gray, #8090a4); }
.pe-field input:focus,
.pe-field textarea:focus { outline: 2px solid var(--onto-amber, #e8a838); outline-offset: 0; }
.pe-consent {
  display: flex; align-items: flex-start; gap: 0.5rem;
  font-size: 0.82rem; color: var(--onto-gray2, #a8b4c8); line-height: 1.3; cursor: pointer;
}
.pe-consent input { margin-top: 0.15rem; }
.pe-msg-thanks { text-align: center; padding: 0.5rem 0; color: var(--onto-silver, #c0ccd8); }

/* Honeypot — kept in the DOM but invisible and out of the tab order. */
.pe-hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px; opacity: 0;
}

@media (max-width: 640px) {
  .pe-msg-block { flex-direction: column; align-items: flex-start; }
  .pe-cta-btn { width: 100%; justify-content: center; }
}
/* ── Newsletter subscribe CTA (nl-cta) — foot of content grids and posts.
   Config-driven (site.ncl [newsletter] → `newsletter` global → subscribe.j2);
   theme-aware via --onto-* tokens (flip under [data-theme="light"]). ───────── */
.nl-cta {
  margin: 2.5rem 0 0;
  padding: 2rem 1.5rem;
  border: 1px solid var(--onto-border);
  border-radius: 0.9rem;
  background: var(--onto-surface);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
  text-align: center;
}
.nl-cta__title {
  color: var(--onto-silver);
  font-size: clamp(1.35rem, 3vw, 1.9rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.15;
  margin: 0;
}
.nl-cta__sub {
  color: var(--onto-gray);
  max-width: 46ch;
  margin: 0 auto;
  font-size: 0.98rem;
  line-height: 1.5;
}
.nl-cta__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
}
.nl-cta__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.62rem 1.25rem;
  border-radius: 0.6rem;
  font-weight: 650;
  font-size: 0.95rem;
  text-decoration: none;
  white-space: nowrap;
  transition: filter 0.15s ease, transform 0.1s ease, background 0.15s ease;
}
.nl-cta__btn--primary {
  background: var(--onto-amber, #e8a838);
  color: #1a1206;
  border: 1px solid transparent;
}
.nl-cta__btn--primary:hover { filter: brightness(1.06); transform: translateY(-1px); }
.nl-cta__btn--ghost {
  background: transparent;
  color: var(--onto-silver);
  border: 1px solid var(--onto-border);
}
.nl-cta__btn--ghost:hover { background: var(--onto-dark); border-color: var(--onto-gray); }
@media (max-width: 640px) {
  .nl-cta__actions { flex-direction: column; width: 100%; }
  .nl-cta__btn { width: 100%; justify-content: center; }
}

/* ── Newsletter subscribe lightbox (nl-modal) — listmonk form ─────────────── */
dialog.nl-modal {
  position: fixed;
  inset: 0;
  margin: auto;
  border: 0;
  padding: 0;
  background: transparent;
  max-width: 30rem;
  width: calc(100% - 2rem);
  height: fit-content;
  max-height: calc(100vh - 2rem);
  overflow: auto;
  color: var(--onto-silver);
}
.nl-modal__submit:disabled { opacity: 0.45; cursor: not-allowed; filter: none; transform: none; }
dialog.nl-modal::backdrop {
  background: rgba(4, 8, 12, 0.62);
  backdrop-filter: blur(3px);
}
.nl-modal__card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  background: var(--onto-surface);
  border: 1px solid var(--onto-border);
  border-radius: 0.9rem;
  padding: 1.75rem 1.5rem;
  box-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.7);
  text-align: left;
}
.nl-modal__title {
  color: var(--onto-silver);
  font-size: 1.25rem;
  font-weight: 700;
  margin: 0 1.6rem 0 0;
  line-height: 1.2;
}
.nl-modal__close {
  position: absolute;
  top: 0.55rem;
  right: 0.7rem;
  padding: 0.1rem 0.4rem;
  background: transparent;
  border: 0;
  border-radius: 0.4rem;
  color: var(--onto-gray);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.nl-modal__close:hover { color: var(--onto-silver); background: var(--onto-dark); }
.nl-field { display: flex; flex-direction: column; gap: 0.3rem; }
.nl-field__lbl { font-size: 0.8rem; font-weight: 600; color: var(--onto-gray); }
.nl-field input {
  width: 100%;
  padding: 0.6rem 0.75rem;
  background: var(--onto-dark);
  color: var(--onto-silver);
  border: 1px solid var(--onto-border);
  border-radius: 0.5rem;
  font-size: 0.95rem;
}
.nl-field input::placeholder { color: var(--onto-gray); opacity: 0.7; }
.nl-field input:focus {
  outline: 2px solid var(--onto-amber);
  outline-offset: 1px;
  border-color: var(--onto-amber);
}
.nl-consent {
  display: flex;
  gap: 0.55rem;
  align-items: flex-start;
  font-size: 0.85rem;
  line-height: 1.4;
  color: var(--onto-gray2);
}
.nl-consent input { margin-top: 0.2rem; flex: 0 0 auto; accent-color: var(--onto-amber); }
.nl-consent a { color: var(--onto-amber); }
.nl-modal__submit { margin-top: 0.4rem; width: 100%; justify-content: center; }

/* ══════════════════════════════════════════════════════════════════════════
   Long-form legal / language pages — privacy.j2, legal.j2, lenguaje.j2

   `ds-prose`, `ds-callout` and `ds-table` were WRITTEN INTO THOSE TEMPLATES AND NEVER
   DEFINED: no rule for any of the three existed in any stylesheet the site loads. A class
   that does not exist raises no error and leaves no gap — the markup simply renders
   unstyled, inheriting whatever colour the page hands down. That is why these three pages
   read badly in the dark and only there: unstyled text stops being legible exactly when the
   canvas stops being white.

   Every colour below resolves through a theme token, so both themes are answered at once
   and neither can drift away from the other.
   ══════════════════════════════════════════════════════════════════════════ */
.ds-prose {
  color: var(--color-neutral-700, #374151);
  line-height: 1.7;
  max-width: 68ch;
}
.dark .ds-prose { color: var(--color-neutral-300, #d1d5db); }

.ds-prose h2 {
  color: var(--color-neutral-900, #111827);
  font-weight: 600;
  font-size: 1.35rem;
  margin: 2.2rem 0 0.7rem;
}
.ds-prose h3 {
  color: var(--color-neutral-800, #1f2937);
  font-weight: 600;
  font-size: 1.08rem;
  margin: 1.5rem 0 0.5rem;
}
.dark .ds-prose h2 { color: #F2F5F8; }
.dark .ds-prose h3 { color: #C0CCD8; }

.ds-prose p  { margin: 0.7rem 0; }
.ds-prose ul { margin: 0.7rem 0; padding-left: 1.3rem; list-style: disc; }
.ds-prose li { margin: 0.35rem 0; }
.ds-prose strong { color: var(--color-neutral-900, #111827); font-weight: 600; }
.dark .ds-prose strong { color: #F2F5F8; }

.ds-prose a { color: var(--color-brand-primary); text-underline-offset: 2px; }
.ds-prose a:hover { color: var(--color-primary-hover); }

/* …salvo cuando el enlace ES un botón. La regla de arriba tiene especificidad (0,2,0) y le
 * gana a `.btn-primary` / `.ds-btn-primary` (0,1,0), que están más abajo en esta misma hoja:
 * el resultado era el AZUL DE MARCA sobre el relleno AZUL del botón — el botón de contacto de
 * las páginas legales salía como un rectángulo azul vacío, sin texto visible.
 *
 * No es un fallo de Brave: cualquier navegador aplica esta cascada igual. Se ve primero en un
 * navegador cualquiera porque no hay nada que avise — dos reglas válidas, ningún error, texto
 * invisible.
 *
 * La corrección es acotar la regla de prosa, no repetir el color en cada plantilla: un botón
 * trae su propia superficie y su tinta viaja con él, no con el párrafo donde cae. Mismo par
 * que el resto de la hoja — #fffcf4 sobre #0268d0, 5.26 — para que la misma acción se vea
 * igual la pinte quien la pinte. `text-decoration:none` porque `no-underline` vive en
 * website.css, que se emite ANTES que esta hoja, y no conviene depender de ese orden. */
.ds-prose a.btn-primary,
.ds-prose a.ds-btn-primary,
.dark .ds-prose a.btn-primary,
.dark .ds-prose a.ds-btn-primary {
  color: #fffcf4;
  text-decoration: none;
}
.ds-prose a.btn-primary:hover,
.ds-prose a.ds-btn-primary:hover,
.dark .ds-prose a.btn-primary:hover,
.dark .ds-prose a.ds-btn-primary:hover {
  color: #fffcf4;
}

/* A set-apart aside (contact block, obligation, caveat). Tinted with the brand amber at low
 * alpha so it reads as "quoted" on either canvas without inventing a second surface colour. */
.ds-callout {
  border-left: 3px solid var(--color-brand-primary);
  background: rgba(232, 168, 56, 0.07);
  border-radius: 0.35rem;
  padding: 0.9rem 1.1rem;
  margin: 1.4rem 0;
}
.ds-callout p { margin: 0.3rem 0; }

.ds-table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.3rem 0;
  font-size: 0.94rem;
  color: var(--color-neutral-700, #374151);
}
.dark .ds-table { color: var(--color-neutral-300, #d1d5db); }
.ds-table th,
.ds-table td {
  border-bottom: 1px solid rgba(128, 144, 164, 0.28);
  padding: 0.55rem 0.7rem;
  text-align: left;
  vertical-align: top;
}
.ds-table th { color: var(--color-neutral-900, #111827); font-weight: 600; }
.dark .ds-table th { color: #F2F5F8; }
/* The table scrolls inside itself: a legal page must never scroll the page sideways. */
.ds-table-wrap { overflow-x: auto; }

/* ══════════════════════════════════════════════════════════════════════════
   Primary button / badge — one filled amber, one ink, both themes

   TWO defects met here:

   1. The base rule for the primary button exists only under the ATTRIBUTE selector
      (`[ds-btn-primary=""]`) — the CLASS `.ds-btn-primary`, which is what every template
      actually writes, has rules for :hover, :focus and :disabled but NO base. So the button
      never got its own background or text colour; it inherited them, and inheritance changes
      with the theme. That is the whole bug: not a colour chosen badly, a colour never chosen.

   2. Where a colour WAS chosen (the badge) it was white on #E8A838 — a contrast of 2.3:1,
      below the 4.5:1 floor. Amber is a LIGHT fill: it takes dark ink, not white. Black on this
      amber is 8.6:1. The dark theme's "black text" that looked like the bug was, in contrast
      terms, the only correct half of the pair.

   Ink is fixed, not theme-dependent, because the surface under it is fixed: the button carries
   its own amber wherever it lands. A filled control should not change its text colour with the
   page — it is not sitting on the page.
   ══════════════════════════════════════════════════════════════════════════ */
/* El par lo fija ahora la MARCA, no un literal heredado.
 *
 * El relleno era `var(--color-brand-primary)` —que en oscuro se aclara para despegarse del
 * fondo— y la tinta un `#15191F` fijo, del proyecto del que salió esta hoja. Sobre el azul
 * claro daba texto casi negro: el botón de enviar el formulario de contacto salía ilegible.
 * El razonamiento original de este bloque era correcto para un relleno ÁMBAR, que es claro y
 * pide tinta oscura; con un relleno azul deja de serlo.
 *
 * Mismo par que `.btn-primary` unas líneas más arriba, y por la misma razón: la misma acción
 * no puede verse de dos maneras según qué plantilla la pinte.
 *   #fffcf4 sobre #0268d0 — 5.26, claro y oscuro.
 * El azul se queda PROFUNDO en ambos temas en vez de aclararse: un botón trae su propia
 * superficie, así que no necesita el realce que sí necesita un enlace sobre el lienzo. */
.ds-btn-primary,
.dark .ds-btn-primary,
.ds-badge-primary,
.dark .ds-badge-primary {
  background-color: #0268d0;
  color: #fffcf4;
}

.ds-btn-primary:hover,
.dark .ds-btn-primary:hover,
.ds-badge-primary:hover,
.dark .ds-badge-primary:hover {
  background-color: #01509f;
  color: #fffcf4;
}

/* The class had no base rule at all, so it also lacked the box the attribute selector gives:
 * restate the shape, or a `class="ds-btn-primary"` renders as bare inline text with a fill. */
.ds-btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 0.375rem;
  padding: 0.5rem 1rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

/* Subscribe modal — result fragment (swapped in by /api/htmx/newsletter). It replaces the form, so
 * it carries its own close button: swap the form away without one and the reader is trapped in a
 * dialog whose only exit is the Escape key. */
.nl-modal__result {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  text-align: left;
}
.nl-modal__result-msg { margin: 0; line-height: 1.6; }
.nl-modal__result--ok  { border-left: 3px solid #E8A838; }
.nl-modal__result--err { border-left: 3px solid #C0504D; }

/* ── Figuras pareadas por tema, para prosa ───────────────────────────────────
   Un cuerpo markdown no alcanza la lógica de tema de la plantilla: el nav usa
   `dark:hidden` / `hidden dark:block`, pero esas son utilidades que UnoCSS
   compila escaneando las PLANTILLAS, y el markdown del contenido no se escanea.
   Un post que las usara enviaría las dos imágenes visibles a la vez. Así que
   aquí van en CSS plano, colgando de la raíz de tema (`html.dark`), que es la
   misma que ya gobierna `.dark .card` y compañía. */
.only-light { display: block; }
.only-dark  { display: none; }
.dark .only-light { display: none; }
.dark .only-dark  { display: block; }
