/* ============================================================
   CLANVAS THEME — component styles.
   Ported section by section from templates/base/section-*.dc.html.
   Every value still reads var(--token, fallback) — brand tokens come from
   include/head.tplp's inline block, everything else from css/tokens.css.
   ============================================================ */

/* Visually-hidden but screen-reader-readable — the standard accessible-hiding
   pattern (clip to 1x1px, not display:none, so it stays in the a11y tree).
   Used for a page's semantic h1 when the visible design has no single natural
   heading to be it (see tpl/home.tpl's sr-only h1 next to the hero's own
   per-slide h2 titles). */
.hp3-sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}

/* ---- Interactive states: one shared policy for every actionable control -----
   Fixes the mobile "stuck pressed" bug project-wide (see docs/decisions.md
   2026-07-09, docs/design-system.md "Interactive states"). Three separate states,
   previously collapsed into a lone :hover:
     - press feedback -> :active below (momentary opacity dim, iOS-native style):
       uniform across every button variant, independent of brand colors, and it
       does NOT stick on touch the way :hover does;
     - tap-highlight off + touch-action:manipulation kill the native grey tap
       flash and the tap delay;
     - hover -> each component keeps its own :hover but ONLY inside
       @media (hover: hover), so touch devices never trigger sticky-hover.
   New control: add its class here and wrap any :hover in @media (hover: hover). */
.hp3-ui-btn, .hp3-ui-navbtn, .hp3-ui-iconbtn, .hp3-ui-link, .hp3-social__link,
.hp3-header__item, .hp3-search-bar__close, .hero-row,
.hp3-ui-dot, .hp3-pagination__num, .hp3-news-filter__tab,
.hp3-month-filter__pill, .hp3-share__btn, .hp3-footer__accordion-toggle,
.hp3-store-card{
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.hp3-ui-btn:active, .hp3-ui-navbtn:not(:disabled):active, .hp3-ui-iconbtn:active,
.hp3-ui-link:active, .hp3-social__link:active,
.hp3-header__item:active, .hp3-search-bar__close:active,
.hero-row:active, .hp3-ui-dot:active,
.hp3-pagination__num:not(.is-current):active, .hp3-news-filter__tab:active,
.hp3-month-filter__pill:active, .hp3-share__btn:active,
.hp3-footer__accordion-toggle:active, .hp3-store-card:active{
  opacity: var(--press-opacity, 0.6);
}

/* ---- Media-card hover: ONE gesture, every media surface ---------------------
   Contract (design-system.md §14): `data-media-hover` on the element the pointer
   enters, `data-media-zoom` on the image/placeholder that scales inside it. That
   pair — not a per-section selector — is what news/search/roster/video/album/photo
   all share, so the gesture is declared once here instead of eight times.

   Two layers, per §13.2:
     - GSAP (js/animations.js initMediaHover) adds .is-motion on the root and
       plays the real gesture: one continuous, eased zoom to --hover-zoom-enhanced
       — see that function's comment for why GSAP over plain CSS. It drives
       transform inline every frame, so the transition below MUST be off or it
       would smooth (and lag) GSAP's own output.
     - No GSAP / reduced motion: .is-motion is never added, and the
       :not(.is-motion) rule below is the fallback — a plain single-stage zoom
       to --hover-zoom, unchanged since before GSAP touched this gesture.
   A media card that must NOT react (the org-chart staff tile — it isn't a link)
   simply omits the attributes; there is no opt-out modifier to keep in sync. */
[data-media-zoom]{ transition:transform var(--dur-short) var(--ease-move); }
[data-media-hover].is-motion [data-media-zoom]{ transition:none; }
@media (hover: hover){
  [data-media-hover]:not(.is-motion):hover [data-media-zoom]{ transform:scale(var(--hover-zoom)); }
}
/* :not(.is-motion) above conflates TWO different reasons GSAP is absent — the
   CDN failed, or the visitor asked for reduced motion — and gives both the
   same single-stage zoom. That's the right call for the first (some feedback
   beats none if GSAP breaks) but not the second: a visitor who set this
   OS-level preference still saw a scale animate, just a simpler one (reported
   live 2026-07-15, macOS Reduce Motion). This is the theme's SECOND plain-CSS
   reduced-motion gate (img[data-media-skeleton]'s above was the first) — same
   fix shape, applied here: no transition, no transform, full stop, regardless
   of .is-motion. */
@media (prefers-reduced-motion: reduce){
  [data-media-zoom]{ transition:none; }
}
/* :hover stays gated behind (hover: hover) even here — design-system.md
   §5.5/ds-lint's rule for every :hover holds regardless of which OTHER media
   feature it's combined with; a touch device was never going to trigger this
   :hover in the first place, reduced-motion or not. */
@media (hover: hover) and (prefers-reduced-motion: reduce){
  [data-media-hover]:not(.is-motion):hover [data-media-zoom]{ transform:none; }
}

/* ---- Media-loading shimmer: opt-in on the <img> itself ---------------------
   Contract (design-system.md §14): add `data-media-skeleton` directly on the
   same <img> that already carries loading="lazy" (+ data-media-zoom, where
   present — the two attributes are independent, composable concerns, not a
   fused pair: staffCard's photo takes this one with no hover-zoom at all).
   NEVER on a .hp3-video__ph "no thumbnail at all" fallback div — that's a
   permanent empty state, not a loading state; an eternal shimmer there would
   misleadingly promise content that will never arrive.

   Why on the <img>, not a wrapper ::after: see docs/decisions.md — pseudo-
   elements don't render on replaced elements, backgrounds do.

   Second deliberate exception to "only transforms/opacity animate" (§13.2,
   the first being the mobile accordion's height collapse): this animates
   background-position, a paint- not compositor-only property. Accepted
   because the box is small/fixed-size and the loop is bounded (stops dead at
   .is-loaded) — see decisions.md. NOT the content-gap "skeleton graphics"
   rejected 2026-07-10 (that was about faking absent CONTENT when a section
   has zero real items; this is a real image, real src, already in flight). */
img[data-media-skeleton]{
  /* A translucent white sheen at a fixed, low ALPHA (not a color-mix
     percentage) — direct feedback twice on 2026-07-15: a bolder mix (tried
     first at 40%, then 8%) still read as a bright diagonal glare, especially
     over a DARK --placeholder (ColorMode=dark resolves it to #2e2c34; ds-lint: allow-hex — a
     documented resolved value in prose, not a live style literal — set in
     head.tplp) — mixing toward opaque white is a much bigger relative jump on
     a dark base than a light one, no matter how small the percentage. A fixed
     alpha composites the SAME small amount of light over any base tone, so
     light and dark placeholders both get an equally quiet sheen, not a
     proportionally-scaled one. Stops spread across the full 0–100% (not a
     narrow band) so the highlight fades in and out gradually instead of
     reading as a hard bar. background-color carries the placeholder tone
     itself; the gradient layer only ever adds the sheen on top of it. */
  background-color:var(--placeholder,#e0e0e0);
  background-image:linear-gradient(100deg, transparent 0%, rgba(255,255,255,0.05) 50%, transparent 100%);
  background-size:200% 100%;
  animation:hp3-shimmer var(--dur-shimmer,1.6s) linear infinite;
}
img[data-media-skeleton].is-loaded{
  background-image:none;
  animation:none;
}
@keyframes hp3-shimmer{
  from{ background-position:150% 0; }
  to{ background-position:-50% 0; }
}
/* Theme's first plain-CSS reduced-motion gate (existing reduced-motion checks
   are either inline JS or behind gsap.matchMedia — this is neither). */
@media (prefers-reduced-motion: reduce){
  img[data-media-skeleton]{ animation:none; background-image:none; }
}

/* Empty-state media placeholder: a SETTLED neutral fill for media that has NO
   source at all (e.g. a news item with no cover uploaded). Deliberately NOT the
   shimmer — that reads as "loading forever" for content that will never arrive
   (direct feedback 2026-07-16). A faint centred image glyph marks it as
   "no image", distinct from a broken/loading tile. Fills its container box
   (the caller's .hp3-news__thumb / media wrapper). */
.hp3-media-ph{
  width:100%; height:100%;
  display:grid; place-items:center;
  background:var(--placeholder,#e0e0e0);
}
.hp3-media-ph::after{
  content:"";
  width:clamp(28px, 16%, 52px); aspect-ratio:4/3;
  background:var(--muted,#6b675f); opacity:.3;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z'/%3E%3C/svg%3E") center/contain no-repeat;
}
/* A real src that failed to load (404): initMediaSkeleton hides the broken-image
   glyph, leaving the container's own neutral background — no glyph, no endless
   shimmer (the shimmer is already stopped by .is-loaded on settle). */
img[data-media-skeleton].is-missing{ visibility:hidden; }
/* A missing single-news hero would leave an empty framed box — drop the whole
   figure instead of a blank gap. */
.hp3-article__media:has(img.is-missing){ display:none; }

/* ---- Shared UI atoms: view-all links, CTA buttons, nav controls, dots,
   section heading/head-row — markup lives in include/macros-ui.tplp.
   view-all links AND nav controls now share one visual style everywhere —
   Hero, News, Video, Roster and Sponsor no longer look different from each
   other (Roster's old filled-red "next" button is gone too, for the same
   reason). CTA buttons still take an `extraClass` for section-specific
   size/skin — see docs/decisions.md, 2026-07-08 "Shared UI macros". */
.hp3-ui-link, .hp3-ui-btn{
  display:inline-flex; align-items:center; text-decoration:none;
}
.hp3-ui-link{
  gap:var(--space-1,4px);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  font-weight:var(--font-weight-semibold,600);
  font-size:var(--text-body);
  letter-spacing:normal;
  color:var(--muted,#6b675f);
  border-bottom:1px solid rgba(0,0,0,0.15);
  padding-bottom:2px;
  transition:color var(--dur-micro) ease, border-color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-ui-link:hover{ color:var(--primary,#E30514); border-color:var(--primary,#E30514); } }
/* row variant — full-width label/arrow row with a top divider (e.g. "Classifica
   completa" under the standings). Display font, brand-scaled, no bottom border. */
.hp3-ui-link--row{
  display:flex; justify-content:space-between; width:100%;
  gap:var(--space-3,12px);
  font-family:var(--font-display,'Anton',sans-serif); font-weight:var(--font-weight-regular,400);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-eyebrow));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wider,0.18em)); text-transform:uppercase;
  color:var(--ink,#0a0a14);
  border-top:1px solid var(--ink,#0a0a14); border-bottom:none;
  padding:var(--space-4,18px) var(--space-1,4px) 0;
}
@media (hover: hover){ .hp3-ui-link--row:hover{ color:var(--primary,#E30514); } }
.hp3-ui-link--row .hp3-ui-link__arrow{ font-size:var(--text-body-lg); }
/* on-dark variant — display/upper link over a photo scrim (e.g. hero "Leggi
   l'articolo"): bold primary underline, light text, primary arrow. */
.hp3-ui-link--on-dark{
  font-family:var(--font-display,'Anton',sans-serif); font-weight:var(--font-weight-regular,400);
  font-size:var(--text-body);
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wider,0.18em)); text-transform:uppercase;
  color:var(--paper,#f2f0eb);
  border-bottom:2px solid var(--primary,#E30514); padding-bottom:var(--space-1,4px);
}
@media (hover: hover){ .hp3-ui-link--on-dark:hover{ color:var(--primary-ink,#E30514); border-color:var(--primary,#E30514); } }
.hp3-ui-link--on-dark .hp3-ui-link__arrow{ color:var(--primary-ink,#E30514); }
/* Button — one component, enumerated size × variant (design-system.md §5.1).
   Display font, brand-scaled like every other display text. No caller sets
   font-size/padding/skin anymore; the retired `extraClass` is layout-only now. */
.hp3-ui-btn{
  justify-content:center; gap:var(--space-2,8px);
  font-family:var(--font-display,'Anton',sans-serif);
  text-transform:uppercase;
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wider,0.18em));
  line-height:1; white-space:nowrap; cursor:pointer;
  border:1px solid transparent; border-radius:var(--radius,2px);
  transition:background var(--dur-micro) ease, color var(--dur-micro) ease, opacity var(--dur-micro) ease;
}
/* size: font + padding as one step */
.hp3-ui-btn--sm{ font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-caption));  padding:var(--space-2,9px) var(--space-4,18px); }
.hp3-ui-btn--md{ font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-subtitle)); padding:var(--space-3,12px) var(--space-6,26px); }
.hp3-ui-btn--lg{ font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-sm)); padding:var(--space-4,16px) var(--space-8,32px); }
/* variant: color skin */
.hp3-ui-btn--solid{ background:var(--primary,#E30514); color:var(--on-primary,#fff); border-color:var(--primary,#E30514); }
@media (hover: hover){ .hp3-ui-btn--solid:hover{ opacity:0.86; } }
.hp3-ui-btn--invert{ background:var(--card,#ffffff); color:var(--primary-ink,#E30514); }
@media (hover: hover){ .hp3-ui-btn--invert:hover{ opacity:0.86; } }
.hp3-ui-btn--on-dark{ background:rgba(0,0,0,0.2); color:var(--paper,#f2f0eb); }
@media (hover: hover){ .hp3-ui-btn--on-dark:hover{ background:rgba(0,0,0,0.32); } }
.hp3-ui-btn--outline{ background:transparent; color:var(--primary-ink,#E30514); border-color:var(--primary,#E30514); }
@media (hover: hover){ .hp3-ui-btn--outline:hover{ background:var(--primary,#E30514); color:var(--on-primary,#fff); } }
.hp3-ui-btn--ghost{ background:transparent; color:var(--ink,#0a0a14); border-color:currentColor; }
@media (hover: hover){ .hp3-ui-btn--ghost:hover{ color:var(--primary-ink,#E30514); } }

.hp3-ui-navgroup{ gap:var(--space-2,8px); flex-shrink:0; }
/* Bootstrap's d-flex is !important, so the JS-controlled no-overflow state
   needs equal specificity/importance to remove an otherwise inert control. */
.hp3-ui-navgroup.is-static{ display:none!important; }
.hp3-ui-navbtn{
  display:flex; align-items:center; justify-content:center; flex-shrink:0;
  width:clamp(30px,3.6vw,42px); height:clamp(30px,3.6vw,42px);
  background:var(--card,#ffffff); color:var(--ink,#0a0a14);
  border:1px solid var(--ink,#0a0a14); border-radius:var(--radius,2px);
  cursor:pointer;
  transition:background var(--dur-micro) ease, color var(--dur-micro) ease;
}
/* Guarded by hover:hover so touch devices never trigger the sticky-hover state
   that keeps the button "pressed" (black) until you tap elsewhere. */
@media (hover: hover){
  .hp3-ui-navbtn:hover:not(:disabled){ background:var(--surface,#111014); color:var(--paper,#f2f0eb); }
}
/* :focus-visible (not :focus) so the ring shows only for keyboard nav, never on
   pointer tap/click — a plain :focus would reintroduce a sticky pressed look. */
.hp3-ui-navbtn:focus{ outline:none; }
.hp3-ui-navbtn:focus-visible{ outline:2px solid var(--primary,#E30514); outline-offset:2px; }
.hp3-ui-navbtn:disabled{ opacity:0.35; cursor:default; }
.hp3-ui-navbtn svg{ width:14px; height:14px; }
/* on-dark variant — every navbtn that sits inside an overlay/scrim over dark
   media (video/photo lightbox close+prev/next, hero-nav-overlay) instead of a
   light card background. Exact inverse of the default skin above: dark chrome
   so it recedes into the near-black overlay by default, flips to the light
   skin on hover so it still reads as an affordance. Border stays --paper
   through both states (mirrors the base rule's border always being --ink),
   so it's a visible edge on the dark default and a seamless one on hover. */
.hp3-ui-navbtn--on-dark{
  background:var(--surface,#111014); color:var(--paper,#f2f0eb); border-color:var(--paper,#f2f0eb);
}
@media (hover: hover){
  /* color:var(--surface), NOT var(--ink) (2026-07-21 fix — direct feedback:
     "the X basically disappears in Dark Mode"): --surface/--paper are the
     "chrome" tokens this whole component is built from, deliberately left
     untouched by options.select.ColorMode=dark (see head.tplp's dark-mode
     block and its own comment). --ink is the EDITORIAL text token dark mode
     DOES invert (near-black -> near-white, so page copy stays readable on
     the now-dark page/card). Pairing --paper with --ink here was mixing a
     mode-fixed token with a mode-variant one — in dark mode both resolved to
     near-white, so a hovered close/prev/next button in an on-dark context
     (video/photo lightbox, hero nav) went white-on-white. --surface's
     fallback is visually near-identical to --ink's own light-mode fallback
     (both a near-black) — no visible change there — but, unlike --ink,
     --surface stays dark in every ColorMode. */
  .hp3-ui-navbtn--on-dark:hover:not(:disabled){ background:var(--paper,#f2f0eb); color:var(--surface,#111014); }
}

.hp3-ui-dots{ display:flex; align-items:center; justify-content:center; }
/* Real <button>s (2026-07-12) — reset UA border/padding so the tiny pill/circle
   sizing (set per-variant by .hp3-sponsor__dot) is unchanged, and add
   the same focus-visible ring as .hp3-ui-navbtn since it's now keyboard-reachable. */
.hp3-ui-dot{ display:block; border:none; padding:0; }
.hp3-ui-dot:focus{ outline:none; }
.hp3-ui-dot:focus-visible{ outline:2px solid var(--primary,#E30514); outline-offset:2px; }

/* Same focus-visible ring, for the controls that became real <button>s
   2026-07-12 (mega-menu triggers, hamburger, search toggle) — keeps the whole
   theme's keyboard-focus look consistent instead of falling back to each
   browser's own default outline shape. (Video/photo close now gets its ring
   from .hp3-ui-navbtn:focus-visible instead — see main.css "Interactive
   states" — since it's the same component now, not a bespoke one.) */
.hp3-header__item:focus{ outline:none; }
.hp3-header__item:focus-visible{ outline:2px solid var(--primary,#E30514); outline-offset:2px; }
.hp3-ui-iconbtn:focus{ outline:none; }
.hp3-ui-iconbtn:focus-visible{ outline:2px solid var(--primary,#E30514); outline-offset:2px; }
.hp3-store-card:focus-visible{ outline:2px solid var(--primary,#E30514); outline-offset:2px; }

/* Social links — one component, two skins (design-system.md §6.1).
   boxed = footer bordered squares; bare = topbar plain inline icons. */
.hp3-social{ display:flex; align-items:center; flex-wrap:wrap; }
.hp3-social__link{ text-decoration:none; flex-shrink:0; }
/* The theme owns the glyph size: the real Slycons helper defaults to 24px
   when getIcon() gets no size (verified in IconsHelper.php — the harness
   stub used to hardcode 13px and masked this; see decisions.md 2026-07-10). */
.hp3-social__link svg{ width:13px; height:13px; }
.hp3-social--boxed{ gap:var(--space-2,10px); }
.hp3-social--boxed .hp3-social__link{
  width:clamp(28px,2.6vw,32px); height:clamp(28px,2.6vw,32px);
  border:1px solid rgba(255,255,255,0.1); border-radius:var(--radius,2px);
  color:var(--muted-4,#5a575a);
  transition:color var(--dur-micro) ease, border-color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-social--boxed .hp3-social__link:hover{ color:var(--paper,#f2f0eb); border-color:rgba(255,255,255,0.3); } }
.hp3-social--bare{ gap:13px; color:var(--sand,#cfc9c0); }
.hp3-social--bare .hp3-social__link{ color:inherit; transition:color var(--dur-micro) ease; }
@media (hover: hover){ .hp3-social--bare .hp3-social__link:hover{ color:var(--primary-ink,#E30514); } }

/* Share bar (ui.shareBar) — the generic "share this content" row: deliberately
   DISCREET — a small muted label + bare icons (no button box: the theme's buttons
   are sharp rectangles, so a round chip would be a foreign shape; bare marks read
   quieter and more elegant). Icons colour to the brand on hover; the invisible
   padding keeps a comfortable tap target on touch. Drop it into any single item. */
.hp3-share{ display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-3,0.75rem); }
.hp3-share__label{
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:var(--text-caption); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--muted,#6b675f);
}
/* Positioning anchor for .hp3-share__feedback (out-of-flow — see below): its
   box always matches .hp3-share__list's, regardless of the list's own wrap. */
.hp3-share__row{ position:relative; display:flex; align-items:center; }
.hp3-share__list{ display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-1,4px); list-style:none; margin:0; padding:0; }
.hp3-share__btn{
  display:inline-flex; align-items:center; justify-content:center;
  padding:6px; border:none; background:transparent; cursor:pointer; font:inherit; text-decoration:none;
  color:var(--muted-4,#5a575a);
  transition:color var(--dur-micro) ease;
}
/* The native button ships [hidden] (revealed by JS on supported devices) — a
   class rule would otherwise beat the UA [hidden] display:none. */
.hp3-share__btn[hidden]{ display:none; }
.hp3-share__btn svg{ width:17px; height:17px; display:block; }
@media (hover: hover){ .hp3-share__btn:hover{ color:var(--primary-ink,#E30514); } }
.hp3-share.is-copied .hp3-share__btn--copy{ color:var(--primary-ink,#E30514); }
/* Out-of-flow ALWAYS (anchored to .hp3-share__row, which never resizes) —
   showing/hiding "Link copiato" must never change layout, full stop: not the
   row's width (centred placements like .hp3-video__player-share would
   re-centre and shift the icons sideways), and not by reserving space either
   (a padding-driven "push the next block down" was tried and reverted — inside
   the lightbox's vertically-centred stage that re-centres it too, shifting the
   icons vertically instead; the exact same bug on the other axis). Since it
   can therefore land on top of whatever sits below (an article's cover image,
   a photo), it carries its own solid chip background so it stays legible
   regardless of what's under it — a background is what pays for zero layout
   space, not reserved room. */
.hp3-share__feedback{
  position:absolute; left:100%; top:50%; transform:translateY(-50%);
  margin-left:var(--space-3,0.75rem); white-space:nowrap;
  font-size:var(--text-caption); color:var(--primary-ink,#E30514);
  background:var(--card,#ffffff); padding:var(--space-1,4px) var(--space-2,10px);
  border-radius:var(--radius,2px);
}
.hp3-share__feedback:empty{ display:none; }
/* Narrow viewports: the icon row already fills most of the width (see the
   photo/video lightbox share bar), so anchoring the feedback to the row's
   right edge at label-height runs it off-screen. Drop it below instead, right
   edge flush with the copy icon (the last icon, i.e. the row's own right
   edge) — stays right next to the button that triggered it instead of
   floating under the row's midpoint, and growing leftward from a right edge
   that's already on-screen can't overflow the way growing rightward did. */
@media (max-width:639px){
  .hp3-share__feedback{
    left:auto; right:0; top:100%; transform:none;
    margin-left:0; margin-top:var(--space-1,4px);
  }
}
/* Per-placement spacing (layout-only `extra` hooks, no re-skin): under an article
   body, hairline-separated; above a video's prev/next nav. */
.hp3-article__share{ margin:var(--space-6,1.5rem) 0 0; }
/* When the share sits right above the cover, collapse the cover's own top gap so
   the two stay close (the cover keeps its full top margin after other elements). */
.hp3-article__share + .hp3-article__media{ margin-top:var(--space-3,0.75rem); }
.hp3-video-page__share{ margin:0 0 var(--space-8,2rem); }
/* On-dark variant (inside the video lightbox) — light icons/label on the scrim,
   centred under the caption. */
.hp3-share--on-dark .hp3-share__label{ color:rgba(255,255,255,0.6); }
.hp3-share--on-dark .hp3-share__btn{ color:rgba(255,255,255,0.72); }
@media (hover: hover){ .hp3-share--on-dark .hp3-share__btn:hover{ color:var(--paper,#f2f0eb); } }
.hp3-share--on-dark.is-copied .hp3-share__btn--copy{ color:var(--paper,#f2f0eb); }
/* Dark chip, not the light .hp3-share__feedback default — a light card on the
   lightbox's own dark scrim would look like a mistake, not a match. */
.hp3-share--on-dark .hp3-share__feedback{ color:var(--paper,#f2f0eb); background:rgba(0,0,0,0.6); }
/* Pinned to the overlay itself (position:fixed on .hp3-video__player-overlay /
   .hp3-photo__player-overlay is its containing block), a sibling of — not
   nested in — the centred .hp3-video__player-stage / .hp3-photo__player-stage.
   That stage's height tracks each video/photo's own aspect ratio and caption
   length; a share bar living inside it rode up/down with every change, most
   visibly across a photo album's mixed portrait/landscape images (2026-07-16).
   Anchoring it to the overlay's own bottom edge instead keeps it still. */
.hp3-video__player-share{
  position:absolute; left:50%; bottom:clamp(16px,3vw,32px);
  /* width:auto here would resolve against the space from left:50% to the
     containing block's right edge (i.e. only the RIGHT HALF of the overlay,
     ~187px on a 375px phone) — an absolutely-positioned box with `left` set
     and `right` auto shrinks to fit THAT space, not its own content, so the
     label wrapped above the icons and the icons wrapped again well before
     running out of real screen room either side (found 2026-07-16). Forcing
     the content's own (unwrapped) width sidesteps that and lets max-width
     below be the only thing that ever forces a wrap. */
  width:max-content;
  transform:translateX(-50%);
  justify-content:center;
  max-width:calc(100% - 32px);
  z-index:2;
}
/* The general mobile rule above drops .hp3-share__feedback BELOW the row —
   correct for an in-flow share bar, where there's always more page underneath,
   but this bar is pinned near the OVERLAY's own bottom edge, so "below" pushes
   the "Link copiato" chip past the actual viewport edge — not just visually
   cramped, genuinely unreachable/invisible, since the overlay that contains it
   already covers the full viewport with no scroll (confirmed live, 2026-07-16).
   Flip to above instead: the image/video fills the space above the bar, so
   there's always room, and the row's own bottom-right anchor point doesn't
   change (still right:0, flush with the copy icon). */
@media (max-width:639px){
  .hp3-video__player-share .hp3-share__feedback{
    top:auto; bottom:100%; margin-top:0; margin-bottom:var(--space-1,4px);
  }
}

/* Crest — logo-or-initials avatar (design-system.md §7.2). sm = standings-row
   logo (small square), md = prominent list row (full standings page), lg =
   fixtures matchup team (big, circular fallback). md keeps sm's square-chip
   fallback, just scaled — it's a list-row size, not a hero one. */
.hp3-crest{ object-fit:contain; flex-shrink:0; }
.hp3-crest--sm{ height:clamp(18px,2vw,24px); width:clamp(18px,2vw,24px); }
.hp3-crest--md{ height:clamp(30px,3.4vw,40px); width:auto; }
.hp3-crest--lg{ height:clamp(48px,6vw,64px); width:auto; }
.hp3-crest--fallback{ display:flex; align-items:center; justify-content:center; font-family:var(--font-display,'Anton',sans-serif); }
.hp3-crest--sm.hp3-crest--fallback{
  width:clamp(18px,2vw,24px);
  background:var(--surface,#111014); color:var(--paper,#f2f0eb); font-size:var(--text-3xs);
}
.hp3-crest--md.hp3-crest--fallback{
  width:clamp(30px,3.4vw,40px);
  background:var(--surface,#111014); color:var(--paper,#f2f0eb); font-size:var(--text-caption);
}
.hp3-crest--lg.hp3-crest--fallback{
  width:clamp(48px,6vw,64px); border-radius:50%;
  border:2px solid var(--sand,#cfc9c0); color:var(--sand,#cfc9c0); font-size:var(--text-body);
}
@media (max-width:639px){
  .hp3-crest--lg, .hp3-crest--lg.hp3-crest--fallback{ height:clamp(38px,10vw,46px); width:clamp(38px,10vw,46px); }
}

/* Icon button — a bare clickable icon (design-system.md §7.4): the search
   trigger and the hamburger. Colour inherits from context; hover → primary-ink.
   (2026-07-20: tried splitting this into a per-consumer dark<->light box
   inversion — reverted by request for all three consumers, back to this one
   shared rule. See docs/decisions.md 2026-07-20.)
   min-width/min-height (2026-07-20): the box used to collapse to the icon's
   own size (13px search/account, 24px hamburger) — under WCAG 2.5.8's 24px
   minimum. 32px clears that with margin and still fits the topbar's own
   32px minimum row height (main.css, .hp3-topbar__inner <=639px) without
   forcing it taller; the icon glyph itself keeps its existing px, only the
   invisible hit area grows around it. */
.hp3-ui-iconbtn{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:32px; min-height:32px;
  background:none; border:none; padding:0; color:inherit; cursor:pointer;
  transition:color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-ui-iconbtn:hover{ color:var(--primary-ink,#E30514); } }

/* ---- Typographic roles (see docs/design-system.md §4). One recipe per role,
   so a heading/label size or tracking changes in ONE place instead of the
   ~15 hand-styled eyebrow / ~7 title sites these replace. Display-font roles
   carry the --type-scale-brand optical correction; the body-font ones
   (.hp3-eyebrow--body, .hp3-subtitle) must NOT — see docs/decisions.md
   2026-07-08 "hero-row h4" bug. Local section classes keep only their own
   LAYOUT (margin/align/special color), never the typography again. ---- */

/* Eyebrow / kicker: small uppercase label above/beside a title, or a meta line. */
.hp3-eyebrow{
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-eyebrow));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wider,0.18em));
  text-transform:uppercase;
  line-height:calc(var(--leading-scale,1) * var(--leading-snug,1.1));
  color:var(--muted,#6b675f);
}
/* family: Inter, no brand multiplier — for meta lines that were always body font */
.hp3-eyebrow--body{
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  font-size:var(--text-eyebrow);
}
/* tone (color role) — default is --muted above */
.hp3-eyebrow--accent{  color:var(--accent-ink,#fa622b); }
.hp3-eyebrow--primary{ color:var(--primary-ink,#E30514); }
.hp3-eyebrow--on-dark{ color:var(--sand,#cfc9c0); }
/* tracking variants (heading-eyebrows sit wider; brand labels a touch tighter) */
.hp3-eyebrow--wide{   letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em)); }
.hp3-eyebrow--widest{ letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-widest,0.26em)); }

/* Headings — display font, uppercase, brand-scaled. .hp3-title-2 keeps the old
   .hp3-section-heading name as an alias during migration. */
.hp3-title-display{
  margin:0; font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-display));
  line-height:calc(var(--leading-scale,1) * var(--leading-snug,1.1));
}
.hp3-title-1{
  margin:0; font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-xl));
  line-height:calc(var(--leading-scale,1) * var(--leading-tight,0.92));
}
.hp3-title-2, .hp3-section-heading{
  margin:0; font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-lg));
  line-height:1; text-transform:uppercase;
}
.hp3-title-3{
  margin:0; font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-sm));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-tight,0.03em));
  line-height:calc(var(--leading-scale,1) * var(--leading-snug,1.1));
}
/* one size up — video caption title (bigger than the roster-name default) */
.hp3-title-3--md{ font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-md)); }
/* Subtitle — body font (Inter), NOT brand-scaled — card titles that were Inter. */
.hp3-subtitle{
  margin:0; font-family:var(--font-body,'Inter',system-ui,sans-serif);
  font-size:var(--text-subtitle);
  line-height:calc(var(--leading-scale,1) * var(--leading-normal,1.3));
}
/* Clamp helper for card titles so cards line up — compose with a title role. */
.hp3-card-title{
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.hp3-section-head{
  display:flex; align-items:flex-end; justify-content:space-between; flex-wrap:wrap;
  gap:var(--section-head-gap, clamp(12px,2vw,20px));
  margin-bottom:var(--section-head-mb, 26px);
}
@media (max-width:639px){
  .hp3-section-head{ margin-bottom:var(--section-head-mb, 16px); }
}

/* ---- Brand stripe, from a team declination's section-brand-stripe.dc.html ---- */

/* Used two places (see section-brand-stripe.tplp): nested inside .hp3-header
   (top instance, travels with the sticky header) and inside .hp3-footer's
   markup (bottom instance, static). The z-index here only matters for the
   header instance, where it's now a CHILD of .hp3-header (since 2026-07-15)
   rather than a sibling — being a child, it already paints after (on top of)
   .hp3-header's own background/box-shadow by normal DOM paint order, so this
   z-index is a defensive belt-and-braces value, not load-bearing the way it
   was when the stripe lived as a sibling in tpl/home.tpl (see the
   2026-07-09 mobile-drawer stacking-context fix in docs/decisions.md — that
   bug doesn't reproduce now that the stripe is a descendant of .hp3-header,
   since the mega-menu's z-index:30 and the mobile drawer's z-index:31 both
   already outrank this within that same stacking context). */
.hp3-stripe-container{ width:100%; display:flex; flex-direction:column; position:relative; z-index:6; }
.hp3-stripe__band{ width:100%; flex:none; }
.hp3-stripe__white-mid{ height:clamp(5px,0.5vw,8px); background:#ffffff; } /* ds-lint: allow-hex — deliberate pure-white stripe band */
.hp3-stripe__red-thin{ height:clamp(4px,0.4vw,6px); background:var(--primary,#c8102e); }
.hp3-stripe__red-thick{ height:clamp(22px,2.2vw,33px); background:var(--primary,#c8102e); }

/* ---- Header (topbar + logo + nav + mega menu), from section-header.dc.html ---- */

/* js/script.js's lockScroll() adds this to <body> for every full-viewport
   scroll-locking overlay (search panel, video/photo lightbox — see its own
   comment on why). visibility, not display: none — keeps .hp3-header's flex
   column box so main/footer don't reflow, just drops it from paint AND focus
   order (a hidden-behind-a-scrim header staying tabbable would be its own a11y
   bug). Verified live: without this, `.hp3-header`/`.hp3-topbar` paint above
   these overlays regardless of z-index, because `.hp3-header`'s position:sticky
   has no scrolling ancestor once lockScroll() pins <body> — a real browser
   stacking bug, not something the overlay side can fix. */
body.hp3-scroll-locked .hp3-topbar,
body.hp3-scroll-locked .hp3-header{ visibility:hidden; }

.hp3-topbar{
  /* Full-bleed background only — the grid (content) lives in
     .hp3-topbar__inner below, capped at 1920px like every other section.
     See docs/decisions.md, 2026-07-08. */
  background:var(--topbar-bg,var(--surface,#111014)); color:var(--paper,#f2f0eb);
  width:100%;
}
.hp3-topbar__inner{
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
  gap:clamp(8px,2vw,24px);
  padding:6px 44px;
  min-height:clamp(32px,3.4vw,40px);
  font-family:var(--font-display,'Anton',sans-serif);
  width:100%; max-width:1920px; margin:0 auto;
}
/* Topbar social is now ui.socialLinks(options,'bare',3) — skin from
   .hp3-social--bare (design-system.md §6.1); only its grid placement is local
   (and the display:none at <=1023 in the @media block below), except the icon
   svg size below. */
.hp3-topbar__social{ grid-column:1; justify-self:start; }
/* 13px -> 16px, through 20px/18px way-stations (2026-07-20): .hp3-social__link
   svg's shared default (main.css, 13px) was never meant as a final size, just
   a fallback — the footer already overrides it to 20px (.hp3-footer__social)
   and the topbar didn't, so the social row was quietly the SMALLEST thing on
   the bar while the search/account icons beside it got bumped up too —
   backwards hierarchy for a brand-identity element. Settled at 16px after two
   "slightly smaller" follow-up passes — still matched to the other topbar
   icons (search/account/crest, same edit below), below the footer's own
   20px precedent — the topbar and footer no longer need to match each
   other exactly, they're different rows with different weight. */
.hp3-topbar__social .hp3-social__link svg{ width:16px; height:16px; }
/* Topbar left-slot alternative to the social icons (options.bool.TopbarQuickLinks,
   see include/header.tplp). Same grid placement as .hp3-topbar__social; inherits
   the topbar's display font from .hp3-topbar__inner, styled to match the uppercase
   caption tone of the rest of the topbar text. Hidden <=1023px alongside the
   social icons in the media block below. */
.hp3-topbar__quicklinks{ grid-column:1; justify-self:start; display:flex; align-items:center; gap:clamp(12px,1.4vw,22px); }
/* font-size bumped --text-caption -> --text-body (2026-07-20): the topbar row
   grew (see .hp3-ui-iconbtn's min-height above) to give the search/hamburger
   a real tap target, and the old 12px caption text read as lost/adrift in the
   taller bar — direct feedback. quicklinks + match + label (below) move
   together so the row stays visually even. Token stepped down again from
   --text-body to --text-eyebrow (15px -> 13px) on a follow-up "slightly
   smaller overall" pass, alongside the icon/logo size below — still bigger
   than the original 12px caption this whole pass started from. */
/* font-size uses the same calc(--type-scale * --type-scale-brand * token)
   pattern as .hp3-eyebrow (main.css ~194), not a flat var() — 2026-07-20,
   found while chasing a "topbar text reads smaller than the rest of the
   page" report on a club with a licensed display font: that club's :root
   override sets --type-scale-brand:1.3 (its licensed face reads smaller at
   the same px than the base font), and every OTHER eyebrow-styled element on
   that club already gets that 1.3x correction — this row didn't, so it was
   quietly the one under-sized holdout on that specific club, not actually
   mis-sized in absolute terms (on the base/default club, --type-scale-brand:1,
   so this calc resolves to the exact same px as before). Fixing this HERE,
   via the existing per-font correction mechanism, is deliberately not the
   same as hand-tuning the px against how any one club happens to look. */
.hp3-topbar__quicklinks a{
  color:var(--sand,#cfc9c0); text-decoration:none; white-space:nowrap; line-height:1;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-caption));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  text-transform:uppercase;
  transition:color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-topbar__quicklinks a:hover{ color:var(--paper,#f2f0eb); } }
.hp3-topbar__match{
  grid-column:2;
  display:flex; align-items:center; justify-content:center;
  white-space:nowrap;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-caption)); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em)); text-transform:uppercase; color:var(--sand,#cfc9c0);
}
.hp3-topbar__match > div{ white-space:nowrap; }
/* Topbar next-match sub-parts — tokenized (was inline styles, which never picked
   up the token system; de-inlined 2026-07-09, design-system.md §6.2). */
.hp3-topbar__match-side{ display:flex; align-items:center; gap:11px; line-height:1; }
.hp3-topbar__match-lead{ color:var(--muted-2,#9a958c); }
/* --muted-2, not --muted (2026-07-15 a11y fix): --muted is a light-background
   token (5.08:1 on --page) and measured 3.37:1 at the topbar's caption size
   (any size under the WCAG "large text" threshold keeps the 4.5:1 requirement,
   so this holds regardless of the 2026-07-20 12px->15px bump) against the
   topbar's dark --surface — same class of bug as the footer fixes above.
   Now level with .hp3-topbar__match-lead rather than one step below it; there's
   no third dark-safe muted tone to keep them differentiated. */
.hp3-topbar__match-vs{ color:var(--muted-2,#9a958c); padding:0 14px; }
/* Played-match variant: the score takes the central 'vs' slot and is the
   strip's most important information — full paper color against the muted
   supporting text around it. */
.hp3-topbar__match-score{ color:var(--paper,#f2f0eb); }
/* 15px -> 16px, through 20px/18px way-stations (2026-07-20): level with the
   social/search/account icons beside it on the same row instead of sitting a
   full size class below them — was reading as the "tiny logos" in the direct
   feedback that started this pass, then stepped down twice more alongside
   those same icons on "slightly smaller" follow-ups. */
.hp3-topbar__match-logo{ width:16px; height:16px; object-fit:contain; flex-shrink:0; }
/* align-items:center (2026-07-20): the search toggle is now a 32px hit-area
   box (.hp3-ui-iconbtn) but the account glyph beside it is a bare 13px <svg>
   with no box of its own — without this they used to coincidentally match at
   13px each; growing just one sibling exposed the missing cross-axis
   alignment (the svg default-aligned to the row's top instead of centering
   against the taller button). */
.hp3-topbar__icons{ grid-column:3; display:flex; align-items:center; gap:16px; color:var(--sand,#cfc9c0); justify-self:end; flex-shrink:0; }
/* Centre-slot motto fallback — shown (both desktop and mobile) only when there
   is no match; header.tplp renders match XOR label, so it shares the match's
   grid-column:2 centre placement and never coexists with it. Typography comes
   from .hp3-eyebrow--primary/--wide (design-system.md §4); placement only here,
   except font-size: overridden to match .hp3-topbar__match's 2026-07-20 bump
   (same calc(--type-scale * --type-scale-brand * --text-body) pattern, not a
   flat var()) since the two are XOR alternates in the same slot — leaving the
   shared eyebrow role's own --text-eyebrow would make whichever one is
   showing visibly smaller than the other, depending on which club/state, and
   a flat var() would drop the per-font --type-scale-brand correction the
   shared .hp3-eyebrow rule already applies (see .hp3-topbar__match's comment
   above for why that correction matters here). */
.hp3-topbar__label{ grid-column:2; justify-self:center; display:flex; align-items:center; font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-caption)); }

/* .hp3-topbar__search-toggle styling now from .hp3-ui-iconbtn (design-system.md §7.4).
   Size the icon here, the same way .hp3-ui-navbtn sizes its own svg: .hp3-ui-iconbtn
   is a size-agnostic hit area shared with the 24px hamburger, so the icon's px can't
   live on the atom — and a Slycons glyph that ships no intrinsic width/height (the
   search magnifier) has nothing for getIcon()'s size arg to set, so it collapses
   without this. Matches the adjacent account icon — both moved 13px -> 16px on
   2026-07-20 alongside the topbar text and the social/crest icons
   (.hp3-topbar__social/.hp3-topbar__match-logo above): first bumped up
   (lost/adrift against the taller .hp3-ui-iconbtn row), then walked back down
   twice on "slightly smaller" follow-ups. All four topbar icon-class elements
   stay level with each other at 16px. */
.hp3-topbar__search-toggle svg{ width:16px; height:16px; }

/* ---- Search bar, include/search-bar.tplp — full-bleed component triggered
   by .hp3-topbar__search-toggle above. 2026-07-09: top-anchored (not
   centered), with a REDUCED scrim below it (.hp3-search-bar__scrim, own
   sibling element, own flex:1 slot — a lighter dim, not the video
   lightbox's near-opaque one, and not the earlier no-scrim-at-all take
   either — direct feedback was "less", not "none"). position:fixed on the
   root so the bar+scrim pair stays reachable/covers the page at any scroll
   position. Left open below the bar (no results markup here) so a future
   search.tpl can fill the rest of the viewport with results under it — see
   docs/decisions.md. Submit is the shared .hp3-ui-btn (md/solid), not a
   lookalike. */
.hp3-search-bar{
  display:none;
  position:fixed; inset:0; z-index:90;
  flex-direction:column;
}
.hp3-search-bar.is-open{ display:flex; }
.hp3-search-bar__bar{
  flex:none;
  background:var(--surface,#111014); color:var(--paper,#f2f0eb);
  box-shadow:var(--shadow-nav,0 16px 40px rgba(0,0,0,0.12));
  animation:hp3-dropdown-in var(--dur-short) var(--ease-enter);
}
/* Click-to-close target — a sibling of .hp3-search-bar__bar, not an
   ancestor, so clicks inside the bar never bubble into it; no
   stopPropagation needed on the bar's own content. */
.hp3-search-bar__scrim{ flex:1; background:rgba(6,6,10,0.5); }
.hp3-search-bar__inner{
  width:100%; max-width:1920px; margin:0 auto;
  display:flex; align-items:stretch; gap:var(--space-4,16px);
  padding:var(--space-4,16px) clamp(16px,4vw,44px);
}
.hp3-search-bar__form{ flex:1; min-width:0; display:flex; align-items:stretch; gap:var(--space-4,16px); }
/* Typography from .hp3-title-2 (design-system.md §4, applied in the
   markup) — the same "section H2" role as e.g. the "NEWS" heading,
   --type-scale-brand included. Only the input's own chrome (no box,
   underline instead) lives here; no font-size/family/case duplicated. */
.hp3-search-bar__input{
  flex:1; min-width:0;
  border:none; border-bottom:2px solid var(--line,rgba(255,255,255,0.10));
  background:transparent;
  padding:var(--space-2,8px) var(--space-1,4px);
  color:var(--paper,#f2f0eb);
  outline:none;
}
.hp3-search-bar__input::placeholder{ color:var(--sand-2,#b0aca4); }
.hp3-search-bar__input:focus{ border-color:var(--primary,#E30514); }
/* Suppress the native type=search clear-icon — this input's own custom
   type is the whole point, a stray browser-chrome × undermines it. */
.hp3-search-bar__input::-webkit-search-cancel-button{ -webkit-appearance:none; }
/* Empty-submit feedback (js/script.js initSearch) — the submit is still
   rejected client-side (no point round-tripping to /search?q=), but never
   silently: a shake + the red border + the hint line below are the visible
   "why". JS re-triggers the class on each rejected click (a CSS animation
   doesn't replay just because the class is already set), so the keyframe
   itself only needs to fire once per .is-invalid application. */
.hp3-search-bar__input.is-invalid{
  border-color:var(--danger,#ff5a5a);
  animation:hp3-shake .3s var(--ease-move);
}
@keyframes hp3-shake{
  0%,100%{ transform:translateX(0); }
  25%{ transform:translateX(-6px); }
  75%{ transform:translateX(6px); }
}
/* Skin/typography from .hp3-ui-btn--md--solid (design-system.md §5.1); the
   component already centers its own content, so only flex-shrink is local. */
.hp3-search-bar__submit{ flex-shrink:0; }
.hp3-search-bar__close{
  flex-shrink:0;
  display:flex; align-items:center;
  background:none; border:none; padding:0;
  color:var(--paper,#f2f0eb);
  cursor:pointer;
}
@media (hover: hover){ .hp3-search-bar__close:hover{ color:var(--sand,#cfc9c0); } }
/* Empty-submit hint text — hidden by default (this project's own
   toggle-a-class convention, not a Bootstrap display utility, see
   CLAUDE.md's "Working in theme/" note on the d-flex/d-none trap); JS adds
   .is-visible on a rejected submit and removes it once the field has real
   input again. Same max-width/padding as .hp3-search-bar__inner so the text
   lines up under the input instead of under the whole bar. */
.hp3-search-bar__hint{
  display:none;
  width:100%; max-width:1920px; margin:0 auto;
  padding:0 clamp(16px,4vw,44px) var(--space-3,12px);
  color:var(--danger,#ff5a5a);
  font-size:var(--text-caption,0.75rem);
}
.hp3-search-bar__hint.is-visible{ display:block; }
@media (max-width:639px){
  .hp3-search-bar__hint{ padding:0 16px var(--space-2,8px); }
  /* Direct, repeated correction: input+submit+× sharing one row was still
     the problem even once × stopped being a flex item (an absolute corner
     still sits in the same visual band as the other two on a 375px
     screen — three things competing for one cramped strip, regardless of
     which CSS mechanism holds the third one there). Real fix: stack two
     actual rows. `order` puts × visually first without moving it in the
     DOM (so the desktop row's L-to-R order — form, then × — is untouched);
     input+submit (only two things) then get the entire second row alone. */
  .hp3-search-bar__inner{
    flex-direction:column; align-items:stretch;
    padding:var(--space-3,12px) 16px;
    gap:var(--space-2,8px);
  }
  .hp3-search-bar__close{ order:-1; align-self:flex-end; }
  .hp3-search-bar__form{ gap:var(--space-2,8px); }
  .hp3-search-bar__submit{ padding:var(--space-2,9px) var(--space-4,18px); }
}

@media (max-width:1023px){
  .hp3-topbar__social,
  .hp3-topbar__quicklinks{ display:none; }
  /* No font-size override here anymore (was --text-3xs 9px, then --text-caption
     12px) — .hp3-topbar__match now inherits the base --text-body size like
     desktop. This range (640-1023px) hits the same row-height growth desktop
     does (.hp3-ui-iconbtn's 32px hit-area overflowing the 32px min-height +
     12px padding budget), so it needs the same bigger text, not a smaller one;
     only true mobile (<=639px, media block below) has its own space-driven
     reason to differ, and even there the row itself stays 32px (see that
     block's zero vertical padding) so text isn't fighting a taller bar. */
  /* Match .hp3-header__tablet-mobile-bar's 26px side inset (below) — without
     this the topbar kept the desktop 44px inset here, so its right edge sat
     18px short of the hamburger row's edge. */
  .hp3-topbar__inner{ padding:6px 26px; }
}
@media (max-width:639px){
  .hp3-topbar__inner{ grid-template-columns:auto auto; justify-content:space-between; min-height:32px; padding:0 var(--section-pad-x-mobile); }
  /* Whichever centre-slot element is present (match or motto) takes the single
     left cell; icons take the right. The match shows REDUCED here — its lead
     label and date drop out, leaving names · crests · score so a game still
     fits a phone width; the vs/score padding tightens to match. */
  .hp3-topbar__match,
  .hp3-topbar__label{ grid-column:1; justify-self:start; }
  .hp3-topbar__match-lead,
  .hp3-topbar__match-meta{ display:none; }
  .hp3-topbar__match-vs{ padding:0 8px; }
  .hp3-topbar__icons{ grid-column:2; }
  /* Quicklinks (Shop/Membership) win the left cell on mobile when the club uses
     them — shown here at the expense of the match/motto, which hide. On desktop
     both coexist; this only flips the priority on phones. */
  .hp3-topbar__inner--quicklinks .hp3-topbar__quicklinks{ display:flex; grid-column:1; }
  .hp3-topbar__inner--quicklinks .hp3-topbar__match,
  .hp3-topbar__inner--quicklinks .hp3-topbar__label{ display:none; }
}

.hp3-header{
  /* Sticky, not relative: the topbar (sibling above, in normal flow) scrolls away
     while this main bar pins to the top and stays visible on scroll — by request.
     sticky is itself a positioned + stacking context, so the z-index below (and
     the mobile-menu z-index scoping the comment further down relies on) still
     hold. js/script.js adds .is-stuck once the topbar has scrolled past; the shadow
     refinement is CSS (below) and js/animations.js settles the logo (GSAP). */
  position:sticky;
  top:0;
  z-index:5;
  width:100%;
  background:var(--card,#ffffff);
  color:var(--ink,#0a0a14);
  box-shadow:var(--shadow-nav,0 16px 40px rgba(0,0,0,0.12));
  font-family:var(--font-body,'Inter',sans-serif);
  transition:box-shadow var(--dur-medium,0.4s) var(--ease-enter,ease);
}
/* Pinned state (topbar scrolled past): swap the big soft nav shadow for a crisper,
   tighter one so the bar reads as floating over the content it now covers. No box
   height change — that would reflow the page (the sticky bar's flow slot is at the
   top); the compact cue is the shadow here + the GSAP logo settle. */
.hp3-header.is-stuck{ box-shadow:var(--shadow-nav-stuck,0 6px 22px rgba(0,0,0,0.16)); }

/* Homepage overlay mode removes the header's flow slot so the hero starts
	under it. Once scrolling begins, or a navigation panel opens, the regular
	card surface returns to preserve contrast and the existing sticky behavior. */
.hp3-page-shell--home-overlay{
	--home-overlay-topbar-height:clamp(32px,3.4vw,40px);
	position:relative;
}
.hp3-page-shell--home-overlay-no-topbar{ --home-overlay-topbar-height:0px; }
.hp3-page-shell--home-overlay-no-topbar .hp3-topbar{ display:none; }
/* The veil belongs to the complete overlay chrome, not only to the main nav:
	when the optional topbar is present it protects both rows uniformly. It is
	anchored to the page, so it naturally scrolls away under the solid sticky bar. */
.hp3-page-shell--home-overlay::before{
	content:"";
	position:absolute;
	top:0;
	left:0;
	right:0;
	height:calc(var(--home-overlay-topbar-height) + clamp(80px,6vw,100px) + var(--space-24,6rem));
	z-index:4;
	pointer-events:none;
	background:linear-gradient(to bottom,
		color-mix(in srgb,var(--surface-deep,#0a090d) 72%,transparent) 0%,
		color-mix(in srgb,var(--surface-deep,#0a090d) 34%,transparent) 60%,
		transparent 100%);
}
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar{
	position:absolute;
	top:0;
	left:0;
	z-index:6;
	background:transparent;
	color:var(--paper,#f2f0eb);
}
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar__match,
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar__icons,
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar__social,
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar__quicklinks a{
	color:var(--paper,#f2f0eb);
}
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar__match-lead,
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar__match-vs,
.hp3-page-shell--home-overlay:not(.hp3-page-shell--home-overlay-no-topbar) .hp3-topbar__match-meta{
	color:var(--sand,#cfc9c0);
}
.hp3-page-shell--home-overlay .hp3-header{
	position:absolute;
	top:var(--home-overlay-topbar-height);
	left:0;
	isolation:isolate;
	background:transparent;
	color:var(--paper,#f2f0eb);
	box-shadow:none;
}
.hp3-page-shell--home-overlay .hp3-header.is-stuck{
	position:fixed;
	top:0;
	background:var(--card,#ffffff);
	color:var(--ink,#0a0a14);
	box-shadow:var(--shadow-nav-stuck,0 6px 22px rgba(0,0,0,0.16));
}
.hp3-page-shell--home-overlay .hp3-header.is-nav-open,
.hp3-page-shell--home-overlay .hp3-header.is-menu-open{
	background:var(--card,#ffffff);
	color:var(--ink,#0a0a14);
}
.hp3-page-shell--home-overlay .hp3-header:not(.is-stuck):not(.is-nav-open):not(.is-menu-open) .hp3-header__item.is-active{
	color:var(--paper,#f2f0eb);
	box-shadow:inset 0 -2px 0 var(--paper,#f2f0eb);
}
@media (hover: hover){
	.hp3-page-shell--home-overlay .hp3-header:not(.is-stuck):not(.is-nav-open):not(.is-menu-open) .hp3-header__item:not(.is-active):hover{
		color:var(--paper,#f2f0eb);
		opacity:var(--press-opacity,0.6);
	}
}
/* .hp3-header__mobile-menu's own z-index:31 is scoped to .hp3-header's
   stacking context (created by the position+z-index above) and can never
   escape it — with the drawer open, .hp3-header still paints as a whole
   below the brand stripe (z-index:6, a sibling later in the DOM), so the
   stripe shows through on top of the open drawer. Lift the header's whole
   stacking context above the stripe only while the drawer is open (toggled
   in js/script.js); the header's own default z-index:5 stays below the
   stripe the rest of the time, which is what keeps its shadow from
   bleeding onto the stripe's white band (see .hp3-stripe-container above). */
.hp3-header.is-menu-open,
.hp3-header.is-nav-open{ z-index:10; }

.hp3-header__inner{ width:100%; max-width:1920px; min-width:0; }

.hp3-header__bar{
  min-height:clamp(80px,6vw,100px);
  padding:14px 44px;
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-subtitle));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wider,0.18em));
  text-transform:uppercase;
  position:relative;
  transition:min-height var(--dur-medium,0.4s) var(--ease-enter,ease), padding var(--dur-medium,0.4s) var(--ease-enter,ease);
}
/* Pinned condense: on scroll the bar drops height and tightens its padding —
   a stronger "compact mode" than the logo settle alone (js/animations.js). The
   height change reflows the sticky bar's flow slot (at the very top), but it's one
   smooth transition at the threshold, not per-frame, so the content below settles
   up rather than jumping. */
.hp3-header.is-stuck .hp3-header__bar{ min-height:clamp(52px,4vw,64px); padding-top:6px; padding-bottom:6px; }

.hp3-header__bar--center{ display:grid; grid-template-columns:1fr auto 1fr; }
.hp3-header__bar--left{ display:flex; gap:clamp(14px,2vw,26px); }

.hp3-header__nav{ gap:clamp(14px,2vw,26px); }
.hp3-header__nav--left{ justify-self:start; }
.hp3-header__nav--right{ justify-self:end; }
.hp3-header__nav--all{ flex:1; }

.hp3-header__item{
  gap:var(--space-1,6px);
  cursor:pointer;
  /* Leaf nav items are real <a>; parent (mega-menu) items are a real <button>
     (2026-07-12 — a keyboard user couldn't reach a <span> toggle at all). The
     background/border/font reset below is a no-op on <a> and makes the button
     look identical to it — colour still comes from .hp3-header. */
  color:inherit;
  text-decoration:none;
  background:none;
  border:none;
  font:inherit;
  /* Parent items are <button>, and form controls do NOT inherit text-transform
     or letter-spacing from an ancestor (the `font` shorthand above covers
     neither) — so without this the <button> parents rendered in raw title-case
     while the <a> leaves got .hp3-header__bar's uppercase+tracking, splitting
     the nav into two casings (2026-07-12). Inherit both so every item matches
     whatever .hp3-header__bar sets. */
  text-transform:inherit;
  letter-spacing:inherit;
  /* Symmetric vertical padding. The bottom inset exists to hold the .is-active
     underline (box-shadow below) a few px clear of the label; matching it on top
     keeps the label optically centered against the logo and CTA in this
     align-items-center row — bottom-only padding pushed every label ~2px high.
     .is-active only adds a box-shadow (never changes size), so there is no
     layout shift to guard against. --space-1 resolves to 4px. */
  padding-block:var(--space-1,4px);
  transition:color var(--dur-micro) ease, box-shadow var(--dur-micro) ease;
}
.hp3-header__item.is-active{ color:var(--primary-ink,#E30514); box-shadow:inset 0 -2px 0 var(--primary,#E30514); }
@media (hover: hover){ .hp3-header__item:not(.is-active):hover{ color:var(--primary-ink,#E30514); } }
/* Dropdown caret next to a nav item — was an inline font-size:9px literal, now
   the --text-3xs token (design-system.md §11, phase-6 cleanup). */
.hp3-header__caret{ font-size:var(--text-3xs,9px); transition:transform var(--dur-micro) ease; }
/* Desktop nav caret flips while its mega-menu is open — data-nav-toggle carries
   .is-active only while open (js/script.js), so this is CSS-only feedback that
   works with or without the GSAP motion layer below. */
[data-nav-toggle].is-active .hp3-header__caret{ transform:rotate(180deg); }

.hp3-header__logo{ position:relative; width:clamp(70px,7.2vw,92px); }
.hp3-header__logo img{ position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); height:clamp(60px,6.4vw,82px); width:auto; transition:height var(--dur-medium,0.4s) var(--ease-enter,ease); }
/* Pinned: shrink the crest's REAL height so it stays INSIDE the condensed bar
   (min-height clamp(52,64) above) with a little breathing room top and bottom —
   the logo is absolutely centered, so height alone controls the fit. A transform
   scale wouldn't: it keeps the layout box and lets the crest spill out of the
   shorter bar (the bug this fixes). */
.hp3-header.is-stuck .hp3-header__logo img{ height:clamp(40px,3.4vw,48px); }
.hp3-header.is-stuck .hp3-header__logo--left img{ height:clamp(36px,3vw,44px); }

.hp3-header__logo--left{ flex-shrink:0; }
.hp3-header__logo--left img{ height:clamp(52px,5.6vw,68px); width:auto; }

/* Desktop CTA is now ui.ctaButton('md','solid') (design-system.md §5.1). The
   tablet cta-slot is an empty grid item (its dead inline-styled anchor was
   removed 2026-07-10), so `.hp3-header__cta` has no live instance left. */

@keyframes hp3-dropdown-in{
  from{ opacity:0; transform:translateY(-8px); }
  to{ opacity:1; transform:translateY(0); }
}
/* Shared companion for every hp3-dropdown-in consumer (search bar, megamenu,
   mobile drawer below) — same reduced-motion fix as the media-hover zoom
   above: the two that already have a GSAP .is-motion path (search bar,
   mobile drawer) turn this off THERE too, but only when GSAP is actually
   driving; a reduced-motion visitor with no GSAP still hit this animation
   before. One rule here instead of three scattered ones so a future fourth
   consumer of this keyframe can't add itself without also seeing this.
   Adding a new selector here: keep it in sync with anything new that sets
   `animation:hp3-dropdown-in ...`. */
@media (prefers-reduced-motion: reduce){
  .hp3-search-bar__bar, .hp3-header__megamenu, .hp3-header__mobile-menu{ animation:none; }
}
.hp3-header__megamenu{
  display:none;
  /* Full-bleed *background only* — same pattern as .hero-container/
     .hp3-footer-container (full-width color + max-width:1920px inner content,
     centered). The content (title text + link columns) stays capped at 1920px
     like every other section — do NOT move any content box to the edge (the
     2026-07-08 reverts were exactly that mistake). The title column's own
     *color* does bleed to the edge, but paint-only via
     .hp3-header__megamenu-title::before below (the footer/hero split-color
     pattern), which extends no content — see docs/decisions.md, 2026-07-10.
     left:calc(50% - 50vw) + width:100vw re-centers on the *viewport* using
     only `left` (not `transform`): the `hp3-dropdown-in` animation below
     already owns `transform` (translateY) for its own entrance motion, and a
     CSS animation exclusively controls every property in its keyframes for
     its whole duration — a static transform:translateX(-50%) here would get
     silently clobbered by it (confirmed: computed transform was the
     animation's matrix, translateX never took effect). `left` has no such
     conflict. */
  position:absolute; top:100%; left:calc(50% - 50vw); right:auto;
  width:100vw;
  background:var(--card,#ffffff);
  /* Soft ambient shadow (--shadow-dropdown) alone reads as almost invisible
     against a busy/light background below (hero image, page content) at
     full viewport width — a crisp 1px edge on top of it keeps the panel's
     true boundary legible regardless of what's underneath. */
  box-shadow:0 1px 0 rgba(0,0,0,0.16), var(--shadow-dropdown,0 24px 60px rgba(0,0,0,0.14));
  border-top:1px solid var(--hair,rgba(0,0,0,0.08));
  z-index:30;
  font-family:var(--font-body,'Inter',sans-serif); font-size:var(--text-caption);
  text-transform:none; letter-spacing:0;
  animation:hp3-dropdown-in var(--dur-short) var(--ease-enter);
}
.hp3-header__megamenu.is-open{ display:block; }
.hp3-header__megamenu-inner{
  display:grid;
  width:100%; max-width:1920px; margin:0 auto;
  grid-template-columns:minmax(280px,max-content) 1fr;
}
.hp3-header__megamenu-title{
  position:relative;
  /* padding-left is the header chrome inset (44px — the same literal .hp3-topbar__inner
     and .hp3-header__bar use, design-system.md §8), NOT --space-8 (32px): the title
     text must start on the same x as the logo and the first topbar item directly above
     it. The other three sides keep --space-8. */
  padding:var(--space-8,34px) var(--space-8,36px) var(--space-8,34px) 44px;
  background:var(--primary,#E30514); color:var(--on-primary,#fff);
}
/* Bleeds this column's OWN color to the true left viewport edge past
   .hp3-header__megamenu-inner's max-width:1920px cap — otherwise that gap shows
   the megamenu's own --card background. Paint only: a pseudo-element (not a
   negative margin) so the title column's box/width and every text glyph in it
   stay exactly on the 1920px reading grid — only the color extends. This is the
   footer/hero split-color bleed pattern (.hp3-footer__cta::before). NOTE: the
   two 2026-07-08 reverts were about moving the *content* to the edge, which was
   wrong; extending only this background color is the correct reading of the
   request and is now the intended state (see docs/decisions.md, 2026-07-10). */
.hp3-header__megamenu-title::before{
  content:"";
  position:absolute; top:0; bottom:0; right:100%;
  /* Exact cap gap, clamped to 0 below 1920px so it adds no horizontal overflow
     at narrow widths — same guard as .hp3-footer__cta::before. */
  width:max(0px, calc((100vw - 1920px) / 2));
  background:var(--primary,#E30514);
}
/* item.label only (the "Naviga" kicker + "Scopri di più" CTA link were
   dropped 2026-07-20 — the title column is now a plain, non-interactive
   label). d-flex/justify-content-center on the .hp3-header__megamenu-title
   markup centers it in the column now that there's nothing to space
   between. */
.hp3-header__megamenu-title strong{
  display:block; font-family:var(--font-display,'Anton',sans-serif); font-weight:var(--font-weight-regular,400);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-xl)); line-height:calc(var(--leading-scale,1) * var(--leading-tight,0.92)); text-transform:uppercase;
}
.hp3-header__megamenu-col{
  padding:var(--space-8,34px) var(--space-8,32px);
  border-right:1px solid var(--hair,rgba(0,0,0,0.08));
  gap:var(--space-4,16px);
  font-family:var(--font-display,'Anton',sans-serif); font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-subtitle));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-tight,0.03em)); text-transform:uppercase; color:var(--muted-4,#5a575a);
}
.hp3-header__megamenu-col:last-child{ border-right:none; }
.hp3-header__megamenu-col a{ color:inherit; text-decoration:none; }
@media (hover: hover){ .hp3-header__megamenu-col a:hover{ color:var(--primary-ink,#E30514); } }

/* Colour/cursor/flex from .hp3-ui-iconbtn; only its grid placement is local. */
.hp3-header__hamburger{ justify-self:start; }

/* Hamburger -> X: the three bars fold into a cross while the drawer is open, so
   the button carries its own open/closed state (same idea as the mobile-accordion
   chevron flip below). Pure CSS off .is-menu-open — the class js/script.js already
   toggles (state lives in script.js; this is only the visual), so it needs no
   GSAP and still lands the end state instantly under reduced motion. Bars are
   ordered top/middle/bottom in the markup; transform-box:fill-box rotates each
   about its own centre rather than the SVG origin, and the top/bottom bars slide
   6px to the middle (viewBox 24 -> 1 unit = 1px) before rotating so both diagonals
   cross at the centre. Bars are <rect> (not <line>) because a horizontal line's
   fill-box is degenerate and won't resolve the transform — see header.tplp. */
.hp3-header__burger rect{
  transform-box:fill-box; transform-origin:center;
  transition:transform var(--dur-short) var(--ease-move), opacity var(--dur-micro) ease;
}
.hp3-header.is-menu-open .hp3-header__burger rect:nth-child(1){ transform:translateY(6px) rotate(45deg); }
.hp3-header.is-menu-open .hp3-header__burger rect:nth-child(2){ opacity:0; transform:scaleX(0); }
.hp3-header.is-menu-open .hp3-header__burger rect:nth-child(3){ transform:translateY(-6px) rotate(-45deg); }

.hp3-header__mobile-menu{
  display:none;
  position:absolute; top:100%; left:0; right:0;
  background:var(--card,#ffffff);
  box-shadow:var(--shadow-nav,0 16px 40px rgba(0,0,0,0.16));
  border-top:1px solid rgba(0,0,0,0.06);
  padding:20px 30px;
  z-index:31;
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-subtitle)); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em)); text-transform:uppercase;
  animation:hp3-dropdown-in var(--dur-short) var(--ease-enter);
  /* .hp3-header is position:sticky, so once it sticks to the viewport top this
     drawer (position:absolute, anchored to the header) is pinned there too —
     the page scrolling underneath no longer moves it. With a long nav (many
     top-level items, or an expanded accordion) its own height can exceed the
     remaining viewport space below the header, and that overflow used to be
     unreachable: not via page scroll (the drawer doesn't move once stuck) and
     not internally (no overflow-y here). 100% in max-height resolves against
     this element's containing block, .hp3-header (the nearest positioned
     ancestor) — i.e. the header's own height — so this caps the drawer to
     "whatever's left of the viewport below the header" without a hardcoded
     header-height value, and overflow-y makes THAT remainder scrollable.
     overscroll-behavior-y:contain stops scroll CHAINING at that same boundary:
     without it, a touch/wheel scroll that runs past the drawer's own scrollTop
     limit (e.g. flicking down once already at the bottom) bleeds into the page
     scrolling underneath — same "no scrim, page stays usable" drawer this is,
     but only via a deliberate gesture outside it, not as a side effect of
     scrolling the menu itself. */
  max-height:calc(100dvh - 100%);
  overflow-y:auto;
  overscroll-behavior-y:contain;
  -webkit-overflow-scrolling:touch;
}
.hp3-header__mobile-menu.is-open{ display:flex; flex-direction:column; }
/* :not(.hp3-ui-btn) so the tickets button (now ui.ctaButton) escapes the drawer
   link styling and shows the component skin cleanly — design-system.md §5.1. */
.hp3-header__mobile-menu a:not(.hp3-ui-btn){
  padding:var(--space-3,12px) 0; border-bottom:1px solid rgba(0,0,0,0.06); text-decoration:none; color:var(--ink,#0a0a14);
}
.hp3-header__mobile-menu [data-mobile-sub]{
  display:none;
}
.hp3-header__mobile-menu [data-mobile-sub].is-open{ display:flex; flex-direction:column; }

.hp3-header__mobile-menu [data-mobile-sub] a{
  padding:var(--space-2,9px) 0 var(--space-2,9px) var(--space-5,20px); font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-subtitle));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-tight,0.03em)); color:var(--muted-4,#5a575a); border-bottom:none;
}
.hp3-header__mobile-menu [data-mobile-chevron]{ font-size:var(--text-3xs); transition:transform var(--dur-micro) ease; }
.hp3-header__mobile-menu [data-mobile-toggle].is-open [data-mobile-chevron]{ transform:rotate(180deg); }

/* Tickets button in the drawer: skin/size from ui.ctaButton('md','solid');
   only its drawer spacing stays local (design-system.md §5.1). It stretches
   full-width as a flex item of the column drawer. */
.hp3-header__mobile-cta{ margin-top:var(--space-3,12px); }

/* ---- Motion layer gate — header. js/animations.js adds .hp3-header.is-motion
   only when GSAP is present AND prefers-reduced-motion is off. In motion mode
   GSAP owns the enter *and exit* of these panels, so the CSS keyframe /
   display-toggle above is neutralised here. Without .is-motion nothing below
   applies and the plain CSS fallback (hp3-dropdown-in, display toggles) stays —
   same split as the hero's .is-enhanced. ---- */
.hp3-header.is-motion .hp3-header__megamenu{
  /* Stays in the layout box (position:absolute → no reflow) but hidden via
     visibility+opacity, so GSAP can animate it *out* on close, not only in — a
     display:none toggle can't be tweened. autoAlpha drives it from this rest
     state. */
  display:block; visibility:hidden; opacity:0; animation:none;
}
.hp3-header.is-motion .hp3-header__mobile-menu{ animation:none; }
.hp3-header.is-motion [data-mobile-sub]{
  /* Always laid out, clipped to height:0 when closed, so GSAP collapses it both
     ways (the plain rule above only toggles display, which can't animate). */
  display:flex; flex-direction:column; height:0; overflow:hidden;
}
/* Hold the header's stacking lift through a mega-menu's *exit* fade too, not
   only while open — js/script.js drops .is-nav-open the instant it closes, so
   without this the brand stripe below (z-index:6) could paint over the panel
   mid-fade. animations.js adds .is-closing for the ~0.2s exit only. */
.hp3-header.is-motion.is-closing{ z-index:10; }
/* Search bar shares the same gate idea: in motion mode js/animations.js drives the
   open entrance (bar drop-in + scrim fade), so the bar's hp3-dropdown-in keyframe is
   handed off to it (otherwise the running keyframe would fight GSAP's inline
   transform/opacity for the property). Close stays the instant display toggle
   js/script.js owns — same as the mobile drawer. Without .is-motion the CSS fallback
   (hp3-dropdown-in above) stays. */
.hp3-search-bar.is-motion .hp3-search-bar__bar{ animation:none; }

.hp3-header__tablet-mobile-bar{ display:none; }
/* .hp3-header__cta-slot stays a reserved, EMPTY grid track (column 1) so the
   logo's column 2 is the true visual center — see the grid-column comment
   below. The hidden CTA anchor it used to hold (and the display:none rule
   for it) were removed 2026-07-10. */

@media (max-width:1023px){
  .hp3-header__bar{ display:none; }
  .hp3-header__tablet-mobile-bar{
    display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
    min-height:80px;
    padding:11px 26px;
    transition:min-height var(--dur-medium,0.4s) var(--ease-enter,ease), padding var(--dur-medium,0.4s) var(--ease-enter,ease);
  }
  /* Pinned condense on mobile too (same as the desktop bar above). */
  .hp3-header.is-stuck .hp3-header__tablet-mobile-bar{ min-height:54px; padding-top:6px; padding-bottom:6px; }
  .hp3-header__logo{ width:clamp(64px,9vw,84px); }
  .hp3-header__logo img{ height:clamp(58px,8vw,76px); }
  /* Crest fits inside the 54px condensed mobile bar with breathing room. */
  .hp3-header.is-stuck .hp3-header__logo img{ height:clamp(34px,4.4vw,40px); }
  /* Tablet and mobile now share the same header design: hamburger on the
     right, tickets link only inside the mobile menu (no top-bar CTA, no
     next-match strip). All three children get an EXPLICIT grid-column: once
     any one sibling in a 3-item auto-placed grid gets an explicit column
     (hamburger, below), the placement algorithm positions explicit items
     first and only then drops the still-auto siblings into the earliest
     free column in DOM order — which put the logo in column 1 (flush left)
     instead of column 2 (center) when this was mobile-only. Pinning all
     three avoids relying on placement order at all. See docs/decisions.md,
     2026-07-08.
     grid-row:1 is also required, not optional: markup order here is
     hamburger, logo, cta-slot — columns 3, 2, 1, i.e. descending. Grid's
     sparse auto-placement cursor moves forward as each item is placed and
     wraps to a new row whenever the next item's column is behind the
     cursor, which this descending order triggers for every item after the
     first — splitting the row into three separate 1-item rows (confirmed:
     computed grid-template-rows was "36px 12px 12px", not one row). That
     silently broke vertical centering: the hamburger centered inside its
     own top row while the logo centered inside a lower, ~12px-tall row,
     leaving the logo poorly contained and visibly offset from the
     hamburger. Pinning grid-row:1 on all three forces one shared row so
     align-items:center on the container centers them together. */
  .hp3-header__cta-slot{ grid-column:1; grid-row:1; }
  .hp3-header__logo{ grid-column:2; grid-row:1; }
  .hp3-header__hamburger{ grid-column:3; grid-row:1; justify-self:end; }
}

@media (max-width:639px){
  .hp3-header__logo{ width:clamp(56px,16vw,68px); }
  .hp3-header__logo img{ height:clamp(52px,15vw,64px); }
  .hp3-header__mobile-menu{ padding:16px 16px; }
  .hp3-header__tablet-mobile-bar{ padding:10px var(--section-pad-x-mobile); }
}

/* ---- Not found (tpl/not_found.tpl) ---- */

.hp3-notfound{
  background:var(--page,#f5f3ee);
  padding:var(--space-16,64px) var(--section-pad-x-mobile,16px);
  text-align:center;
  gap:var(--space-5,20px);
}
.hp3-notfound__title{ margin:0; }
.hp3-notfound__text{
  margin:0;
  color:var(--muted,#6b665e);
  font-size:var(--text-body,16px);
}

/* ---- Footer, from section-footer.dc.html ---- */

/* margin-top:auto anchors the footer to the bottom of the flex-column page
   shell (see .hp3-page-shell in head.tplp), so short pages don't expose the
   page background below the footer. */
.hp3-footer-container{ width:100%; margin-top:auto; background:var(--surface-deep,#0a090d); }
/* The alternate footer has no primary CTA column, so its one dark register can
   safely fill the capped container's outer gutters as well as the menu panel. */
.hp3-footer-container--extended{ background:var(--surface,#111014); }
/* Sitewide sponsor banner (base.tpl, above the footer): it takes the margin-top:auto
   instead of the footer, so on a short page the banner + footer drop to the bottom
   together and the empty space falls ABOVE the banner — never a gap between banner
   and footer. The footer then follows it directly (its own auto neutralised). Pages
   without the sitewide banner are unaffected: the footer keeps the auto. */
.hp3-footer-sponsors{ width:100%; margin-top:auto; }
/* Not a direct-sibling selector (.hp3-footer-sponsors + .hp3-footer-container)
   since 2026-07-15: <footer> (layouts/base.tpl) sits between them in the DOM
   now, for the ARIA landmark — display:contents (head.tplp) drops its box so
   .hp3-footer-container is still the next FLEX item after the banner, but CSS
   sibling combinators match the DOM tree, not the box tree, so the selector has
   to reach through the <footer> element explicitly. */
.hp3-footer-sponsors + footer .hp3-footer-container{ margin-top:0; }
/* The brand stripe (include/section-brand-stripe.tplp, footer.tplp) — a
   specific club's own decorative ribbon, see that file's header comment —
   sits BETWEEN the sponsor banner and .hp3-footer-container, and was missing
   from this same hand-off chain (2026-07-21 fix — found via screenshot: the
   stripe visibly detached from the footer on the 404 page, which has no
   sponsor banner to absorb the gap). `footer .hp3-stripe-container` scopes
   this to the FOOTER's copy only — the same partial is also nested inside
   .hp3-header for the sticky-header copy, an unrelated flex context that must
   not get an auto top-margin. Three-way hand-off, exactly one winner:
   sponsors present -> sponsors pushes, stripe+footer-container both zeroed;
   sponsors absent, stripe present -> stripe pushes, footer-container zeroed;
   neither present -> footer-container's own base auto-margin (above) wins. */
footer .hp3-stripe-container{ margin-top:auto; }
.hp3-stripe-container + .hp3-footer-container{ margin-top:0; }
.hp3-footer-sponsors + footer .hp3-stripe-container{ margin-top:0; }
/* min-width:0 overrides the flex-item default (min-width:auto), which would
   otherwise refuse to shrink below this element's widest descendant's
   intrinsic content width — e.g. an unscrolled horizontal carousel — and
   push the whole page into horizontal overflow instead of letting that
   descendant's own overflow-x:auto contain it. Every *-inner wrapper in this
   file sits inside a `-container{d-flex justify-content-center}` the same
   way, so all of them need this. See docs/decisions.md, 2026-07-08. */
/* Shared section-inner wrapper (design-system.md §8, phase 5): the 1920px reading
   cap + the anti-overflow min-width:0 (see the 2026-07-08 flex/overflow entry),
   one rule instead of six identical ones. .hp3-section-inner is the reusable name
   for any new section/route; the per-section names stay so no markup changed. */
.hp3-section-inner,
.hp3-footer-inner, .hp3-news-inner, .hp3-calendario-inner,
.hp3-video-inner, .hp3-roster-inner, .hp3-sponsor-inner{ width:100%; max-width:1920px; min-width:0; }
.hp3-footer{ width:100%; font-family:var(--font-body,'Inter',system-ui,sans-serif); }

.hp3-footer__cta{
  position:relative;
  background:var(--primary,#E30514);
  padding:56px 64px;
  gap:clamp(24px,4vw,48px);
  min-height:clamp(220px,26vw,320px);
}
/* Bleeds this column's OWN color to the true left viewport edge past
   .hp3-footer-inner's max-width:1920px cap — otherwise that gap would show
   .hp3-footer-container's own (different) background instead. A pseudo
   -element, not a negative margin, so the column's own box/width (and where
   it meets .hp3-footer__nav) never moves — only paint extends. Same
   principle as .hp3-header__megamenu-title's edge bleed, see
   docs/decisions.md, 2026-07-08. */
.hp3-footer__cta::before{
  content:"";
  position:absolute; top:0; bottom:0; right:100%;
  /* Exact gap width, clamped to 0 below the 1920px cap — NOT a flat 100vw:
     that unconditionally added ~100vw of extra box past the true edge even
     when there's no cap-driven gap to fill (viewport <1920px), which doesn't
     show visually (same color as the background either way) but does add
     genuine horizontal overflow to the whole page at narrow widths. Confirmed
     via document.documentElement.scrollWidth. See docs/decisions.md, 2026-07-08. */
  width:max(0px, calc((100vw - 1920px) / 2));
  background:var(--primary,#E30514);
}
.hp3-footer__cta-brand{ gap:var(--space-3,12px); }
.hp3-footer__cta-brand img{ height:56px; }
/* Typography from .hp3-eyebrow--wide (design-system.md §4); only its custom
   translucent-white color stays local. */
.hp3-footer__cta-brand span{ color:rgba(255,255,255,0.7); }
.hp3-footer__cta-season{
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-xl));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-tight,0.03em));
  text-transform:uppercase;
  color:var(--paper,#f2f0eb);
  line-height:calc(var(--leading-scale,1) * var(--leading-tight,0.92));
  margin-bottom:28px;
}
/* Buttons now ui.ctaButton('sm','invert') / ('sm','on-dark') — skin from the
   component (design-system.md §5.1); only the row gap stays local. */
.hp3-footer__cta-actions{ gap:var(--space-1,3px); }

/* Footer newsletter — opt-in swap for the season/tickets CTA in the left column
   (options.bool.ShowFooterNewsletter). Sits on the same --primary field: the
   input is a light card, the submit a dark pill so both stay legible on red. */
.hp3-footer__cta-season--newsletter{ margin-bottom:var(--space-3,14px); }
/* Anchors the absolutely-positioned hint below (so it never adds height to the
   real page flow — see .hp3-footer__newsletter-hint). */
.hp3-footer__newsletter{ position:relative; }
.hp3-footer__newsletter-lead{
  font-family:var(--font-body,'Inter',sans-serif);
  font-size:var(--text-body);
  color:rgba(255,255,255,0.82);
  line-height:calc(var(--leading-scale,1) * var(--leading-relaxed,1.55));
  max-width:38ch;
  margin:0 0 24px;
}
.hp3-footer__newsletter-form{ max-width:440px; }
.hp3-footer__newsletter-input{
  flex:1 1 auto; min-width:0;
  font-family:var(--font-body,'Inter',sans-serif);
  /* 16px, not --text-body's 15px: iOS Safari auto-zooms the viewport on focus
     of any input whose font-size is below 16px. Keep at the 16px threshold. */
  font-size:var(--text-body-lg,1rem);
  color:var(--ink,#0a0a14);
  background:var(--card,#ffffff);
  border:1px solid var(--card,#ffffff);
  border-right:none;
  border-radius:var(--radius,2px) 0 0 var(--radius,2px);
  padding:var(--space-3,12px) var(--space-4,18px);
  outline:none;
}
.hp3-footer__newsletter-input::placeholder{ color:var(--muted-2,#9a958c); }
/* Empty-submit feedback — the SAME shake + red border + hint the search bar
   uses (js/script.js's shared initInlineRequired), not the browser's native
   bubble (the form carries novalidate). Red border marks the field; the shake
   runs on the whole form so the joined field+button wobble as one unit (reusing
   the search bar's hp3-shake keyframe). */
.hp3-footer__newsletter-input.is-invalid{ border-color:var(--danger,#ff5a5a); }
.hp3-footer__newsletter-form:has(.hp3-footer__newsletter-input.is-invalid){ animation:hp3-shake .3s var(--ease-move); }
.hp3-footer__newsletter-hint{
  /* absolute + top:100%: the hint is an out-of-flow LABEL under the form — showing
     it must never add height and shove the footer down, so it can't be an in-flow
     block. Anchored to .hp3-footer__newsletter (position:relative above). */
  position:absolute; top:100%; left:0;
  display:none;
  margin-top:var(--space-2,8px);
  /* --on-primary, not --danger: this block sits on the brand --primary (which is
     itself red-ish for some clubs), so a red hint would clash
     — on-primary is the token guaranteed to read on any primary. The error is
     still unmistakable from the input's red border + the form shake. */
  color:var(--on-primary,#ffffff);
  font-size:var(--text-caption,0.75rem);
}
.hp3-footer__newsletter-hint.is-visible{ display:block; }
.hp3-footer__newsletter-btn{
  flex:0 0 auto;
  font-family:var(--font-display,'Anton',sans-serif);
  text-transform:uppercase;
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wider,0.18em));
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-caption));
  line-height:1; white-space:nowrap; cursor:pointer;
  color:var(--paper,#f2f0eb);
  background:var(--surface,#111014);
  border:1px solid var(--surface,#111014);
  border-radius:0 var(--radius,2px) var(--radius,2px) 0;
  padding:var(--space-3,12px) var(--space-6,26px);
  transition:opacity var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-footer__newsletter-btn:hover{ opacity:0.86; } }

.hp3-footer__nav{ position:relative; background:var(--surface,#111014); padding:56px 64px; gap:clamp(24px,4vw,32px); }
/* Mirrors .hp3-footer__cta::before, bleeding right instead of left — same
   clamped width, not a flat 100vw (see that rule's comment: unclamped, a
   rightward bleed genuinely extends the page's scrollWidth at narrow
   viewports, unlike a leftward one in an LTR page). */
.hp3-footer__nav::before{
  content:"";
  position:absolute; top:0; bottom:0; left:100%;
  width:max(0px, calc((100vw - 1920px) / 2));
  background:var(--surface,#111014);
}
.hp3-footer__nav-cols{ --bs-gutter-x: clamp(20px,3.4vw,32px); --bs-gutter-y: clamp(20px,3.4vw,32px); }
.hp3-footer__extended-logo{
  flex:0 0 clamp(140px,14vw,220px);
  width:auto;
  display:flex;
  align-items:flex-start;
  justify-content:flex-start;
}
.hp3-footer__extended-logo img{
  display:block;
  width:auto;
  height:auto;
  max-width:min(100%,160px);
  max-height:96px;
  object-fit:contain;
}
/* With the CTA column removed, distribute every footer menu root across the
   full desktop width instead of leaving the old 7-column footprint half empty. */
@media (min-width:640px){
	.hp3-footer--extended .hp3-footer__menu-col{ flex:1 1 0; width:auto; max-width:none; }
}
/* Typography from .hp3-eyebrow--primary--widest (design-system.md §4); layout local. */
.hp3-footer__col-title{ margin-bottom:16px; }
.hp3-footer__col-links-inner{ display:flex; flex-direction:column; gap:var(--space-2,10px); }
.hp3-footer__accordion-toggle{ display:none; }
/* --muted-4, not --surface-4, but the same bug: --muted-4 is tuned for LIGHT
   backgrounds (6.43:1 on --page) and measured 2.66:1 here against the footer's
   dark --surface — a token reused across a light/dark boundary without
   re-checking contrast (2026-07-15 a11y fix). --muted-2 is the dark-surface-safe
   member of the same family (6.36:1 here), so the visual hierarchy (secondary,
   not primary, text) is preserved. */
.hp3-footer__col-links a{ font-family:var(--font-body,'Inter',sans-serif); font-size:var(--text-body); color:var(--muted-2,#9a958c); text-decoration:none; transition:color var(--dur-micro) ease; }
@media (hover: hover){ .hp3-footer__col-links a:hover{ color:var(--paper,#f2f0eb); } }
.hp3-footer__nav-bottom{
  gap:var(--space-3,12px);
  margin-top:32px;
  padding-top:24px;
  border-top:1px solid var(--line,rgba(255,255,255,0.10));
}
/* Footer social is ui.socialLinks(options,'bare') — icon only, no border,
   skin from .hp3-social--bare (design-system.md §6.1), same as the topbar;
   .hp3-footer__social carries its own larger icon size (the topbar's bare
   icons stay compact — limited space next to search/hamburger) plus the
   mobile-centering override in the @media block below. */
.hp3-footer__social{ gap:18px; }
.hp3-footer__social .hp3-social__link svg{ width:20px; height:20px; }
/* Typography from .hp3-eyebrow (design-system.md §4); dark tone + nowrap local. */
/* --muted-2, not --surface-4 (2026-07-15 a11y fix): --surface-4 is a border/chrome
   token, not a text one — at 12px on --surface-deep it measured 1.72:1 against
   WCAG's 4.5:1 floor, functionally invisible (confirmed via computed styles in the
   live harness). --muted-2 is the token this theme already uses for secondary text
   on dark chrome elsewhere (.hp3-topbar__match-lead) and clears 6.67:1 here. */
.hp3-footer__copyright{ color:var(--muted-2,#9a958c); white-space:nowrap; }

.hp3-footer__legal-bar{
  background:var(--surface-deep,#0a090d);
  padding:14px 64px;
  gap:clamp(16px,2.4vw,24px);
}
/* --muted-2, not --surface-4 — see .hp3-footer__copyright above (same fix, same reason). */
.hp3-footer__legal-bar a{ font-family:var(--font-body,'Inter',sans-serif); font-size:var(--text-caption); color:var(--muted-2,#9a958c); text-decoration:none; }

@media (max-width:1023px){
  .hp3-footer__cta{ min-height:0; }
  .hp3-footer__nav-cols{ --bs-gutter-x:clamp(16px,3vw,24px); --bs-gutter-y:clamp(16px,3vw,24px); }
}
@media (max-width:639px){
  .hp3-footer__cta,
  .hp3-footer__nav{ padding:24px var(--section-pad-x-mobile); }
  .hp3-footer__cta-season{ margin-bottom:16px; }
  .hp3-footer__col-title{ margin-bottom:12px; }
  /* The expanded footer becomes a single-column accordion on
     phones. The classic footer keeps its existing two-column menu grid. */
	.hp3-footer--extended .hp3-footer__nav-cols{
    --bs-gutter-x:0;
    --bs-gutter-y:0;
    display:block;
  }
	.hp3-footer--extended .hp3-footer__extended-logo{
    width:100%;
    max-width:none;
    padding:0 0 var(--space-7,1.75rem);
    justify-content:center;
  }
	.hp3-footer--extended .hp3-footer__extended-logo img{
    max-width:min(64vw,180px);
    max-height:104px;
  }
	.hp3-footer--extended .hp3-footer__menu-col{
    width:100%; max-width:none; padding:0;
    border-bottom:1px solid var(--line,rgba(255,255,255,0.10));
  }
	.hp3-footer--extended .hp3-footer__col-title--desktop{ display:none; }
	.hp3-footer--extended .hp3-footer__accordion-toggle{
    width:100%; display:flex; align-items:center; justify-content:space-between;
    padding:var(--space-5,1.25rem) 0;
    color:var(--muted-2,#9a958c); background:transparent;
    border:0; border-radius:0; cursor:pointer; text-align:left;
  }
  .hp3-footer__accordion-chevron{
    flex-shrink:0;
    display:flex;
    transform:rotate(90deg);
    transition:transform var(--dur-micro,.15s) ease;
  }
  .hp3-footer__accordion-chevron svg{ width:14px; height:14px; }
  .hp3-footer__accordion-toggle.is-open .hp3-footer__accordion-chevron{ transform:rotate(-90deg); }
  .hp3-footer__accordion-toggle:focus{ outline:none; }
  .hp3-footer__accordion-toggle:focus-visible{ outline:2px solid var(--primary,#E30514); outline-offset:2px; }
	.hp3-footer--extended .hp3-footer__nav-cols.is-accordion-ready .hp3-footer__col-links{
    display:grid;
    grid-template-rows:0fr;
    overflow:hidden;
    padding-bottom:0;
    transition:grid-template-rows var(--dur-medium,.4s) var(--ease-move,cubic-bezier(.4,0,.2,1)),
               padding-bottom var(--dur-medium,.4s) var(--ease-move,cubic-bezier(.4,0,.2,1));
  }
	.hp3-footer--extended .hp3-footer__col-links-inner{ min-height:0; overflow:hidden; }
	.hp3-footer--extended .hp3-footer__nav-cols.is-accordion-ready .hp3-footer__col-links.is-open{
    grid-template-rows:1fr;
    padding-bottom:var(--space-5,1.25rem);
  }
  @media (prefers-reduced-motion: reduce){
		.hp3-footer--extended .hp3-footer__nav-cols.is-accordion-ready .hp3-footer__col-links{ transition:none; }
  }
  /* Stack social over copyright on phones: side-by-side the copyright (bigger
     now — eyebrow size × --type-scale-brand) plus 7 social boxes overflow the
     viewport (a long club name in the copyright line was clipped off the
     right on a real test club). flex-direction is safe to set (no Bootstrap
     utility owns it, unlike
     align-items — see CLAUDE.md constraint #3). Copyright drops nowrap here so
     a long club name wraps instead of pushing the page wider. */
  .hp3-footer__nav-bottom{ margin-top:16px; padding-top:14px; flex-direction:column; gap:var(--space-4,16px); }
  /* The last accordion item already closes the menu block; repeating the
     desktop nav-bottom rule here reads as an unexplained fourth row. */
	.hp3-footer--extended .hp3-footer__nav-bottom{ border-top:0; }
  .hp3-footer__social{ justify-content:center; }
  .hp3-footer__copyright{ white-space:normal; text-align:center; }
  .hp3-footer__legal-bar{ padding:12px var(--section-pad-x-mobile); }
}

/* ---- Hero News, from section-hero-news.dc.html (variant 3b) ---- */

/* --hero-bleed: how far each full-bleed layer must reach past the 1920px reading
   grid to touch the true viewport edge. Defined here on the common ancestor so
   the photo (.hero-stage, left) and the rail rows (.hero-row, right) share one
   source. 0 below 1920px, so mobile/tablet are untouched. */
.hero-outer{
  --hero-bleed:max(0px, (100vw - 1920px) / 2);
  /* In the standard homepage header, the hero starts after both chrome rows.
     Budget them out of the full-width stage so its CTA and navigation remain
     inside the opening viewport instead of landing below the fold. */
  --hero-page-chrome-height:calc(clamp(32px,3.4vw,40px) + clamp(80px,6vw,100px));
  width:100%; background:var(--card,#ffffff); font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
/* Overlay mode paints the chrome over the hero and removes it from document
   flow, so there is no occupied height to subtract from the stage. */
.hp3-page-shell--home-overlay .hero-outer{ --hero-page-chrome-height:0px; }
/* Pure white (var(--card)), not var(--page) (an off-white/cream): direct
   correction 2026-07-08 — the first pass used var(--page) (right idea, wrong
   token: light instead of dark, but not the exact white asked for). See
   docs/decisions.md, 2026-07-08. */
/* The bottom border lives here, on the full-width container, not on the capped
   .hero-inner below — otherwise it would stop at the 1920px reading width while
   the photo/rail bleed past it, leaving the line floating short of both edges. */
.hero-container{ background:var(--card,#ffffff); border-bottom:1px solid var(--ink,#0a0a14); }
/* Divider between the photo column and the rail. Bootstrap's border-lg-end
   utility owns the border itself (with !important), so only the color is set
   here — !important is required to win against that same utility. */
.hero-stage-col{ border-color:var(--surface-3,#2a2930) !important; }
/* Capped at the 1920px reading width and centered by .hero-container's
   justify-content-center, like every other section. This is what keeps the hero
   TEXT (title + rail rows) inside the reading container at any width, with plain
   fixed insets and zero collapse — the rail column is now a constant 3/12 of
   1920px (~480px) rather than 3/12 of the whole viewport. The full-bleed visuals
   are reintroduced per layer: .hero-stage extends left to the true edge (photo),
   each .hero-row extends right to it (rail block backgrounds + dividers).
   Supersedes the older "hero-inner unbounded, only the text is inset" model —
   docs/decisions.md, 2026-07-10. */
.hero-inner{ width:100%; max-width:1920px; min-width:0; }

.hero-stage{
  height:clamp(420px, 80vh, 780px); color:var(--paper,#f2f0eb);
  /* touch-action:pan-y (2026-07-15, touch swipe support in js/script.js's
     initSwipe/initHero) — same rationale as .hp3-sponsor__viewport below. */
  touch-action:pan-y;
  /* Photo bleed. The stage now sits in the capped grid, so its left edge is at
     the reading edge; pull just this layer back out to the true left edge on
     ultra-wide. margin-left shifts it, the matching width keeps its RIGHT edge on
     the 9/3 divider. --hero-bleed (from .hero-outer) is 0 below 1920px, so
     narrower screens are untouched. overflow:hidden (markup) still clips the
     slider to these bounds. */
  margin-left:calc(-1 * var(--hero-bleed));
  width:calc(100% + var(--hero-bleed));
}
/* Without the news rail, the same stage reaches both viewport edges while its
	caption remains pinned to the shared 1920px reading grid. */
.hero-outer--full .hero-stage{
	height:clamp(600px,calc(100vh - var(--hero-page-chrome-height)),960px);
	height:clamp(600px,calc(100svh - var(--hero-page-chrome-height)),960px);
	width:calc(100% + var(--hero-bleed) + var(--hero-bleed));
}
.hero-track{ transition:transform var(--dur-carousel) var(--ease-move); }
/* Auto-advance already fully stops under reduced motion (js/script.js
   initHero's prefersReduced check) — this covers what that check doesn't:
   clicking prev/next/a rail row still slid the track (a reduced-motion
   visitor asked for no motion on interaction too, not just no motion on a
   timer — WCAG 2.3.3). GSAP's own hero enhancement already skips itself the
   same way (.hero-stage.is-enhanced never gets added), so this is the CSS
   fallback's turn. */
@media (prefers-reduced-motion: reduce){
  .hero-track{ transition:none; }
}
/* Slides stay in normal flex flow (no display:none/is-active toggle) — the
   track's translateX is what brings the current one into view (see
   js/script.js's render()). A display:none/is-active pair here used to fight
   that: a display:none slide drops out of the flex row entirely, so only one
   slide ever occupied the track's x:0, and translateX(-N*100%) then pushed
   that single slide off-screen for any index > 0 — verified 2026-07-08 by
   clicking through the hero once real photos exposed it (previously masked
   by every non-first slide's image also being lazy-loaded, so nobody had
   looked closely at an advanced slide's position). */
.hero-slide{ min-width:100%; }
.hero-slide img{ object-fit:cover; }
.hero-scrim{
  /* Bottom-up scrim for caption legibility. Extended upward with a light veil
     (~18-28%) through the upper band so the top eyebrow (category · date) reads
     over bright photos too — the old gradient faded to ~0 by 74% and left the
     eyebrow on the bare image. Kept deliberately light above the title so the
     photo stays clear; the eyebrow's own text-shadow (.hero-cat) does the rest,
     mirroring the roster card's scrim + number-shadow pair. */
  background:linear-gradient(to top,
    color-mix(in srgb, var(--surface-deep,#0a090d) 92%, transparent) 2%,
    color-mix(in srgb, var(--surface-deep,#0a090d) 45%, transparent) 40%,
    color-mix(in srgb, var(--surface-deep,#0a090d) 28%, transparent) 68%,
    color-mix(in srgb, var(--surface-deep,#0a090d) 18%, transparent) 90%,
    transparent 100%);
}

/* Pinned to the reading edge. .hero-content's offset parent (.hero-slide) fills
   the left-extended .hero-stage, so a plain start-0 would ride the photo out to
   the true left edge; left:var(--hero-bleed) brings the title back to the reading
   edge, level with the topbar/news text. --hero-bleed is 0 below 1920px, so
   mobile/tablet are untouched. start-0 was removed from the markup so this
   (non-important) left wins over Bootstrap's .start-0. GSAP animates only
   transform/opacity on this element (js/animations.js), never left, so the two
   don't fight. */
.hero-content{
  max-width:clamp(640px, 48vw, 920px);
  left:max(0px, (100vw - 1920px) / 2);
  padding:0 44px 56px;
}
/* .hero-cat → .hp3-eyebrow--body--on-dark; .hero-title → .hp3-title-display
   (design-system.md §4). Mobile line-clamp on .hero-title kept below. */
/* Soft legibility shadow behind the small eyebrow so category · date stays
   readable where the scrim is lightest (upper band), without darkening the photo
   — same functional-shadow idea as .hp3-roster__ghost-num. */
.hero-cat{ text-shadow:0 1px 10px rgba(0,0,0,0.55), 0 0 2px rgba(0,0,0,0.4); }
/* "Leggi l'articolo" is now ui.viewAllLink(..., 'on-dark') — skin from
   .hp3-ui-link--on-dark (design-system.md §5.2). The headline shares the same
   destination by explicit product choice; its link remains visually identical
   to the display role and adds only interaction/focus feedback. */
.hero-title__link{
	display:block;
	width:fit-content;
	max-width:100%;
	color:inherit;
	text-decoration:none;
	transition:opacity var(--dur-micro) ease;
}
.hero-title__link:visited{ color:inherit; }
@media (hover: hover){ .hero-title__link:hover{ opacity:0.86; } }
.hero-title__link:focus{ outline:none; }
.hero-title__link:focus-visible{
	outline:2px solid var(--paper,#f2f0eb);
	outline-offset:var(--space-1,4px);
}
/* Full-width headlines have more horizontal room but no rail to preview the
   remaining stories. Four lines preserve the editorial title while reserving
   a stable bottom band for the CTA and carousel controls on first paint. */
.hero-outer--full .hero-title{
  display:-webkit-box;
  -webkit-box-orient:vertical;
  -webkit-line-clamp:4;
  overflow:hidden;
}

.hero-nav-overlay{ padding:0 44px 56px; gap:var(--space-4,1rem); }
/* Own spacing (was Bootstrap gap-2/mb-3/mt-3 utilities — same values, moved
   onto tokens per CLAUDE.md constraint #3 / ds-lint, 2026-07-10). */
.hero-meta{ gap:var(--space-2,0.5rem); margin-bottom:var(--space-4,1rem); }
.hero-footer{ gap:var(--space-2,0.5rem); margin-top:var(--space-4,1rem); }

.hero-rail-col{ display:flex; flex-direction:column; background:var(--card,#ffffff); color:var(--ink,#0a0a14); }
/* Each row's BLOCK (background + bottom-border divider) bleeds to the true right
   edge, its TEXT stays on the reading edge — the mirror of the photo bleeding left
   past where the title stops. The row is a stretched flex child of the capped
   ~480px rail column, so a negative right margin widens it by --hero-bleed out to
   the true edge (bg + border follow); the same amount added to the right padding
   holds the text exactly where it was. Because the block grows by precisely what
   the padding grows, the text box stays a constant width — no collapse at any
   viewport (verified to 3440px). --hero-bleed is 0 below 1920px, so the tablet/
   mobile rules below are untouched. This replaces the old .hero-rail-col::after
   flat strip, which reached the edge but couldn't carry the active-row colour or
   the dividers. See docs/decisions.md, 2026-07-10. */
.hero-row{
  margin-right:calc(-1 * var(--hero-bleed));
  padding:18px calc(24px + var(--hero-bleed)) 18px 24px;
  cursor:pointer; background:var(--card,#ffffff); transition:background var(--dur-micro) ease;
}
.hero-row:not(:last-child){ border-bottom:1px solid var(--placeholder,#e0e0e0); }
@media (hover: hover){ .hero-row:not(.is-active):hover{ background:var(--page,#f5f3ee); } }
.hero-row.is-active{ background:var(--page,#f5f3ee); box-shadow:inset 4px 0 0 var(--primary,#E30514); }
/* .hero-kick typography from .hp3-eyebrow--body (default tone); layout local. */
.hero-kick{ margin-bottom:var(--space-1,4px); }
.hero-row.is-active .hero-kick{ color:var(--primary,#E30514); }
/* .hero-row h4 → .hp3-subtitle (design-system.md §4). */

/* ---- GSAP-enhanced hero (js/animations.js adds .is-enhanced on the stage and
   the rail; under reduced motion or with JS off both are absent and script.js's
   translateX slider is the accessible fallback). Slides stack as overlaid layers
   so the background image and the caption can transition on independent planes,
   and the rail's active indicator slides between rows instead of the box-shadow
   jumping. ---- */
.hero-stage.is-enhanced .hero-track{ transform:none !important; }
.hero-stage.is-enhanced .hero-slide{ position:absolute !important; inset:0; }
.hero-rail-col{ position:relative; }
.hero-rail-marker{ position:absolute; left:0; top:0; width:4px; height:0; background:var(--primary,#E30514); border-radius:0 2px 2px 0; pointer-events:none; z-index:2; }
@media (min-width:992px){ .hero-rail-col.is-enhanced .hero-row.is-active{ box-shadow:none; } }

@media (max-width: 991.98px){
  /* Rail removed on tablet too, not just mobile (by request 2026-07-20): below the
     desktop 3-col layout the horizontal preview strip is dropped entirely and the
     slider is driven by the prev/next arrows alone — same behaviour as mobile. The
     old flex-direction:row strip and its .hero-row dividers / .hero-kick went with
     it (they only styled descendants of this now-hidden column). */
  .hero-rail-col{ display:none; }
}

@media (max-width: 639px){
  .hero-container{ border-width:2px; }
  .hero-stage{ height:clamp(300px, 56vh, 460px); }
	.hero-outer--full .hero-stage{
		height:clamp(420px,calc(100vh - var(--hero-page-chrome-height)),620px);
		height:clamp(420px,calc(100svh - var(--hero-page-chrome-height)),620px);
	}
  .hero-content{ padding:0 132px 20px var(--section-pad-x-mobile); max-width:none; }
  .hero-title{ display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; overflow:hidden; }
  .hero-nav-overlay{ padding:0 var(--section-pad-x-mobile) 20px; }
  /* Caption legibility is harder on phones — the photo is viewed close and the
     eyebrow (muted, 13px) sits mid-stage, so the shared scrim/shadow weren't
     enough. A touch more scrim through the upper band, plus a tighter/darker
     text-shadow — the halo (not extra darkening) does most of the work, so the
     photo stays clear. */
  .hero-scrim{
    background:linear-gradient(to top,
      color-mix(in srgb, var(--surface-deep,#0a090d) 92%, transparent) 2%,
      color-mix(in srgb, var(--surface-deep,#0a090d) 52%, transparent) 40%,
      color-mix(in srgb, var(--surface-deep,#0a090d) 38%, transparent) 66%,
      color-mix(in srgb, var(--surface-deep,#0a090d) 24%, transparent) 90%,
      transparent 100%);
  }
  .hero-cat{ text-shadow:0 1px 3px rgba(0,0,0,0.9), 0 0 9px rgba(0,0,0,0.55); }
  /* .hero-rail-col is already display:none from the ≤991.98px block above (rail
     dropped on tablet + mobile alike), so no mobile-only rule is needed here. */
}

/* ---- News, from section-news.dc.html ---- */

.hp3-news-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-news{
  width:100%;
  background:var(--page,#f5f3ee);
  padding:var(--space-12) var(--space-10) var(--space-12);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
.hp3-news__track{
  --news-gap:clamp(14px,2vw,24px);
  display:grid;
  grid-auto-flow:column;
  /* A fixed (non-minmax) track size: minmax(240px, calc(...)) still lets grid's
     "maximize tracks" step redistribute leftover row space evenly across all 6 cards,
     which grows them right back to fill-the-row (identical to the old 1fr bug). max()
     resolves to a single definite length, so cards can't grow past it, and 6 cards'
     worth of width reliably overflows into a scrollbar, capping visible cards at 4.5/row
     (peeking half-card hints scroll). */
  grid-auto-columns:max(240px,calc((100% - 4 * var(--news-gap)) / 4.5));
  grid-template-rows:1fr;
  gap:var(--news-gap);
  overflow-x:auto;
  overflow-y:hidden;
  scroll-snap-type:x mandatory;
  scroll-behavior:smooth;
  -ms-overflow-style:none;
  scrollbar-width:none;
  padding-bottom:2px;
}
.hp3-news__track::-webkit-scrollbar{ display:none; }
/* Covers .hp3-roster__strip too (same scroll-behavior:smooth, further down
   this file) — one rule, not two, same reasoning as hp3-dropdown-in's shared
   companion above. Direct trackpad/wheel scrolling is native and already
   instant regardless (this property only governs scrollIntoView/scrollBy
   calls that don't pass their own `behavior`); js/script.js's prev/next
   arrows and the month-filter's scrollIntoView both now check
   prefers-reduced-motion themselves and pass 'auto' instead, so this is
   belt-and-suspenders for any OTHER trigger (e.g. a #hash landing inside the
   strip) rather than the primary fix. */
@media (prefers-reduced-motion: reduce){
  .hp3-news__track, .hp3-roster__strip{ scroll-behavior:auto; }
}

.hp3-news__footer-link{ margin-top:var(--space-8,32px); }

.hp3-news__card{ scroll-snap-align:start; text-decoration:none; color:var(--ink,#0a0a14); }
.hp3-news__thumb{ aspect-ratio:4/3; overflow:hidden; border-radius:var(--radius-card,2px); background:var(--placeholder,#e0e0e0); }
.hp3-news__thumb img{ width:100%; height:100%; object-fit:cover; }
.hp3-news__body{ padding:0; margin-top:var(--space-4,16px); }
/* Typography now from .hp3-eyebrow--body--primary / .hp3-subtitle + .hp3-card-title
   (design-system.md §4); only layout stays local. */
.hp3-news__tag{ margin-bottom:var(--space-1,4px); }

/* Compact news-card variant (macros-cards.tplp) — used for related articles,
   where a horizontal media/text rhythm keeps the curated suggestions visually
   distinct from full editorial feeds without introducing a second component
   language. */
.hp3-news-compact__card{
	display:grid; grid-template-columns:minmax(96px,32%) minmax(0,1fr);
	align-items:center; gap:var(--space-4,16px); min-width:0;
	color:var(--ink,#0a0a14); text-decoration:none;
}
.hp3-news-compact__thumb{
	aspect-ratio:4/3; overflow:hidden;
	border-radius:var(--radius-card,2px); background:var(--placeholder,#e0e0e0);
}
.hp3-news-compact__thumb img{ width:100%; height:100%; object-fit:cover; }
.hp3-news-compact__body{ min-width:0; }
.hp3-news-compact__tag{
	margin-bottom:var(--space-2,8px); overflow:hidden;
	text-overflow:ellipsis; white-space:nowrap;
}
.hp3-news-compact__title{
	display:-webkit-box; overflow:hidden; -webkit-box-orient:vertical;
	-webkit-line-clamp:2;
}

@media (max-width:639px){
  .hp3-news{ padding:var(--space-6) var(--section-pad-x-mobile) var(--space-12); }
}

/* ---- Homepage store — optional Slyvi Shop module -------------------------
   Two editor-selected presentations share one card component: an editorial
   feature grid, or the same native swipe/arrow strip used by News. */
.hp3-store-container{ width:100%; background:var(--card,#ffffff); }
.hp3-store{
  --store-gap:clamp(14px,2vw,24px);
  width:100%; padding:var(--space-12) var(--space-10);
  background:var(--card,#ffffff); font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
.hp3-store__grid{
  display:grid; grid-template-columns:minmax(0,3fr) minmax(320px,2fr);
  align-items:stretch; gap:var(--store-gap);
}
.hp3-store__secondary{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  grid-template-rows:repeat(2,minmax(0,1fr)); gap:var(--store-gap);
}
.hp3-store__cover-layout{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  align-items:stretch; gap:var(--store-gap);
}
.hp3-store__cover{
  position:relative; display:block; min-height:0; overflow:hidden;
  border-radius:var(--radius-card,2px); background:var(--placeholder,#e0e0e0);
}
.hp3-store__cover img{ position:absolute; inset:0; display:block; width:100%; height:100%; object-fit:cover; }
.hp3-store__cover-products{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  align-items:stretch; gap:var(--space-2,8px);
}
.hp3-store__cover-products .hp3-store-card--compact{ background:transparent; }
.hp3-store__cover-products .hp3-store-card__media{
  aspect-ratio:5/7; padding:0; background:transparent;
}
.hp3-store__cover-products .hp3-store-card__media img{ object-fit:contain; }
.hp3-store--count-1 .hp3-store__grid{ grid-template-columns:1fr; }
.hp3-store--count-2 .hp3-store__secondary{ grid-template-columns:1fr; grid-template-rows:1fr; }
.hp3-store__track{
  display:grid; grid-auto-flow:column;
  grid-auto-columns:max(240px,calc((100% - 4 * var(--store-gap)) / 4.5));
  grid-template-rows:auto; align-items:start; gap:var(--store-gap);
  overflow-x:auto; overflow-y:hidden;
  scroll-snap-type:x mandatory; scroll-behavior:smooth;
  -ms-overflow-style:none; scrollbar-width:none; padding-bottom:2px;
}
.hp3-store__track::-webkit-scrollbar{ display:none; }
.hp3-store__footer-link{ margin-top:var(--space-8,32px); }
.hp3-store-card{
  position:relative; min-width:0; overflow:hidden;
  border-radius:var(--radius-card,2px); color:var(--ink,#0a0a14); text-decoration:none;
}
.hp3-store-card__media{ overflow:hidden; background:var(--placeholder,#e0e0e0); }
.hp3-store-card__media img{ display:block; width:100%; height:100%; object-fit:cover; }
.hp3-store-card--featured{ min-height:0; background:var(--surface,#111014); }
.hp3-store--count-1 .hp3-store-card--featured{ aspect-ratio:16/9; }
.hp3-store-card--featured .hp3-store-card__media{
  position:absolute; inset:0; padding:var(--space-6,24px); background:var(--page,#f5f3ee);
}
.hp3-store-card--featured .hp3-store-card__media img{ object-fit:contain; }
.hp3-store-card--featured .hp3-store-card__scrim{
  position:absolute; inset:0;
  background:linear-gradient(to top,color-mix(in srgb,var(--surface-deep,#0a090d) 94%,transparent) 0%,color-mix(in srgb,var(--surface-deep,#0a090d) 48%,transparent) 48%,transparent 76%);
}
.hp3-store-card--featured .hp3-store-card__body{
  position:absolute; inset:auto 0 0; z-index:1;
  padding:clamp(var(--space-5,20px),3vw,var(--space-10,40px)); color:var(--paper,#f2f0eb);
}
.hp3-store-card--featured .hp3-store-card__title{ max-width:18ch; color:var(--paper,#f2f0eb); }
.hp3-store-card__description{
  max-width:54ch; margin:var(--space-3,12px) 0 0;
  color:var(--sand,#cfc9c0); font-size:var(--text-body); line-height:var(--leading-normal,1.5);
}
.hp3-store-card__footer{
  display:flex; align-items:end; justify-content:space-between;
  gap:var(--space-4,16px); margin-top:var(--space-5,20px);
}
.hp3-store-card__price{
  white-space:nowrap;
}
.hp3-store-card__link{
  flex-shrink:0; font-family:var(--font-body,'Inter',system-ui,sans-serif);
  font-size:var(--text-subtitle); line-height:1;
}
.hp3-store-card--featured .hp3-store-card__link{ color:var(--primary-ink,#E30514); }
.hp3-store-card--compact{
  display:grid; align-self:start;
  grid-template-rows:auto auto; background:var(--page-2,#eeebe3);
}
.hp3-store-card--compact .hp3-store-card__media{ width:100%; min-height:0; aspect-ratio:1; }
.hp3-store__track .hp3-store-card__media{ aspect-ratio:5/7; }
.hp3-store__secondary .hp3-store-card__media{
  aspect-ratio:1; padding:var(--space-4,16px); background:var(--page,#f5f3ee);
}
.hp3-store__secondary .hp3-store-card__media img{ object-fit:contain; }
.hp3-store__track .hp3-store-card{ scroll-snap-align:start; }
.hp3-store-card--compact .hp3-store-card__body{
  display:grid; min-width:0; grid-template-rows:auto auto;
  gap:var(--space-3,12px); padding:var(--space-4,16px);
}
.hp3-store-card--compact .hp3-store-card__title{
  display:block; min-width:0; overflow:hidden; color:var(--ink,#0a0a14);
  font-size:var(--text-subtitle); text-overflow:ellipsis; white-space:nowrap;
}
.hp3-store-card--compact .hp3-store-card__footer{
  align-items:center; margin-top:0; color:var(--muted,#6b675f);
}
.hp3-store-card--compact .hp3-store-card__link{ color:var(--primary-ink,#E30514); }

@media (min-width:1024px){
  .hp3-store__cover-products .hp3-store-card__media{ aspect-ratio:2/3; }
}

@media (max-width:1023px){
  .hp3-store__grid{ grid-template-columns:1fr; }
  .hp3-store__cover-layout{ grid-template-columns:1fr; }
  .hp3-store__cover{ aspect-ratio:16/9; }
  .hp3-store-card--featured, .hp3-store--count-1 .hp3-store-card--featured{ aspect-ratio:16/9; }
  .hp3-store__secondary{ grid-template-rows:none; grid-auto-rows:auto; }
}

@media (max-width:639px){
  .hp3-store{
    padding:var(--space-8) var(--section-pad-x-mobile) var(--space-12);
  }
  .hp3-store-card--featured, .hp3-store--count-1 .hp3-store-card--featured{ aspect-ratio:4/5; }
  .hp3-store__cover{ aspect-ratio:4/5; }
  .hp3-store__cover-products{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .hp3-store__cover-products .hp3-store-card:last-child:nth-child(odd){ grid-column:1 / -1; }
  .hp3-store__secondary{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-auto-rows:auto; }
  .hp3-store__track{ grid-auto-columns:min(78vw,280px); }
  .hp3-store-card--compact .hp3-store-card__body{ padding:var(--space-3,12px); }
  .hp3-store-card__description{ display:none; }
  .hp3-store-card--featured .hp3-store-card__footer{ align-items:flex-start; flex-direction:column; gap:var(--space-2,8px); }
}

@media (prefers-reduced-motion: reduce){
  .hp3-store__track{ scroll-behavior:auto; }
}

/* ---- Search results (/search — tpl/search.tpl + include/section-search-results.tplp).
   The theme's first internal route. Reuses newsCard for news hits (a wrapping
   grid instead of the homepage's horizontal track — .hp3-news__card has no
   intrinsic width, so it fills each grid cell) and the row view-all link for
   page hits; only the page-level layout below is search-specific. ---- */
.hp3-search-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-search{ padding:var(--space-16) var(--space-10) var(--space-20); }
/* ---- Page scaffolding: .hp3-page-head (h1+meta, no eyebrow kicker — dropped
   2026-07-13, the nav already says what section you're in) and
   .hp3-empty-state (message+CTA), shared by ui.pageHeader()/ui.emptyState()
   (macros-ui.tplp) — every internal route reuses these, not just search. ---- */
.hp3-page-head{ margin-bottom:var(--space-10); }
.hp3-page-head__meta{ color:var(--muted,#6b675f); white-space:nowrap; }
.hp3-search__list{ display:flex; flex-direction:column; gap:var(--space-6); max-width:860px; }
.hp3-search__result{ gap:var(--space-6); text-decoration:none; color:var(--ink,#0a0a14); align-items:center; }
.hp3-search__result-thumb{ flex:0 0 34%; max-width:220px; aspect-ratio:4/3; overflow:hidden; border-radius:var(--radius-card,2px); background:var(--placeholder,#e0e0e0); }
.hp3-search__result-thumb img{ width:100%; height:100%; object-fit:cover; }
.hp3-search__result-body{ min-width:0; gap:var(--space-2); }
.hp3-search__result-sub{ margin:0; font-family:var(--font-body,'Inter',system-ui,sans-serif); font-size:var(--text-body,0.9375rem); line-height:var(--leading-relaxed,1.55); color:var(--muted-4,#5a575a); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.hp3-empty-state{ display:flex; flex-direction:column; align-items:flex-start; gap:var(--space-6); padding:var(--space-8) 0 var(--space-16); }
.hp3-empty-state__lead{ margin:0; font-family:var(--font-body,'Inter',system-ui,sans-serif); font-size:var(--text-body-lg,1rem); color:var(--muted-4,#5a575a); }
.hp3-search__pages{ margin-top:var(--space-16); max-width:860px; }
/* Shared group label for the "News"/"Pagine" result sections. */
.hp3-search__group-title{ margin-bottom:var(--space-4); color:var(--muted,#6b675f); }
/* Searched-term highlight (js/script.js initSearchHighlight wraps matches in
   <mark>). A restrained accent wash, not a loud fill — same color-mix idiom as
   the standings is-me row in tokens.css. */
.hp3-mark{ background:color-mix(in srgb, var(--accent-ink,#c9a227) 22%, transparent); color:inherit; border-radius:2px; padding:0 0.12em; }
@media (max-width:639px){
  .hp3-search{ padding:var(--space-10) var(--section-pad-x-mobile) var(--space-16); }
  /* Stack photo over text on mobile: a side-by-side thumb + wrapping text column
     is too cramped at phone widths (a short thumb floating beside a 2-line
     title). Full-width image, text below — same reading order as the homepage
     news card. width:100% + min-width:0 pin the body to the column width so a
     long title wraps instead of overflowing to the right. */
  /* width:100% on the result makes its width DEFINITE (a stretched flex cross-size
     doesn't resolve child percentages), so the thumb/body width:100% below resolve
     against it instead of the body shrink-wrapping to the nowrap title. */
  .hp3-search__result{ flex-direction:column; align-items:stretch; gap:var(--space-4); min-width:0; width:100%; }
  .hp3-search__result-thumb{ flex:none; width:100%; max-width:none; aspect-ratio:16/9; }
  .hp3-search__result-body{ width:100%; min-width:0; }
}

/* ---- News index (/news — tpl/news.tpl + include/page-news.tplp) + pagination.
   The homepage newsCard laid out as a wrapping responsive grid instead of the
   horizontal track (the card has no intrinsic width — design-system.md §7.1);
   .hp3-news-index__grid is shared with the single article's related-news block. ---- */
.hp3-news-index-container{ width:100%; background:var(--page,#f5f3ee); }
/* Full-width, not the 1180px reading cap used for prose (.hp3-article) — this
   wraps a card grid, not text, so it only needs the generic 1920px cap already
   applied by the shared .hp3-section-inner it sits inside. */
.hp3-news-index{
  width:100%; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
/* Bootstrap grid (.row + col-* on each card), like the footer/fixtures/hero
   sections. Gutters are bridged to the theme's spacing via --bs-gutter-* (the
   same --bs-* token-bridge idiom as tokens.css); the responsive ladder is
   Bootstrap's own (col-12 col-sm-6 col-md-4 col-lg-3 on the markup). Shared by
   the /news list and the single article's related-news grid. */
.hp3-news-index__grid{
  --bs-gutter-x:clamp(16px,2vw,28px);
  --bs-gutter-y:clamp(28px,3vw,44px);
}
.hp3-news-index__grid > div{ min-width:0; }

/* Pagination — arrows reuse the shared .hp3-ui-navbtn square; page numbers are
   token-styled cells and the current page is filled with the brand colour. */
.hp3-pagination{
  margin-top:var(--space-16,4rem);
  flex-direction:column; gap:var(--space-3,12px); flex-wrap:wrap; justify-content:center;
}
.hp3-pagination__pages{ gap:var(--space-2,8px); flex-wrap:wrap; }
.hp3-pagination__arrow.is-disabled{ opacity:0.35; cursor:default; pointer-events:none; }
.hp3-pagination__num{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:clamp(30px,3.6vw,42px); height:clamp(30px,3.6vw,42px);
  padding:0 var(--space-2,8px);
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-eyebrow));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--ink,#0a0a14); text-decoration:none;
  border:1px solid var(--hair,rgba(0,0,0,0.08)); border-radius:var(--radius,2px);
  transition:color var(--dur-micro) ease, border-color var(--dur-micro) ease, background var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-pagination__num:not(.is-current):hover{ color:var(--primary-ink,#E30514); border-color:var(--primary,#E30514); } }
.hp3-pagination__num.is-current{
  background:var(--primary,#E30514); color:var(--on-primary,#fff);
  border-color:var(--primary,#E30514); cursor:default;
}
/* News category filter tabs (include/news-filter-tabs.tplp) — a horizontal row
   of tab links shared by /news and the category pages; the active tab (current
   category, or "Tutte" on /news) gets the brand underline. Scrolls horizontally
   on narrow screens instead of wrapping. */
.hp3-news-filter{ margin-bottom:var(--space-8,2rem); }
/* Baseline hairline lives here as an INSET shadow, not a border on the nav: it is
   painted inside the scroll container's padding box, on the exact row the tabs'
   border-bottom ends, so the active tab's 2px underline sits flush ON it. A real
   border can't be overlapped from inside — overflow-x:auto forces overflow-y
   clipping that eats any margin-bottom:-1px pull, leaving the active underline 1px
   above the line instead of superimposed on it. */
.hp3-news-filter__track{
  display:flex; gap:var(--space-6,1.5rem);
  overflow-x:auto; overflow-y:hidden;
  scrollbar-width:none; -ms-overflow-style:none;
  box-shadow:inset 0 -1px 0 var(--hair,rgba(0,0,0,0.08));
}
.hp3-news-filter__track::-webkit-scrollbar{ display:none; }
.hp3-news-filter__tab{
  flex:0 0 auto; padding:0 0 var(--space-3,12px);
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-eyebrow));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--muted,#6b675f); text-decoration:none; white-space:nowrap;
  border-bottom:2px solid transparent; margin-bottom:-1px;
  transition:color var(--dur-micro) ease, border-color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-news-filter__tab:hover{ color:var(--ink,#0a0a14); } }
.hp3-news-filter__tab.is-active{ color:var(--primary-ink,#E30514); border-bottom-color:var(--primary,#E30514); }

/* ---- Single article (/news/{id} — tpl/single-news.tpl + include/page-single-news.tplp).
   Reading view: an editorial hero, a token-driven prose role (.hp3-article__body),
   a related-news grid and a latest-news row (no author byline, no tags). No homepage precedent — the prose role is
   introduced here (design-system.md §4 had no body-copy role). ---- */
.hp3-article-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-article, .hp3-article-related{ width:100%; max-width:1180px; margin:0 auto; }
.hp3-article{ padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-12,3rem); }
.hp3-article-related{ padding:0 var(--space-10,2.5rem) var(--space-20,5rem); }
.hp3-article-related__grid{
	display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
	gap:clamp(var(--space-6,1.5rem),3vw,var(--space-10,2.5rem));
}
.hp3-article-latest{
	width:100%; padding:var(--space-12,3rem) var(--space-10,2.5rem) var(--space-20,5rem);
	border-top:1px solid var(--hair,rgba(0,0,0,0.08));
}
/* Keep this editorial strip to one row at every breakpoint. It reuses the
   homepage card and native horizontal-track behavior, but intentionally has no
   arrow controls: direct swipe/trackpad scrolling is the compact interaction
   used for this secondary end-of-article content. */
.hp3-article-latest__track{ grid-auto-columns:max(220px,calc((100% - 4 * var(--news-gap)) / 5)); }

.hp3-article__kicker{ margin-bottom:var(--space-4,16px); }
.hp3-article__kicker .hp3-article__cat{ color:inherit; text-decoration:none; }
@media (hover: hover){ .hp3-article__kicker .hp3-article__cat:hover{ text-decoration:underline; } }
/* Article headline is editorial long-form and routinely wraps to 2+ lines, where
   .hp3-title-1's punchy display leading (--leading-tight, for single-line hero/
   section headers) reads cramped — open it one step to --leading-snug just here. */
.hp3-article__title{ margin:0; line-height:calc(var(--leading-scale,1) * var(--leading-snug,1.1)); }
.hp3-article__lead{
  margin:var(--space-5,1.25rem) 0 0;
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  font-size:var(--text-subtitle,1.0625rem); line-height:var(--leading-normal,1.3);
  color:var(--muted-4,#5a575a);
}
.hp3-article__media{ margin:var(--space-10,2.5rem) 0 0; }
.hp3-article__media img{ width:100%; height:auto; display:block; border-radius:var(--radius-card,2px); background:var(--placeholder,#e0e0e0); }
.hp3-article__caption{ margin-top:var(--space-3,12px); color:var(--muted,#6b675f); text-transform:none; letter-spacing:normal; }

/* Prose role — the CMS body HTML. Body font, relaxed measure; child rhythm via
   the owl selector so raw <p>/<h2>/<ul> from the DAO get consistent spacing
   without per-element margins. */
.hp3-article__body{
  margin-top:var(--space-10,2.5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  font-size:var(--text-body-lg,1rem); line-height:var(--leading-relaxed,1.55);
  color:var(--ink,#0a0a14);
}
.hp3-article__body > * + *{ margin-top:var(--space-5,1.25rem); }
.hp3-article__body h2, .hp3-article__body h3{
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  line-height:calc(var(--leading-scale,1) * var(--leading-snug,1.1));
  margin-top:var(--space-10,2.5rem);
}
.hp3-article__body h2{ font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-md)); }
.hp3-article__body h3{ font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-sm)); }
.hp3-article__body a{ color:var(--primary-ink,#E30514); text-decoration:underline; }
.hp3-article__body ul, .hp3-article__body ol{ padding-left:var(--space-6,1.5rem); }
.hp3-article__body li + li{ margin-top:var(--space-2,8px); }
.hp3-article__body blockquote{
  margin-left:0; margin-right:0; padding-left:var(--space-5,1.25rem);
  border-left:3px solid var(--primary,#E30514);
  color:var(--muted-4,#5a575a); font-style:italic;
}
/* Embedded media in the CMS body is authored OUTSIDE the theme and can't be
   trusted to fit the column: CKEditor wraps resized images in
   <figure class="image image_resized" style="width:NNNpx"> with a hard pixel
   width, and pasted-in iframes/tables/video can be arbitrarily wide. Cap every
   embedded block to the reading column so external content can never break the
   layout out — a 1268px resized figure overflowed the mobile viewport in
   production (decisions.md 2026-07-16). max-width clamps the figure's inline px
   width with no !important fight (it targets a different property), so a smaller
   in-editor resize is still honoured on wide viewports. */
.hp3-article__body{ overflow-wrap:break-word; }
.hp3-article__body img,
.hp3-article__body video{ max-width:100%; height:auto; }
.hp3-article__body figure,
.hp3-article__body iframe{ max-width:100%; }
/* CKEditor wraps tables in figure.table. That wrapper owns overflow, so the
   table fills the reading column when it fits and keeps its natural wider
   layout behind a local horizontal scrollbar when its columns need more room.
   The direct-table branch preserves the same behavior for imported HTML that
   reaches the article body without CKEditor's figure wrapper. */
.hp3-article__body figure.table{
	display:block; width:100%; min-width:0; max-width:100%;
	overflow-x:auto; overscroll-behavior-inline:contain;
}
.hp3-article__body figure.table table{
	width:100%; min-width:100%; max-width:none;
}
.hp3-article__body > table{
	display:block; width:100%; min-width:100%; max-width:100%;
	overflow-x:auto; overscroll-behavior-inline:contain;
}
.hp3-article__body figure{ margin-left:0; margin-right:0; }
.hp3-article__body img{ border-radius:var(--radius-card,2px); }

.hp3-article__foot{ margin-top:var(--space-12,3rem); }
.hp3-article-related .hp3-section-head{ margin-bottom:var(--space-8,2rem); }

@media (max-width:991.98px){
	.hp3-article-related__grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}

@media (max-width:639px){
  .hp3-news-index{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  .hp3-article{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-10,2.5rem); }
  .hp3-article-related{ padding:0 var(--section-pad-x-mobile) var(--space-16,4rem); }
	.hp3-article-related__grid{ grid-template-columns:1fr; }
	.hp3-article-latest{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
}

/* ---- Calendario (fixtures + standings window), from section-fixtures.dc.html ---- */

.hp3-calendario-container{ width:100%; background:var(--card,#ffffff); }
/* width/max/min from the shared .hp3-section-inner group above; only padding +
   Bootstrap gutters are calendario-specific. */
.hp3-calendario-inner{
  padding:var(--space-12) var(--space-10);
  --bs-gutter-x: clamp(var(--space-6),3.4vw,var(--space-8));
  --bs-gutter-y: clamp(var(--space-6),3.4vw,var(--space-8));
}

.hp3-calendario__next{
  position:relative;
  background:var(--surface,#111014); color:var(--paper,#f2f0eb);
  border-top:4px solid var(--primary,#E30514);
  padding:var(--space-8) var(--space-6);
  gap:clamp(20px,2.6vw,28px);
}
/* Dark-surface tones for the shared matchup shape — the away team and the
   kickoff-day label read as secondary against the strong .hp3-calendario__next
   base (--paper). See .hp3-events__row below for the light-surface equivalent. */
.hp3-calendario__next .hp3-calendario__team--away span{ color:var(--sand,#cfc9c0); }
.hp3-calendario__next .hp3-calendario__kickoff-day{ color:var(--muted-2,#9a958c); }
/* Eyebrow ("PROSSIMA"/"ULTIMO MATCH") pinned to --sand, not --accent-ink: this
   card is ALWAYS the dark --surface chrome regardless of the club's own
   ColorMode (see head.tplp), but --accent-ink self-derives to the club's raw
   AccentColor when no code-level AccentInkColor override is set (see
   docs/decisions.md 2026-07-12) — a club whose accent IS a near-black/dark
   brand colour (a real, common case, not an edge case) gets invisible text
   here. --sand is already a fixed dark-context tone (untouched by ColorMode),
   matching the away-team/kickoff-day treatment right above. */
.hp3-calendario__next .hp3-calendario__eyebrow{ color:var(--sand,#cfc9c0); }
/* No left-bleed here, direct correction (2026-07-08): unlike the footer/hero/
   megamenu bleeds, the gap past .hp3-calendario-inner's max-width:1920px cap
   should stay the page's own white (var(--card), same as
   .hp3-calendario-container's own background) — this dark card is a
   foreground accent element, not a section-defining background the way the
   footer/hero halves are. */
/* Typography from .hp3-eyebrow--accent (design-system.md §4); only layout local. */
.hp3-calendario__eyebrow{ text-align:center; }
/* Horizontal, name-and-crest-side-by-side layout (2026-07-13 redesign) —
   shared by the homepage "next match" card (dark, .hp3-calendario__next) and
   the /events list row (light, .hp3-events__row). Only shape/size live here;
   color is set per-container just below, so the same markup reads correctly
   on either background (design-system §16-adjacent: a component shouldn't
   assume its own background). */
.hp3-calendario__matchup{ display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:clamp(14px,1.8vw,22px); }
.hp3-calendario__team{ gap:clamp(10px,1.2vw,14px); justify-content:flex-end; }
.hp3-calendario__team--away{ justify-content:flex-start; }
/* Team crest (image or initials) now from .hp3-crest--lg (design-system.md §7.2). */
.hp3-calendario__team span{
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-sm)); line-height:1; text-transform:uppercase;
}
.hp3-calendario__kickoff{ text-align:center; }
/* Typography from .hp3-eyebrow (design-system.md §4); only its lighter tone
   (set per-container below) and spacing stay local. */
.hp3-calendario__kickoff-day{
  /* Breathing room before the big display line below (score or kickoff
     time) — the two sat nearly touching ('WED 30 DEC' on '4 - 3'). */
  margin-bottom:clamp(4px,0.6vw,8px);
}
/* Score/time: deliberately plain text, no badge/background — size alone
   carries the emphasis (explicit request, 2026-07-13). color:inherit so it
   takes each container's own base tone (paper on dark, ink on light). */
.hp3-calendario__kickoff-time{
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-lg)); line-height:1; color:inherit;
}
/* "Acquista biglietti" is ui.ctaButton('md','solid') — the SAME compact pill as
   the header/footer "Biglietti" (design-system.md §5.1). Layout-only: align-self
   un-stretches it from the flex column (default align-items:stretch would force it
   full-width) and centers it under the centered matchup. No skin/size here — the
   md/solid variant owns those. (Was full-width until 2026-07-20 — see decisions.md.) */
.hp3-calendario__cta{ align-self:center; }

.hp3-calendario__standings-head{ margin-bottom:4px; }
/* Competition crest in the standings header — height-driven, width free (league
   marks vary from square emblems to tall portrait badges); sized for legibility. */
.hp3-calendario__competition-logo{ height:clamp(40px,4.6vw,52px); width:auto; max-width:45%; object-fit:contain; }
/* .hp3-calendario__giornata typography now fully from .hp3-eyebrow (default tone). */
.hp3-calendario__row-head, .hp3-calendario__row{
  display:grid;
  /* Column order: pos | team | PTI | G V N P — points sit right after the team
     name (left side) rather than at the far right, so the standing a fan reads
     first is next to the club it belongs to. */
  grid-template-columns:clamp(28px,3.2vw,42px) 1fr clamp(40px,4.4vw,58px) clamp(30px,3.4vw,46px) clamp(30px,3.4vw,46px) clamp(30px,3.4vw,46px) clamp(30px,3.4vw,46px);
  align-items:center; column-gap:clamp(4px,0.6vw,8px);
}
/* Horizontal-scroll wrapper (added by both consumers around head + rows): on
   desktop the 7-column grid fits and this is inert; on mobile it lets the full
   table scroll sideways instead of dropping the per-match columns — head and
   rows share one scroll box so they stay column-aligned. */
.hp3-standings-scroll{ min-width:0; }
.hp3-calendario__row-head{
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:var(--text-caption); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em)); text-transform:uppercase; color:var(--muted,#6b675f);
  border-bottom:1px solid var(--ink,#0a0a14);
  padding:10px 0;
}
.hp3-calendario__row{ padding:13px 0; border-bottom:1px solid var(--placeholder,#e0e0e0); }
.hp3-calendario__row.is-me{ background:var(--standings-me-bg, color-mix(in srgb, var(--primary,#E30514) 8%, transparent)); }
.hp3-calendario__col-center{ text-align:center; }
.hp3-calendario__pos{ text-align:center; font-family:var(--font-display,'Anton',sans-serif); font-size:var(--text-body); color:var(--muted,#6b675f); }
.hp3-calendario__row.is-me .hp3-calendario__pos{ color:var(--standings-me-ink,var(--primary,#E30514)); }
.hp3-calendario__team-cell{ gap:clamp(8px,1vw,11px); padding-left:6px; min-width:0; }
/* Standings-row crest (image or initials) now from .hp3-crest--sm (design-system.md §7.2). */
.hp3-calendario__name{
  font-family:var(--font-body,'Inter',sans-serif); font-weight:var(--font-weight-semibold,600);
  font-size:var(--text-body); color:var(--ink,#0a0a14);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.hp3-calendario__row.is-me .hp3-calendario__name{ color:var(--standings-me-ink,var(--primary,#E30514)); }
.hp3-calendario__stat{ text-align:center; font-family:var(--font-body,'Inter',sans-serif); font-size:var(--text-body); color:var(--muted,#6b675f); }
.hp3-calendario__stat--v{ color:var(--ink,#0a0a14); }
.hp3-calendario__pti{ text-align:center; font-family:var(--font-display,'Anton',sans-serif); font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-subtitle)); color:var(--ink,#0a0a14); }
.hp3-calendario__row.is-me .hp3-calendario__pti{ color:var(--standings-me-ink,var(--primary,#E30514)); }

/* "Classifica completa" is the DEFAULT ui.viewAllLink (design-system.md §5.2) —
   the same compact link as "Tutte le news"; only layout stays local. align-self
   un-stretches it: the standings column is a flex column with the default
   align-items:stretch, which would force this (inline-flex) link to full width so
   its bottom-border underline spanned the whole column — same trap as the CTA.
   center keeps it content-width (not stretched full-width) and centers it in the
   column, mirroring the centered CTA in the next-match card. margin-top:auto pins it
   to the column bottom (bootstrap equal-height rows); padding-top gives a min gap
   from the last standings row, since the link has none. */
.hp3-calendario__link{ margin-top:auto; align-self:center; padding-top:var(--space-4,18px); }

@media (max-width:1023px){
  .hp3-calendario__matchup{ gap:clamp(8px,2vw,12px); }
  /* Keep all columns visible and scroll the table sideways instead of collapsing
     to pos/team/points — min-width forces the 7-column grid past the viewport so
     the wrapper's overflow-x engages; head + rows carry the same min-width, so
     they scroll as one and stay aligned. */
  .hp3-standings-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; }
  .hp3-calendario__row-head, .hp3-calendario__row{ min-width:480px; }
}

@media (max-width:639px){
  /* --bs-gutter-x:0 removes the Bootstrap column gutter (was ~12px) that inset the
     standings column further than every other section — the two cols stack here
     (col-12), so no horizontal gutter is needed and the standings now starts flush
     at the section edge like the rest. Vertical spacing between the stacked cols is
     kept by the row gap below (gutter-y is untouched). */
  .hp3-calendario-inner{ padding:20px var(--section-pad-x-mobile); gap:clamp(16px,3.4vw,20px); --bs-gutter-x:0; }
  .hp3-calendario__next{ padding:22px var(--space-4); }
  .hp3-calendario__standings-head{ margin-bottom:10px; }
  .hp3-calendario__row-head{ padding:8px 0; }
  .hp3-calendario__row{ padding:9px 0; }
  .hp3-calendario__team-cell{ padding-left:4px; }
  /* Tighter top gap on mobile; no side padding (the old `row` variant's 2px
     inset is gone — a content-width link aligns flush-left with the rows). */
  .hp3-calendario__link{ padding-top:12px; }
}

/* ---- Events (/events list + /events/{id} single event — tpl/events.tpl /
   tpl/event.tpl, include/page-events-list.tplp / page-event.tplp).
   The single-event calendario is one row per game; reuses the shared
   cards.matchup() shape (main.css, "Calendario" section above) — this is its
   light-surface color context, the counterpart to .hp3-calendario__next's
   dark one.
   1fr/auto/1fr (2026-07-13 revision, by request): the SAME centering trick
   .hp3-calendario__matchup already uses one level up — meta and location
   get equal-width flanking columns so the matchup in the middle is always
   visually centered regardless of how much text either side holds (a plain
   flex row with justify-content:space-between couldn't guarantee that).
   No background fill (removed the is-me tint — every row on this page is
   the club's own game, so it was never actually differentiating anything);
   the bottom border alone separates rows. ---- */
.hp3-events-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-events{
  width:100%; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
.hp3-events__rows{ width:100%; }
.hp3-events__matchday-group{ margin-top:var(--space-10,2.5rem); }
.hp3-events__matchday-group:first-child{ margin-top:0; }
.hp3-events__matchday-heading{
  display:flex; align-items:center; justify-content:flex-start;
  padding-bottom:var(--space-3,0.75rem);
  border-bottom:1px solid var(--hair,rgba(0,0,0,0.08));
}
.hp3-events__matchday-title{
  color:var(--primary-ink,#E30514);
}
.hp3-events__row{
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:var(--space-4,1rem);
  padding:var(--space-6,1.5rem) 0; border-bottom:1px solid var(--placeholder,#e0e0e0);
  color:var(--ink,#0a0a14);
}
/* The empty left 1fr track mirrors the venue column so the `auto` centre
   matchup stays page-centred even when the right side contains a long venue. */
.hp3-events__meta{ grid-column:1; color:var(--muted,#6b675f); min-width:0; }
.hp3-events__row .hp3-calendario__team--away span{ color:var(--muted-4,#5a575a); }
/* Centre column (the `auto` grid track): the matchup with the Highlights link
   stacked and centred directly beneath the score (2026-07-20, by request). The
   1fr…auto…1fr row symmetry keeps this column page-centred. */
.hp3-events__center{ grid-column:2; display:flex; flex-direction:column; align-items:center; gap:var(--space-3,12px); }
/* Right column: stadium only (the action moved to the centre). Right-aligned;
   min-width:0 lets it shrink inside the mirrored 1fr track without pushing the
   matchup off the page centre. */
.hp3-events__aside{ grid-column:3; display:flex; flex-direction:column; align-items:flex-end; gap:var(--space-2,0.5rem); min-width:0; }
/* Stadium stays muted metadata under the stronger giornata heading. It wraps
   within its own column so a long venue name never steals width from the
   matchup. */
.hp3-events__location{ color:var(--muted,#6b675f); text-align:right; white-space:normal; min-width:0; }

@media (max-width:639px){
  .hp3-events{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  /* stacked single-column on mobile: matchup / venue — more breathing
     room between them than the tight desktop-side --space-2. */
  .hp3-events__row{ grid-template-columns:1fr; justify-items:center; text-align:center; gap:var(--space-5,1.25rem); }
  .hp3-events__meta,
  .hp3-events__center,
  .hp3-events__aside{ grid-column:auto; }
  .hp3-events__aside{ align-items:center; }
  .hp3-events__location{ text-align:center; }
}

/* Calendario / Classifica tabs (tpl/event.tpl). The SAME component as the /news
   category filter (.hp3-news-filter) — identical rules on purpose, so the two tab
   bars are indistinguishable. The track's overflow-y clip is what lets the active
   tab's 2px border-bottom sit flush ON the 1px inset baseline (see the
   .hp3-news-filter note above); without it the active bar floats a hair above the
   line, which is the exact mismatch we're avoiding. */
.hp3-events__tabs{ margin-bottom:var(--space-8,2rem); }
.hp3-events__tabs-track{
  display:flex; gap:var(--space-6,1.5rem);
  overflow-x:auto; overflow-y:hidden;
  scrollbar-width:none; -ms-overflow-style:none;
  box-shadow:inset 0 -1px 0 var(--hair,rgba(0,0,0,0.08));
}
.hp3-events__tabs-track::-webkit-scrollbar{ display:none; }
.hp3-events__tab{
  flex:0 0 auto; padding:0 0 var(--space-3,12px);
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-eyebrow));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--muted,#6b675f); text-decoration:none; white-space:nowrap;
  border-bottom:2px solid transparent; margin-bottom:-1px;
  transition:color var(--dur-micro) ease, border-color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-events__tab:hover{ color:var(--ink,#0a0a14); } }
.hp3-events__tab.is-active{ color:var(--primary-ink,#E30514); border-bottom-color:var(--primary,#E30514); }

/* Month filter (page-event.tplp calendario view) — a horizontal, scrollable row
   of CHIP buttons, deliberately a different shape from the underline Calendario/
   Classifica tabs above (filled chip vs underline) so the two never read as a tab
   inside a tab. Sharp corners (var(--radius), this theme is not a rounded-pill
   theme) keep it native; the active chip borrows the solid-button fill
   (--primary / --on-primary), the same filled-brand treatment ctaButton('solid')
   uses. Track hides its scrollbar and scrolls sideways when the months outrun the
   width — same overflow idiom as the tabs track. */
.hp3-month-filter{ margin-bottom:var(--space-8,2rem); }
.hp3-month-filter__track{
  display:flex; gap:var(--space-2,0.5rem);
  overflow-x:auto; overflow-y:hidden;
  scrollbar-width:none; -ms-overflow-style:none;
  padding-bottom:2px;
}
.hp3-month-filter__track::-webkit-scrollbar{ display:none; }
.hp3-month-filter__pill{
  flex:0 0 auto;
  padding:var(--space-2,0.5rem) var(--space-4,1rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  font-weight:var(--font-weight-semibold,600); font-size:var(--text-caption);
  color:var(--muted,#6b675f); white-space:nowrap; cursor:pointer;
  background:transparent; border:1px solid var(--placeholder,#e0e0e0);
  border-radius:var(--radius,2px);
  transition:color var(--dur-micro) ease, border-color var(--dur-micro) ease, background-color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-month-filter__pill:hover{ color:var(--ink,#0a0a14); border-color:var(--ink,#0a0a14); } }
.hp3-month-filter__pill.is-active{
  background:var(--primary,#E30514); color:var(--on-primary,#fff); border-color:var(--primary,#E30514);
}

/* Month filter hides the rows outside the selected month (script.js toggles the
   class; the server pre-sets it on the non-active months so there is no
   first-paint flash). The matchday wrapper uses the same state when every row
   inside it is filtered out. Two-class specificity beats the base display rules
   without !important — deliberately NOT a Bootstrap .d-none, whose !important
   would fight the grid (the trap called out in CLAUDE.md). */
.hp3-events__row.is-hidden,
.hp3-events__matchday-group.is-hidden{ display:none; }

/* Full standings table (tpl/event.tpl, ?standings) — FULL WIDTH, by request. The
   row/head grid and the ≤1023px column collapse are the shared
   .hp3-calendario__row(-head) rules (Calendario section above); their tokens
   (--ink/--muted/--placeholder) are already light-surface, so nothing re-declared. */
.hp3-events__standings{ width:100%; }
/* The prominent row's name carries the .hp3-subtitle role (macros-cards.tplp),
   but .hp3-calendario__name sets its own body size at equal specificity and wins
   by source order. This scoped rule only lifts specificity so the existing
   subtitle size applies — no classifica-specific value, same --text-subtitle the
   role uses everywhere else. */
.hp3-events__standings .hp3-calendario__name{ font-size:var(--text-subtitle); }

/* Events list (tpl/events.tpl → getEvents()) — one row per competition, the same
   divider-separated rhythm as the match list, linking to each event's page. */
.hp3-events__list{ width:100%; }
.hp3-events__list-item{
  gap:var(--space-4,1rem); padding:var(--space-5,1.25rem) 0;
  border-bottom:1px solid var(--placeholder,#e0e0e0);
  color:var(--ink,#0a0a14); text-decoration:none;
  transition:color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-events__list-item:hover .hp3-events__list-title{ color:var(--primary-ink,#E30514); } }
.hp3-events__list-title{ transition:color var(--dur-micro) ease; }
.hp3-events__list-sport{ color:var(--muted,#6b675f); white-space:nowrap; }

/* ---- Groups: roster page (/groups, /groups/{id} — page-group.tplp) and the
   single-player profile (/groups/{gid}/{mid} — page-group-member.tplp).
   Light surface like the events page; reuses rosterCard (the homepage strip card)
   in a responsive grid instead of a horizontal scroller. ---- */
.hp3-roster-page-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-roster-page{
  width:100%; max-width:1920px; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}

/* Group switcher — the SAME underline-tab component as .hp3-events__tabs /
   .hp3-news-filter (server-side view switch), reproduced here so the three bars
   stay indistinguishable; only shown when the club has more than one group. */
.hp3-group-switch{ margin-bottom:var(--space-8,2rem); }
.hp3-group-switch__track{
  display:flex; gap:var(--space-6,1.5rem);
  overflow-x:auto; overflow-y:hidden;
  scrollbar-width:none; -ms-overflow-style:none;
  box-shadow:inset 0 -1px 0 var(--hair,rgba(0,0,0,0.08));
}
.hp3-group-switch__track::-webkit-scrollbar{ display:none; }
.hp3-group-switch__tab{
  flex:0 0 auto; padding:0 0 var(--space-3,12px);
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-eyebrow));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--muted,#6b675f); text-decoration:none; white-space:nowrap;
  border-bottom:2px solid transparent; margin-bottom:-1px;
  transition:color var(--dur-micro) ease, border-color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-group-switch__tab:hover{ color:var(--ink,#0a0a14); } }
.hp3-group-switch__tab.is-active{ color:var(--primary-ink,#E30514); border-bottom-color:var(--primary,#E30514); }

/* One role block (Portieri, Difensori, …) — headed by the app's own section-head
   vocabulary (.hp3-section-head/.hp3-section-heading, same as the homepage), not a
   bespoke label, so the roster page reads as part of the same system. */
.hp3-roster-page__section{ margin-bottom:var(--space-12,3rem); }
.hp3-roster-page__section:last-child{ margin-bottom:0; }
.hp3-roster-page__head{ margin-bottom:var(--space-6,1.5rem); }

/* Card grid = Bootstrap row-cols (2→3→4→6, capped at 6 per row), the theme's
   structural grid. The only local touch is bridging Bootstrap's gutter to the
   project's --space scale (it isn't bridged globally — see section-sponsors),
   so the gaps match the rest of the design instead of Bootstrap's own values. */
.hp3-roster-grid{ --bs-gutter-x:clamp(14px,2vw,24px); --bs-gutter-y:clamp(20px,3vw,36px); }
/* The rosterCard carries no width of its own — it fills its Bootstrap .col. */
.hp3-roster-grid > .col{ display:flex; }
.hp3-roster-grid > .col > .hp3-roster__card{ width:100%; }
/* Detailed (roster-page) card: the jersey number scales with the CARD width via
   container units, so it reads big on a wide card and shrinks on a narrow one —
   the homepage strip keeps its own --roster-ghost-num-fs, untouched. container-type
   is scoped here so it never alters the frozen homepage strip. */
.hp3-roster-grid .hp3-roster__card{ container-type:inline-size; }
.hp3-roster-grid .hp3-roster__ghost-num{ font-size:clamp(46px, 34cqi, 104px); }
.hp3-roster-grid .hp3-roster__fallback-monogram{ --roster-mono-fs:44cqi; }

/* Hover: the shared media gesture (see the [data-media-hover] block at the top of
   this file) — nothing roster-specific. Org-chart staff tiles simply carry no
   data-media-hover (they aren't links), so they need no counter-rule here. No
   page-open reveal: by request the roster/profile pages don't animate on load. */

/* Branded photo fallback (a member with no headshot) — a diagonal primary panel
   with a faint monogram, never a blank tile or a stock face. Sits in the same
   3:4 wrap as the photo; the ghost number stays on top. */
.hp3-roster__fallback{
  position:absolute; inset:0;
  display:flex; align-items:center; justify-content:center;
  background:linear-gradient(150deg, var(--primary,#E30514), color-mix(in srgb, var(--primary,#E30514) 52%, black));
}
.hp3-roster__fallback-monogram{
  font-family:var(--font-display,'Anton',sans-serif); line-height:0.9;
  font-size:var(--roster-mono-fs, clamp(44px,12vw,80px));
  color:rgba(255,255,255,0.20); letter-spacing:0.02em;
}

/* Detailed card info: nationality (flag + name) replaces the role, and a CTA
   affordance sits at the bottom — the whole card is still the single link, the
   CTA just signals it and animates its arrow on hover. */
.hp3-roster__card--detailed .hp3-roster__info{ display:flex; flex-direction:column; gap:var(--space-1,0.25rem); }
.hp3-roster__nat{
  display:flex; align-items:center; gap:var(--space-2,0.5rem);
  font-family:var(--font-body,'Inter',sans-serif); font-size:var(--text-caption);
  text-transform:uppercase; letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--muted-4,#5a575a);
}
.hp3-roster__flag{ font-size:1.15em; line-height:1; }
.hp3-roster__cta{
  display:inline-flex; align-items:center; gap:var(--space-2,0.5rem);
  margin-top:var(--space-3,0.75rem);
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:var(--text-caption); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--primary-ink,#E30514);
}
.hp3-roster__cta-arrow{ transition:transform var(--dur-micro) ease; }
@media (hover: hover){ .hp3-roster__card--detailed:hover .hp3-roster__cta-arrow{ transform:translateX(4px); } }

/* Single-player profile. */
.hp3-member-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-member{
  width:100%; max-width:1920px; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
/* Breadcrumb trail (ui.breadcrumb): the shared "where am I" path on depth-3+
   detail routes — one component, not a per-page skin. Uses the eyebrow display
   type so it reads as chrome, not content. */
.hp3-crumbs{ margin-bottom:var(--space-8,2rem); }
.hp3-crumbs__list{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2,0.5rem);
  list-style:none; margin:0; padding:0;
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:var(--text-eyebrow); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
}
.hp3-crumbs__item{ display:inline-flex; align-items:center; gap:var(--space-2,0.5rem); min-width:0; }
.hp3-crumbs__link{
  color:var(--muted,#6b675f); text-decoration:none;
  transition:color var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-crumbs__link:hover{ color:var(--primary-ink,#E30514); } }
.hp3-crumbs__sep{ color:var(--muted,#6b675f); opacity:.55; }
.hp3-member__hero{
  display:grid; grid-template-columns:minmax(0, 440px) 1fr;
  gap:clamp(28px,4.5vw,72px); align-items:start;
}
.hp3-member__photo-wrap{ position:relative; overflow:hidden; border-radius:var(--radius-card,2px); background:var(--card,#ffffff); container-type:inline-size; aspect-ratio:3/4; }
.hp3-member__photo{ width:100%; aspect-ratio:3/4; object-fit:cover; object-position:top; display:block; filter:brightness(.95) saturate(.9); }
/* Same branded fallback as the roster card, scaled to the hero photo (monogram
   sized by container units so it tracks the photo width). */
.hp3-member__fallback{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  background:linear-gradient(150deg, var(--primary,#E30514), color-mix(in srgb, var(--primary,#E30514) 52%, black));
}
.hp3-member__fallback-monogram{
  font-family:var(--font-display,'Anton',sans-serif); line-height:0.9;
  font-size:42cqi; color:rgba(255,255,255,0.22); letter-spacing:0.02em;
}
.hp3-member__intro{ display:flex; flex-direction:column; min-width:0; }
/* Oversized name — the profile's headline moment, a step above .hp3-title-1. It's
   the ONLY thing beside the photo now; every fact (role, number, nationality,
   vitals) is a detail row below. */
.hp3-member__name{ color:var(--ink,#0a0a14); margin:0; font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-xl) * 1.35); }
.hp3-member__related{ margin-top:var(--space-16,4rem); }

/* Details — one fact per row: small label on the left, a big Anton value on the
   right, hairline-divided. Role / number / nationality live here too (not
   repeated around the name). --text-title-lg carries its own breakpoint
   step-downs, so a long value (a country name) stays legible on mobile. */
.hp3-member__stats{ margin:clamp(28px,3.5vw,48px) 0 0; border-top:2px solid var(--ink,#0a0a14); }
.hp3-member__stat{
  display:flex; align-items:center; justify-content:space-between; gap:var(--space-5,1.25rem);
  padding:var(--space-5,1.25rem) 0; border-bottom:1px solid var(--placeholder,#e0e0e0);
}
.hp3-member__stat-label{ flex:0 0 auto; color:var(--muted,#6b675f); }
.hp3-member__stat-value{
  margin:0; min-width:0; text-align:right;
  font-family:var(--font-display,'Anton',sans-serif); line-height:1; text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-title-lg));
  color:var(--ink,#0a0a14);
}
/* SI unit symbols stay lowercase; the value's uppercase must not reach them. */
.hp3-member__unit{ text-transform:none; font-size:0.45em; margin-left:0.2em; color:var(--muted-4,#5a575a); }
/* Emoji flag — text font so it renders as the glyph, not Anton; sized to sit with
   the value rather than dwarf it. */
.hp3-member__flag{ font-family:var(--font-body,'Inter',sans-serif); font-size:0.7em; line-height:1; margin-right:0.5em; vertical-align:middle; }

@media (max-width:767px){
  /* Stack — and the photo takes the FULL width (no cap), by request. */
  .hp3-member__hero{ grid-template-columns:1fr; gap:var(--space-8,2rem); }
}

@media (max-width:639px){
  .hp3-roster-page{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  .hp3-roster-grid{ --bs-gutter-x:clamp(8px,2.5vw,14px); --bs-gutter-y:clamp(14px,4vw,22px); }
  .hp3-member{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
}

/* ---- Video, from section-video.dc.html ---- */

.hp3-video-container{ width:100%; background:var(--card,#ffffff); }
.hp3-video{
  width:100%;
  background:var(--card,#ffffff);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  padding:var(--space-12) var(--space-10) var(--space-8);
}
.hp3-video__head{ --section-head-mb:40px; }
@media (max-width:639px){ .hp3-video__head{ --section-head-mb:18px; } }
.hp3-video__mosaic{ display:grid; grid-template-columns:1fr 1fr; grid-template-rows:auto auto; gap:clamp(2px,0.3vw,4px); }
/* display:block + width:100% + the reset below: now a real <button> (2026-07-15
   a11y fix), so the UA's own button box (inline-block sizing, border, padding,
   centered text, system font) needs zeroing — same treatment .hp3-photo-tile
   already had. display:block also covers the .hp3-video-grid > .col context
   (page-video-album.tplp), where the tile is a plain in-flow child, not itself
   a grid/flex item that CSS would blockify for free. */
.hp3-video__card{
  position:relative; overflow:hidden; cursor:pointer; background:var(--surface,#111014);
  display:block; width:100%; margin:0; padding:0; border:none;
  font:inherit; text-align:left; color:inherit; appearance:none;
}
.hp3-video__card:focus-visible{ outline:2px solid var(--paper,#f2f0eb); outline-offset:2px; }
/* Fills the card (image + play + badge) — the overlay caption sits on top of it.
   Overridden to in-flow only for small mobile tiles (see the mobile block below). */
.hp3-video__media{ position:absolute; inset:0; }
.hp3-video__ph{
  position:absolute; inset:0;
  background:
    repeating-linear-gradient(135deg, rgba(255,255,255,0.05) 0px, rgba(255,255,255,0.05) 2px, transparent 2px, transparent 14px),
    linear-gradient(135deg, #050505 0%, #1c1c1c 45%, #050505 100%); /* ds-lint: allow-hex — decorative chrome gradient */
}
.hp3-video__ph::before{ content:""; position:absolute; inset:0; background:linear-gradient(135deg, rgba(255,255,255,0.08) 0%, transparent 55%); }
/* Real thumb_hq photo, when a video has one — falls back to .hp3-video__ph
   above otherwise (see section-video.tplp's thumb() macro). */
.hp3-video__thumb{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }

.hp3-video__hero{ grid-row:1/3; aspect-ratio:auto; min-height:clamp(280px,32vw,460px); }
.hp3-video__mid{ aspect-ratio:16/9; }
.hp3-video__small-row{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(2px,0.3vw,4px); }
.hp3-video__small{ aspect-ratio:16/9; }
/* Count-adaptive degenerate cases (see section-video.tplp): with 2 videos the
   hero stops spanning rows so both tiles sit as equal 16:9; with 1 it becomes
   a single cinematic band; a lone small tile fills its whole row. */
.hp3-video__mosaic--duo .hp3-video__hero{ grid-row:auto; aspect-ratio:16/9; min-height:0; }
.hp3-video__mosaic--solo{ grid-template-columns:1fr; }
.hp3-video__mosaic--solo .hp3-video__hero{ grid-row:auto; aspect-ratio:21/9; min-height:clamp(280px,26vw,420px); }
.hp3-video__small-row .hp3-video__small:only-child{ grid-column:1/-1; }

.hp3-video__play{ position:absolute; inset:0; z-index:2; }
.hp3-video__play-btn{ background:var(--primary,#E30514); border-radius:50%; }
/* Play button: ONE fixed diameter per tile tier, not a fluid clamp (2026-07-20,
   by request). The old clamp(min,vw,max) made the circle drift with viewport width
   so it never had a stable size; a fixed px per tier keeps the deliberate
   hero>mid>small hierarchy while reading as intentional at every width. The glyph
   stays a % of the button so it tracks the diameter without its own breakpoint. */
.hp3-video__hero .hp3-video__play-btn{ width:64px; height:64px; }
.hp3-video__hero .hp3-video__play-btn svg{ width:38%; height:38%; }
.hp3-video__mid .hp3-video__play-btn{ width:44px; height:44px; }
.hp3-video__mid .hp3-video__play-btn svg{ width:38%; height:38%; }
.hp3-video__small .hp3-video__play-btn{ width:32px; height:32px; }
.hp3-video__small .hp3-video__play-btn svg{ width:36%; height:36%; }

.hp3-video__badge{
  position:absolute; top:clamp(10px,1.5vw,20px); left:clamp(10px,1.5vw,20px);
  z-index:2;
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:var(--text-caption);
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wider,0.18em)); text-transform:uppercase;
  background:var(--primary,#E30514); color:var(--on-primary,#fff);
  padding:var(--space-1,4px) var(--space-2,10px);
  border:1px solid var(--primary,#E30514); border-radius:var(--radius,2px);
  line-height:1;
}

.hp3-video__caption{
  position:absolute; bottom:0; left:0; right:0; z-index:2;
  background:linear-gradient(to top, color-mix(in srgb, var(--surface-deep,#0a090d) 92%, transparent) 0%, transparent 100%);
  padding:28px 28px 24px;
}
/* Typography from .hp3-title-3--md (design-system.md §4/§7.1); only its
   on-dark color stays local (the tile scrim is dark). Video tiles carry the
   title ONLY (the old duration · date meta line was dropped 2026-07-10). */
.hp3-video__caption-title{ color:var(--paper,#f2f0eb); }
.hp3-video__small .hp3-video__caption{ padding:14px 14px 11px; }
/* Small tiles go 2-up on mobile — cap the title at 2 lines so a long headline
   can't overrun the tile or crowd the meta line above it. */
.hp3-video__small .hp3-video__caption-title{
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2;
  overflow:hidden;
}

.hp3-video__player-overlay{
  display:none;
  position:fixed; inset:0; z-index:80;
  background:rgba(6,6,10,0.92);
  padding:clamp(16px,4vw,48px);
}
/* align-items:stretch (not center) — the STAGE fills the overlay's full height
   so it can center the video box itself internally (see .hp3-video__player-stage
   below); centering this flex item as a whole would center the video+caption
   BLOCK instead, pushing the video off-centre by half the caption's height. */
.hp3-video__player-overlay.is-open{ display:flex; align-items:stretch; justify-content:center; }
/* Stage = the video + its caption. Grid, not flex-column: a 1fr/auto/1fr row
   split centers the video box (row 2) in the AVAILABLE HEIGHT regardless of
   the caption's (row 3) size — the caption just occupies whatever space is
   left below, it never pulls the video up to make room for itself. The video
   box never has anything overlaid on it (the close X is a corner of the
   overlay, the caption is below), so the provider's own controls stay fully
   reachable. */
/* pointer-events:none — the stage now spans the overlay's FULL height (the two
   1fr rows are real empty space, not just visual padding), and js/script.js's
   click-outside-to-close listens on the overlay with stage.stopPropagation()
   guarding it (the video/caption must not close on click). Without this, that
   guard would swallow clicks in the empty rows too, since they're now inside
   stage's box — re-enabled below on the box/caption so those still block
   close. */
.hp3-video__player-stage{
  display:grid; grid-template-rows:1fr auto 1fr; grid-template-columns:1fr;
  justify-items:center; width:min(1040px, 100%); height:100%; max-height:100%;
  pointer-events:none;
}
.hp3-video__player-box{ grid-row:2; pointer-events:auto; position:relative; width:100%; aspect-ratio:16/9; background:#000; overflow:hidden; } /* ds-lint: allow-hex — video letterbox black */
.hp3-video__player-box .hp3-video__ph{ opacity:0.9; }
.hp3-video__player-mount{ position:absolute; inset:0; }
.hp3-video__player-mount iframe{ width:100%; height:100%; border:0; display:block; }
/* Close — a corner of the OVERLAY (outside the video), always visible, never over
   the player or its controls. Positioning only: the button itself is the SAME
   .hp3-ui-navbtn component Hero/News/Roster/pagination use (identical square
   skin, icon sizing, hover/press/focus/disabled states) — only the icon inside
   it changes (close instead of an arrow). Shared with the photo lightbox close,
   which reuses this same class. */
.hp3-video__player-close{
  position:absolute; top:clamp(10px,1.6vw,20px); right:clamp(10px,1.6vw,20px); z-index:3;
}
/* Caption — a line BELOW the video (not overlaid), so it never sits on the
   player's own controls. Hidden when empty. grid-row:3 + align-self:start (not
   the row's own gap) puts it right under the box: row 3 is the bottom spacer,
   and the caption sits at ITS top edge rather than stretching/centering
   through the whole spacer. */
.hp3-video__player-caption{
  grid-row:3; align-self:start; margin-top:clamp(12px,1.6vw,20px); pointer-events:auto;
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:calc(var(--type-scale,1) * var(--type-scale-brand,1) * var(--text-subtitle));
  letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-tight,0.03em));
  color:var(--paper,#f2f0eb); text-align:center;
}
.hp3-video__player-caption:empty{ display:none; }

.hp3-video__tablet-mosaic{ gap:clamp(2px,0.5vw,4px); }
.hp3-video__tablet-mosaic .hp3-video__hero{ grid-row:auto; aspect-ratio:16/9; min-height:0; }
/* Count-adaptive secondary row (§16): 6-col base so any count divides evenly —
   3 tiles span 2, 2 tiles span 3, a lone tile takes the full row (the real
   2-video club showed a third-width orphan on mobile before this). */
.hp3-video__tablet-row{ display:grid; grid-template-columns:repeat(6,1fr); gap:clamp(2px,0.5vw,4px); }
.hp3-video__tablet-row .hp3-video__small{ grid-column:span 2; }
.hp3-video__tablet-row .hp3-video__small:first-child:nth-last-child(2),
.hp3-video__tablet-row .hp3-video__small:first-child:nth-last-child(2) ~ .hp3-video__small{ grid-column:span 3; }
.hp3-video__tablet-row .hp3-video__small:only-child{ grid-column:1/-1; }

.hp3-video__footer-link{ margin-top:var(--space-8,32px); }

@media (max-width:639px){
  /* Was --space-10 (40px) with no mobile override — brought in line with every
     other section's mobile inset (vertical padding kept). */
  .hp3-video{ padding-left:var(--section-pad-x-mobile); padding-right:var(--section-pad-x-mobile); }
  /* Mobile: two smalls per row (span 3 of 6), the third hides; a lone small
     still spans the full row via the :only-child rule above. */
  .hp3-video__tablet-row .hp3-video__small{ grid-column:span 3; }
  .hp3-video__tablet-row .hp3-video__small:only-child{ grid-column:1/-1; }
  .hp3-video__tablet-row .hp3-video__small:nth-child(3){ display:none; }
  .hp3-video__caption{ padding:10px 10px 8px; }
  .hp3-video__small .hp3-video__caption{ padding:8px 10px 7px; }
  /* The old in-flow caption-below-thumbnail variant for mobile smalls is gone
     (direct feedback 2026-07-10: the title belongs INSIDE the preview) — with
     the meta line dropped, a 2-line clamped title coexists with the play
     button even on half-width tiles. */
}

/* ---- Videos routes (/videos albums index + /videos/album/{id}). Light-surface
   page like events/roster; the album grid and the album's video grid are both
   Bootstrap row-cols with the gutter bridged to --space tokens. ---- */
.hp3-videos-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-videos-page{
  width:100%; max-width:1920px; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
.hp3-video-albums-grid, .hp3-video-grid{ --bs-gutter-x:clamp(14px,2vw,24px); --bs-gutter-y:clamp(20px,3vw,32px); }

/* Video album card — a 16:9 cover that NAVIGATES to the album page (an album is a
   set of clips, not one video, so it links rather than opening the lightbox). */
.hp3-video-album{ display:flex; flex-direction:column; text-decoration:none; background:var(--card,#ffffff); border-radius:var(--radius-card,2px); overflow:hidden; }
.hp3-video-album__media{ position:relative; aspect-ratio:16/9; overflow:hidden; background:var(--surface,#111014); }
.hp3-video-album__thumb{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
/* Videos-count badge — the uppercase brand pill, same look as .hp3-video__badge. */
.hp3-video-album__count{
  position:absolute; top:var(--space-3,0.75rem); right:var(--space-3,0.75rem); z-index:3;
  padding:4px 8px; background:var(--primary,#E30514); color:var(--on-primary,#fff);
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase; line-height:1;
  font-size:var(--text-caption); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
}
.hp3-video-album__info{ padding:16px 16px 18px; border-top:2px solid var(--primary,#E30514); }
.hp3-video-album__title{ color:var(--ink,#0a0a14); transition:color var(--dur-micro) ease; }
@media (hover: hover){ .hp3-video-album:hover .hp3-video-album__title{ color:var(--primary-ink,#E30514); } }
.hp3-video-album__date{ color:var(--muted-4,#5a575a); margin-top:var(--space-1,4px); }
/* The album's videos reuse videoCard ('small' = 16:9); it fills its Bootstrap col. */
.hp3-video-grid > .col > .hp3-video__card{ width:100%; }

/* Single-video page (/videos/{id}) — a focused, centred column: the embedded
   player (16:9), then title/description and prev/next within the album. Narrower
   than the 1920 list pages on purpose — a lone video reads best centred. */
.hp3-video-page{
  width:100%; max-width:1100px; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
.hp3-video-page__player{
  position:relative; aspect-ratio:16/9; overflow:hidden;
  background:var(--surface,#111014); border-radius:var(--radius-card,2px);
  margin-bottom:var(--space-8,2rem);
}
.hp3-video-page__frame{ position:absolute; inset:0; width:100%; height:100%; border:0; }
.hp3-video-page__title{ color:var(--ink,#0a0a14); margin:0 0 var(--space-4,1rem); }
.hp3-video-page__desc{ color:var(--muted-4,#5a575a); font-size:var(--text-body-lg,1rem); max-width:70ch; margin:0 0 var(--space-8,2rem); }
.hp3-video-page__nav{
  display:flex; align-items:center; justify-content:space-between; gap:var(--space-4,1rem);
  border-top:1px solid var(--placeholder,#e0e0e0); padding-top:var(--space-6,1.5rem);
}
.hp3-video-page__navlink{
  display:inline-flex; align-items:center; gap:var(--space-2,0.5rem); text-decoration:none;
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase;
  font-size:var(--text-eyebrow); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--muted,#6b675f); transition:color var(--dur-micro) ease;
}
.hp3-video-page__navlink--next{ margin-left:auto; text-align:right; }
@media (hover: hover){ .hp3-video-page__navlink:hover{ color:var(--primary-ink,#E30514); } }

@media (max-width:639px){
  .hp3-videos-page{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  .hp3-video-page{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  .hp3-video-albums-grid, .hp3-video-grid{ --bs-gutter-x:clamp(10px,3vw,16px); }
}

/* ============================================================================
   PHOTOS — the sibling of the video section. Every piece mirrors an existing one:
   the album cover reuses the video-album card treatment, the tiles reuse the
   shared hover-zoom, the lightbox reuses the video lightbox chrome (grouped
   selectors below), and the single-photo page reuses the video-page nav. The
   lightbox prev/next arrows ARE `.hp3-ui-navbtn` — the identical component
   Hero/News/Roster/pagination use, same skin, same icon sizing, only the
   positioning below is net-new. The only fully net-new piece is the CSS mosaic
   (a wide/square rhythm off the real crops).
   ============================================================================ */

/* Homepage photos block — mirrors .hp3-video-container/.hp3-video/.hp3-video-inner.
   Uses --page (not --card) so it reads as its own band between the white video and
   roster sections. */
.hp3-photos-home-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-photos-inner{ width:100%; max-width:1920px; min-width:0; }
.hp3-photos-home{
  width:100%;
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  padding:var(--space-12) var(--space-10) var(--space-8);
}
.hp3-photos-home__head{ --section-head-mb:40px; }
@media (max-width:639px){ .hp3-photos-home__head{ --section-head-mb:18px; } }
.hp3-photos-home__footer-link{ margin-top:var(--space-8,32px); }

/* Route pages (/photos, /photos/album/{id}) — mirror .hp3-videos-container/-page. */
.hp3-photos-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-photos-page{
  width:100%; max-width:1920px; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
.hp3-album-grid{ --bs-gutter-x:clamp(14px,2vw,24px); --bs-gutter-y:clamp(20px,3vw,32px); }

/* Photo album cover — the direct clone of .hp3-video-album (a cover that NAVIGATES
   to the album page), 4:3 instead of 16:9 (photos aren't widescreen), no play. */
.hp3-album-cover{ display:flex; flex-direction:column; text-decoration:none; background:var(--card,#ffffff); border-radius:var(--radius-card,2px); overflow:hidden; }
.hp3-album-cover__media{ position:relative; aspect-ratio:4/3; overflow:hidden; background:var(--surface,#111014); }
.hp3-album-cover__img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.hp3-album-cover__count{
  position:absolute; top:var(--space-3,0.75rem); right:var(--space-3,0.75rem); z-index:3;
  padding:4px 8px; background:var(--primary,#E30514); color:var(--on-primary,#fff);
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase; line-height:1;
  font-size:var(--text-caption); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
}
.hp3-album-cover__info{ padding:16px 16px 18px; border-top:2px solid var(--primary,#E30514); }
.hp3-album-cover__title{ color:var(--ink,#0a0a14); transition:color var(--dur-micro) ease; }
@media (hover: hover){ .hp3-album-cover:hover .hp3-album-cover__title{ color:var(--primary-ink,#E30514); } }
.hp3-album-cover__date{ color:var(--muted-4,#5a575a); margin-top:var(--space-1,4px); }

/* Photo mosaic — the wall of tiles (album page + homepage). Every tile is the
   same square cell (aspect-ratio:1, gapless), cropped via object-fit:cover. */
.hp3-photo-mosaic{
  display:grid; grid-template-columns:repeat(2,1fr);
  gap:clamp(3px,0.6vw,8px);
}
@media (min-width:640px){ .hp3-photo-mosaic{ grid-template-columns:repeat(3,1fr); } }
@media (min-width:1024px){ .hp3-photo-mosaic{ grid-template-columns:repeat(4,1fr); } }
/* Real <button> reset — the tile opens the lightbox (an action, not a link). */
.hp3-photo-tile{
  position:relative; overflow:hidden; cursor:pointer; padding:0; border:none;
  background:var(--surface,#111014); aspect-ratio:1;
}
.hp3-photo-tile__media{ position:absolute; inset:0; }
.hp3-photo-tile__img{ width:100%; height:100%; object-fit:cover; }
/* Masked title reveal — a decoupled plane sliding up on hover (the caption plane
   moves independently of the image-zoom plane). Visible at rest on touch (no
   hover), tucked away then revealed on hover-capable devices. */
.hp3-photo-tile__caption{
  position:absolute; left:0; right:0; bottom:0; z-index:2;
  padding:var(--space-6,1.5rem) var(--space-3,0.75rem) var(--space-2,0.5rem);
  background:linear-gradient(to top, rgba(0,0,0,0.72), rgba(0,0,0,0) 100%);
  pointer-events:none;
}
/* font-size bumped --text-caption -> --text-eyebrow -> --text-body
   (2026-07-21, direct feedback: "ingrandisci leggermente le scritte sulle
   foto", then a follow-up "si può fare di più?") — two token steps up total
   (12px -> 13px -> 15px), same scale used elsewhere this pass. */
.hp3-photo-tile__title{
  display:block; color:var(--paper,#f2f0eb);
  font-family:var(--font-display,'Anton',sans-serif); text-transform:uppercase; line-height:1.1;
  font-size:var(--text-body); letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-tight,0.03em));
}
@media (hover: hover){
  .hp3-photo-tile__caption{ transform:translateY(100%); transition:transform var(--dur-short) var(--ease-enter); }
  .hp3-photo-tile:hover .hp3-photo-tile__caption{ transform:translateY(0); }
}
/* Homepage cap: keep the mosaic to two tidy rows of square tiles on desktop. */
.hp3-photo-mosaic--home{ margin-top:0; }

/* Photo lightbox — reuses the video lightbox chrome. The overlay, stage, close X
   and caption are the SAME components (grouped selectors / shared classes in the
   markup); only the box holds an <img> (contain, not a 16:9 iframe) and the prev/
   next arrows are new. */
.hp3-photo__player-overlay{
  display:none;
  position:fixed; inset:0; z-index:80;
  background:rgba(6,6,10,0.92);
  padding:clamp(16px,4vw,48px);
}
/* align-items:stretch, not center — same reasoning as the video overlay above:
   the STAGE fills the overlay's full height so it can center the photo box
   itself (grid rows below), instead of centering the photo+caption block. */
.hp3-photo__player-overlay.is-open{ display:flex; align-items:stretch; justify-content:center; }
/* Grid (1fr/auto/1fr rows), not flex-column — centers the photo box (row 2) in
   the available height regardless of the caption's (row 3) size, same as the
   video stage above. touch-action:pan-y (2026-07-15, touch swipe support in
   js/script.js's initSwipe/initPhoto) — same rationale as .hp3-sponsor__viewport
   below. */
/* pointer-events:none — same reasoning as .hp3-video__player-stage above: the
   stage now spans the overlay's full height, so its empty spacer rows must not
   swallow the backdrop click-to-close (js/script.js guards real clicks on the
   photo/caption via stage.stopPropagation(), re-enabled on box/caption below).
   initSwipe's touch listener is bound to this same stage element, but a touch
   starting on the (pointer-events:auto) box still bubbles through it normally
   — only touches starting in the truly empty area stop reaching it, which is
   the correct behaviour (that area is backdrop, not the photo). */
.hp3-photo__player-stage{
  display:grid; grid-template-rows:1fr auto 1fr; grid-template-columns:1fr;
  justify-items:center; width:min(1200px, 100%); height:100%; max-height:100%;
  touch-action:pan-y; pointer-events:none;
}
.hp3-photo__player-box{ grid-row:2; pointer-events:auto; position:relative; width:100%; display:flex; align-items:center; justify-content:center; min-height:0; }
.hp3-photo__player-mount{ display:flex; align-items:center; justify-content:center; max-width:100%; }
/* 70vh, not 78vh — leaves clearance below the tallest portrait photo for the
   caption plus the share bar, now pinned to the overlay's bottom edge instead
   of following the stage (see .hp3-video__player-share above). */
.hp3-photo__player-img{ display:block; max-width:100%; max-height:70vh; width:auto; height:auto; object-fit:contain; }
/* Prev/next — positioning only. The button itself IS `.hp3-ui-navbtn` (see
   photo-lightbox.tplp) — the identical square component Hero/News/Roster use,
   same skin/icon-sizing/hover/press/focus states, just a different icon.
   initPhoto disables these buttons at the album ends via the native
   :disabled attribute, same as every other .hp3-ui-navbtn. */
.hp3-photo__player-nav{ position:absolute; top:50%; transform:translateY(-50%); z-index:3; }
.hp3-photo__player-nav--prev{ left:clamp(10px,2vw,28px); }
.hp3-photo__player-nav--next{ right:clamp(10px,2vw,28px); }

/* Single-photo page (/photos/{id}) — mirrors .hp3-video-page (centred column),
   reuses .hp3-video-page__nav/__navlink for prev/next. */
.hp3-photo-page{
  width:100%; max-width:1100px; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
.hp3-photo-page__stage{ margin:0 0 var(--space-6,1.5rem); }
.hp3-photo-page__img{
  display:block; width:100%; height:auto; max-height:80vh; object-fit:contain;
  background:var(--surface,#111014); border-radius:var(--radius-card,2px);
}
.hp3-photo-page__desc{ color:var(--muted-4,#5a575a); font-size:var(--text-body-lg,1rem); max-width:70ch; margin:var(--space-4,1rem) 0 0; }
.hp3-photo-page__title{ color:var(--ink,#0a0a14); margin:var(--space-6,1.5rem) 0 var(--space-4,1rem); }
.hp3-photo-page__share{ margin:0 0 var(--space-8,2rem); }

@media (max-width:639px){
  .hp3-photos-page{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  .hp3-photo-page{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  .hp3-photos-home{ padding-left:var(--section-pad-x-mobile); padding-right:var(--section-pad-x-mobile); }
  .hp3-album-grid{ --bs-gutter-x:clamp(10px,3vw,16px); }
}

/* ---- Roster, from section-roster.dc.html ---- */

.hp3-roster-container{ width:100%; background:var(--card,#ffffff); }
.hp3-roster{
  width:100%;
  background:var(--card,#ffffff);
  padding:var(--space-12) var(--space-10) var(--space-12);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
  /* Ghost jersey number is a bespoke decorative numeral, far outside the
     shared --text-* scale (32-64px) — kept as a local token since it's not
     a type-scale value at all (see docs/decisions.md, 2026-07-08). */
  --roster-ghost-num-fs:clamp(48px,8vw,96px);
}
.hp3-roster__head{ --section-head-mb:26px; }

.hp3-roster__strip{
  /* Same grid-based carousel as .hp3-news__track (see section-news.dc.html
     comment above): a fixed grid-auto-columns size, not minmax, keeps 4.5
     cards' worth of width overflowing into a scrollbar so exactly 4/row are
     fully visible with a half-card peek hint — matches News's slider
     mechanics and gap 1:1. */
  --roster-gap:clamp(14px,2vw,24px);
  display:grid;
  grid-auto-flow:column;
  grid-auto-columns:max(240px,calc((100% - 4 * var(--roster-gap)) / 4.5));
  grid-template-rows:1fr;
  gap:var(--roster-gap);
  overflow-x:auto;
  overflow-y:hidden;
  scroll-snap-type:x mandatory;
  scroll-behavior:smooth;
  -ms-overflow-style:none;
  scrollbar-width:none;
  padding-bottom:2px;
}
.hp3-roster__strip::-webkit-scrollbar{ display:none; }

.hp3-roster__card{
  scroll-snap-align:start;
  display:flex; flex-direction:column;
  background:var(--card,#ffffff);
  overflow:hidden;
  text-decoration:none;
  border-radius:var(--radius-card,2px);
}
.hp3-roster__photo-wrap{
  position:relative;
  /* Own the 3:4 ratio here (not just via the <img>) so the box keeps its height
     when there is no photo — the branded fallback fills it by position:absolute
     and would otherwise collapse to zero. Matches the photo's own ratio, so a
     card WITH a photo is unchanged. */
  aspect-ratio:3/4;
  /* Ghost number now sits fully inside this box (see .hp3-roster__ghost-num),
     so it no longer overhangs the .hp3-roster__info divider below — clip to
     the card so the photo's bottom corners follow the card radius. */
  overflow:hidden;
}
.hp3-roster__photo{ width:100%; aspect-ratio:3/4; object-fit:cover; object-position:top; filter:brightness(.9) saturate(.85); }
/* Scrim behind the number so it stays legible over any photo, not just dark ones. */
.hp3-roster__photo-wrap::after{
  content:''; position:absolute; inset:auto 0 0 0; height:55%;
  background:linear-gradient(to top, rgba(0,0,0,0.55), rgba(0,0,0,0) 100%);
  pointer-events:none;
}
.hp3-roster__ghost-num{
  --ghost-inset:clamp(10px,1.4vw,16px);
  /* Equal left/bottom insets give equal *box* positions, but with line-height:1
     the face keeps its descent+half-leading band below the baseline and digits
     have no descender — so equal insets leave the numeral floating higher off
     the bottom edge than off the left. Subtract that band (a per-face metric,
     --display-baseline-trim from include/head.tplp) from bottom so the two
     optical gaps match across every FontPairing. */
  position:absolute; left:var(--ghost-inset); bottom:calc(var(--ghost-inset) - var(--display-baseline-trim,0.09em)); z-index:1;
  font-family:var(--font-display,'Anton',sans-serif);
  font-size:var(--roster-ghost-num-fs);
  color:rgba(255,255,255,0.92);
  text-shadow:0 2px 10px rgba(0,0,0,0.55), 0 0 2px rgba(0,0,0,0.4);
  line-height:1;
}
.hp3-roster__info{ padding:16px 16px 18px; border-top:2px solid var(--primary,#E30514); }
/* Typography from .hp3-title-3 (design-system.md §4/§7.1); only its color local. */
.hp3-roster__name{ color:var(--ink,#0a0a14); }
.hp3-roster__role{
  font-family:var(--font-body,'Inter',sans-serif);
  font-size:var(--text-caption); text-transform:uppercase; letter-spacing:calc(var(--tracking-scale,1) * var(--tracking-wide,0.12em));
  color:var(--muted-4,#5a575a);
  margin-top:var(--space-1,4px);
}

/* Static people card (org chart /ochart): the roster card visual reused as a
   non-interactive tile — staff have no profile route, so no hover gesture and no
   number, and the number scrim is suppressed (nothing sits over the photo). The
   hover is opted out by omitting data-media-hover/data-media-zoom on the tile (see
   .hp3-roster__card--static in macros-cards.tplp), not by a counter-rule here. */
.hp3-roster__card--static .hp3-roster__photo-wrap::after{ display:none; }

/* Player-card hover is the shared media gesture (the [data-media-hover]
   block at the top of this file) — no roster-specific rule. The photo's desaturated
   rest look (filter above) is the roster's existing design and is deliberately NOT
   animated: hover only scales. The gesture stays inside .hp3-roster__photo-wrap
   because the strip's overflow-y is hidden — which is also why no media card in this
   theme lifts on hover: the news rail and this strip would both clip it. */

@media (max-width:639px){
  .hp3-roster__head{ --section-head-mb:16px; }
  /* Was --space-10 (40px) with no mobile override — matched to the shared inset. */
  .hp3-roster{ --roster-ghost-num-fs:42px; padding-left:var(--section-pad-x-mobile); padding-right:var(--section-pad-x-mobile); }
  .hp3-roster__info{ padding:10px 12px 12px; }
}

/* ---- Sponsors, from section-sponsors.dc.html ---- */

.hp3-sponsor-container{ width:100%; background:var(--card,#ffffff); }
.hp3-sponsor{
  width:100%;
  background:var(--card,#ffffff);
  padding:var(--space-10) var(--space-10);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
/* touch-action:pan-y (2026-07-15, touch swipe support in js/script.js's
   initSponsors): leaves vertical page scroll to the browser natively while
   handing horizontal drags to JS, without the JS needing to preventDefault
   on every touchmove to stop the browser fighting for the gesture. */
.hp3-sponsor__viewport{ overflow:hidden; position:relative; touch-action:pan-y; }
.hp3-sponsor__track{ transition:transform var(--dur-carousel) var(--ease-move); }
/* Same fix as .hero-track above: auto-advance already stops under reduced
   motion (js/script.js initSponsors — didn't even have that check until
   today, see docs/decisions.md), this covers a manual dot click. */
@media (prefers-reduced-motion: reduce){
  .hp3-sponsor__track{ transition:none; }
}
/* display:flex/flex-wrap and the column widths come from Bootstrap's
   row/row-cols-N classes on this element — N is 2/4/6 depending on
   viewport (js/script.js's initSponsors(), see docs/decisions.md
   2026-07-09), applied dynamically since a carousel page's item count
   varies by breakpoint; the tplp's own static markup only ever renders the
   no-JS-fallback row-cols-2/row-cols-md-4/row-cols-xxl-6 version. min-width
   and flex-shrink here are still ours regardless, needed for this
   element's OTHER job as one full-width slide in .hp3-sponsor__track's
   horizontal paging. */
.hp3-sponsor__page{
  min-width:100%;
  flex-shrink:0;
}
.hp3-sponsor__logo{ padding:clamp(12px, 2vw, 24px); }
.hp3-sponsor__logo img{
  height:clamp(36px, 5.2vw, 66px);
  max-width:clamp(108px, 13.5vw, 180px);
  object-fit:contain;
}
/* Small is the historical size. Medium and Large enlarge the rendered mark by
   exactly 25% and 50%; the carousel page count remains unchanged. */
.hp3-sponsor--size-medium .hp3-sponsor__logo img{
  height:clamp(45px, 6.5vw, 82.5px);
  max-width:min(clamp(135px, 16.875vw, 225px),100%);
}
.hp3-sponsor--size-large .hp3-sponsor__logo img{
  height:clamp(54px, 7.8vw, 99px);
  max-width:min(clamp(162px, 20.25vw, 270px),100%);
}
/* Homepage-strip treatment (--mono class, set per banner extension in
   cards.sponsorLogo): a theme-tinted silhouette — black on a light --card, white
   on a dark one (--sponsor-logo-filter, head.tplp) — kept at rest AND on hover;
   the logo is NEVER recoloured (by request 2026-07-16). Hover only lifts the
   silhouette from 0.5 to full opacity as a subtle affordance. Only alpha-capable
   formats get this (the macro gates by extension) — an opaque JPEG keeps full
   colour, as brightness(0) would flatten it into a solid block. The /sponsors
   page passes mono=false, so this never applies there (its logos stay full-colour
   on a fixed light plate below). */
.hp3-sponsor__logo--mono img{
  filter:var(--sponsor-logo-filter, brightness(0));
  opacity:0.5;
  transition:opacity var(--dur-micro) ease;
}
@media (hover: hover){ .hp3-sponsor__logo--mono:hover img{ opacity:1; } }
.hp3-sponsor__dots{ gap:clamp(6px, 1vw, 8px); margin-top:clamp(16px, 2.5vw, 28px); }
.hp3-sponsor__dot{
  width:8px; height:8px; border-radius:4px; flex-shrink:0;
  /* --ink-based mix, not a fixed rgba(0,0,0,...): --ink flips to a light color
     in dark mode (head.tplp's ColorMode block), so a hardcoded black dot was
     nearly invisible on the dark --card. This tracks the flip automatically. */
  background:color-mix(in srgb, var(--ink,#0a0a14) 12%, transparent);
  /* Explicit props (not `all`): the pill grows width and swaps background when
     .is-active — same short tier + move curve as the other page-dot indicators. */
  transition:width var(--dur-short) var(--ease-move), background var(--dur-short) ease;
  cursor:pointer;
}
.hp3-sponsor__dot.is-active{ width:28px; background:var(--primary,#E30514); }

@media (max-width:639px){
  .hp3-sponsor{ padding:var(--space-5) var(--section-pad-x-mobile); }
  .hp3-sponsor__logo{ padding:10px; }
  /* Below 640px the page shows only 2 sponsors per row (row-cols-2), so
     each column has far more room than the 4/6-per-row tiers — but the
     shared clamp() above is driven by vw alone, with no notion of column
     count, so it stayed pinned at its floor (36px/108px) across this
     entire range, making logos look flat and undersized next to the 4/6
     tiers. Raised floor + steeper vw ramp for this tier only; ceiling
     (58px/160px) is reached right at 639px so it hands off smoothly into
     the unscoped rule's own values at 768px. */
  .hp3-sponsor__logo img{
    height:clamp(46px, 11vw, 58px);
    max-width:clamp(120px, 34vw, 160px);
  }
  .hp3-sponsor--size-medium .hp3-sponsor__logo img{
    height:clamp(57.5px, 13.75vw, 72.5px);
    max-width:min(clamp(150px, 42.5vw, 200px),100%);
  }
  .hp3-sponsor--size-large .hp3-sponsor__logo img{
    height:clamp(69px, 16.5vw, 87px);
    max-width:min(clamp(180px, 51vw, 240px),100%);
  }
  .hp3-sponsor__dots{ margin-top:16px; }
}

/* ---- Static categorized sponsor wall (FixedSponsors) ---- */
.hp3-sponsor-wall-container{ width:100%; background:var(--card,#ffffff); }
.hp3-sponsor-wall{ width:100%; background:var(--card,#ffffff); }
.hp3-sponsor-wall__row{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:center;
  gap:var(--space-4,1rem) var(--space-8,2rem);
  padding:var(--space-8,2rem) var(--space-10,2.5rem);
  border-top:1px solid var(--hair,rgba(0,0,0,0.08));
}
.hp3-sponsor-wall__row:last-child{ border-bottom:1px solid var(--hair,rgba(0,0,0,0.08)); }
.hp3-sponsor-wall .hp3-sponsor__logo{
  flex:0 1 clamp(140px,14vw,220px);
  min-height:var(--space-20,5rem);
  padding:var(--space-4,1rem);
}
.hp3-sponsor-wall--size-medium .hp3-sponsor__logo{ flex-basis:clamp(175px,17.5vw,275px); }
.hp3-sponsor-wall--size-large .hp3-sponsor__logo{ flex-basis:clamp(210px,21vw,330px); }
.hp3-sponsor-wall .hp3-sponsor__logo img{
  height:clamp(36px,5.2vw,66px);
  max-width:min(clamp(108px,13.5vw,180px),100%);
  transform-origin:center;
}
.hp3-sponsor-wall--size-medium .hp3-sponsor__logo img{
  height:clamp(45px,6.5vw,82.5px);
  max-width:min(clamp(135px,16.875vw,225px),100%);
}
.hp3-sponsor-wall--size-large .hp3-sponsor__logo img{
  height:clamp(54px,7.8vw,99px);
  max-width:min(clamp(162px,20.25vw,270px),100%);
}
/* Optional category hierarchy: the first configured category is the visual
   reference (100%), then the confirmed 85/70/55/40 progression. Scaling only
   the mark preserves stable row alignment when categories contain mixed files. */
.hp3-sponsor-wall--degrade .hp3-sponsor-wall__row--level-2 img{ transform:scale(.85); }
.hp3-sponsor-wall--degrade .hp3-sponsor-wall__row--level-3 img{ transform:scale(.7); }
.hp3-sponsor-wall--degrade .hp3-sponsor-wall__row--level-4 img{ transform:scale(.55); }
.hp3-sponsor-wall--degrade .hp3-sponsor-wall__row--level-5 img{ transform:scale(.4); }

@media (max-width:639px){
  .hp3-sponsor-wall__row{
    gap:var(--space-2,.5rem);
    padding:var(--space-5,1.25rem) var(--section-pad-x-mobile);
  }
  .hp3-sponsor-wall .hp3-sponsor__logo,
  .hp3-sponsor-wall--size-medium .hp3-sponsor__logo,
  .hp3-sponsor-wall--size-large .hp3-sponsor__logo{
    flex-basis:calc(50% - var(--space-2,.5rem));
    min-width:0;
    padding:var(--space-3,.75rem);
  }
}

/* ---- Sponsors page (/sponsors — tpl/sponsors.tpl + include/page-sponsors.tplp).
   The full partner roster grouped by category (getSponsors().categories), each
   tier a titled block with a wrapping grid of the SAME cards.sponsorLogo tile the
   homepage carousel uses — laid out here as bordered card cells (not a carousel)
   so a static, scannable page reads at a glance. Container/padding mirror the
   /news index; the tile shows each logo in its original colours (block above). */
.hp3-sponsors-page-container{ width:100%; background:var(--page,#f5f3ee); }
.hp3-sponsors-page{
  width:100%; margin:0 auto;
  padding:var(--space-16,4rem) var(--space-10,2.5rem) var(--space-20,5rem);
  font-family:var(--font-body,'Inter',system-ui,sans-serif);
}
/* Space between category tiers; the first group sits directly under the page
   header (its own margin), so only add the gap BETWEEN groups. */
.hp3-sponsors-page__group + .hp3-sponsors-page__group{ margin-top:var(--space-16,4rem); }
.hp3-sponsors-page__group-head{ margin-bottom:var(--space-8,2rem); }
/* Bootstrap grid gutters bridged to the theme scale (same --bs-gutter idiom as
   the /news grid); row-cols-2/3/4 on the markup owns the responsive column count. */
.hp3-sponsors-page__grid{
  --bs-gutter-x:clamp(12px,1.6vw,20px);
  --bs-gutter-y:clamp(12px,1.6vw,20px);
}
/* Each cell stretches so its logo card fills the column to an equal height. */
.hp3-sponsors-page__cell{ min-width:0; display:flex; }
/* The shared logo tile is a bordered card in this page context (layout only —
   no re-skin of the component's identity): each partner logo sits on its own
   FIXED light plate (--sponsor-plate, white in both themes — NOT var(--card),
   which goes dark in dark mode and swallowed dark logos like Puma; request
   2026-07-16) so a wall of original-colour marks reads as a clean directory.
   STATIC by request (2026-07-15): no hover movement/tint/shadow — a partner
   list, not an interactive strip. */
.hp3-sponsors-page .hp3-sponsor__logo{
  width:100%;
  min-height:clamp(84px,11vw,120px);
  background:var(--sponsor-plate,#ffffff);
  border:1px solid var(--hair,rgba(0,0,0,0.08));
  border-radius:var(--radius-card,2px);
  padding:clamp(16px,2.5vw,28px);
}

@media (max-width:639px){
  .hp3-sponsors-page{ padding:var(--space-10,2.5rem) var(--section-pad-x-mobile) var(--space-16,4rem); }
  .hp3-sponsors-page__group + .hp3-sponsors-page__group{ margin-top:var(--space-12,3rem); }
  .hp3-sponsors-page__group-head{ margin-bottom:var(--space-6,1.5rem); }
}
