/* Estilo 2 — shared visual system for /medios and /informe.
 *
 * Extracted verbatim from the two approved mockups:
 *   - docs/mockup-propuesta-b.html   (base: hero, panels, items, table, side)
 *   - docs/mockup-propuesta-d-detalle.html (the .fx-* right slide-over "ficha")
 * Only additions beyond those two files: the --web/--web-bg tokens + .mark.w
 * (the mockups only had L/S), the --clase-* palette + .clase-* pill classes
 * (new requirement: a colour per medio classification, spec §3), a handful
 * of small classes ficha2.js/grafica.js need that neither static mockup
 * needed (an actual chart, a toggle button, a tooltip), and the motion
 * block for the slide-over open/close (the mockups were static screenshots,
 * not real dialogs). Everything new is clearly marked below.
 *
 * Spanish stays in the UI copy; this file has none — it is markup-agnostic.
 */

:root {
  --navy: #0f1b2d;
  --navy-2: #16243a;
  --surface: #f5f7fa;
  --card: #ffffff;
  --ink: #0f172a;
  --ink-2: #334155;
  --mut: #64748b;
  --line: #e2e8f0;
  --line-2: #cbd5e1;
  --accent: #0ea5a4; /* teal: acción y barras */
  --accent-2: #0f8584; /* hover de --accent (mockup.html), Task 11: .btn.primario:hover */
  --leo: #c2410c;
  --leo-bg: #ffedd5; /* Leolytics · naranja */
  --sem: #1d4ed8;
  --sem-bg: #dbeafe; /* Semrush · azul */
  --web: #1f6f5f;
  --web-bg: #e6f2ee; /* Web revisada · verde — nuevo respecto a los mockups */
  --ok: #15803d;
  --ok-bg: #dcfce7;
  --warn: #a16207;
  --warn-bg: #fef9c3;
  --no: #b91c1c;
  --no-bg: #fee2e2;
  --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Paleta de --clase-* (spec §3): un color por clasificación derivada.
   * Seis tonos distintos — Crecimiento fuerte y Crecimiento comparten el
   * verde (misma dirección, distinta intensidad de la métrica, no de color);
   * "Sin tendencia" no suma un séptimo tono: usa el neutro --mut/--line ya
   * definido arriba, coherente con "clase = null" siendo también neutro. */
  --clase-crecimiento: #15803d;
  --clase-crecimiento-bg: #dcfce7; /* verde, reutiliza --ok */
  --clase-estable: #475569;
  --clase-estable-bg: #e2e8f0; /* gris */
  --clase-descenso: #a16207;
  --clase-descenso-bg: #fef9c3; /* ámbar, reutiliza --warn */
  --clase-caida: #b91c1c;
  --clase-caida-bg: #fee2e2; /* rojo, reutiliza --no */
  --clase-nuevo: #1d4ed8;
  --clase-nuevo-bg: #dbeafe; /* azul, reutiliza --sem */
  --clase-muerto: #7f1d1d;
  --clase-muerto-bg: #fecaca; /* rojo oscuro, más saturado que --clase-caida */
}

* { box-sizing: border-box; }
html, body { margin: 0; }
body { background: var(--surface); color: var(--ink); font: 14.5px/1.5 var(--sans); -webkit-font-smoothing: antialiased; }
/* [hidden] must win over every author `display` rule (e.g. .wrap's
 * display:grid, .foot's display:flex) — without !important, the first
 * class selector below beats the user-agent stylesheet's [hidden] rule
 * and the "hidden" element stays visible. See .barra-seleccion[hidden]
 * further down for the same bug fixed locally before this global rule. */
[hidden] { display: none !important; }
.num { font-variant-numeric: tabular-nums; }
a { color: inherit; text-decoration: none; }

/* banda superior */
.hero { background: linear-gradient(180deg, var(--navy), var(--navy-2)); color: #fff; padding: 0 0 28px; }
.nav { display: flex; align-items: center; justify-content: space-between; padding: 14px 32px; border-bottom: 1px solid rgba(255,255,255,.08); font-size: 13px; color: #b6c2d6; }
.nav b { color: #fff; font-weight: 600; letter-spacing: -.01em; }
.nav a { margin-left: 18px; }
.nav a:hover { color: #fff; }
.hero-in { max-width: 1180px; margin: 0 auto; padding: 28px 32px 0; }
.crumb { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: #8fa1bb; margin: 0 0 10px; }
h1 { font: 600 30px/1.15 var(--sans); letter-spacing: -.02em; margin: 0 0 6px; }
.tags { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 24px; }
.tag { font-size: 12px; padding: 3px 9px; border-radius: 999px; background: rgba(255,255,255,.1); color: #dbe4f0; }
/* repeat(6,1fr): matches medios.html's actual six cards (the mismatched
 * repeat(5,1fr) this replaces is why they wrapped onto two rows — a real
 * dashboard needs them in one). informe.html's #kpis sets its own
 * grid-template-columns inline (auto-fit,minmax(150px,1fr)), which always
 * wins over this class rule, so its variable-count KPI row is unaffected.
 * Padding/font here are also tightened (spec: "smaller numbers, less
 * vertical padding") — shared safely across both pages' cards. */
.kpis { display: grid; grid-template-columns: repeat(6,1fr); gap: 10px; }
.kpi { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1); border-radius: 10px; padding: 10px 12px; }
.kpi .v { font: 600 20px/1 var(--sans); letter-spacing: -.02em; }
.kpi .v small { font-size: 13px; color: #9fb0c8; margin-left: 2px; font-weight: 500; }
.kpi .l { font-size: 11.5px; color: #9fb0c8; margin-top: 5px; }
.kpi.accent { border-color: rgba(14,165,164,.6); background: rgba(14,165,164,.12); }
.kpi.accent .v { color: #5eead4; }

/* cuerpo */
/* minmax(0, 1fr), never a bare 1fr: a grid track's implicit min-width is
 * auto, so the left column refuses to shrink below its content's min-content
 * width and the whole page ends up wider than the viewport (measured: 499 px
 * of horizontal overflow at 1374 px, with .wrap itself 1404 px wide despite
 * its own max-width of 1180 px). Every grid ancestor of wide content needs
 * this plus min-width: 0 on the child. */
.wrap { max-width: 1180px; margin: -16px auto 0; padding: 0 32px 64px; display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 20px; align-items: start; }
.wrap > * { min-width: 0; }
/* .wrap-ancho — page-scoped modifier, /medios only (dashboard: use the whole
 * window, not the 1180px reading width /informe and friends still want).
 * Only max-width changes; grid columns/gap/padding stay inherited from
 * .wrap so this never has to duplicate them. .panel-oculto (set on the same
 * element by the segmentation-panel toggle below) drops the 320px aside
 * track entirely so the table gets the full row, not just a wider left
 * track next to an empty-ish sidebar. */
.wrap-ancho { max-width: none; }
.wrap-ancho.panel-oculto { grid-template-columns: 1fr; }
/* min-width: 0 here too: a panel is a grid item in the explorer's left
 * column, and a grid track's automatic minimum is the item's min-content
 * width — driven by the wide table inside. Without this the panel grows to
 * 1404 px inside a 776 px track and the body scrolls sideways, even though
 * the table already lives in its own overflow-x container. */
.panel { background: var(--card); border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 1px 2px rgba(15,23,42,.04); min-width: 0; }
/* flex-wrap: a header with a heading + button/legend must wrap onto a
 * second line on a narrow viewport, never force the row (and everything
 * below it in the same block-formatting context) wider than the viewport. */
.ph { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 14px; padding: 14px 18px; border-bottom: 1px solid var(--line); }
.ph h2 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.legend, .fx-legend { display: flex; gap: 12px; font-size: 12px; color: var(--mut); flex-wrap: wrap; }
.fx-legend { margin: 0 0 14px; }
.mark { display: inline-grid; place-items: center; width: 16px; height: 16px; border-radius: 4px; font: 700 10px/1 var(--sans); margin-right: 4px; vertical-align: 1px; font-style: normal; }
.mark.l { background: var(--leo-bg); color: var(--leo); }
.mark.s { background: var(--sem-bg); color: var(--sem); }
.mark.w { background: var(--web-bg); color: var(--web); } /* nuevo: fuente "Web revisada" */
.criterio { padding: 16px 18px; border-bottom: 1px solid var(--line); color: var(--ink-2); font-size: 14.5px; line-height: 1.6; }
.criterio b { color: var(--ink); font-weight: 600; }

/* ===== .filtros-grid — barra de filtros del explorador (nuevo: ningún
 * mockup tenía un formulario de filtros, solo un párrafo .criterio).
 * auto-fit/minmax en vez de columnas fijas: cada campo declara el ancho
 * mínimo que necesita y la rejilla decide cuántos caben por fila (4, 2, 1…)
 * sin que ninguna combinación de ancho de viewport llegue a desbordar —
 * la alternativa (columnas fijas + flex-wrap con min-width en cada campo)
 * es la que producía el desborde horizontal que este bloque reemplaza.
 * min-width:0 en .campo invalida el mínimo automático "no encoger por
 * debajo del contenido" que grid aplica por defecto a sus items. */
.filtros-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px 18px; padding: 16px 18px; min-width: 0; }
/* .campo is dual-purpose: the filter-row field above (unused today — no
 * page renders class="campo" yet, .filtros-grid predates .fila-filtro/
 * .grupo-filtro) AND, from Task 11, a modal form field (mockup.html's
 * .campo, used by abrirModal() callers building forms with escapeHtml() +
 * <label>/<input>). Extended in place rather than duplicated under a
 * second selector: margin-bottom/padding/border only matter to whichever
 * container it's actually placed in, and nothing currently depends on the
 * bare 4px gap. */
.campo { display: flex; flex-direction: column; gap: 5px; min-width: 0; margin-bottom: 14px; }
.campo label { font-size: 12px; color: var(--mut); font-weight: 600; }
.campo input, .campo select { width: 100%; min-width: 0; font: inherit; color: inherit; padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); }
.campo-ancho { grid-column: 1 / -1; }
.campo-check { display: flex; flex-wrap: wrap; gap: 10px 18px; align-items: center; }
.campo-check span { display: inline-flex; align-items: center; gap: 5px; }

/* fila de medio */
.item { display: grid; grid-template-columns: 1fr 200px; gap: 18px; padding: 18px; border-bottom: 1px solid var(--line); }
.item:last-child { border-bottom: 0; }
.item .head { display: flex; align-items: center; gap: 10px; margin: 0 0 6px; }
.item h3 { margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
.fit { font-size: 11.5px; font-weight: 600; padding: 2px 8px; border-radius: 999px; }
.fit.ok { background: var(--ok-bg); color: var(--ok); }
.fit.part { background: var(--warn-bg); color: var(--warn); }
.fit.no { background: var(--no-bg); color: var(--no); }
.meta { font-size: 12.5px; color: var(--mut); margin: 0 0 12px; }
.meta .cat, .cat { background: var(--surface); border: 1px solid var(--line); border-radius: 4px; padding: 1px 7px; margin-right: 5px; color: var(--ink-2); display: inline-block; font-size: 12.5px; }
.metrics { display: grid; grid-template-columns: repeat(3,110px); gap: 10px; margin: 0 0 10px; }
.metric .v { font: 600 18px/1.1 var(--sans); letter-spacing: -.01em; }
.metric .l { font-size: 11.5px; color: var(--mut); margin-top: 3px; }
.bar { height: 6px; background: var(--surface); border: 1px solid var(--line); border-radius: 4px; overflow: hidden; margin: 0 0 12px; max-width: 340px; }
.bar i { display: block; height: 100%; background: var(--accent); }
.why { margin: 0; color: var(--ink-2); font-size: 14px; line-height: 1.55; }
.web { margin: 8px 0 0; font-size: 13px; color: var(--ink-2); padding: 8px 10px; background: var(--surface); border-radius: 8px; border-left: 3px solid var(--accent); }
.web b { color: var(--ink); font-weight: 600; }
.warn { display: flex; gap: 7px; align-items: flex-start; margin: 10px 0 0; font-size: 13px; color: var(--warn); }
.warn svg { flex: none; margin-top: 2px; }
.side { text-align: right; }
.side .price { font: 600 26px/1 var(--sans); letter-spacing: -.02em; }
.side .pl { font-size: 12px; color: var(--mut); margin-top: 6px; }
.side .pl b { color: var(--ink-2); font-weight: 600; }
.side .btn, .btn { display: inline-block; margin-top: 14px; font-size: 12.5px; font-weight: 600; color: var(--accent); border: 1px solid var(--accent); border-radius: 8px; padding: 6px 11px; background: none; cursor: pointer; font-family: var(--sans); }
.side .btn:hover, .btn:hover { background: var(--accent); color: #fff; }

/* tablas */
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--mut); font-weight: 600; text-align: left; padding: 10px 18px; background: var(--surface); border-bottom: 1px solid var(--line); }
td { padding: 12px 18px; border-bottom: 1px solid var(--line); vertical-align: top; color: var(--ink-2); }
tr:last-child td { border-bottom: 0; }
td.m { color: var(--ink); font-weight: 600; }
th.r, td.r { text-align: right; }
td.why { font-size: 13px; max-width: 380px; }

/* lateral */
.sticky { position: sticky; top: 20px; display: grid; gap: 10px; }
/* .seg-head / .seg-cuerpo — the segmentation aside's own persistent header
 * (medios.html only) and the collapsible body below it. The header stays
 * outside #seg-cuerpo specifically so its "Mostrar panel" button is still
 * reachable once the body is hidden — a toggle buried inside the thing it
 * hides could never be clicked again. .seg-cuerpo carries the 16px gap the
 * five .box panels used to get straight from .sticky, now that .sticky's
 * own gap (above) is the smaller one between the header and the body. */
/* Opaque card of its own: .wrap pulls the grid 16px up into the dark hero so
 * the panels' rounded tops overlap it, and this header is short enough to
 * land entirely inside that band — where light-mode grey text and a teal
 * outlined button are unreadable. Giving it the same surface as every other
 * panel fixes the contrast and keeps the column visually consistent. */
.seg-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 9px 12px 9px 16px; box-shadow: 0 1px 2px rgba(15,23,42,.04); }
.seg-head h3 { margin: 0; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--mut); font-weight: 600; }
.seg-head .btn { margin-top: 0; }
.seg-cuerpo { display: grid; gap: 16px; }
.box { padding: 16px 18px; }
.box h4 { margin: 0 0 12px; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--mut); font-weight: 600; }
.split { height: 12px; border-radius: 6px; overflow: hidden; display: flex; background: var(--surface); border: 1px solid var(--line); margin: 0 0 10px; }
.split i { display: block; height: 100%; }
.split .a { background: var(--accent); }
.split .b { background: var(--line-2); }
.row { display: flex; justify-content: space-between; font-size: 13.5px; padding: 6px 0; border-bottom: 1px dashed var(--line); }
.row:last-child { border: 0; }
.row .k { color: var(--mut); }
.row .v { font-weight: 600; }
.row.total .v { color: var(--accent); }
.src { font-size: 12.5px; color: var(--mut); line-height: 1.6; }
.src b { color: var(--ink-2); font-weight: 600; }
.foot { max-width: 1180px; margin: 0 auto; padding: 0 32px 40px; font-size: 12px; color: var(--mut); display: flex; justify-content: space-between; }
.foot code { font-family: var(--mono); font-size: 11.5px; }

@media (max-width: 960px) {
  .wrap { grid-template-columns: 1fr; }
  .kpis { grid-template-columns: repeat(2,1fr); }
  .item { grid-template-columns: 1fr; }
  .side { text-align: left; }
  .sticky { position: static; }
}

/* ===== docs/mockup-propuesta-d-detalle.html — panel deslizante (ficha) ===== */

.fx-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin: 0 0 10px; }
.fx-title { font-size: 22px; font-weight: 600; letter-spacing: -.02em; }
.fx-sub { font-size: 13px; color: var(--mut); margin-top: 4px; }
.fx-cat { background: rgba(0,0,0,.05); border-radius: 4px; padding: 1px 7px; margin-left: 5px; color: var(--ink-2); }
.fx-close { border: 0; background: transparent; font-size: 22px; color: var(--mut); cursor: pointer; width: 32px; height: 32px; border-radius: 8px; }
.fx-close:hover { background: rgba(0,0,0,.06); }
.fx-sec { border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; margin: 0 0 10px; background: #fff; border-left-width: 3px; }
.fx-sec.s { border-left-color: var(--sem); }
.fx-sec.l { border-left-color: var(--leo); }
.fx-sec.w { border-left-color: var(--web); }
.fx-sec.c { border-left-color: var(--line-2); }
.fx-sec h5 { margin: 0 0 10px; font: 600 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mut); }
.fx-sec h5 em { font-style: normal; text-transform: none; letter-spacing: 0; font-weight: 500; margin-left: 6px; }
.fx-sec p { margin: 0; font-size: 14px; color: var(--ink-2); line-height: 1.6; }
.fx-gauges { display: grid; grid-template-columns: repeat(4,1fr); gap: 8px; margin: 0 0 12px; }
.fx-gauges div { background: rgba(0,0,0,.03); border-radius: 8px; padding: 10px; text-align: center; }
.fx-gauges b { display: block; font-size: 18px; font-variant-numeric: tabular-nums; }
.fx-gauges span { font-size: 11px; color: var(--mut); }
.fx-t { width: 100%; border-collapse: collapse; font-size: 13px; }
.fx-t th { font-weight: 500; color: var(--mut); text-align: left; padding: 4px 8px 6px 0; border-bottom: 1px solid var(--line); }
.fx-t td { padding: 7px 8px 7px 0; border-bottom: 1px solid var(--line); }
.fx-t .r { text-align: right; }
.fx-offer-card + .fx-offer-card { margin-top: 16px; padding-top: 16px; border-top: 1px dashed var(--line); }
.fx-offer-title { margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--ink); }
.fx-offer { display: grid; grid-template-columns: 120px 1fr; gap: 16px; }
.fx-price b { display: block; font-size: 22px; font-variant-numeric: tabular-nums; }
.fx-price span { font-size: 12px; color: var(--mut); }
.fx-kv { display: grid; grid-template-columns: auto 1fr; gap: 5px 14px; margin: 0; font-size: 13.5px; }
.fx-kv dt { color: var(--mut); }
.fx-kv dd { margin: 0; color: var(--ink-2); }
.fx-acts { display: flex; gap: 18px; font-size: 13.5px; font-weight: 600; margin-top: 6px; }
/* z-index explicit on both (not left to stacking-order-by-DOM-position):
 * the panel is appended straight onto <body>, and without an explicit
 * z-index it can end up painted underneath other positioned elements on
 * the page (e.g. the sticky segmentation sidebar) depending on how the
 * browser has already promoted them to their own layers — the bug this
 * fixes was a .fx that rendered with the page's own text showing through
 * it. background is repeated here as its own declaration (not
 * just inherited) so the panel is never transparent regardless of what
 * else changes upstream. */
.fx-back { position: fixed; inset: 0; background: rgba(15,23,42,.45); z-index: 1000; }
.fx { position: fixed; top: 0; right: 0; bottom: 0; width: min(520px,100vw); background: var(--surface); overflow: auto; padding: 22px 24px; box-shadow: -16px 0 40px rgba(0,0,0,.18); z-index: 1001; }

/* ===== .clase-* — clasificación derivada (spec §3), pill + color de trazo =====
 * Un mismo par background/color sirve para dos usos:
 *  1) <span class="clase-pill clase-caida"> — pastilla de texto (igual que .fit).
 *  2) elemento SVG (path/polyline/g) con class="clase-caida" — la propiedad
 *     `color` fija currentColor, y los trazos del gráfico usan
 *     stroke="currentColor"/fill="currentColor" para heredarlo. `background`
 *     no tiene efecto visual sobre un elemento SVG de forma, así que la
 *     misma clase es inocua ahí — no hace falta una segunda clase "solo trazo".
 *  3) <button class="chip-clase clase-caida"> (spec §9.2) — the --c custom
 *     property added below is what .chip-clase reads for its pressed-state
 *     tint, border and text colour, so no .clase-* needs a chip-specific
 *     override of its own. */
.clase-pill { display: inline-block; font-size: 11.5px; font-weight: 600; padding: 2px 9px; border-radius: 999px; line-height: 1.5; }
.clase-crecimiento-fuerte, .clase-crecimiento { --c: var(--clase-crecimiento); background: var(--clase-crecimiento-bg); color: var(--clase-crecimiento); }
.clase-estable { --c: var(--clase-estable); background: var(--clase-estable-bg); color: var(--clase-estable); }
.clase-descenso { --c: var(--clase-descenso); background: var(--clase-descenso-bg); color: var(--clase-descenso); }
.clase-caida { --c: var(--clase-caida); background: var(--clase-caida-bg); color: var(--clase-caida); }
.clase-nuevo { --c: var(--clase-nuevo); background: var(--clase-nuevo-bg); color: var(--clase-nuevo); }
.clase-muerto { --c: var(--clase-muerto); background: var(--clase-muerto-bg); color: var(--clase-muerto); }
.clase-sin-tendencia, .clase-sin-clase { --c: var(--mut); background: var(--line); color: var(--mut); }

/* ===== grafica.js — piezas nuevas, ningún mockup dibujaba un gráfico real ===== */

.grafica-wrap { position: relative; }
.grafica-svg { width: 100%; height: auto; display: block; overflow: visible; }
.grafica-svg:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.grafica-eje-texto { font-size: 10.5px; fill: var(--mut); font-family: var(--sans); }
.grafica-rejilla { stroke: var(--line); stroke-width: 1; }
.grafica-ventana { fill: var(--accent); opacity: .08; }
.grafica-ventana-etiqueta { font-size: 10px; fill: var(--mut); font-family: var(--sans); }
.grafica-mediana { stroke: var(--mut); stroke-width: 1; stroke-dasharray: 2 3; fill: none; }
.grafica-keywords { stroke: var(--sem); stroke-width: 1.5; fill: none; stroke-dasharray: 4 3; }
.grafica-guia { stroke: var(--ink-2); stroke-width: 1; pointer-events: none; }
.grafica-punto { fill: var(--card); stroke-width: 2; }
.grafica-pico { fill: var(--warn); stroke: var(--card); stroke-width: 1; }
.grafica-controles { display: flex; justify-content: flex-end; margin: 0 0 6px; }
.grafica-toggle { font-size: 11.5px; font-weight: 600; color: var(--sem); background: var(--sem-bg); border: 1px solid transparent; border-radius: 999px; padding: 3px 10px; cursor: pointer; font-family: var(--sans); }
.grafica-toggle[aria-pressed="false"] { color: var(--mut); background: var(--surface); border-color: var(--line); }
.grafica-tooltip { position: absolute; pointer-events: none; background: var(--navy); color: #fff; font-size: 12px; padding: 5px 9px; border-radius: 6px; white-space: nowrap; transform: translate(-50%,-100%); box-shadow: 0 4px 12px rgba(15,23,42,.25); top: 0; left: 0; }
.grafica-slot { min-height: 220px; }
.medidor-fuente { font-size: 9.5px; color: var(--mut); }
.coste-medicion { font-size: 13px; color: var(--ink-2); margin: 8px 0 0; }

/* ===== spec §9.2 — dressed filter controls =====
 * The explorer's filter bar was raw native <select>/<input> elements sitting
 * inside an otherwise fully designed page. Every control below is drawn by
 * this stylesheet: appearance:none on every native widget, our own borders,
 * our own focus ring and, where the browser would otherwise draw one, our
 * own icon as an inline SVG or data-uri — no third-party asset anywhere.
 * Shared baseline repeated across the rules below: 36px height (32px for
 * the segmented control), 1px solid var(--line), 8px radius, 13.5px text,
 * var(--ink) on var(--card); components override only what their own spec
 * bullet says is different (chip-clase's pill radius, the switch, etc). */

/* .app-nav — app-wide navigation bar inside the hero (spec §9.5): the same
 * row as .nav but for the fixed set of top-level screens (Catálogo,
 * Propuestas, Informes, Análisis) with an explicit aria-current state; kept
 * as its own class so .nav's existing meaning elsewhere is untouched.
 *
 * Task 11: montarCabecera() (public/app.js) renders the shared header as
 * .app-nav > .app-nav-left (brand + links) + .menu-usuario, ported from
 * docs/mockup.html's shell(). justify-content/flex-wrap added so the user
 * menu sits at the far right and the row still wraps on a narrow viewport;
 * the link rules move from ".app-nav a" to ".app-nav-left a" since links
 * now nest one level deeper — no page still renders a bare <a> directly
 * inside .app-nav. */
.app-nav { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 32px; border-bottom: 1px solid rgba(255,255,255,.08); font-size: 13px; flex-wrap: wrap; }
.app-nav-left { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.app-nav b.brand { color: #fff; font-weight: 600; letter-spacing: -.01em; margin-right: 4px; }
.app-nav-left a { position: relative; color: #b6c2d6; padding: 4px 0; }
.app-nav-left a:hover, .app-nav-left a[aria-current="page"] { color: #fff; }
.app-nav-left a[aria-current="page"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -5px; height: 2px; background: var(--accent); border-radius: 1px; }

/* ---- menú de usuario (arriba a la derecha del .app-nav) — mockup.html,
 * ported for montarCabecera(). ---- */
.menu-usuario { position: relative; }
.menu-usuario-btn { display: flex; align-items: center; gap: 8px; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.14); border-radius: 999px; padding: 4px 12px 4px 4px; color: #fff; cursor: pointer; font-size: 13px; font-family: var(--sans); }
.menu-usuario-btn:hover { background: rgba(255,255,255,.16); }
.avatar { display: inline-grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: var(--accent); color: #05201f; font-weight: 700; font-size: 12px; flex: none; }
.menu-usuario-panel { position: absolute; top: calc(100% + 8px); right: 0; z-index: 40; width: 260px; background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: 12px; padding: 14px; box-shadow: 0 16px 40px rgba(15,23,42,.28); }
.menu-usuario-panel .nombre { font-weight: 600; font-size: 14px; }
.menu-usuario-panel .email { font-size: 12.5px; color: var(--mut); margin: 2px 0 8px; word-break: break-all; }
.badge-role { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .04em; padding: 2px 8px; border-radius: 999px; text-transform: uppercase; }
.badge-role.admin { background: var(--sem-bg); color: var(--sem); }
.badge-role.usuario { background: var(--line); color: var(--ink-2); }

/* ---- estado de una invitación (Admin · Usuarios, task 15) — mockup.html,
 * ported. Only the three states this schema actually has: DONE (gasto
 * Semrush, a later task) is left out on purpose. ---- */
.estado-pill { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .03em; padding: 2px 9px; border-radius: 999px; }
.estado-pill.ACTIVO { background: var(--ok-bg); color: var(--ok); }
.estado-pill.INVITADO { background: var(--warn-bg); color: var(--warn); }
.estado-pill.REVOCADO { background: var(--no-bg); color: var(--no); }
.menu-usuario-panel .salir { display: block; width: 100%; margin-top: 12px; text-align: left; background: none; border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; font-size: 13px; font-weight: 600; color: var(--no); cursor: pointer; font-family: var(--sans); }
.menu-usuario-panel .salir:hover { background: var(--no-bg); }

/* ---- login (pages/login.html) — mockup.html, ported: the page already
 * shipped with these class names with no matching CSS yet. ---- */
.login-shell { min-height: 100vh; background: linear-gradient(180deg, var(--navy), var(--navy-2)); display: flex; align-items: center; justify-content: center; padding: 24px; }
.login-card { width: 100%; max-width: 380px; background: var(--card); border-radius: 16px; padding: 36px 30px; box-shadow: 0 30px 70px rgba(0,0,0,.35); text-align: center; }
.login-brand { font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: var(--mut); margin-bottom: 6px; }
.login-titulo { font-size: 21px; font-weight: 600; letter-spacing: -.01em; margin-bottom: 26px; color: var(--ink); }
.btn-google { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; height: 46px; border-radius: 10px; border: 1px solid var(--line-2); background: #fff; color: var(--ink); font-size: 14px; font-weight: 600; font-family: var(--sans); cursor: pointer; }
.btn-google:hover { background: var(--surface); }
.btn-google svg { flex: none; }
.login-nota { margin-top: 20px; font-size: 12px; color: var(--mut); line-height: 1.5; }

/* ---- sin acceso (pages/sin-acceso.html, pages/403.html) — mockup.html,
 * ported: both pages already shipped with these class names. ---- */
.sin-acceso-shell { min-height: 100vh; background: linear-gradient(180deg, var(--navy), var(--navy-2)); display: flex; align-items: center; justify-content: center; padding: 24px; }
.sin-acceso-card { width: 100%; max-width: 440px; background: var(--card); border-radius: 16px; padding: 36px 30px; box-shadow: 0 30px 70px rgba(0,0,0,.35); text-align: center; }
.sin-acceso-card svg { color: var(--no); margin-bottom: 14px; }
.sin-acceso-card h1 { font-size: 20px; margin-bottom: 10px; }
.sin-acceso-card p { color: var(--mut); font-size: 13.5px; }
.sin-acceso-card .correo { display: inline-block; margin: 10px 0 18px; font-family: var(--mono); font-size: 13px; background: var(--surface); padding: 5px 10px; border-radius: 6px; color: var(--ink-2); }
.sin-acceso-card a.volver { display: inline-block; margin-top: 4px; color: var(--accent); font-weight: 600; font-size: 13.5px; text-decoration: underline; }

/* ---- tarjetas de proyecto (pages/proyectos.html, futura tarea) —
 * mockup.html, ported. ---- */
.barra-proyectos { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; margin: 18px 0 20px; }
.grid-proyectos { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px,1fr)); gap: 16px; }
.tarjeta-proyecto { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 18px; box-shadow: 0 1px 2px rgba(15,23,42,.04); display: flex; flex-direction: column; gap: 10px; }
.tarjeta-proyecto:hover { border-color: var(--accent); box-shadow: 0 8px 24px rgba(15,23,42,.08); }
.tarjeta-proyecto h3 { font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
.tarjeta-proyecto h3 a { text-decoration: none; }
.tarjeta-proyecto h3 a:hover { text-decoration: underline; }
.tarjeta-proyecto .cliente { font-size: 13px; color: var(--mut); }
.tarjeta-proyecto .desc { font-size: 13px; color: var(--ink-2); flex: 1; }
.tarjeta-proyecto .meta { display: flex; justify-content: space-between; font-size: 12px; color: var(--mut); border-top: 1px dashed var(--line); padding-top: 10px; }
.tarjeta-proyecto .meta b { color: var(--ink-2); font-weight: 600; }

/* ---- pestañas del proyecto (Informes / Guardados / Utilizados) —
 * mockup.html, ported. ---- */
.tabs-proyecto { display: flex; gap: 24px; margin-top: 22px; border-bottom: 1px solid rgba(255,255,255,.12); overflow-x: auto; overflow-y: hidden; scrollbar-width: none; }
.tabs-proyecto a { position: relative; color: #b6c2d6; padding: 0 0 10px; font-size: 13.5px; font-weight: 600; white-space: nowrap; }
.tabs-proyecto a:hover, .tabs-proyecto a[aria-current="page"] { color: #fff; }
.tabs-proyecto a[aria-current="page"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--accent); border-radius: 1px; }
.tabs-proyecto .cuenta { display: inline-block; min-width: 20px; padding: 0 6px; margin-left: 5px; border-radius: 999px; background: rgba(255,255,255,.12); font-size: 11.5px; text-align: center; }
/* .tabs-proyecto reused for the Admin sub-nav (task 15): Semrush/Leolytics
 * aren't built yet, so they render as non-interactive text (aria-disabled),
 * not <a> — a parallel rule since .tabs-proyecto a's hover/current styling
 * doesn't apply to a non-link. */
.tabs-proyecto .tab-proximamente { display: inline-flex; align-items: baseline; gap: 6px; padding: 0 0 10px; font-size: 13.5px; font-weight: 600; color: rgba(182,194,214,.55); white-space: nowrap; cursor: default; }
.tabs-proyecto .tab-proximamente .etq { font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.kpis.kpis-5 { grid-template-columns: repeat(5,1fr); }
@media (max-width: 720px) { .kpis.kpis-5 { grid-template-columns: repeat(2,1fr); } }

/* ---- veredicto de Claude, origen del registro y usos del medio —
 * mockup.html, ported. ---- */
.veredicto { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; padding: 2px 9px; border-radius: 999px; white-space: nowrap; }
.veredicto.APTO { background: var(--ok-bg); color: var(--ok); }
.veredicto.DUDOSO { background: var(--warn-bg); color: var(--warn); }
.veredicto.DESCARTAR { background: var(--no-bg); color: var(--no); }
.veredicto.PENDIENTE { background: var(--line); color: var(--mut); }
.veredicto-motivo { display: block; font-size: 12.5px; color: var(--ink-2); margin-top: 5px; line-height: 1.45; }
.veredicto-fecha { display: block; font-size: 11.5px; color: var(--mut); margin-top: 3px; }
.origen-pill { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .03em; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
.origen-pill.WEB { background: var(--sem-bg); color: var(--sem); }
.origen-pill.MCP { background: var(--web-bg); color: var(--web); }
.origen-pill.CSV { background: var(--leo-bg); color: var(--leo); }
.uso-pill { display: inline-block; font-size: 11px; font-weight: 600; padding: 1px 7px; border-radius: 4px; margin: 4px 4px 0 0; }
.uso-pill.utilizado { background: var(--no-bg); color: var(--no); }
.uso-pill.guardado { background: var(--sem-bg); color: var(--sem); }
.url-texto { font-family: var(--mono); font-size: 12px; color: var(--ink-2); word-break: break-all; }
.acciones-celda { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }

/* ---- selección en el catálogo (checkboxes de fila + barra de acción) —
 * mockup.html, ported. ---- */
.tabla th.chk, .tabla td.chk { width: 34px; padding-right: 0; }
.tabla input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; margin: 2px 0 0; }
.tabla tr.seleccionada td { background: color-mix(in srgb, var(--accent) 7%, var(--card)); }
.barra-seleccion { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 10px 18px; background: color-mix(in srgb, var(--accent) 10%, var(--card)); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 5; font-size: 13px; color: var(--ink); }
.barra-seleccion b { font-weight: 700; }
.nota-excluidos { display: flex; gap: 7px; align-items: flex-start; padding: 10px 18px; font-size: 12.5px; color: var(--warn); background: var(--warn-bg); border-bottom: 1px solid var(--line); }
.nota-excluidos svg { flex: none; margin-top: 2px; }
.campo-inline { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--mut); font-weight: 600; }
/* Task 13 — the catalogue's table is .tabla-catalogo, not .tabla, so the
 * three .tabla selection rules above don't reach it (and adding .tabla to
 * it would drag in .tabla's own padding/min-width over .tabla-catalogo's).
 * The checkbox column is a fixed 36px <col> in catalogo.html — under
 * table-layout:fixed Chrome keeps it at 36px and scales the ten percentage
 * columns down to make room (checked in headless Chrome at 500/696/836/
 * 1200px: no horizontal scroll). It now takes the sticky left:0 slot via
 * :first-child, so Medio sticks right after it at that same 36px. The
 * bar/note start with the `hidden` attribute, which their display:flex
 * above would otherwise override. */
.tabla-catalogo th.chk, .tabla-catalogo td.chk { padding: 8px 0 8px 12px; text-overflow: clip; }
.tabla-catalogo input[type="checkbox"] { width: 16px; height: 16px; margin: 0; vertical-align: middle; accent-color: var(--accent); cursor: pointer; }
.tabla-catalogo th.chk + th, .tabla-catalogo td.chk + td { position: sticky; left: 36px; z-index: 1; background: var(--card); }
.tabla-catalogo thead th.chk + th { z-index: 3; background: var(--surface); }
.tabla-catalogo tbody tr.seleccionada td { background: color-mix(in srgb, var(--accent) 7%, var(--card)); }
.medio-usos { display: block; overflow: hidden; text-overflow: ellipsis; }
.barra-seleccion[hidden], .nota-excluidos[hidden] { display: none; }
.barra-seleccion .btn { margin-top: 0; }
.fx-sec.p { border-left-color: var(--accent); }
.fx-sec.p a { color: var(--accent); text-decoration: underline; }

/* ---- aviso tras una acción (public/app.js aviso()) — mockup.html
 * renderAviso(), ported verbatim. ---- */
.aviso-flash { display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; background: var(--card); border: 1px solid var(--line); border-left: 3px solid var(--ok); border-radius: 10px; padding: 10px 14px; margin: 18px 0 16px; font-size: 13.5px; color: var(--ink-2); box-shadow: 0 1px 2px rgba(15,23,42,.04); }
.aviso-flash.warn { border-left-color: var(--warn); }
.aviso-flash a { color: var(--accent); text-decoration: underline; font-weight: 600; }
.aviso-flash button { border: 0; background: none; color: var(--mut); cursor: pointer; font-size: 15px; border-radius: 6px; width: 26px; height: 26px; flex: none; font-family: var(--sans); }
.aviso-flash button:hover { background: var(--surface); }

/* ---- modal centrado (public/app.js abrirModal()) — mockup.html
 * envolverModal(), merged with its "modales grandes" override (.ancho,
 * max-height) into one rule instead of two, since nothing here needs them
 * to stay separate. .campo below already existed (filtros-grid, currently
 * unused by any page) with a plain column layout; extended in place with
 * the modal-field bits (margin-bottom, textarea, .error) rather than
 * duplicated under a second selector, since no page uses class="campo" for
 * both meanings at once. */
.modal-back { position: fixed; inset: 0; background: rgba(15,23,42,.5); display: grid; place-items: center; z-index: 600; padding: 20px; }
.modal { width: 100%; max-width: 440px; max-height: calc(100vh - 40px); overflow: auto; background: var(--card); border-radius: 14px; padding: 24px; box-shadow: 0 30px 70px rgba(0,0,0,.3); }
.modal.ancho { max-width: 660px; }
.modal h2 { font-size: 17px; margin-bottom: 16px; }
.modal .intro { font-size: 13px; color: var(--mut); margin: -8px 0 14px; }
.campo textarea { font: inherit; padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); color: var(--ink); resize: vertical; }
.campo .error { font-size: 12px; color: var(--no); }
.modal-acciones { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
.campos-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
@media (max-width: 560px) { .campos-2 { grid-template-columns: 1fr; } }
.lista-guardar { list-style: none; margin: 0 0 6px; padding: 0; max-height: 240px; overflow: auto; border: 1px solid var(--line); border-radius: 8px; }
.lista-guardar li { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 13px; }
.lista-guardar li:last-child { border-bottom: 0; }
.lista-guardar .ok { color: var(--ok); font-weight: 600; text-align: right; }
.lista-guardar .no { color: var(--no); font-weight: 600; text-align: right; }
.lista-guardar .mut { color: var(--mut); font-weight: 600; text-align: right; }
.csv-cols { font-family: var(--mono); font-size: 12px; background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: 7px 9px; color: var(--ink-2); word-break: break-all; margin: 0 0 12px; }
.csv-resumen { font-size: 13px; color: var(--ink-2); margin: 12px 0 8px; }
.tabla.compacta { min-width: 0; font-size: 12.5px; }
.tabla.compacta td, .tabla.compacta th { padding: 7px 9px; }

/* .btn variants the modal's own action buttons need (Cancelar/Guardar) —
 * pure additions: the existing ".side .btn, .btn" base rule above (§ tabla
 * lateral) is untouched so nothing already shipped shifts. Also the
 * generic focus ring from mockup.html, missing until now. */
.btn.primario { background: var(--accent); color: #fff; }
.btn.primario:hover { background: var(--accent-2); }
.btn.peligro { color: var(--no); border-color: var(--no); }
.btn.peligro:hover { background: var(--no); color: #fff; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn:disabled:hover { background: none; color: var(--accent); }
.btn-fantasma { background: none; border: 1px solid var(--line); color: var(--ink-2); border-radius: 8px; padding: 7px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer; font-family: var(--sans); }
.btn-fantasma:hover { background: var(--surface); }
.btn:focus-visible, a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, [tabindex]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- casos de uso con Claude Code — mockup.html, ported. ---- */
.casos { display: grid; grid-template-columns: minmax(0,1fr); gap: 4px; padding: 6px 18px 16px; }
.caso { min-width: 0; padding: 12px 0; border-bottom: 1px dashed var(--line); }
.caso pre.bloque-codigo { white-space: pre-wrap; overflow-wrap: anywhere; }
.caso:last-child { border-bottom: 0; }
.caso h3 { font-size: 13.5px; font-weight: 600; margin-bottom: 2px; color: var(--ink); }
.caso p { font-size: 13px; color: var(--ink-2); margin: 6px 0 0; }

/* ---- cuerpo de página de una sola columna (proyectos, proyecto, admin,
 * vista "hoja" del informe) — mockup.html, ported. Base .page-body/
 * .page-body-ancho added alongside .page-body-hoja: the modifier is
 * useless without the base rule it modifies, and no page uses .wrap for
 * these single-column layouts. ---- */
.page-body { max-width: 1180px; margin: -14px auto 0; padding: 0 24px 64px; }
.page-body-ancho { max-width: 1280px; }
.page-body-hoja { max-width: 1420px; }
.hero-in.hero-hoja { max-width: 1420px; }

/* ---- informe en vista hoja de cálculo, con filas desplegables —
 * mockup.html, ported verbatim. ---- */
.informe-grid { display: grid; grid-template-columns: minmax(0,1fr) 280px; gap: 20px; align-items: start; }
@media (max-width: 900px) { .informe-grid { grid-template-columns: minmax(0,1fr); } }
.barra-vista { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin: 28px 0 14px; font-size: 12.5px; font-weight: 600; color: var(--mut); }
.notas-hoja { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px,1fr)); gap: 16px; }
.hoja-cab { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 14px; margin: 0 0 8px; }
.hoja-cab h2 { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.hoja-cab span { font-size: 12px; color: var(--mut); }
.hoja-wrap { position: relative; overflow-x: auto; border: 1px solid var(--line-2); border-radius: 10px; background: var(--card); margin-bottom: 20px; box-shadow: 0 1px 2px rgba(15,23,42,.04); }
table.hoja { width: 100%; min-width: 1358px; border-collapse: collapse; table-layout: fixed; font-size: 12.5px; color: var(--ink-2); }
.hoja th, .hoja td { border: 1px solid var(--line); padding: 0 8px; height: 32px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hoja tr > :first-child { border-left: 0; }
.hoja tr > :last-child { border-right: 0; }
.hoja thead tr:first-child th { border-top: 0; }
/* text-transform/letter-spacing set explicitly here (not just inherited as
 * "normal"/"0"): the generic `th { text-transform: uppercase; letter-spacing:
 * .06em; }` rule above is otherwise still in effect for every property it
 * sets that this more specific rule doesn't override, which uppercased and
 * spaced-out these headers until they truncated ("TEND. VISIT…") at the
 * designed column widths. .grupo below stays uppercase on purpose — it's
 * more specific than this rule, so it still wins for those two properties. */
.hoja thead th { background: #eef2f6; color: var(--ink-2); font-size: 11px; font-weight: 700; text-align: left; height: 28px; text-transform: none; letter-spacing: 0; }
.hoja thead th button.orden { text-transform: none; letter-spacing: 0; font-size: 11px; font-weight: 700; }
.hoja thead th.grupo { text-align: center; letter-spacing: .06em; text-transform: uppercase; font-size: 10.5px; }
.hoja thead th.l { background: var(--leo-bg); color: var(--leo); }
.hoja thead th.s { background: var(--sem-bg); color: var(--sem); }
.hoja th.num, .hoja td.num { text-align: right; font-variant-numeric: tabular-nums; }
.hoja th button.orden { all: unset; cursor: pointer; display: block; width: 100%; text-align: inherit; }
.hoja th button.orden:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.hoja th .flecha { color: var(--accent); margin-left: 3px; }
.hoja td.idx, .hoja th.idx { background: #f1f5f9; color: var(--mut); text-align: center; font-size: 11px; padding: 0 4px; }
.hoja td.exp { padding: 0; text-align: center; }
.hoja td.medio { font-weight: 600; color: var(--ink); }
.hoja .fija { position: sticky; z-index: 1; }
.hoja .fija-1 { left: 0; }
.hoja .fija-2 { left: 36px; }
.hoja .fija-3 { left: 70px; box-shadow: 1px 0 0 var(--line-2); }
.hoja tbody td.fija, .hoja tfoot td.fija { background: var(--card); }
.hoja tbody td.idx.fija, .hoja tfoot td.fija { background: #f1f5f9; }
.hoja thead th.fija { background: #eef2f6; }
.hoja tbody tr.fila-medio { cursor: pointer; }
.hoja tbody tr.fila-medio:hover td:not(.idx) { background: #f8fafc; }
.hoja tbody tr.fila-medio.abierta td:not(.idx) { background: color-mix(in srgb, var(--accent) 8%, var(--card)); }
.hoja .pos { color: var(--ok); font-weight: 600; }
.hoja .neg { color: var(--no); font-weight: 600; }
.hoja .alerta { color: var(--warn); font-weight: 600; }
.hoja .vacia { color: var(--line-2); }
.hoja .pico { font-size: 10px; font-weight: 700; color: var(--warn); margin-left: 4px; }
.hoja .clase-pill { font-size: 10.5px; padding: 1px 7px; }
.bloque-tag { display: inline-block; font-size: 10.5px; font-weight: 700; padding: 1px 7px; border-radius: 4px; }
.bloque-tag.seleccion { background: var(--ok-bg); color: var(--ok); }
.bloque-tag.otro { background: var(--line); color: var(--ink-2); }
.hoja tfoot td { background: #eef2f6; font-weight: 700; color: var(--ink); }
.hoja tr.fila-detalle td { padding: 0; height: auto; white-space: normal; background: #f8fafc; }
.hoja tr.fila-detalle:not(.abierta) td { border-top: 0; border-bottom: 0; }
.btn-exp { display: inline-grid; place-items: center; width: 24px; height: 24px; border: 1px solid var(--line); border-radius: 6px; background: var(--card); color: var(--ink-2); cursor: pointer; padding: 0; }
.btn-exp:hover { border-color: var(--accent); color: var(--accent); }
.btn-exp svg { display: block; }
.btn-exp[aria-expanded="true"] svg { transform: rotate(90deg); }
.detalle-wrap { display: grid; grid-template-rows: 0fr; }
.detalle-wrap > div { overflow: hidden; min-height: 0; }
.hoja tr.fila-detalle.abierta .detalle-wrap { grid-template-rows: 1fr; }
.detalle-in { position: sticky; left: 0; display: grid; grid-template-columns: minmax(0,1fr) 260px; gap: 18px; padding: 14px 18px 16px 76px; max-width: min(1100px, calc(100vw - 48px)); }
.detalle-in h4 { font: 600 11px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; color: var(--mut); margin: 0 0 7px; }
.detalle-in .why { font-size: 13.5px; }
.detalle-lado { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; font-size: 12.5px; }
@media (max-width: 720px) { .detalle-in { grid-template-columns: 1fr; padding-left: 18px; } }

/* .buscador — search field with an inline SVG magnifier icon (spec §9.2).
 * The icon is markup (an inline <svg> child, per the contract), not a CSS
 * asset: this only positions it and clears room for it in the input.
 * type=search's own native decorations are stripped so nothing native shows. */
.buscador { position: relative; display: block; width: 100%; }
.buscador svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--mut); pointer-events: none; }
.buscador input { width: 100%; height: 36px; padding: 0 10px 0 32px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); color: var(--ink); font-size: 13.5px; font-family: var(--sans); appearance: none; -webkit-appearance: none; }
.buscador input::placeholder { color: var(--mut); }
.buscador input::-webkit-search-decoration,
.buscador input::-webkit-search-cancel-button,
.buscador input::-webkit-search-results-button,
.buscador input::-webkit-search-results-decoration { -webkit-appearance: none; appearance: none; }
.buscador input:disabled { opacity: .55; cursor: not-allowed; }

/* .grupo-filtro / .etiqueta — a labelled filter block (spec §9.2): layout
 * and label only, the control placed inside supplies its own look.
 * container-type: inline-size turns it into the query container .rango
 * (below) reads to know its own available width, independent of viewport. */
.grupo-filtro { display: flex; flex-direction: column; gap: 6px; min-width: 0; container-type: inline-size; }
.etiqueta { font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--mut); font-weight: 600; }

/* .segmentado — single-container segmented control (spec §9.2): one rounded
 * --surface box holds role="radio" buttons with no gap between them; the
 * checked button lifts to --card with a subtle shadow, the rest stay --mut.
 * Corners are rounded on the end buttons rather than clipped with
 * overflow:hidden, so a focused button's ring is never cut off. */
.segmentado { display: inline-flex; height: 32px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.segmentado button { flex: 1; height: 32px; padding: 0 12px; border: 0; background: transparent; color: var(--mut); font-size: 12.5px; font-weight: 600; font-family: var(--sans); cursor: pointer; }
.segmentado button:first-child { border-radius: 7px 0 0 7px; }
.segmentado button:last-child { border-radius: 0 7px 7px 0; }
.segmentado button[aria-checked="true"] { background: var(--card); color: var(--ink); box-shadow: 0 1px 2px rgba(15,23,42,.16); }
.segmentado button:disabled { opacity: .55; cursor: not-allowed; }

/* .chip-clase — clickable pill filter for one clase (spec §9.2). Reads its
 * colour from --c, set on each .clase-* modifier above (spec §3 palette),
 * so .chip-clase.clase-caida etc. need no extra rule of their own — only
 * the toggle's own shape and states live here. */
.chip-clase { --c: var(--mut); display: inline-flex; align-items: center; height: 36px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--line); background: var(--surface); color: var(--mut); font-size: 11.5px; font-weight: 600; font-family: var(--sans); cursor: pointer; }
.chip-clase[aria-pressed="true"] { background: color-mix(in srgb, var(--c) 12%, var(--card)); color: var(--c); border-color: var(--c); }
.chip-clase:disabled { opacity: .55; cursor: not-allowed; }

/* .select — wrapper that draws the chevron for a native <select> (spec
 * §9.2): appearance:none strips the browser's own arrow on the <select>,
 * the wrapper paints a hand-authored chevron as a data-uri background (no
 * external asset) and owns height/border/focus so the select is just the
 * hit target inside it. */
.select { position: relative; display: inline-block; width: 100%; height: 36px; }
.select select { appearance: none; -webkit-appearance: none; width: 100%; height: 100%; margin: 0; padding: 0 32px 0 10px; border: 1px solid var(--line); border-radius: 8px; background-color: var(--card); background-repeat: no-repeat; background-position: right 10px center; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3e%3cpath d='M1 1l4 4 4-4' stroke='%2364748b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"); color: var(--ink); font-size: 13.5px; font-family: var(--sans); cursor: pointer; }
.select select:disabled { opacity: .55; cursor: not-allowed; }
.select:focus-within { border-radius: 8px; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
.select:focus-within select { border-color: var(--accent); outline: none; }

/* .rango — paired numeric inputs with a centred en dash (spec §9.2). Uses a
 * container query, not a viewport media query, because the 200px threshold
 * is about the control's own available width inside its grid cell, which
 * viewport size alone doesn't tell you (relies on .grupo-filtro above being
 * the query container). Native number-input spinners are stripped too. */
.rango { display: flex; align-items: center; gap: 8px; }
.rango input { flex: 1 1 0; width: 0; min-width: 56px; height: 36px; padding: 0 8px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); color: var(--ink); font-size: 13.5px; font-family: var(--sans); text-align: center; appearance: textfield; -moz-appearance: textfield; }
.rango input::-webkit-outer-spin-button, .rango input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.rango span { flex: none; color: var(--mut); font-size: 13px; }
.rango input:disabled { opacity: .55; cursor: not-allowed; }
@container (max-width: 200px) {
  .rango { flex-wrap: wrap; }
  .rango input { flex: 1 1 45%; }
}

/* .interruptor — a real on/off switch built on a visually-hidden checkbox
 * (spec §9.2): the checkbox stays in the accessibility tree and keeps
 * keyboard/click behaviour, .pista is the only part actually painted. */
.interruptor { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--ink); cursor: pointer; }
.interruptor input { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.interruptor .pista { position: relative; flex: none; width: 34px; height: 20px; border-radius: 999px; background: var(--line-2); }
.interruptor .pista::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--card); box-shadow: 0 1px 2px rgba(15,23,42,.3); }
.interruptor input:checked + .pista { background: var(--accent); }
.interruptor input:checked + .pista::after { transform: translateX(14px); }
.interruptor input:disabled + .pista { opacity: .55; cursor: not-allowed; }
.interruptor:has(input:disabled) { cursor: not-allowed; }

/* custom checkboxes for .campo-check (spec §9.2 control rules): appearance:
 * none strips the native box, :checked paints an inline SVG tick via a
 * data-uri background so no native widget renders in the filter bar. */
.campo-check input[type="checkbox"] { appearance: none; -webkit-appearance: none; flex: none; width: 18px; height: 18px; margin: 0; border: 1px solid var(--line); border-radius: 4px; background-color: var(--card); background-repeat: no-repeat; background-position: center; cursor: pointer; }
.campo-check input[type="checkbox"]:checked { background-color: var(--accent); border-color: var(--accent); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='10' viewBox='0 0 12 10' fill='none'%3e%3cpath d='M1 5l3.5 3.5L11 1' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"); }
.campo-check input[type="checkbox"]:disabled { opacity: .55; cursor: not-allowed; }

/* .contador — small result-count badge next to a filter option (spec §9.2),
 * fed by /api/medios/facetas so people see how many media are behind a
 * value before picking it. */
.contador { display: inline-block; font-size: 11px; color: var(--mut); font-variant-numeric: tabular-nums; background: var(--surface); border-radius: 4px; padding: 1px 6px; }

/* .chips-activos / .chip-activo — the row of applied filters above the
 * table (spec §9.2/§9.3), each removable on its own via .quitar. */
.chips-activos { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 18px 14px; }
.chip-activo { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 6px 0 10px; border-radius: 999px; background: var(--sem-bg); color: var(--sem); font-size: 12.5px; font-weight: 600; }
.chip-activo .quitar { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border: 0; border-radius: 50%; background: transparent; color: inherit; font-size: 13px; line-height: 1; cursor: pointer; font-family: var(--sans); }
.chip-activo .quitar:hover { background: rgba(29,78,216,.18); }

/* .tabla-scroll / .tabla-catalogo — the catalogue table (spec §9.2/§9.4):
 * its own horizontal scroller, sticky header row and sticky first column,
 * each on a solid background so content scrolling underneath never shows
 * through. z-index order: top-left corner (3) > header row / first column
 * (2 / 1) > body. */
.tabla-scroll { overflow-x: auto; min-width: 0; }
/* table-layout: fixed + the <colgroup> percentage widths in medios.html turn
 * the ten default columns into a fixed proportion of whatever width the
 * table actually gets — the table always fills exactly 100% of its
 * container (never forces it wider), so no default-column configuration can
 * push .tabla-scroll into a sideways scrollbar. min-width is a deliberate
 * floor, not a mistake: without one, columns would keep shrinking forever
 * on a narrower viewport until they're unreadable. 480px is chosen to sit
 * comfortably under both guaranteed no-scroll widths (the table container
 * is ~696px at a 1100px viewport with the panel shown, ~836px at 900px with
 * it hidden — see medios.html's .pista-scroll comment) so the floor never
 * fires inside either guarantee, only below them, which is exactly when
 * horizontal scroll is allowed to reappear. */
.tabla-catalogo { table-layout: fixed; min-width: 480px; border-collapse: separate; border-spacing: 0; }
/* .columnas-extra — medios.html toggles this on the table whenever at least
 * one of the 28 optional columns (OPCIONALES) is visible. Tested live: none
 * of them has a width declared in the <colgroup> above (the ten default
 * columns already sum to 100%, on purpose — see the guarantee comment
 * above), and under table-layout:fixed an undeclared column doesn't widen
 * the table to fit itself, it gets squeezed to 0 and effectively
 * disappears. Falling back to table-layout:auto here — content-sized
 * columns, the browser's ordinary table behaviour — is what actually lets
 * the new column claim real space and, correctly, push the table past its
 * container so .tabla-scroll's overflow-x:auto does its job. This is the
 * mechanism behind "adding optional columns may bring the sideways scroll
 * back" (.menu-columnas-aviso below): scroll, not a vanished column. */
.tabla-catalogo.columnas-extra { table-layout: auto; }
.tabla-catalogo thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); border-bottom: 1px solid var(--line); }
.tabla-catalogo th:first-child, .tabla-catalogo td:first-child { position: sticky; left: 0; z-index: 1; background: var(--card); }
.tabla-catalogo thead th:first-child { z-index: 3; background: var(--surface); }
.tabla-catalogo tbody tr:hover td { background: var(--surface); }
/* Fixed-width cells need overflow rules of their own or a long value just
 * spills past the column instead of respecting it. th/td padding is also
 * tightened here (not in the shared td/th rule above, which .fx-t and other
 * tables still use) — 18px a side was generous room this table's narrowest
 * percentage columns (7-8%) can't spare at the 1100px guarantee width. */
.tabla-catalogo th, .tabla-catalogo td { padding: 8px 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tabla-catalogo td svg { max-width: 100%; height: auto; display: block; }
/* .medio-nombre / .medio-sub — the Medio cell's two lines (spec: fold
 * Cobertura + Base into "the existing subtitle line"). .medio-nombre
 * replaces the old inline style="all:unset;...": all:unset here still resets
 * every property first, but display:block (declared after it, in the same
 * rule) is what lets a single-line ellipsis actually apply to a <button> —
 * all:unset alone leaves display at CSS's initial value (inline), which
 * text-overflow:ellipsis does not affect. white-space:nowrap is inherited
 * from .tabla-catalogo td above, not repeated here. */
.medio-nombre { all: unset; display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; cursor: pointer; color: var(--ink); font-weight: 600; text-decoration: underline; }
.medio-sub { display: block; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color: var(--mut); margin-top: 3px; }
.mini-base { font-size: 11px; font-weight: 600; color: var(--mut); text-transform: uppercase; }
/* .pista-scroll — starts hidden (the `hidden` attribute in medios.html, not
 * a display rule here) and is un-hidden by medios.html's
 * actualizarPistaScroll() only when .tabla-scroll actually has horizontal
 * overflow to scroll. A viewport-width media query looked tempting (the
 * guarantee is phrased as two widths, 1100px/900px) but the real trigger —
 * .tabla-catalogo's min-width floor above — kicks in well below either
 * number, so a width-only query would show the hint across a wide band
 * where there is nothing yet to scroll. Measuring the real thing is the
 * only way the hint and the scrollbar agree. */
.pista-scroll { margin: 0; padding: 0 18px 8px; font-size: 11.5px; color: var(--mut); }

/* .menu-columnas — column-visibility <details> menu (spec §9.2). summary
 * reuses the existing .btn look with the native marker removed; expected
 * markup below the summary is a `.menu-columnas-panel` card holding one
 * `.interruptor` row per column (the "scrollable list of switch rows"). */
.menu-columnas { position: relative; display: inline-block; }
.menu-columnas summary { display: inline-block; list-style: none; font-size: 12.5px; font-weight: 600; color: var(--accent); border: 1px solid var(--accent); border-radius: 8px; padding: 6px 11px; background: none; cursor: pointer; font-family: var(--sans); }
.menu-columnas summary::-webkit-details-marker { display: none; }
.menu-columnas summary:hover { background: var(--accent); color: #fff; }
.menu-columnas[open] summary { margin-bottom: 8px; }
.menu-columnas-panel { position: absolute; top: calc(100% + 6px); right: 0; z-index: 20; width: 240px; max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 10px; box-shadow: 0 12px 32px rgba(15,23,42,.16); }
.menu-columnas-panel .interruptor { width: 100%; justify-content: space-between; padding: 6px 4px; }
/* medios.html's construirMenuColumnas() renders this as the panel's own
 * first line (it owns the panel's whole innerHTML, so a static sibling here
 * would just get wiped on load) — the ten default columns already fit with
 * no horizontal scroll, but any of these 28 optional ones adds real width
 * table-layout:fixed has nowhere to take from, so it's the user's informed
 * choice, not a surprise regression they file as a bug. */
.menu-columnas-aviso { margin: 2px 4px 6px; font-size: 11.5px; line-height: 1.4; color: var(--mut); }

/* .filtros-filas / .fila-filtro — spec §9's row-based filter bar (medios.html
 * rebuild): six stacked rows replacing the old single flat .filtros-grid of
 * native controls. Genuinely missing from the file: §9.2's contract table
 * defines every individual control's own look (.buscador, .segmentado,
 * .chip-clase, .select, .rango, .interruptor...) but nothing arranges
 * several of them side by side into a row, or stacks successive rows —
 * .filtros-grid's auto-fit grid is a DIFFERENT, single-level layout (one
 * flat pool of fields) that the "rows, not one flat grid" instruction
 * explicitly replaces. flex-wrap (not grid) so a row degrades to stacked
 * fields on a narrow viewport without a media query, same reasoning as
 * .filtros-grid's own comment above. */
.filtros-filas { display: flex; flex-direction: column; gap: 14px; padding: 16px 18px; }
.fila-filtro { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px 18px; }
.fila-filtro > .grupo-filtro { flex: 1 1 180px; }

/* shared focus ring (spec §9.2 control rules): one declaration block reused
 * by every self-targeting control, so focus looks identical everywhere.
 * .select (:focus-within, above) and .interruptor (ring lands on a sibling,
 * above) can't share this selector list structurally, but use the exact
 * same three declarations. */
.buscador input:focus-visible,
.rango input:focus-visible,
.segmentado button:focus-visible,
.chip-clase:focus-visible,
.campo-check input[type="checkbox"]:focus-visible,
.menu-columnas summary:focus-visible,
.chip-activo .quitar:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
  border-color: var(--accent);
}
.interruptor input:focus-visible + .pista {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

@media (max-width: 720px) {
  .menu-columnas-panel { right: auto; left: 0; }
}

/* ===== Task 12 — pages/proyectos.html + pages/proyecto.html. The rest of
 * mockup.html's classes these two screens use that no earlier task had
 * ported yet (hero title/subtitle, .tabla, .tipo-pill, .vacio,
 * .visually-hidden, .medio-nombre-btn, pre.bloque-codigo), plus the
 * mockup's heading/paragraph/panel spacing — scoped to .page-body/.modal/
 * .hero-in so the catálogo and informe pages, which never use those
 * containers for it, are untouched. ===== */
h1.titulo { font: 600 28px/1.15 var(--sans); letter-spacing: -.02em; margin: 0 0 6px; }
.subtitulo { color: #b6c2d6; font-size: 14px; margin: 0 0 18px; }
.crumb a { text-decoration: underline; }
.crumb a:hover { color: #fff; }
.hero-in .subtitulo a:hover { color: #fff; }
.page-body h2, .page-body h3, .modal h2 { margin-top: 0; }
.tarjeta-proyecto h3 { margin: 0; }
.page-body .panel { margin-bottom: 20px; }
.page-body .btn, .modal .btn { margin-top: 0; }
.tarjeta-proyecto .desc { margin: 0; }
.barra-proyectos .buscador { max-width: 320px; }
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

table.tabla { min-width: 560px; }
.tabla th { padding: 10px 14px; white-space: nowrap; }
.tabla td { padding: 12px 14px; }
.tabla tr:hover td { background: var(--surface); }
.tabla tr.fila-informe { cursor: pointer; }
.tabla tr.fila-informe td a { text-decoration: none; }
.tabla tr.fila-informe:focus-visible td { background: color-mix(in srgb, var(--accent) 8%, var(--card)); }
.tabla a.url-texto { text-decoration: underline; }
.medio-nombre-btn { all: unset; display: block; cursor: pointer; color: var(--ink); font-weight: 600; text-decoration: underline; }
.medio-nombre-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.medio-nombre-txt { display: block; color: var(--ink); font-weight: 600; }

.tipo-pill { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; padding: 2px 9px; border-radius: 999px; background: var(--line); color: var(--ink-2); }
.tipo-pill.PROPUESTA { background: var(--sem-bg); color: var(--sem); }
.tipo-pill.SEGMENTO { background: var(--web-bg); color: var(--web); }
.tipo-pill.REVISION { background: var(--leo-bg); color: var(--leo); }
.tipo-pill.AUDITORIA { background: var(--warn-bg); color: var(--warn); }

.vacio { padding: 46px 24px; text-align: center; color: var(--mut); }
.vacio svg { color: var(--line-2); margin-bottom: 10px; }
.vacio p { margin: 0; font-size: 14px; }
.vacio .fuerte { color: var(--ink-2); font-weight: 600; margin-bottom: 4px; }
.vacio a { color: var(--accent); text-decoration: underline; }

pre.bloque-codigo { background: var(--navy); color: #d9e6f2; border-radius: 10px; padding: 12px 14px; font-family: var(--mono); font-size: 13px; overflow-x: auto; margin: 8px 0; }
.campo input[aria-invalid="true"], .campo textarea[aria-invalid="true"] { border-color: var(--no); }
.campo input[readonly] { background: var(--surface); color: var(--ink-2); }

/* ===== movimiento — spec §6: 180–260 ms de entrada, ~140 ms de salida,
 * cubic-bezier(.2,.8,.2,1). Todo bajo prefers-reduced-motion: no-preference;
 * el estado base (fuera de este bloque) es siempre el final, visible, sin
 * transformar — quien prefiera sin movimiento ve el mismo resultado al
 * instante. ===== */
@media (prefers-reduced-motion: no-preference) {
  .bar i, .split i { transition: width .4s ease; }

  .fx-back { opacity: 0; transition: opacity .2s cubic-bezier(.2,.8,.2,1); }
  .fx-back.fx-open { opacity: 1; }
  .fx-back.fx-closing { opacity: 0; transition-duration: .14s; }

  .fx { transform: translateX(32px); opacity: 0; transition: transform .24s cubic-bezier(.2,.8,.2,1), opacity .2s cubic-bezier(.2,.8,.2,1); }
  .fx.fx-open { transform: translateX(0); opacity: 1; }
  .fx.fx-closing { transform: translateX(32px); opacity: 0; transition-duration: .14s; }

  .grafica-keywords { transition: opacity .18s cubic-bezier(.2,.8,.2,1); }
  .grafica-tooltip { transition: opacity .12s ease; }

  /* spec §9.2 filter controls: .app-nav hover (160ms) and .interruptor's
   * knob slide (180ms) are the spec's own numbers; the rest of the toggle
   * controls match at a consistent 160ms so the bar moves as one system. */
  .app-nav-left a { transition: color .16s ease; }
  .interruptor .pista, .interruptor .pista::after { transition: background-color .18s cubic-bezier(.2,.8,.2,1), transform .18s cubic-bezier(.2,.8,.2,1); }
  .segmentado button { transition: background-color .16s ease, color .16s ease, box-shadow .16s ease; }
  .chip-clase { transition: background-color .16s ease, color .16s ease, border-color .16s ease; }
  .chip-activo .quitar { transition: background-color .16s ease; }

  /* Task 11 — mockup.html's own reduced-motion block, folded into this one
   * shared block instead of a second @media rule (this file's convention:
   * one trailing block). .app-nav-left/.chip-clase/.segmentado button are
   * deliberately left out of the combined .btn/.tarjeta-proyecto rule below
   * (unlike mockup.html) — they already have their own transition-property
   * lists two rules above; re-declaring them here under one shorthand
   * would replace those lists instead of adding to them. */
  .modal-back { opacity: 0; transition: opacity .16s ease; }
  .modal-back.abierto { opacity: 1; }
  .modal { transform: translateY(10px); opacity: 0; transition: transform .18s cubic-bezier(.2,.8,.2,1), opacity .18s ease; }
  .modal-back.abierto .modal { transform: translateY(0); opacity: 1; }
  .btn, .tarjeta-proyecto { transition: color .15s ease, background-color .15s ease, border-color .15s ease, box-shadow .15s ease; }

  .detalle-wrap { transition: grid-template-rows .24s cubic-bezier(.2,.8,.2,1); }
  .btn-exp svg { transition: transform .2s cubic-bezier(.2,.8,.2,1); }
}
