/* ============================================================
   Westenergie – Highscore Leaderboard
   ------------------------------------------------------------
   Colour use follows "Quick-win Guideline – Multicolour digital"
   (Westenergie, 27.09.2024). See design/ in the repo root.

   The page is light; the colour sits in the top three, which use
   three distinct grounds with white type, and carry the rank numeral
   and star in the contrast colour the icon sheet defines for each.

   Rules enforced here:
     • jede Kombination enthält mind. einen Bright-Ton (MYB)
     • weisse Schrift nur auf Tönen der "uneingeschränkt"-Gruppe;
       PPM, FFM and IIM all qualify, so white type is valid at every
       size. MYB is a mark colour only and never sits behind text —
       white on MYB measures 2.19:1 and is excluded outright.
     • DEVIATION: four Markenfarben instead of the maximum of three.
       See the token block; this is a deliberate, flagged exception
       that needs client sign-off, not an oversight.
   White, AA5 and anthracite are neutrals, not Markenfarben; the
   wordmark yellow is part of the logo only.
   ============================================================ */

/* --- corporate typeface ----------------------------------------------
   innogyWEB, the Westenergie corporate font (innogy_FONT_web_v2),
   self-hosted so display clients need no network access.

   Licence (fonts/LEGAL-NOTE.txt): may only be used for media and
   documents of innogy SE and its subsidiaries. Westenergie is such a
   subsidiary, so this board is covered; third-party use is not.
   Do not reuse these files for MYI's own or any other client's work.

   Per brand guideline the family is used in Light and Regular for
   copy and Medium to highlight. Bold ships in the web kit and is
   used here only for the very large rank numerals. */
@font-face {
    font-family: "innogyWEB";
    src: url('fonts/innogyWEB-Light.woff2') format('woff2');
    font-weight: 300;
    font-style: normal;
    font-display: block;
}
@font-face {
    font-family: "innogyWEB";
    src: url('fonts/innogyWEB-Regular.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: block;
}
@font-face {
    font-family: "innogyWEB";
    src: url('fonts/innogyWEB-Medium.woff2') format('woff2');
    font-weight: 500;
    font-style: normal;
    font-display: block;
}
@font-face {
    font-family: "innogyWEB";
    src: url('fonts/innogyWEB-Bold.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
    font-display: block;
}

/* ============================================================
   1 · Brand tokens
   ============================================================ */
:root {
    /* Multicolour – 7 Farbtöne, je Bright + Muted */
    --ppb: #c81e82;  --ppm: #780a5f;   /* Pulsing Purple    */
    --ffb: #e60055;  --ffm: #a50032;   /* Fiery Fuchsia     */
    --rrb: #eb4b0a;  --rrm: #b9280a;   /* Radiant Red       */
    --myb: #f59b00;  --mym: #d27300;   /* Mellow Yellow     */
    --ggb: #64b42d;  --ggm: #00875a;   /* Galvanic Green    */
    --bbb: #009ba5;  --bbm: #005f69;   /* Blazing Blue      */
    --iib: #00aae1;  --iim: #143c8c;   /* Iridescent Indigo */

    /* Neutrals */
    --anthracite:    #3c3732;
    --anthracite-55: #94908e;
    --anthracite-25: #d8d7d6;
    --anthracite-5:  #ecebeb;
    --white:         #ffffff;
    --logo-yellow:   #ffc300;

    /* ---- active combination ----------------------------------
       Three distinct grounds for the top three, white text on all of
       them, and the numeral + star in the contrast colour the icon
       sheet defines for that ground:

         Platz 1   PPM #780a5f  ·  Marke MYB   ("MYB auf PPM")
         Platz 2   FFM #a50032  ·  Marke MYB   ("MYB auf FFM")
         Platz 3   IIM #143c8c  ·  Marke MYB   ("MYB auf IIM")

       NOTE — this is FOUR Markenfarben (PPM, FFM, IIM, MYB) and the
       guideline caps a Sichtfeld at three. Every individual pairing
       above is sanctioned; only the count is over. The limit cannot be
       met with three distinct grounds: of the grounds that take white
       type (PPM, FFM, BBM, IIM) all four are Muted, so the mandatory
       Bright tone has to come from the marks and becomes a fourth
       colour. Checked by enumerating all 14 combinations.

       Needs a client waiver. The compliant fallback is two grounds and
       one mark colour — set --c2 to var(--iim) and the board is back
       to three Markenfarben. */
    --c1: var(--ppm);   --i1: var(--myb);   --t1: var(--white);
    --c2: var(--ffm);   --i2: var(--myb);   --t2: var(--white);
    --c3: var(--iim);   --i3: var(--myb);   --t3: var(--white);

    /* surfaces */
    --page:   var(--white);
    --ink:    var(--anthracite);

    /* scale unit — see §2 */
    --s: min(1vh, 0.85vw);

    /* rhythm */
    --swap: 520ms;          /* keep in step with CONFIG.SWAP_MS */
    --gap:  calc(var(--s) * 1.6);
    --pad:  calc(var(--s) * 3.4);
}

/* ============================================================
   2 · Reset + viewport fitting
   ------------------------------------------------------------
   The board must fill the viewport and never scroll — it is used
   on 16:9 and 9:16 displays and in an 800px-tall iframe.
   Everything is sized from --s, which tracks whichever axis is
   the binding constraint for that orientation.
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
    height: 100%;
    overflow: hidden;
    background: var(--page);
    color: var(--ink);
    font-family: "innogyWEB", sans-serif;
    font-weight: 400;
    font-variant-numeric: tabular-nums lining-nums;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

#stage {
    height: 100dvh;
    display: grid;
    /* header, boards fill the rest, ticker pinned to the bottom */
    grid-template-rows: auto minmax(0, 1fr) auto;
    padding: var(--pad);
    gap: calc(var(--s) * 2.2);
}

/* 9:16 and other portrait formats: width becomes the constraint */
@media (orientation: portrait) {
    :root { --s: min(1.45vw, 0.62vh); }
}

/* ============================================================
   3 · Header
   ============================================================ */
#head {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: calc(var(--s) * 3);
    padding-bottom: calc(var(--s) * 1.8);
    border-bottom: max(1px, calc(var(--s) * 0.12)) solid var(--anthracite-25);
}

.head-brand {
    display: flex;
    align-items: center;
    gap: calc(var(--s) * 2.2);
    min-width: 0;
}
/* claim device, original asset — sized off the logo, never recoloured */
.chatbox {
    display: block;
    height: calc(var(--s) * 4.8);
    width: auto;
    flex: none;
}

/* Original SVG, never recoloured. Clear space per guideline is
   1/5 of the logo width on every side; the header's own padding
   supplies the rest, this reserves the horizontal share. */
.wordmark {
    display: block;
    height: calc(var(--s) * 3.4);
    width: auto;
    flex: none;
    padding-inline: calc(var(--s) * 0.9);
}

/* Event title, set as on the key visual: Medium, mixed case, no
   letterspacing. The rotating list name sits underneath it. */
.head-title { text-align: center; min-width: 0; }
.head-title h1 {
    font-size: calc(var(--s) * 3.4);
    font-weight: 500;
    letter-spacing: -0.01em;
    line-height: 1;
    color: var(--ink);
    white-space: nowrap;
}
.head-sub {
    font-size: calc(var(--s) * 1.8);
    font-weight: 300;
    color: var(--anthracite-55);
    letter-spacing: 0.04em;
    margin-top: calc(var(--s) * 0.7);
    white-space: nowrap;
}

.head-live {
    display: flex;
    align-items: center;
    gap: calc(var(--s) * 1.2);
    font-size: calc(var(--s) * 1.6);
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--anthracite-55);
    white-space: nowrap;
}

/* which of the two rotating lists is on screen */
.view-dots {
    display: flex;
    align-items: center;
    gap: calc(var(--s) * 0.7);
}
.view-dot {
    width:  calc(var(--s) * 1.1);
    height: calc(var(--s) * 1.1);
    border-radius: 50%;
    background: var(--anthracite-25);
    transition: background-color 300ms ease;
}
.view-dot[data-active="true"] {
    background: var(--c1);
    animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse {
    0%, 100% { opacity: 1;    transform: scale(1); }
    50%      { opacity: 0.35; transform: scale(0.82); }
}
/* stale connection */
#stage[data-stale="1"] .view-dot { background: var(--anthracite-25); animation: none; }
#stage[data-stale="1"] .live-label { color: var(--anthracite-25); }

/* ============================================================
   4 · Board grid
   ------------------------------------------------------------
   Landscape (16:9 · 1400×800 iframe):   hero left, list right
   Portrait  (9:16):                     hero top, list below
   ============================================================ */
/* Viewport for the two boards. Both are always present and always
   current; only which one is on screen changes. overflow:hidden clips
   the one sliding out. */
#boards {
    position: relative;
    min-height: 0;
    overflow: hidden;
}

.board {
    position: absolute;
    inset: 0;
    min-height: 0;
    display: grid;
    gap: var(--gap);
    /* podium band over a full-width ledger */
    grid-template-columns: 1.55fr 2fr;
    grid-template-rows: minmax(0, 34fr) minmax(0, 46fr);
    grid-template-areas:
        "hero   podium"
        "ledger ledger";
}

@media (orientation: portrait) {
    .board {
        grid-template-columns: 1fr;
        grid-template-rows: minmax(0, 23fr) minmax(0, 13fr) minmax(0, 46fr);
        grid-template-areas:
            "hero"
            "podium"
            "ledger";
    }
}

/* ---------- Platz 1 – Farbkachel ---------- */
.hero {
    grid-area: hero;
    position: relative;
    overflow: hidden;
    background: var(--c1);
    color: var(--t1);
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: calc(var(--s) * 2.8);
    min-height: 0;
}

/* The rank numeral: in the accent colour and set fully inside the
   panel. Both points came straight from the client — the numerals
   were previously a white tint (not a contrast colour) and were
   deliberately cropped by the panel edge. */
.hero-rank {
    position: absolute;
    right: calc(var(--s) * 2.8);
    top:   calc(var(--s) * 1.6);
    font-size: calc(var(--s) * 17);
    font-weight: 700;
    line-height: 0.92;
    color: var(--i1);
    pointer-events: none;
    user-select: none;
}

.hero-body {
    position: relative;
    margin-top: auto;
    min-width: 0;
}
.hero-eyebrow {
    font-size: calc(var(--s) * 1.6);
    font-weight: 500;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--i1);
}
.hero-name {
    font-size: calc(var(--s) * 5.4);
    font-weight: 600;
    line-height: 1.02;
    letter-spacing: -0.02em;
    margin-top: calc(var(--s) * 1.2);
    /* two lines max, then ellipsis */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}
/* Standort under the team name (teams list), or the team count
   that puts a winrate in context (locations list). */
.hero-meta {
    font-size: calc(var(--s) * 2);
    font-weight: 300;
    letter-spacing: 0.01em;
    margin-top: calc(var(--s) * 0.7);
    opacity: 0.85;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.hero-meta:empty { display: none; }

.hero-score {
    display: flex;
    align-items: baseline;
    gap: calc(var(--s) * 1);
    margin-top: calc(var(--s) * 1.4);
}
.hero-score .ico {
    width:  calc(var(--s) * 4.4);
    height: calc(var(--s) * 4.4);
    align-self: center;
    color: var(--i1);
    margin-right: calc(var(--s) * -0.2);
}
/* the star is the unit for "Erreichte Sterne"; the locations list is a
   percentage, so it carries no pictogram */
.board[data-list="locations"] .ico { display: none; }

.hero-score .num {
    font-size: calc(var(--s) * 6.6);
    font-weight: 300;
    line-height: 0.9;
    letter-spacing: -0.03em;
}
.hero-score .unit {
    font-size: calc(var(--s) * 1.7);
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    opacity: 0.85;
}
/* the team's own winrate, secondary to the star count */
.hero-rate {
    font-size: calc(var(--s) * 1.7);
    font-weight: 400;
    letter-spacing: 0.06em;
    opacity: 0.85;
    margin-left: calc(var(--s) * 1.6);
    padding-left: calc(var(--s) * 1.8);
    border-left: max(1px, calc(var(--s) * 0.1)) solid rgba(255, 255, 255, 0.4);
    white-space: nowrap;
}
.hero-rate:empty { display: none; }

@media (orientation: portrait) {
    .hero-rank { font-size: calc(var(--s) * 15); }
}

/* ---------- Plätze 2 + 3 – Farbkacheln ---------- */
.podium {
    grid-area: podium;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--gap);
}

/* Same composition as the hero, one step down in scale:
   faded numeral cropped top-right, name + score bottom-left. */
.tile {
    position: relative;
    overflow: hidden;
    color: var(--ink);
    padding: calc(var(--s) * 2.2);
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: 0;
}
.tile[data-rank="2"] { background: var(--c2); color: var(--t2); }
.tile[data-rank="3"] { background: var(--c3); color: var(--t3); }

/* as the hero numeral: accent colour, fully inside the tile */
.tile-rank {
    position: absolute;
    right: calc(var(--s) * 2);
    top:   calc(var(--s) * 1.2);
    font-size: calc(var(--s) * 10);
    font-weight: 700;
    line-height: 0.92;
    pointer-events: none;
    user-select: none;
}
.tile[data-rank="2"] .tile-rank,
.tile[data-rank="2"] .tile-score .ico { color: var(--i2); }
.tile[data-rank="3"] .tile-rank,
.tile[data-rank="3"] .tile-score .ico { color: var(--i3); }

.tile-name {
    position: relative;
    padding-right: calc(var(--s) * 5);
    font-size: calc(var(--s) * 2.9);
    font-weight: 600;
    line-height: 1.08;
    letter-spacing: -0.015em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tile-meta {
    position: relative;
    font-size: calc(var(--s) * 1.7);
    font-weight: 300;
    line-height: 1.2;
    margin-top: calc(var(--s) * 0.3);
    opacity: 0.85;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tile-meta:empty { display: none; }

.tile-score {
    position: relative;
    display: flex;
    align-items: center;
    gap: calc(var(--s) * 0.8);
    font-size: calc(var(--s) * 3.4);
    font-weight: 300;
    line-height: 1.1;
    letter-spacing: -0.02em;
    margin-top: calc(var(--s) * 0.5);
}
.tile-score .ico {
    width:  calc(var(--s) * 2.7);
    height: calc(var(--s) * 2.7);
    flex: none;
}
.tile-rate {
    font-size: calc(var(--s) * 1.7);
    font-weight: 400;
    letter-spacing: 0.04em;
    opacity: 0.85;
    margin-left: calc(var(--s) * 0.6);
    padding-left: calc(var(--s) * 1.2);
    border-left: max(1px, calc(var(--s) * 0.1)) solid rgba(255, 255, 255, 0.4);
    white-space: nowrap;
}
.tile-rate:empty { display: none; }

/* ---------- Plätze 4+ – Ledger ---------- */
.ledger {
    grid-area: ledger;
    list-style: none;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.row {
    /* fixed share of the ledger height, so row rhythm stays identical
       whether the board is full or half empty */
    flex: 1 1 0;
    min-height: 0;
    background: var(--anthracite-5);
    display: grid;
    /* rank | name + Standort | winrate | stars — the winrate column is
       empty on the locations list, where the winrate *is* the score */
    grid-template-columns: calc(var(--s) * 6) minmax(0, 1fr) auto calc(var(--s) * 7);
    align-items: center;
    gap: calc(var(--s) * 1.6);
    padding: 0 calc(var(--s) * 2.2);
    border-top: max(1px, calc(var(--s) * 0.1)) solid var(--anthracite-25);
    color: var(--ink);
}
.row:first-child { border-top: 0; }

/* locations list: the winrate is the score, so the extra column goes away
   entirely rather than collapsing to a stray gap */
.board[data-list="locations"] .row {
    grid-template-columns: calc(var(--s) * 6) minmax(0, 1fr) auto;
}
.board[data-list="locations"] .row-rate { display: none; }

.row-rank {
    font-size: calc(var(--s) * 2.4);
    font-weight: 500;
    color: var(--iim);
    text-align: right;
    font-variant-numeric: tabular-nums;
}
/* name plus its Standort / team-count, on one baseline */
.row-main {
    display: flex;
    align-items: baseline;
    gap: calc(var(--s) * 1.2);
    min-width: 0;
}
.row-name {
    font-size: calc(var(--s) * 2.4);
    font-weight: 500;
    letter-spacing: -0.005em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 0 1 auto;
}
.row-meta {
    font-size: calc(var(--s) * 1.7);
    font-weight: 300;
    color: var(--anthracite-55);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 0 1 auto;
}
.row-rate {
    font-size: calc(var(--s) * 1.9);
    font-weight: 300;
    color: var(--anthracite-55);
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.row-score {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: calc(var(--s) * 0.7);
    font-size: calc(var(--s) * 2.4);
    font-weight: 300;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.row-score .ico {
    width:  calc(var(--s) * 1.9);
    height: calc(var(--s) * 1.9);
    flex: none;
    color: var(--anthracite-55);
}

/* Empty slots hold their space so the row rhythm never shifts, but
   leave no visible surface behind — a short board just ends.

   visibility, not opacity: the "rise" entrance animation animates
   opacity, and an animation's value outranks a normal declaration in
   the cascade, so opacity:0 here was silently ignored and the star
   pictogram stayed visible on every empty row. */
.row[data-empty="1"] {
    background: transparent;
    border-top-color: transparent;
    visibility: hidden;
}

/* ============================================================
   6 · Motion
   ------------------------------------------------------------
   One orchestrated reveal on load; a FLIP transition when the
   standings change (see script.js). Flat brand surfaces, so
   movement carries the feedback rather than glow or shadow.
   ============================================================ */
/* The staggered reveal is a one-off on load. It is scoped to .is-booting,
   which script.js drops once the sequence has run, so nothing here can
   re-trigger later. Two reasons that matters: a row flipping from empty
   to filled changes properties that would restart its animation, and a
   list swap would otherwise replay the whole cascade element by element
   instead of reading as one board changing. */
#stage.is-booting .hero,
#stage.is-booting .tile,
#stage.is-booting .row { animation: rise 620ms cubic-bezier(0.22, 1, 0.36, 1) both; }
#stage.is-booting .hero                { animation-delay: 40ms; }
#stage.is-booting .tile[data-rank="2"] { animation-delay: 120ms; }
#stage.is-booting .tile[data-rank="3"] { animation-delay: 180ms; }
#stage.is-booting .row:nth-child(1) { animation-delay: 240ms; }
#stage.is-booting .row:nth-child(2) { animation-delay: 285ms; }
#stage.is-booting .row:nth-child(3) { animation-delay: 330ms; }
#stage.is-booting .row:nth-child(4) { animation-delay: 375ms; }
#stage.is-booting .row:nth-child(5) { animation-delay: 420ms; }
#stage.is-booting .row:nth-child(6) { animation-delay: 465ms; }
#stage.is-booting .row:nth-child(7) { animation-delay: 510ms; }

@keyframes rise {
    from { opacity: 0; transform: translateY(calc(var(--s) * 1.6)); }
    to   { opacity: 1; transform: none; }
}

/* score/name just changed */
.is-updated { animation: mark 900ms ease-out; }
@keyframes mark {
    0%   { background-color: var(--anthracite-25); }
    100% { background-color: transparent; }
}

/* FLIP reorder — script sets the inverted transform, then clears it */
.is-moving { transition: transform 520ms cubic-bezier(0.22, 1, 0.36, 1); }

/* Switching lists moves whole boards, never their contents. Both are
   drawn and up to date at all times, so this is a compositor-only
   transform+opacity change — no layout, no repaint of any row, and
   nothing can pop in element by element. */
.board {
    transition: transform var(--swap) cubic-bezier(0.4, 0, 0.2, 1),
                opacity   var(--swap) cubic-bezier(0.4, 0, 0.2, 1);
    will-change: transform, opacity;
}
.board[data-state="active"] { transform: translateX(0);    opacity: 1; }
.board[data-state="before"] { transform: translateX(-12%); opacity: 0; }
.board[data-state="after"]  { transform: translateX(12%);  opacity: 0; }

/* the board that is off screen must not be clickable or focusable */
.board:not([data-state="active"]) { pointer-events: none; }

/* used to place the first board without animating it in */
#boards.no-anim .board { transition: none; }

@media (prefers-reduced-motion: reduce) {
    #stage.is-booting .hero,
    #stage.is-booting .tile,
    #stage.is-booting .row,
    .is-updated { animation: none; }
    .is-moving { transition: none; }
    .view-dot[data-active="true"] { animation: none; }
    .board { transition: none; }
}

/* ============================================================
   7 · Ticker
   ------------------------------------------------------------
   Bottom rule that fills over one rotation period, so the room can
   see how long the current list stays up. Driven by the Web Animations
   API from script.js and restarted on every switch, so it cannot drift
   away from the interval that actually rotates the boards.
   ============================================================ */
#ticker {
    height: max(5px, calc(var(--s) * 0.5));
    background: var(--anthracite-25);
    overflow: hidden;
}
#ticker[hidden] { display: none; }

#ticker-bar {
    display: block;
    height: 100%;
    width: 100%;
    background: var(--i1);
    transform: scaleX(0);
    transform-origin: left center;
}

@media (prefers-reduced-motion: reduce) {
    #ticker { display: none; }
}
