/* webapp/static/app.css
 * sdd/ui-professional-redesign — component tokens + base element rules +
 * app shell classes (design.md A3, A6). References the semantic layer from
 * tokens.css ONLY — no raw hex anywhere in this file.
 *
 * Motion policy (frontend-redesign-v2 WU1 — supersedes the v1 "A14" hard
 * no-motion rule, which was documented here and in per-page template
 * comments): micro-interactions ONLY. Allowed = 150-250ms ease on buttons,
 * inputs, selects, links, hovers, plus the skeleton->content crossfade,
 * chart draw-in, calendar day states, and the side-panel slide (added in
 * later WUs). NOT allowed = scroll-triggered animation, page-load
 * choreography, parallax, decorative motion, hover-transform that shifts
 * layout. Durations/easing come from tokens.css (--motion-* / --ease-*).
 * `prefers-reduced-motion: reduce` collapses every transition to instant —
 * see the @media block at the end of this file; that path is mandatory for
 * anything that animates. `.cargando` / `.skeleton` remain static fills.
 */

:root {
  /* --- Component tokens --- */
  --nav-h: 56px;
  --card-pad: var(--sp-4);
  --btn-bg: var(--color-primary);
  --btn-bg-hover: var(--color-primary-dark);
  --btn-fg: var(--color-primary-contrast);
  --focus-ring: 2px solid var(--color-focus);
}

/* --- Self-hosted Lexend (frontend-redesign-v2 WU1) ------------------------
 * Variable weight axis, vendored .woff2 under static/fonts/ — no CDN, no
 * build step, immune to any CSP. Two subsets: Latin covers Spanish; Latin
 * Extended covers the odd extended glyph + extra currency/punctuation.
 * `font-display: swap` — the system fallback in --font-sans paints first. */
@font-face {
  font-family: "Lexend";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/lexend-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Lexend";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/lexend-latin-ext-var.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
    U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Reset --- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-sans);
  background: var(--color-bg);
  color: var(--color-text);
  font-size: var(--fs-md);
  line-height: 1.5;
}

h1,
h2,
h3,
h4 {
  color: var(--color-text);
  font-weight: 600;
  margin: 0 0 var(--sp-3);
}

h1 {
  font-size: var(--fs-2xl);
}

h2 {
  font-size: var(--fs-xl);
}

h3 {
  font-size: var(--fs-lg);
}

p {
  margin: 0 0 var(--sp-3);
}

a {
  color: var(--color-primary);
  transition: color var(--motion-fast) var(--ease-standard);
}

a:hover {
  color: var(--color-primary-dark);
}

/* Accessibility (design A4): a visible focus ring is never removed. */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

/* Visually-hidden but exposed to assistive tech (frontend-redesign-v2 WU3).
 * Used by the kpi_card macro's tooltip: the tip text lives in a real DOM node
 * that `aria-describedby` can point at, instead of an `aria-label` that would
 * suppress it, or CSS `::after` generated content that screen readers ignore. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Visually hidden until focused (frontend-redesign-v2 a11y foundation).
 * Used by the skip-to-content link in _layout.html: off-screen for sighted
 * users, but on keyboard focus it resets the clip/size so it renders as a
 * normal focusable link pinned to the top-left of the page. */
.sr-only-focusable:focus,
.sr-only-focusable:focus-within {
  position: fixed;
  top: var(--sp-2);
  left: var(--sp-2);
  z-index: 1000;
  width: auto;
  height: auto;
  padding: var(--sp-2) var(--sp-4);
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: normal;
  background: var(--color-surface);
  color: var(--color-text);
  border: var(--bw-1) solid var(--color-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
}

button,
input,
select,
textarea {
  font-family: inherit;
  font-size: var(--fs-md);
}

/* --- Icon sprite consumer (design A7) --- */
/* The sprite <svg hidden> in _icons.html relies on the HTML `hidden`
   attribute alone, but browsers do not reliably apply the UA `[hidden] {
   display: none }` rule to a root <svg> element — it can render inline at
   its default 300x150 intrinsic size, pushing all following content (the
   header) down by that much. Force it explicitly. */
svg[hidden] {
  display: none;
}

.icon {
  width: 1em;
  height: 1em;
  display: inline-block;
  vertical-align: -0.125em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- App shell (design A6: all shell IDs/classes prefixed app-) --- */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--nav-h);
  padding: 0 var(--sp-5);
  background: var(--color-primary-darker);
  color: var(--color-primary-contrast);
  border-bottom: var(--bw-1) solid var(--c-navy-800);
  position: sticky;
  top: 0;
  /* sdd/mobile-responsive-webapp: `position: sticky` + z-index makes this
   * element its OWN stacking context — #app-nav's z-index:45 below is only
   * compared against .app-header's other children, never against
   * .app-nav-backdrop (a sibling of <header>, z-index:44). This value is
   * what actually has to outrank the backdrop, or the backdrop paints over
   * the whole header — drawer included — and swallows taps meant for its
   * links (confirmed live: a link tap inside the open drawer just closed
   * it, never navigated). 50 clears every current z-index below it. */
  z-index: 50;
}

.app-header a {
  color: inherit;
  text-decoration: none;
}

/* sdd/evir-rebranding: the wordmark is an SVG lockup (alt="e-VIR"), not an
 * icon + text node, so gap/font-size/font-weight/colour here are now inert. */
.app-brand {
  display: flex;
  align-items: center;
}

.app-brand__logo {
  display: block;
  height: 32px;
  width: auto;
}

#app-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

#app-nav a {
  color: var(--color-primary-contrast);
  font-size: var(--fs-sm);
  padding: var(--sp-2) var(--sp-1);
  opacity: 0.9;
  transition: opacity var(--motion-fast) var(--ease-standard);
}

#app-nav a:hover {
  opacity: 1;
  text-decoration: underline;
}

/* sdd/evir-rebranding — net-new. The active entry was previously
 * indistinguishable (only hover opacity/underline existed). Lime on the navy
 * header measures 10.4:1. The inset rule adds a non-colour cue with ZERO
 * layout impact (a real border-bottom would grow the item inside the 56px bar);
 * hover already owns text-decoration, so the active cue must not be underline.
 * This is the ONLY legal raw --color-brand (lime) site — it is on navy. */
#app-nav a[aria-current="page"] {
  color: var(--color-brand);
  opacity: 1;
  box-shadow: inset 0 -2px 0 var(--color-brand);
}

.app-header-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.app-user {
  font-size: var(--fs-sm);
  opacity: 0.85;
}

.app-logout-btn {
  background: transparent;
  border: var(--bw-1) solid rgba(255, 255, 255, 0.4);
  color: var(--color-primary-contrast);
  border-radius: var(--r-sm);
  padding: var(--sp-1) var(--sp-3);
  cursor: pointer;
  font-size: var(--fs-sm);
  transition: background-color var(--motion-fast) var(--ease-standard);
}

.app-logout-btn:hover {
  background: rgba(255, 255, 255, 0.1);
}

.app-main {
  width: 100%;
  margin: 0 auto;
  padding: var(--sp-5);
}

/* --- Buttons (frontend-redesign-v2 WU3 restyle) ---
 * Micro-interaction policy: the ONLY transitioned property is background-color
 * (--motion-fast). :active adds an instant, non-transitioned 1px press that
 * does not reflow siblings (transform, not margin/top). Focus ring is the
 * highlight-tint box-shadow recipe shared with the WU2 input/select restyle
 * so keyboard focus reads the same on every control. 44px min-height keeps
 * primary actions at the touch target (ui-ux-pro-max Priority 2). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 44px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: none;
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-standard);
}

.btn:hover {
  background: var(--btn-bg-hover);
}

.btn:active {
  background: var(--color-primary-darker);
  transform: translateY(1px);
}

/* Highlight-tint ring, layered ON TOP of the global :focus-visible outline
 * (design A4 — a visible focus ring is never removed). The tint ring alone is
 * faint on a white-bg .btn-secondary, so the navy outline stays as the
 * guaranteed indicator; the ring is the shared accent with the WU2
 * input/select focus treatment. */
.btn:focus-visible {
  box-shadow: 0 0 0 3px var(--color-brand-tint);
}

.btn:disabled {
  background: var(--c-gray-300);
  color: var(--c-gray-600);
  cursor: not-allowed;
  transform: none;
}

.btn-secondary {
  background: var(--color-surface);
  color: var(--color-primary);
  border: var(--bw-1) solid var(--color-border);
}

.btn-secondary:hover {
  background: var(--c-gray-100);
}

.btn-secondary:active {
  background: var(--c-gray-200);
}

.btn-danger {
  background: var(--color-danger);
}

/* No red-800 primitive exists; derive a darker shade with the same
 * relative-color idiom tokens.css already uses for the shadow tints. Without
 * this, .btn:hover would repaint a danger button navy (--btn-bg-hover). */
.btn-danger:hover {
  background: rgb(from var(--color-danger) calc(r * 0.85) calc(g * 0.85) calc(b * 0.85));
}

.btn-danger:active {
  background: rgb(from var(--color-danger) calc(r * 0.75) calc(g * 0.75) calc(b * 0.75));
  transform: translateY(1px);
}

/* Icon-only interactive control (e.g. inline-edit pencil trigger).
   44x44 minimum per ui-ux-pro-max Priority 2 (Touch & Interaction) —
   the icon graphic itself stays small, this pads the hit area. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  background: none;
  border: none;
  border-radius: var(--r-md);
  color: var(--color-primary);
  cursor: pointer;
  text-decoration: none;
  transition: background-color var(--motion-fast) var(--ease-standard);
}

.icon-btn:hover {
  background: var(--c-gray-100);
}

/* --- Cards / surfaces --- */
.card {
  background: var(--color-surface);
  border: var(--bw-1) solid var(--color-border);
  border-radius: var(--r-lg);
  padding: var(--card-pad);
  box-shadow: var(--shadow-sm);
}

/* Interactive card variant ONLY (frontend-redesign-v2 WU3). A plain
 * non-interactive `.card` never lifts — hover elevation has to mean "this is
 * clickable". Opt in with `a.card`, `.card--link`, or a `.card` wrapping an
 * `a.stretched-link`. box-shadow is the only transitioned property here
 * (--motion-base); no transform, no layout shift. */
a.card,
.card--link {
  text-decoration: none;
  color: inherit;
}

a.card,
.card--link,
.card:has(a.stretched-link) {
  transition: box-shadow var(--motion-base) var(--ease-standard);
}

a.card:hover,
a.card:focus-visible,
.card--link:hover,
.card--link:focus-within,
.card:has(a.stretched-link:hover),
.card:has(a.stretched-link:focus-visible) {
  box-shadow: var(--shadow-md);
}

/* --- Badges / status (frontend-redesign-v2 WU3 refine) ---
 * Every text/bg pairing below was checked >= 4.5:1 (WCAG AA normal text):
 * success/investor 4.65, warning/operador 4.68, danger 5.50, role-admin 7.59,
 * role-viewer 6.92. Transparent border keeps the box crisp on tinted rows
 * without shifting metrics. */
/* Kept minimal on purpose — `.badge` is used on ~33 pages. No `white-space`
 * override (would stop multi-word badges wrapping in narrow table cells and
 * widen columns / force horizontal scroll) and no border (would add 2px to
 * every existing badge box and nudge inline neighbours). Only the two
 * typographic nudges below, both sub-pixel at this font size. */
.badge {
  display: inline-block;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.01em;
}

.badge-success {
  background: var(--color-success-bg);
  color: var(--color-success);
}

.badge-warning {
  background: var(--color-warning-bg);
  color: var(--color-warning);
}

.badge-danger {
  background: var(--color-danger-bg);
  color: var(--color-danger);
}

/* Role/identity badges — shared classes over the --color-role-* tokens
 * (previously only defined per-page in usuarios_lista.html). Identity, not
 * outcome: kept distinct from success/warning/danger on purpose. */
.badge-role-admin {
  background: var(--color-role-admin-bg);
  color: var(--color-role-admin);
}

.badge-role-operador {
  background: var(--color-role-operador-bg);
  color: var(--color-role-operador);
}

.badge-role-viewer {
  background: var(--color-role-viewer-bg);
  color: var(--color-role-viewer);
}

.badge-role-investor {
  background: var(--color-role-investor-bg);
  color: var(--color-role-investor);
}

/* --- Forms --- */
.field {
  margin-bottom: var(--sp-4);
}

.field label {
  display: block;
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--sp-1);
}

input[type="text"],
input[type="number"],
input[type="email"],
input[type="password"],
input[type="date"],
select,
textarea {
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  border: var(--bw-1) solid var(--color-border);
  border-radius: var(--r-md);
  background: var(--color-surface);
  color: var(--color-text);
  /* Micro-interaction only (WU1 motion policy): colour + elevation, never
   * width/height. prefers-reduced-motion collapses it (block at file end). */
  transition: border-color var(--motion-fast) var(--ease-standard),
    box-shadow var(--motion-fast) var(--ease-standard);
}

/* Native <select> — CSS-only restyle (frontend-redesign-v2 WU2, plan 2.4).
 * `appearance: none` strips the OS chevron; a custom inline data: URI SVG
 * chevron (navy stroke #475569 — matches --color-text-muted; CSS custom
 * properties can't be referenced inside a data: URI) is painted at the right
 * edge, with `padding-right` wide enough that the option label never runs
 * under it. Height + horizontal padding come from the shared control block
 * above, so a <select> and a text <input> stay visually identical. The open
 * option list stays OS-native — every select in this app is a short list. No
 * JS. Hover/focus styling is shared with the text inputs below. */
select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: var(--sp-8);
  background-color: var(--color-surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  background-size: 16px 16px;
}

select::-ms-expand {
  display: none;
}

/* Hover: a subtle border-color shift on every control (WU1 leftover — text
 * input focus/hover styling was deferred to WU2 so it lands consistent with
 * the select). */
input[type="text"]:hover,
input[type="number"]:hover,
input[type="email"]:hover,
input[type="password"]:hover,
input[type="date"]:hover,
select:hover,
textarea:hover {
  border-color: var(--c-gray-400);
}

/* Focus: a visible ring in --color-brand-ink (never a bare `outline: none`).
 * `outline: none` here is always paired with the box-shadow ring + border
 * shift below, plus --shadow-sm elevation. Applied to :focus and
 * :focus-visible so mouse and keyboard users both see it, consistent with
 * how a restyled <select> must always show focus. */
input[type="text"]:focus,
input[type="number"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="date"]:focus,
select:focus,
textarea:focus,
input[type="text"]:focus-visible,
input[type="number"]:focus-visible,
input[type="email"]:focus-visible,
input[type="password"]:focus-visible,
input[type="date"]:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--color-brand-ink);
  box-shadow: 0 0 0 3px var(--color-brand-tint), var(--shadow-sm);
}

/* Forced colors / Windows High Contrast (WCAG 2.4.7): `box-shadow` is
 * dropped in this mode, so the box-shadow focus ring above would vanish.
 * Restore a real `outline` as the keyboard focus indicator — it survives
 * forced-colors and uses a system color that always contrasts. */
@media (forced-colors: active) {
  input[type="text"]:focus,
  input[type="number"]:focus,
  input[type="email"]:focus,
  input[type="password"]:focus,
  input[type="date"]:focus,
  select:focus,
  textarea:focus,
  input[type="text"]:focus-visible,
  input[type="number"]:focus-visible,
  input[type="email"]:focus-visible,
  input[type="password"]:focus-visible,
  input[type="date"]:focus-visible,
  select:focus-visible,
  textarea:focus-visible,
  :focus-visible {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }
}

/* --- Alerts --- */
.alert {
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  margin-bottom: var(--sp-4);
}

.alert-danger {
  background: var(--color-danger-bg);
  color: var(--color-danger);
  border: var(--bw-1) solid var(--color-danger);
}

.alert-success {
  background: var(--color-success-bg);
  color: var(--color-success);
  border: var(--bw-1) solid var(--color-success);
}

/* .alert-warning — three templates use it (usuarios_nuevo.html,
 * admin_fleets_nuevo.html, admin_fleets_detalle.html) but no shared rule
 * existed, so they fell back to the base `.alert` (no colour). No template
 * uses `.alert-info`, so that variant is intentionally not added. */
.alert-warning {
  background: var(--color-warning-bg);
  color: var(--color-warning);
  border: var(--bw-1) solid var(--color-warning);
}

/* --- Loading state (frontend-redesign-v2 WU1 motion policy) ---
 * `.cargando` dims content and disables interaction; `.skeleton` is a static
 * fill. Callers set explicit width/height per use to avoid layout shift.
 * Still NO shimmer sweep / @keyframes on the skeleton itself — that stays
 * banned. A short skeleton->content crossfade on reveal is allowed under the
 * new policy but is wired per-surface in later WUs, not here. */
.cargando {
  opacity: 0.45;
  pointer-events: none;
}

.skeleton {
  background: var(--color-skeleton);
  border-radius: var(--r-sm);
  display: block;
}

/* --- Tabular figures (frontend-redesign-v2 WU1 — unchanged, stays global) ---
 * Digits line up in columns in every data table. tabular-nums only affects
 * digit glyphs, so it is harmless on text cells. */
table td,
table th {
  font-variant-numeric: tabular-nums;
}

/* --- Data tables (frontend-redesign-v2 WU3 — OPT-IN via .data-table) ------
 * DEVIATION from plan §2.5 ("propagate to the foundation-only pages"): a bare
 * `table` restyle CANNOT be safe here. A compound selector like `table td`
 * (specificity 0,0,2) beats every already-migrated page's scoped `th, td { }`
 * (0,0,1) regardless of source order, so a global restyle silently regresses
 * usuarios_lista / cuentas_lista / auditoria / inversionistas_lista /
 * admin_fleets_detalle (padding, alignment, header colour, zebra they never
 * had). So the treatment is OPT-IN: the WU4+ reworked pages (and any shared
 * markup that renders a data grid) add `class="data-table"`. The ~33
 * foundation-only pages keep their current table look until each is reworked
 * individually — regressing migrated pages is worse than not propagating.
 * No plain-table opt-out class is needed once the whole thing is opt-in. */
.data-table {
  border-collapse: collapse;
}

.data-table td,
.data-table th {
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
}

/* Header: weight + muted colour + a stronger bottom rule. --color-text-muted
 * (#475569) clears 4.5:1 on both --color-surface (7.58) and --color-bg
 * (7.24), so it is safe on a card or on the page ground. */
.data-table thead th {
  font-weight: 600;
  color: var(--color-text-muted);
  border-bottom: 2px solid var(--color-border);
}

.data-table tbody td {
  border-bottom: var(--bw-1) solid var(--color-border);
}

/* Zebra + row hover. Very light gray stripe (gray-50), a touch stronger on
 * hover (gray-100) — matches the existing `.icon-btn:hover` fill. Only
 * background-color transitions, --motion-fast. */
.data-table tbody tr {
  transition: background-color var(--motion-fast) var(--ease-standard);
}

.data-table tbody tr:nth-child(even) {
  background: var(--c-gray-50);
}

.data-table tbody tr:hover {
  background: var(--c-gray-100);
}

/* --- CSS-only tooltip (frontend-redesign-v2 WU3) -------------------------
 * Promoted to app.css from home.html's page-local copy so the shared
 * `kpi_card` macro's `tooltip` arg works on any page. home.html /
 * usuarios_lista.html keep their own identical copies (later in the cascade,
 * so they still win on those pages — no visual change). Reveal on hover AND
 * keyboard focus; instant (no opacity transition — intentional, matches the
 * v1 decision). */
.info-tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  margin-left: var(--sp-1);
  border-radius: 50%;
  background: var(--c-gray-300);
  color: var(--color-text-muted);
  font-size: var(--fs-xs);
  line-height: 1;
  cursor: help;
  position: relative;
}

.info-tip::after {
  content: attr(data-tip);
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%);
  background: var(--c-gray-800);
  color: var(--color-primary-contrast);
  padding: var(--sp-2);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  white-space: normal;
  width: max-content;
  max-width: 14rem;
  opacity: 0;
  pointer-events: none;
  z-index: 5;
}

.info-tip:hover::after,
.info-tip:focus-visible::after {
  opacity: 1;
}

/* --- kpi_card macro (frontend-redesign-v2 WU3) -------------------------- */
.kpi-card {
  background: var(--color-surface);
  border: var(--bw-1) solid var(--color-border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  box-shadow: var(--shadow-sm);
}

.kpi-card__label {
  display: flex;
  align-items: center;
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.kpi-card__value {
  margin-top: var(--sp-1);
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

/* sdd/evir-rebranding: "highlight" is the ROLE (the "look here" key metric,
 * e.g. Neto); --color-brand-ink is its VALUE. Raw lime here would be 1.55:1 on
 * the white card — the exact regression this token split prevents. The class
 * name and the `highlight` macro arg are kept (byte-asserted across 6 tests). */
.kpi-card--highlight .kpi-card__value {
  color: var(--color-brand-ink);
}

/* Semantic status on a KPI tile (feature/dashboard-kpi-status-color).
 * Outcome states carry a coloured left accent, a faint background tint, and a
 * coloured value — used only where the number has a verdict (Neto). The value
 * rules sit AFTER --highlight so a tile that is both (Neto is highlight + an
 * outcome state) shows the verdict colour, not the "look here" blue.
 * Identity states (income/expense) are a fixed hue on the left edge only — no
 * tint, no value recolour — so the KPI row reads in the same green/amber
 * language as the income/expense line chart. */
.kpi-card--positive {
  border-left: 3px solid var(--color-success);
  background: var(--color-success-bg);
}
.kpi-card--negative {
  border-left: 3px solid var(--color-danger);
  background: var(--color-danger-bg);
}
.kpi-card--neutral {
  border-left: 3px solid var(--color-neutral);
  background: var(--color-neutral-bg);
}
.kpi-card--positive .kpi-card__value {
  color: var(--color-success);
}
.kpi-card--negative .kpi-card__value {
  color: var(--color-danger);
}
.kpi-card--neutral .kpi-card__value {
  color: var(--color-text);
}
.kpi-card--income {
  border-left: 3px solid var(--color-success);
}
.kpi-card--expense {
  border-left: 3px solid var(--color-warning);
}

.kpi-card__hint {
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--color-text-muted);
}

/* --- section_header macro (frontend-redesign-v2 WU3) ------------------- */
.section-header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-4);
}

.section-header__title {
  margin: 0;
}

.section-header__desc {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.section-header__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
}

.section-header__divider {
  grid-column: 1 / -1;
  width: 100%;
  height: 0;
  margin: var(--sp-2) 0 0;
  border: 0;
  border-top: var(--bw-1) solid var(--color-border);
}

/* Narrow viewports: stack the actions under the title instead of letting the
 * `auto` track push a long title + two 44px buttons past the viewport edge
 * (horizontal page scroll). */
@media (max-width: 640px) {
  .section-header {
    grid-template-columns: 1fr;
  }
}

/* --- side_panel macro shell (frontend-redesign-v2 WU3) -----------------
 * Slide is `transform: translateX()` ONLY — never width/left (those reflow
 * and jank). Open/closed state is keyed purely off `aria-hidden` so the
 * element stays in the DOM and the transition runs both ways; `[hidden]`
 * is only the pre-boot / no-JS hard hide. `visibility` is transitioned with
 * a delay so the off-screen panel is not focusable/hit-testable while
 * closed, then flips immediately on open. WU5 wires focus trap + return. */
.side-panel__backdrop {
  position: fixed;
  inset: 0;
  z-index: 44;
  background: rgb(from var(--c-navy-900) r g b / 0.35);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--motion-base) var(--ease-standard),
    visibility 0s linear var(--motion-base);
}

.side-panel__backdrop[aria-hidden="false"] {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--motion-base) var(--ease-standard),
    visibility 0s linear 0s;
}

.side-panel {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 45;
  display: flex;
  flex-direction: column;
  width: 380px;
  max-width: 100%;
  background: var(--color-surface);
  border-left: var(--bw-1) solid var(--color-border);
  box-shadow: var(--shadow-lg);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform var(--motion-base) var(--ease-standard),
    visibility 0s linear var(--motion-base);
}

.side-panel[aria-hidden="false"] {
  transform: translateX(0);
  visibility: visible;
  transition: transform var(--motion-base) var(--ease-standard),
    visibility 0s linear 0s;
}

@media (max-width: 768px) {
  .side-panel {
    width: 100%;
  }
}

.side-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--bw-1) solid var(--color-border);
}

.side-panel__title {
  margin: 0;
  font-size: var(--fs-lg);
}

.side-panel__body {
  flex: 1;
  overflow-y: auto;
  padding: var(--sp-4);
}

/* --- Reduced motion (frontend-redesign-v2 WU1) ---
 * Mandatory escape hatch for the narrow micro-interaction policy. Anything
 * that animates anywhere in the app collapses to effectively instant here —
 * near-zero rather than 0 so `transitionend` / `animationend` listeners still
 * fire. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }

  /* Panel-specific belt-and-braces: the global rule above already collapses
   * the transform/visibility transitions, but state the side-panel and its
   * backdrop explicitly so the "no slide under reduced motion" contract is
   * self-evident here and cannot be broken by a future edit that scopes the
   * global reset. The panel still appears/disappears — just instantly. */
  .side-panel,
  .side-panel[aria-hidden="false"],
  .side-panel__backdrop,
  .side-panel__backdrop[aria-hidden="false"],
  /* slice A — the folded nav drawer animates the same recipe. */
  #app-nav,
  #app-nav[data-open="true"],
  /* slice B — the confirm-sheet <dialog>'s native open/close transition. */
  .confirm-sheet {
    transition: none !important;
  }
}

/* ==================================================================
 * sdd/mobile-responsive-webapp — Slice A: global shell + nav fold.
 * Per-rule media direction (design Decision 1): OVERRIDE blocks use
 * max-width; NEW standalone helpers are mobile-first. Values are tokens.
 * ================================================================== */

/* NEW standalone — wide tables scroll inside their own region, not the page. */
.table-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}

.table-scroll > table {
  min-width: 100%;
}

/* NEW standalone — default Chart.js container height (dashboard, later slice). */
:root {
  --chart-h: 18rem;
}

/* OVERRIDE — chart height ladder (narrow phones). */
@media (max-width: 768px) {
  :root {
    --chart-h: 14rem;
  }
}

/* OVERRIDE — chart height ladder (small phones). */
@media (max-width: 480px) {
  :root {
    --chart-h: 12rem;
  }
}

/* NEW standalone — kill the 300ms tap delay on controls; content still zooms. */
a,
button,
.btn,
.icon-btn,
label,
summary,
select,
[role="button"] {
  touch-action: manipulation;
}

/* NEW standalone — hamburger hidden by default; shown only in the <=768 block. */
.app-nav-toggle {
  display: none;
}

/* NEW standalone — no-JS safety net: the inline nav can never widen the page. */
#app-nav {
  overflow-x: auto;
}

/* NEW standalone — the scrim is inert until the drawer opens. */
.app-nav-backdrop {
  display: none;
}

/* OVERRIDE — fold #app-nav into a LEFT-side off-canvas drawer (mirrors the
 * .side-panel recipe). Scoped html[data-nav-js="on"] so the no-JS fallback
 * keeps the inline nav; height uses dvh, never vh (iOS URL-bar clip). */
@media (max-width: 768px) {
  html[data-nav-js="on"] .app-nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    min-width: 44px;
  }

  html[data-nav-js="on"] #app-nav {
    position: fixed;
    inset: var(--nav-h) auto 0 0;
    z-index: 45;
    width: min(320px, 86vw);
    height: calc(100dvh - var(--nav-h));
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--sp-3);
    background: var(--color-primary-darker);
    overflow-y: auto;
    overscroll-behavior: contain;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--motion-base) var(--ease-standard),
      visibility 0s linear var(--motion-base);
  }

  html[data-nav-js="on"] #app-nav[data-open="true"] {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--motion-base) var(--ease-standard),
      visibility 0s linear 0s;
  }

  html[data-nav-js="on"] #app-nav a {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-height: 44px;
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--fs-md);
  }

  html[data-nav-js="on"] .app-nav-backdrop[aria-hidden="false"] {
    display: block;
    position: fixed;
    inset: var(--nav-h) 0 0 0;
    z-index: 44;
    background: rgb(from var(--c-navy-900) r g b / 0.35);
  }

  body.app-nav-open {
    overflow: hidden;
  }
}

/* OVERRIDE — tighten shell chrome on small phones; lift form controls to the
 * 44px touch floor (base input/select/textarea are ~38px). */
@media (max-width: 480px) {
  .app-header {
    padding: 0 var(--sp-3);
  }

  .app-main {
    padding: var(--sp-4) var(--sp-4)
      calc(var(--sp-4) + env(safe-area-inset-bottom));
  }

  .app-user {
    display: none;
  }

  .app-logout-btn {
    min-height: 44px;
  }

  .app-logout-btn span {
    display: none;
  }

  input[type="text"],
  input[type="number"],
  input[type="email"],
  input[type="password"],
  input[type="date"],
  input[type="file"],
  select,
  textarea {
    min-height: 44px;
  }
}

/* ==================================================================
 * sdd/mobile-responsive-webapp — Slice B: tarifas wizard.
 * `.confirm-sheet` is a reusable blocking-dialog primitive (native
 * <dialog>), not page layout, so it lives here rather than in
 * comprobante_form.html's page-local <style> block.
 * ================================================================== */

/* NEW standalone — backs confirmarAdvertencias() in comprobante_form.html.
 * showModal() gives blocking semantics, focus trap, Esc and backdrop
 * inertness for free; no custom overlay/focus-trap code needed. */
.confirm-sheet {
  border: 0;
  padding: 0;
  width: min(32rem, 100%);
  max-width: 100%;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}

.confirm-sheet::backdrop {
  background: rgb(from var(--c-navy-900) r g b / 0.45);
}

.confirm-sheet__body {
  padding: var(--sp-4);
}

.confirm-sheet__actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: 0 var(--sp-4) var(--sp-4);
}

.confirm-sheet__actions .btn {
  width: 100%;
  min-height: 48px;
}

/* OVERRIDE — bottom sheet on phones (equal-or-narrower than the wizard's
 * own <=480px breakpoint). */
@media (max-width: 480px) {
  .confirm-sheet {
    position: fixed;
    inset: auto 0 0 0;
    margin: 0;
    width: 100%;
    max-height: 85dvh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
  }
}
