/* 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 62.5–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));
	/* Superficie ELEVADA: la única del skin que abandona la temperatura
	   cálida del papel y va a los extremos —blanco puro en claro, negro
	   cálido en oscuro— para que una tarjeta al elevarse CONTRASTE con la
	   hoja en vez de fundirse con ella. Existe porque el sanitizador de
	   TemplateStyles rechaza `light-dark()`: una plantilla no puede escribir
	   una superficie que voltee con el tema, solo referenciar var(--token).
	   Este es ese token (contrato externo: lo consume Plantilla:Portafolio y
	   el hover-elevar transversal de tarjetas).

	   DOS ASIMETRÍAS CONOCIDAS, deliberadas (2026-07-20):
	   · En claro el salto contra el papel es de 3–8 puntos por canal, o sea
	     que el color casi no se ve y el gesto lo lleva --sn-lift. Es
	     intencional — el papel cálido ya es casi blanco.
	   · En oscuro el valor COINCIDE con --sn-field (el fondo de página tras
	     la hoja), así que la tarjeta se lee recortada sobre el fondo, no
	     flotando sobre el papel: efecto lightbox, no elevación Material.
	     Si algún día se quiere lo segundo, el cambio es una palabra:
	     --sn-tinta-950 → --sn-tinta-700 (un peldaño MÁS CLARO que el papel,
	     que es como la elevación se lee en oscuro). */
	--sn-paper-raised: light-dark(var(--sn-blanco), var(--sn-tinta-950));
	--sn-sunk:         light-dark(var(--sn-papel-200), var(--sn-tinta-900));
	/* Superficie del área de edición (el <textarea> de wikitexto y de
	   CSS/JS/JSON). Es un LAVADO, no una superficie de la escala papel:
	   un 3% de tinta disuelto en el papel de la página. Apenas se despega
	   del fondo —lo justo para que el área de escritura se lea como una
	   zona distinta— y voltea sola con el tema, porque sus dos ingredientes
	   ya voltean. Se separó de --sn-sunk (2026-07-20) porque el pozo del
	   bloque de código resultó demasiado marcado para un área grande: en
	   una caja de <pre> de cinco líneas es un acento, en un textarea de
	   pantalla completa es una mancha. Un solo número para calibrarlo. */
	--sn-edit-surface: color-mix(in oklab, var(--sn-ink) 3%, var(--sn-paper));

	/* — 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 62.5–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. Las DOS
	   familias de texto lo consumen, pero NO comparten piso: IBM Plex Sans
	   baja a wdth 75; Roboto Serif se re-instanció con piso 62.5 para que el
	   modo serif pueda apretar más (el override de familia más abajo cambia
	   este token). Este default (80%) es el del modo sans.
	   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 para más densidad (sans hasta 75, serif hasta 62.5). */
	--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.61;
	--sn-leading-table: 1.305;

	--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(1.04rem, 1rem + .18vw, 1.1rem) * var(--sn-font-scale));
	--sn-fs-md:    calc(clamp(1.15rem, 1.1rem + .24vw, 1.28rem) * 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): 85% 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.
	     4. El padding vertical que compensa MEDIA interlínea (p. ej. una
	        tarjeta que debe cerrar en un número entero de baselines) usa
	        `--sn-baseline-half`, NUNCA un valor de la escala de espaciado
	        (--sn-s-*): esa escala es fija en rem y no sigue a
	        --sn-font-scale, así que aproximarla deriva de la retícula al
	        cambiar la preferencia FontSize. El submúltiplo se calcula aquí
	        (calc() está permitido en la skin) y las TemplateStyles de la
	        wiki lo consumen como var() desnudo — el sanitizador de MW no
	        admite dividir dentro de calc() ni un fallback en el var(). */
	--sn-baseline:      calc(var(--sn-fs-base) * var(--sn-leading));
	--sn-baseline-half: calc(var(--sn-baseline) / 2);  /* media interlínea. Existe para que las TemplateStyles de la wiki cierren cajas (tarjetas) en un número entero de baselines sin dividir dentro de calc() — el sanitizador de MW no lo admite, así que la división ocurre aquí y la plantilla lo consume como var() desnudo. Padding vertical de compensación: usar este token, nunca --sn-s-* (fijo en rem, no sigue a --sn-font-scale). */
	--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) */
	/* Alto de la cabecera sticky: isotipo (2rem) + padding vertical de
	   .sn-header-bar (2 × --sn-s-3 = 1.5rem). Alimenta el scroll-padding-top de
	   la página, para que las anclas (TOC, #target) no queden bajo la barra. */
	--sn-header-h: 3.5rem;

	/* Í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 es el gesto de ELEVACIÓN (menús, popovers, tarjetas que se
	   despegan): desenfoque corto (6px) y desplazamiento vertical largo, para
	   que se lea "levantado hacia arriba" y no como una mancha difusa. El
	   spread negativo recoge la sombra hacia el centro y afila el borde. */
	--sn-lift:
		0 1px 1px rgba(40, 33, 20, .06),
		0 8px 6px -3px rgba(40, 33, 20, .16);
	--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 1px rgba(0,0,0,.45), 0 8px 6px -3px rgba(0,0,0,.6);
	--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 1px rgba(0,0,0,.45), 0 8px 6px -3px rgba(0,0,0,.6);
		--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 del woff2
	   re-instanciado (62.5%, su piso; ver fonts.css) para apretar su mancha
	   por debajo de lo que llega el sans (que solo baja a 75). El peso (normal
	   y negrita) se afina abajo. */
	--sn-text-width: 62.5%;
}
/* 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 más fino (350) 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.
   Tracking negativo: encima de la condensación por eje wdth (--sn-text-width
   en su piso 62.5%, ver arriba) se aprieta el avance — letra y, sobre todo,
   palabra (la serif abre demasiado los espacios al condensar). En `ex` para
   que escale con el tamaño de fuente del lector. Solo el cuerpo: cabeceras y
   citas conservan su avance natural. */
:where(:root[data-sn-family="serif"]) .sn-body {
	font-weight: 350;
	letter-spacing: -0.01ex;
	word-spacing: -0.13ex;
}
: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; }
}
