/**
 * @file
 * Global variables - Solo estructura, sin estilos visuales
 */

/* ===================================
   SKINNABLE TOKEN CONTRACT (S12-T1)
   Canonical, single source of truth for which custom properties a
   "skin" (preset palette) is allowed to override. Do NOT scatter this
   list — if the skinnable surface changes, update ONLY this block.
   See docs/superpowers/specs/2026-07-16-theming-foundation-design.md
   ("Contrato de tokens") for the design-name -> app-token mapping and
   web/modules/custom/pool_core/src/Theme/SkinRegistry.php for the
   preset skin values that fill these tokens.

   These are annotations only — no values below are changed by this
   block. The values stay wherever they already live (the two :root
   blocks in this file).

   Role         | Token (this file)      | Defined at
   -------------|-------------------------|------------
   bg           | --color-white           | :root (line ~28)
   chrome       | --color-chrome          | :root (line ~29)
   card         | --pm-card-bg            | :root (line ~281, PREDICCIONES block)
   border       | --pm-card-border        | :root (line ~282)
   field-bg     | --pm-field-bg           | :root (line ~289)
   field-border | --pm-field-border       | :root (line ~288)
   accent       | --pm-accent             | :root (line ~286)
   accent-ink   | --pm-accent-contrast    | :root (line ~287)
   text         | --pm-text               | :root (line ~284)
   muted        | --pm-text-muted         | :root (line ~285)
   good         | --pm-move-up            | :root (line ~297)
   warn         | --pm-hint               | :root (line ~294)
   danger       | --color-danger          | :root (line ~35)
   me           | --pm-row-me-bg          | :root (line ~303)

   Any other --pm-* / --color-* token NOT listed above (shadows,
   derived -bg variants, layout/spacing tokens, etc.) is NOT
   skinnable and stays fixed across all skins.
   =================================== */

:root {
  /* Tema claro por default (S15-T1, Nivel 0 de theming, 2026-07-17):
     "Día de partido" — blanco/off-white con tinte azul sutil, texto
     navy. El tema oscuro "Noche de estadio" vive detrás de
     [data-theme="dark"] más abajo — mismo set de tokens, valores
     oscuros preservados tal cual estaban antes de esta reestructura.
     Sin selector de modo aún (Tajada 2): esto solo fija el default.
     Ver docs/superpowers/specs/2026-07-17-light-mode-design.md. */
  color-scheme: light;

  /* Spacing básico */
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;

  /* Typography básica */
  --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-size-base: 16px;
  --font-size-lg: 20px;
  --font-size-xl: 28px;

  --color-white: #F6F1E6;
  --color-chrome: #EFE7D4;
  --color-text: #14262F;
  /* Marca = Petróleo (fija, mismo hue en ambos modos, dos
     luminosidades). El verde queda reservado para positivo/puntos
     (--pm-points / --pm-move-up), NO se usa para marca. */
  --color-primary: #1F6F8B;
  --color-primary-dark: #185D75;
  /* "Brand chrome" (S15 polish, corrección 2): el header del sitio, el
     menú de navegación del pool y el footer llevan fondo Petróleo con
     texto claro en modo claro — mismo look sólido que el modo oscuro ya
     tenía, pero en el azul de marca. Tokens propios (--pm-chrome-*) en
     vez de reusar --color-chrome/--pm-text directamente porque
     --color-chrome también resuelve --pm-field-bg dentro de .auth-main
     (ver ese bloque más abajo) y NO debe volverse Petróleo ahí. En
     oscuro estos tokens son un espejo exacto de los valores que esas
     regiones ya usaban (--color-chrome / --pm-text / --pm-text-muted),
     así que el modo oscuro no cambia un solo pixel. */
  --pm-chrome-bg: #1F6F8B;
  --pm-chrome-text: #FFFFFF;
  /* Lifted from #D8E7EC (4.47:1, just under AA) to clear 4.5:1 on the
     petróleo chrome bg for 12px footer text — measured ~4.86:1. */
  --pm-chrome-text-muted: #E6EFF2;
  --color-secondary: #2A9D8F;
  /* --color-warning es el rol "relleno/borde" (amber), constante entre
     modos — el rol "texto" mode-dependent vive en --pm-hint. */
  --color-warning: #FFB703;
  --color-danger: #D62839;
  --color-danger-bg: rgba(214, 40, 57, 0.12);
  --shadow-lg: 0 4px 12px rgba(15, 27, 45, 0.08);
  --shadow-dropdown: 0 16px 36px rgba(15, 27, 45, 0.10);
  --shadow-bottom-bar: 0 -4px 16px rgba(15, 27, 45, 0.08);
  --shadow-panel: 0 8px 24px rgba(15, 27, 45, 0.09);
  --pmatch-inclination-home: #1F8A7D;
  --pmatch-inclination-draw: #94A3B8;
  --pmatch-inclination-away: #D62839;
  /* Reserved height for the fixed bottom tab bar (see SIDEBAR NAV
     section) so page content/footer never sits underneath it. Kept in
     sync with .sidebar-nav's actual rendered height at <1024px
     (measured ~74px after the B2 restyle's item padding/chip). */
  --sidebar-nav-height: 74px;
  /* Atmospheric section paragraph (S29-T5): the "hero" variant always
     reads as a dark, cinematic backdrop for the canvas glow/particles —
     same idea as reveal cards/slider staying on --pm-card-bg, but this
     component intentionally does NOT follow the light/dark page surface,
     it stays dark in both modes (only the shade shifts slightly) so the
     particle glow always has contrast to draw on. --pm-atmospheric-echo-*
     feeds BOTH the "echo" paragraph variant's CSS-only gradient AND the
     unrelated app-wide echo further down (.app-content) — same subtle
     petróleo wash, two call sites. */
  --pm-atmospheric-bg: #0F2233;
  --pm-atmospheric-glow: rgba(111, 197, 214, 0.20);
  --pm-atmospheric-particle: rgba(224, 196, 130, 0.55);
  --pm-atmospheric-echo-1: rgba(31, 111, 139, 0.10);
  --pm-atmospheric-echo-2: rgba(224, 166, 60, 0.06);
}

:root[data-theme="dark"] {
  /* Tema oscuro "Noche de estadio" (2026-07-13): azul noche, verde
     césped eléctrico bajo reflectores. Valores preservados tal cual
     — esta reestructura los MUEVE detrás de [data-theme="dark"], no
     los cambia (excepto la marca --color-primary/-dark, que pasa de
     verde a Petróleo aclarado por decisión del dueño — el verde queda
     libre para --pm-points/--pm-move-up).
     Los controles nativos (selects, spinners, scrollbars) renderizan
     con chrome oscuro del sistema en este modo. */
  color-scheme: dark;

  --color-white: #0C1826;
  --color-chrome: #0A1420;
  --color-text: #E9F0F6;
  --color-primary: #6FA6BB;
  --color-primary-dark: #8FBED0;
  /* Espejo exacto de los valores que el header/sidebar/footer ya usaban
     antes de esta corrección — el modo oscuro no cambia (ver el
     bloque --pm-chrome-* en :root arriba). */
  --pm-chrome-bg: #0A1420;
  --pm-chrome-text: #EAF2F8;
  --pm-chrome-text-muted: #7E93AD;
  --color-warning: #FFB703;
  --color-danger: #E63946;
  --color-danger-bg: rgba(230, 57, 70, 0.15);
  --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.45);
  --shadow-dropdown: 0 16px 36px rgba(0, 0, 0, 0.55);
  --shadow-bottom-bar: 0 -4px 16px rgba(0, 0, 0, 0.35);
  --shadow-panel: 0 8px 24px rgba(0, 0, 0, 0.4);
  --pmatch-inclination-home: #2A9D8F;
  --pmatch-inclination-draw: #9CA3AF;
  --pmatch-inclination-away: #E63946;
  /* Atmospheric section (S29-T5): a touch deeper than the light-mode
     value above so the hero backdrop still reads distinctly "night" in
     dark mode, per --pm-atmospheric-bg's docblock in the light :root. */
  --pm-atmospheric-bg: #081420;
  --pm-atmospheric-glow: rgba(111, 197, 214, 0.24);
  --pm-atmospheric-particle: rgba(224, 196, 130, 0.6);
  --pm-atmospheric-echo-1: rgba(111, 166, 187, 0.10);
  --pm-atmospheric-echo-2: rgba(224, 180, 85, 0.05);
}

/* ===================================
   FONTS
   =================================== */

/* Barlow Condensed (SIL OFL, see fonts/OFL.txt) — self-hosted, no
   external network dependency at runtime. Used only for the big
   numbers in prediction cards (S10-T2, Capa 3): score inputs, the
   final/live score, and earned points. */
@font-face {
  font-family: 'ScoreboardCondensed';
  src: url('../fonts/barlow-condensed-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'ScoreboardCondensed';
  src: url('../fonts/barlow-condensed-800.woff2') format('woff2');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  line-height: 1.5;
  /* Desktop gutters outside the centered .app-wrapper — slightly
     darker than the page surface so the app column reads as "the
     pitch" against the surrounding stadium dark. */
  background: var(--color-chrome);
  color: var(--color-text);
}

/* ===================================
   APP WRAPPER - RESPONSIVE CONTAINER
   =================================== */

.app-wrapper {
  min-height: 100vh;
  /* Sticky footer (S13 B1 fix): make the wrapper itself the flex
     column whose last child (footer) sticks to the bottom of the
     viewport on short pages instead of floating right after a
     shorter-than-viewport .app-layout. */
  display: flex;
  flex-direction: column;
  /* Image-model rework: the per-championship full-page background image
     (--pm-theme-bg-image) was removed — field_theme_background_url is now the
     subtle per-block diagonal accent (.champ-diagonal), not a page-wide
     background. Only the flat surface color remains here. */
  background: var(--color-white);
}

/* ===================================
   CHAMPIONSHIP DIAGONAL ACCENT
   ===================================
   Image-model rework: subtle per-championship image anchored top-right,
   masked to fade diagonally toward the bottom-left. Applied to the "Mis
   pollas" pool card and the pool Inicio welcome block, ONLY when the
   championship defines a diagonal image — the caller adds both the
   `champ-diagonal` class and the `--champ-diagonal-img` custom property
   inline; without the var, ::before paints nothing. */
.champ-diagonal {
  position: relative;
  isolation: isolate;
}
.champ-diagonal::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--champ-diagonal-img) top right / auto 150% no-repeat;
  -webkit-mask-image: linear-gradient(225deg, #000 0%, transparent 55%);
          mask-image: linear-gradient(225deg, #000 0%, transparent 55%);
  opacity: 0.10;
  border-radius: inherit;
  pointer-events: none;
}

/* Mobile: Full width */
@media (max-width: 767px) {
  .app-wrapper {
    max-width: 100%;
  }
}

/* Tablet (768–1023px, S15-T1 F1 fix, 2026-07-18): used to cap at 700px
   centered with a column shadow — left visible gutters on either side
   that didn't match the "reaches the edges" full-bleed treatment the
   rest of the breakpoints get (<768 is 100% above; >=1024 is
   max-width:none below). Removed so this range falls through to the
   same full-width behavior as <768 — no separate rule needed here.
   `.app-content` still gets breathing room from its own padding
   (var(--space-md)/var(--space-lg)); it only gains a max-width at
   >=1024 (see MAIN CONTENT below), which is wide enough that 768–1023
   never triggers it. */

/* Sidebar nav (S10-T2): reserve space so the fixed bottom tab bar
   never covers the footer or the tail of scrollable content. Only
   applies where the tab bar is fixed (<1024px, see SIDEBAR NAV
   section below) — >=1024px the nav is inline in the sticky header
   (S15-T1) and takes no space out of the content flow at all.
   Selector still keys on `.sidebar-nav-container` (the aside that now
   holds only the pool-name brand strip, S15-T1) rather than the `<nav>`
   itself: both render/vanish together under the same
   `pool.status == 'approved'` gate, so the presence check is identical
   either way, and this way the rule doesn't care that the actual
   `<nav>` moved out to header.html.twig.
   Scoped to Capa 2 (:has(.pm-pool-context) — the pool-context
   fixed bottom bar's OWN container (.sidebar-nav-container, rendered only
   inside pool-sidebar.html.twig's `pool.status == 'approved'` gate) — NOT
   .pm-pool-context, which also wraps non-member pages (preview / request-join)
   that carry the skin but no bar (S13 B3). Keying on the bar's container means
   Capa 1 AND non-member Capa 2 pages get zero reservation (no bar to reserve
   for), while member pages still reserve exactly the bar's height — S13 B1 fix
   + B3 seam correction. */
@media (max-width: 1023px) {
  .app-wrapper:has(.sidebar-nav-container) {
    padding-bottom: calc(var(--sidebar-nav-height) + env(safe-area-inset-bottom));
  }
}

/* Desktop app-shell (S15 full-bleed, 2026-07-18): the app fills the
   viewport instead of living in a centered 900/1000px column — the
   pool nav moves inline into the sticky header (S15-T1, no more left
   rail) and .app-content gets its own max-width (see below), so the
   "column with giant gutters" look goes away. */
@media (min-width: 1024px) {
  .app-wrapper {
    max-width: none;
    box-shadow: none; /* no floating-column shadow when full-bleed */
  }
}

/* ===================================
   MAIN CONTENT
   =================================== */

.app-content {
  padding: var(--space-md);
  /* min-height: calc(100vh - 120px) removed (S13 B1 sticky-footer
     fix) — it forced this region to fill a near-full viewport on its
     own, reproducing the same footer gap the flex sticky-footer
     above already fixes; layout.css's `.app-content { flex: 1 }`
     already makes it grow to fill .app-layout without this. */

  /* App-wide atmospheric echo (S29-T5), pure CSS, no JS, no server cost:
     a faint two-tone gradient wash that reads as continuity with the
     "hero" atmospheric paragraph's canvas glow on internal pages
     (dashboard, ranking, etc.) that never render that paragraph. This is
     the element's OWN background (box background painting is always the
     first, bottom-most layer of the box per the CSS box model) — it
     needs no pseudo-element, no position/z-index, so it can never end up
     stacked above the header/sidebar/modals or any other part of the
     site's carefully-tuned z-index scheme (see SIDEBAR NAV / dropdown
     panels below). Cards (--pm-card-bg, opaque) and other content paint
     on top of it normally; only the gutters between them show the wash,
     which is the point — "subtle", not a background image.
     background-size > 100% + an animated background-position is the
     GPU-cheap way to drift a gradient without repainting geometry;
     disabled entirely under prefers-reduced-motion below. */
  background-image:
    radial-gradient(ellipse 60% 50% at 20% 20%, var(--pm-atmospheric-echo-1), transparent 60%),
    radial-gradient(ellipse 55% 45% at 85% 75%, var(--pm-atmospheric-echo-2), transparent 60%);
  background-repeat: no-repeat;
  background-size: 160% 160%;
  background-position: 0% 0%;
}
@media (prefers-reduced-motion: no-preference) {
  .app-content {
    animation: pm-app-echo-drift 36s ease-in-out infinite alternate;
  }
}
@media (prefers-reduced-motion: reduce) {
  .app-content {
    animation: none;
  }
}
@keyframes pm-app-echo-drift {
  0% {
    background-position: 0% 0%;
  }
  100% {
    background-position: 100% 30%;
  }
}

/* Desktop app-shell (S15 full-bleed): .app-wrapper is full-bleed above
   >=1024px, so .app-content needs its own cap — otherwise it stretches
   to the viewport edge on wide screens. This also caps the RAM card
   grids (.matches-grid etc., auto-fill + minmax) at ~4 columns.
   layout.css's `min-width: 0` on .app-content stays untouched — it still
   prevents wide grids/tables from overflowing this column.

   `width: 100%` (S16, "width jump" fix, 2026-07-18) is NOT redundant with
   max-width — it is the actual fix for a real bug. `.app-content` is a
   flex item of `.app-layout` (layout.css, `display:flex; flex-direction:
   column`), so its CROSS axis is horizontal/width. Per the flexbox spec,
   `align-items: stretch` (the default, and what makes a flex item fill
   its line) is DISABLED whenever the item has an auto margin on that
   axis. Without an explicit width, the browser falls back to sizing
   `.app-content` by CONTENT (fit-content/shrink-to-wrap) instead of by
   the container, so the box only ever grows as wide as whatever is
   inside it happens to want to be — capped at 1360 only by coincidence
   when content is wide enough, and stuck far narrower than 1360 with big
   empty side margins on any page whose content doesn't happen to want
   that width — reported as "Predicciones is wide, every other page stays
   narrow on desktop".

   `padding-inline` instead of `margin-inline: auto` (S16-T fix, overflow
   on Predicciones, 2026-07-19): centering via an auto margin on
   `.app-content` ITSELF turned out to still trip the same stretch-disabled
   fallback the paragraph above describes — `align-items: stretch` is
   disabled by ANY non-stretch condition on this item (auto margin, or
   `align-items: center` on `.app-layout`, confirmed by direct measurement),
   and once disabled, `width: 100%` stops resolving against the flex
   container's definite width and the box is sized by CONTENT again, exactly
   like before the S16 fix. On most pages the content's own preferred width
   happens to be close enough to 1360 that this went unnoticed. Predicciones
   exposed it hard: the day-chip carousel (`.day-chips-wrap` > `.day-chips`,
   one pill per match day, `white-space: nowrap`, no wrap) has a preferred
   width equal to the sum of EVERY chip un-wrapped (measured ~2270px with 27
   days) — clamped only by `max-width: 1360px`, so `.app-content` rendered at
   a fixed 1360px on ANY viewport from 1024px up to ~1360px+scrollbar,
   overflowing the real viewport by up to ~350px on both edges (header,
   title, carousel and cards all live inside this one over-wide box, so they
   all appeared to shift/cut together).

   Fix: never give `.app-content` an auto margin (or any other non-stretch
   alignment) at all — let it stay TRULY stretched to the flex container's
   full width always (bulletproof: a stretched flex item's width can't be
   hijacked by a child's preferred/content width, no matter how wide that
   child wants to be). The old `max-width: 1360px` + `margin-inline: auto`
   pair effectively reserved (container - 1360) / 2 of MARGIN on each side
   once the container passed 1360px; `padding-inline` below reserves the
   exact same amount as PADDING instead, added on top of the base
   `var(--space-lg)` gutter — same visual result (content area caps at
   1280 - 2*24 = 1232px, centered) for every page, but computed with zero
   dependency on flex alignment, so it can never fall back to shrink-to-fit
   again regardless of what any child's carousel/grid wants to be. */
@media (min-width: 1024px) {
  .app-content {
    width: 100%;
    padding-block: var(--space-lg);
    padding-inline: calc(var(--space-lg) + max(0px, (100% - 1280px) / 2));
  }
}

/* ===================================
   TYPOGRAPHY
   =================================== */

h1 {
  font-size: var(--font-size-xl);
  font-weight: 700;
  margin-bottom: var(--space-md);
  color: var(--color-text);
}

h2 {
  font-size: var(--font-size-lg);
  font-weight: 600;
  margin-bottom: var(--space-sm);
  color: var(--color-text);
}

h3 {
  font-size: var(--font-size-base);
  font-weight: 600;
  margin-bottom: var(--space-sm);
  color: var(--color-text);
}

p {
  margin-bottom: var(--space-md);
  color: var(--color-text);
}

/* ===================================
   LINKS
   =================================== */

a {
  color: var(--color-primary);
  transition: color 0.2s ease;
}

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

/* Alpine.js: hide elements until Alpine has initialized them, so
   conditionally-shown UI (e.g. quick-picks) doesn't flash visible first. */
[x-cloak] {
  display: none !important;
}

/* Match status badges — see predictions.js getStatusClass(). Color/weight
   only (obra gris — no decorative styling), so the locked-vs-open state is
   visible from page load per S1-T6. */
.status-not-started {
  color: var(--pm-text-muted);
}

.status-locked {
  color: var(--pm-hint);
  font-weight: 600;
}

.status-in-progress {
  color: var(--pm-points);
  font-weight: 600;
}

.status-finished {
  color: var(--pm-text-muted);
}

.status-postponed {
  color: var(--pm-hint);
}

.status-cancelled {
  color: var(--color-danger);
}

.status-calculating {
  color: var(--pm-hint);
  font-weight: 600;
}

.status-scoring-error {
  color: var(--color-danger);
  font-weight: 600;
}

/* Async scoring in progress (S2-T5) — same palette as the warning
   message, obra gris. */
.scoring-banner {
  padding: var(--space-md);
  margin-bottom: var(--space-md);
  background: var(--pm-hint-bg);
  border-left: 4px solid var(--color-warning);
  color: var(--pm-hint);
}

/* Classifier selector (KO matches) — obra gris: solo estados
   funcionales, sin decoración. */
.classifier-selector {
  margin-top: var(--space-sm);
}

.classifier-label {
  margin: 0 0 4px 0;
  font-size: 12px;
  color: var(--pm-text-muted);
}

.classifier-options {
  display: flex;
  gap: 8px;
}

.classifier-option {
  padding: 6px 12px;
  border: 1px solid var(--pm-field-border);
  background: var(--pm-field-bg);
  color: var(--pm-text);
  cursor: pointer;
}

.classifier-option:disabled {
  cursor: default;
  opacity: 0.6;
}

.classifier-option.selected {
  border-color: var(--pm-points);
  background: var(--pm-points-bg);
  font-weight: 600;
}

/* ===================================
   PREDICCIONES — selector de día + tarjeta
   Variables de tema (S15-T1: claro por default, "Día de partido";
   oscuro "Noche de estadio" detrás de [data-theme="dark"] abajo,
   valores preservados tal cual estaban antes de la reestructura).
   --pm-accent / --pm-accent-contrast quedan fuera de esta reestructura
   a propósito: son el eje Nivel 2 (kit de identidad por-torneo,
   SkinResolver), no el modo claro/oscuro — ver
   docs/superpowers/specs/2026-07-17-light-mode-design.md ("El acento
   por-torneo").
   Corrección (S15 polish, 2026-07-18, ítem 1): el default SIN skin
   (SkinRegistry::shell(), no-op — ver ese archivo) leía este --pm-accent
   directamente, así que "Mis pollas"/dashboard mostraba verde brillante
   en claro en vez de la marca. El claro pasa a Petróleo (con
   accent-contrast blanco); el oscuro pasa a declarar explícitamente su
   valor de siempre (antes lo heredaba por cascada al no existir un
   segundo valor) para que NO cambie con este ajuste. Los 5 skins
   (SkinRegistry) siguen intactos — solo afecta al pool sin skin. */
:root {
  --pm-card-bg: #FFFFFF;
  --pm-card-border: #E6DDCA;
  --pm-card-shadow: 0 1px 3px rgba(41, 32, 16, 0.10);
  --pm-text: #14262F;
  --pm-text-muted: #55636B;
  /* Retokenización (theme-retokenization): el sistema separa el acento
     en dos roles porque el lima brillante (#C6F24E) NO sirve como TEXTO
     fino sobre el papel cálido (queda verde-oliva, falla AA — rechazado).
     --pm-accent = acento de TEXTO/línea/anillo: teal petróleo en claro
     (AA sobre papel), lima en oscuro (donde el lima sí funciona como
     texto). --pm-accent-fill = relleno de marca (lima en AMBOS modos)
     para botones/píldoras/marcadores, con --pm-accent-contrast (tinta
     petróleo casi negra) encima. Ver .superpowers/sdd/part7-report.md. */
  --pm-accent: #127A8A;
  --pm-accent-fill: #C6F24E;
  --pm-accent-contrast: #0D2027;
  /* Overlay circular sobre media (flechas de carrusel slider/champ y
     scrims de caption): petróleo profundo translúcido, igual en ambos
     modos porque siempre va sobre imágenes. */
  --pm-media-btn-bg: rgba(12, 24, 38, 0.55);
  --pm-media-btn-bg-hover: rgba(12, 24, 38, 0.82);
  /* Notification badge (S28): en CLARO --pm-accent (#1F6F8B) es idéntico al
     --pm-chrome-bg del header, así que un badge accent-sobre-chrome se funde.
     Se le da color propio que contraste con el header en cada tema: acá
     píldora blanca con número petróleo (pop sobre el teal). */
  --pm-badge-bg: #FFFFFF;
  --pm-badge-text: #1F6F8B;
  --pm-field-border: #DDD3BD;
  --pm-field-bg: #FFFFFF;
  --pm-field-disabled-bg: #F0E8D8;
  --pm-surface-2: #F0E8D8;
  /* S126 bug fix: --pm-notice-bg backs actionable info panels (the Camino
     a la Final home card's alert/info-note). It must NOT equal
     --pm-field-disabled-bg — in light mode --pm-surface-2 coincidentally
     matched that exact tan, so an OPEN, actionable Camino card read as
     "disabled" (same swatch a real disabled form field uses). A soft teal
     tint off --pm-accent reads as "informational" instead. Dark mode never
     had this collision (--pm-field-disabled-bg there equals --pm-card-bg,
     invisible against the card, while --pm-surface-2 is a distinct navy),
     so it keeps reusing --pm-surface-2 unchanged below. */
  --pm-notice-bg: rgba(18, 122, 138, 0.08);
  --pm-points: #0E8A44;
  --pm-points-bg: rgba(18, 161, 80, 0.10);
  /* --pm-hint es el rol "texto" del amber de alerta (oscurecido en
     claro para legibilidad sobre fondo claro); --color-warning arriba
     es el rol "relleno/borde", constante entre modos. */
  --pm-hint: #C77E00;
  --pm-hint-bg: rgba(255, 183, 3, 0.12);
  /* Ranking movement (S3-T7): arrow up / down / no-change line. */
  --pm-move-up: #0E8A44;
  --pm-move-down: #D62839;
  --pm-move-same: #2563EB;
  --pm-row-me-bg: rgba(37, 99, 235, 0.09);
  /* Unified action-button system (Task 2, S15): hover for .btn-primary
     (darkens petróleo in light mode) + the danger variant for destructive
     actions (Salir de la polla, remover/rechazar miembro). */
  /* Hover del relleno lima (botones primarios): lima más profundo para
     que el hover se lea sobre el papel (aclarar el lima no se notaría). */
  --pm-accent-hover: #9EE037;
  --pm-danger: #C23B45;
  --pm-danger-hover: #A32F38;
  --pm-danger-ink: #FFFFFF;
}

:root[data-theme="dark"] {
  --pm-card-bg: #16293B;
  --pm-card-border: rgba(255, 255, 255, 0.09);
  --pm-card-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  --pm-text: #E9F0F6;
  --pm-text-muted: #8399AD;
  /* Declarado explícito (antes se heredaba por cascada de :root, único
     valor para ambos modos) para que el fix del default en claro de
     arriba no arrastre el oscuro con él. Petróleo (misma familia que
     el claro #1F6F8B) en lugar del verde anterior (S15-T…, consolidación
     petróleo): mantiene legibilidad de los CTA primarios sobre el fondo
     navy del chrome oscuro. */
  /* Oscuro (el tema querido): lima como acento de texto Y de relleno —
     funciona en todos lados sobre el petróleo profundo. --pm-accent
     (texto) y --pm-accent-fill (relleno) coinciden en lima aquí; solo
     divergen en claro. La tinta sobre el relleno lima es petróleo casi
     negro. */
  --pm-accent: #C6F24E;
  --pm-accent-fill: #C6F24E;
  --pm-accent-contrast: #16330A;
  /* Notification badge (S28): píldora lima con número tinta sobre el
     chrome petróleo profundo (#0A1420) — pop máximo. */
  --pm-badge-bg: #C6F24E;
  --pm-badge-text: #16330A;
  --pm-field-border: #2A4056;
  --pm-field-bg: #0D1C2B;
  --pm-field-disabled-bg: #16293B;
  --pm-surface-2: #1C3247;
  /* No collision here (see :root comment) — the existing navy already
     reads as "informational", not "disabled". */
  --pm-notice-bg: var(--pm-surface-2);
  --pm-points: #4BC482;
  --pm-points-bg: rgba(75, 196, 130, 0.14);
  --pm-hint: #FFC24B;
  --pm-move-up: #4BC482;
  --pm-move-down: #F87171;
  --pm-move-same: #60A5FA;
  --pm-row-me-bg: rgba(96, 165, 250, 0.10);
  /* Dark-mode pair of the button-system tokens declared in :root above
     (Task 2, S15): hover lightens instead of darkening, danger stays a
     legible rose-red on the navy chrome. */
  --pm-accent-hover: #D4FF5E;
  --pm-danger: #F0737C;
  --pm-danger-hover: #F58A92;
  --pm-danger-ink: #2A0A0D;
}

/* Tira de fichas de día con flechas + drag-to-scroll. */
.day-chips-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: var(--space-md);
}
.day-chips-arrow {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border: 1px solid var(--pm-card-border);
  background: var(--pm-card-bg);
  color: var(--pm-text);
  border-radius: 999px;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
}
.day-chips {
  display: flex;
  gap: var(--space-sm);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-sm) 2px;
  scrollbar-width: none;
  cursor: grab;
  user-select: none;
}
.day-chips:active {
  cursor: grabbing;
}
.day-chips::-webkit-scrollbar {
  display: none;
}
.day-chip {
  flex: 0 0 auto;
  scroll-snap-align: start;
  padding: 8px 14px;
  border: 1px solid var(--pm-field-border);
  border-radius: 999px;
  background: var(--pm-card-bg);
  color: var(--pm-text-muted);
  font-size: 14px;
  white-space: nowrap;
  cursor: pointer;
  text-transform: capitalize;
}
.day-chip.selected {
  background: var(--pm-accent-fill);
  border-color: var(--pm-accent);
  color: var(--pm-accent-contrast);
  font-weight: 600;
}

/* Grid responsive fluido: 1 col en celular, 2 en tablet, 3 en desktop.
   `minmax(min(100%, 300px), 1fr)` instead of a bare `minmax(300px, 1fr)`
   (defense-in-depth, overflow audit 2026-07-19): a hard 300px minimum
   track can never shrink below 300px even when a single column is
   narrower than that (very narrow viewports, or this grid nested inside
   a tighter container in the future) — `min(100%, 300px)` lets that one
   column shrink down to the container's own 100% instead of forcing
   overflow. Not the cause of the Predicciones overflow bug (that was
   `.app-content`'s own sizing, see MAIN CONTENT above) but the standard
   safe form of this pattern regardless. */
.matches-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: var(--space-md);
}

/* Tarjeta. */
.match-card {
  border: 1px solid var(--pm-card-border);
  border-left: 4px solid var(--pm-accent);
  border-radius: 12px;
  padding: var(--space-md);
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* 4 estados de tarjeta (S14 B4, Predicción restyle) — color del borde
   izquierdo por estado, calculado client-side por getCardState() en
   predictions.js. "closed" es la breve ventana bloqueada-pero-todavía-
   no-en-vivo entre el cierre y que el partido arranque: NEUTRA (gris),
   no ámbar — a diferencia de "closing" (todavía editable, urgente),
   "closed" ya no tiene nada accionable, solo espera al kickoff. */
.match-card.card-state-open {
  border-left-color: var(--pm-accent);
}
.match-card.card-state-closing {
  border-left-color: var(--pm-hint);
}
.match-card.card-state-closed {
  border-left-color: var(--pm-text-muted);
}
/* Pending first leg (S133): a vuelta whose ida hasn't finished — its
   prediction isn't open yet. Amber "on hold" accent (same --pm-hint token
   as the pre-lock hints), distinct from closed's neutral gray and open's
   accent, and legible in light + dark. */
.match-card.card-state-pending {
  border-left-color: var(--pm-hint);
}
.match-card.card-state-live {
  border-left-color: var(--color-danger);
}
/* Suspended = live match paused: score visible + locked. Amber (like the
   "attention/hold" hint) sets it apart from live's urgent red and closed's
   neutral gray. */
.match-card.card-state-suspended {
  border-left-color: var(--pm-hint);
}
.match-card.card-state-finished {
  border-left-color: var(--pm-card-border);
  opacity: 0.82;
}

/* Bonus tooltip escape (S42): a finished card's opacity (<1) creates a
   stacking context that would trap the footer chip's tooltip behind the
   next card in DOM order. Raise the whole card above its siblings while
   it's hovered or holds focus (tap on touch), so the tooltip paints over
   the grid gap and the neighbour. position:relative is layout-neutral for
   a grid item; z-index only applies on interaction. */
.match-card { position: relative; }
.match-card:hover,
.match-card:focus-within { z-index: 5; }

/* Single-card contexts (minigame, home "Next match"): capped width +
   denser spacing so elements don't look lost on a wide viewport
   (minigame card redesign, 2026-07-24). Additive modifier only — never
   applied inside .matches-grid (the predictions page, the ONLY .matches-
   grid usage in the codebase), so the grid's own sizing is untouched. */
.match-card--compact {
  max-width: 440px;
  margin-inline: auto;
  padding: var(--space-sm) var(--space-md);
  gap: 6px;
}

/* 1. Cabecera — 2 filas FIJAS (S16 header consistency fix).
   Antes .pmatch-head era un único flex-wrap container: fecha, pill y
   "Ver predicciones" competían por el mismo renglón y un pill largo
   ("Finalizado", "Cierra pronto") empujaba el link a una 2da línea
   mientras uno corto ("Cerrada") no — la cabecera terminaba con altura
   distinta según el estado y .pmatch-teams arrancaba a distinta altura
   entre tarjetas vecinas en la grilla. Con 2 filas explícitas
   (ronda+fecha / pill+link) cada fila tiene su propio par space-between
   y ninguna reordena a la otra: la cabecera mide siempre 2 líneas. */
.pmatch-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--pm-text-muted);
}
.pmatch-head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.pmatch-round {
  /* Ellipsis for genuinely long phase/group names — the time never
     shrinks (flex: 0 0 auto below), so all the give has to come from
     here. Same truncation pattern .team-name uses. */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pmatch-time {
  flex: 0 0 auto;
  white-space: nowrap;
}
.pmatch-head-status {
  display: flex;
  align-items: center;
  /* Wrapping happens ONLY inside this inner group (pill / calculating /
     error badge), never on .pmatch-head-row itself — so even in the
     rare case of two badges at once, it grows this row alone and never
     reflows "Ver predicciones" onto a 3rd line. */
  flex-wrap: wrap;
  flex: 1 1 auto;
  min-width: 0;
  gap: 4px 8px;
}
.pmatch-detail-link {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Ficha de estado (status pill) — mismo mapeo de color que el borde
   izquierdo de la tarjeta. */
.status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.status-pill.card-state-open {
  background: var(--pm-points-bg);
  color: var(--pm-accent);
}
.status-pill.card-state-closing {
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
}
.status-pill.card-state-closed {
  background: var(--pm-surface-2);
  color: var(--pm-text-muted);
}
.status-pill.card-state-pending {
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
}
.status-pill.card-state-live {
  background: var(--color-danger-bg);
  color: var(--color-danger);
}
.status-pill.card-state-suspended {
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
}
.status-pill.card-state-finished {
  background: var(--pm-surface-2);
  color: var(--pm-text-muted);
}

/* 2. Fila del partido: equipos simétricos + marcadores al centro. */
.pmatch-teams {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
}
.pmatch-team {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.pmatch-team--home {
  justify-content: flex-end;
}
.pmatch-team--away {
  justify-content: flex-start;
}
.team-flag {
  width: 26px;
  height: 18px;
  object-fit: cover;
  border-radius: 2px;
  flex: 0 0 auto;
}
/* Club crest: square slot, contained (never cropped) — unlike the
   rectangular .team-flag it sits beside for national teams. Fed a WebP
   derivative (crest_48/96) sized for this slot, so no oversized PNG ships. */
.team-crest {
  width: 22px;
  height: 22px;
  object-fit: contain;
  flex: 0 0 auto;
}
.team-name {
  font-size: 13px;
  color: var(--pm-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pmatch-scores {
  display: flex;
  align-items: center;
  gap: 6px;
}
.score-field {
  width: 44px;
  height: 40px;
  text-align: center;
  font-family: 'ScoreboardCondensed', var(--font-body);
  /* S14 B4 (Predicción restyle): "a touch smaller" than the previous
     20px, per design decision — still comfortably tappable. */
  font-size: 18px;
  font-weight: 700;
  border: 1px solid var(--pm-field-border);
  border-radius: 8px;
  background: var(--pm-field-bg);
  color: var(--pm-text);
  /* Sin spinners nativos: la app tiene su propio numpad
     (inputmode="none" + pickNumber()), las flechas solo suman ruido. */
  appearance: textfield;
  -moz-appearance: textfield;
}
.score-field::-webkit-outer-spin-button,
.score-field::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* Bloqueado: se lee como texto plano, no como un control muerto. */
.score-field:disabled {
  background: transparent;
  border-color: transparent;
  color: var(--pm-text);
}
.score-separator {
  font-weight: 700;
  color: var(--pm-text-muted);
}

/* 3. Fila de números (propio espacio). */
.pmatch-numpad {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px;
  padding: 6px;
  background: var(--pm-surface-2);
  border-radius: 8px;
}
.numpad-btn {
  width: 34px;
  height: 34px;
  border: 1px solid var(--pm-field-border);
  background: var(--pm-card-bg);
  color: var(--pm-text);
  border-radius: 6px;
  font-size: 15px;
  cursor: pointer;
}

/* 5. Fila de mensajes. */
.pmatch-messages {
  min-height: 18px;
  text-align: center;
  font-size: 12px;
}
.msg-hint {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
}
.msg-save {
  color: var(--pm-points);
}
.msg-lock {
  color: var(--pm-text-muted);
}
/* "No ingresaste predicción para este partido" (S65): a gentle, muted
   warning on past/locked cards where the member never predicted — small,
   amber-tinted like the other pre-lock hints, deliberately NOT an alarming
   error color. */
.msg-no-prediction {
  display: inline-block;
  color: var(--pm-hint);
}
/* "Disponible cuando finalice el partido de ida" (S133): a vuelta whose ida
   hasn't finished. Amber-tinted like the other on-hold hints, deliberately
   NOT the muted "Predicciones cerradas" treatment — this is "not open yet",
   not "closed". */
.msg-pending-firstleg {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
}
/* "Cierra en"/"Sin predicción" (S14 B4) — visible on every pre-lock
   card; tinted amber to match the border/pill once the card enters the
   "closing" state (closing-soon OR no prediction yet). */
.msg-countdown {
  color: var(--pm-text-muted);
}
.match-card.card-state-closing .msg-countdown {
  color: var(--pm-hint);
}

/* 6. Footer (en vivo / finalizado). */
.pmatch-footer {
  border-top: 1px solid var(--pm-card-border);
  padding-top: var(--space-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.pmatch-footer-label {
  font-size: 12px;
  color: var(--pm-text-muted);
}
.pmatch-footer-score {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 22px;
  font-weight: 800;
  color: var(--pm-text);
}
/* Two-legged tie context (S5): first-leg / aggregate score under the main
   scoreline. Small and muted auxiliary weight, never competing with the
   primary figure. */
.pmatch-tie {
  text-align: center;
  font-size: 13px;
  color: var(--pm-text-muted);
}
.points-projected {
  font-size: 13px;
  font-style: italic;
  color: var(--pm-text-muted);
}
.points-earned {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--pm-points-bg);
  color: var(--pm-points);
  font-size: 13px;
}
.points-earned strong {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 17px;
  font-weight: 800;
}

/* % de inclinación (S7-T4) — obra gris: solo layout y colores
   funcionales, sin decoración (el pase de diseño integral es S9-T2).
   Reusa la paleta move-up/same/down (verde/azul/rojo) ya establecida
   para "arriba/igual/abajo" en el ranking, aplicada acá a
   local/empate/visitante. */
.pmatch-inclination {
  margin-top: var(--space-sm);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--pm-card-border);
}
.inclination-label {
  display: block;
  font-size: 12px;
  color: var(--pm-text-muted);
  margin-bottom: 4px;
}
.inclination-bar {
  display: flex;
  width: 100%;
  height: 5px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--pm-surface-2);
}
.inclination-segment--home {
  background: var(--pmatch-inclination-home);
}
.inclination-segment--draw {
  background: var(--pmatch-inclination-draw);
}
.inclination-segment--away {
  background: var(--pmatch-inclination-away);
}
.inclination-legend {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--pm-text-muted);
  margin-top: 2px;
}

/* ===================================
   MATCH-CARD ZONES (S68 card redesign)
   The card is a fixed 5-zone skeleton — header, prediction, tie context,
   messages/wager, result — with coordinated collapse: a zone with no
   content contributes no gap (flex `gap` skips display:none children, and
   .card-state-finished drops the messages min-height so it collapses flush).
   A single hairline divides the prediction from the result (owned by
   .pmatch-zone--result); the inclination keeps its own. Before this,
   .pmatch-footer AND .pmatch-inclination each drew a border-top, stacking
   two rules on a finished card.
   =================================== */
.pmatch-zone {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.pmatch-zone--result {
  border-top: 1px solid var(--pm-card-border);
  padding-top: var(--space-sm);
}
/* Uppercase micro-label naming a zone on a card that already has a result
   (hidden on open cards — there is nothing to contrast against yet). */
.zone-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--pm-text-muted);
}
/* Finished cards carry no live/closing hint, so drop the messages zone's
   reserved min-height and let it collapse flush to the separator. */
.match-card.card-state-finished .pmatch-messages {
  min-height: 0;
}

/* Your predicted classifier, read-only once locked: neutral while
   unresolved, accent (teal/lime) + ✓ when it matches who really advanced,
   danger (red) + ✗ when it doesn't. Same win/lose palette as the bonus
   icons — the two states must be unmistakable. */
.classifier-readout {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 13px;
}
.classifier-readout .cr-label {
  color: var(--pm-text-muted);
}
.classifier-readout .cr-team {
  font-weight: 700;
  color: var(--pm-text);
}
.classifier-readout .cr-derived {
  font-size: 11px;
  color: var(--pm-text-muted);
}
.classifier-readout .cr-mark {
  font-weight: 800;
}
.classifier-readout--correct .cr-team,
.classifier-readout--correct .cr-mark {
  color: var(--pm-accent);
}
.classifier-readout--wrong .cr-team,
.classifier-readout--wrong .cr-mark {
  color: var(--pm-danger);
}

/* Result zone rows: the real final + aggregate, who advanced + applied
   bonuses, and the points — each a centered line. */
.result-line {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
}
.result-final {
  font-size: 13px;
  color: var(--pm-text-muted);
}
.result-final .rf-score {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 18px;
  font-weight: 800;
  color: var(--pm-text);
  margin-left: 4px;
}
.result-agg {
  font-size: 13px;
  color: var(--pm-text-muted);
}
.result-advances {
  font-size: 13px;
  color: var(--pm-text);
}
.result-advances strong {
  font-weight: 700;
}

/* ===================================
   RANKING (S3-T7) — banner + tabla
   S14 B4 restyle: top-3 podium + sticky "Tu posición" + switch toggle.
   =================================== */

/* Top-3 podium: leader centered and elevated (markup renders rows in
   ranking order 1-2-3; CSS `order` re-seats them as 2-1-3).
   align-items:flex-end drops the side slots to the baseline while the
   leader's extra padding + negative top margin lift it. */
.ranking-podium {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}
.ranking-podium-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex: 1 1 0;
  min-width: 0;
  max-width: 150px;
  padding: var(--space-md) var(--space-sm);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  text-align: center;
}
.ranking-podium-slot--2 {
  order: 1;
}
.ranking-podium-slot--1 {
  order: 2;
  max-width: 170px;
  padding-top: var(--space-md);
  padding-bottom: var(--space-lg);
  margin-top: calc(-1 * var(--space-md));
  /* Winner slot: de-greened — teal/petrol glow, not the old lime tint
     (no lime as a background fill anywhere). The larger size + negative
     margin already single it out; the soft teal wash adds the "featured"
     touch without green. */
  border-color: var(--pm-card-border);
  background:
    linear-gradient(160deg, var(--pm-atmospheric-glow), transparent 62%),
    var(--pm-card-bg);
}
.ranking-podium-slot--3 {
  order: 3;
}
/* The viewer's own podium slot gets the same "me" tint as their table
   row — subtle, the leader accent (if both) still dominates. */
.ranking-podium-slot--me:not(.ranking-podium-slot--1) {
  background: var(--pm-row-me-bg);
}
.ranking-podium-leader-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--pm-accent);
}
.ranking-podium-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 600;
  color: var(--pm-text);
}
.ranking-podium-points {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 800;
  font-size: 24px;
  line-height: 1;
  color: var(--pm-accent);
}
.ranking-podium-slot--1 .ranking-podium-points {
  font-size: 30px;
}
.ranking-podium-meta {
  font-size: 11px;
  color: var(--pm-text-muted);
  white-space: nowrap;
}

/* Projected (live) table toggle (S6-T7; S14 B4 restyle): the same
   checkbox Alpine logic, drawn as a pill switch — accent when on. */
.ranking-toggle-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--space-sm) var(--space-md);
}
.ranking-toggle-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  cursor: pointer;
}
.ranking-toggle-label input[type='checkbox'] {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 38px;
  height: 22px;
  margin: 0;
  border: 1px solid var(--pm-field-border);
  border-radius: 999px;
  background: var(--pm-surface-2);
  position: relative;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.ranking-toggle-label input[type='checkbox']::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--pm-text-muted);
  transition: transform 0.15s ease, background 0.15s ease;
}
.ranking-toggle-label input[type='checkbox']:checked {
  /* ON = solid lime track (lime used as a solid control color, like a
     button), not a lime-tinted background. */
  background: var(--pm-accent-fill);
  border-color: var(--pm-accent-fill);
}
.ranking-toggle-label input[type='checkbox']:checked::after {
  transform: translateX(16px);
  /* Dark-ink knob so it reads against the solid lime track. */
  background: var(--pm-accent-contrast);
}
/* "Projected" banner (S14 B4 restyle): hint-tinted pill, same token
   family as the projection columns it explains. */
.ranking-toggle-disclaimer {
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
  align-self: flex-start;
}
.ranking-settling-label {
  color: var(--pm-move-down);
  font-weight: 600;
}

/* Ranking view switch pill (ranking/live split, Tajada 1): segmented
   control navigating between the official table and the live view. */
.ranking-view-switch {
  display: inline-flex;
  gap: 0.25rem;
  padding: 0.25rem;
  margin-bottom: 0.75rem;
  border: 1px solid var(--pm-card-border);
  border-radius: 999px;
  background: var(--pm-field-bg);
}

.ranking-view-switch-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.9rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--pm-text-muted);
  text-decoration: none;
}

.ranking-view-switch-item.is-active {
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
}

/* Settling chip: visible while scoring runs for this pool. */
.ranking-settling-chip {
  display: inline-block;
  margin: 0 0 0.75rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  font-size: 0.8rem;
  color: var(--pm-hint);
  border: 1px solid var(--pm-card-border);
  background: var(--pm-card-bg);
}

/* Reload CTA banner: replaces the chip when scoring completes. */
/* Toned down from a full-width solid-lime slab (too loud, and lime as a
   large background breaks the "lime only as a solid control color" rule):
   now a calm neutral banner with an accent border + accent text and a ↻
   glyph — still obviously a click-to-refresh CTA, no shouting. */
.ranking-reload-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin: 0 0 0.75rem;
  padding: 0.45rem 1rem;
  border: 1px solid var(--pm-accent);
  border-radius: 8px;
  /* S141 light-theme fix (same class as S126 Camino): this banner is an
     ACTIONABLE "toca para actualizar" control, but --pm-surface-2 in light
     equals --pm-field-disabled-bg exactly (#F0E8D8), so its fill was the
     literal disabled-field swatch. --pm-notice-bg (soft teal tint in light,
     unchanged navy in dark) reads as active/informational instead. */
  background: var(--pm-notice-bg);
  color: var(--pm-accent);
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.ranking-reload-banner::before {
  content: '↻';
  font-size: 1rem;
  line-height: 1;
}
.ranking-reload-banner:hover {
  background: var(--pm-card-bg);
}

/* Live view exit notice (~2s before navigating back). */
.ranking-live-exit-notice {
  margin: 0 0 0.75rem;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--pm-card-border);
  border-radius: 8px;
  background: var(--pm-card-bg);
  color: var(--pm-text-muted);
  font-size: 0.85rem;
}

.ranking-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
}
.ranking-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  color: var(--pm-text);
}
.ranking-table th {
  text-align: left;
  font-size: 12px;
  font-weight: 600;
  color: var(--pm-text-muted);
  padding: 10px 8px;
  border-bottom: 1px solid var(--pm-card-border);
  background: var(--pm-surface-2);
  white-space: nowrap;
}
.ranking-table td {
  padding: 10px 8px;
  border-bottom: 1px solid var(--pm-card-border);
}
.ranking-table tbody tr:last-child td {
  border-bottom: none;
}
.ranking-row--me td {
  background: var(--pm-row-me-bg);
  font-weight: 600;
}
.ranking-table .col-pos {
  width: 36px;
  text-align: center;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
}
.ranking-table .col-move {
  width: 44px;
  text-align: center;
}
.ranking-table .col-num {
  text-align: center;
  white-space: nowrap;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 600;
}
.ranking-points {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
}

/* Sumado/Proyectado (S7 ranking-live-table settling fix): tinte distinto
   (mismo token --pm-hint* usado para avisos temporales en otras partes
   de la UI) para que se note a simple vista que son columnas de
   proyección, no datos oficiales — solo existen mientras "Live
   projected table" está activo. */
.ranking-col-projection {
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
}
.ranking-table th.ranking-col-projection {
  background: var(--pm-hint-bg);
}

/* Mobile no-horizontal-scroll fix (Task 3, fix 8): headers swap to
   1-2 letter abbreviations, the player column gets a hard width cap
   with ellipsis truncation, and cell padding/font-size shrink — only
   below the same 767px breakpoint the rest of the app already treats
   as "mobile" (see .app-wrapper above), so desktop keeps the original
   full-word headers and unconstrained layout untouched. The legend
   below the table follows the same breakpoint since it only makes
   sense once the abbreviated form is actually visible. */
.ranking-table .col-label-abbr {
  display: none;
}
.ranking-legend {
  display: none;
}
/* Tiebreaker ¹²³ (S14 B4): superscript markers on the sorted columns'
   headers + the always-visible footnote under the table mapping them
   to the sort order (Puntos → Exactos → Promedio). */
.ranking-tiebreak-sup {
  margin-left: 1px;
  font-size: 9px;
  font-weight: 400;
  color: var(--pm-text-muted);
}
.ranking-tiebreaker-note {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  font-size: 11px;
  color: var(--pm-text-muted);
  border-top: 1px solid var(--pm-card-border);
}
.ranking-player-cell {
  min-width: 0;
}
.ranking-player-link {
  display: inline-block;
  min-width: 0;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}
@media (max-width: 767px) {
  .ranking-table th,
  .ranking-table td {
    padding: 8px 4px;
  }
  .ranking-table th {
    font-size: 11px;
  }
  .ranking-table .col-label-full {
    display: none;
  }
  .ranking-table .col-label-abbr {
    display: inline;
  }
  /* S126 bug fix: `max-width` alone doesn't constrain a column in the
     table's default auto layout — the browser was still handing #/Jugador
     spare width the score columns (Ex/Gn/Jd/Pr/Pts) needed, squeezing them
     down to ~26-35px each. Pairing max-width with an explicit `width` on
     both columns makes the auto-layout algorithm actually honor the cap,
     and shrinking # (36px desktop → 22px here, digits only need room for
     up to 3 chars) frees more room for the data columns. */
  .ranking-table .col-pos {
    width: 22px;
    font-size: 12px;
  }
  .ranking-table .col-player {
    width: 84px;
    max-width: 84px;
  }
  .ranking-player-link {
    max-width: 84px;
  }
  /* The player cell is a flex row (avatar + gap + name) — on a narrow
     column, the avatar+gap overhead was eating most of the 84px budget
     (28px avatar + 8px gap = 36px), leaving so little for the name that
     even "admin" truncated to "a…". Shrinking the avatar reclaims that
     space for actual name characters without growing the column (and
     without touching the avatar anywhere else in the app). */
  .ranking-player-cell {
    gap: 6px;
  }
  .ranking-player-cell .pm-avatar {
    width: 20px;
    height: 20px;
  }
  .ranking-player-cell .pm-avatar--initials {
    font-size: 9px;
  }
  .ranking-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    padding: var(--space-sm) var(--space-md);
    font-size: 11px;
    color: var(--pm-text-muted);
    border-top: 1px solid var(--pm-card-border);
  }
  .ranking-legend-item strong {
    color: var(--pm-text);
  }
}

/* Flecha de movimiento: verde sube, roja baja, línea azul sin cambio. */
.move {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 13px;
  line-height: 1;
}
.move small {
  font-size: 11px;
  font-weight: 600;
}
.move--up {
  color: var(--pm-move-up);
}
.move--down {
  color: var(--pm-move-down);
}
.move--same {
  color: var(--pm-move-same);
}

/* Public prize block (S27-F2-T4): prize total + per-person amount + the
   per-position distribution, tie-pooling rule already applied server-side
   by PrizeDistribution. Public — identical for every viewer, sits above
   the view-switch pill/podium. Card language matches .ranking-sticky-me
   below (border + shadow) rather than the owner-only .pm-payment-summary
   strip on the member-management screen, since this lives on the
   member-facing ranking page. Hidden entirely by the template when the
   pool has no entryAmount/prizeSplit set. */
.ranking-prize-block {
  margin-bottom: var(--space-sm);
  padding: 12px 16px;
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
}
.ranking-prize-header {
  margin: 0;
  color: var(--pm-text);
  font-size: 14px;
  font-weight: 600;
}
.ranking-prize-header strong {
  color: var(--pm-accent);
}
.ranking-prize-per-person {
  margin: 2px 0 8px;
  color: var(--pm-text-muted);
  font-size: 13px;
}
.ranking-prize-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.ranking-prize-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
}
.ranking-prize-position {
  min-width: 64px;
  color: var(--pm-text);
  font-weight: 600;
}
.ranking-prize-pct {
  color: var(--pm-text-muted);
}
.ranking-prize-amount {
  margin-left: auto;
  color: var(--pm-points);
  font-weight: 700;
}

/* Sticky "Tu posición" button (S14 B4): replaces the old paginated-only
   .ranking-my-position shortcut. Sticks below the (non-fixed) page
   header while the member scrolls through the table; opaque card
   background + shadow so table rows never bleed through. */
.ranking-sticky-me {
  position: sticky;
  top: var(--space-sm);
  z-index: 20;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  width: 100%;
  padding: 10px 16px;
  margin-bottom: var(--space-sm);
  border: 1px solid color-mix(in srgb, var(--pm-accent) 35%, var(--pm-card-border));
  border-radius: 999px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  color: var(--pm-text);
  font-size: 13px;
  cursor: pointer;
  text-align: left;
}
.ranking-sticky-me:hover {
  background: var(--pm-surface-2);
}
.ranking-sticky-me-pos {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 800;
  font-size: 18px;
  color: var(--pm-accent);
}
.ranking-sticky-me-label {
  font-weight: 600;
}
.ranking-sticky-me-move {
  font-size: 12px;
}
.ranking-sticky-me-points {
  margin-left: auto;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: 15px;
}
.ranking-sticky-me-cta {
  color: var(--pm-accent);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.ranking-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
}
.ranking-page-btn {
  padding: 6px 12px;
  border: 1px solid var(--pm-field-border);
  border-radius: 6px;
  background: var(--pm-card-bg);
  color: var(--pm-text);
  cursor: pointer;
  font-size: 13px;
}
.ranking-page-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.ranking-page-status {
  font-size: 13px;
  color: var(--pm-text-muted);
}
.ranking-row--me-flash td {
  background: var(--pm-hint-bg);
  transition: background 1.5s ease;
}

/* Bloque de marcadores en vivo (S6-T6) — obra gris: solo layout y
   estados funcionales, sin decoración (el pase de diseño integral es
   S8-T2). Oculto por defecto vía x-show/x-cloak en el twig. */
.ranking-live {
  margin-bottom: var(--space-md);
}
.ranking-live-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-sm);
}
.ranking-live-card {
  border: 1px solid var(--pm-card-border);
  border-left: 4px solid var(--pm-accent);
  border-radius: 8px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  padding: var(--space-sm) var(--space-md);
}
.rlive-head {
  margin-bottom: 4px;
}
.rlive-head .live-badge {
  margin-inline-start: 0;
}
.rlive-teams {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  font-weight: 600;
}
.rlive-score {
  font-family: 'ScoreboardCondensed', var(--font-body);
  color: var(--pm-accent);
  font-size: 18px;
  font-weight: 800;
}
.rlive-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 4px;
  font-size: 13px;
  color: var(--pm-text-muted);
}
.rlive-projected-points {
  color: var(--pm-points);
  background: var(--pm-points-bg);
  border-radius: 4px;
  padding: 2px 6px;
  align-self: flex-start;
  font-weight: 600;
}

/* Preview público de polla (S5-T2). Meta line ("Public · N members") —
   the CTA button/link classes this section used to define were replaced
   by the shared .card/.btn atoms in the "NON-MEMBER STATES" section
   below (B3 restyle). */
.pool-preview-meta {
  color: var(--pm-text-muted);
  font-size: 14px;
}

/* Ranking history comparison chart (ranking history chart plan,
   2026-07-09; split into inline top-3 + popup top-10+you, 2026-07-17) —
   obra gris: layout and legibility only, no decorative polish (the real
   design pass is a later sprint). */
.ranking-history-top {
  margin-bottom: var(--space-md);
}
.ranking-history-inline-loading,
.ranking-history-inline-error {
  color: var(--pm-text-muted);
  font-size: 13px;
}
.ranking-history-inline {
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-field-border);
  border-radius: 12px;
  padding: var(--space-md);
  /* Cap + center the whole chart block on wide screens (S26 fix #6,
     supersedes the "left-aligned, card stays full-width" call noted on
     .ranking-history-chart-wrap--inline below). At the ~1312px content
     column width the card used to stretch full-width, which read as an
     oversized empty block around a comparatively small chart. 780px
     keeps it in the same range as the inner canvas cap already in place
     and centers it via margin-inline so it doesn't lean left against a
     column of unused whitespace on the right. Only this block is capped
     — the ranking table and leader cards elsewhere on the page are
     untouched. */
  max-width: 780px;
  margin-inline: auto;
}
.ranking-history-inline-title {
  font-size: 15px;
  margin: 0 0 var(--space-sm);
  color: var(--pm-text);
}
.ranking-history-chart-wrap--inline {
  margin-top: var(--space-sm);
  /* No max-width of its own (S26 fix #6): the original ranking overflow/
     width audit (2026-07-19) capped this wrap at 820px because the
     .ranking-history-inline CARD still spanned the full ~1312px content
     column back then, and `renderInlineChart()` (ranking-history-
     chart.js) sizes the canvas from THIS element's clientWidth at render
     time — the wrap's own width is what mattered. Now that the card
     itself is capped + centered at 780px (see .ranking-history-inline
     above), the card's content box (~748px) already bounds this wrap
     more tightly than 820px ever did, so a separate cap here would be
     dead weight. The chart still shrinks correctly with the wrap on
     narrower/mobile screens either way. */
}
.ranking-history-chart-cta {
  margin-top: var(--space-sm);
  padding: 6px 12px;
  border: 1px solid var(--pm-field-border);
  border-radius: 6px;
  background: var(--pm-card-bg);
  color: var(--pm-text);
  cursor: pointer;
  font-size: 13px;
}
.ranking-history-chart-cta:hover {
  background: var(--pm-surface-2);
}

.ranking-history-modal-overlay {
  position: fixed;
  inset: 0;
  /* Mode-agnostic dimming scrim (S15-T1 audit): a black backdrop reads
     correctly behind a modal in both light and dark, left as a literal
     on purpose — not a leak. */
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: var(--space-md);
}
.ranking-history-modal {
  background: var(--pm-card-bg);
  border-radius: 12px;
  padding: var(--space-md);
  width: 100%;
  max-width: 640px;
  max-height: 90vh;
  overflow-y: auto;
  position: relative;
}
.ranking-history-modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  color: var(--pm-text-muted);
}

.ranking-history-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-bottom: var(--space-sm);
}
.ranking-history-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  color: var(--pm-text);
}
.ranking-history-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
}

.ranking-history-chart-wrap {
  overflow-x: auto;
}

/* ===================================
   SHARE POOL (Task 2): copy-link + QR modal
   Modal overlay/close pattern mirrors ranking-history-modal above.
   =================================== */

/* .pool-share-btn's shape/color now come from the unified .btn/.btn-primary
   system above (Task 2, S15) — it used to be a standalone gray outline
   button here, one of the 3 inconsistent styles the system replaced. */

.pool-share-modal-overlay {
  position: fixed;
  inset: 0;
  /* Mode-agnostic dimming scrim (S15-T1 audit) — see
     .ranking-history-modal-overlay above, same reasoning. */
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: var(--space-md);
}
.pool-share-modal {
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  padding: var(--space-md);
  width: 100%;
  max-width: 360px;
  max-height: 90vh;
  overflow-y: auto;
  position: relative;
  text-align: center;
}
.pool-share-modal h3 {
  margin: 0 0 var(--space-md);
  color: var(--pm-text);
}
.pool-share-modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  color: var(--pm-text-muted);
}

.pool-share-link-row {
  display: flex;
  gap: 8px;
  margin-bottom: var(--space-md);
}
.pool-share-link-input {
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--pm-field-border);
  border-radius: 6px;
  background: var(--pm-surface-2);
  color: var(--pm-text);
  font-size: 13px;
}
/* .pool-share-copy-btn was the REFERENCE for the unified .btn/.btn-primary
   system above (Task 2, S15) — its solid-petróleo look is now shared with
   every other primary action button. Its old hover (filter:brightness(1.05))
   was too subtle to read as an affordance; replaced by the system's
   background-darken hover. Only the layout-specific bit stays local. */
.pool-share-copy-btn {
  white-space: nowrap;
}

/* WhatsApp share — the highest-leverage invite action in LATAM. Uses the
   official WhatsApp green (mode-agnostic brand color, like the QR backing)
   so it reads as "this opens WhatsApp", not a generic app button. */
.pool-share-whatsapp-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-bottom: var(--space-md);
  padding: 11px 16px;
  border: none;
  border-radius: 8px;
  background: #25d366;
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}
.pool-share-whatsapp-btn:hover {
  background: #1ebe5a;
}
.pool-share-whatsapp-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

.pool-share-qr-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.pool-share-qr {
  width: 200px;
  height: 200px;
  /* Mode-agnostic (S15-T1 audit): a QR code needs a white backing for
     scanner contrast/quiet-zone regardless of theme — not a leak. */
  background: #fff;
  border-radius: 8px;
  padding: 8px;
}
.pool-share-qr-hint {
  margin: 0;
  color: var(--pm-text-muted);
  font-size: 13px;
}

/* ===================================
   POOL DETAIL BACK-NAV (S41 Problem 2)
   Shared by match-detail (.pmd-*) and user-detail (.pud-*) — the
   container itself (.pool-back-nav) is always in the server-rendered
   HTML; only its content (the link) is injected client-side by
   pool_core/nav_origin (js/nav-origin.js), see that file's docblock.
   =================================== */
.pool-back-nav:empty {
  display: none;
}
.pool-back-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-bottom: var(--space-sm);
  font-size: 13px;
  font-weight: 600;
  color: var(--pm-text-muted);
  text-decoration: none;
}
.pool-back-nav-link:hover,
.pool-back-nav-link:focus-visible {
  color: var(--pm-accent);
}
.pool-back-nav-link::before {
  content: '\2039';
  font-size: 17px;
  line-height: 1;
}

/* ===================================
   MATCH DETAIL PAGE (S10-T2, Capa 6; restyle S14 B4 Tablas Task 1)
   =================================== */

/* Header card (S14 B4): wraps round/kickoff + teams + the inclination
   bar in the same card recipe already used across the restyled internal
   views (.match-card, .pool-home-card, .ranking-table-wrap) — border +
   radius + surface bg + shadow — so match-detail's header reads as one
   more card in the same visual system instead of floating bare text. */
.pmd-header-card {
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  padding: var(--space-md);
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  margin-bottom: var(--space-md);
}
.pmd-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: var(--space-md);
  font-size: 12px;
  color: var(--pm-text-muted);
}
.pmd-teams {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
}
.pmd-team {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  color: var(--pm-text);
}
.pmd-vs {
  font-size: 13px;
  color: var(--pm-text-muted);
}
.pmd-real-score {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 20px;
  font-weight: 800;
  color: var(--pm-accent);
}
/* Two-legged tie aggregate (S5): global score + who classified, under the
   real scoreline. Auxiliary/muted (same weight as .pmd-vs), with the
   classifier name accented since it is the meaningful call-out. */
.pmd-aggregate {
  margin-top: 6px;
  text-align: center;
  font-size: 13px;
  color: var(--pm-text-muted);
}
.pmd-classifier {
  margin-left: 8px;
  color: var(--pm-accent);
  font-weight: 700;
}
.pmd-not-locked {
  padding: var(--space-md);
  color: var(--pm-text-muted);
  font-size: 14px;
}
.pmd-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
}
.pmd-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  color: var(--pm-text);
}
.pmd-table th {
  text-align: left;
  font-size: 12px;
  font-weight: 600;
  color: var(--pm-text-muted);
  padding: 10px 8px;
  border-bottom: 1px solid var(--pm-card-border);
  background: var(--pm-surface-2);
  white-space: nowrap;
}
.pmd-table td {
  padding: 10px 8px;
  border-bottom: 1px solid var(--pm-card-border);
}
.pmd-table tbody tr:last-child td {
  border-bottom: none;
}
.pmd-row--me td {
  background: var(--pm-row-me-bg);
  font-weight: 600;
}
.pmd-table .col-num {
  text-align: center;
  white-space: nowrap;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
}

/* Mobile tightening (S14 B4), same breakpoint/recipe as .ranking-table's
   own mobile rule — smaller padding/header font so the 4-column table
   doesn't feel bulkier than the already-restyled ranking table. */
@media (max-width: 767px) {
  .pmd-table th,
  .pmd-table td {
    padding: 8px 4px;
  }
  .pmd-table th {
    font-size: 11px;
  }
}

.pmd-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
}
.pmd-page-btn {
  padding: 6px 12px;
  border: 1px solid var(--pm-field-border);
  border-radius: 6px;
  background: var(--pm-card-bg);
  color: var(--pm-text);
  cursor: pointer;
  font-size: 13px;
}
.pmd-page-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.pmd-page-status {
  font-size: 13px;
  color: var(--pm-text-muted);
}
.pmd-bonus-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  background: var(--pm-hint-bg);
  color: var(--pm-text);
  font-size: 12px;
  margin-right: 4px;
}

/* Bonus badge states (S30-T8) — server-resolved per row.badges
   (ScoringService::resolveBadgeStates()): 'candidate' pre-finish (gray,
   a bonus this predictor could still win), 'won'/'neutral'/'lost' once
   the match finishes. All four modifiers ride --pm-* tokens already
   defined for both light and dark themes above, so no extra dark-mode
   override block is needed here. */
.pmd-bonus-badge--candidate {
  background: var(--pm-text-muted);
  color: var(--pm-accent-contrast);
  opacity: 0.7;
}

.pmd-bonus-badge--won {
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
}

.pmd-bonus-badge--neutral {
  background: var(--pm-hint-bg);
  color: var(--pm-text);
}

.pmd-bonus-badge--lost {
  background: var(--pm-danger);
  color: var(--pm-danger-ink);
  opacity: 0.7;
}

/* ── Bonus icon system (S42) ───────────────────────────────────────────
   One reusable icon component shared by the predictions cards, the
   per-match detail table, and the legends. The <symbol> sprite lives in
   @pool_mobile/bonus-icons-sprite.html.twig; each icon inherits its color
   from the state modifier via currentColor. Colors ride --pm-* tokens that
   are AA-safe as INK on both themes: --pm-accent (lime on dark / teal on
   light) for applied, --pm-danger for failed, --pm-text-muted for
   possible. Do NOT use --pm-accent-fill here — that's a fill tint, not
   legible as a thin icon stroke in light mode. */
.bonus-ico {
  width: 20px;
  height: 20px;
  display: inline-block;
  vertical-align: middle;
  flex: 0 0 auto;
  color: var(--pm-text-muted);
}
.bonus-ico--candidate { color: var(--pm-text-muted); opacity: 0.85; }
.bonus-ico--available { color: var(--pm-accent); }
.bonus-ico--won       { color: var(--pm-accent); }
.bonus-ico--missed    { color: var(--pm-hint); }
.bonus-ico--lost      { color: var(--pm-danger); }
.bonus-ico--neutral   { color: var(--pm-text-muted); opacity: 0.5; }

/* Icon "chip" wrapper — a tap target that carries a styled tooltip. */
.bonus-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
  margin-right: 2px;
  cursor: default;
}
/* Styled, tap-friendly tooltip (no native title needed). Shown on hover
   and on focus (tap moves focus on touch). */
.bonus-chip[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  background: var(--pm-tooltip-bg, #1c2b2b);
  color: var(--pm-tooltip-ink, #fff);
  font-size: 11px;
  line-height: 1.2;
  padding: 4px 8px;
  border-radius: 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: 30;
}
.bonus-chip:hover[data-tip]::after,
.bonus-chip:focus-visible[data-tip]::after,
.bonus-chip.is-tip-open[data-tip]::after { opacity: 1; }

/* Bonus legend (foot of the detail table + the predictions strip). */
.bonus-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
  padding: 10px 12px;
  margin-top: 8px;
  font-size: 12px;
  color: var(--pm-text-muted);
  background: var(--pm-hint-bg);
  border-radius: 10px;
}
.bonus-legend__item { display: inline-flex; align-items: center; gap: 6px; }
.bonus-legend__states { display: inline-flex; align-items: center; gap: 12px; }
.bonus-legend__state { display: inline-flex; align-items: center; gap: 5px; }
.bonus-legend__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
}
.bonus-legend__dot--posible  { background: var(--pm-text-muted); opacity: 0.85; }
.bonus-legend__dot--aplicado { background: var(--pm-accent); }
.bonus-legend__dot--nosedio  { background: var(--pm-hint); }
.bonus-legend__dot--fallado  { background: var(--pm-danger); }

/* Applied bonus icons in the finished match card footer (S42). */
.pmatch-bonus-icons {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}

/* Bonus convention strip (S42) — collapsible band under the day chips. */
.bonus-strip { margin: 8px 0 4px; }
.bonus-strip__toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  /* S141 light-theme fix: --pm-border was never defined, so this always
     fell back to a WHITE 12% border — invisible on the light paper ground
     (fine only on the dark chrome it silently assumed). Use --pm-card-border,
     which is a real token valued for BOTH themes (warm tan in light, faint
     white in dark), so the actionable pill keeps a visible edge in light. */
  border: 1px solid var(--pm-card-border);
  border-radius: 999px;
  background: var(--pm-hint-bg);
  color: var(--pm-text);
  font-size: 13px;
  cursor: pointer;
}
.bonus-strip__toggle .bonus-ico { width: 16px; height: 16px; color: var(--pm-accent); }
.bonus-strip__chev { transition: transform 0.15s ease; font-size: 10px; color: var(--pm-text-muted); }
.bonus-strip__chev.is-open { transform: rotate(180deg); }
.bonus-strip__body {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  padding: 10px 12px;
  margin-top: 6px;
  background: var(--pm-hint-bg);
  border-radius: 10px;
  font-size: 12px;
}
.bonus-strip__bonuses { display: flex; flex-wrap: wrap; gap: 8px 14px; }
.bonus-strip__item { display: inline-flex; align-items: center; gap: 6px; color: var(--pm-text); }
.bonus-strip__name { white-space: nowrap; }
.bonus-strip__counter {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  /* Theme-aware badge pair (white+teal in light, lime+dark in dark) — the
     old --pm-accent-soft was never defined so it fell back to a fixed faint
     lime tint, and --pm-accent alone went lime-on-lime (invisible) in dark. */
  color: var(--pm-badge-text);
  background: var(--pm-badge-bg);
  padding: 0 6px;
  border-radius: 999px;
}
.bonus-strip__states { color: var(--pm-text-muted); }

/* Wager ("Double or Nothing") activation button (S30-T7), prediction
   card. --pm-accent/--pm-accent-contrast/--pm-text-muted are defined for
   both light and dark themes (see the token block near the top of this
   file), so no extra dark-mode override is needed here. */
.pmatch-wager {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 0;
}

.pmatch-wager-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 8px;
  border: 1px solid var(--pm-accent);
  background: transparent;
  color: var(--pm-accent);
  font-size: 13px;
  cursor: pointer;
}
/* The dice icon (S42) rides the button's own text color: --pm-accent on
   the offer button, --pm-accent-contrast on the filled "Activated" pill —
   so it reads on both instead of a fixed state color. */
.pmatch-wager-btn .bonus-ico { width: 16px; height: 16px; color: currentColor; }

.pmatch-wager-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Activated but not yet resolved = "possible" (S42): gray, NOT the accent
   fill — green now means "won", so an activated-pending pill must not read
   as a win. Turns into the footer's won/lost/missed icon once finished. */
.pmatch-wager-btn--active {
  background: transparent;
  border-color: var(--pm-text-muted);
  color: var(--pm-text-muted);
}

.pmatch-wager-quota {
  margin-left: 6px;
  font-size: 12px;
  color: var(--pm-text-muted);
}

/* S41: the offered / activated states each render as a single wrapper
   (Alpine template x-if needs one root). Same inline layout as the
   parent .pmatch-wager row. */
.pmatch-wager-offer,
.pmatch-wager-active {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* The "Activated" pill is a non-interactive span in the S41 markup —
   keep the pill look from .pmatch-wager-btn but drop the hand cursor. */
.pmatch-wager-active .pmatch-wager-btn {
  cursor: default;
}

/* Deactivate "X" next to the activated pill (S41): only rendered while
   the match is still open — a locked activation is frozen. */
.pmatch-wager-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--pm-field-border);
  border-radius: 50%;
  background: transparent;
  color: var(--pm-text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.pmatch-wager-remove:hover {
  color: var(--pm-accent);
  border-color: var(--pm-accent);
}

/* ===================================
   USER DETAIL PAGE (S10-T2, Capa 6; restyle S14 B4 Tablas Task 2)
   =================================== */

/* Header card (S14 B4): same recipe as match-detail's .pmd-header-card —
   border + radius + surface bg + shadow — wrapping the profile identity
   row and the stat tiles that replaced the old .pud-summary chips. */
.pud-header-card {
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  padding: var(--space-md);
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  margin-bottom: var(--space-md);
}
.pud-profile-name {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: var(--font-size-xl);
  color: var(--pm-text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* Stat tiles (S14 B4): same visual language as the pool-home banner's
   .pool-home-stat* (ScoreboardCondensed accent-colored value + uppercase
   muted label) — replaces the old .pud-summary chips row. */
.pud-stats {
  display: flex;
  gap: var(--space-lg);
  margin-top: var(--space-md);
}
.pud-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.pud-stat-value {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 800;
  font-size: 28px;
  color: var(--pm-accent);
}
.pud-stat-label {
  font-size: 12px;
  color: var(--pm-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.pud-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
}
.pud-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  color: var(--pm-text);
}
.pud-table th {
  text-align: left;
  font-size: 12px;
  font-weight: 600;
  color: var(--pm-text-muted);
  padding: 7px 8px;
  border-bottom: 1px solid var(--pm-card-border);
  background: var(--pm-surface-2);
  white-space: nowrap;
}
.pud-table td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--pm-card-border);
}
.pud-table tbody tr:last-child td {
  border-bottom: none;
}
.pud-table .col-num {
  text-align: center;
  white-space: nowrap;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
}
/* S65: unpredicted locked match — mute the "—" so it reads as an
   intentional empty state (tooltip explains it), not missing data. */
.pud-table .col-num--no-pred {
  color: var(--pm-text-muted);
  font-weight: 400;
  cursor: help;
}

/* Temporal context columns (Fix #1): matchday/phase + kickoff date. Muted,
   secondary weight so they add context without competing with the score
   columns. Hidden on mobile (see the @media (max-width: 767px) block below),
   matching the members table's .col-date responsive-hide pattern. */
.pud-table .col-phase,
.pud-table .col-date {
  color: var(--pm-text-muted);
  font-size: 13px;
  white-space: nowrap;
}

/* S45: Camino "Road to the final" phase row — a distinct accent band that
   cuts the match list between phases (double function: separator + the
   member's points for that phase). Reads as a mini-header, not a match. */
.pud-camino-row td {
  background: var(--pm-surface-2);
  border-top: 2px solid var(--pm-accent-fill);
  font-weight: 700;
}
.pud-camino-desc {
  color: var(--pm-text);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 12px;
}
.pud-camino-points {
  color: var(--pm-accent);
}

/* S45-T4: Camino a la Final audit accordion — per-phase breakdown of the
   member's picks with their scoring state (post-lock). Native <details>. */
.camino-audit {
  margin-top: 22px;
}
.camino-audit-title {
  font-size: 15px;
  margin: 0 0 8px;
}
.camino-audit-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 12px;
}
.camino-audit-legend .camino-team {
  font-size: 11px;
  padding: 1px 7px;
}
.camino-phase {
  border: 1px solid var(--pm-card-border);
  border-radius: 10px;
  margin-bottom: 8px;
  overflow: hidden;
}
.camino-phase-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  background: var(--pm-surface-2);
}
.camino-phase-summary::-webkit-details-marker {
  display: none;
}
.camino-phase-summary::after {
  content: '⌄';
  margin-left: auto;
  color: var(--pm-text-muted);
  transition: transform 0.15s ease;
}
.camino-phase[open] > .camino-phase-summary::after {
  transform: rotate(180deg);
}
.camino-phase-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
}
.camino-phase-points {
  color: var(--pm-accent);
}
.camino-phase-counts {
  color: var(--pm-text-muted);
  font-size: 12px;
}
.camino-phase-body {
  padding: 0 14px 14px;
}
/* Per-team points (auxiliary): the "+N" a pick earned, opening the same
   breakdown tooltip the match table uses (.points-with-tooltip). */
.camino-team-pts {
  margin-left: 2px;
  font-size: 11px;
  font-weight: 700;
  color: var(--pm-text-muted);
  cursor: help;
}
/* Groups laid out as cards in a responsive grid — several per row on wider
   screens so the breakdown grows sideways, not only down. */
.camino-scopes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 8px;
  margin-top: 12px;
}
.camino-scope {
  padding: 8px 10px;
  border: 1px solid var(--pm-card-border);
  border-radius: 8px;
  background: var(--pm-surface-2);
}
.camino-scope-label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--pm-text-muted);
  margin-bottom: 6px;
}
.camino-teams {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* S45: Champions band separator (top-Y direct | playoff) — a full-width line
   that breaks the chip flow with a small label. */
.camino-band-sep {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 3px 0 1px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--pm-text-muted);
}
.camino-band-sep::before,
.camino-band-sep::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--pm-card-border);
}
.camino-team {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 11px;
  border: 1.5px solid var(--pm-card-border);
  font-size: 12px;
  font-weight: 600;
}
.camino-team .team-crest {
  width: 16px;
  height: 16px;
  object-fit: contain;
}
.camino-team-pos {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  color: var(--pm-text-muted);
}
/* State coding: exact = lime border + soft lime tint (no solid brick),
   advanced = teal/accent outline, eliminated = greyed, pending = neutral. */
.camino-team--exact {
  background: color-mix(in srgb, var(--pm-accent-fill) 14%, transparent);
  border-color: var(--pm-accent-fill);
}
.camino-team--exact .camino-team-pos {
  color: var(--pm-text);
}
.camino-team--hit {
  border-color: var(--pm-accent);
  color: var(--pm-text);
}
.camino-team--dead {
  opacity: 0.5;
  filter: grayscale(1);
}

/* Mobile tightening (S14 B4), same recipe as .pmd-table's own mobile
   rule — smaller padding/header font so the 5-column table doesn't feel
   bulkier than the already-restyled tables. */
@media (max-width: 767px) {
  .pud-table th,
  .pud-table td {
    padding: 5px 4px;
  }
  .pud-table th {
    font-size: 11px;
  }
  /* Temporal context columns are desktop/tablet only — the phone layout keeps
     the compact Match/Predicted/Real/Points(/Bonuses) set. The Camino
     separator row's colspan=5 cell still spans the visible columns because
     these two collapse to zero width here. */
  .pud-table .col-phase,
  .pud-table .col-date {
    display: none;
  }
}

.pud-bonus-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  background: var(--pm-hint-bg);
  color: var(--pm-text);
  font-size: 12px;
  margin-right: 4px;
}
.pud-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
}
.pud-page-btn {
  padding: 6px 12px;
  border: 1px solid var(--pm-field-border);
  border-radius: 6px;
  background: var(--pm-card-bg);
  color: var(--pm-text);
  cursor: pointer;
  font-size: 13px;
}
.pud-page-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.pud-page-status {
  font-size: 13px;
  color: var(--pm-text-muted);
}

.ranking-player-link,
.pmd-player-link,
.pud-match-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--pm-field-border);
}
/* Defensive truncation for the "quién predijo" match-detail table so long
   display names ellipsize instead of inflating the cell. */
.pmd-player-link {
  display: inline-block;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

/* ===================================
   AVATAR (S11-T2): user_picture image, or an initials circle colored by
   a hash of the display name — used across ranking, user detail, the
   header user-menu, and member lists (avatar.html.twig).
   =================================== */
.pm-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--pm-avatar-size, 40px);
  height: var(--pm-avatar-size, 40px);
  border-radius: 50%;
  overflow: hidden;
  flex: none;
}
.pm-avatar--img {
  object-fit: cover;
}
.pm-avatar--initials {
  background: hsl(var(--pm-avatar-hue, 200) 45% 30%);
  /* Fijo, NO --pm-text (S15 polish, corrección 3): el fondo es siempre
     un HSL a 30% de luminosidad, sea cual sea el modo — no varía con
     --pm-text (que sí varía: navy en claro, casi blanco en oscuro). El
     bug era exactamente eso: en claro el texto (navy) se volvía
     invisible sobre un fondo que siempre es oscuro. El texto tiene que
     ser igual de fijo que el fondo que ilumina. */
  color: #FFFFFF;
  font-weight: 700;
  font-size: calc(var(--pm-avatar-size, 40px) * 0.45);
}

.ranking-player-cell {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
.pud-title-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
}
.pm-member-cell {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
}
.pm-member-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ===================================
   MEMBER MANAGEMENT TABLES (Task 3, fix 5): the 3 tables in
   manage-members.html.twig (pending/active/removed) had zero CSS
   classes and rendered unstyled/broken at mobile widths. Reuses the
   same wrap+table+col-* convention already established by
   .ranking-table/.pmd-table/.pud-table (overflow-x:auto scroll
   container, not a card-per-row layout) instead of inventing a new
   pattern.
   =================================== */
.pm-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  margin-bottom: var(--space-md);
}
.pm-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  color: var(--pm-text);
}
.pm-table th {
  text-align: left;
  font-size: 12px;
  font-weight: 600;
  color: var(--pm-text-muted);
  padding: 10px 8px;
  border-bottom: 1px solid var(--pm-card-border);
  background: var(--pm-surface-2);
  white-space: nowrap;
}
.pm-table td {
  padding: 10px 8px;
  border-bottom: 1px solid var(--pm-card-border);
}
.pm-table tbody tr:last-child td {
  border-bottom: none;
}
.pm-table .col-actions,
.pm-table .col-date,
.pm-table .col-role {
  white-space: nowrap;
}
.pm-table .col-date {
  color: var(--pm-text-muted);
  font-size: 13px;
}
@media (max-width: 767px) {
  .pm-table .col-member {
    max-width: 96px;
  }
}

/* Payment pill (S27-T2, owner-only toggle on the Active Members table).
   Same pill shape as .status-pill above, but a distinct class so it can
   evolve independently (this one is interactive/tappable, .status-pill
   is display-only). Paid uses --pm-points/--pm-points-bg — the site's
   existing "positive achievement" green (already used for ranking
   points) — deliberately NOT --pm-accent (petróleo), which is reserved
   for brand/navigation chrome, not a payment-collected signal. Pending
   uses the same muted surface/text pair as .status-pill's closed/finished
   states.

   Affordance restyle (S27-F2-T3): a faint border at rest plus a hover
   state so the pill reads as a toggle rather than a plain label — the
   owner asked for something noticeable but NOT a loud button
   ("no pido un botón muy llamativo"), so this stays a border/color shift,
   no shadow or scale. Logic (payment-status.js) is unchanged. */
.payment-pill {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}
.payment-pill--paid {
  background: var(--pm-points-bg);
  color: var(--pm-points);
}
.payment-pill--paid:hover {
  border-color: var(--pm-points);
}
.payment-pill--pending {
  background: var(--pm-surface-2);
  color: var(--pm-text-muted);
  border-color: var(--pm-field-border);
}
.payment-pill--pending:hover {
  border-color: var(--pm-accent);
  color: var(--pm-accent);
}
.payment-pill:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}
.pm-table .col-payment {
  white-space: nowrap;
}

/* Owner-only prize/collected/pending summary (S27-F2-T3), rendered above
   the member tables — see manage-members.html.twig. Compact single line,
   no card chrome (this is a quick-glance figure, not a dashboard widget).
   Hidden entirely by the template when the pool has no entryAmount set. */
.pm-payment-summary {
  margin: 0 0 var(--space-md);
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--pm-surface-2);
  color: var(--pm-text-muted);
  font-size: 13px;
}
.pm-payment-summary strong {
  color: var(--pm-text);
  font-weight: 600;
}
.pm-payment-summary-value--positive {
  color: var(--pm-points);
}

.sidebar-championship-logo {
  height: 26px;
  width: 26px;
  border-radius: 7px;
  object-fit: cover;
  flex-shrink: 0;
}

/* ===================================
   SIDEBAR NAV (S10-T2, restyled S13 B2, Capa 2)
   =================================== */

/* Pool identity strip — the "where am I" anchor of Capa 2 (pool name +
   championship logo). Lives in normal document flow at every width
   (only .sidebar-nav itself gets pinned to the viewport bottom, and
   only <1024px — see below), so it renders right after the header on
   mobile, and as a full-width heading bar in the body under the sticky
   header on desktop (S15-T1; no more rail — see design spec
   docs/superpowers/specs/2026-07-18-desktop-header-nav-and-buttons-design.md). */
.sidebar-nav-brand {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 10px var(--space-md);
  font-size: 15px;
  font-weight: 700;
  color: var(--pm-chrome-text);
  background: var(--pm-chrome-bg);
  border-bottom: 1px solid var(--pm-card-border);
}

.sidebar-pool-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-nav {
  display: flex;
}

/* Aside container itself (S15 polish, corrección 2): sin esto el rail
   de escritorio (>=1024px, ver media query más abajo) solo pintaba
   Petróleo en la tira de marca de arriba y quedaba blanco debajo de
   ella, un rail de dos tonos en vez del "chrome sólido" pedido. En
   mobile/tablet no cambia nada visible (el contenedor no tiene alto
   propio ahí — la tira de marca y la barra fija ya pintan su propio
   fondo). */
/* Light-only: petróleo chrome bg on the rail/bottom-bar container. Scoped
   to light so dark keeps inheriting .app-wrapper's background exactly as it
   did before this token existed (no dark-mode delta on the desktop rail). */
:root:not([data-theme="dark"]) .sidebar-nav-container {
  background: var(--pm-chrome-bg);
}

.sidebar-nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  color: var(--pm-chrome-text);
  text-decoration: none;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
}

.sidebar-nav-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}

/* Active state (dark): lime accent icon+label on a NEUTRAL translucent-
   white chip — not a lime-tinted background. --pm-accent is lime in dark
   mode, and a lime background fill read as too strong/green (same
   de-greening as the pool banner); lime stays only as the accent color
   on the icon/label (allowed as "text" in dark), which is the real
   "active" signal. Light theme keeps its own translucent-white chip with
   white text via the scoped override below (lime on the petróleo chrome
   would be invisible). */
.sidebar-nav-item.is-active,
.sidebar-nav-more-link.is-active {
  color: var(--pm-accent);
  background: rgba(255, 255, 255, 0.10);
  border-radius: 10px;
}

/* ── HOVER INSTITUCIONAL DEL CHROME ─────────────────────────────
   Un solo lenguaje de hover para los controles ícono/pill del chrome:
   nav de pool (inline en el header + bottom bar + more-menu), "Mis pollas" del header,
   la campana y los íconos sociales del footer. Ring blanco inset +
   texto/ícono en blanco: se lee igual sobre el chrome petróleo (claro)
   y el navy (oscuro), overridea el a:hover genérico (que dejaba el texto
   en --pm-accent = petróleo sobre petróleo, invisible) y se distingue
   del relleno del estado activo. Excepciones por contexto (NO ring):
   los links de texto del footer (.footer-links a → brillo) y los ítems
   de menú dentro de tarjetas (profile/notif dropdown → relleno), porque
   un ring sobre texto suelto o dentro de una tarjeta se ve mal. */
.sidebar-nav-item:not(.is-active):hover,
.sidebar-nav-more-link:not(.is-active):hover,
.header-nav-link:hover,
.notification-bell-btn:hover,
.footer-social-link:hover,
.header-content-nav a:hover {
  color: var(--pm-chrome-text);
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.75);
  border-radius: 10px;
}

.sidebar-nav-more {
  position: relative;
}

.sidebar-nav-more-menu {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  position: absolute;
  bottom: 100%;
  right: 0;
  margin-bottom: var(--space-sm);
  padding: var(--space-sm);
  background: var(--pm-chrome-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 13px;
  box-shadow: var(--shadow-dropdown);
  min-width: 190px;
}

.sidebar-nav-more-link {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm);
  border-radius: 8px;
  color: var(--pm-chrome-text);
  text-decoration: none;
  white-space: nowrap;
}

/* Mobile + tablet (<1024px): fixed bottom tab bar. Tablet matches
   mobile here; >=1024px the nav moves inline into the sticky header
   instead (S15-T1, see media query further below) — there's no
   separate rail breakpoint anymore. */
@media (max-width: 1023px) {
  .sidebar-nav-brand {
    display: flex;
  }

  /* .sidebar-nav-container is intentionally NOT positioned — it stays
     a normal-flow block so the identity strip above renders at the
     top of the pool page. Only the tab bar (.sidebar-nav) is pinned
     to the viewport bottom; position:fixed takes it out of the
     container's flow without affecting where the container itself
     (and the brand inside it) sits. */
  .sidebar-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 100;
    justify-content: space-around;
    padding: 10px 4px calc(10px + env(safe-area-inset-bottom));
    background: var(--pm-chrome-bg);
    border-top: 1px solid var(--pm-card-border);
    box-shadow: var(--shadow-bottom-bar);
  }

  .sidebar-nav-item {
    flex-direction: column;
    flex: 1;
    justify-content: center;
    font-size: 11px;
    gap: 2px;
    padding: 6px 2px;
    border-radius: 10px;
  }

  .sidebar-nav-more-menu {
    /* right:0 keeps the menu inside the viewport since it's the last
       tab-bar item — no horizontal centering needed at this width. */
    right: 4px;
    bottom: calc(100% + 8px);
  }
}

/* Desktop (>=1024px, S15-T1 header-nav, 2026-07-18): the rail is gone —
   the 3 nav items + More now render inline in the sticky header (see
   .header-pool-nav below), not as a left column. `.app-layout` stays
   the base flex-direction:column (no `row` override here anymore), so
   `.sidebar-nav-container` (now only the pool-name brand strip, the
   `<nav>` having moved to header.html.twig) simply flows as a
   full-width heading bar under the header, same shape as the <1024
   identity strip above content — no extra rule needed for that part.
   Only the brand's own spacing gets a desktop tweak (bottom margin no
   longer applies — nothing sits below it inside the aside anymore). */
@media (min-width: 1024px) {
  .sidebar-nav-brand {
    margin-bottom: 0;
    /* Center the strip's content the same way header/footer/.app-content
       do at very wide viewports, so it doesn't look wider than
       everything else stacked under it once .app-wrapper is
       full-bleed. */
    padding-inline: max(var(--space-md), calc((100% - 1280px) / 2));
  }

  /* The nav itself, inline in the sticky header (see .header-pool-nav
     in SITE HEADER below for the wrapper's display:flex + centering).
     `.sidebar-nav` base rule is already `display: flex` with no
     direction override here, so it's a horizontal row by default —
     same icon-over-label items as the mobile bottom bar, just row
     instead of column and without the fixed/bottom positioning. */
  .sidebar-nav {
    align-items: center;
    gap: 4px;
  }

  .sidebar-nav-item {
    padding: 8px var(--space-sm);
    font-size: 13px;
  }

  /* Unlike the old rail (where the trigger was hidden and the menu
     force-visible in-flow), the header-nav "More" is a real dropdown:
     trigger stays visible/clickable and the menu opens BELOW it
     (top:100%, mirroring .notification-bell-panel/.profile-menu-panel)
     instead of above (the mobile bottom-bar's bottom:100%), since the
     header sits at the top of the viewport, not the bottom. */
  .sidebar-nav-more-menu {
    top: 100%;
    bottom: auto;
    margin-top: var(--space-sm);
    margin-bottom: 0;
  }
}

/* ===================================
   DASHBOARD + GENERIC UI ATOMS (S10-T2, Capa 7)
   =================================== */

.dashboard-container {
  padding: var(--space-md);
}
.dashboard-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
  flex-wrap: wrap;
}
.empty-state {
  padding: var(--space-lg);
  text-align: center;
  color: var(--pm-text-muted);
}
.empty-state-hint {
  font-size: 13px;
}
.pools-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-md);
}

/* Generic card atom — same border/background/shadow values already
   used by .match-card (Capa 3) and .ranking-live-card (Capa 4). */
.card {
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.pool-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}
.pool-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.pool-meta {
  font-size: 13px;
  color: var(--pm-text-muted);
}
.card-footer {
  display: flex;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

/* Generic badge atom — one neutral style regardless of role; the
   badge's own text ("Owner"/"Admin"/"Member") already distinguishes
   the role, no need to also encode it in color. */
.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--pm-surface-2);
  color: var(--pm-text);
  font-size: 12px;
  font-weight: 600;
}

/* Generic alert atom — reuses the exact same token values as
   Drupal's own status messages (.messages--status/--warning/--error),
   just under a different selector for the dashboard's generic markup.
   .alert-info has no .messages--* equivalent, so it reuses the
   existing --pm-hint/--pm-hint-bg pair instead. */
.alert {
  padding: var(--space-sm) var(--space-md);
  border-radius: 8px;
  border-left: 4px solid;
  font-size: 13px;
}
.alert-warning {
  background: var(--pm-hint-bg);
  border-color: var(--color-warning);
  color: var(--pm-hint);
}
.alert-info {
  background: var(--pm-hint-bg);
  border-color: var(--pm-hint);
  color: var(--pm-hint);
}
.alert-danger {
  background: var(--color-danger-bg);
  border-color: var(--color-danger);
  color: var(--color-danger);
}

/* Generic button atom (extended Task 2, S15: unify EVERY action button —
   .pool-home-cta/.pool-share-btn/.pool-share-copy-btn/.pool-leave-link had
   3 different looks, one with a broken hover — onto this single system,
   reference shape = the old .pool-share-copy-btn "Copiar enlace". Grouped
   selectors below so those existing template classes get the system
   without a twig rewrite; the bespoke rules that used to live under
   SHARE POOL / pool-home-cta were folded in here — see their old spots for
   the removal notes. New: .btn-danger for destructive actions (Salir de la
   polla, remover/rechazar miembro) plus real :hover on every variant. */
.btn,
.pool-home-cta,
.pool-share-btn,
.pool-share-copy-btn,
.pool-leave-link {
  display: inline-block;
  padding: 9px 16px;
  border-radius: 6px;
  border: 1px solid transparent;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  text-align: center;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn-primary,
.pool-home-cta,
.pool-share-btn,
.pool-share-copy-btn {
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
  border-color: var(--pm-accent);
}
.btn-primary:hover,
.pool-home-cta:hover,
.pool-share-btn:hover,
.pool-share-copy-btn:hover {
  background: var(--pm-accent-hover);
  border-color: var(--pm-accent-hover);
  color: var(--pm-accent-contrast);
}
.btn-danger,
.pool-leave-link {
  background: var(--pm-danger);
  color: var(--pm-danger-ink);
  border-color: var(--pm-danger);
}
.btn-danger:hover,
.pool-leave-link:hover {
  background: var(--pm-danger-hover);
  border-color: var(--pm-danger-hover);
  color: var(--pm-danger-ink);
}
.btn-secondary {
  background: var(--pm-card-bg);
  border-color: var(--pm-field-border);
  color: var(--pm-text);
}
.btn-outline {
  background: transparent;
  border-color: var(--pm-field-border);
  color: var(--pm-text);
}
.btn-sm {
  padding: 4px 10px;
  font-size: 13px;
}
/* .pool-leave-link is grouped above but only inside .pool-home-leave
   (text-align:center, see 2026-07-16 rule further down) — the anchor
   becomes an inline-block danger button, centered by its wrapper. */
/* Table action links reuse .btn/.btn-primary/.btn-danger directly in
   manage-members.html.twig (Approve/Reinstate vs. Reject/Remove); the
   old " | " text separator was dropped from the template in favor of
   real spacing between adjacent buttons. */
.pm-table .col-actions .btn + .btn {
  margin-left: 8px;
}

/* ===================================
   SITE HEADER + FOOTER (dark chrome, 2026-07-13)
   =================================== */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 10px var(--space-md);
  background: var(--pm-chrome-bg);
  border-bottom: 1px solid var(--pm-card-border);
}
/* Desktop: la barra petróleo sigue full-width, pero su contenido (brand +
   acciones) se alinea a una zona centrada de 1360 para no dispersarse a los
   bordes extremos en pantallas anchas — mismo criterio que el content y el
   footer. Va DESPUÉS de la base para ganar el padding-inline en cascada. */
@media (min-width: 1024px) {
  .site-header {
    padding-inline: max(var(--space-md), calc((100% - 1280px) / 2));
    /* S15-T1: sticky so the inline pool nav (Home/Predictions/Ranking)
       stays reachable while scrolling long pool pages — there's no
       sidebar rail to anchor it anymore. z-index kept comfortably above
       normal content (cards, tables have no z-index of their own) but
       below the header's OWN dropdown panels (.notification-bell-panel,
       .profile-menu-panel, .sidebar-nav-more-menu at z-index:1000+ /
       unset-but-DOM-later) — those are descendants of this stacking
       context so they always paint above it regardless. Verified against
       the sticky footer (S13 B1): unrelated elements (footer isn't
       sticky/fixed), no interaction.
       `top` uses Drupal core's own `--drupal-displace-offset-top` custom
       property (set on :root by toolbar.js, e.g. 80px when an admin's
       toolbar tray is open) instead of a bare 0 — without it, an admin
       viewing a pool page would see this header stick at true viewport
       y:0, which is BEHIND the toolbar's own fixed bar (#toolbar-bar,
       z-index:502 in core, above this header's 500), i.e. hidden under
       it once scrolled. Falls back to 0 for anonymous/non-admin
       sessions, where the variable is unset — no visual change for
       them (verified: this is the vast majority of pool-page traffic). */
    position: sticky;
    top: var(--drupal-displace-offset-top, 0);
    z-index: 500;
  }

  /* Pool nav inline between brand and .header-actions: flex:1 so it
     absorbs the header's middle space (classic logo | nav | actions
     layout) and centers its own content, instead of `.site-header`'s
     justify-content:space-between spreading 3 items apart with a big
     gap on either side of the nav. <1024px this is left at the default
     block display — its only child (.sidebar-nav) is position:fixed at
     that width (see SIDEBAR NAV section), so this wrapper can't be
     display:none there or it would hide that fixed child along with it
     (display:none on an ancestor removes the whole subtree from
     rendering, fixed positioning or not) — it just collapses to zero
     height/width on its own, harmless as an empty flex sibling. */
  .header-pool-nav {
    display: flex;
    flex: 1;
    justify-content: center;
  }

  /* S17-T1: same middle-slot treatment as .header-pool-nav above — the
     two are mutually exclusive per page (pool vs Capa 1), see
     header.html.twig's gate. */
  .header-content-nav {
    flex: 1;
    justify-content: center;
  }
}

/* S17-T1: header 'content' menu — Capa 1 only (home, content pages),
   never alongside .header-pool-nav. Renders the 'content' menu's <ul>
   (see header.html.twig + pool_mobile_preprocess_page()); the link
   styling mirrors .header-nav-link so it reads as the same chrome
   control, ring hover included (see the unified hover rule above). */
.header-content-nav {
  display: flex;
  align-items: center;
}
.header-content-nav ul {
  display: flex;
  align-items: center;
  gap: 4px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.header-content-nav a {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 10px;
  border-radius: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--pm-chrome-text);
  text-decoration: none;
  white-space: nowrap;
}
.site-brand {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 18px;
  font-weight: 800;
  letter-spacing: 0.5px;
  color: var(--pm-chrome-text);
  text-decoration: none;
  white-space: nowrap;
}
.site-brand:hover {
  color: var(--pm-chrome-text);
}
.site-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 13px;
  font-weight: 800;
}
/* --pm-accent es Petróleo por default en claro (fix ítem 1) — igual al
   nuevo --pm-chrome-bg del header, así que este texto decorativo
   quedaría invisible. Corregido solo en claro (regla scoped abajo,
   "CHROME — CORRECCIONES DE MODO CLARO"); el oscuro sigue leyendo
   --pm-accent tal cual (no colisiona: su acento en oscuro es lima
   #C6F24E sobre chrome casi negro). Rebrand: mismo mecanismo, ahora
   aplicado al "Podio" de la wordmark MiPodio en vez del "2026". */
.site-brand-accent {
  /* "Podio" en lime en AMBOS temas (--pm-accent-fill = lime siempre). El
     header es chrome petróleo en claro y oscuro, así que el lime como texto
     de marca se lee bien en los dos — resuelve la colisión vieja donde en
     claro --pm-accent quedaba igual al chrome y forzaba texto blanco. */
  color: var(--pm-accent-fill);
}

/* Right-hand cluster: "Mis pollas" (logged in) + notification bell +
   profile trigger / "Ingresar" CTA — all inline, no wrapping, no
   hamburger, even at 375px (B1 design contract). */
.header-actions {
  display: flex;
  align-items: center;
  gap: 14px;
}
.header-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* S15-T1 F2: give the hover ring the same "air" .notification-bell-btn
     gets (34x34 box around its icon) — unlike the bell this is an
     icon+label pair, not icon-only, so it can't be a fixed 34x34
     square without clipping the label. A 34px-tall box with symmetric
     horizontal padding matches the bell's height and gives the ring
     room on all 4 sides instead of hugging the icon+text tightly. */
  height: 34px;
  padding: 0 10px;
  border-radius: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--pm-chrome-text);
  text-decoration: none;
  white-space: nowrap;
}
/* Hover en la regla "HOVER INSTITUCIONAL DEL CHROME" (ring blanco). */
.header-nav-icon {
  width: 18px;
  height: 18px;
  flex: none;
}
.header-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
/* S23-T2b: icon-only on mobile, text-only on desktop — swapped via the
   min-width: 1024px breakpoint below (matches the header's existing
   desktop breakpoint, see @media (min-width: 1024px) further down).
   Both nodes render server-side either way (cache-invariant markup);
   only visibility toggles. */
.header-cta-icon {
  display: inline-flex;
  flex: none;
}
.header-cta-label {
  display: none;
}

/* S23-T2: anonymous-only language selector, sits inline with the Login
   CTA (no theme/timezone controls for anon — see user-menu.html.twig).
   Plain native <select>, no Alpine — kept deliberately compact so the
   pair still fits the 375px header alongside the brand mark. Colors
   reuse --pm-chrome-text/the header's white hover-ring convention
   (rgba white border, see .notification-bell-btn:hover above) instead
   of --pm-field-*, because .header-actions sits on --pm-chrome-bg
   (Petróleo/near-black), not the page surface those tokens assume.
   S23-T2b: restyled as a compact "[ ES v ]" chip — appearance:none
   drops the browser's native arrow (and the width it reserves), and a
   background-image caret replaces it so the closed control stays
   tight even though its content is now just 2 uppercase letters
   (langcode, not the full language name — see user-menu.html.twig).
   The caret is hardcoded white/near-white (not currentColor — native
   <select> background-image can't reference it) because
   --pm-chrome-text resolves to #FFFFFF (light) / #EAF2F8 (dark), both
   effectively white against --pm-chrome-bg's dark chrome in either
   theme — see the token block near the top of this file. */
.header-anon-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.header-lang-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  height: 28px;
  max-width: 54px;
  padding: 0 16px 0 8px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 6px;
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath d='M1 2.5l3 3 3-3' fill='none' stroke='white' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 6px center;
  background-size: 8px 8px;
  color: var(--pm-chrome-text);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.header-lang-select:focus-visible {
  outline: 2px solid var(--pm-chrome-text);
  outline-offset: 1px;
}

/* S23-T2b: desktop swap for the Login CTA — icon-only below 1024px
   (mobile/tablet, matches the header's own desktop breakpoint used by
   .header-pool-nav/.header-content-nav above), text-only at 1024px+.
   The anchor's aria-label (see user-menu.html.twig) is the accessible
   name in BOTH states, so this is purely a visual swap. */
@media (min-width: 1024px) {
  .header-cta-icon {
    display: none;
  }
  .header-cta-label {
    display: inline;
  }
}

/* Profile trigger + dropdown (avatar circle -> name / Mi cuenta / Cerrar
   sesión). Panel styling mirrors .notification-bell-panel below —
   same dark dropdown-card, tokens only. */
.profile-menu {
  position: relative;
}
.profile-menu-trigger {
  display: inline-flex;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  border-radius: 999px;
}
.profile-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 190px;
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  box-shadow: var(--shadow-panel);
  padding: 6px;
  z-index: 1000;
}
.profile-menu-name {
  margin: 0;
  padding: 8px 10px 6px;
  font-size: 13px;
  font-weight: 700;
  color: var(--pm-text);
  border-bottom: 1px solid var(--pm-card-border);
  margin-bottom: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-menu-link {
  display: block;
  padding: 8px 10px;
  border-radius: 8px;
  font-size: 13px;
  color: var(--pm-text-muted);
  text-decoration: none;
  white-space: nowrap;
}
.profile-menu-link:hover {
  color: var(--pm-accent);
  background: var(--pm-surface-2);
}

/* S23-T2 (uniform rows + drill-down): every entry in the logged-in panel
   — "My Account", Appearance, Language, Timezone, Logout — is now the
   same .pm-row shape (icon · label · value · chevron), replacing the
   S23-T1b mix of a filled .profile-menu-cta button + smaller
   .profile-menu-settings* controls that read as different components.
   Reset/anchor-and-button-agnostic so <a> and <button> rows look
   identical. */
.pm-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 10px;
  border: none;
  background: transparent;
  border-radius: 8px;
  color: var(--pm-text);
  font: inherit;
  font-size: 13px;
  text-decoration: none;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}
.pm-row:hover {
  background: var(--pm-surface-2);
}
.pm-row-ic {
  display: inline-flex;
  flex: none;
  color: var(--pm-text-muted);
}
.pm-row-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pm-row-value {
  flex: none;
  color: var(--pm-text-muted);
  font-size: 12px;
}
.pm-row-chevron {
  flex: none;
  color: var(--pm-text-muted);
  font-size: 14px;
  line-height: 1;
}

/* "My Account" leads the list (right after the name) and stays visually
   primary within the uniform row shape via its accent icon + slightly
   heavier label — not a filled button like the old .profile-menu-cta. */
.pm-row--account .pm-row-ic {
  color: var(--pm-accent);
}
.pm-row--account .pm-row-label {
  font-weight: 700;
}

/* Logout keeps the muted treatment .profile-menu-link used to carry. */
.pm-row--logout {
  color: var(--pm-text-muted);
}
.pm-row--logout .pm-row-ic {
  color: var(--pm-text-muted);
}
.pm-row--logout:hover {
  color: var(--pm-accent);
}

/* Drill-down sub-panels (Appearance/Language/Timezone) opened on demand
   from a .pm-row tap via openPanel(name); .sg-seg/.sg-select below are
   reused as-is for the appearance segment and timezone select. */
.pm-picker-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 4px 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--pm-text);
}
.pm-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--pm-text);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.pm-back:hover {
  background: var(--pm-surface-2);
}
.pm-picker .sg-seg {
  margin: 0 4px 4px;
}
.pm-picker .sg-select {
  margin: 0 4px 4px;
  width: calc(100% - 8px);
}

/* Language picker list rows (one per language_links entry) — check mark
   on the active language, otherwise the same row shape as .pm-row. */
.pm-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  background: transparent;
  border-radius: 8px;
  color: var(--pm-text);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.pm-opt:hover {
  background: var(--pm-surface-2);
}
.pm-opt-mark {
  flex: none;
  width: 14px;
  color: var(--pm-accent);
  font-size: 12px;
  font-weight: 700;
}
.profile-menu-divider {
  border-top: 1px solid var(--pm-card-border);
  margin: 4px 0;
}

/* ===================================
   NOTIFICATION BELL (Task 3): header dropdown, API-driven (js/notification-bell.js)
   Panel pattern mirrors pool-share-modal above (dark dropdown-card,
   tokens only — no new colors introduced).
   =================================== */
.notification-bell {
  position: relative;
}
.notification-bell-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--pm-chrome-text-muted);
  cursor: pointer;
}
/* Hover en la regla "HOVER INSTITUCIONAL DEL CHROME" (ring blanco) —
   antes usaba inversión (fondo claro + ícono acento); unificado al ring. */
.notification-bell-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--pm-badge-bg);
  color: var(--pm-badge-text);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}
.notification-bell-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 320px;
  max-height: 70vh;
  overflow-y: auto;
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  box-shadow: var(--shadow-panel);
  z-index: 1000;
}
.notification-bell-header {
  padding: 12px 14px;
  border-bottom: 1px solid var(--pm-card-border);
}
.notification-bell-header h3 {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  color: var(--pm-text);
}
.notification-bell-list {
  display: flex;
  flex-direction: column;
}
.notification-bell-state {
  padding: 20px 14px;
  text-align: center;
  font-size: 13px;
  color: var(--pm-text-muted);
}
.notification-bell-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  padding: 10px 14px;
  border: none;
  border-bottom: 1px solid var(--pm-card-border);
  background: transparent;
  color: var(--pm-text);
  text-align: left;
  cursor: pointer;
}
.notification-bell-item:last-child {
  border-bottom: none;
}
.notification-bell-item:hover {
  background: var(--pm-surface-2);
}
.notification-bell-item-message {
  font-size: 13px;
  line-height: 1.4;
}
.notification-bell-item-time {
  font-size: 11px;
  color: var(--pm-text-muted);
}

@media (max-width: 767px) {
  /* The header stays a single row at every width down to 375px — logo +
     Mis pollas + bell + profile all inline, no wrapping, no hamburger
     (B1 design contract). Only the gaps/sizes tighten. */
  .site-header {
    padding: 10px 12px;
    gap: 8px;
  }
  .header-actions {
    gap: 10px;
  }
  .site-brand {
    gap: 5px;
    font-size: 16px;
  }
  .header-nav-label {
    font-size: 11.5px;
  }
  /* S23-T2b: icon-only Login button shouldn't carry the full .btn
     text-button padding (9px 16px) — that's sized for the word "Login",
     not an 18px glyph, and was enough to push the header 8px past the
     375px viewport (measured, not eyeballed) with the language chip
     alongside it. Sized to match .notification-bell-btn's 34x34 icon
     box instead. */
  .header-cta {
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
  }
  .header-content-nav ul {
    gap: 2px;
  }
  .header-content-nav a {
    height: 30px;
    padding: 0 6px;
    font-size: 11px;
  }
  .notification-bell-panel {
    /* True viewport-relative full-width panel below the header. --bell-
       panel-top is computed in JS (notification-bell.js updatePanelTop())
       from the header's actual rendered bottom edge. */
    position: fixed;
    top: var(--bell-panel-top, 96px);
    left: var(--space-sm);
    right: var(--space-sm);
    width: auto;
    max-height: 60vh;
  }
  .profile-menu-panel {
    right: -8px;
  }
}

/* ===================================
   FOOTER (dark chrome, persistent on Capa 1 and Capa 2)
   =================================== */

footer {
  padding: var(--space-lg) var(--space-md) var(--space-md);
  background: var(--pm-chrome-bg);
  border-top: 1px solid var(--pm-card-border);
  color: var(--pm-chrome-text-muted);
  font-size: 12px;
}
/* S17-T1: .footer-links now wraps the 'footer' menu's rendered <ul> (see
   footer.html.twig + pool_mobile_preprocess_page()) instead of holding
   <a> children directly — the flex row moves one level down onto that
   <ul>, list styling reset there too. .footer-links itself stays a
   plain block (its own margin-bottom, and the max-width/centering rule
   further below still applies to it directly). */
.footer-links {
  margin-bottom: var(--space-md);
}
.footer-links ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px var(--space-md);
  list-style: none;
  margin: 0;
  padding: 0;
}
.footer-links a {
  color: var(--pm-chrome-text-muted);
  text-decoration: none;
}
/* --pm-accent es petróleo en claro E IGUAL a --pm-chrome-bg del footer
   en ese modo (colisión exacta, texto invisible); en oscuro no colisiona
   por hex pero ya no tiene sentido depender del acento para un hover de
   chrome (consolidación S15-T…) — --pm-chrome-text resuelve ambos modos
   con un solo valor estable, sin importar hacia dónde cambie el acento. */
.footer-links a:hover {
  color: var(--pm-chrome-text);
}
.footer-meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--pm-card-border);
  text-align: center;
}
/* S17-T1: same treatment as .footer-links above — .footer-social wraps
   the 'social' menu's rendered <ul>, the flex row moves onto it. */
.footer-social {
  display: flex;
  align-items: center;
}
.footer-social ul {
  display: flex;
  align-items: center;
  /* S15-T1 F2: gap trimmed from 14px now that each icon sits in its own
     34x34 box (below) — the box's own padding already provides visual
     separation, so the old naked-icon gap would read as too spread out
     stacked on top of it. */
  gap: 2px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.footer-social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* S15-T1 F2: same 34x34 centered box as .notification-bell-btn, so the
     hover ring gets the same breathing room around these icon-only
     links (unlike .header-nav-link, no label to protect from clipping). */
  width: 34px;
  height: 34px;
  border-radius: 8px;
  color: var(--pm-chrome-text-muted);
  /* S17-T1: the link's title ("Facebook", etc.) stays in the DOM as its
     accessible name, but is visually collapsed — the icon itself is a
     ::before mask-image glyph (below), so this reads as icon-only, same
     as the old hardcoded footer's <svg aria-hidden> + aria-label. */
  font-size: 0;
  line-height: 0;
}
/* Hover en la regla "HOVER INSTITUCIONAL DEL CHROME" (ring blanco). */

/* S17-T1: per-network icon glyph. .footer-social-link's own class comes
   from the 'social' menu link's menu_link_attributes "class" field
   (admin-editable at /admin/structure/menu/manage/social); each
   .social--{network} class here only supplies the mask-image, so
   swapping/adding a network is a CSS-only change, no markup change. */
.footer-social-link::before {
  content: '';
  display: block;
  width: 18px;
  height: 18px;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}
.social--facebook::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M22 12a10 10 0 1 0-11.6 9.9v-7H8v-2.9h2.4V9.8c0-2.4 1.4-3.7 3.6-3.7 1 0 2.1.2 2.1.2v2.3h-1.2c-1.2 0-1.5.7-1.5 1.4v1.7h2.6l-.4 2.9h-2.2v7A10 10 0 0 0 22 12Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M22 12a10 10 0 1 0-11.6 9.9v-7H8v-2.9h2.4V9.8c0-2.4 1.4-3.7 3.6-3.7 1 0 2.1.2 2.1.2v2.3h-1.2c-1.2 0-1.5.7-1.5 1.4v1.7h2.6l-.4 2.9h-2.2v7A10 10 0 0 0 22 12Z'/%3E%3C/svg%3E");
}
.social--instagram::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.2c3.2 0 3.6 0 4.9.1 1.2.1 1.8.3 2.2.4.6.2 1 .4 1.4.8.4.4.6.8.8 1.4.1.4.3 1 .4 2.2.1 1.3.1 1.7.1 4.9s0 3.6-.1 4.9c-.1 1.2-.3 1.8-.4 2.2-.2.6-.4 1-.8 1.4-.4.4-.8.6-1.4.8-.4.1-1 .3-2.2.4-1.3.1-1.7.1-4.9.1s-3.6 0-4.9-.1c-1.2-.1-1.8-.3-2.2-.4-.6-.2-1-.4-1.4-.8-.4-.4-.6-.8-.8-1.4-.1-.4-.3-1-.4-2.2C2.2 15.6 2.2 15.2 2.2 12s0-3.6.1-4.9c.1-1.2.3-1.8.4-2.2.2-.6.4-1 .8-1.4.4-.4.8-.6 1.4-.8.4-.1 1-.3 2.2-.4C8.4 2.2 8.8 2.2 12 2.2Zm0 3.1A6.7 6.7 0 1 0 18.7 12 6.7 6.7 0 0 0 12 5.3Zm0 11a4.3 4.3 0 1 1 4.3-4.3 4.3 4.3 0 0 1-4.3 4.3Zm6.9-11.3a1.6 1.6 0 1 1-1.6-1.6 1.6 1.6 0 0 1 1.6 1.6Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.2c3.2 0 3.6 0 4.9.1 1.2.1 1.8.3 2.2.4.6.2 1 .4 1.4.8.4.4.6.8.8 1.4.1.4.3 1 .4 2.2.1 1.3.1 1.7.1 4.9s0 3.6-.1 4.9c-.1 1.2-.3 1.8-.4 2.2-.2.6-.4 1-.8 1.4-.4.4-.8.6-1.4.8-.4.1-1 .3-2.2.4-1.3.1-1.7.1-4.9.1s-3.6 0-4.9-.1c-1.2-.1-1.8-.3-2.2-.4-.6-.2-1-.4-1.4-.8-.4-.4-.6-.8-.8-1.4-.1-.4-.3-1-.4-2.2C2.2 15.6 2.2 15.2 2.2 12s0-3.6.1-4.9c.1-1.2.3-1.8.4-2.2.2-.6.4-1 .8-1.4.4-.4.8-.6 1.4-.8.4-.1 1-.3 2.2-.4C8.4 2.2 8.8 2.2 12 2.2Zm0 3.1A6.7 6.7 0 1 0 18.7 12 6.7 6.7 0 0 0 12 5.3Zm0 11a4.3 4.3 0 1 1 4.3-4.3 4.3 4.3 0 0 1-4.3 4.3Zm6.9-11.3a1.6 1.6 0 1 1-1.6-1.6 1.6 1.6 0 0 1 1.6 1.6Z'/%3E%3C/svg%3E");
}
.social--x::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18.9 1.2h3.7l-8 9.1 9.4 12.5h-7.4l-5.8-7.6-6.6 7.6H.5l8.6-9.8L0 1.2h7.6l5.2 6.9 6.1-6.9Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18.9 1.2h3.7l-8 9.1 9.4 12.5h-7.4l-5.8-7.6-6.6 7.6H.5l8.6-9.8L0 1.2h7.6l5.2 6.9 6.1-6.9Z'/%3E%3C/svg%3E");
}
.social--linkedin::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20.45 20.45h-3.55v-5.57c0-1.33-.02-3.03-1.85-3.03-1.85 0-2.14 1.44-2.14 2.94v5.66H9.36V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.46zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12M7.12 20.45H3.56V9h3.56z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20.45 20.45h-3.55v-5.57c0-1.33-.02-3.03-1.85-3.03-1.85 0-2.14 1.44-2.14 2.94v5.66H9.36V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.46zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12M7.12 20.45H3.56V9h3.56z'/%3E%3C/svg%3E");
}
.social--youtube::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.5 6.2a3 3 0 0 0-2.11-2.12C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.39.58A3 3 0 0 0 .5 6.2 31 31 0 0 0 0 12a31 31 0 0 0 .5 5.8 3 3 0 0 0 2.11 2.12c1.89.58 9.39.58 9.39.58s7.5 0 9.39-.58a3 3 0 0 0 2.11-2.12A31 31 0 0 0 24 12a31 31 0 0 0-.5-5.8ZM9.6 15.6V8.4l6.3 3.6Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.5 6.2a3 3 0 0 0-2.11-2.12C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.39.58A3 3 0 0 0 .5 6.2 31 31 0 0 0 0 12a31 31 0 0 0 .5 5.8 3 3 0 0 0 2.11 2.12c1.89.58 9.39.58 9.39.58s7.5 0 9.39-.58a3 3 0 0 0 2.11-2.12A31 31 0 0 0 24 12a31 31 0 0 0-.5-5.8ZM9.6 15.6V8.4l6.3 3.6Z'/%3E%3C/svg%3E");
}
.social--tiktok::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12.53.02C13.84 0 15.14.01 16.44 0c.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12.53.02C13.84 0 15.14.01 16.44 0c.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z'/%3E%3C/svg%3E");
}
.footer-copyright {
  margin: 0;
  /* Chrome text (fixed light), not the inherited mode-dependent --pm-text —
     that one is near-black in light mode and unreadable on the petróleo
     footer bg. Matches the footer links' treatment. */
  color: var(--pm-chrome-text-muted);
}
.footer-brand {
  display: inline-flex;
  opacity: 0.85;
}
.footer-brand:hover {
  opacity: 1;
}
.footer-logo {
  display: block;
  height: 28px;
  width: auto;
}

@media (min-width: 768px) {
  .footer-links ul {
    justify-content: flex-start;
  }
  .footer-meta {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }
}

/* Desktop app-shell (S15 full-bleed): footer has no single wrapping
   container (.footer-links and .footer-meta are direct siblings of
   <footer>), so cap each row individually — otherwise `justify-content:
   space-between` on .footer-meta spreads the social icons and brand
   mark across the full viewport width once footer stops being
   constrained by the old 900/1000px .app-wrapper column. */
@media (min-width: 1024px) {
  footer {
    padding-inline: var(--space-lg);
  }
  .footer-links,
  .footer-meta {
    max-width: 1280px;
    margin-inline: auto;
  }
}

/* ===================================
   CHROME — CORRECCIONES DE MODO CLARO (S15 polish, 2026-07-18, ítem 2)
   El fix del ítem 1 (--pm-accent por default pasa de verde a Petróleo
   en claro) deja --pm-accent EXACTAMENTE igual a --pm-chrome-bg del
   header/footer/sidebar (ambos #1F6F8B). Cualquier regla que pinte
   texto/pill con --pm-accent sobre --pm-chrome-bg (marca decorativa,
   hovers, ítem activo del nav) quedaría invisible en claro.
   Scoped con :root:not([data-theme="dark"]) — mayor especificidad que
   las reglas de arriba, así que gana solo en claro; en oscuro no matchea
   nada y las reglas originales (arriba) siguen aplicando tal cual, sin
   tocar ni un pixel del oscuro. Los 5 skins (SkinRegistry) no colisionan
   — solo el pool sin skin (--pm-accent = Petróleo) lo hace.
   =================================== */
:root:not([data-theme="dark"]) .site-brand-accent {
  /* Rebrand MiPodio: el "Podio" ya va en lime (--pm-accent-fill) en la regla
     base, visible sobre el chrome petróleo también en claro — ya no hace
     falta forzarlo a blanco como con el "2026" viejo. */
  color: var(--pm-accent-fill);
}
/* Ítem activo del sidebar-nav: en claro, --pm-accent queda idéntico a
   --pm-chrome-bg (ambos Petróleo #1F6F8B) y los ítems inactivos ya son
   texto blanco sobre ese mismo Petróleo — un chip sólido en --pm-accent
   con texto --pm-accent-contrast sería invisible (fondo igual al
   contenedor, texto igual a los hermanos). En su lugar usamos un tinte
   blanco translúcido que SIEMPRE contrasta contra el chrome Petróleo,
   manteniendo el texto blanco (--pm-chrome-text). Esto rige tanto el
   rail lateral como la bottom tab bar en móvil. */
:root:not([data-theme="dark"]) .sidebar-nav-item.is-active,
:root:not([data-theme="dark"]) .sidebar-nav-more-link.is-active {
  color: var(--pm-chrome-text);
  background: rgba(255, 255, 255, 0.16);
}

/* ===================================
   DRUPAL CORE FORMS (login/registro, dark)
   Scoped to core form classes — .score-field/.classifier-option and
   the app's own inputs don't carry these, so they're unaffected.
   =================================== */

.form-item {
  margin-bottom: var(--space-md);
}
.form-item label {
  display: block;
  margin-bottom: 4px;
  font-size: 13px;
  font-weight: 600;
  color: var(--pm-text);
}
.form-text,
.form-email,
.form-password,
.form-select,
.form-textarea {
  width: 100%;
  max-width: 420px;
  padding: 8px 10px;
  background: var(--pm-field-bg);
  color: var(--pm-text);
  border: 1px solid var(--pm-field-border);
  border-radius: 8px;
  font-size: 14px;
}
.form-text:focus,
.form-email:focus,
.form-password:focus,
.form-select:focus,
.form-textarea:focus {
  outline: 2px solid var(--pm-accent);
  outline-offset: 1px;
}
.form-item .description {
  margin-top: 4px;
  font-size: 12px;
  color: var(--pm-text-muted);
}
.form-submit {
  padding: 8px 16px;
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
  border: none;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

/* ===================================
   POINTS BREAKDOWN TOOLTIP (S10-T7)
   =================================== */

.points-with-tooltip {
  position: relative;
  cursor: help;
  border-bottom: 1px dotted var(--pm-accent);
  border-radius: 2px;
}
.points-with-tooltip:focus-visible {
  outline: 1px dotted var(--pm-accent);
  outline-offset: 2px;
}
/* Positioning (top/left) is computed by JS on open — see match-detail.js
   / user-detail.js — from the trigger's getBoundingClientRect(), then set
   inline. `position: fixed` (rather than the old `position: absolute` +
   `bottom: calc(100% + 6px)`) is required so the tooltip escapes the
   predictions table wrap's overflow box (`.pmd-table-wrap`/
   `.pud-table-wrap` set `overflow-x: auto`, which per the CSS overflow
   spec also computes `overflow-y` to `auto` when unset — clipping any
   absolutely-positioned child that overflows the wrap's bounds) instead
   of clipping/pushing off-viewport for rows near the top of the table. */
.points-tooltip {
  display: none;
  position: fixed;
  z-index: 1200;
  min-width: 200px;
  padding: var(--space-sm) var(--space-md);
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 8px;
  box-shadow: var(--shadow-lg);
  font-size: 12px;
  font-weight: 400;
  text-align: left;
  white-space: nowrap;
}
.points-tooltip.points-tooltip--open {
  display: block;
}
.points-tooltip-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  color: var(--pm-text);
}
.points-tooltip-total {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid var(--pm-card-border);
  font-weight: 700;
}

/* Mensajes de status de Drupal (status_messages), inyectados por
   pool_mobile_preprocess_page() al tope del contenido. Estilo dark
   "Noche de estadio", patrón tarjeta con borde-acento a la izquierda. */
.messages {
  border: 1px solid var(--pm-card-border);
  border-left: 4px solid var(--pm-text-muted);
  background: var(--pm-card-bg);
  color: var(--pm-text);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  margin: 0 0 1rem;
  box-shadow: var(--pm-card-shadow);
  max-width: 100%;
}
.messages a {
  color: var(--pm-accent);
}
.messages--status {
  border-left-color: var(--color-primary);
}
.messages--warning {
  border-left-color: var(--color-warning);
}
.messages--error {
  border-left-color: var(--color-danger);
}
.messages-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.messages-list__item + .messages-list__item {
  margin-top: 0.4rem;
}

/* ============ Auth pages (login/registro) — dark "Noche de estadio" ============
   Estructura real del DOM (confirmada contra el HTML servido, no asumida):
   .app-content.auth-main > [ .auth-tabs (tabs, van en el #prefix del form),
   .openid-connect-login-form (mini-form separado que openid_connect ya
   renderiza en ese mismo #prefix, ANTES del form de login/registro — NO
   anidado dentro de .auth-form), .auth-form (el form real de Drupal, con el
   divisor como primer elemento por weight) ].
   .auth-main agrupa todo lo anterior; .auth-form solo aporta los campos.
   S11: .auth-main vive DENTRO del flujo normal de contenido (.app-content,
   con header/footer del sitio alrededor), centrado con margin auto +
   max-width, sin min-height:100vh ni fondo propio.
   S11 fix (auth-layout-fluid): se removió el fondo/borde/radio/sombra de
   tarjeta de .auth-main — el contenido queda fluido con el fondo de la
   página (--color-white), igual que el resto de páginas del sitio, en vez
   de flotar como un doble contenedor tipo popup. En oscuro, el fondo de
   página y --pm-field-bg comparten el mismo tono (#0F1B2D), así que
   .auth-main redefine --pm-field-bg a --color-chrome (#0A1420) solo en
   este scope para que los campos (.auth-form/.account-form) se sigan
   leyendo como inputs por contraste de fondo, además del borde
   --pm-field-border que ya tenían (S15-T1: en claro --color-chrome y
   --pm-field-bg ya son el mismo blanco, así que este override es un
   no-op inofensivo en ese modo, no un problema); el token global
   --pm-field-bg no se toca, así que el resto del sitio
   (dashboard, predicciones) no se ve afectado.
   El wordmark "POOL 2026" se sacó del #prefix del form
   (_pool_mobile_auth_form_common()) porque ahora lo aporta el header del
   sitio; quedan los tabs. .auth-brand/.auth-wordmark siguen en uso —
   pool_mobile_form_user_form_alter() los reutiliza para el título
   "My Account" de Mi cuenta (título de página, no marca del sitio, así que
   se conserva); .auth-wordmark-year y .auth-tagline eran exclusivos del
   wordmark "POOL 2026" ya removido y no los usa nadie más. */
.auth-main {
  width: 100%;
  max-width: 420px;
  margin: 2.5rem auto;
  color: var(--pm-text);
  --pm-field-bg: var(--color-chrome);
  /* Own inline padding so the ≥1024 `.app-content` centering calc
     (padding-inline grows with viewport) does NOT apply here — .app-content
     and .auth-main are the SAME element (<main class="app-content auth-main">),
     and on wide screens that calc collapsed this 420px card's content to 0.
     Source order (this rule is later) wins at equal specificity. */
  padding-inline: var(--space-md);
}
.auth-brand {
  text-align: center;
  margin-bottom: 1.5rem;
}
.auth-wordmark {
  font-family: 'ScoreboardCondensed', sans-serif;
  font-weight: 800;
  font-size: 2.25rem;
  letter-spacing: 0.04em;
  color: var(--pm-text);
  display: block;
  line-height: 1.1;
}

/* Tabs (links) — solo uno queda "activo" según la ruta actual. */
.auth-tabs {
  display: flex;
  gap: 0;
  margin-bottom: 1.5rem;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--pm-card-border);
}
.auth-tab {
  flex: 1;
  text-align: center;
  padding: 0.6rem 0.5rem;
  text-decoration: none;
  color: var(--pm-text-muted);
  font-weight: 600;
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: transparent;
}
.auth-tab.is-active {
  background: var(--color-chrome);
  color: var(--pm-text);
}
.auth-tab:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: -2px;
}

/* Botón de Google — openid_connect renderiza su propio <form> separado
   (hermano de .auth-form, no descendiente) inyectado vía #prefix antes del
   form de login/registro. Selectores verificados contra el markup real
   (Step 1): id único del input submit + la clase del form que lo envuelve
   como respaldo. Estilo de marca de Google (blanco, logo "G" multicolor) —
   NO se recolorea con el acento del tema. */
.openid-connect-login-form {
  margin: 0 0 1.25rem;
}
.openid-connect-login-form input[type="submit"],
#edit-openid-connect-client-google-login {
  width: 100%;
  padding: 0.7rem 1rem 0.7rem 2.75rem;
  border-radius: 8px;
  background-color: #ffffff;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23FFC107' d='M43.6 20.5H42V20H24v8h11.3c-1.7 4.9-6.1 8-11.3 8-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.7-.4-3.5z'/%3E%3Cpath fill='%23FF3D00' d='M6.3 14.7l6.6 4.8C14.5 16.1 18.9 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 16.3 4 9.7 8.3 6.3 14.7z'/%3E%3Cpath fill='%234CAF50' d='M24 44c5.2 0 9.9-2 13.4-5.2l-6.2-5.2C29.2 35.4 26.7 36 24 36c-5.2 0-9.6-3.1-11.3-7.6l-6.5 5C9.5 39.6 16.2 44 24 44z'/%3E%3Cpath fill='%231976D2' d='M43.6 20.5H42V20H24v8h11.3c-.9 2.6-2.6 4.8-4.9 6.4l6.2 5.2C39.9 36.6 44 31 44 24c0-1.3-.1-2.7-.4-3.5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 0.9rem center;
  background-size: 18px 18px;
  color: #3c4043;
  border: 1px solid #dadce0;
  font-weight: 600;
  font-size: 0.9rem;
  text-align: center;
  cursor: pointer;
}
.openid-connect-login-form input[type="submit"]:hover {
  background-color: #f8f9fa;
}
.openid-connect-login-form input[type="submit"]:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}

/* Divisor entre el botón de Google y los campos de email/password. */
.auth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 1.1rem;
  color: var(--pm-text-muted);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--pm-card-border);
}

/* Campos del form real (name/pass) — reusa el estilo de field oscuro del
   tema (mismos tokens que las tarjetas de predicción). */
.auth-form {
  width: 100%;
}
.auth-form .form-item {
  margin-bottom: 1rem;
}
.auth-form label {
  display: block;
  color: var(--pm-text);
  font-weight: 600;
  font-size: 0.9rem;
  margin-bottom: 0.4rem;
}
.auth-form input[type="text"],
.auth-form input[type="email"],
.auth-form input[type="password"] {
  width: 100%;
  background: var(--pm-field-bg);
  border: 1px solid var(--pm-field-border);
  color: var(--pm-text);
  border-radius: 8px;
  padding: 0.65rem 0.75rem;
  font-size: 1rem;
}
.auth-form input[type="text"]::placeholder,
.auth-form input[type="email"]::placeholder,
.auth-form input[type="password"]::placeholder {
  color: var(--pm-text-muted);
}
.auth-form input:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 1px;
}

/* Textarea + checkbox (S25-T4: CreatePoolForm's description/
   requires_approval are the first .auth-form consumers with these field
   types — login/register/account-edit never had them). */
.auth-form textarea {
  width: 100%;
  background: var(--pm-field-bg);
  border: 1px solid var(--pm-field-border);
  color: var(--pm-text);
  border-radius: 8px;
  padding: 0.65rem 0.75rem;
  font-size: 1rem;
  font-family: inherit;
  resize: vertical;
}
.auth-form textarea::placeholder {
  color: var(--pm-text-muted);
}
.auth-form textarea:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 1px;
}
.auth-form .description {
  color: var(--pm-text-muted);
  font-size: 0.8rem;
  margin-top: 0.35rem;
}
.auth-form input[type="checkbox"] {
  accent-color: var(--pm-accent-fill);
  width: 18px;
  height: 18px;
}
/* Checkbox rows read label-after-input (Drupal's own order for
   #type=checkbox) -- override the block/margin label rule above just for
   the item that has one, instead of assuming a specific Drupal wrapper
   class name that can change between core versions. */
.auth-form .form-item:has(input[type="checkbox"]) {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.auth-form .form-item:has(input[type="checkbox"]) label {
  display: inline;
  margin-bottom: 0;
  font-weight: 500;
}

/* Show-password + forgot link. */
.auth-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: -0.25rem 0 1.25rem;
  font-size: 0.82rem;
}
.auth-show-pw {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--pm-text-muted);
  cursor: pointer;
}
.auth-show-pw input[type="checkbox"] {
  accent-color: var(--pm-accent-fill);
}
.auth-forgot {
  color: var(--pm-accent);
  text-decoration: none;
}
.auth-forgot:hover {
  text-decoration: underline;
}
.auth-forgot:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}

/* Submit relleno con el acento del tema. */
.auth-form input[type="submit"]#edit-submit,
.auth-submit {
  width: 100%;
  padding: 0.75rem;
  border: none;
  border-radius: 8px;
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
  font-weight: 700;
  font-size: 1rem;
  cursor: pointer;
}
.auth-form input[type="submit"]#edit-submit:hover,
.auth-submit:hover {
  background: var(--pm-accent-fill);
  filter: brightness(1.08);
}
.auth-form input[type="submit"]#edit-submit:focus-visible,
.auth-submit:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}
.auth-form .form-actions {
  margin-top: 0.25rem;
}

/* Aviso de verificación de email (registro). */
.auth-notice {
  color: var(--pm-text-muted);
  font-size: 0.85rem;
  margin: 0.75rem 0 0;
}

/* Mi cuenta (entity.user.edit_form, S11-T3) — mismo .auth-form de
   login/registro, extendido con los tipos de campo que ese form no tiene:
   select (idioma — timezone se sacó en S21-T2), file + preview (avatar)
   y el link de reseteo de password para cuentas de Google. */

/* Field order (S24): Display name is the user-facing identity, so it leads
   the form — above the email/password ('account') fieldset. Done with flex
   `order` because Drupal re-applies the entity form's field weights after
   the theme form_alter runs, so a PHP #weight isn't honored here. The form
   is a flex column; only display name is pulled up (order:-1) and the
   actions stay last (order:1); everything else keeps source order. */
.account-form {
  display: flex;
  flex-direction: column;
}
.account-form > [data-drupal-selector="edit-field-display-name-wrapper"] {
  order: -1;
}
.account-form > [data-drupal-selector="edit-actions"] {
  order: 1;
}

.account-form select {
  width: 100%;
  background: var(--pm-field-bg);
  border: 1px solid var(--pm-field-border);
  color: var(--pm-text);
  border-radius: 8px;
  padding: 0.65rem 0.75rem;
  font-size: 1rem;
}
.account-form select:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 1px;
}
.account-form .description {
  color: var(--pm-text-muted);
  font-size: 0.8rem;
  margin-top: 0.35rem;
}
.account-form .image-widget img {
  display: block;
  max-width: 96px;
  border-radius: 50%;
  margin-bottom: 0.5rem;
}
.account-form input[type="file"] {
  width: 100%;
  color: var(--pm-text-muted);
  font-size: 0.85rem;
}
.account-form input[type="file"]::file-selector-button {
  padding: 0.45rem 0.8rem;
  margin-right: 0.6rem;
  border-radius: 8px;
  border: 1px solid var(--pm-field-border);
  background: var(--pm-field-bg);
  color: var(--pm-text);
  font-weight: 600;
  font-size: 0.8rem;
  cursor: pointer;
}
.account-form input[type="file"]::file-selector-button:hover {
  border-color: var(--pm-accent);
}
.account-google-notice {
  margin-bottom: 1rem;
}

/* Progressive-disclosure toggles for email/password on Mi cuenta
   (S11-T4) — same checkbox look as .auth-show-pw on login. */
.account-field-toggle {
  margin-bottom: 0.25rem;
}
.account-current-value {
  color: var(--pm-text-muted);
  font-size: 0.85rem;
  margin: 0 0 0.4rem;
}
.account-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--pm-text-muted);
  font-size: 0.85rem;
  cursor: pointer;
  margin-bottom: 0.75rem;
}
.account-toggle input[type="checkbox"] {
  accent-color: var(--pm-accent-fill);
}

/* Light/dark toggle on Mi cuenta (S15-T2) used to live here as a
   segmented control (.account-theme-mode/.account-theme-toggle/
   .account-theme-option). Moved out in S21-T1 into the shell-wide
   settings gear (removed S23-T3) — see SHARED SETTINGS CONTROLS below
   for the equivalent .sg-seg/.sg-select rules (same segmented-control
   shape, new class names). */
.account-form input[type="submit"] {
  padding: 0.5rem 0.9rem;
  border-radius: 8px;
  border: 1px solid var(--pm-field-border);
  background: var(--pm-field-bg);
  color: var(--pm-text);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
}
.account-form input[type="submit"]:hover {
  border-color: var(--pm-accent);
}
/* El submit principal (#edit-submit) mantiene el relleno de acento — el
   selector por id de .auth-form ya gana por especificidad sobre la regla
   genérica de arriba, sin necesitar !important. */

@media (prefers-reduced-motion: reduce) {
  .auth-form * {
    transition: none !important;
  }
}

/* ===================================
   POOL "CONFIGURADORES" (S25-T4): the zone below CreatePoolForm's basic
   fields that holds the match-selection configurador (and, later, any
   other create/edit-time config block). Each configurador is a summary
   row (.match-configurator-summary) that opens a full-screen sheet
   (mobile) / centered modal (desktop) — the drill-down content inside
   the sheet is .match-selector, styled in css/match-selector.css
   (S25-T3); this block only owns the summary row + the sheet/modal shell
   around it. Token-based only, no new tokens declared — both themes fall
   out of the existing --pm-* cascade.
   =================================== */
.pool-configurators {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-top: var(--space-md);
  /* Bottom breathing room before the form actions (Create/Cancel), S26
     fix #4 — this zone used to sit flush against the buttons. Matches the
     1rem/--space-md rhythm .auth-form .form-item already uses between
     fields, so the configurador reads as just another field in the
     form's vertical cadence. */
  margin-bottom: var(--space-md);
}

.match-configurator {
  position: relative;
}
.match-configurator-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  width: 100%;
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  padding: var(--space-md);
  font: inherit;
  color: var(--pm-text);
  text-align: left;
  cursor: pointer;
  box-shadow: var(--pm-card-shadow);
}
.match-configurator-summary:hover {
  border-color: var(--pm-accent);
}
.match-configurator-summary:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}
.match-configurator-summary-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.match-configurator-summary-label {
  font-weight: 700;
  font-size: 0.95rem;
}
.match-configurator-summary-state {
  font-size: 0.82rem;
  color: var(--pm-text-muted);
}
.match-configurator-summary-edit {
  flex: none;
  color: var(--pm-accent);
  font-weight: 600;
  font-size: 0.85rem;
}

/* Locked state (S25-T5): a pool already in play (PoolService::isInPlay(),
   S22) renders the summary row read-only — no hover affordance, no "Edit"
   trigger (dropped in the Twig, not just hidden) — plus a message below,
   same 12px/muted register `.form-item .description` uses for the
   disabled bonus checkboxes on the same lock (not reused directly: this
   message sits outside any .form-item, it's a theme-element sibling). */
.match-configurator.is-locked .match-configurator-summary {
  cursor: default;
  opacity: 0.7;
}
.match-configurator.is-locked .match-configurator-summary:hover {
  border-color: var(--pm-card-border);
}
.match-configurator-locked-message {
  margin-top: 4px;
  font-size: 12px;
  color: var(--pm-text-muted);
}

/* Backdrop + sheet, mobile-first: a true full-screen sheet (position:
   fixed; inset:0), same z-index tier as the header's own dropdown panels
   (.profile-menu-panel/.notification-bell-panel sit at 1000+, see the
   comment on .site-header above) so it always paints on top regardless
   of where in the DOM this configurador lives. */
.match-configurator-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(6, 14, 24, 0.55);
  z-index: 1000;
}
.match-configurator-sheet {
  position: fixed;
  inset: 0;
  z-index: 1001;
  display: flex;
  flex-direction: column;
  background: var(--pm-card-bg);
  color: var(--pm-text);
}
.match-configurator-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-md);
  border-bottom: 1px solid var(--pm-card-border);
  font-weight: 700;
  font-size: 1rem;
  flex: none;
}
.match-configurator-sheet-close {
  flex: none;
  border: none;
  background: transparent;
  color: var(--pm-text-muted);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 6px;
}
.match-configurator-sheet-close:hover {
  background: var(--pm-surface-2);
  color: var(--pm-text);
}
.match-configurator-sheet-close:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}
.match-configurator-sheet-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md);
}
.match-configurator-sheet-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-md);
  border-top: 1px solid var(--pm-card-border);
  flex: none;
}
.match-configurator-done {
  flex: none;
}

/* Desktop: centered modal instead of a full-screen sheet — same markup,
   split purely by media query (mirrors .settings-gear-panel /
   .profile-menu-panel's own popover-vs-modal split, S21-T5). */
@media (min-width: 768px) {
  .match-configurator-sheet {
    inset: auto;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(480px, calc(100vw - 2 * var(--space-lg)));
    max-height: min(640px, calc(100vh - 2 * var(--space-lg)));
    border: 1px solid var(--pm-card-border);
    border-radius: 16px;
    box-shadow: var(--shadow-panel);
  }
}

@media (prefers-reduced-motion: reduce) {
  .match-configurator-sheet * {
    transition: none !important;
  }
}

/* ===================================
   DANGER ZONE (EditPoolForm delete section, S26)
   Was raw/unstyled Drupal markup — a bare <details> collapsible with plain
   <p> copy, Save/Cancel crammed right underneath. Uses the semantic danger
   accent (--color-danger / --color-danger-bg) to delineate the section,
   NOT --pm-accent (petróleo) — this must read as "careful" independent of
   the pool's own theme color. Delete button reuses the existing .btn/
   .btn-danger atoms (S15, --pm-danger tokens) rather than introducing a
   third red. margin-bottom on .danger-zone is what gives the Save/Cancel
   actions below it real breathing room again.
   =================================== */
.danger-zone {
  margin-top: var(--space-lg);
  margin-bottom: var(--space-lg);
  padding: var(--space-md);
  border: 1px solid var(--color-danger);
  border-radius: 8px;
  background: var(--color-danger-bg);
}
.danger-zone > summary {
  font-weight: 600;
  color: var(--color-danger);
  cursor: pointer;
}
.danger-zone__copy {
  margin: var(--space-sm) 0 0;
  color: var(--pm-text);
  font-size: 14px;
  line-height: 1.5;
}
/* Calm, not alarming: reuses the existing .alert-info hint tokens, not the
   danger palette — this is an explanatory state (predictions exist), not a
   destructive one. */
.danger-zone__notice {
  margin: var(--space-sm) 0 0;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--pm-hint);
  border-radius: 6px;
  background: var(--pm-hint-bg);
  color: var(--pm-hint);
  font-size: 14px;
  line-height: 1.5;
}
/* Drupal's #type => submit auto-adds a 'form-submit' class, and the
   generic '.form-submit' rule (above, ~line 3337) shares .btn-danger's
   specificity but sits later in source order, so it silently overrode
   the delete button back to the accent teal. Re-declare background/
   color/border here at higher specificity (.danger-zone .btn-danger, two
   classes) so this button wins regardless of Drupal's own class or where
   .form-submit happens to sit in the cascade. */
.danger-zone .btn-danger {
  margin-top: var(--space-md);
  background: var(--pm-danger);
  color: var(--pm-danger-ink);
  border-color: var(--pm-danger);
}
.danger-zone .btn-danger:hover {
  background: var(--pm-danger-hover);
  border-color: var(--pm-danger-hover);
  color: var(--pm-danger-ink);
}

/* ===================================
   NON-MEMBER STATES: preview + request-join (Capa 2, B3, 2026-07-16)
   pool-preview.html.twig (public preview) and request-join.html.twig
   (request/pending/removed) — both sit inside .pm-pool-context (the pool
   skin applies) but never render the pool menu, see pool-sidebar.html.twig's
   `pool.status == 'approved'` gate. Reuses the existing .card/.btn/.alert
   atoms wherever possible; only the layout wrappers below are new.
   =================================== */
.pool-nonmember-page {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-md);
  max-width: 640px;
  margin: 0 auto;
}
.pool-identity {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
.pool-identity-text h1,
.pool-request-join h1 {
  margin-bottom: 2px;
}

/* CTA card — the "want to join?" / "join this pool" prompt. Same .card
   atom as the dashboard's pool cards, with an accent-tinted border/bg so
   it reads as the primary action on the page (mirrors the design ref's
   frame a "¿Te sumás?" card). */
.pool-cta-card {
  align-items: center;
  text-align: center;
  /* De-greened to match the pool welcome banner: soft teal/petrol glow
     over the card bg, neutral border — no lime as a background fill. */
  border-color: var(--pm-card-border);
  background:
    linear-gradient(160deg, var(--pm-atmospheric-glow), transparent 62%),
    var(--pm-card-bg);
}
.pool-cta-card p {
  margin-bottom: 0;
  color: var(--pm-text-muted);
  font-size: 13px;
}
.pool-cta-hint {
  font-size: 12px;
}
.pool-cta-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  width: 100%;
}
@media (min-width: 480px) {
  .pool-cta-actions {
    flex-direction: row;
    justify-content: center;
  }
  .pool-cta-actions .btn,
  .pool-cta-actions button {
    flex: 0 1 auto;
  }
}

/* Status card — "pending approval" / "removed" states on request-join. */
.pool-status-card {
  align-items: center;
  text-align: center;
}
.pool-status-card p {
  margin-bottom: 0;
  color: var(--pm-text-muted);
  font-size: 13px;
}
.pool-status-card .alert {
  align-self: center;
}

.pool-nonmember-back {
  text-align: center;
  margin-bottom: 0;
}

.pool-preview-ranking-card h2 {
  margin-bottom: var(--space-sm);
}
/* "Join to see the full ranking" note under the teaser table (S82). */
.pool-preview-ranking-more {
  margin: var(--space-sm) 0 0 0;
  color: var(--pm-text-muted);
  font-size: 12px;
  text-align: center;
}

/* Preview conversion showcase (S82). */

/* "Oficial" badge next to the pool name. Teal/petróleo chip with a lime
   accent border — never a lime background fill (project rule). */
.pool-official-badge {
  display: inline-block;
  vertical-align: middle;
  margin-left: 8px;
  padding: 2px 10px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--pm-accent) 55%, transparent);
  background: var(--pm-surface-2);
  color: var(--pm-text);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Active-bonus chips. Neutral chips, token-driven — no lime fill. */
.pool-preview-bonuses {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: var(--space-sm) 0 0 0;
  padding: 0;
  list-style: none;
}
.pool-preview-bonus-chip {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid var(--pm-card-border);
  background: var(--pm-surface-2);
  color: var(--pm-text);
  font-size: 13px;
  font-weight: 600;
}
/* Clickable chip variant (S82 redesign): each active bonus links to its
   `bono` landing page (/reglas/bonos/{slug}). A trailing chevron plus an
   accent hover/focus state make the affordance visible without turning
   the chip into a lime fill (project rule: lime never as background). */
a.pool-preview-bonus-chip--link {
  text-decoration: none;
  transition: border-color 0.15s ease, color 0.15s ease;
}
a.pool-preview-bonus-chip--link::after {
  content: ' ›';
  color: var(--pm-text-muted);
}
a.pool-preview-bonus-chip--link:hover,
a.pool-preview-bonus-chip--link:focus-visible {
  border-color: color-mix(in srgb, var(--pm-accent) 55%, transparent);
  color: var(--pm-accent);
}
a.pool-preview-bonus-chip--link:hover::after,
a.pool-preview-bonus-chip--link:focus-visible::after {
  color: var(--pm-accent);
}

/* Hero stat panel (S82 redesign). Reuses the pool-home welcome banner
   treatment (.pool-home-banner gradient + .pool-home-stats big-number/
   small-label atoms) but stacked: identity row on top, a full-width
   pool-level stat strip (players / matches / bonuses) below, separated
   by a hairline. Stats are centered — this is a showcase panel, not the
   member's personal greeting. */
/* Double class: this section sits BEFORE the POOL HOME section in this
   file, so .pool-home-banner's own align-items: center would win a
   same-specificity tie — the extra class makes the override stick. */
.pool-preview-hero.pool-home-banner {
  flex-direction: column;
  align-items: stretch;
}
.pool-preview-stats {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--pm-card-border);
}
.pool-preview-stats .pool-home-stat {
  flex: 1 1 0;
  align-items: center;
  text-align: center;
}
.pool-preview-stats .pool-home-stat-value {
  justify-content: center;
}

/* ===================================
   POOL HOME ("Inicio del Pool", B4 rebuild, S14)
   Shared shell rendered by pool-view.html.twig; four inner fragments
   (pool-home-banner-stats/next-match-score/mini-ranking-you/
   pending-requests) are #lazy_builder placeholders — same classes as
   their parent card so no extra CSS is needed for them specifically.
   =================================== */
.pool-home {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

/* 0. Championship identity hero — REMOVED in the image-model rework. The
   big banner strip on the pool Inicio is gone; the banner image now lives
   on the Home championships slider card (.champ-card__banner), and the
   Inicio welcome block instead carries a subtle diagonal accent
   (.champ-diagonal). */

/* 1. Banner + stats. Lays out in a row: the welcome/stats block on the
   left (.pool-home-banner-main) and the "Invitar amigos" action on the
   right (.pool-home-banner-aside) — the invite trigger moved here out of
   the grid. Wraps to a stacked column on narrow widths.

   Background: a soft teal/petrol glow (--pm-atmospheric-glow, a fixed
   teal tone in BOTH themes) over the card bg, NOT the old lime accent
   tint — --pm-accent is lime in dark mode and read as too strong/green
   here. Same de-greening applied to the home CTA band
   (.pm-paragraph--atmospheric--home_cta); neutral card border so no lime
   remains on this surface. */
.pool-home-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  flex-wrap: wrap;
  padding: var(--space-lg);
  border-radius: 12px;
  border: 1px solid var(--pm-card-border);
  background:
    linear-gradient(160deg, var(--pm-atmospheric-glow), transparent 62%),
    var(--pm-card-bg);
}
.pool-home-banner-main {
  flex: 1 1 260px;
  min-width: 0;
}
.pool-home-banner-aside {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-sm);
}
.pool-home-invite-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}
.pool-home-invite-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}
@media (max-width: 560px) {
  /* Stack: action drops below the greeting. Keep it a COMPACT, left-
     aligned button — a full-width lime slab dominated the whole banner on
     mobile. Auto width + smaller padding/font so it reads as a clear CTA
     without taking over. */
  .pool-home-banner-aside {
    align-items: flex-start;
    width: 100%;
  }
  .pool-home-invite-btn {
    padding: 6px 12px;
    font-size: 13px;
  }
}
.pool-home-greeting {
  margin: 0 0 var(--space-sm) 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: var(--font-size-xl);
  color: var(--pm-text);
}
.pool-home-banner-meta {
  margin: 0 0 var(--space-md) 0;
  color: var(--pm-text-muted);
  font-size: 13px;
}
.pool-home-stats {
  display: flex;
  gap: var(--space-lg);
}
.pool-home-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.pool-home-stat-value {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 800;
  font-size: 28px;
  color: var(--pm-accent);
  display: flex;
  align-items: center;
  gap: 4px;
}
.pool-home-stat-label {
  font-size: 12px;
  color: var(--pm-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* Own payment status chip (S27-T3, member-facing, pool home banner).
   Read-only — unlike .payment-pill (owner's tappable toggle on the
   member-management table), this is never clickable, so no cursor or
   focus-visible styling. Same pill shape and same semantic colors as
   .payment-pill for visual consistency: confirmed/paid reuses
   --pm-points/--pm-points-bg (the site's "positive achievement" green),
   pending reuses the muted --pm-surface-2/--pm-text-muted pair. */
.pool-home-payment-chip {
  display: inline-flex;
  align-items: center;
  margin-top: var(--space-sm);
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}
.pool-home-payment-chip--paid {
  background: var(--pm-points-bg);
  color: var(--pm-points);
}
.pool-home-payment-chip--pending {
  background: var(--pm-surface-2);
  color: var(--pm-text-muted);
}

/* Camino a la Final home reminder (N11). Two states: a persistent alert
   when the member hasn't loaded their prediction, and a soft info line once
   they have. Lime only as a thin accent border, never as a fill. */
.pool-home-structure-alert {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin: 0 0 var(--space-md) 0;
  padding: var(--space-sm) var(--space-md);
  border-radius: 10px;
  font-size: 14px;
  line-height: 1.35;
  background: var(--pm-notice-bg);
  color: var(--pm-text);
}
.pool-home-structure-alert__icon {
  flex: none;
  font-size: 16px;
  line-height: 1;
}
.pool-home-structure-alert--missing {
  border-left: 4px solid var(--pm-accent-fill, #c6f24e);
  font-weight: 600;
}
.pool-home-structure-alert--missing .pool-home-structure-alert__icon {
  color: var(--pm-accent, #c6f24e);
}
.pool-home-structure-alert--loaded {
  border-left: 4px solid var(--pm-card-border);
  color: var(--pm-text-muted);
}
.pool-home-structure-alert--loaded .pool-home-structure-alert__icon {
  color: var(--pm-points, #2e7d32);
}
/* Subtle informational note on pool-home cards (S84): neutral info tone —
   NOT an error, NOT a warning. Muted copy on the soft surface, with the
   text accent (teal light / lime dark) reserved for the small "i" icon;
   never a lime fill. Used for the Camino card's "unlocks when…",
   "Closes on…" and "Closed on…" lines. */
.pool-home-info-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: var(--space-sm) 0 0 0;
  padding: var(--space-sm) var(--space-md);
  border-radius: 8px;
  background: var(--pm-notice-bg);
  color: var(--pm-text-muted);
  font-size: 13px;
  line-height: 1.4;
}
.pool-home-info-note__icon {
  flex: none;
  width: 15px;
  height: 15px;
  margin-top: 1px;
  color: var(--pm-accent);
}

/* Generic card used by every non-banner section. */
.pool-home-card {
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  padding: var(--space-md);
  background: var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
}
.pool-home-card-title {
  margin: 0 0 var(--space-sm) 0;
  font-size: var(--font-size-lg);
  color: var(--pm-text);
}

/* Responsive 3/2/1 masonry grid (2026-07-27 rework, on top of the S16
   `.app-content` 1280 cap): the old layout hard-split the 4 cards into
   two `.pool-home-col` wrappers (2 cards each), so it could only ever
   render 1 or 2 columns — on wide desktop (.app-content capped at
   ~1232px content width) each column stretched to ~600px and the cards
   read as bloated/stretched instead of comfortably narrow. The 4 cards
   (next match, mini-ranking, rules, invite) are now DIRECT children of
   this container (see pool-view.html.twig) so column count can respond
   to width freely.

   CSS multi-column (not CSS Grid) on purpose: with exactly 4 cards a
   plain grid's row-major auto-placement leaves the 4th card alone on
   its own row with 2 empty slots at 3 columns — visibly unbalanced,
   and the cards' natural heights differ a lot (Reglas is short,
   Invitar amigos short-medium, Así va el pool medium, Próximo partido
   tall) so no fixed span reads right at every width/data combination.
   `column-width` lets the browser both (a) pick the column count from
   available width, same math auto-fit would use, and (b) BALANCE card
   distribution across columns by height (default `column-fill:
   balance` for an auto-height container) — the shortest column always
   gets the next card, so there's no lonely trailing card/empty region
   at any width; it reflows automatically as card content changes
   (e.g. bombazo rule text, ranking row count) instead of needing a
   hand-picked span to keep matching.

   320px + 24px gap (--space-lg) lands on: 1 col up to ~696px viewport
   (390 verified), 2 cols from ~696px up to ~1200px viewport content
   width (768 AND 1024 verified — .app-content's content area is
   ~976px at 1024, short of the ~1032px three 320px-columns need), 3
   cols once content width passes that (1440 verified, content capped
   at 1232px so it never exceeds 3 cols even on ultra-wide screens). */
.pool-home-grid {
  column-width: 320px;
  column-gap: var(--space-lg);
}
.pool-home-grid > .pool-home-card {
  break-inside: avoid;
  margin-bottom: var(--space-lg);
}
.pool-home-grid > .pool-home-card:last-child {
  margin-bottom: 0;
}

/* Pre-active pool home panel (S83, spec §6): the "You're signed up"
   status card shown while the championship is still registration_open.
   Neutral card tokens for the surface (NEVER lime as a panel fill —
   standing design rule); the accent token only colors the check mark. */
.pool-home-preactive {
  max-width: 560px;
  margin: 0 auto;
  text-align: center;
}
.pool-home-preactive-title {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
}
.pool-home-preactive-check {
  width: 22px;
  height: 22px;
  flex: none;
  color: var(--pm-accent);
}
.pool-home-preactive-body {
  margin: 0 0 var(--space-sm);
  color: var(--pm-text-muted);
}
.pool-home-preactive-date {
  margin: 0 0 var(--space-md);
  font-weight: 600;
}
.pool-home-preactive-invite {
  display: inline-block;
}

/* 2. Próximo partido */
/* Fills its .pool-home-grid column like every other .pool-home-card
   section (Reglas, Invitar, Así va el pool). A max-width + auto margin
   here (2026-07-24 redesign) capped it to 440px and centered it inside
   its ~560-644px column while its sibling cards filled the full column
   width — an inconsistent-width bug, not a safe density tweak. Density
   padding is kept; only the width cap is removed. */
.pool-home-next-match {
  padding: 12px;
}
.pool-home-next-match-teams {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}
.pool-home-next-match-team {
  display: flex;
  align-items: center;
  gap: 8px;
}
.pool-home-next-match-team:last-child {
  justify-content: flex-end;
}
.pool-home-next-match-vs {
  color: var(--pm-text-muted);
  font-size: 12px;
  text-transform: uppercase;
}
.pool-home-next-match-kickoff {
  margin: 0 0 var(--space-sm) 0;
  color: var(--pm-text-muted);
  font-size: 13px;
}
.pool-home-next-match-score-row {
  margin: 0 0 var(--space-sm) 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-lg);
  color: var(--pm-text);
}
.pool-home-your-score--empty {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--pm-text-muted);
}
.pool-home-close-timer {
  margin: 0 0 var(--space-md) 0;
  font-size: 13px;
  color: var(--pm-hint);
}
.pool-home-close-timer--closed {
  color: var(--pm-text-muted);
}
/* .pool-home-cta ("Completar predicciones") is an <a>, so the generic
   a:hover rule (LINKS section above) used to repaint its text
   --color-primary-dark on top of the still-petróleo background — same
   family of color, text effectively invisible. Its shape/color/hover now
   come entirely from the unified .btn/.btn-primary system above (Task 2,
   S15), whose grouped :hover selector has higher specificity than the
   bare a:hover and wins outright. */

/* 3. Mini-ranking "Así va el pool" */
.pool-home-rank-list {
  list-style: none;
  margin: 0 0 var(--space-sm) 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.pool-home-rank-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.pool-home-rank-row--me {
  background: var(--pm-row-me-bg);
  border-radius: 8px;
  padding: 4px 6px;
  margin: 0 -6px;
}
.pool-home-rank-pos {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  color: var(--pm-text-muted);
  width: 28px;
}
.pool-home-rank-name {
  flex: 1;
  color: var(--pm-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pool-home-rank-exact {
  font-size: 12px;
  color: var(--pm-text-muted);
}
.pool-home-rank-points {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  color: var(--pm-accent);
}
.pool-home-rank-link {
  color: var(--pm-accent);
  font-size: 13px;
  text-decoration: none;
}

/* 4. Reglas del pool */
.pool-home-rules-list {
  list-style: none;
  margin: 0 0 var(--space-sm) 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.pool-home-rules-list li {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  font-size: 14px;
  color: var(--pm-text);
}
.pool-home-rule-points {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  color: var(--pm-accent);
  white-space: nowrap;
}
/* "Bonos" sub-section (S43): icon + title per active bonus, replacing the
   old inline bombazo text block. Reuses .bonus-card__logo for the icon
   (already sized/tinted) — only the divider + compact row layout are new
   here. */
.pool-home-bonuses {
  margin: var(--space-sm) 0;
  padding-top: var(--space-sm);
  border-top: 1px solid var(--pm-card-border);
}
.pool-home-bonuses-title {
  margin: 0 0 var(--space-sm) 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-base);
  color: var(--pm-text);
}
.pool-home-bonus-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.pool-home-bonus-link {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  color: var(--pm-text);
  text-decoration: none;
}
.pool-home-bonus-label {
  font-size: 14px;
}
.pool-home-rules-link {
  color: var(--pm-accent);
  font-size: 13px;
  text-decoration: none;
}

/* 5. Invitar / compartir — reuses .pool-share-* from pool-share.js's
   markup (Task 2); only the card wrapper + pending-requests line below
   are new. */
.pool-home-pending-link {
  display: block;
  margin-top: var(--space-sm);
  color: var(--pm-hint);
  font-size: 13px;
  text-decoration: none;
}

.pool-home-leave {
  text-align: center;
}

/* ===================================
   SHARED SETTINGS CONTROLS (S21-T1..T5) — originally built for the
   floating shell-wide settings gear (templates/includes/settings-gear.html.twig,
   attached from footer.html.twig on every page). S23-T3 removed that
   floating mount now that S23-T1/T2 relocated appearance/timezone/language
   into the logged-in account menu's drill-down pickers (.pm-picker above,
   templates/includes/user-menu.html.twig) — .sg-seg and .sg-select below
   are the two rule sets that survive because the account menu still uses
   them; the gear's own bubble/backdrop/popover/drill-down chrome (that
   had no other consumer) was deleted with it. */

/* Segmented control — same shape the old Mi-cuenta theme toggle used
   (.account-theme-toggle/.account-theme-option, removed in S21-T1), new
   class names since this control isn't inside a form field anymore. Used
   by the appearance Light/Dark buttons (client-only, no navigation). */
.sg-seg {
  display: inline-flex;
  border: 1px solid var(--pm-field-border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--pm-field-bg);
}
.sg-seg button {
  display: inline-flex;
  align-items: center;
  border: none;
  background: transparent;
  color: var(--pm-text-muted);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.sg-seg button:not(:last-child) {
  border-right: 1px solid var(--pm-field-border);
}
.sg-seg button:hover {
  color: var(--pm-text);
}
.sg-seg button:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: -2px;
}
.sg-seg button.on {
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
}

/* Timezone/language <select> — same field look as .form-select
   (global.css above), scoped to the gear panel's narrower width instead
   of the 420px form max-width. Language (S21-T3) moved here from a
   .sg-seg <a> row in S21-T5 — a <select> scales to N enabled languages
   instead of a fixed 2-up segmented control. */
.sg-select {
  width: 100%;
  padding: 0.5rem 0.6rem;
  background: var(--pm-field-bg);
  color: var(--pm-text);
  border: 1px solid var(--pm-field-border);
  border-radius: 8px;
  font-size: 0.85rem;
}
.sg-select:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: -2px;
}

/* ===================================
   MERCURY PARAGRAPHS — basics: text/image/heading/cta (S29-T2)
   =================================== */

.pm-paragraph {
  margin: 0 0 var(--space-lg) 0;
}
.pm-paragraph:last-child {
  margin-bottom: 0;
}

/* text */
.pm-paragraph__text {
  color: var(--pm-text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
}
.pm-paragraph__text p {
  margin: 0 0 var(--space-md) 0;
}
.pm-paragraph__text p:last-child {
  margin-bottom: 0;
}
.pm-paragraph__text a {
  color: var(--pm-accent);
}
.pm-paragraph__text ul,
.pm-paragraph__text ol {
  margin: 0 0 var(--space-md) 0;
  padding-inline-start: 1.25em;
}
/* S53: reset.css sets a blanket `ul, ol { list-style: none; }`, so these
   full_html lists render with no visible marker. Scoped to
   .pm-paragraph__text only (full_html text-paragraph body copy) — every
   <ul> emitted by the Mercury build scripts already lives inside this
   class (build-reglas.php, build-bono-landings.php), so this restores
   bullets uniformly across those pages without touching <ul> elsewhere in
   the theme (nav, card lists, etc., which use their own classes/resets). */
.pm-paragraph__text ul {
  list-style: disc;
}

/* Callout blockquote + worked-example table (S51, the /reglas landing
   page's full_html text paragraphs — the only text-paragraph copy that
   emits these two tags today). Left-accent callout + a bordered,
   horizontally-scrollable table, reusing the same token vocabulary as the
   rest of the theme so both read correctly in light and dark mode. */
.pm-paragraph__text blockquote {
  position: relative;
  margin: 0 0 var(--space-md) 0;
  padding: var(--space-md) var(--space-md) var(--space-md) calc(var(--space-lg) + 22px);
  border: 1px solid var(--pm-card-border);
  border-left: 4px solid var(--pm-accent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--pm-accent) 7%, var(--pm-card-bg));
  color: var(--pm-text-muted);
}
.pm-paragraph__text blockquote::before {
  content: "i";
  position: absolute;
  left: var(--space-md);
  top: var(--space-md);
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--pm-accent);
  color: var(--pm-card-bg);
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: 15px;
  line-height: 1;
}
.pm-paragraph__text blockquote p {
  margin: 0;
}
.pm-paragraph__text blockquote strong {
  color: var(--pm-text);
}

/* Scorecard (S52, /reglas visual rework): points ARE the design language.
   The 4 scoring criteria become point cards, and the worked example
   becomes a scoreboard-style prediction list with graded point badges —
   replacing the flat bullet list + Word-like table. Token-driven, both
   themes. Custom HTML lives in the page's full_html text paragraph. */
.pm-score-cards {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-md);
  margin: 0 0 var(--space-md) 0;
}
.pm-score-card {
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  padding: var(--space-lg) var(--space-md);
  text-align: center;
}
.pm-score-card__pts {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 1.9rem;
  line-height: 1;
  font-weight: 700;
  color: var(--pm-accent);
}
.pm-score-card__label {
  margin-top: 10px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--pm-text-muted);
}
.pm-score-total {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 var(--space-xl, 32px) 0;
  padding: 7px 18px;
  border-radius: 999px;
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
  font-size: 13px;
  font-weight: 600;
}
.pm-score-total strong {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 1.2rem;
  line-height: 1;
  color: var(--pm-accent-contrast);
}

/* Worked-example scoreboard: the given match result + graded predictions.
   Generous vertical rhythm so the sub-blocks (cards / callout / example)
   read as distinct groups, not one crowded run. */
.pm-example {
  margin: var(--space-lg) 0 var(--space-md) 0;
}
.pm-example__match {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
  padding: var(--space-lg) var(--space-md);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-field-bg);
  margin-bottom: var(--space-md);
}
.pm-example__team {
  font-weight: 600;
  color: var(--pm-text);
}
.pm-example__score {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1;
  padding: 5px 14px;
  border-radius: 8px;
  background: var(--pm-text);
  color: var(--pm-card-bg);
}
.pm-pred-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--pm-card-border);
}
.pm-pred-row:last-child {
  border-bottom: 0;
}
.pm-pred-row__score {
  flex: 0 0 auto;
  min-width: 50px;
  text-align: center;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 1.05rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 8px;
  border: 1px solid var(--pm-card-border);
  color: var(--pm-text);
}
.pm-pred-row__pts {
  flex: 0 0 auto;
  min-width: 62px;
  text-align: center;
  padding: 4px 12px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 12px;
}
.pm-pred-row__pts--hot {
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
}
.pm-pred-row__pts--warm {
  background: color-mix(in srgb, var(--pm-accent) 16%, transparent);
  color: var(--pm-accent);
}
.pm-pred-row__pts--cool {
  background: color-mix(in srgb, var(--pm-text-muted) 14%, transparent);
  color: var(--pm-text-muted);
}
.pm-pred-row__why {
  flex: 1;
  color: var(--pm-text-muted);
  font-size: 13px;
  line-height: 1.5;
}
@media (max-width: 680px) {
  .pm-score-cards {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-sm);
  }
  .pm-pred-row {
    flex-wrap: wrap;
    gap: var(--space-sm);
  }
  .pm-pred-row__why {
    flex: 1 1 100%;
  }
}
.pm-paragraph__text table {
  display: block;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 var(--space-md) 0;
}
.pm-paragraph__text th,
.pm-paragraph__text td {
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--pm-card-border);
  text-align: left;
  vertical-align: top;
}
.pm-paragraph__text thead th {
  background: var(--pm-field-bg);
  color: var(--pm-text);
  font-weight: 600;
  white-space: nowrap;
}

/* image */
/* Content image paragraphs (S49 "Cómo funciona" screenshots): a raw
   screenshot rendered at the theme's full content width (~1090px) reads
   huge and pixelated, since screenshots are captured at a phone-ish
   width. Cap + center at a component level (keyed on the paragraph's own
   wrapper class, not a page-specific selector) so it applies to every
   image paragraph in content, not just this page. */
.pm-paragraph--image {
  max-width: 560px;
  margin-inline: auto;
}
.pm-paragraph__figure {
  margin: 0;
}
.pm-paragraph__figure img {
  display: block;
  /* width:auto (not 100%) so a screenshot never upscales past its native
     pixel size and pixelates — it renders at its intrinsic width, capped
     by the container (max-width:100%), and only ever downscales. Small
     card captures (e.g. the ~319px KO cards) stay crisp and centered;
     wider ones downscale to fit. */
  width: auto;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
  border-radius: 8px;
}
.pm-paragraph__caption {
  margin-top: var(--space-sm);
  color: var(--pm-text-muted);
  font-size: 13px;
  text-align: center;
}

/* heading */
.pm-paragraph__heading {
  margin: 0;
  color: var(--pm-text);
  font-family: 'ScoreboardCondensed', var(--font-body);
  line-height: 1.25;
}
h2.pm-paragraph__heading {
  font-size: var(--font-size-lg, 22px);
}
h3.pm-paragraph__heading {
  font-size: 18px;
}

/* cta — buttons reuse the shared .btn/.btn-primary/.btn-secondary atoms
   (see the "Generic button atom" section above); this only lays out the
   1-2 buttons a cta paragraph can hold. */
.pm-paragraph__cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}
@media (max-width: 480px) {
  .pm-paragraph__cta-actions .btn {
    flex: 1 1 100%;
    text-align: center;
  }
}

/* slider — self-hosted banner/carousel (S29-T3). The track is a native
   CSS scroll-snap container: touch/wheel/keyboard paging works with zero
   JS. js/paragraph-slider.js only adds the arrows, dots, and optional
   autoplay on top (see paragraph--slider.html.twig for the rationale). */
.pm-paragraph--slider {
  position: relative;
}
.pm-slider__viewport {
  overflow: hidden;
  border-radius: 8px;
}
/* pool_mobile's field.html.twig is plain Drupal core (no Olivero/Claro
   base theme), which emits NO "field"/"field--name-*" wrapper classes —
   just bare <div>s (see core/modules/system/templates/field.html.twig:
   template_preprocess_field() doesn't set any class, only Olivero/Claro
   theme overrides do). So content.field_slides renders as two anonymous
   wrapper divs deep (the field wrapper, then one div per delta) before
   reaching each slide's own .pm-paragraph--slide root (paragraph--slide
   .html.twig). Rather than depend on generated markup, `display:
   contents` hoists both anonymous wrappers out of the box model so
   .pm-paragraph--slide becomes an effective direct flex/scroll-snap
   child of .pm-slider__track with zero markup coupling. */
.pm-slider__track {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.pm-slider__track::-webkit-scrollbar {
  display: none;
}
.pm-slider__track > div,
.pm-slider__track > div > div {
  display: contents;
}
.pm-slider__track .pm-paragraph--slide {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
}
@media (prefers-reduced-motion: reduce) {
  .pm-slider__track {
    scroll-behavior: auto;
  }
}
.pm-slider__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--pm-media-btn-bg);
  color: #FFFFFF;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
}
.pm-slider__arrow--prev {
  left: var(--space-sm);
}
.pm-slider__arrow--next {
  right: var(--space-sm);
}
.pm-slider__arrow:hover,
.pm-slider__arrow:focus-visible {
  background: var(--pm-media-btn-bg-hover);
}
.pm-slider__arrow[hidden] {
  display: none;
}
.pm-slider__dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}
.pm-slider__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--pm-card-border);
  cursor: pointer;
}
.pm-slider__dot.is-active {
  background: var(--pm-accent-fill);
}

/* slide — one slide inside a "slider" (image with an optional
   heading/text/link caption overlaid on the bottom edge). */
.pm-paragraph--slide {
  position: relative;
}
.pm-slide__media img {
  display: block;
  width: 100%;
  height: auto;
}
.pm-slide__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--space-md);
  background: linear-gradient(to top, rgba(15, 27, 45, 0.75), rgba(15, 27, 45, 0));
  color: #FFFFFF;
}
.pm-slide__heading {
  margin: 0 0 var(--space-sm) 0;
  color: #FFFFFF;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-lg, 20px);
  line-height: 1.25;
}
.pm-slide__text {
  margin: 0 0 var(--space-sm) 0;
  font-size: 14px;
}
.pm-slide__heading:last-child,
.pm-slide__text:last-child {
  margin-bottom: 0;
}
.pm-slide__link {
  display: inline-block;
}

/* reveal-cards — a grid of cards that fade/slide in as they scroll into
   view (S29-T4), no library. field_reveal_columns (2/3/4) sets the grid's
   column count on wide screens via the [data-pm-reveal-columns] attribute
   set in paragraph--reveal-cards.html.twig; narrow/mobile screens always
   stay at 1 column regardless of that setting. */
.pm-paragraph--reveal-cards {
  position: relative;
}
/* Same field-wrapper gotcha as .pm-slider__track (see its comment above):
   pool_mobile's field.html.twig emits no "field"/"field--name-*" classes,
   so content.field_reveal_card_items renders as two anonymous wrapper
   divs deep before reaching each card's own .pm-paragraph--reveal-card
   root. `display: contents` hoists both wrappers out of the box model so
   each card becomes an effective direct grid child with zero markup
   coupling. */
.pm-reveal-cards__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}
.pm-reveal-cards__grid > div,
.pm-reveal-cards__grid > div > div {
  display: contents;
}
@media (min-width: 480px) {
  .pm-reveal-cards__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1024px) {
  .pm-paragraph--reveal-cards[data-pm-reveal-columns="2"] .pm-reveal-cards__grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .pm-paragraph--reveal-cards[data-pm-reveal-columns="3"] .pm-reveal-cards__grid {
    grid-template-columns: repeat(3, 1fr);
  }
  .pm-paragraph--reveal-cards[data-pm-reveal-columns="4"] .pm-reveal-cards__grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* reveal-card — one card inside a "reveal_cards" grid. Starts hidden
   (opacity 0 + offset down) and js/paragraph-reveal.js adds the `in`
   class as it scrolls into view, staggered per card, then stops
   observing it — see that file's docblock. The transition itself is
   gated behind `prefers-reduced-motion: no-preference` below; the JS
   independently short-circuits to showing every card immediately (no
   observer at all) under reduced motion or without IntersectionObserver
   support, so both layers agree even if one of them is bypassed. */
.pm-paragraph--reveal-card {
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 8px;
  box-shadow: var(--pm-card-shadow);
  padding: var(--space-md);
  opacity: 0;
  transform: translateY(16px);
}
.pm-paragraph--reveal-card.in {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: no-preference) {
  .pm-paragraph--reveal-card {
    transition: opacity 0.5s ease, transform 0.5s ease;
  }
}
@media (prefers-reduced-motion: reduce) {
  .pm-paragraph--reveal-card {
    opacity: 1;
    transform: none;
  }
}
.pm-reveal-card__icon {
  margin-bottom: var(--space-sm);
  color: var(--pm-accent);
  font-size: var(--font-size-xl, 28px);
  line-height: 1;
}
.pm-reveal-card__heading {
  margin: 0 0 var(--space-sm) 0;
  color: var(--pm-text);
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-lg, 20px);
  line-height: 1.25;
}
.pm-reveal-card__text {
  margin: 0 0 var(--space-sm) 0;
  color: var(--pm-text-muted);
  font-size: 14px;
}
.pm-reveal-card__heading:last-child,
.pm-reveal-card__text:last-child {
  margin-bottom: 0;
}
.pm-reveal-card__link {
  display: inline-block;
  margin-top: var(--space-sm);
}

/* atmospheric section — a full-bleed block with two variants (S29-T5).
   "hero": js/paragraph-atmospheric.js draws a few drifting particles plus
   a soft moving radial glow on the [data-pm-atmospheric-canvas] behind
   the content — the canvas paints inside .pm-paragraph--atmospheric-
   -hero's dark backdrop (--pm-atmospheric-bg), never the page's light/
   dark surface color, so the glow always has contrast (see that token's
   docblock in the :root blocks above). "echo": no canvas, no JS at all —
   just a CSS gradient using the same --pm-atmospheric-echo-* tokens the
   app-wide echo further up (.app-content) also draws from, so a page can
   repeat the "atmosphere" further down without paying for another
   canvas. */
.pm-paragraph--atmospheric {
  position: relative;
  overflow: hidden;
  border-radius: 8px;
  padding: var(--space-lg) var(--space-md);
  min-height: 280px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.pm-paragraph--atmospheric--hero {
  background: var(--pm-atmospheric-bg);
}
.pm-paragraph--atmospheric--echo {
  background-color: var(--pm-atmospheric-bg);
  background-image:
    radial-gradient(ellipse 70% 60% at 25% 30%, var(--pm-atmospheric-echo-1), transparent 65%),
    radial-gradient(ellipse 60% 50% at 80% 70%, var(--pm-atmospheric-echo-2), transparent 65%);
  min-height: 200px;
}
@media (min-width: 768px) {
  .pm-paragraph--atmospheric {
    min-height: 360px;
    padding: calc(var(--space-lg) * 2) var(--space-lg);
  }
  .pm-paragraph--atmospheric--echo {
    min-height: 240px;
  }
}
/* Behind .pm-atmospheric__content (z-index:1 below) — see this file's
   canvas/content z-index docblock convention (same idea as
   .pm-slider__arrow sitting above .pm-slider__viewport). */
.pm-atmospheric__canvas {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.pm-atmospheric__content {
  position: relative;
  z-index: 1;
  max-width: 640px;
}
.pm-atmospheric__heading {
  margin: 0 0 var(--space-sm) 0;
  color: #FFFFFF;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: clamp(24px, 6vw, 36px);
  line-height: 1.2;
}
.pm-atmospheric__text {
  margin: 0 0 var(--space-md) 0;
  color: rgba(255, 255, 255, 0.82);
  font-size: 15px;
  line-height: 1.6;
}
.pm-atmospheric__text:last-child {
  margin-bottom: 0;
}
.pm-atmospheric__cta {
  display: inline-block;
}

/* atmospheric section variant "home_cta" (home blocks Part 5) — the
   home's closing "Armamos la tuya?" band. Unlike "hero"/"echo" (which
   sit on their own --pm-atmospheric-bg backdrop with hardcoded white
   text, meant to stand out as a dark card), this variant is
   TRANSPARENT and inherits the page's own background/text tokens —
   same pattern as .pm-paragraph--home-hero further below — with only a
   soft radial glow sitting at the top of the band, an oversized
   heading, a subline, and the shared .btn.btn-primary CTA already
   rendered by paragraph--section-atmospheric.html.twig. Every color
   routes through --pm-* tokens (see the SKINNABLE TOKEN CONTRACT
   docblock near the top of this file), no hardcoded hex, so Part 7's
   site-wide re-tokenization reskins this for free.

   Glow color (home polish pass): --pm-atmospheric-glow, NOT --pm-accent
   — --pm-accent is lime in dark mode (it's the site's lime/teal TEXT
   accent, see the rotator docblock further below), so mixing it into a
   background glow read as "too much green" once the lime .btn-primary
   CTA sits right below it. --pm-atmospheric-glow is a fixed teal/petrol
   tone in BOTH themes (see its :root docblock near the top of this
   file) — same "atmosphere" effect, no green in the background. */
.pm-paragraph--atmospheric--home_cta {
  background:
    radial-gradient(ellipse 700px 400px at 50% 0%, var(--pm-atmospheric-glow), transparent 65%),
    transparent;
  min-height: 0;
  /* More padding-block than the base .pm-paragraph--atmospheric rule
     (S49 spacing polish): "hero"/"echo" rely on min-height to give the
     band presence, but this variant zeroes that out (it's a
     transparent band, not a dark card), so padding is the ONLY thing
     giving the CTA band room to breathe — the base value read tight
     once min-height stopped compensating for it. */
  padding-block: calc(var(--space-lg) * 2);
}
.pm-paragraph--atmospheric--home_cta .pm-atmospheric__heading {
  color: var(--pm-text);
  font-size: clamp(38px, 7vw, 78px);
  line-height: 0.94;
  margin-bottom: var(--space-md);
}
.pm-paragraph--atmospheric--home_cta .pm-atmospheric__text {
  color: var(--pm-text-muted);
  font-size: 18px;
  line-height: 1.5;
  max-width: 46ch;
  margin-left: auto;
  margin-right: auto;
}
@media (min-width: 768px) {
  .pm-paragraph--atmospheric--home_cta {
    min-height: 0;
    padding-block: calc(var(--space-lg) * 3);
  }
}

/* "home_hero" paragraph (home blocks Part 1) — the public home's hero
   TEXT side: eyebrow, big display headline with a rotating %word%,
   subhead, two CTAs. Text only: the decorative stacked match-card
   visual is a SEPARATE paragraph (home_match_stack, Part 2) placed
   alongside this one in the home assembly (Part 6) — this block has no
   background/canvas of its own and is transparent, inheriting whatever
   surrounds it in the page flow.

   Every color below routes through the --pm-* skinnable tokens (see the
   SKINNABLE TOKEN CONTRACT docblock near the top of this file) — none of
   it is hardcoded — so Part 7's site-wide re-tokenization (petróleo +
   lima palettes) reskins this hero for free, no CSS changes needed here.

   Convention reminder for this file: the bare `:root` block near the
   top is LIGHT (the default, `color-scheme: light`); DARK requires the
   `[data-theme="dark"]` override. That is why the rotator's "light"
   treatment below has no extra selector but "dark" does. */
.pm-paragraph--home-hero {
  padding: var(--space-lg) 0;
}

/* Hero 2-col split (home blocks Part 6 assembly): node 225's
   layout_paragraphs "section" wraps home_hero (region "first") + the
   match-card stack, home_match_stack (region "second") in a core
   layout_twocol (.layout--twocol, 50/50 flex-wrap — see
   web/core/modules/layout_discovery/layouts/twocol/twocol.css, not
   overridden by this theme before now). That "section" bundle is
   reused elsewhere (e.g. node 179) with different children, so these
   rules are scoped with :has() to the specific instance wrapping THIS
   hero, never touching layout_twocol generally.

   DESKTOP/TABLET (home polish pass): the stacked cards floated too far
   from the hero text at 50/50 with no gap — rebalance toward the text
   (58/42) and pull the columns together with a real column-gap so the
   cards read as part of the hero, not a separate floating block.

   MOBILE (<40em, core's own stacking breakpoint, home polish pass 2):
   the stacked match-card visual (region "second", home_match_stack) is
   a desktop/tablet flourish that does not fit a small screen — it's
   HIDDEN outright on mobile rather than reflowed above the text (the
   previous pass's "cards above text" order is gone). The hero text
   BLOCK (region "first") is centered on the page via max-width +
   margin-inline:auto, but the TEXT ITSELF (eyebrow/headline/subhead/
   CTA row) stays LEFT-aligned inside that centered block — do not
   reintroduce text-align:center here, that was the previous pass's
   mistake. */
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-hero) {
  column-gap: var(--space-lg);
  align-items: center;
}
@media (min-width: 40em) {
  /* calc() accounts for the column-gap above: core's own twocol.css sets
     first/second to flex-basis 50%/50% with NO gap, so 50%+50% fits
     exactly; adding a gap on top without subtracting from the basis
     pushes the total past 100% and — because the container is
     flex-wrap:wrap — silently wraps the second region onto its own
     full-width row instead of shrinking it, which looked like the
     columns had collapsed back to stacked. */
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-hero) > .layout__region--first {
    flex-basis: calc(58% - (var(--space-lg) / 2));
  }
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-hero) > .layout__region--second {
    flex-basis: calc(42% - (var(--space-lg) / 2));
  }
}
@media (min-width: 64em) {
  /* DESKTOP ONLY (home polish pass 3, 2026-07-26): 64em/1024px, a step
     above the 900px tablet width this file's own comments confirm
     already "looks good" — do not lower this breakpoint, it would
     regress tablet. At full desktop widths (.app-content's content box
     is ~1232-1312px, see the .app-content docblock above), the 58/42
     split on that whole width left the hero text flush left and the
     match-card stack flush right with a wide dead gap in the middle —
     text and cards read as two disconnected blocks instead of one
     group. Capping the row itself to a narrower, centered max-width
     pulls both columns close together so they read as ONE aligned
     group, exactly like the points panel below. */
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-hero) {
    max-width: 960px;
    margin-inline: auto;
  }
}
@media (max-width: 39.99em) {
  /* home_match_stack doesn't fit mobile — hide it, don't reflow it. */
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-hero) > .layout__region--second {
    display: none;
  }
  /* Center the hero text BLOCK as a whole; text inside stays left-aligned
     (no text-align here — that's the point of this rule). */
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-hero) .pm-paragraph--home-hero {
    max-width: 46ch;
    margin-inline: auto;
  }
}

.pm-hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--pm-accent);
  margin: 0 0 var(--space-sm) 0;
}
.pm-hero__eyebrow::before {
  content: '';
  display: inline-block;
  width: 26px;
  height: 2px;
  /* Hairline decorativo: sigue el acento de TEXTO (teal en claro, lima en
     oscuro), no el relleno — una línea fina lima sobre papel no cumple. */
  background: var(--pm-accent);
}

.pm-hero__headline {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: clamp(40px, 7vw, 88px);
  line-height: 0.95;
  margin: 0 0 var(--space-md) 0;
  color: var(--pm-text);
}
.pm-hero__headline-line {
  display: block;
}

/* Rotator: a split-flap 3D flip (departure-board style), fast tempo —
   see js/paragraph-home-hero.js's docblock for the full mechanics.
   .pm-hero__rotator stays overflow:visible on purpose: the word tilts
   on its X axis as it flips, and clipping it would slice the letters
   mid-rotation. Server-rendered markup already shows the first word
   statically with no JS at all (correct no-JS / prefers-reduced-motion
   fallback: the behavior never starts the interval in that case).

   .pm-hero__rotator-track is the position:relative + perspective
   stacking context; .pm-hero__rotator-word is absolutely stacked
   inside it (top:0; left:0) so the outgoing and incoming words occupy
   the exact same spot and flip simultaneously instead of pushing each
   other out of the way. width:fit-content is preserved on the word so
   the underline below (a ::after on the word itself, width:100%)
   always matches the CURRENT word's real rendered width instead of the
   widest word in the list. Extra headroom (1.3em vs the 1.05em line)
   gives the underline room to sit under the letters, and the tilting
   word room to flip, without touching the headline lines above/below. */
.pm-hero__rotator {
  display: inline-block;
  position: relative;
  vertical-align: top;
  overflow: visible;
  height: 1.3em;
  min-width: 1ch;
}
.pm-hero__rotator-track {
  position: relative;
  height: 1.05em;
  perspective: 640px;
}
.pm-hero__rotator-word {
  position: absolute;
  top: 0;
  left: 0;
  width: fit-content;
  height: 1.05em;
  line-height: 1.05;
  color: var(--pm-accent);
  backface-visibility: hidden;
  will-change: transform, opacity;
}
/* Hinge edges: the incoming word flips down into place from its TOP
   edge, the outgoing word flips away from its BOTTOM edge — opposite
   pivots so the two motions read as one continuous card, not two
   independent tilts. */
.pm-hero__rotator-word--incoming {
  transform-origin: 50% 0;
}
.pm-hero__rotator-word--outgoing {
  transform-origin: 50% 100%;
}
/* Thin (3-4px), word-width underline — a child of the word's own box,
   never a fixed-to-container sibling, so it can never overhang past a
   short word (the "TOO BIG" complaint the old highlighter-block/
   sibling-underline combo caused). Sits below the baseline, never over
   the letters. Same lime fill in both themes — --pm-accent-fill is
   already lime in light AND dark (see the :root token blocks near the
   top of this file). */
.pm-hero__rotator-word::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -0.18em;
  width: 100%;
  height: 4px;
  background: var(--pm-accent-fill);
  border-radius: 2px;
}

/* LIGHT (default): petrol-ink word text, lime underline beneath — no
   dark-ink-on-lime-fill block covering the letters. */
:root:not([data-theme="dark"]) .pm-hero__rotator-word {
  color: var(--pm-text);
}

/* DARK: lime word text with a soft glow; same thin underline as light. */
:root[data-theme="dark"] .pm-hero__rotator-word {
  text-shadow: 0 0 24px color-mix(in srgb, var(--pm-accent) 55%, transparent);
}

.pm-hero__subhead {
  max-width: 46ch;
  font-size: 18px;
  line-height: 1.5;
  color: var(--pm-text-muted);
  margin: 0 0 var(--space-md) 0;
}
.pm-hero__subhead p {
  margin: 0 0 var(--space-sm) 0;
}
.pm-hero__subhead p:last-child {
  margin-bottom: 0;
}

.pm-hero__ctas {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.pm-hero__cta-secondary {
  font-weight: 700;
  text-decoration: none;
  color: var(--pm-text);
  border-bottom: 2px solid var(--pm-card-border);
  padding-bottom: 3px;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.pm-hero__cta-secondary:hover {
  border-color: var(--pm-accent);
  color: var(--pm-accent);
}

/* ---------- home match stack (home blocks Part 2) ----------
   Static, non-interactive stack of up to 3 .match-card instances showing
   real historical results — reuses .match-card/.pmatch-head/.pmatch-
   teams/.pmatch-team/.team-flag/.team-name/.pmatch-footer/.points-earned
   verbatim (see paragraph--home-match-stack.html.twig's docblock for
   exactly what's reused vs. new). The only new classes are the stack
   positioning (.hms-card--back/--mid/--front) and the static final-score
   row (.hms-final-score/.hms-score/.hms-score-sep) — there is no
   existing static equivalent to .pmatch-scores, which is input-based.
   Deliberately smaller than the original mockup and with the back cards
   pulled further apart (owner refinement) so the card behind visibly
   peeks its score out from under the front card. Tokens only, no
   hardcoded color — Part 7's site-wide re-tokenization reskins this for
   free. No transitions/animation: the tilt is fixed, so there is nothing
   for prefers-reduced-motion to disable here. */
.pm-paragraph--home-match-stack {
  display: flex;
  justify-content: center;
}

.home-match-stack {
  position: relative;
  width: 100%;
  max-width: 320px;
  padding: 34px 0 76px;
}

.hms-card {
  padding: var(--space-sm) var(--space-md);
  gap: 6px;
}

.hms-card--back,
.hms-card--mid {
  position: absolute;
  inset: 34px 0 76px 0;
}

.hms-card--back {
  transform: rotate(-13deg) translate(-64px, 100px) scale(0.86);
  opacity: 0.7;
  z-index: 1;
}

.hms-card--mid {
  transform: rotate(9deg) translate(60px, 104px) scale(0.92);
  opacity: 0.88;
  z-index: 2;
}

.hms-card--front {
  position: relative;
  inset: auto;
  transform: rotate(-3deg);
  z-index: 3;
}

.hms-final-score {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.hms-score {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 800;
  font-size: 30px;
  line-height: 1;
  color: var(--pm-text);
}

.hms-score-sep {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 800;
  font-size: 18px;
  color: var(--pm-text-muted);
}

@media (max-width: 767px) {
  .home-match-stack {
    max-width: 280px;
  }
  .hms-card--back {
    transform: rotate(-13deg) translate(-56px, 90px) scale(0.84);
  }
  .hms-card--mid {
    transform: rotate(9deg) translate(52px, 92px) scale(0.9);
  }
}

/* Tablet band (640-1023px): the twocol split puts this stack in a ~42%
   column (region "second"). At the desktop translate magnitudes the
   fanned .hms-card--mid poked ~60px past the 320px box, spilling past the
   narrow column and off the right page edge (horizontal scroll at
   ~640-900px; ≥1024 is fine because the row caps to a centered 960px).
   Shrink the whole composition — smaller box + reduced translate/scale —
   so the tilt and the peek (back card poking left, score peeking bottom)
   still read but the entire fan stays inside the column with margin. This
   rule follows the max-width:767 block so it wins in the 640-767 overlap,
   where the mobile values still overflowed the tablet column. Verified
   scrollWidth <= innerWidth at 700/820/960px. Desktop (>=1024) and mobile
   (<640, region hidden) are untouched. */
@media (min-width: 640px) and (max-width: 1023px) {
  .home-match-stack {
    max-width: 240px;
    padding: 30px 0 66px;
  }
  .hms-card--back {
    inset: 30px 0 66px 0;
    transform: rotate(-11deg) translate(-36px, 68px) scale(0.82);
  }
  .hms-card--mid {
    inset: 30px 0 66px 0;
    transform: rotate(8deg) translate(33px, 70px) scale(0.86);
  }
}

/* ---------- home championships (home blocks Part 3) ----------
   Admin heading/subtitle + a horizontal carousel of AUTO-generated cards
   (one per active championship — see pool_mobile_preprocess_paragraph__
   home_championships() in pool_mobile.theme). Same native CSS
   scroll-snap mechanic as the "slider" paragraph (see the pm-slider
   rules above), with its own pm-champ and champ-card prefixed classes
   since the cards are plain server-rendered markup, not a paragraph
   reference field (no anonymous Drupal field-wrapper divs to collapse
   with `display: contents` here).

   COLOR: deliberately NEUTRAL/token-only — no per-championship accent
   color. The theming pivot (docs/superpowers home-blocks-and-theming
   workplan, decision 7, re-tokenization) killed per-championship color
   everywhere on the site except the "Mis pollas" dashboard card; this
   public band is not that card, so it uses the same pm- and color-
   prefixed custom-property tokens as everything else. Flagged for
   owner confirmation at review (the original mockup's colored chips
   predate that pivot). */
.pm-paragraph--home-championships {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  /* Breathing room independent of the spacer paragraph's own height, so
     this band never reads as glued to whatever precedes it (home
     polish pass). */
  padding: var(--space-lg) 0;
}

.pm-champ__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  text-align: center;
}

.pm-champ__heading {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: clamp(28px, 4vw, 40px);
  line-height: 1.05;
  margin: 0;
  color: var(--pm-text);
}

.pm-champ__subtitle {
  max-width: 60ch;
  margin: 0 auto;
  font-size: 16px;
  line-height: 1.5;
  color: var(--pm-text-muted);
}

/* Carousel row: arrows are grid siblings of the viewport (not absolutely
   positioned on top of it), so they never overlap/awkwardly float over
   the card content at intermediate widths — they simply sit in their
   own columns beside it, tidy at every width. Dots span the full row
   underneath. */
.pm-champ-carousel {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-sm);
}

.pm-champ__viewport {
  grid-column: 2;
  grid-row: 1;
  overflow: hidden;
  border-radius: 8px;
}

.pm-champ__track {
  display: flex;
  gap: var(--space-md);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 4px;
  /* Center the cards in the track when they don't fill the available
     width; "safe" falls back to start-alignment instead of clipping the
     first card once they DO overflow and become scrollable (home polish
     pass — avoids the classic flex-center-plus-scroll clipping bug). */
  justify-content: safe center;
}
.pm-champ__track::-webkit-scrollbar {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .pm-champ__track {
    scroll-behavior: auto;
  }
}

.champ-card {
  flex: 0 0 260px;
  min-width: 0;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
}

/* Home listing card image — the "Banner del Home" (or icon fallback), in
   prod's layout (image + title + description + CTA). Contained so any shape
   (landscape banner or square logo) shows whole — no 16:9 crop that would
   slice it — and a bit larger than prod's old 56px. */
.champ-card__banner {
  max-width: 100%;
  max-height: 96px;
  object-fit: contain;
  align-self: flex-start;
  border-radius: 8px;
}

.champ-card__title {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.1;
  margin: 0;
  color: var(--pm-text);
}

.champ-card__desc {
  font-size: 14px;
  line-height: 1.5;
  color: var(--pm-text-muted);
}
.champ-card__desc p {
  margin: 0 0 var(--space-sm) 0;
}
.champ-card__desc p:last-child {
  margin-bottom: 0;
}

/* Card footer (S82): ONE row holding the primary action (left) and the
   optional "View details" learn link (right). Because the footer is a
   single row whether or not the learn link exists, every card's footer
   has the same height and the "Play now" buttons align at the same Y
   across the carousel. */
.champ-card__footer {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* Card CTA (S82). Two intents, two shapes:
   - "Play now" (--play): the primary ACTION → a filled lime button (the
     site's primary-button treatment; lime as a BUTTON fill is allowed —
     the rule only bans lime as a panel/card BACKGROUND).
   - "View details" (--detail): a lower-emphasis LEARN link, kept as the
     original accent text link so a landing-only card reads unchanged. */
.champ-card__cta {
  font-weight: 600;
  color: var(--pm-accent);
  text-decoration: none;
  white-space: nowrap;
}
.champ-card__cta--detail:hover,
.champ-card__cta--detail:focus-visible {
  text-decoration: underline;
}
.champ-card__cta--play {
  padding: 8px 16px;
  border-radius: 999px;
  background: var(--pm-accent-fill);
  color: var(--pm-accent-contrast);
  font-weight: 700;
  transition: filter 0.15s ease;
}
.champ-card__cta--play:hover,
.champ-card__cta--play:focus-visible {
  filter: brightness(0.94);
  text-decoration: none;
}

/* Small "learn" link beside a playable card's action button —
   right-aligned on the footer row, deliberately quieter than the button.
   On narrow cards it shrinks instead of wrapping so the footer stays a
   single row and button alignment across cards is preserved. */
.champ-card__detail-link {
  font-size: 13px;
  font-weight: 500;
  color: var(--pm-text-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-align: right;
  min-width: 0;
}
.champ-card__detail-link:hover,
.champ-card__detail-link:focus-visible {
  color: var(--pm-accent);
}

/* "Coming soon" badge for scheduled championships (no pool creation yet). */
.champ-card__soon {
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--pm-text-muted);
  background: var(--pm-surface-2);
}

.pm-champ__arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--pm-media-btn-bg);
  color: #FFFFFF;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.pm-champ__arrow--prev {
  grid-column: 1;
  grid-row: 1;
}
.pm-champ__arrow--next {
  grid-column: 3;
  grid-row: 1;
}
.pm-champ__arrow:hover,
.pm-champ__arrow:focus-visible {
  background: var(--pm-media-btn-bg-hover);
}

.pm-champ__dots {
  grid-column: 1 / -1;
  grid-row: 2;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-sm);
}
.pm-champ__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--pm-card-border);
  cursor: pointer;
}
.pm-champ__dot.is-active {
  background: var(--pm-accent-fill);
}

@media (max-width: 480px) {
  .champ-card {
    flex-basis: 88%;
  }
  /* Narrow cards (arrows flank the track, so the card is ~217px wide):
     keep the footer a SINGLE row — tighter button padding + a smaller,
     nowrap "View details" — so the link never wraps and the "Play now"
     buttons keep their shared baseline across cards. */
  .champ-card__footer {
    gap: 6px;
  }
  .champ-card__cta--play {
    padding: 8px 12px;
  }
  .champ-card__detail-link {
    font-size: 11px;
    white-space: nowrap;
  }
}

/* ---------- home scoring (home blocks Part 4) ----------
   Admin heading/intro + the REAL "Reglas del pool" card — reuses the
   dashboard's own .pool-home-rules-list/.pool-home-rule-label/
   .pool-home-rule-points/.pool-home-rules-link classes verbatim (see
   "4. Reglas del pool" further up this file, and pool-view.html.twig)
   so this band can never visually or numerically drift from the
   dashboard/reglas. Only .pm-scoring__rules-card (the card chrome) and
   .pm-scoring__rules-title are new here.

   Node 225's layout_paragraphs "section" wraps this paragraph (region
   "first") and the minigame paragraph (region "second") in the same
   core layout_twocol used by the hero (see that :has()-scoped block
   above). Home polish pass 2 turns that section into ONE unified panel
   — see the ":has(...home-scoring)" grid block further below for the
   mechanism (this paragraph's own root stays a plain flex column; the
   grid/display:contents trick lives entirely in that scoped block). */
.pm-paragraph--home-scoring {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.pm-scoring__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  text-align: center;
}

.pm-scoring__heading {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: clamp(28px, 4vw, 40px);
  line-height: 1.05;
  margin: 0;
  color: var(--pm-text);
}

.pm-scoring__intro {
  max-width: 60ch;
  margin: 0 auto;
  font-size: 16px;
  line-height: 1.5;
  color: var(--pm-text-muted);
}

.pm-scoring__rules-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
}

.pm-scoring__rules-title {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  margin: 0;
  color: var(--pm-text-muted);
}

.pm-scoring__note {
  text-align: left;
  font-size: 13px;
  margin: 0;
  color: var(--pm-text-muted);
}

/* Rules-card + minigame UNIFIED PANEL (home blocks Part 6 assembly, home
   polish pass 2): the previous pass read as "two detached cards glued
   side by side" (separate backgrounds, misaligned heights, a header
   that only sat over the left column). Fixed CSS-only — no paragraph
   or field restructuring, same two paragraphs in the same two regions.

   The section itself (.layout--twocol, scoped to the instance wrapping
   home_scoring, same :has() pattern as the hero block above) BECOMES
   the single shared panel: one background/border/radius/padding for
   the whole band. Its two regions — and this paragraph's own root,
   .pm-paragraph--home-scoring — are flattened with display:contents so
   their children become direct grid children of the panel, which is
   switched to a 2-column CSS grid:
     row 1: .pm-scoring__head — grid-column 1/-1, spanning the FULL
            panel width as the shared header ("Cómo se puntúa" + intro)
            for both columns below it.
     row 2: .pm-scoring__rules-card (col 1) + the minigame widget
            (col 2) — grid's default align-items:stretch gives them
            equal height for free; a vertical divider sits between them
            on desktop. Both columns' own individual card chrome
            (background/border/padding) is turned OFF here — scoped to
            THIS instance only, via the compound selector below — since
            the shared panel already provides it; the minigame
            paragraph keeps its normal standalone card look wherever
            else it might be placed.

   MOBILE (<40em, same breakpoint as the hero): single column — header,
   then the rules card, then the minigame, stacked full-width, still
   inside the one shared panel (grid auto-placement handles the stack
   order from source order; no explicit grid-row needed). */
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--space-lg);
  row-gap: var(--space-lg);
  padding: var(--space-lg);
  border: 1px solid var(--pm-card-border);
  border-radius: 16px;
  background: var(--pm-card-bg);
}
@media (min-width: 64em) {
  /* DESKTOP ONLY (home polish pass 3, 2026-07-26) — same reasoning and
     same breakpoint as the hero row above: at full desktop width the
     panel spans the whole ~1232-1312px content box, so "Reglas del
     pool" and the minigame sat at opposite ends of it with a wide dead
     gap in between despite already being one grid/panel. Capping and
     centering the panel itself groups both columns together visually,
     without touching the shared-panel mechanism (grid/display:contents)
     below, which stays exactly as is. Tablet (900px, below this
     breakpoint) is untouched. */
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) {
    max-width: 960px;
    margin-inline: auto;
  }
}
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) > .layout__region--first,
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) > .layout__region--second,
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) .pm-paragraph--home-scoring {
  display: contents;
}
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) .pm-scoring__head {
  grid-column: 1 / -1;
  min-width: 0;
}
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) .pm-scoring__rules-card {
  grid-column: 1;
  /* Allow the grid column to shrink below the card's content width so the
     panel never grows wider than the viewport on mobile (overflow fix). */
  min-width: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
}
.layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) .pm-paragraph--minigame.minigame-widget {
  grid-column: 2;
  min-width: 0;
  background: transparent;
  box-shadow: none;
  padding: 0;
}
@media (min-width: 40em) {
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) .pm-paragraph--minigame.minigame-widget {
    border-left: 1px solid var(--pm-card-border);
    padding-left: var(--space-lg);
  }
}
@media (max-width: 39.99em) {
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) {
    grid-template-columns: 1fr;
  }
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) .pm-scoring__rules-card,
  .layout--twocol:has(> .layout__region--first .pm-paragraph--home-scoring) .pm-paragraph--minigame.minigame-widget {
    grid-column: 1;
  }
}

/* Top-level field_sections rhythm (S49 "Cómo funciona" spacing polish):
   layout_paragraphs renders each top-level component (hero, intro text,
   heading, the 4 feature-row sections, closing text, CTA band) inside
   its own unstyled, unclassed wrapper div — those wrapper divs carry no
   padding/border, so adjoining margins collapse straight through them
   and behave exactly like margins on the components themselves. Setting
   margin-top directly on the components is therefore enough to create a
   consistent gap between EVERY top-level section, without needing a
   selector for the classless wrapper divs.

   Guarded with :not(.layout__region ...) because several of these same
   paragraph classes are reused as INNER content of a twocol row (the
   feature rows' own .pm-paragraph--text) or of another page's twocol
   hero (.pm-paragraph--home-hero on node 225) — only true top-level
   instances get this rhythm; nested ones keep their existing spacing.

   home_hero is intentionally excluded: it is always first, has its own
   vertical padding, and doesn't need a margin-top pulling it away from
   whatever precedes field_sections. */
.pm-paragraph--text:not(.layout__region .pm-paragraph--text),
.pm-paragraph--heading:not(.layout__region .pm-paragraph--heading),
.paragraph--type--section:not(.layout__region .paragraph--type--section),
.pm-paragraph--atmospheric:not(.layout__region .pm-paragraph--atmospheric) {
  margin-top: calc(var(--space-lg) * 1.5);
}
@media (min-width: 48em) {
  .pm-paragraph--text:not(.layout__region .pm-paragraph--text),
  .pm-paragraph--heading:not(.layout__region .pm-paragraph--heading),
  .paragraph--type--section:not(.layout__region .paragraph--type--section),
  .pm-paragraph--atmospheric:not(.layout__region .pm-paragraph--atmospheric) {
    margin-top: calc(var(--space-lg) * 2);
  }
}

/* reveal_cards grid (hub "3 steps") was NOT in the top-level rhythm rule
   above, so it sat flush (0 gap) under its section heading — cramped. Give
   it a comfortable grouping gap: clearly separated from the heading, but
   tighter than the full section-to-section rhythm so the heading still
   reads as the cards' label rather than a detached block. */
.pm-paragraph--reveal-cards:not(.layout__region .pm-paragraph--reveal-cards) {
  margin-top: calc(var(--space-lg) * 1.5);
}

/* Hub intro lead: cap the reading measure so the tournament blurb doesn't
   run the full ~1232px content width (long, hard-to-read lines). A
   top-level text paragraph otherwise spans the whole column; left-aligned
   here to sit under its heading. */
.pm-paragraph--text:not(.layout__region .pm-paragraph--text) {
  max-width: 46rem;
}

/* Offset the #minijuego scroll target below the fixed header so the hero's
   "Juega el reto ↓" jump doesn't land with the minigame hidden under the
   top bar. */
#minijuego {
  scroll-margin-top: 5rem;
}

/* Contact form (S74): the ContactFormBlock, embedded via a `drupal_block`
   paragraph inside a one-column layout_paragraphs `section` on /contacto +
   /en/contact (build-contacto.php). A bare drupal_block carries no reading
   container, so the form bled to the full content width — its CAPTCHA fieldset
   spanned the whole ~1232px column while the text inputs capped at 420px on the
   left, reading as a lopsided "flush-left" form. Cap the form to a centered
   single-column width so the inputs, the CAPTCHA fieldset and the submit button
   all line up as one contained block, matching the composed feel of the other
   landing pages. Scoped to the onecol section wrapping this block, so no other
   `drupal_block` placement is touched; the hero above it stays full-width. */
.layout--onecol .pm-paragraph--drupal-block form {
  max-width: 34rem;
  margin-inline: auto;
}
.layout--onecol .pm-paragraph--drupal-block form input,
.layout--onecol .pm-paragraph--drupal-block form textarea {
  max-width: 100%;
}

/* Text + image twocol feature rows (S49 "Cómo funciona" step-by-step
   rows): a reused layout_twocol "section" wrapping a `text` paragraph in
   one region and an `image` paragraph in the other — either order, since
   rows alternate which side gets the screenshot. Keyed off the paragraph
   PAIRING itself (not this specific page), same :has() scoping approach
   as the hero/scoring rows above, so this applies wherever this text+
   image row pattern gets reused, not just here.

   Without this, core's bare layout_twocol (display:flex, no gap, default
   align-items:stretch) puts the two regions flush against each other and
   top-aligns their content — with an ~85px-tall text block next to a
   ~560px-tall screenshot, that reads as a huge dead gap under the short
   column. column-gap/row-gap add breathing room (also covers the <40em
   stacked case, where core drops both regions to full width and rows
   wrap), align-items:center vertically centers the short text against
   the tall image instead of pinning it to the top, and margin-bottom
   gives the row itself rhythm matching the standard .pm-paragraph
   spacing (the bare "section" wrapper carries no .pm-paragraph class of
   its own, so it gets none of that spacing for free). */
.layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image),
.layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) {
  align-items: center;
  gap: var(--space-lg);
  margin-bottom: var(--space-lg);
}

/* Feature-row CARD treatment (S49 spacing/rhythm polish): each step reads
   as a contained block instead of bleeding into the page background —
   this is what visually differentiates the alternating rows from each
   other and from the plain-background intro/closing text paragraphs
   around them. Token-driven (--pm-card-bg/--pm-card-border/
   --pm-card-shadow), same tokens the rest of the site's cards already
   use, so light/dark both fall out of the cascade with no extra
   overrides here — light gets white-on-cream, dark gets the navy card
   tint (never pure white). box-sizing:border-box (site-wide reset, see
   css/reset.css) means this padding eats into the row's existing width
   instead of growing it, so the card still lines up with the unpadded
   text paragraphs above/below it. */
.layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image),
.layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) {
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  box-shadow: var(--pm-card-shadow);
  padding: var(--space-md);
}
@media (min-width: 40em) {
  .layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image),
  .layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) {
    padding: var(--space-lg);
  }
}
@media (min-width: 40em) {
  /* Same fix as the hero row: core sets first/second to flex-basis
     50%/50% with NO gap, so 50%+50% fits exactly. Adding a column-gap on
     top without subtracting from the basis pushes the total past 100%
     and — flex-wrap:wrap — silently wraps the second region onto its
     own full-width row, which looks like the columns collapsed back to
     stacked. */
  .layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image) > .layout__region--first,
  .layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image) > .layout__region--second,
  .layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) > .layout__region--first,
  .layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) > .layout__region--second {
    flex-basis: calc(50% - (var(--space-lg) / 2));
    min-width: 0;
  }
}

/* Cap the feature-row screenshot at 480px so it never upscales past the
   source. The `wide` image style tops these captures out at ~746px, but
   the generic .pm-paragraph--image cap is 560px — at desktop widths the
   50% region is wide enough that 560px let the ~746px source scale up on
   hi-DPI and read soft/pixelated. 480px keeps it a comfortable downscale
   (crisp at 1x, near-native on hi-DPI) and stays centered in its region.
   Scoped to THIS text+image row pairing so standalone image paragraphs
   elsewhere keep the 560px cap. */
.layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image) .pm-paragraph--image,
.layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) .pm-paragraph--image {
  max-width: 480px;
}
@media (max-width: 39.99em) {
  /* MOBILE sequential order: when the two regions stack, every step must
     read TEXT first, IMAGE second — regardless of which DOM region holds
     which (rows alternate the image side on desktop, so half the rows
     have the image in region--first). The bare layout_twocol is
     display:flex/flex-wrap at every width, so `order` on the regions
     reflows the stack without disturbing the desktop alternation above
     (which relies on default source order and is untouched here). The
     region is selected by the paragraph it actually contains
     (:has(> .pm-paragraph--*)), not by its --first/--second position. */
  .layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image) > .layout__region:has(> .pm-paragraph--text),
  .layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) > .layout__region:has(> .pm-paragraph--text) {
    order: 1;
  }
  .layout--twocol:has(> .layout__region--first .pm-paragraph--text):has(> .layout__region--second .pm-paragraph--image) > .layout__region:has(> .pm-paragraph--image),
  .layout--twocol:has(> .layout__region--first .pm-paragraph--image):has(> .layout__region--second .pm-paragraph--text) > .layout__region:has(> .pm-paragraph--image) {
    order: 2;
  }
}

/* ===================================
   Bonus cards + pool-config sections + /reglas bonus sections
   (theme-retokenization task 6, 2026-07-27). Token-driven, both themes
   fall out of the --pm-* cascade — no per-theme overrides needed.

   LIME (--pm-accent-fill) is never used as a large background fill here:
   the card surface is always --pm-card-bg, the logo circle only takes a
   ~12% color-mix tint, and the category badge / detail link use
   --pm-accent as text/border, never a solid lime fill.
   =================================== */

/* --- Bonus card (create/edit pool "Bonuses" section, bonus-card.html.twig) --- */
/* Responsive grid of VERTICAL cards (owner request, 2026-07-30): mirror the
   match-prediction grid layout — 1 column on mobile, 2 on tablet (>=768px),
   3 on desktop (>=1024px, the theme's header breakpoint). Each card stacks
   logo -> head (title + badge) -> description -> example/scope -> footer
   ("Ver detalle"), so it reads taller than wide. The activation checkbox is
   pinned to the card's top-right corner. Full copy still lives on /reglas
   via "Ver detalle". */
.bonus-card-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
  /* Each card sizes to its OWN content (owner request) — no equal-height
     row stretch, so a short coming-soon card stays short instead of gaining
     empty space above its bottom-pinned "Ver detalle". */
  align-items: start;
}
@media (min-width: 768px) {
  .bonus-card-list {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1024px) {
  .bonus-card-list {
    grid-template-columns: repeat(3, 1fr);
  }
}
.bonus-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
}
.bonus-card__logo {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  /* Subtle accent tint, not a fill — see block comment above. */
  background: color-mix(in srgb, var(--pm-accent) 12%, var(--pm-card-bg));
  color: var(--pm-accent);
}
/* Shrink the inline SVG to the logo circle. */
.bonus-card__logo svg {
  width: 24px;
  height: 24px;
}
.bonus-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  gap: 2px;
}
.bonus-card__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
  /* Leave room for the activation checkbox pinned top-right. */
  padding-right: 28px;
  margin-bottom: 2px;
}
.bonus-card__title {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-lg);
  color: var(--pm-text);
}
.bonus-card__badge {
  padding: 2px 10px;
  border: 1px solid color-mix(in srgb, var(--pm-accent) 45%, transparent);
  border-radius: 999px;
  color: var(--pm-accent);
  font-size: 12px;
  white-space: nowrap;
}
.bonus-card__desc {
  margin: 0;
  color: var(--pm-text);
  font-size: 14px;
  /* Clamp to 2 lines so a long description can't dominate the card and rows
     align — the full text lives on /reglas (Ver detalle). */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.bonus-card__example {
  margin: 4px 0 0 0;
  padding-left: 10px;
  border-left: 2px solid var(--pm-card-border);
  color: var(--pm-text-muted);
  font-size: 13px;
  font-style: italic;
}
.bonus-card__scope {
  margin-top: var(--space-sm);
}
.bonus-card__detail {
  /* Pin the CTA to the bottom so all cards in a row align. */
  margin-top: auto;
  padding-top: var(--space-sm);
  color: var(--pm-accent);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
}
.bonus-card__detail:hover,
.bonus-card__detail:focus-visible {
  text-decoration: underline;
}
/* Activation checkbox pinned to the card's top-right corner (real cards
   only; coming-soon cards render no toggle at all). */
.bonus-card__toggle {
  position: absolute;
  top: var(--space-md);
  right: var(--space-md);
  flex-shrink: 0;
}
/* Locked-state explanatory copy (review fix, task 6): the card mutes
   itself below, so this hint is the only thing that tells a member WHY
   the checkbox is disabled instead of leaving it unexplained. */
.bonus-card__locked-hint {
  margin: 4px 0 0 0;
  color: var(--pm-text-muted);
  font-size: 13px;
  font-style: italic;
}
/* Locked = pool already in play, the whole card is disabled (S22).
   --pm-surface-2 (not --pm-field-disabled-bg) on purpose: in dark mode
   --pm-field-disabled-bg equals --pm-card-bg exactly, so it wouldn't
   read as muted against the card underneath it. */
.bonus-card.is-locked {
  background: var(--pm-surface-2);
}
.bonus-card.is-locked .bonus-card__title,
.bonus-card.is-locked .bonus-card__desc {
  color: var(--pm-text-muted);
}
.bonus-card.is-locked .bonus-card__logo {
  background: color-mix(in srgb, var(--pm-text-muted) 12%, var(--pm-surface-2));
  color: var(--pm-text-muted);
}
.bonus-card.is-locked .bonus-card__badge {
  border-color: var(--pm-field-border);
  color: var(--pm-text-muted);
}
.bonus-card.is-locked .bonus-card__toggle {
  cursor: not-allowed;
}

/* Coming-soon = presentational preview of a FUTURE bonus (ComingSoonBonuses):
   visible but non-interactive (no activation toggle rendered), muted, with a
   dashed edge to read as "not yet available". */
.bonus-card--coming-soon {
  background: var(--pm-surface-2);
  border-style: dashed;
}
.bonus-card--coming-soon .bonus-card__title,
.bonus-card--coming-soon .bonus-card__desc {
  color: var(--pm-text-muted);
}
.bonus-card--coming-soon .bonus-card__logo {
  background: color-mix(in srgb, var(--pm-text-muted) 12%, var(--pm-surface-2));
  color: var(--pm-text-muted);
}
.bonus-card--coming-soon .bonus-card__detail {
  color: var(--pm-text-muted);
}
/* "Próximamente" pill: neutral (not accent), used on the cards and on the
   matching /reglas sections. */
.bonus-card__badge--soon {
  border-color: var(--pm-field-border);
  background: color-mix(in srgb, var(--pm-text-muted) 10%, transparent);
  color: var(--pm-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}
/* /reglas coming-soon section: mute the heading to match the card state. */
.pool-rules-bonus.is-coming-soon h2 {
  color: var(--pm-text-muted);
}

/* --- Bonus carousel (S53-T2, paragraph--bonus-carousel.html.twig) ---
   A DISTINCT .pm-bono-* class family from .bonus-card/.bonus-card__* above
   (that component is the create/edit-pool bonus-ACTIVATION UI — checkboxes,
   locked/coming-soon states, a different context). This is a read-only
   marketing carousel on /reglas: icon + title + short description + "Ver
   detalles" link to the bonus's own `bono` node. Mobile: horizontal
   scroll-snap. Desktop (>=768px, this file's existing breakpoint): a 3-up
   grid row, same convention as .bonus-card-list above. No lime/green CARD
   fill anywhere here — only .pm-bono-card__logo's inherited small
   accent-tinted icon badge (from bonus-icon.html.twig's own
   .bonus-card__logo span) and .pm-bono-card__link's accent TEXT color. */
.pm-bono-carousel__heading {
  margin: 0 0 var(--space-md) 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-xl);
  color: var(--pm-text);
}
/* Cards STACK vertically on mobile + tablet (owner: they must go downward,
   no horizontal scroll); a 3-up row only kicks in on wide desktop. */
.pm-bono-carousel__track {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}
@media (min-width: 1024px) {
  .pm-bono-carousel__track {
    grid-template-columns: repeat(3, 1fr);
  }
}
.pm-bono-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
}
.pm-bono-card__logo {
  margin-bottom: 2px;
}
/* .bonus-card__logo (the span inside includes/bonus-icon.html.twig's own
   markup) already supplies the circle size + accent tint + currentColor;
   nothing to override here. */
.pm-bono-card__title {
  margin: 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-lg);
  color: var(--pm-text);
}
.pm-bono-card__desc {
  margin: 0;
  color: var(--pm-text-muted);
  font-size: 14px;
}
.pm-bono-card__link {
  margin-top: auto;
  padding-top: 4px;
  color: var(--pm-accent);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  align-self: flex-start;
}
.pm-bono-card__link:hover,
.pm-bono-card__link:focus-visible {
  text-decoration: underline;
}

/* --- "Otros bonos" mini-card row (S53, paragraph--bono-related.html.twig) ---
   A DISTINCT, more compact .pm-bono-mini-* family from .pm-bono-card above:
   this is a small cross-link footer at the end of a bono landing page (icon
   + title only, no description, no separate "Ver detalles" link — the
   WHOLE card is the <a>), not a repeat of the carousel's full cards. Same
   breakpoint convention as .pm-bono-carousel__track (>=1024px = 3-up grid,
   stacked below that). Card background/border use --pm-card-bg/
   --pm-card-border like everywhere else — NO lime/accent-fill background;
   the accent stays only on the icon (inherited from bonus-icon.html.twig's
   own .bonus-card__logo span), matching the rest of the site's convention. */
.pm-bono-mini-row__heading {
  margin: 0 0 var(--space-md) 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-xl);
  color: var(--pm-text);
}
.pm-bono-mini-row__track {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-sm);
}
@media (min-width: 1024px) {
  .pm-bono-mini-row__track {
    grid-template-columns: repeat(3, 1fr);
  }
}
.pm-bono-mini-card {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  text-decoration: none;
  transition: border-color 0.15s ease;
}
.pm-bono-mini-card:hover,
.pm-bono-mini-card:focus-visible {
  border-color: var(--pm-accent);
}
.pm-bono-mini-card__logo {
  flex: 0 0 auto;
  display: flex;
}
/* .bonus-card__logo (the span inside includes/bonus-icon.html.twig's own
   markup) already supplies the circle size + accent tint + currentColor;
   nothing to override here. */
.pm-bono-mini-card__title {
  margin: 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 16px;
  color: var(--pm-text);
}

/* --- Pool config sections (create/edit pool forms: General / Privacy /
   Bonuses / Matches) --- */
.pool-config-section {
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--pm-card-border);
}
.pool-config-section__title {
  margin: 0 0 var(--space-md) 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-xl);
  color: var(--pm-text);
}

/* --- /reglas bonus sections (pool-rules.html.twig). Each section reuses
   .pool-home-card for its border/bg/radius/padding — only the internal
   head row + copy rhythm are new here. --- */
.pool-rules-bonuses {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-top: var(--space-lg);
}
/* Anchor-jump offset (S16-T3 /reglas#bono-{id} links, plus the "See
   detail" link on every bonus card): without this, the fixed/sticky
   .site-header (desktop, position:sticky) hides the section heading
   right after the jump. 84px comfortably clears the header's ~60px
   height plus breathing room. */
.pool-rules-bonus {
  scroll-margin-top: 84px;
}
.pool-rules-bonus__head {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}
.pool-rules-bonus__head h2 {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-lg);
  color: var(--pm-text);
}
.pool-rules-bonus__detail {
  margin: 0 0 4px 0;
  color: var(--pm-text);
}
.pool-rules-bonus__example {
  margin: 0;
  padding-left: 10px;
  border-left: 2px solid var(--pm-card-border);
  color: var(--pm-text-muted);
  font-size: 14px;
  font-style: italic;
}

/* ===================================
   STRUCTURE-PREDICTION EDITOR (S35): styles moved to the module
   (pool_core/css/structure-editor.css) so the admin answer-key page —
   rendered in the claro admin theme — shares the exact same look. The
   module CSS resolves the --pm-* brand tokens defined in this file.
   =================================== */

/* ===================================
   ERROR PAGES (S73)
   Branded 403 / 404 surfaces (ErrorPageController + error-page.html.twig).
   Centered, editorial: a big ghosted status number, a calm headline, one
   line of help, and the way back. Uses only --pm-* tokens so it tracks the
   light/dark theme automatically. The matching static 500 (web/500.html)
   mirrors this look with inlined values.
   =================================== */
.pm-error {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100%;
  padding: var(--space-lg) var(--space-md);
}
.pm-error__card {
  width: 100%;
  max-width: 460px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 40px 24px;
  border-radius: 16px;
  border: 1px solid var(--pm-card-border);
  background:
    radial-gradient(120% 90% at 50% -10%, var(--pm-atmospheric-glow), transparent 60%),
    var(--pm-card-bg);
  box-shadow: var(--pm-card-shadow);
}
.pm-error__code {
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-weight: 800;
  font-size: 104px;
  line-height: 0.9;
  letter-spacing: 1px;
  color: var(--pm-accent-fill);
  /* Keep the lime number legible on the light card: a petrol edge stops it
     from washing out on the pale surface, harmless on the dark card. */
  text-shadow: 0 2px 0 rgba(12, 24, 38, 0.14);
}
.pm-error__headline {
  margin: 4px 0 0;
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--pm-text);
}
.pm-error__help {
  margin: 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--pm-text-muted);
}
.pm-error__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  margin-top: 12px;
}
.pm-error__actions .btn {
  padding: 11px 20px;
}
@media (max-width: 400px) {
  .pm-error__code {
    font-size: 84px;
  }
  .pm-error__actions {
    flex-direction: column;
    align-self: stretch;
  }
  .pm-error__actions .btn {
    width: 100%;
  }
}

/* -------------------------------------------------------------------------
   S111/S112 — tournament hub dynamic teaser + cross-links.
   The standings/scorers tables reuse .ranking-table (native ranking
   styles) and "próximos partidos" reuses .match-card/.pmatch-* (native
   match-card styles, see predictions-match-cards.html.twig) — the rules
   below only add wrapper layout, crests and the "Explora otras pollas"
   cross-link cards (mirroring .pm-bono-mini-*, the "Otros bonos" family
   above), all on brand tokens.
   ------------------------------------------------------------------------- */
.hub-teaser {
  margin: 2rem auto;
  max-width: 960px;
  padding: 0 1rem;
}
.hub-teaser-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  margin-bottom: 1rem;
}
.hub-teaser-heading {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* Sized to match the includes/avatar.html.twig include's own --pm-avatar-
   size (passed as size:32 from the template) — no separate class needed
   for the <img>/initials variants, this only affects layout of the
   surrounding heading row. */
.hub-teaser-heading .pm-avatar {
  flex: 0 0 auto;
}
.hub-teaser-title {
  margin: 0;
}
.hub-teaser-updated {
  font-size: 0.8rem;
  color: var(--pm-text-muted);
}
.hub-teaser-block {
  margin-bottom: 1.5rem;
}
.hub-teaser-subtitle {
  margin: 0 0 0.6rem;
  font-size: 1rem;
}
.hub-crest {
  width: 22px;
  height: 22px;
  object-fit: contain;
  flex: 0 0 auto;
  vertical-align: middle;
}
.hub-team-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hub-standings-table .col-player,
.hub-scorers-table .col-player {
  max-width: 0;
  width: 100%;
}
.hub-scorer-player {
  font-weight: 600;
}

/* Standings + scorers side by side at desktop width (S112) — 1 column
   (stacked) on mobile, 2 columns at >=1024px. Each table keeps its own
   .ranking-table-wrap (native horizontal-overflow handling), so a still-
   too-narrow viewport scrolls its OWN table instead of squeezing both. */
.hub-teaser-tables {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}
.hub-teaser-tables .hub-teaser-block {
  margin-bottom: 0;
}
@media (min-width: 1024px) {
  .hub-teaser-tables {
    grid-template-columns: 1fr 1fr;
  }
}

/* Próximos partidos — up to 3 .match-card--compact instances (S112),
   informative only (crest+name+time, no score inputs — see
   pool-hub-teaser.html.twig). A vertical stack (single grid column), not
   a side-by-side grid: a 3-across layout at this width truncated team
   names down to a couple of letters, which defeated the point of an
   "informative" card. Single-column grid (not flex) so every card gets
   the SAME track width regardless of its own content. */
.hub-next-matches-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-sm);
}
/* .match-card--compact caps at 440px + centers itself (margin-inline:
   auto) for single-card contexts (minigame, home "Next match") — here
   the cards are a list. Cap at a comfortable reading width and left-align
   so all three are the SAME width with aligned edges, and the match row
   spreads inside instead of clustering in the middle of a full-bleed card. */
.hub-next-match-card.match-card--compact {
  max-width: 640px;
  width: 100%;
  margin-inline: 0;
}
/* Spread the fixture across the card: home left, kickoff centered, away
   right — fills the width instead of a centered cluster with dead space. */
.hub-next-match-card .pmatch-teams {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}
.hub-next-match-card .pmatch-team {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 0;
  min-width: 0;
}
.hub-next-match-card .pmatch-team--home {
  justify-content: flex-start;
}
.hub-next-match-card .pmatch-team--away {
  justify-content: flex-end;
}
/* Team names are the row's visual anchor (S114 polish — owner feedback:
   date/time and team names both read flat before this). Slightly bolder
   than the base .team-name weight so they outweigh the muted datetime
   beside them. */
.hub-next-match-card .team-name {
  font-weight: 600;
}
/* Datetime reads as structured data, not plain text (S114): the same
   condensed/tabular treatment numeric data gets elsewhere (.ranking-points,
   .col-num) — see timezone.js's 'short-datetime' preset, which now hydrates
   TWO spans (.pmatch-date-part / .pmatch-time-part) instead of one
   textContent string, so the date group and the HH:MM group can carry
   different weights/colors. text-transform:uppercase lives on the PARENT
   so the pre-hydration server fallback ("8 sep · 11:45", see
   _pool_core_hub_fixture_when()) reads consistently with the post-hydration
   markup — it only touches the month abbreviation's letters, numerals and
   the middot are unaffected. */
.hub-next-match-card .pmatch-time {
  flex: 0 0 auto;
  white-space: nowrap;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.hub-next-match-card .pmatch-time .pmatch-date-part,
.hub-next-match-card .pmatch-time .pmatch-time-sep {
  color: var(--pm-text-muted);
}
.hub-next-match-card .pmatch-time .pmatch-time-part {
  color: var(--pm-text);
  font-weight: 700;
}

/* Champions "Partidos más atractivos del arranque" featured preview (S114) —
   curated marquee fixtures, reusing the SAME .hub-next-match-card partial
   (includes/hub-fixture-card.html.twig) as "Próximos partidos" above, but
   in an actual multi-column grid (2 at tablet, 3 at desktop) since these
   are the star content of a hub with no standings yet — a single-column
   list would bury them. */
.champions-featured-intro {
  color: var(--pm-text-muted);
  max-width: 720px;
  margin: 0 0 1.25rem;
}
.champions-featured-grid {
  grid-template-columns: 1fr;
}
.champions-featured-grid .hub-next-match-card.match-card--compact {
  max-width: none;
  margin-inline: 0;
}
@media (min-width: 720px) {
  .champions-featured-grid {
    grid-template-columns: 1fr 1fr;
  }
}
@media (min-width: 1100px) {
  .champions-featured-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* "Explora otras pollas" cross-links (S112 redesign) — mirrors the bono
   cross-link mini row (.pm-bono-mini-*, "Otros bonos" section above):
   league LOGO + short name cards in the same grid, then a separate,
   clearly intentional nav line ("¿Nuevo por acá?" + two real buttons)
   instead of stray inline links. */
.hub-crosslinks {
  margin: 2.5rem auto;
  max-width: 960px;
  padding: 0 1rem;
}
.hub-crosslinks-title {
  margin: 0 0 1rem;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: var(--font-size-xl);
  color: var(--pm-text);
}
.hub-crosslinks-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-sm);
}
@media (min-width: 1024px) {
  .hub-crosslinks-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
.hub-crosslink-card {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--pm-card-border);
  border-radius: 12px;
  background: var(--pm-card-bg);
  text-decoration: none;
  transition: border-color 0.15s ease;
}
.hub-crosslink-card:hover,
.hub-crosslink-card:focus-visible {
  border-color: var(--pm-accent);
}
.hub-crosslink-card__logo {
  flex: 0 0 auto;
  display: flex;
}
.hub-crosslink-card__title {
  margin: 0;
  font-family: 'ScoreboardCondensed', var(--font-body);
  font-size: 16px;
  color: var(--pm-text);
}
.hub-crosslinks-nav {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--pm-card-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
}
.hub-crosslinks-nav__label {
  margin: 0;
  color: var(--pm-text-muted);
  font-size: 0.9rem;
}
.hub-crosslinks-nav__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* S123: "Todo sobre {liga}" intro — give the body copy a designed panel
   (card bg + left accent stripe + card border + soft shadow) instead of plain
   floating text, with the heading sitting just above as its title. Scoped to
   .tournament-hub (set in pool_core_preprocess_node) so it never touches other
   landing pages; the intro body is the only `text` paragraph on a hub (the
   rest are reveal_cards/scoring/minigame), so this targets it uniquely.
   layout_paragraphs wraps each paragraph in its own div, so grouping the two
   with CSS adjacency isn't reliable — paneling the single text box + tightening
   the title gap reads as one titled panel and is robust. */
.tournament-hub .pm-paragraph--heading:first-of-type .pm-paragraph__heading {
  margin-bottom: 0.6rem;
}
.tournament-hub .pm-paragraph--text {
  background: var(--pm-card-bg);
  border: 1px solid var(--pm-card-border);
  border-left: 4px solid var(--pm-accent);
  border-radius: 14px;
  padding: 1.4rem 1.75rem;
  box-shadow: var(--pm-card-shadow);
}
.tournament-hub .pm-paragraph--text > :first-child {
  margin-top: 0;
}
.tournament-hub .pm-paragraph--text > :last-child {
  margin-bottom: 0;
}
/* Desktop: the intro was left-aligned + narrow while the teaser below is a
   centered ~960 column, so the panel looked orphaned upper-left. Match the
   intro heading + panel to the same centered reading column so the top of the
   page reads as one aligned stack. Mobile is full-width already (max-width has
   no effect below the breakpoint). */
@media (min-width: 1024px) {
  .tournament-hub .pm-paragraph--heading:first-of-type,
  .tournament-hub .pm-paragraph--text {
    max-width: 46rem;
    margin-inline: auto;
  }
}
