:root {
  --gutter: 1.5rem;

  /* Dark theme (default -- no data-theme attribute means dark, matching the
     site's original look before light mode existed). See theme-toggle.js
     for how [data-theme="light"] below gets applied, and PAGE_TEMPLATE's
     inline head script for why it happens before first paint. */
  color-scheme: dark;
  --bg: #14161c;
  --text: #e6e6ea;
  --text-muted: #8b8f9c;
  --border: #2a2d38;
  --surface: #1e2129;
  --surface-hover: #262a35;
  --table-bg: #1c1f28;
  --table-header-bg: #262b36;
  --table-header-hover: #313745;
  --table-row-alt: #23262f;
  --accent: #9aa4ff;
  --win: #22c55e;
  --loss: #ef4444;
  --toggle-track: #2a2d38;
  --toggle-thumb: #8b8f9c;
  --toggle-track-active: #31365e;
  --scrollbar-thumb: #3e4655;
}
:root[data-theme="light"] {
  color-scheme: light;
  /* Slightly cooler/darker than the first pass -- the initial values were
     too close to white to read as real borders/stripes (tables looked like
     they'd lost their gridlines entirely). */
  --bg: #eef0f4;
  --text: #1a1c23;
  --text-muted: #5b6070;
  --border: #c7cbd4;
  --surface: #ffffff;
  --surface-hover: #e4e7ee;
  --table-bg: #ffffff;
  --table-header-bg: #e2e5ec;
  --table-header-hover: #d4d8e2;
  --table-row-alt: #f3f4f8;
  --accent: #4338ca;
  --win: #15803d;
  --loss: #b91c1c;
  --toggle-track: #c7cbd4;
  --toggle-thumb: #868b9b;
  --toggle-track-active: #c7caf7;
  --scrollbar-thumb: #b5bac6;
}
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  /* The frise line's 100vw full-bleed (see .frise-wrap::before) is wider
     than the visible viewport by exactly the scrollbar's width on a page
     tall enough to have one, which would otherwise poke out a few px and
     add its own horizontal scrollbar -- clip it here instead. */
  overflow-x: hidden;
}
header, main {
  max-width: 1400px;
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
  box-sizing: border-box;
}
main { padding-bottom: 3rem; }
header {
  position: relative;
  padding-top: 1.5rem;
  padding-bottom: 0.5rem;
}
#header-extra {
  /* Absolutely positioned (empty on every page but index) so its presence
     never adds to header's own flow height -- header must stay exactly the
     same height across page shapes for the frise not to jump on nav (see
     h1's line-height comment). Top-aligned with the breadcrumb (header's
     padding-top, 1.5rem from header's top edge) rather than centered on the
     h1. The two toggles (theme + roster, index only) stack vertically and
     right-align -- kept deliberately compact (see #header-extra .toggle
     below) so the stack's total height still clears the h1 text sitting
     right underneath it instead of overlapping "Classement général". */
  position: absolute;
  top: 1.5rem;
  right: var(--gutter);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.35rem;
}
#header-extra .toggle {
  font-size: 0.75rem;
  gap: 0.45rem;
}
#header-extra .toggle-track {
  width: 1.8rem;
  height: 1rem;
}
#header-extra .toggle-thumb {
  width: 0.8rem;
  height: 0.8rem;
}
#header-extra .toggle.active .toggle-thumb {
  transform: translateX(0.8rem);
}
.toggle {
  display: inline-flex;
  flex-direction: row-reverse;
  align-items: center;
  gap: 0.6rem;
  border: none;
  background: none;
  padding: 0;
  color: var(--text);
  font-family: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.toggle-track {
  position: relative;
  flex-shrink: 0;
  width: 2.2rem;
  height: 1.2rem;
  border-radius: 999px;
  background: var(--toggle-track);
  transition: background-color 150ms ease;
}
.toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--toggle-thumb);
  transition: transform 150ms ease, background-color 150ms ease;
}
.toggle.active .toggle-track { background: var(--toggle-track-active); }
.toggle.active .toggle-thumb {
  background: var(--accent);
  transform: translateX(1rem);
}
.toggle-label { white-space: nowrap; }
header a {
  color: var(--accent);
  text-decoration: none;
  font-size: 0.9rem;
}
.breadcrumb {
  min-height: 1.35rem;
}
.breadcrumb-label {
  /* Same visual format as the breadcrumb back-link (header a) -- just not a
     link (nothing to navigate to from index) and no leading arrow. */
  color: var(--accent);
  font-size: 0.9rem;
}
h1 {
  margin: 0.3rem 0 1rem;
  /* Fixed (not font-size-relative) so the header is exactly the same total
     height on every page shape, regardless of which title class is active
     -- otherwise index's bigger hero-title makes the header taller than a
     season/war page's, and swapping between them on nav shifts everything
     below (the frise!) up or down. */
  line-height: 3rem;
}
h1.hero-title { font-size: 2.5rem; }
.table-scroll {
  overflow-x: auto;
  margin-bottom: 2rem;
}
table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.85rem;
  table-layout: fixed;
  background: var(--table-bg);
}
th, td {
  border: 1px solid var(--border);
  padding: 0.3rem 0.5rem;
  text-align: right;
  white-space: nowrap;
  width: 4.5em;
  overflow: hidden;
  text-overflow: ellipsis;
}
th:first-child, td:first-child {
  text-align: left;
  position: sticky;
  left: 0;
  background: var(--table-header-bg);
  width: 10em;
}
th {
  cursor: pointer;
  background: var(--table-header-bg);
  user-select: none;
}
th:hover { background: var(--table-header-hover); }
tbody tr:nth-child(even) { background: var(--table-row-alt); }
ul { line-height: 1.8; }

.frise-wrap {
  position: relative;
  /* Top margin trimmed by exactly the 1.1rem the war-number caption needs
     (see .frise below): the caption floats in that reclaimed space via
     absolute positioning, so the dot/label/line keep the exact page
     position they had before captions existed -- nothing else shifts,
     whether or not a given frise has captions. */
  margin: 0.4rem 0 2.5rem;
}
.frise-wrap::before {
  /* Full-bleeds past not just header/main's own gutter but main's own
     max-width:1400px box too, so the line reaches the true viewport edge on
     a wide screen instead of stopping at the centered container's edge --
     .frise-scroll can't host this itself since its overflow-x:auto would
     clip anything sticking out past its own box. The classic "break out of
     a centered max-width container" trick: 50% + a negative 50vw margin
     lands each edge at the viewport edge regardless of container width,
     since main is itself centered in the viewport. Top offset must pass
     through the dot's vertical center: .frise's own margin-top (1.1rem) +
     .frise-scroll's padding-top (0.5rem) + half the dot's height
     (14px/2 = 0.4375rem) = 2.0375rem. */
  content: "";
  position: absolute;
  top: 2.0375rem;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  height: 2px;
  background: var(--border);
}
.frise-scroll {
  overflow-x: auto;
  /* Lives here rather than on .frise itself: .frise is the positioned
     ancestor for .frise-old-layer (position:absolute), and an absolutely
     positioned child's "top:0" ignores its containing block's own padding
     -- so during the transition, the outgoing items would snap 0.5rem
     higher than where they normally sit in-flow. Padding on this
     non-positioned wrapper applies equally to both cases. */
  padding: 0.5rem 0.5rem 0.5rem 0;
}
.frise {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
  position: relative;
  width: max-content;
  min-width: 100%;
  box-sizing: border-box;
  /* Reserves room for .frise-above above the dots without pushing the dots
     themselves down -- margin (unlike padding on this same element) doesn't
     shift the padding-edge origin that .frise-old-layer's "top:0" resolves
     against, so it can't reintroduce the padding-mismatch bug described
     above. Also margin (unlike frise-scroll's padding) sits outside this
     element's own box, so it isn't clipped by the "overflow:hidden" that
     .frise-transitioning applies to this same element during a page
     transition -- padding here would've made captions flicker-clip mid-animation. */
  margin-top: 1.1rem;
}
.frise-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--text);
  flex-shrink: 0;
  position: relative;
}
.frise-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  transition: transform 150ms ease;
}
.frise-dot.frise-dot-win { background: var(--win); }
.frise-dot.frise-dot-loss { background: var(--loss); }
.frise-item:hover .frise-dot { transform: scale(1.2); }
.frise-above {
  /* Absolutely positioned (not a normal flex child) so its presence/absence
     never affects where the dot or label sit -- see .frise's margin-top
     comment for where the space it floats in comes from. */
  position: absolute;
  top: -1.1rem;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.7rem;
  line-height: 1;
  color: var(--text-muted);
  white-space: nowrap;
}

.frise.frise-transitioning { overflow: hidden; }
.frise-old-layer, .frise-new-layer {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
}
.frise-old-layer {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  transition: transform 420ms cubic-bezier(0.55, 0.06, 0.68, 0.19),
    opacity 420ms cubic-bezier(0.55, 0.06, 0.68, 0.19);
}
.frise-old-layer.frise-animate-out {
  transform: translateX(-60px);
  opacity: 0;
}
.frise-new-layer {
  transition: transform 420ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
    opacity 420ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
  transform: translateX(60px);
  opacity: 0;
}
.frise-new-layer.frise-animate-in {
  transform: translateX(0);
  opacity: 1;
}

.result-badge {
  display: inline-block;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.35rem 0.9rem;
  color: var(--text);
  font-size: 0.85rem;
  margin: 0 0 1rem;
}
.result-badge.is-win { border-color: var(--win); color: var(--win); }
.result-badge.is-loss { border-color: var(--loss); color: var(--loss); }
.result-badge-date {
  /* Muted/neutral rather than inheriting the badge's win/loss color --
     it's metadata, not part of the win/loss statement itself. */
  margin-left: 0.5rem;
  color: var(--text-muted);
}
.frise-label {
  /* Navigation idiom: a link, not a toggle -- deliberately kept visually
     distinct from .chip (filter idiom, see below) so hovering a frise item
     doesn't read as "selecting" it the way an active chip does. Border is
     transparent (not var(--border)) so nothing shifts on hover, only the
     background/dot react. */
  position: relative;
  font-size: 0.95rem;
  white-space: nowrap;
  text-align: center;
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 0.5rem 1.1rem;
  background: var(--surface);
  transition: background-color 150ms ease;
}
.frise-item:hover .frise-label {
  background: var(--surface-hover);
}

.chips {
  display: flex;
  gap: 0.5rem;
  margin: 1.5rem 0 1rem;
  overflow-x: auto;
}
.timeline-player-chips {
  /* The player-name row can run to dozens of chips (every player who
     fought that war/season/globally) -- wrap instead of forcing a
     horizontal scrollbar on anything roomier than a phone. Mobile keeps
     the scrolling row (see the @media block below) since wrapping that
     many short chips there would eat a lot of vertical space. */
  flex-wrap: wrap;
  overflow-x: visible;
}
.chip {
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.35rem 0.9rem;
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
  flex-shrink: 0;
}
.chip:hover { background: var(--surface-hover); }
.chip.active { border-color: var(--accent); color: var(--accent); }
.chart-legend {
  margin: 0 0 1rem;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.chart-scroll {
  overflow-x: auto;
  margin-bottom: 2rem;
}
.chart-value {
  font-size: 9px;
  fill: var(--text);
  text-anchor: middle;
}
.chart-label {
  font-size: 10px;
  fill: var(--text);
  text-anchor: end;
}
.chart-axis-label {
  /* Reference scale for the score box-plot's fixed 0-1600 y-axis (see
     render_boxplot_svg) -- muted like .chart-legend so it reads as a
     background gridline label, not a per-bar value like .chart-value. */
  font-size: 9px;
  fill: var(--text-muted);
  text-anchor: end;
}
.chart-box-group {
  transition: opacity 150ms ease;
}
.chart-box-group .chart-box,
.chart-box-group .chart-median {
  transition: filter 150ms ease;
}
.chart-box-group:hover .chart-box,
.chart-box-group:hover .chart-median {
  /* --accent is set inline per-group (see render_boxplot_svg) so the glow
     matches that player's own indigo/green without a second CSS rule per
     color -- same idea as .frise-dot-win/.frise-dot-loss but driven by a
     custom property instead of a class, since the color here is chosen
     per-player (active roster) rather than a fixed win/loss pair. Unrelated
     to the page-wide --accent theme variable above despite the same name --
     this one is set inline per <g>, scoped to it. */
  filter: drop-shadow(0 0 4px var(--accent));
}

.timeline-scroll {
  overflow-x: auto;
  margin-bottom: 2rem;
}
.timeline-scroll svg {
  display: block;
}
.timeline-axis-line {
  /* The baseline and the every-6h x gridlines (see render_activity_curve_svg)
     -- a class rather than render_boxplot_svg's hardcoded stroke="#2a2d38"
     so it actually follows the light/dark theme, unlike that older chart's
     gridlines. */
  stroke: var(--border);
  stroke-width: 1;
}
.timeline-axis-label {
  /* Overrides just .chart-axis-label's text-anchor:end (meant for its
     right-aligned y-axis labels elsewhere) back to centered, for this
     chart's horizontally-centered x-axis time labels sharing that same
     class for font-size/fill. */
  text-anchor: middle;
}
.timeline-player-mark {
  /* One per battle, hidden until its chip is toggled on (see
     timeline-filter.js) -- a thin vertical tick over the aggregate curve
     marking exactly when that player's token was played. Fixed green
     regardless of active-roster status (unlike the old per-bar green/
     purple split): this is a "when did they play" marker, not a
     performance indicator. */
  stroke: #22c55e;
  stroke-width: 1.5;
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms ease;
}
.timeline-player-mark.active {
  opacity: 0.9;
}
.timeline-milestone-line {
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
.timeline-milestone-label {
  font-size: 10px;
  font-weight: 600;
  fill: var(--text);
  text-anchor: middle;
}
.timeline-milestone-sublabel {
  font-size: 9px;
  fill: var(--text-muted);
  text-anchor: middle;
}
.timeline-activity-area {
  fill: var(--accent);
  fill-opacity: 0.18;
  stroke: none;
}
.timeline-activity-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
}

* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background-color: var(--scrollbar-thumb);
  border-radius: 999px;
  border: 2px solid var(--bg);
}
::-webkit-scrollbar-thumb:hover {
  background-color: var(--accent);
}

@media (max-width: 480px) {
  :root { --gutter: 1rem; }
  h1 { font-size: 1.4rem; }
  /* h1.hero-title (index only) is more specific than the plain h1 rule
     above, so it wouldn't otherwise shrink on mobile -- left at 2.5rem, a
     long title like "Classement général" wraps to 2 lines, which breaks
     the fixed 3rem line-height's "same header height on every page" trick
     (2 wrapped lines = 6rem, not 3rem) and shows up as the frise jumping
     when navigating to/from a season page. */
  h1.hero-title { font-size: 1.6rem; }
  table, th, td { font-size: 0.75rem; }
  th, td { padding: 0.25rem 0.4rem; }
  /* The toggle's longer label ("Joueurs actifs seulement") is wide enough
     to crowd into the shrunk mobile breadcrumb/guild-name label otherwise --
     shrink the label itself rather than the position math above (see
     #header-extra), which stays valid at any font-size since it's driven by
     line-height/margin, not text size. Applies to both toggles (theme +
     roster) since they share the same stacked #header-extra column. */
  #header-extra .toggle { font-size: 0.65rem; gap: 0.4rem; }
  .timeline-player-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
  }
}
