/* Stella Nova — sistema de tokens (custom properties).
 *
 * Metáfora rectora: "página de papel". Una hoja cálida flota sobre un campo
 * gris ahuesado (el atelier de la e[ad]); un único punto de luz carmín es la
 * nova. Editorial, sobrio, cálido. IBM Plex Sans (sans del cuerpo · UI ·
 * cabeceras, variable de dos ejes) · Roboto Serif (serif del cuerpo cuando
 * el lector la elige desde el menú, citas y poemas; variable de cuatro ejes
 * wght 100–900 + wdth 75–100 + opsz 8–60 + GRAD −50–100) · IBM Plex Mono
 * (código).
 * Ritmo vertical sobre la interlínea del cuerpo: la unidad es
 * `--sn-baseline`, todo line-height/margen vertical de bloque es un múltiplo.
 *
 * ── Tres capas (Material 3: reference → system → component) ──────────────
 *
 *   1. PRIMITIVAS (reference). Paleta cromática nombrada por lo que el
 *      color ES (papel, tinta, rojo, coral, azul, malva, verde, masking,
 *      blanco), con stops 50–950 donde el número crece con la oscuridad.
 *      Agnósticas del tema: se definen UNA SOLA VEZ en :root. No tocar
 *      salvo para añadir/retirar un color del taller.
 *
 *   2. SEMÁNTICA (system). Roles del skin (--sn-paper, --sn-ink,
 *      --sn-nova, --sn-link, --sn-danger, …): cada rol apunta a UNA
 *      primitiva por tema vía light-dark(). El conmutador es color-scheme,
 *      conducido por [data-sn-theme]. Aquí se decide CUÁL es la tinta del
 *      cuerpo en cada tema, qué papel sostiene el chrome, etc. Capa que
 *      un diseñador del taller tocaría para reasignar un acento sin
 *      cambiar primitivas.
 *
 *   3. COMPONENTE (system, suprasemántica). Tokens de control
 *      (--sn-btn-*, --sn-field-*, --sn-on-*, --sn-opt-*, --sn-focus-*) y
 *      la capa Codex / WikimediaUI (--color-*, --background-color-*,
 *      --border-color-*). Consumen variables semánticas, NUNCA primitivas
 *      directas. Heredan automáticamente el flip de tema.
 *
 * Contratos externos en niveles SEMÁNTICA y COMPONENTE: los consumen los
 * TemplateStyles por plantilla (Plantilla:X/style.css), un espejo en
 * producción de MediaWiki:Common.css y OOUI. NO renombrar ni eliminar
 * variables --sn-* semánticas existentes, ni los alias Codex. La capa
 * primitiva es PURAMENTE ADITIVA por debajo.
 *
 * ── Conmutación de tema ──────────────────────────────────────────────────
 *
 * DEFAULT = CLARO. El esquema del SO NO oscurece por sí solo: solo se
 * aplica oscuro si el usuario lo elige explícitamente
 * (data-sn-theme="dark") o si elige seguir al SO (data-sn-theme="auto") y
 * el SO está en oscuro. Sin preferencia guardada → claro. (Diverge del
 * paso `os_scheme` del spec para el tema; decisión de producto del
 * usuario, 2026-05-18.)
 *
 * El flip lo conduce `color-scheme` en :root y light-dark() en cada rol
 * de color. Cada rol semántico de color se escribe UNA sola vez. Solo
 * cuatro tokens NO usan light-dark() (--sn-field-grain y los tres
 * --sn-lift*): varían en geometría además de color, y se redefinen
 * explícitamente en los selectores de tema al final del archivo.
 */

:root {
	/* ═══════════════════════════════════════════════════════════════════
	   CAPA 1 — PRIMITIVAS (REFERENCE)
	   Paleta cromática agnóstica del tema. Nombrada por hue (qué ES el
	   color), no por uso. Stops 50–950 con luminancia creciente.
	   ═══════════════════════════════════════════════════════════════════ */

	/* — Primitiva · papel — neutros cálidos claros (la hoja, sus pliegues, su sombra) */
	--sn-papel-50:  #fcfbf7;
	--sn-papel-75:  #f4f2ed;   /* fondo de página + pie (--sn-field claro) */
	--sn-papel-100: #f3f0e7;
	--sn-papel-200: #efece2;
	--sn-papel-300: #ece7da;
	--sn-papel-400: #e8e4da;
	--sn-papel-500: #e7e1d3;
	--sn-papel-600: #ddd6c7;
	--sn-papel-700: #a39a86;
	--sn-papel-800: #968c7c;

	/* — Primitiva · tinta — neutros cálidos oscuros (pizarra y tinta china del atelier) */
	--sn-tinta-300: #756d5d;
	--sn-tinta-400: #6b6357;
	--sn-tinta-500: #353123;
	--sn-tinta-600: #2c2410;
	--sn-tinta-650: #2a2719;
	--sn-tinta-700: #262318;
	--sn-tinta-750: #221f1a;   /* la tinta canónica del cuerpo (claro) */
	--sn-tinta-800: #1e1c15;
	--sn-tinta-850: #1a0f0b;   /* casi-negro para tinta sobre acento (oscuro) */
	--sn-tinta-900: #181610;
	--sn-tinta-950: #141209;

	/* — Primitiva · rojo — sangre del carmín (single source para nova y link claros) */
	--sn-rojo-500: #ae2d13;    /* la nova y el link nacen del mismo carmín */
	--sn-rojo-600: #b21e3e;    /* danger claro (vino más profundo, menos cálido) */
	--sn-rojo-700: #962c08;    /* link-hover claro */
	--sn-rojo-900: #612403;    /* link-visited claro */

	/* — Primitiva · coral — el rosa cálido del modo noche */
	--sn-coral-300: #c8a89a;   /* (legacy) */
	--sn-coral-400: #ff6b7e;   /* nova + danger oscuros (un coral, dos roles) */

	/* — Primitiva · rosa — el rosado pálido de las páginas inexistentes (redlink) */
	--sn-rosa-300: #e6acb7;    /* link-new oscuro (rosa pálido sobre papel noche) */
	--sn-rosa-400: #c98592;    /* link-new claro (rosa apagado, legible sobre crema) */

	/* — Primitiva · azul — único frío del skin, link de noche */
	--sn-azul-200: #b3cef2;
	--sn-azul-300: #8fb4e8;

	/* — Primitiva · malva — visitado en la noche */
	--sn-malva-400: #a99cae;

	/* — Primitiva · verde — la "ok" del taller */
	--sn-verde-400: #6cc18a;
	--sn-verde-500: #2f6f43;

	/* — Primitiva · masking — la cinta amarilla del taller y sus parientes */
	--sn-masking-300: #f7db7e;
	--sn-masking-500: #d9a14b;
	--sn-masking-700: #8a5a12;
	--sn-masking-900: #5a4416;

	/* — Primitiva · blanco — papel químicamente puro (solo donde la nova exige) */
	--sn-blanco: #ffffff;


	/* ═══════════════════════════════════════════════════════════════════
	   CAPA 2 — SEMÁNTICA (SYSTEM)
	   Roles del skin. Cada rol → primitiva, con flip de tema vía
	   light-dark(claro, oscuro). El default es claro (color-scheme: light);
	   los selectores [data-sn-theme] al final del archivo conmutan
	   color-scheme y light-dark() resuelve a la rama correspondiente.
	   ═══════════════════════════════════════════════════════════════════ */

	color-scheme: light;

	/* — Semántica · campo y papel — luz cálida (el papel tiene cuerpo, no es #fff puro) */
	--sn-field:        light-dark(var(--sn-papel-75), var(--sn-tinta-950));
	--sn-field-grain:  0.035;          /* opacidad del grano de papel; override por tema al final */
	--sn-paper:        light-dark(var(--sn-papel-50),  var(--sn-tinta-800));
	--sn-paper-edge:   light-dark(var(--sn-papel-100), var(--sn-tinta-700));
	--sn-sunk:         light-dark(var(--sn-papel-200), var(--sn-tinta-900));

	/* — Semántica · tinta — texto, secundarios, filetes */
	--sn-ink:          light-dark(var(--sn-tinta-750), var(--sn-papel-300));
	--sn-ink-soft:     light-dark(var(--sn-tinta-400), var(--sn-papel-700));
	--sn-ink-faint:    light-dark(var(--sn-papel-800), var(--sn-tinta-300));
	--sn-hairline:     light-dark(var(--sn-papel-600), var(--sn-tinta-500));
	--sn-hairline-soft:light-dark(var(--sn-papel-500), var(--sn-tinta-650));

	/* — Semántica · la nova — único acento agudo, usado con avaricia */
	--sn-nova:         light-dark(var(--sn-rojo-500),  var(--sn-coral-400));
	--sn-nova-ink:     light-dark(var(--sn-blanco),    var(--sn-tinta-850));
	/* nova-wash: lavados muy específicos (selección, fondo activo, foco-ring).
	   No promovidos a primitivas con nombre porque no se reutilizan en otro rol. */
	--sn-nova-wash:    light-dark(#f7e9e6,             #2b1c1d);

	/* — Semántica · enlaces — rojos en AMBOS temas (carmín en claro, coral rojo
	   en oscuro). El visitado es levemente más oscuro que el enlace base: en
	   claro el vino rojo-900; en oscuro un coral apagado (mezcla con negro). */
	--sn-link:         light-dark(var(--sn-rojo-500),  var(--sn-coral-400));
	--sn-link-hover:   light-dark(var(--sn-rojo-700),  color-mix(in oklab, var(--sn-coral-400) 80%, white));
	--sn-link-visited: light-dark(var(--sn-rojo-900),  color-mix(in oklab, var(--sn-coral-400) 75%, black));
	/* Enlace a página inexistente (redlink): ROSADO PÁLIDO en ambos temas. En
	   claro un rosa apagado legible sobre crema; en oscuro un rosa más pálido
	   sobre el papel noche. No se oscurece al visitarse (ver a.new:visited). */
	--sn-link-new:     light-dark(var(--sn-rosa-400), var(--sn-rosa-300));

	/* Íconos de UI: nunca negro pleno ni acento; tinta al ~55% en reposo,
	   tinta plena al hover/activo. Nunca carmín. (Color-mix sobre --sn-ink
	   ya voltea con el tema porque --sn-ink lo hace.) */
	--sn-icon:         color-mix(in oklab, var(--sn-ink) 55%, transparent);
	--sn-icon-active:  var(--sn-ink);

	/* — Semántica · señales — ok / warn / danger / notice */
	--sn-ok:           light-dark(var(--sn-verde-500),   var(--sn-verde-400));
	--sn-warn:         light-dark(var(--sn-masking-700), var(--sn-masking-500));
	--sn-warn-ink:     light-dark(var(--sn-papel-50),    var(--sn-tinta-850));
	--sn-danger:       light-dark(var(--sn-rojo-600),    var(--sn-coral-400));

	/* Aviso editorial (banner .sn-notice gestionado desde Stella-Nova:Aviso):
	   fondo ámbar pálido propio (no se confunde con --sn-warn-wash). En claro
	   fondo masking-300 y tinta tinta-600; en oscuro se invierte (fondo
	   masking-900, tinta masking-300) — la misma primitiva ámbar reaparece. */
	--sn-notice-bg:    light-dark(var(--sn-masking-300), var(--sn-masking-900));
	--sn-notice-ink:   light-dark(var(--sn-tinta-600),   var(--sn-masking-300));

	/* Washes de señales: fondos suaves para badges, banners, drop-targets,
	   bloques de información y aviso. Derivados con color-mix sobre tokens
	   semánticos para que el flip oscuro venga gratis. (Reemplazan a colores
	   hardcoded como #d5fdf4, #fef6e7, #fee7e6, #fcfada/#fffded del
	   Common.css original.) */
	--sn-ok-wash:     color-mix(in oklab, var(--sn-ok)     14%, var(--sn-paper));
	--sn-warn-wash:   color-mix(in oklab, var(--sn-warn)   14%, var(--sn-paper));
	--sn-danger-wash: color-mix(in oklab, var(--sn-danger) 12%, var(--sn-paper));
	--sn-info-wash:   color-mix(in oklab, var(--sn-warn)    8%, var(--sn-paper));


	/* ═══════════════════════════════════════════════════════════════════
	   CAPA 3a — COMPONENTE: controles del skin
	   Botones, campos, casillas, foco. Único origen de verdad para
	   stella-nova.css y skinStyles/oojs-ui.css. Consumen SOLO variables
	   semánticas (capa 2), nunca primitivas: heredan el flip automático.
	   ═══════════════════════════════════════════════════════════════════ */
	/* Botón neutro (enmarcado, acción secundaria) */
	--sn-btn-bg:            var(--sn-sunk);
	--sn-btn-bg-hover:      var(--sn-paper);
	--sn-btn-fg:            var(--sn-ink);
	--sn-btn-border:        var(--sn-hairline);
	--sn-btn-border-hover:  var(--sn-ink-faint);
	/* Acción primaria (Guardar): tinta plena, decidida — sin azul */
	--sn-btn-primary-bg:        var(--sn-ink);
	--sn-btn-primary-bg-hover:  color-mix(in oklab, var(--sn-ink) 86%, var(--sn-paper));
	--sn-btn-primary-fg:        var(--sn-paper);
	/* Acción destructiva primaria */
	--sn-btn-danger-bg:     var(--sn-danger);
	--sn-btn-danger-fg:     var(--sn-nova-ink);
	/* Campos de texto / select / textarea */
	--sn-field-bg:          var(--sn-paper);
	--sn-field-bg-disabled: var(--sn-sunk);
	--sn-field-fg:          var(--sn-ink);
	--sn-field-border:      var(--sn-hairline);
	--sn-field-placeholder: var(--sn-ink-faint);
	/* Marcado (casilla/radio/interruptor activos) */
	--sn-on-bg:             var(--sn-ink);
	--sn-on-fg:             var(--sn-paper);
	/* Opciones de menú/desplegable */
	--sn-opt-hover-bg:      var(--sn-sunk);
	--sn-opt-sel-bg:        var(--sn-nova-wash);
	--sn-opt-fg:            var(--sn-ink);
	/* Foco: único uso de la nova en controles (aro + borde) */
	--sn-focus-border:      var(--sn-nova);
	--sn-focus-ring:        0 0 0 2px var(--sn-nova-wash);
	/* Velo de diálogos modales */
	--sn-scrim:             color-mix(in oklab, var(--sn-ink) 45%, transparent);

	/* — Tipografía — dos familias para texto. IBM Plex Sans (sans del cuerpo,
	   UI y TODAS las cabeceras; VARIABLE, dos ejes wght 100–700 + wdth 75–100)
	   y Roboto Serif (serif del cuerpo cuando el lector la elige desde el
	   menú; siempre en citas/poemas; VARIABLE, cuatro ejes wght 100–900 +
	   wdth 75–100 + opsz 8–60 + GRAD −50–100). IBM Plex Mono para código.
	   Auto-alojadas (fonts.css). `--sn-font-display` queda
	   como alias de `--sn-font-text`: la doctrina del skin es "todo sans en
	   cabeceras"; el alias permite reintroducir un display distinto tocando
	   solo el token. */
	/* Primitivas tipográficas: las dos familias reales (no se redefinen al alternar). */
	--sn-font-sans:  'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
	--sn-font-serif: 'Roboto Serif', 'Iowan Old Style', Palatino, 'Times New Roman', Georgia, serif;
	--sn-font-mono:  'IBM Plex Mono', ui-monospace, 'SFMono-Regular', 'Cascadia Code', Menlo, Consolas, monospace;
	/* Semánticas: alias que el resto del skin consume.
	   --sn-font-text  → familia del cuerpo + UI + cabeceras (default: sans).
	   --sn-font-quote → familia de citas y <poem>            (default: serif).
	   Al activar data-sn-family="serif" desde el menú del usuario, ambos
	   se invierten en el bloque al final de este archivo:
	     · cuerpo + UI    → serif
	     · citas/<poem>   → sans
	   Mantener este patrón al introducir nuevos componentes editoriales:
	   consumir --sn-font-text o --sn-font-quote, NUNCA las primitivas. */
	--sn-font-text:  var(--sn-font-sans);
	--sn-font-display: var(--sn-font-text);
	--sn-font-quote: var(--sn-font-serif);

	/* Grado (eje GRAD de Roboto Serif): engrosa el trazo SIN cambiar el ancho
	   ni el avance del texto — la caja no se mueve, solo el peso percibido.
	   Únicamente la serif tiene este eje; sans y mono lo ignoran. Se aplica a
	   TODA la cascada vía font-variation-settings en :root (línea siguiente),
	   para que acompañe a la serif dondequiera que aparezca: citas/poemas y el
	   cuerpo+UI cuando el lector alterna a familia serif. Default 0; sube en
	   modo oscuro para compensar el aclaramiento óptico del texto claro sobre
	   fondo negro (los overrides de tema más abajo). */
	--sn-serif-grade: 70;
	font-variation-settings: "GRAD" var(--sn-serif-grade);

	/* Ancho del cuerpo (eje wdth, vía font-stretch). 100% = normal, 75% = lo
	   más condensado. Lo comparten las DOS familias de texto: IBM Plex Sans
	   (sans) y Roboto Serif (serif) declaran el MISMO rango wdth 75–100, así
	   el cuerpo condensa por igual elija el lector la familia que elija.
	   Aplica a párrafos, listas y tablas (el cuerpo de .sn-body); las
	   CABECERAS y las CITAS se resetean a 100% aparte (doctrina: condensada
	   solo en el cuerpo corrido, no en titulares ni cita). El VERSO tiene su
	   propio ancho (--sn-poem-width, abajo). Mono no tiene eje de ancho →
	   font-stretch lo ignora sin daño. Sube a 100% para desactivar la
	   condensación; baja hacia 75% para más densidad. */
	--sn-text-width: 80%;

	/* Verso (<poem>): tratamiento editorial propio, distinto del cuerpo y de
	   la cita. Más condensado que el cuerpo (la métrica del verso pide una
	   columna estrecha), algo menor y de peso medio para diferenciar su voz.
	   El tamaño es % del cuerpo → hereda la preferencia FontSize del lector;
	   el peso es literal (el skin no tokeniza pesos, salvo este caso de voz
	   editorial). */
	--sn-poem-width:  77%;
	--sn-poem-size:   85%;
	--sn-poem-weight: 500;

	/* Escala fluida sobria (sin titulares exagerados). --sn-font-scale lo
	   modula la preferencia tamaño-de-fuente. --sn-leading es FIJO (no es
	   preferencia de usuario): cambiarlo desparametriza el ajuste al
	   baseline grid. */
	--sn-font-scale: 1;
	--sn-leading: 1.65;

	--sn-fs-root:  calc(1rem * var(--sn-font-scale));
	--sn-fs-xs:    clamp(.72rem, .70rem + .10vw, .78rem);
	--sn-fs-sm:    clamp(.82rem, .79rem + .14vw, .90rem);
	--sn-fs-base:  calc(clamp(1rem, .97rem + .16vw, 1.05rem) * var(--sn-font-scale));
	--sn-fs-md:    calc(clamp(1.05rem, 1.01rem + .22vw, 1.18rem) * var(--sn-font-scale));
	--sn-fs-lg:    calc(clamp(1.2rem, 1.12rem + .40vw, 1.42rem) * var(--sn-font-scale));
	--sn-fs-xl:    calc(clamp(1.35rem, 1.22rem + .55vw, 1.62rem) * var(--sn-font-scale));
	--sn-fs-display: calc(clamp(1.5rem, 1.32rem + .85vw, 2rem) * var(--sn-font-scale));
	/* Título de página (h1#firstHeading): un escalón por encima del display
	   — la voz editorial del documento debe leerse mayor que cualquier h1
	   del cuerpo. */
	--sn-fs-page-title: calc(clamp(1.85rem, 1.55rem + 1.3vw, 2.5rem) * var(--sn-font-scale));
	/* Tamaño de TABLA — única fuente del "achicado" de tablas de contenido
	   (wikitable/smwtable/broadtable Y table.template/.plantilla): 80% de base, ABSOLUTO
	   (no compone con anidamiento, a diferencia de un `font-size: 80%` relativo).
	   Tunear aquí cambia todas las tablas a la vez. */
	--sn-fs-table: calc(var(--sn-fs-base) * .8);

	/* — Baseline grid — la unidad del ritmo vertical es la interlínea del
	   cuerpo: `--sn-baseline` = fs-base × leading. Escala SOLO con la
	   preferencia FontSize del usuario (leading y ancho de hoja son fijos),
	   para que el cálculo del ajuste al baseline grid no se desparametrice.
	   Reglas:
	     1. body usa `--sn-leading` (unitless) como line-height, para que
	        las cajas inline (sub, sup, code) hereden proporción.
	     2. Headings y bloques (p, ul/ol, blockquote, poem) usan
	        line-height ABSOLUTO en rem (= 1 o 2 baselines), garantizando
	        que la altura por línea cae siempre en la retícula.
	     3. Márgenes verticales de bloque (margin-top / margin-bottom) son
	        múltiplos enteros de --sn-baseline. El colapso vertical de MW
	        sigue funcionando porque NO usamos padding-top de compensación. */
	--sn-baseline:    calc(var(--sn-fs-base) * var(--sn-leading));
	--sn-baseline-2:  calc(var(--sn-baseline) * 2);
	--sn-baseline-3:  calc(var(--sn-baseline) * 3);

	/* — Ritmo espacial (escala 1.5) — */
	--sn-s-1: .25rem;  --sn-s-2: .5rem;   --sn-s-3: .75rem;
	--sn-s-4: 1rem;    --sn-s-5: 1.5rem;  --sn-s-6: 2.25rem;
	--sn-s-7: 3.5rem;  --sn-s-8: 5.25rem;

	/* Ancho máximo de la HOJA (no del texto: el contenido va al 100%).
	   Fijo, no es preferencia de usuario. */
	--sn-measure: 64rem;         /* (ex 46~50) medida de lectura: única restricción de ancho de la hoja (~75-80 car. en Plex Sans). Sustituye al viejo truco `width:80%` sobre los <p>. Súbelo/bájalo a gusto. */
	--sn-rail: 15.5rem;           /* (sin uso; reservado)                   */
	--sn-shell: 81rem;            /* ancho máximo del conjunto (header/pie) */

	/* Ícono de enlace externo (Feather "external-link"), como máscara para
	   teñirlo con currentColor. Definido por diseño en el skin. */
	--sn-ext-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3Cpolyline points='15 3 21 3 21 9'/%3E%3Cline x1='10' y1='14' x2='21' y2='3'/%3E%3C/svg%3E");

	/* Altura común de los controles de la barra superior (todos igual). */
	--sn-ctl: 2.4rem;

	/* — Forma y materia — */
	--sn-radius-s: 2px;
	--sn-radius:   4px;
	--sn-radius-l: 8px;
	--sn-radius-paper: 3px;        /* la hoja: esquinas apenas redondeadas  */
	--sn-radius-pill: 999px;        /* badges, chips, contadores             */
	--sn-hair: 1px solid var(--sn-hairline);

	/* Sombra de la hoja: muy sutil, apenas un asentamiento sobre el campo.
	   Estos tres tokens varían geometría además de color entre temas, así
	   que NO usan light-dark(): se redefinen en los selectores de tema al
	   final del archivo. */
	--sn-lift-paper:
		0 1px 1px rgba(40, 33, 20, .03),
		0 3px 8px rgba(40, 33, 20, .04);
	--sn-lift:
		0 1px 2px rgba(40, 33, 20, .05),
		0 8px 24px rgba(40, 33, 20, .08);
	--sn-lift-soft: 0 2px 8px rgba(40, 33, 20, .05);

	/* — Movimiento (--sn-motion lo apaga la preferencia/SO) — */
	--sn-motion: 1;
	--sn-dur-1: calc(120ms * var(--sn-motion));
	--sn-dur-2: calc(220ms * var(--sn-motion));
	--sn-dur-3: calc(420ms * var(--sn-motion));
	--sn-ease: cubic-bezier(.22, 1, .36, 1);

	--sn-z-rail: 10;
	--sn-z-header: 20;
	--sn-z-panel: 40;
	--sn-z-skip: 60;


	/* ═══════════════════════════════════════════════════════════════════
	   CAPA 3b — COMPONENTE: alias Codex / WikimediaUI
	   OOUI (oojs-ui-core/widgets/windows) y algunas extensiones consumen
	   variables del design system Codex de Wikimedia con la forma
	     var(--color-base, #202122)
	     var(--background-color-progressive--active, #233566)
	     …etc.
	   Si la variable Codex no está definida en el documento, el var() cae
	   al hex hardcodeado — y esos hex NO respetan el tema (vienen del tema
	   claro Codex). Resultado en modo oscuro: texto #202122 sobre fondo
	   #181610 (contraste <1.1:1, ilegible), fondos #eaf3ff dentro de
	   controles oscuros, etc.
	   Aquí declaramos las variables Codex como ALIAS a tokens semánticos
	   (--sn-*). Como --sn-* ya voltea entre claro/oscuro vía light-dark(),
	   los alias Codex voltean con él automáticamente — sin redeclarar.
	   Estas reglas no tocan nada que YA defina explícitamente cada
	   --codex-* (si el día de mañana añadimos Codex completo, gana lo más
	   específico).
	   ═══════════════════════════════════════════════════════════════════ */

	/* Texto */
	--color-base:           var(--sn-ink);
	--color-base--hover:    var(--sn-ink);
	--color-base--subtle:   var(--sn-ink-soft);
	--color-subtle:         var(--sn-ink-soft);
	--color-emphasized:     var(--sn-ink);
	--color-placeholder:    var(--sn-ink-faint);
	--color-disabled:       var(--sn-ink-faint);
	/* `inverted` = el color que contrasta contra los fondos saturados
	   del propio tema (botón primario, scrim, etc.). En claro es el
	   papel; en oscuro también — porque `--sn-paper` voltea. */
	--color-inverted:       var(--sn-paper);
	--color-inverted-fixed: var(--sn-paper);

	/* Acento progresivo (en Stella Nova: carmín en claro, azul en
	   oscuro — el flip vive en --sn-link). Hover/active derivados con
	   color-mix hacia la tinta del tema, así "presionado" siempre
	   significa "intensificado" en ambos esquemas. */
	--color-progressive:           var(--sn-link);
	--color-progressive--hover:    color-mix(in oklab, var(--sn-link) 80%, var(--sn-ink));
	--color-progressive--active:   color-mix(in oklab, var(--sn-link) 65%, var(--sn-ink));
	--color-progressive--focus:    var(--sn-link);

	/* Destructivo: misma lógica, ahora atado a --sn-danger. */
	--color-destructive:           var(--sn-danger);
	--color-destructive--hover:    color-mix(in oklab, var(--sn-danger) 80%, var(--sn-ink));
	--color-destructive--active:   color-mix(in oklab, var(--sn-danger) 65%, var(--sn-ink));
	--color-destructive--focus:    var(--sn-danger);

	--color-error:    var(--sn-danger);
	--color-success:  var(--sn-ok);

	/* Fondos.
	   La pareja `neutral` / `neutral-subtle` la usan diálogos y formularios
	   de core para distinguir un contenedor "atenuado" del fondo base. En
	   Stella Nova ambos caen a `--sn-sunk` (la única gradación que la hoja
	   sostiene sin perder ritmo); si el día de mañana queremos un tercer
	   tono, conviene introducir `--sn-sunk-soft` y diferenciarlos aquí. */
	--background-color-base:                 var(--sn-paper);
	--background-color-base-fixed:           var(--sn-paper);
	--background-color-base-print:           var(--sn-paper);
	--background-color-interactive:          var(--sn-sunk);
	--background-color-interactive-subtle:   var(--sn-sunk);
	--background-color-neutral:              var(--sn-sunk);
	--background-color-neutral-subtle:       var(--sn-sunk);
	--background-color-disabled:             var(--sn-sunk);
	--background-color-disabled-subtle:      var(--sn-sunk);
	--background-color-inverted:             var(--sn-ink);
	--background-color-page-container:       var(--sn-field);
	--background-color-secondary:            var(--sn-sunk);
	--background-color-transparent:          transparent;

	--background-color-progressive:           var(--sn-link);
	--background-color-progressive--hover:    color-mix(in oklab, var(--sn-link) 80%, var(--sn-ink));
	--background-color-progressive--active:   color-mix(in oklab, var(--sn-link) 65%, var(--sn-ink));
	--background-color-progressive--focus:    var(--sn-link);
	--background-color-progressive-subtle:    var(--sn-nova-wash);

	--background-color-error:              var(--sn-danger);
	--background-color-error--hover:       color-mix(in oklab, var(--sn-danger) 80%, var(--sn-ink));
	--background-color-error--active:      color-mix(in oklab, var(--sn-danger) 65%, var(--sn-ink));
	--background-color-error-subtle:       var(--sn-danger-wash);
	--background-color-destructive:           var(--sn-danger);
	--background-color-destructive--hover:    color-mix(in oklab, var(--sn-danger) 80%, var(--sn-ink));
	--background-color-destructive--active:   color-mix(in oklab, var(--sn-danger) 65%, var(--sn-ink));
	--background-color-destructive--focus:    var(--sn-danger);
	--background-color-destructive-subtle: var(--sn-danger-wash);
	--background-color-warning-subtle:     var(--sn-warn-wash);
	--background-color-success-subtle:     var(--sn-ok-wash);
	--background-color-notice-subtle:      var(--sn-sunk);
	--background-color-backdrop-light:     var(--sn-scrim);
	--background-color-backdrop-dark:      color-mix(in oklab, var(--sn-ink) 70%, transparent);
	/* Diff: añadido = wash verde tenue, removido = wash carmín tenue. */
	--background-color-content-added:      var(--sn-ok-wash);
	--background-color-content-removed:    var(--sn-danger-wash);
	/* Tab framed (variante con borde — usada por Vector y algunas
	   extensiones que reusan ese estilo). */
	--background-color-tab-list-item-framed--hover:  var(--sn-sunk);
	--background-color-tab-list-item-framed--active: var(--sn-paper);

	/* "Quiet" buttons: lavados de tinta translúcidos — voltean con el
	   tema porque --sn-ink lo hace. */
	--background-color-button-quiet--hover:  color-mix(in oklab, var(--sn-ink) 5%, transparent);
	--background-color-button-quiet--active: color-mix(in oklab, var(--sn-ink) 12%, transparent);

	/* Bordes */
	--border-color-base:                var(--sn-ink-faint);
	--border-color-subtle:              var(--sn-hairline);
	--border-color-interactive:         var(--sn-ink-faint);
	--border-color-disabled:            var(--sn-hairline);
	--border-color-inverted:            var(--sn-paper);
	--border-color-notice:              var(--sn-ink-faint);
	--border-color-warning:             var(--sn-warn);
	--border-color-error:               var(--sn-danger);
	--border-color-success:             var(--sn-ok);
	--border-color-destructive--hover:  color-mix(in oklab, var(--sn-danger) 80%, var(--sn-ink));

	--border-color-input--hover:         var(--sn-ink-faint);
	--border-color-input-binary:         var(--sn-ink-faint);
	--border-color-input-binary--active: var(--sn-link);
	--border-color-input-binary--checked: var(--sn-link);
	--border-color-progressive--hover:   color-mix(in oklab, var(--sn-link) 80%, var(--sn-ink));
	--border-color-progressive--focus:   var(--sn-link);

	/* Sombras de foco */
	--box-shadow-color-progressive--focus:  var(--sn-link);
	--box-shadow-color-progressive--active: color-mix(in oklab, var(--sn-link) 65%, var(--sn-ink));
	--box-shadow-color-destructive--focus:  var(--sn-danger);
	--box-shadow-color-inverted:            var(--sn-paper);
}


/* ═══════════════════════════════════════════════════════════════════════
   Conmutación de tema. Solo dos responsabilidades:
     1. Setear `color-scheme` para que light-dark() resuelva al lado correcto.
     2. Sobrescribir los CINCO tokens NO-color que varían por tema
        (`--sn-field-grain`, los tres `--sn-lift*` y `--sn-serif-grade`):
        mezclan geometría/número y color, así que no encajan en light-dark()
        (light-dark() solo interpola colores). El grado sube en oscuro para
        compensar el aclaramiento óptico del texto claro sobre fondo negro.

   La duplicación de los overrides entre [data-sn-theme="dark"] y
   @media (prefers-color-scheme: dark) [data-sn-theme="auto"] es inevitable
   con CSS estándar: ambos selectores activan oscuro pero en condiciones
   diferentes (el primero ignora al SO, el segundo lo sigue). La duplicación
   pasó de 14 declaraciones a 4 y queda CONTENIDA aquí.
   ═══════════════════════════════════════════════════════════════════════ */

/* Forzado claro (sobreescribe cualquier preferencia del SO). */
:root[data-sn-theme="light"] { color-scheme: light; }

/* Forzado oscuro (el SO no participa). */
:root[data-sn-theme="dark"] {
	color-scheme: dark;
	--sn-field-grain: .05;
	--sn-lift-paper: 0 1px 1px rgba(0,0,0,.25), 0 3px 10px rgba(0,0,0,.35);
	--sn-lift:       0 1px 2px rgba(0,0,0,.35), 0 8px 22px rgba(0,0,0,.45), 0 24px 60px rgba(0,0,0,.4);
	--sn-lift-soft:  0 2px 12px rgba(0,0,0,.4);
	--sn-serif-grade: 30;
}

/* Auto: sigue al SO. `color-scheme: light dark` deja a light-dark() honrar
   el `prefers-color-scheme`. Los 4 no-color se aplican solo cuando el SO
   está en oscuro. */
:root[data-sn-theme="auto"] { color-scheme: light dark; }
@media (prefers-color-scheme: dark) {
	:root[data-sn-theme="auto"] {
		--sn-field-grain: .05;
		--sn-lift-paper: 0 1px 1px rgba(0,0,0,.25), 0 3px 10px rgba(0,0,0,.35);
		--sn-lift:       0 1px 2px rgba(0,0,0,.35), 0 8px 22px rgba(0,0,0,.45), 0 24px 60px rgba(0,0,0,.4);
		--sn-lift-soft:  0 2px 12px rgba(0,0,0,.4);
		--sn-serif-grade: 30;
	}
}


/* ── Preferencia: tamaño de fuente (spec FontSize) ──
   Única preferencia tipográfica modulable por el usuario. Ancho de hoja
   e interlineado son fijos: cambiarlos desparametriza el ajuste al
   baseline grid. */
:root[data-sn-font="small"]  { --sn-font-scale: .92; }
:root[data-sn-font="medium"] { --sn-font-scale: 1; }
:root[data-sn-font="large"]  { --sn-font-scale: 1.14; }

/* ── Familia tipográfica (preferencia del lector). Default = sans
   (cuerpo+UI en IBM Plex Sans, citas/<poem> en Roboto Serif). Al elegir
   "serif" desde el menú del usuario, se invierten los dos alias y, en
   cascada, todos los componentes que los consumen voltean a la vez:
     · cuerpo + UI + cabeceras → Roboto Serif
     · citas + <poem>          → IBM Plex Sans
   Las primitivas --sn-font-sans/--sn-font-serif no se tocan: las dos
   familias siguen disponibles, sólo cambia QUIÉN consume cuál. ── */
:root[data-sn-family="serif"] {
	--sn-font-text:  var(--sn-font-serif);
	--sn-font-quote: var(--sn-font-sans);
	/* Normaliza el TAMAÑO aparente de la serif al de la sans. Con
	   `font-optical-sizing: auto`, a tamaño de cuerpo el eje opsz de Roboto
	   Serif baja y su x-height REAL sube (≈0.53–0.56) → "crece" frente a IBM
	   Plex Sans (x-height ≈0.516). `font-size-adjust` fuerza la x-height
	   RENDERIZADA al objetivo de la sans, conservando el trazo óptico. Solo
	   en modo serif (el modo sans por defecto queda intacto). El valor es la
	   relación x-height/em de IBM Plex Sans; ajústalo para afinar. */
	--sn-serif-size-adjust: 0.516;
	font-size-adjust: var(--sn-serif-size-adjust);
	/* Roboto Serif renderiza más ANCHA y PESADA que IBM Plex Sans al mismo
	   ajuste. Se angosta el cuerpo serif al wdth más condensado (75%) para
	   acercar su mancha a la de la sans. El peso (normal y negrita) se afina
	   abajo. */
	--sn-text-width: 75%;
}
/* El monoespaciado (IBM Plex Mono) conserva su propio tamaño en modo serif:
   no se normaliza a la x-height de la sans. */
:root[data-sn-family="serif"] :is(code, pre, kbd, samp, tt, .mw-code) {
	font-size-adjust: none;
}
/* Serif: cuerpo un punto más fino (380) y negrita proporcionalmente más
   liviana (600, no 700), para calibrar el contraste normal↔negrita ahora que
   el cuerpo serif es menos pesado. `:where()` neutraliza la especificidad del
   selector de familia para NO pisar los pesos propios de las cabeceras. */
:where(:root[data-sn-family="serif"]) .sn-body { font-weight: 380; }
:where(:root[data-sn-family="serif"]) .sn-body :is(b, strong) { font-weight: 600; }

/* ── Movimiento: sólo la señal del SO (ya no es preferencia del usuario;
   reduced-motion sigue siendo 1ª clase). ── */
@media (prefers-reduced-motion: reduce) {
	:root { --sn-motion: 0; }
}
