/* Fase 10, Plan 1, Tarea 6: escalas y alias de PBergy como custom properties -- ver
   tokens.css para el origen y las conversiones oklch()->hex. */
@import url("/static/tokens.css");

/* Shared shell styling for both production screens (item 3, piece C). Ported from the
   validated mockup's shared.css (Claude Design c4b2d1ae-21ed-4f51-b826-d25a709122bc):
   copied verbatim except the two `body[data-scheme]` blocks and the Scheme A/B variable
   block on `:root`, which are removed here -- the eleven scheme tokens (--bg, --panel,
   --panel2, --border, --neutral, --alert, --warn, --text, --accent, --header,
   --badge-bg) are set at runtime by ui.js's applyScheme() from GET /api/esquemas
   instead of being baked into two fixed `data-scheme` blocks. The PBergy brand tokens
   are scheme-invariant and stay here on :root. */
:root {
  /* PBergy brand (from .claude/skills/pbergy-design/tokens/colors.css) */
  --pb-blue: #003E7E;
  --pb-blue-dark: #0A2D6B;
  --pb-turquoise: #00B8A9;
  --pb-turquoise-light: #39D2C7;
  --pb-gray-light: #D7DDE4;
  --sub-box-border: #8a94a0;
  --graph-line: #5a6472;
}

html, body { height: 100%; }

body { margin: 0; background: var(--bg); font-family: system-ui, sans-serif; color: var(--text); overflow: hidden; }
/* Hallazgo 9, revisión manual 2026-08-23: `body` sets a family, but native form
   controls do not inherit family OR size from an ancestor -- the browser's own
   stylesheet wins instead. Measured live: dropdowns/inputs/buttons rendered in
   Arial 13.33px (1.67x the surrounding 8px `.row`/`.wb` text) while a `<textarea>`
   rendered in `monospace` at 8px -- a THIRD family on the same screen. Brought onto
   the page's own scale (system-ui, 8px, matching `.row`/`.win .wb` -- the dominant
   size everywhere else in this compact layout) rather than inventing a new size;
   any element that already sets its own `font-size` inline (several selects/inputs
   already do, e.g. the Eventos panel's fields) keeps that value untouched -- inline
   `style` always outranks this tag selector. */
select, input, textarea, button { font-family: inherit; font-size: 8px; }

.topbar {
  height: 34px; display: flex; align-items: center; justify-content: space-between;
  padding: 0 14px; font-size: 11px; color: #fff; font-weight: 600;
  background: linear-gradient(135deg, var(--pb-blue) 0%, var(--pb-blue-dark) 55%, var(--pb-turquoise) 100%);
}
.transport { display: flex; align-items: center; gap: 6px; }
.menuchip { font-size: 9px; background: rgba(255,255,255,.15); padding: 3px 8px; border-radius: 10px; cursor: pointer; }
.tbtn { width: 20px; height: 20px; border-radius: 4px; background: rgba(255,255,255,.18); display: flex; align-items: center; justify-content: center; font-size: 9px; cursor: pointer; }
/* `.chip` (Fase 8, Plan 5, Tarea 4): mismo look que `.speed`/`.clock` -- una etiqueta
   corta en la topbar, no un control -- reusado para el identificador de rol operativo
   del entrenado (`#rol-operativo`). Se queda vacío/`display` normal cuando no hay
   texto que mostrar (coordinador sin restricción o modo `off`); JS decide su
   contenido, no su visibilidad, así que un span vacío no ocupa hueco visible. */
.speed, .clock, .chip { font-size: 9px; background: rgba(255,255,255,.15); padding: 3px 8px; border-radius: 10px; }
.chip:empty { display: none; }
/* Hallazgo 6, revisión manual 2026-08-23: un botón de transporte que el estado
   actual de la sesión no admite se atenúa -- sigue siendo clicable a propósito (ver
   el comentario de consola.js's `_rechazoSesion`), la opacidad es solo la primera
   pista, el toast del 409 real es la que explica por qué. `.btn-atencion` resalta
   ↻ Reiniciar escenario cuando 'detenida' lo deja como el único camino de vuelta. */
.tbtn.disabled { opacity: .35; }
.btn-atencion { outline: 2px solid var(--warn); outline-offset: 1px; }

.canvas { position: fixed; top: 34px; bottom: 0; left: 0; right: 26px; }
/* Product owner's manual review, 2026-08-23 (fix 1 of the canvas-fill pass): the
   previous 52% band (manual review, finding 3, same date) still only let the mimic use
   22% of the canvas's WIDTH at 88% of its height (measured live, 1850x900, WSCC-9:
   mimic-wrap 1804x450, fitted drawing 400x382). The owner's ruling: stop reserving a
   band for the mimic at all -- the floating panels below are already draggable
   (`.win`'s `.wh` header, ui.js's makeDraggable) and meant to sit OVER the network, the
   same way the validated mockup did, so give the mimic the FULL canvas and let the
   panels overlap it by default. That roughly doubles the fitted scale (866 vs 436px of
   vertical room), which is the binding axis for this near-square drill-down layout (see
   mimic_render.py's viewBox-cropping comment) -- width was never the constraint, height
   was. left/top/width/height keep a small 4-10px margin so the drawing's own edges
   don't touch the canvas edge; espejo.html's own 94% override is gone too -- it never
   had a panel grid to make room for in the first place, so the shared default now
   gives it exactly what its override used to (see espejo.html's own comment). */
.mimic-wrap { position: absolute; left: 10px; top: 4px; width: calc(100% - 20px); height: calc(100% - 8px); }

.rail {
  position: fixed; top: 34px; right: 0; bottom: 0; width: 26px; background: var(--panel2);
  border-left: 1px solid var(--border); display: flex; flex-direction: column;
  align-items: center; gap: 7px; padding-top: 8px; z-index: 20;
}
/* Hallazgo 7, revisión manual 2026-08-23: eran 14 cuadritos vacíos idénticos de
   15x15, sin ningún indicio de qué panel escondía cada uno salvo el `title` del
   navegador (que ni siquiera aparecía sin pasar el mouse). Fallo del dueño del
   producto: un glifo corto SIEMPRE visible + el nombre completo al pasar el mouse.
   Ensanchado a 20px para que quepan las 2-3 letras de los glifos de texto
   (Hz/MW/SOE) sin recortarse -- ver consola.html/entrenado.html por el inventario
   completo de glifos, uno por panel. */
.railicon { width: 20px; height: 16px; border-radius: 3px; background: var(--panel); border: 1px solid var(--accent); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 9px; color: var(--text); overflow: hidden; white-space: nowrap; }
.railicon.off { border-color: var(--border); }
.railicon.hi { border-color: var(--warn); box-shadow: 0 0 0 2px rgba(240,180,41,.25); }

.win {
  position: absolute; border-radius: 6px; box-shadow: 0 8px 20px rgba(0,0,0,.4);
  overflow: hidden; background: var(--panel); border: 1px solid var(--border); min-width: 160px;
}
.win .wh { height: 20px; background: var(--header); color: #fff; font-size: 9px; display: flex; align-items: center; justify-content: space-between; padding: 0 8px; font-weight: 600; cursor: move; user-select: none; }
.win .wb { padding: 6px 8px; }

.mimic-stage { position: relative; width: 100%; height: 100%; }
.breadcrumb { font-size: 9px; color: var(--accent); font-weight: 600; padding: 0 0 2px; }
/* Manual review 2026-08-23, finding 3: pan/zoom (ui.js's wirePanZoom). The cursor is
   the only visual affordance that the mimic is draggable -- there is no other chrome
   change, on purpose, so the drawing itself stays the focus. */
.mimic-holder svg { cursor: grab; touch-action: none; }
.mimic-holder svg:active { cursor: grabbing; }
/* Sibling of .breadcrumb, not a child of it -- cargarMimico() (ui.js) overwrites
   .breadcrumb's textContent wholesale on every navigation, which would delete a
   button placed inside it (same reason .cuerpoSesion keeps btnReset OUTSIDE of
   itself, see consola.html's own comment on that button). */
.zoom-reset { position: absolute; right: 2px; top: 0; width: 16px; height: 14px;
  border-radius: 3px; background: var(--panel2); border: 1px solid var(--border);
  color: var(--text); font-size: 9px; line-height: 12px; text-align: center;
  cursor: pointer; z-index: 10; }
/* Task 7: holds the <svg class="mimic-stage"> GET /api/mimico returns -- the tag itself
   only had inline sizing (viewBox + preserveAspectRatio) before it existed server-side,
   so this reserves the space .mimic-wrap's fixed height minus the breadcrumb line
   leaves, the same way .win .wb already reserves space under .win .wh. */
.mimic-holder { width: 100%; height: calc(100% - 14px); }
/* .bus/.eq-normal/.eq-warn/.eq-alert used to be declared here AND again, differently,
   further down under the "Voltage-class colour" comment (final review of item 3, piece
   C, Minor: found duplicated, not cosmetic -- the duplicate CSS rule beat the
   `fill="none"` presentation attribute on the IEC transformer symbol regardless of
   source order, rendering it as two solid overlapping discs). Consolidated to the one
   definition below, which also carries --kv. */
.eq-selected { stroke: var(--accent) !important; stroke-width: 2 !important; }
.eq-card { display: none; }
/* Hallazgo 4, revisión manual 2026-08-23: era la MISMA clase de defecto que el
   Crítico que la revisión propia del mockup ya había encontrado y cerrado una vez
   (texto casi invisible sobre el esquema claro por un color de marca FIJO usado
   donde hacía falta uno consciente del esquema) -- reintroducida aquí, en el único
   elemento que las dos revisiones de esquema nunca habían tocado. Repintada con
   tokens de esquema: --panel2 (la MISMA superficie "elevada" que .dropdown/.win ya
   usan) en vez de --pb-blue, --text/--accent en vez de #fff/--pb-turquoise-light.
   Medido en los dos esquemas que se distribuyen (ver el reporte de esta revisión):
   text/panel2 6.26:1 (pbergy) y 5.35:1 (cliente_demo); accent/panel2 7.41:1 (pbergy)
   y 4.08:1 (cliente_demo) -- los dos muy por encima del piso gráfico de 3:1, y
   `schemes.py`'s propio gate ahora comprueba esa misma pareja para cualquier
   esquema futuro (ver su comentario). */
.eq-card-bg { fill: var(--panel2); stroke: var(--accent); stroke-width: 0.8; }
/* Manual review 2026-08-23 (product owner): every label class below used to be a
   FIXED viewBox-unit constant (5px/5.5px/6px), but each drill-down tier derives its
   own viewBox from its own content bbox (mimic_render._viewbox), so the on-screen px
   those constants render at swings 2.5x between tiers -- 5 viewBox-units read as ~5px
   at the system tier's ~0.99 screen-px-per-unit scale and ~12.3px at a substation's
   ~2.45. Measured live at 1850x900 before this fix: eqlabel 5px/5px/12.3px across
   sistema/area/subestacion. ui.js's `_actualizarEscalaTexto` (wirePanZoom section)
   now sets these as CSS custom properties on the <svg> itself, computed as
   target_px / (screen-px-per-viewBox-unit) every time the viewBox's SCALE changes
   (fit, zoom, tier change) -- so a label lands at the same target screen size
   regardless of tier or network span, chosen over "labels grow with zoom" because
   this is a SCADA mimic (a dispatcher needs a name legible at any zoom level, not
   a map where zooming in is how you read fine print). The literal fallback after
   each var(...) is what paints for the one frame before JS runs (and for any static
   read of this file, e.g. a browser with JS disabled) -- kept at the OLD constant so
   that fallback is at least as legible as what shipped before, never blank. */
.eq-card-txt { fill: var(--text); font-size: var(--fs-cardtxt, 5px); }
.eq-card-txt.head { fill: var(--accent); font-weight: 700; font-size: var(--fs-cardhead, 5.5px); }
.v-badge-bg { fill: var(--badge-bg); }
.v-badge-txt { fill: #fff; font-size: var(--fs-badge, 5.5px); font-weight: 700; }
.v-badge-txt.warn { fill: var(--warn); }
/* Task 7 review, Finding 2: mimic_render.py only paints a .v-badge-bg rect behind
   .v-badge-txt at the system/area tier's .sub-box (_caja()) -- the substation tier's
   bar voltage text ([data-barra] .v-badge-txt, _render_subestacion) has no backing
   rect and sits directly on the canvas, i.e. var(--bg), which is scheme-controlled and
   can be light (cliente_demo's is a pale grey). Measured live: white text on
   cliente_demo's canvas contrasts at 1.40:1, far under WCAG's 3:1 floor for a graphical
   object. schemes.py's own WCAG gate (validar()) never had a chance to catch this: it
   only checks the fixed PAIRS it lists (_PARES_TEXTO/_PARES_GRAFICO), and --bg paired
   against a hard-coded #fff in ui.css is not one of them -- a scheme file cannot
   declare an unsafe --bg/#fff combination that gate would reject, because the gate has
   no way to know a hard-coded #fff exists 3 layers away in a stylesheet. (Related, fixed
   in the final review of item 3 piece C: --badge-bg was in the same boat -- a declared
   token appearing in neither pair list, so the system/area badge's #fff-on-badge_bg
   pairing just below had never actually been validated, working only because both
   shipped schemes happened to choose a dark badge_bg. `schemes.validar()` now checks
   #fff against `badge_bg`/`header` directly, at the same 3:1 graphical threshold.)
   [data-barra] only wraps substation-tier bar groups
   (never a .sub-box), so this scopes the fix to exactly the unsafe case, using
   --neutral -- the token schemes.py's WCAG gate already guarantees >=3:1 against --bg
   for every scheme that loads at all (measured: 9.70:1 for cliente_demo). Left the
   system/area badge's #fff untouched: --neutral measures only 1.62:1 against
   cliente_demo's --badge-bg, so swapping it there instead would have broken a case
   that already worked. */
[data-barra] .v-badge-txt { fill: var(--neutral); }
.sub-label { fill: var(--neutral); font-size: var(--fs-sublabel, 6px); font-weight: 600; }
.eqlabel { fill: var(--text); font-size: var(--fs-eqlabel, 5px); }
.selchip-bg { fill: var(--panel2); stroke: var(--border); stroke-width: 0.8; }
.selchip-txt { fill: var(--text); font-size: 4.5px; }

.row { display: flex; justify-content: space-between; font-size: 8px; color: var(--text); padding: 2px 0; }
.row b { color: var(--neutral); font-weight: 600; }
.row .good { color: var(--accent); }
.row .warn { color: var(--warn); }
/* Fase 8, Plan 2, Tarea 5: aviso de una sola línea, informativo -- NO una alarma --
   para "persistencia de sesiones apagada". `.row` es un par etiqueta:valor de dos
   columnas; este aviso es una sola oración, así que no pasa por `fila()`. Dimmed the
   same way `.tbtn.disabled`/`.eq-abierto` already de-emphasise something that is
   expected, not wrong. */
.row-nota { font-size: 8px; color: var(--text); opacity: .7; padding: 2px 0; }
/* Fase 9, Plan 6, Tarea 2c: una fila de la biblioteca cuyo escenario corre sobre una
   red sin modelo CIM dinámico. NO se oculta ni se deshabilita: el escenario es válido
   para flujo de potencia y conservarlo es la decisión honesta; lo que hace falta es que
   se VEA distinto antes del clic, porque `POST /api/escenario` lo va a rechazar. Mismo
   atenuado que `.fuera-aor` usa en el mímico para lo que la jurisdicción no cubre --
   ningún color, sólo opacidad, así que no depende de ningún esquema. */
.sin-sesion { opacity: .45; }
.miniChart { height: 26px; background: var(--panel2); border-radius: 4px; margin-top: 3px; }
.miniChart svg { width: 100%; height: 100%; }
.scenlist { font-size: 8px; color: var(--text); }
.scenlist .sel { color: var(--accent); font-weight: 700; }
/* Task 9: fixed under the topbar, same as .stale-banner -- unlike the mockup (a
   single fixed-network demo where the banner was an occasional overlay), the trainee
   screen's alarm banner carries the whole Phase 7 Plan 4 workflow (ack/delete/
   inhibit/suppression buttons) and is always present, so it needs a real slot in the
   layout rather than floating over the canvas. entrenado.html gives .canvas and
   .stale-banner an inline `top` that adds this height on top of the topbar's 34px;
   consola.html never renders #alarmBar at all, so its own (unset) canvas/stale-banner
   top is untouched. */
.alarmbar { position: fixed; top: 34px; left: 0; right: 0; height: 24px; z-index: 140;
  background: #4a1620; color: #ffb4c0; font-size: 9px; display: none; align-items: center;
  flex-wrap: wrap; padding: 2px 12px; gap: 6px; border-bottom: 1px solid #6a1f2e; }
.alarmbar .adot { width: 6px; height: 6px; border-radius: 50%; background: #ff4d6d; flex: none; }
.alarmbar button { font-size: 8px; }
/* Ola final de arreglos (Fase 10, Plan 6): mismo atenuado .45 que `.fuera-aor`
   (mímico) y `.sin-sesion` (vestíbulo) ya usan para "esto se va a rechazar" --
   sobre los siete controles de escritura de este panel cuando `pintarRolOperativo`
   (entrenado.js) los deshabilita para un `observador`. Rechazado-antes-del-clic,
   no un tercer lenguaje visual: solo opacidad, sin color, así que no depende de
   ningún esquema. */
.alarmbar button:disabled { opacity: .45; cursor: not-allowed; }
.toast { position: fixed; top: 44px; right: 34px; background: var(--pb-turquoise); color: #06231f; font-size: 10px; font-weight: 700; padding: 6px 10px; border-radius: 6px; z-index: 200; box-shadow: 0 6px 16px rgba(0,0,0,.3); }

.dropdown { position: absolute; width: 170px; background: var(--panel); border: 1px solid var(--accent); border-radius: 6px; box-shadow: 0 8px 20px rgba(0,0,0,.4); z-index: 30; overflow: hidden; display: none; }
.dropdown .dd-head { font-size: 8px; color: var(--accent); font-weight: 700; padding: 6px 8px 2px; text-transform: uppercase; letter-spacing: .03em; }
.dropdown .dd-item { font-size: 9px; color: var(--neutral); padding: 5px 10px; border-top: 1px solid var(--panel2); cursor: pointer; }
.dropdown .dd-item.sel { color: var(--accent); font-weight: 700; }

.ctxmenu { position: absolute; width: 160px; background: var(--panel); border: 1px solid var(--warn); border-radius: 6px; box-shadow: 0 8px 20px rgba(0,0,0,.4); z-index: 30; overflow: hidden; display: none; }
.ctxmenu .cx-title { font-size: 8px; color: var(--neutral); font-weight: 700; padding: 6px 8px; background: var(--panel2); }
.ctxmenu .cx-cat { font-size: 7.5px; color: var(--warn); font-weight: 700; padding: 5px 8px 1px; text-transform: uppercase; letter-spacing: .03em; }
.ctxmenu .cx-item { font-size: 8.5px; color: var(--neutral); padding: 4px 10px; cursor: pointer; }
.ctxmenu .cx-item:hover { background: var(--panel2); }

/* Voltage-class colour (paletas.py, Phase 7 Plan 3) rides on the bus group as an
   inherited custom property, so equipment in a normal state paints the SAME tone as
   the bar it hangs off -- the mockup's "neutral except for the exception" rule -- while
   still being voltage-class coloured. --warn/--alert override it for what needs
   attention. See the plan's finding 3. */
.bus { stroke: var(--kv, var(--neutral)); stroke-width: 2.4; }
.eq-normal { fill: var(--kv, var(--neutral)); stroke: var(--kv, var(--neutral)); }

/* Outline-only glyphs (the IEC transformer symbol, `_simbolo_transformador`) must not
   inherit .eq-normal's own fill: a class rule always beats an SVG presentation
   attribute (`fill="none"` on the element itself), regardless of source order, so the
   override has to be an explicit rule of its own. */
.eq-outline { fill: none; }

/* Load heat-map indicator (Important 1, same review): `gen_unifilar.py` had this
   (`_rangos_carga_linea`/`indicador_color`) and item 3 piece C's rewrite dropped it
   without a replacement, so an overloaded line looked identical to an idle one. Idle
   grey by default; `patchValores` (ui.js) adds .eq-warn/.eq-alert once the flow
   crosses 70%/100% of the line's declared thermal limit (`mimic_render._limite_linea`,
   absent -- and so this indicator absent too -- for a line with no declared limit).
   Declared BEFORE .eq-warn/.eq-alert on purpose (same trap as --kv above, caught live:
   an alert-state indicator painted var(--panel2)/var(--border) -- its OWN idle colour --
   instead of red the first time this was tried, because `.carga-ind` sat AFTER
   `.eq-warn`/`.eq-alert` and won the same-specificity tie). */
.carga-ind { fill: var(--panel2); stroke: var(--border); stroke-width: 0.6; }

/* Alarm/heat-map colours OVERRIDE the voltage-class colour above and the heat-map
   indicator's own idle colour -- same specificity (one class) as both, so they must
   come AFTER .bus/.eq-normal/.carga-ind in the cascade or those would win instead
   (final review of item 3, piece C: found declared in the wrong order relative to
   .bus/.eq-normal, which is why `.eq-warn`/`.eq-alert` had never actually applied to
   anything before this fix -- see ui.js's patchValores, the first real consumer). */
.eq-warn { fill: var(--bg); stroke: var(--warn); stroke-width: 1.6; }
.eq-alert { fill: var(--bg); stroke: var(--alert); stroke-width: 1.6; }

/* Breaker/tie-line position (Important 2, same review): WSCC-9/IEEE-14 have no
   detailed-substation tier, so the tie-line IS the switchgear at every level they have
   -- an open element needs its own visual, distinct from the voltage-class/heat-map
   colour it already carries. Dashed + dimmed is the common SCADA convention for a
   de-energised/open element; applies equally to a bay/disconnector <g> (inherited by
   its glyph children) and to a tie-line's own <line>. */
.eq-abierto { stroke-dasharray: 4,3; opacity: .6; }

/* Jurisdiction visible BEFORE the click (Phase 8, Plan 5, Task 4): an operable
   terminal/bay/disconnector this caller's assignment does not cover
   (`mimic_render._atributos_maniobra`) still DRAWS -- the equipment is real and the
   dispatcher should see it exists -- but reads as clearly out of reach: dimmed, and
   its own glyph dashed the same visual language `.eq-abierto` already uses for "not
   in its normal state", so a restricted operator never has to click to learn a
   maneuver is not theirs (the server's own 403, Task 3, is the enforcement; this is
   only the advance notice). The element carries no `data-operable`/`data-cmd` at all
   in this state, so it is not clickable regardless of styling. */
.fuera-aor { opacity: .45; }
.fuera-aor .eq-normal { stroke-dasharray: 2 2; }

/* Staleness (Phase 1 requirement): a screen that lost the engine dims and says so
   rather than showing an old value as current. */
body.desactualizado .canvas { opacity: .45; filter: grayscale(.6); }
body.desactualizado .stale-banner { display: flex; }
.stale-banner { display: none; position: fixed; top: 34px; left: 0; right: 26px;
  height: 18px; align-items: center; justify-content: center; gap: 8px;
  background: var(--warn); color: #1b1305; font-size: 10px; font-weight: 700;
  z-index: 150; }

/* -- Componentes base del sistema de diseño (Fase 10, Plan 1, Tarea 6) -----------
   Reimplementados a mano: el bundle del sistema de diseño es React y esta app no
   tiene paso de build. Cubren los seis que las plantillas usan. */
.pb-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
        padding:10px 18px;border:0;border-radius:var(--radius-md);cursor:pointer;
        font:inherit;font-weight:800;font-size:13px;line-height:1}
.pb-btn:disabled{opacity:.5;cursor:not-allowed}
.pb-btn--primary{background:var(--blue-700);color:var(--text-on-brand)}
.pb-btn--primary:hover:not(:disabled){background:var(--blue-800)}
.pb-btn--secondary{background:transparent;color:var(--blue-700);
                   box-shadow:inset 0 0 0 1px var(--border-default)}
.pb-btn--secondary:hover:not(:disabled){background:var(--surface-subtle)}
.pb-btn--danger{background:var(--danger-500);color:#fff}
.pb-btn--block{width:100%}

.pb-field{display:flex;flex-direction:column;gap:var(--space-1)}
.pb-field > label{color:var(--text-secondary);font-size:12px;font-weight:700}
.pb-field > input,.pb-field > select{padding:10px 12px;border:1px solid var(--border-default);
        border-radius:var(--radius-md);font:inherit;font-size:15px;
        background:var(--surface-page);color:var(--text-primary)}
.pb-field > input:focus,.pb-field > select:focus{outline:2px solid var(--border-focus);
        border-color:transparent}

.pb-badge{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;
          border-radius:20px;font-size:11.5px;font-weight:700;white-space:nowrap}
.pb-badge--neutral{background:var(--slate-100);color:var(--slate-700)}
.pb-badge--brand{background:var(--blue-100);color:var(--blue-700)}
.pb-badge--success{background:var(--success-100);color:var(--success-500)}
.pb-badge--warning{background:var(--warning-100);color:var(--warning-500)}
.pb-badge--danger{background:var(--danger-100);color:var(--danger-500)}

.pb-tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--border-default)}
.pb-tabs button{padding:11px 18px;border:0;background:transparent;cursor:pointer;
        font:inherit;font-weight:700;font-size:13px;color:var(--text-secondary);
        border-bottom:3px solid transparent;margin-bottom:-1px}
.pb-tabs button[aria-selected="true"]{color:var(--blue-700);
        border-bottom-color:var(--blue-700)}

/* Fase 10, Plan 5, Tarea 3: tarjeta/grilla genéricas para los paneles que las
   Tareas 4-6 llenan dentro de la consola de administración (`admin.html`) --
   mismo nombramiento que el resto del sistema de diseño (`.pb-btn`/`.pb-field`/
   `.pb-badge`/`.pb-tabs` arriba), reimplementado a mano por la misma razón: el
   bundle de componentes del sistema de diseño es React y esta app no tiene paso
   de build. */
.pb-card{background:var(--surface-card);border-radius:var(--radius-lg);
        padding:var(--space-6);box-shadow:var(--shadow-md)}
.pb-card h2{color:var(--blue-700);font-size:15px;font-weight:800;
        margin-bottom:var(--space-4)}
.pb-card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
        gap:var(--space-5)}

/* Fase 10, Plan 5, Tarea 4: pestaña Usuarios -- filtros, la lista de cuentas y el
   formulario de alta. Filas armadas como divs, no una tabla: cada fila ya es
   autodescriptiva (insignias con su propio texto), y evita tener que mantener
   encabezados de columna sincronizados con lo que cada insignia ya dice. */
.pbu-filtros{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-3);
        margin-bottom:var(--space-4)}
.pbu-filtros .pb-field{min-width:160px}
.pbu-lista{display:flex;flex-direction:column;gap:var(--space-3)}
.pbu-vacio{color:var(--text-muted);font-size:13px;padding:var(--space-4) 0;
        text-align:center}
.pbu-fila{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3);
        padding:var(--space-3) var(--space-4);border:1px solid var(--border-default);
        border-radius:var(--radius-md);background:var(--surface-page)}
.pbu-fila-principal{display:flex;flex-direction:column;gap:2px;flex:1 1 200px;
        min-width:180px}
.pbu-email{font-weight:700;color:var(--text-primary);font-size:14px;
        word-break:break-all}
.pbu-empresa{font-size:12px;color:var(--text-secondary)}
.pbu-roles{display:flex;flex-wrap:wrap;gap:4px}
.pbu-acciones{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-left:auto}
.pbu-form{display:flex;flex-direction:column;gap:var(--space-4);
        margin-top:var(--space-4);padding-top:var(--space-4);
        border-top:1px solid var(--border-default)}
/* `[hidden]` y una regla de clase con la misma especificidad (0,1,0) resuelven por
   ORIGEN, no por orden en el archivo -- el origen "autor" siempre le gana al
   estilo de referencia del navegador, así que `.pbu-form{display:flex}` por sí
   sola habría dejado el formulario visible aun con el atributo `hidden` puesto.
   `.panel[hidden]` (arriba, en el `<style>` de admin.html) ya resuelve el mismo
   caso para las pestañas; estas dos repiten la misma corrección para sus propios
   `display` explícitos. */
.pbu-form[hidden]{display:none}
.pbu-form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
        gap:var(--space-3)}
.pbu-roles-elegir{display:flex;flex-wrap:wrap;gap:var(--space-3)}
.pbu-roles-elegir label{display:flex;align-items:center;gap:6px;font-size:13px;
        color:var(--text-primary)}
.pbu-roles-elegir label.pbu-deshabilitada{opacity:.5}
.pbu-ayuda{font-size:11.5px;color:var(--text-muted)}
.pbu-clave-temporal{display:flex;flex-direction:column;gap:var(--space-2);
        background:var(--warning-100);border-radius:var(--radius-md);
        padding:var(--space-3) var(--space-4);font-size:13px;color:var(--text-primary)}
.pbu-clave-temporal[hidden]{display:none}
.pbu-clave-temporal code{font-size:15px;font-weight:700;word-break:break-all;
        user-select:all;background:var(--surface-page);padding:4px 8px;
        border-radius:var(--radius-sm);align-self:flex-start}

/* Fase 10, Plan 5, Tarea 5: pestaña Salas -- una tarjeta por sala dentro de
   `.pb-card-grid` (ya definida en la Tarea 3). Cada tarjeta es su propia
   `.pb-card` anidada, más liviana que la de nivel superior. */
.pbs-barra{display:flex;justify-content:flex-end;margin-bottom:var(--space-4)}
.pbs-vacio{color:var(--text-muted);font-size:13px;padding:var(--space-4) 0;
        text-align:center}
.pbs-card{background:var(--surface-page);border:1px solid var(--border-default);
        border-radius:var(--radius-md);padding:var(--space-4);
        display:flex;flex-direction:column;gap:var(--space-2)}
.pbs-card-header{display:flex;align-items:center;justify-content:space-between;
        gap:var(--space-2)}
.pbs-card-header h3{font-size:14px;font-weight:800;color:var(--text-primary);
        word-break:break-word}
.pbs-badges{display:flex;flex-wrap:wrap;gap:6px}
.pbs-discrepancia{font-size:11.5px;color:var(--danger-500);font-weight:700}
.pbs-tiempo{font-size:12px;color:var(--text-secondary)}
.pbs-miembros{display:flex;flex-direction:column;gap:6px}
.pbs-miembros-titulo{font-size:11.5px;font-weight:800;color:var(--text-secondary);
        text-transform:uppercase;letter-spacing:.03em}
.pbs-miembro{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:12px}
.pbs-miembro-email{flex:1 1 120px;color:var(--text-primary);word-break:break-all}
.pbs-miembro-areas{font-size:11px;color:var(--text-muted)}
/* `.pbs-miembro-nuevo` reusa los controles nativos de `.pb-field` pero en línea
   (una fila compacta), en vez del layout apilado que `.pb-field` usa en el
   formulario de alta -- por eso sus `input`/`select` se estilan a mano en vez
   de envolverse en `.pb-field`. */
.pbs-miembro-nuevo{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--space-1)}
.pbs-miembro-nuevo input,.pbs-miembro-nuevo select{font:inherit;font-size:12px;
        padding:6px 8px;border:1px solid var(--border-default);
        border-radius:var(--radius-sm);background:var(--surface-page);
        color:var(--text-primary)}
.pbs-miembro-nuevo input[type=email]{flex:1 1 140px;min-width:120px}
.pbs-acciones{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-1)}
/* Importante 3 (wave final): el motivo VISIBLE de un control deshabilitado --
   `title` sobre un botón `disabled` es un tooltip nativo que Chrome/Safari
   suprimen (nunca en touch), así que la regla de "lo que se rechaza se ve
   antes del clic" necesita un nodo de texto, no solo un atributo. `flex:1 1
   100%` fuerza su propia línea dentro de cualquier contenedor flex con
   `flex-wrap:wrap` (`.pbs-acciones` arriba, `.pbe-paginacion` abajo);
   `display:block` hace lo mismo fuera de un contenedor flex. */
.pb-motivo{display:block;flex:1 1 100%;font-size:11.5px;color:var(--text-muted);
        margin-top:2px}
.pb-motivo[hidden]{display:none}

/* Fase 10, Plan 5, Tarea 6: pestaña Estadísticas y auditoría -- KPI, historial de
   sesiones y auditoría. Filas armadas como divs (mismo argumento que
   `.pbu-fila` arriba: cada celda ya es autodescriptiva y no hace falta un
   `<table>` para mantener columnas alineadas -- CSS grid en su lugar), con una
   fila de cabecera propia (`.pbe-fila-cab`) en vez de un `<thead>`. */
.pbe-aviso{font-size:12.5px;color:var(--text-muted);padding:var(--space-2) 0;margin:0}
.pbe-aviso--alerta{color:var(--danger-500);font-weight:700}
.pbe-aviso[hidden]{display:none}
.pbe-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
        gap:var(--space-4);margin-top:var(--space-3)}
.pbe-kpi{background:var(--surface-page);border:1px solid var(--border-default);
        border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);
        display:flex;flex-direction:column;gap:2px}
.pbe-kpi-valor{font-size:22px;font-weight:800;color:var(--blue-700)}
.pbe-kpi-valor--alerta{color:var(--danger-500)}
.pbe-kpi-etiqueta{font-size:11.5px;color:var(--text-secondary);font-weight:700}
.pbe-tabla{display:flex;flex-direction:column;gap:6px;margin-top:var(--space-3)}
.pbe-fila-cab,.pbe-fila{display:grid;gap:var(--space-2);align-items:center;
        padding:var(--space-2) var(--space-3);font-size:12px}
.pbe-fila{background:var(--surface-page);border:1px solid var(--border-default);
        border-radius:var(--radius-sm)}
.pbe-fila span{overflow-wrap:anywhere}
.pbe-fila-cab{color:var(--text-secondary);font-weight:800;font-size:11px;
        text-transform:uppercase;letter-spacing:.02em}
.pbe-sesiones .pbe-fila-cab,.pbe-sesiones .pbe-fila{
        grid-template-columns:1.3fr 1fr 1fr .8fr 1fr .8fr .6fr}
.pbe-auditoria .pbe-fila-cab,.pbe-auditoria .pbe-fila{
        grid-template-columns:1.3fr 1.2fr .7fr 1.6fr .9fr .8fr 1fr .8fr}
.pbe-vacio{color:var(--text-muted);font-size:13px;padding:var(--space-4) 0;
        text-align:center}
.pbe-paginacion{display:flex;flex-wrap:wrap;justify-content:center;
        margin-top:var(--space-3)}

/* Fase 10, Plan 6, Tarea 3: el vestíbulo del instructor -- los tres selectores en
   cascada (red -> caso -> escenario) más el detalle del escenario elegido. La
   tarjeta de roster reusa `.pbs-miembro*`/`.pb-motivo` tal cual (ya definidas
   arriba para la pestaña Salas de admin.html): la forma de una fila -- correo,
   selector de rol, campo de áreas, botones -- es la misma en las dos pantallas.
   Solo los chips de selección (sin equivalente reusable: `.vercomo-opcion` de
   admin.html vive en su propio <style>, no en este archivo compartido) son
   nuevos aquí. */
.pbv-seccion{margin-bottom:var(--space-5)}
.pbv-seccion h3{font-size:12px;font-weight:800;color:var(--text-secondary);
        text-transform:uppercase;letter-spacing:.03em;margin-bottom:var(--space-2)}
.pbv-chips{display:flex;flex-wrap:wrap;gap:var(--space-2)}
.pbv-chip{padding:8px 14px;border-radius:20px;border:1px solid var(--border-default);
        background:var(--surface-page);color:var(--text-primary);cursor:pointer;
        font:inherit;font-size:12.5px;font-weight:700}
.pbv-chip:hover:not(:disabled){border-color:var(--blue-700);background:var(--surface-subtle)}
.pbv-chip.activo{border-color:var(--blue-700);color:var(--blue-700);background:var(--blue-100)}
.pbv-chip:disabled{opacity:.5;cursor:not-allowed}
.pbv-vacio{color:var(--text-muted);font-size:13px}
.pbv-detalle{display:none;flex-direction:column;gap:var(--space-3);margin-top:var(--space-4);
        padding-top:var(--space-4);border-top:1px solid var(--border-default)}
.pbv-detalle.activo{display:flex}
.pbv-campo-etiqueta{font-size:11.5px;font-weight:800;color:var(--text-secondary);
        text-transform:uppercase;letter-spacing:.03em}
.pbv-campo-valor{font-size:13.5px;color:var(--text-primary);margin-top:2px}
