/* ==========================================================================
   Face TOP9 Tournament -- style.css
   Vanilla CSS, no framework, no build step.
   ========================================================================== */

/* ---- design tokens ----------------------------------------------------
 *
 * ONE PALETTE, THE SAME FOR EVERYONE.
 *
 *   This started as a dark theme, with a light variant behind
 *   `prefers-color-scheme: light`. The pink theme was then added the same way
 *   -- as a variant -- which meant it only appeared on a machine whose OS was
 *   set to light. The owner's PC is set to DARK, so the site they asked for
 *   rendered black, and every screenshot came back black.
 *
 *   There is now no media query at all. The reference site this project
 *   follows is pink for every visitor and has no dark mode, and matching that
 *   is the requirement. A dark variant can be reintroduced behind an explicit
 *   toggle if it is ever wanted, but it must not be driven by the OS setting,
 *   because "looks black on my machine and pink on yours" is not a theme.
 *
 * Palette taken from the reference's :root block.
 * ---------------------------------------------------------------------- */
:root {
  --bg:          #fff5f8;
  --bg-elev:     #ffffff;
  --bg-elev-2:   #ffeef4;
  --line:        #f1d4de;
  --line-strong: #e8bccd;

  --text:        #2b1a22;
  --text-muted:  #8a7580;
  --text-dim:    #a8969e;

  --accent:      #e8437a;
  --accent-soft: #ff8fb3;
  --accent-tint: #ffe0ea;           /* the reference's --accent-soft */

  --gold:        #e8a020;
  --silver:      #9aa3b8;
  --bronze:      #c07a3e;
  --ok:          #1f9d63;
  --danger:      #d63b3b;

  --shadow:      0 4px 16px rgba(232, 67, 122, .12);
  --shadow-soft: 0 2px 8px rgba(232, 67, 122, .08);

  --radius:      12px;
  --radius-sm:   8px;
  --radius-lg:   16px;

  --tap:           44px;               /* minimum touch target */
  --safe-b:        env(safe-area-inset-bottom, 0px);

  /* Hong Kong fonts FIRST.
   *
   * This list began as the Japanese reference site's, including "Noto Sans JP".
   * A JP font renders Chinese characters with Japanese glyph variants, which is
   * not merely a preference: several characters look visibly different (直, 骨,
   * 今 all differ between the two standards), and a Hong Kong reader notices.
   *
   * "Hiragino Sans" is kept as a late fallback rather than removed, because on
   * a Mac without the HK faces it is still better than a generic sans. */
  --font: "PingFang HK", "PingFang TC", "Noto Sans HK", "Noto Sans TC",
          "Microsoft JhengHei", "Hiragino Sans", system-ui, -apple-system,
          "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* 100dvh tracks iOS Safari's collapsing toolbars. The vh fallback is for
     older browsers; dvh is ignored where it is unsupported. */
  min-height: 100vh;
  min-height: 100dvh;
  font-family: var(--font);
  background:
    radial-gradient(1100px 620px at 50% -12%, var(--accent-tint), transparent 62%),
    var(--bg);
  color: var(--text);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: contain;
  /* iOS otherwise inflates body text in landscape, which reflows the bars. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

img { display: block; max-width: 100%; }
button { font: inherit; color: inherit; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.skip-link {
  position: absolute; left: -9999px; top: 0;
  padding: 10px 16px; background: var(--accent); color: #fff;
  border-radius: 0 0 var(--radius-sm) 0; z-index: 999;
}
.skip-link:focus { left: 0; }

/* ---- topbar -----------------------------------------------------------
   `position: sticky` rather than `fixed`, so it participates in layout and
   cannot overlap the first screen's content. On iOS the document scrolls the
   whole page, and a sticky element stays put without the drift a fixed one
   shows when Safari's toolbars collapse.
------------------------------------------------------------------------ */
/*
 * THE HEADER SCROLLS AWAY. IT IS NOT STICKY.
 *
 * It was `position: sticky; top: 0`, so the wordmark and the two buttons stayed
 * pinned over the grid while the player scrolled -- a 66px band of chrome
 * permanently covering the top of every screen, on a device where the whole
 * viewport is 852px.
 *
 * The reference site does not do this. It has NO sticky or fixed header at all:
 * only its bottom bar is fixed. Scrolling up there shows the question, then
 * three clean rows of nine faces with nothing above them.
 *
 * THE BACKGROUND AND BORDER ARE STILL NEEDED, but for the top of the page
 * rather than for an overlap: the header sits on the same pink as the page, and
 * the border marks where the content starts.
 *
 * env(safe-area-inset-top) stays: on a notched phone the header is now the
 * first thing on the page and must clear the status bar.
 */
.topbar {
  position: static;
  padding-top: env(safe-area-inset-top, 0px);
  /* NO color-mix() FOR BACKGROUNDS.
   *
   *   color-mix() voids the entire declaration in a browser that does not
   *   support it -- there is no fallback, the property is simply dropped. On
   *   iOS Safari before 16.2 (which includes some iOS Edge builds) that left
   *   the sticky bars with NO background at all, so the cards scrolled visibly
   *   through them.
   *
   *   The value below is the same colour at the same alpha, written out.
   *
   * NO backdrop-filter EITHER. The reference's bars have none -- just an opaque
   * rgba() background -- and on iOS a blurred element can be painted into its
   * own layer, which is a known source of bars that look detached or let
   * content show through. */
  background: rgba(255, 245, 248, .92);
  border-bottom: 1px solid var(--line);
}

.topbar__inner {
  /* Matches .app so the wordmark lines up with the content below it. These two
     must move together -- a mismatch shows up as the logo sitting further left
     than the first card.
   *
   * COMPACT ON PURPOSE. Measured on an iPhone 15 Pro (852px tall), the stack
   * above the first card used to be 256px -- 30% of the screen -- against 196px
   * on the reference site. The padding here was 12px/10px; the wordmark and its
   * buttons also carry their own padding, so the row came to 58px for content
   * that is about 30px tall. */
  max-width: 560px; margin: 0 auto;
  padding: 6px clamp(12px, 4vw, 24px);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}

/* The wordmark takes the leftover space and may shrink; the buttons keep their
   size. Without min-width:0 on the h1 the wordmark refuses to shrink and pushes
   the buttons off-screen on a narrow phone. */
.topbar__inner > .brand { flex: 0 1 auto; }
.topbar__meta { flex: 0 0 auto; }

/* The site wordmark: "9PICK", inside a single rounded box.
 *
 * It used to be two separate pills ("明記" and "餸 TOP9") driven by the
 * competition, which meant the box changed size and could wrap when the user
 * switched. The site now has a fixed name and the competition lives in its own
 * picker row below, so this is static and the same everywhere. */
.brand {
  margin: 0;
  min-width: 0;
  white-space: nowrap;
}

.brand__btn {
  appearance: none;
  margin: 0;
  /* Compact: this box is decoration around a 4-character wordmark, and every
     pixel here is a pixel the first row of cards is pushed down. Measured on an
     iPhone 15 Pro, 3px vertical padding keeps the single box at ~30px tall. */
  padding: 3px 10px;
  border: 2px solid var(--line-strong);
  border-radius: 10px;
  background: var(--bg-elev);
  font: inherit; font-weight: inherit; letter-spacing: inherit;
  color: inherit; cursor: pointer;
  display: inline-flex; align-items: center;
  transition: border-color .15s ease, background .15s ease;
}
.brand__btn:hover { border-color: var(--accent); }
.brand__btn:active { transform: translateY(1px); }

.brand__mark {
  font-size: clamp(1rem, 3.4vw, 1.2rem);
  font-weight: 900;
  letter-spacing: -.01em;
  line-height: 1.15;
  /* Reset the gradient rule below -- only the accent half is tinted. */
  background: none;
  -webkit-background-clip: border-box; background-clip: border-box;
  color: var(--text);
}

.brand__accent {
  background: linear-gradient(92deg, var(--accent), var(--gold));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

.topbar__meta { display: flex; align-items: center; gap: 8px; }

/* The .badge and .badge--muted rules were removed with the category badge they
   styled. Nothing else in the markup or the server-rendered pages uses either
   class, so leaving them would only invite a future element to look like a
   control that no longer exists. */

/*
 * The progress bar lives INSIDE the action bar.
 *
 * WHY IT WRAPS TO ITS OWN LINE
 *   The action bar is a flex row holding the counter and the button. The bar
 *   needs the full width, so it takes `flex: 0 0 100%`, which forces a wrap.
 *   The container must therefore allow wrapping -- and its children must not be
 *   squeezed by the full-width item sharing their line.
 *
 *   Without `align-items: center` and a shrinkable counter, the counter was
 *   compressed to 14px wide and stretched to 131px tall, making the whole bar
 *   156px: a quarter of an 852px screen spent on a button and a 3px rule.
 *
 * The negative margins cancel the padding of .actionbar__inner so the bar spans
 * the full width; the bottom margin separates it from the row beneath.
 */
.progress {
  flex: 0 0 100%;
  width: auto;
  height: 3px;
  margin: -6px calc(-1 * clamp(12px, 4vw, 24px)) 2px;
  background: var(--bg-elev-2);
  overflow: hidden;
  border-radius: 999px;
}

/* Let the flex row wrap, so the full-width bar sits on its own line. */
.actionbar__inner { flex-wrap: wrap; }
/* And keep the counter and button on one line, centred, without being crushed
   by the bar that shares the container. */
.actionbar__inner > .counter { flex: 0 1 auto; min-width: 0; white-space: nowrap; }
.progress__fill {
  height: 100%; width: 0%;
  background: linear-gradient(90deg, var(--accent), var(--gold));
  transition: width .35s cubic-bezier(.4, 0, .2, 1);
}

/* ---- layout ----------------------------------------------------------- */
/* The app is a centred column of fixed maximum width, NOT a full-bleed
   responsive layout. This is what keeps the game looking identical on a phone
   and on a desktop: three face cards per row, sized for judging a face,
   rather than nine cards stretched across a wide monitor.
   
   560px matches the reference implementation. At this width the three pick
   cards land around 165px each on a desktop -- comfortably large enough to
   judge a face -- and the column still fills a phone screen edge to edge.
   
   1400px was the original value, and it is why the prelim round fell apart on
   a desktop -- there was simply too much horizontal room for a 3-column grid
   of faces, so the grid (then auto-filling) kept adding columns until it ran
   out of faces to add, ending with nine ~113px cards in one truncated row. */
.app {
  max-width: 560px; margin: 0 auto;
  /* The top padding is what separates the sticky header from the heading, so
     it is kept small: every pixel here pushes the first card down. */
  padding: clamp(8px, 2vw, 16px) clamp(10px, 2.5vw, 20px);
  outline: none;
}

/* The statistics screen is the one exception. Its rows carry a face, a rank,
   a name, four numeric columns and a sort control -- information density, not
   faces, so the 560px cap would only force wrapping without any benefit. */
.app--wide { max-width: 900px; }

.screen[hidden] { display: none !important; }

.screen--center {
  min-height: 58dvh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center; gap: 12px;
}

/* ---- home screen ------------------------------------------------------ */
/*
 * THE MODE LIST, COPIED FROM THE REFERENCE SITE.
 *
 *   Its shape is a full-width pill per mode: the name on the left, the size and
 *   time on the right, a 2px accent border, and 999px radius. The FIRST entry is
 *   filled solid, which marks it as the default choice without a separate
 *   "recommended" label.
 *
 *   The reason this reads well is the two-column layout: the eye can compare
 *   "225 · about 6 min" against "64 · about 4 min" down a single right-hand
 *   edge, instead of reading two stacked lines per card.
 */

.home__head { margin: 4px 0 14px; }
.home__title {
  margin: 0 0 6px;
  font-size: clamp(1.3rem, 5vw, 1.7rem); font-weight: 900; letter-spacing: -.02em;
}
.home__lede { margin: 0; color: var(--text-muted); font-size: .92rem; }

.home__label {
  margin: 0 0 8px; text-align: center;
  font-size: .8rem; font-weight: 700; color: var(--text-muted);
}

.home__modes { display: grid; gap: 10px; margin-bottom: 14px; }

.modecard {
  appearance: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 14px 20px;
  border: 2px solid var(--accent);
  border-radius: 999px;
  background: var(--bg-elev);
  color: var(--accent);
  font: inherit;
  transition: background .15s ease, color .15s ease;
}
.modecard:hover { background: var(--accent-soft); }
.modecard:active { transform: translateY(1px); }
.modecard:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.modecard__name { font-size: 1.05rem; font-weight: 800; white-space: nowrap; }
.modecard__meta { font-size: .8rem; color: var(--text-muted); white-space: nowrap; }

/* The first playable competition is filled, matching the reference's default
   mode. Purely a pointer to the more popular option -- it changes nothing about
   how the game plays. */
.modecard--primary { background: var(--accent); color: #fff; }
.modecard--primary .modecard__meta { color: rgba(255, 255, 255, .85); }
.modecard--primary:hover { background: var(--accent); }

/* Announced but not playable: dashed and amber, again as the reference does for
   the mode that is not a normal mode. It must not look like a start button. */
.modecard--soon {
  border-style: dashed; border-color: #f0a030;
  background: #fffaf0; color: #d9800f;
}
.modecard--soon:hover { background: #fff3e0; }
.modecard--soon .modecard__meta { color: #c98a2e; }

.modecard__tag {
  display: inline-block; margin-right: 6px;
  padding: 1px 6px; border-radius: 4px;
  background: #f0a030; color: #fff;
  font-size: .68rem; font-weight: 800; vertical-align: 2px;
}

/* The signup state: the card becomes a panel rather than a button. */
.modecard--form {
  display: block; cursor: default;
  border-style: solid; border-color: var(--line-strong);
  background: var(--bg-elev); color: var(--text);
  border-radius: var(--radius);
}
.modecard--form:hover { background: var(--bg-elev); }
.modecard__note { margin-top: 6px; color: var(--text-muted); font-size: .85rem; }

.waitlist { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.waitlist__input {
  flex: 1 1 150px; min-width: 0;
  font: inherit; padding: 8px 14px;
  border: 2px solid var(--line-strong); border-radius: 999px;
  background: var(--bg);
}
.waitlist__input:focus { outline: none; border-color: var(--accent); }
.waitlist__input:disabled { opacity: .6; }

.waitlist__msg { flex: 1 1 100%; margin: 2px 0 0; font-size: .85rem; }
.waitlist__msg--ok { color: var(--accent); font-weight: 700; }
.waitlist__msg--err { color: #c0392b; font-weight: 700; }

.home__rules {
  margin: 0 0 12px; padding: 12px 16px 12px 34px;
  background: var(--bg-elev-2); border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--text-muted); font-size: .86rem;
  display: flex; flex-direction: column; gap: 5px;
}
.home__rules b { color: var(--text); }

.home__note { margin: 0; text-align: center; color: var(--text-dim); font-size: .8rem; }

/* The heading block above the grid, sized down to free vertical room.
 *
 * Measured on an iPhone 15 Pro (852px): the reference site puts its first card
 * at 196px from the top; ours was at 256px. The title and hint alone cost
 * 107px, and the hint is a sentence that most players read once. */
.screen__head { margin-bottom: clamp(10px, 2vw, 16px); }

.phase-kicker {
  margin: 0 0 3px; font-size: .72rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--accent);
}

.screen__title {
  margin: 0 0 3px;
  font-size: clamp(1.1rem, 3.9vw, 1.4rem);
  font-weight: 800; letter-spacing: -.02em; line-height: 1.25;
}

.screen__hint {
  margin: 0; color: var(--text-muted);
  font-size: .82rem; line-height: 1.4; max-width: 62ch;
}
.muted { color: var(--text-muted); }

code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: .88em; background: var(--bg-elev-2);
  padding: 2px 6px; border-radius: 5px; border: 1px solid var(--line);
}

/* ---- buttons ---------------------------------------------------------- */
.btn {
  appearance: none; cursor: pointer;
  min-height: var(--tap); padding: 11px 22px;
  border-radius: 999px; border: 1px solid var(--line-strong);
  background: var(--bg-elev-2); color: var(--text);
  font-weight: 700; font-size: .94rem;
  transition: transform .12s ease, background .18s ease, border-color .18s ease, opacity .18s ease;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.btn:hover:not(:disabled) { background: var(--line); transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0) scale(.985); }
.btn:disabled { opacity: .42; cursor: not-allowed; }

.btn--primary {
  background: linear-gradient(94deg, var(--accent), #ff7a5c);
  border-color: transparent; color: #fff;
  box-shadow: 0 6px 18px rgba(255, 77, 125, .3);
}
.btn--primary:hover:not(:disabled) { background: linear-gradient(94deg, #ff3d70, #ff6a4a); }

.btn--ghost { background: transparent; border-color: var(--line-strong); color: var(--text-muted); }
.btn--ghost:hover:not(:disabled) { color: var(--text); }

.btn--sm { min-height: 34px; padding: 6px 14px; font-size: .82rem; }

/* ---- grids ------------------------------------------------------------
   The pick and rank grids are ALWAYS three columns, and the app column they
   live in is capped at a phone-ish width. That combination is what makes the
   layout read as a grid of faces rather than a scatter of cards.

   WHY NOT auto-fill
   The grid used to be
       repeat(auto-fill, minmax(110px, 1fr))
   with no upper bound. `auto-fill` keeps adding columns until the cards hit
   the floor width, so a maximised desktop window laid all NINE prelim faces in
   a single row of ~113px cards, truncated every name, and left the rest of the
   page empty. The intent behind auto-fill ("a wide screen shows more faces
   rather than the same three at triple the size") is reasonable for a dense
   contact sheet, but it is wrong for this game: the player is comparing
   FACES, and a face needs enough pixels to judge.

   The reference implementation solves it the other way round -- a fixed
   3-column grid inside a `max-width: 560px` app column -- so the game looks the
   same on a phone and on a 27" monitor, just centred. That is the behaviour
   here too. The card is square (aspect-ratio 1) so three of them form a tidy
   block, not three letterboxed strips.

   The tests missed the original bug because every browser check ran at phone
   widths (320-430px), where auto-fill happens to resolve to exactly 3 columns.
------------------------------------------------------------------------ */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(6px, 1.6vw, 10px);
}

/* Two-up: the 1v1 final and a few other paired screens. */
.grid--two { grid-template-columns: repeat(2, 1fr); }

/* The ranking grid compares only four faces at a time, so it stays 2-up on
   narrow screens and 3-up once there is room. */
.grid--rank { grid-template-columns: repeat(2, 1fr); }

@media (min-width: 560px) {
  .grid--rank { grid-template-columns: repeat(3, 1fr); }
}

/* A very narrow screen cannot fit three comfortable cards. Keep the 3-column
   shape (which is the whole point) but tighten the gap and rely on the card
   shrinking; `1fr` tracks shrink to fit, so no overflow is possible. */
@media (max-width: 360px) {
  .grid { gap: 5px; }
}

/* candidate card */
.card {
  position: relative; display: block; width: 100%; padding: 0;
  border: 2px solid var(--line); border-radius: var(--radius);
  background: var(--bg-elev); overflow: hidden; cursor: pointer;
  text-align: left;
  transition: transform .14s ease, border-color .18s ease, box-shadow .18s ease;
  -webkit-tap-highlight-color: transparent;
}
.card:hover { transform: translateY(-3px); border-color: var(--line-strong); box-shadow: var(--shadow-soft); }
.card:active { transform: translateY(-1px) scale(.99); }

/* ---- card media -------------------------------------------------------
   SQUARE (1:1), not 4:5.

   A 4:5 portrait box made every card 25% taller than it was wide, which at
   three columns turned the grid into a column of tall strips and pushed the
   third row off the fold on a phone. The reference layout uses a square media
   box, and the difference is visible immediately: the cards read as tiles in a
   grid rather than as portraits standing side by side.

   Square also matches the SHARE BOARD, which crops each cell to a square. With
   both square, what the player picks is framed the same way as what they share.

   Keep faces in the top half: `object-position: center top` anchors the crop at
   the top, so the lower part of a tall source photo is what gets dropped.
------------------------------------------------------------------------ */
.card__media {
  position: relative; aspect-ratio: 1 / 1; overflow: hidden;
  background: var(--bg-elev-2);
}
.card__img { width: 100%; height: 100%; object-fit: cover; object-position: center top; }

/* THE TEXT BLOCK IS DELIBERATELY TIGHT.
 *
 * Measured before this change, on a 393px phone:
 *
 *   one-line name  ->  22px of empty space below the street name
 *   two-line name  ->  12px
 *
 * The asymmetry is structural, not a mistake in the padding: the grid uses
 * `grid-auto-rows: 1fr`, so every card in a row is the same height, and a
 * one-line name simply cannot fill the space a two-line name needs. With the
 * roster's MEDIAN name being five characters, about nine cards in ten are
 * one-line -- so the gap was on nearly every card, not on an unlucky few.
 *
 * The clamp stays at two lines (22 of 225 names need it, and truncating them
 * would lose the restaurant's name, which is the one thing a card must say).
 * What is reduced is the padding and the leading around the text. */
.card__body { padding: 6px 8px 7px; }
.card__name {
  margin: 0; font-size: .92rem; font-weight: 700; line-height: 1.25;
  /* Two lines before clipping: three-column grids are narrow, and real names
     are often longer than the placeholder ones. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}
.card__sub {
  margin: 1px 0 0; font-size: .76rem; line-height: 1.2; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* selection state
 *
 * The ring is written twice on purpose. The rgba() line is the fallback: it is
 * the same colour at the same alpha, and a browser that does not understand
 * color-mix() keeps it. Browsers that do understand the second line override
 * it and get the glow derived from --accent, so the ring still follows the
 * theme if the accent ever changes.
 *
 * Selection is a real affordance -- without the ring the player cannot tell
 * what they picked -- so it is not something to leave to a graceful degrade. */
.card[aria-pressed="true"] {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(232, 67, 122, .28), var(--shadow-soft);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent), var(--shadow-soft);
}
.card[aria-pressed="true"] .card__media::after {
  content: ""; position: absolute; inset: 0;
  /* Fallback first, for browsers without color-mix() -- an unsupported value
     voids the declaration, and with nothing behind it the selected card would
     have NO tint at all. */
  background: linear-gradient(180deg, transparent 45%, rgba(232, 67, 122, .55));
  background: linear-gradient(180deg, transparent 45%, color-mix(in srgb, var(--accent) 55%, transparent));
}

.card__tick {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: #fff; font-weight: 800; font-size: .95rem;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .4);
  opacity: 0; transform: scale(.5);
  transition: opacity .16s ease, transform .16s cubic-bezier(.34, 1.56, .64, 1);
}
.card[aria-pressed="true"] .card__tick { opacity: 1; transform: scale(1); }

/* ranking state: 1st / 2nd place */
.grid--rank .card { cursor: pointer; }

.pos-badge {
  position: absolute; top: 8px; left: 8px; z-index: 3;
  min-width: 30px; height: 30px; padding: 0 8px; border-radius: 999px;
  display: none; align-items: center; justify-content: center;
  font-size: .8rem; font-weight: 800; letter-spacing: .02em;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .45);
}
.card[data-pos="1"] .pos-badge { display: inline-flex; background: var(--gold); color: #241a00; }
.card[data-pos="2"] .pos-badge { display: inline-flex; background: var(--silver); color: #1a1f2b; }
.card[data-pos="1"] { border-color: var(--gold); box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 30%, transparent); }
.card[data-pos="2"] { border-color: var(--silver); box-shadow: 0 0 0 3px color-mix(in srgb, var(--silver) 26%, transparent); }

.card[data-pos="1"] .card__media::after,
.card[data-pos="2"] .card__media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 52%, rgba(0, 0, 0, .5));
}

/* elo chip on ranking cards */
.elo {
  display: inline-block; margin-top: 4px;
  font-size: .72rem; font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}

/* ---- locked board ----------------------------------------------------- */
.locked {
  margin-top: 22px; padding: 14px 16px;
  background: var(--bg-elev); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.locked__title {
  margin: 0 0 8px; font-size: .76rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--gold);
}
.locked__list {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.locked__list li {
  display: flex; align-items: center; gap: 7px;
  background: var(--bg-elev-2); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 12px 4px 5px; font-size: .82rem;
}
.locked__rank {
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--line-strong); color: var(--text);
  font-size: .68rem; font-weight: 800;
}
.locked__list img { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; }

/* ---- versus (final 1v1) ----------------------------------------------- */
.versus {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: clamp(8px, 2.5vw, 22px);
  max-width: 780px; margin: 0 auto;
}

.versus .card { cursor: pointer; }
.versus .card:hover { border-color: var(--accent); }

.versus__vs {
  font-size: clamp(1.1rem, 4vw, 1.7rem); font-weight: 900;
  color: var(--text-dim); letter-spacing: .06em;
}

@media (max-width: 560px) {
  .versus { grid-template-columns: 1fr; gap: 10px; }
  .versus__vs { text-align: center; }
}

/* ---- action bar -------------------------------------------------------
   Pinned to the bottom of the VIEWPORT.

   Why a wrapper instead of `position: fixed` on the bar itself:
   `position: fixed` with `left: 0; right: 0; max-width: <any>; margin: auto`
   does NOT centre -- a fixed box is out of flow, so auto margins have nothing
   to distribute against. On a wide screen that is invisible; on a phone it
   pushed the bar sideways and off the bottom.

   The wrapper spans the viewport and the inner element does the centring.
------------------------------------------------------------------------ */
/*
 * THE BAR MUST CLEAR THE BROWSER'S OWN TOOLBAR.
 *
 *   On iOS Safari and Edge the bottom toolbar FLOATS OVER the page instead of
 *   shrinking the viewport, so `bottom: 0` puts the bar underneath it and the
 *   Continue button becomes untappable -- visible in a screenshot, impossible
 *   to press. `env(safe-area-inset-bottom)` does not help: it reserves room for
 *   the home indicator (about 34px), not for the toolbar (60-90px).
 *
 *   `--toolbar-clearance` is measured in app.js from window.innerHeight against
 *   the visual viewport, and is 0 on desktop browsers, where nothing overlaps.
 *   Before this the button was simply unreachable on an iPhone.
 */
.actionbar {
  position: fixed;
  left: 0; right: 0;
  bottom: var(--toolbar-clearance, 0px);
  z-index: 40;
  pointer-events: none;        /* the wrapper is a positioning frame only */
}
.actionbar__inner {
  pointer-events: auto;
  /* Matches .app and .topbar__inner -- all three must share a width or the
     sticky bars drift out of alignment with the grid between them. */
  max-width: 560px;
  margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 12px;
  padding: 12px clamp(12px, 4vw, 24px) calc(12px + var(--safe-b));
  /* NO color-mix() HERE. See the note on .topbar: an unsupported color-mix
     voids the whole declaration, leaving the bar fully transparent, and the
     cards scrolling underneath show straight through it. That is exactly what
     iOS Edge was doing. The plain rgba() below is the same colour with the
     same alpha, computed once, and works everywhere. */
  background: rgba(255, 245, 248, .94);
  border-top: 1px solid var(--line);
}
.actionbar--center .actionbar__inner { justify-content: center; }
.actionbar--wrap .actionbar__inner { flex-wrap: wrap; }

/* The bar's height depends on how many buttons wrap, so the page reserves
   room with a variable the bar updates at runtime (see app.js) rather than a
   hard-coded guess that is wrong on a narrow screen. */
.app {
  /* The bar is lifted by --toolbar-clearance when a browser toolbar overlaps
     it, so the page must reserve that much more or the last row of cards ends
     up behind the bar. */
  padding-bottom: calc(var(--actionbar-h, 96px) + 16px + var(--safe-b) + var(--toolbar-clearance, 0px));
}

.counter {
  margin: 0; font-size: .88rem; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.counter strong { color: var(--text); }

/* ---- result board (3x3) ----------------------------------------------- */
.board {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: clamp(6px, 1.6vw, 12px);
  max-width: 760px; margin: 0 auto 18px;
}

.tile {
  position: relative; margin: 0;
  border-radius: var(--radius); overflow: hidden;
  border: 2px solid var(--line); background: var(--bg-elev);
  animation: pop .34s cubic-bezier(.34, 1.4, .64, 1) backwards;
}
@keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.94); } }

.tile:nth-child(1) { border-color: var(--gold); box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 26%, transparent); }
.tile:nth-child(2) { border-color: var(--silver); }
.tile:nth-child(3) { border-color: var(--bronze); }

/* SQUARE, matching .card__media.
 *
 * This was left at 4:5 when the pick grid was corrected, so the RESULT board
 * showed tall letterboxed tiles while the prelim grid showed square ones --
 * and, worse, both cropped the same 900x900 source photo differently, so a
 * topping looked different depending on the screen.
 *
 * There are exactly two media boxes in the app. If a third is ever added it
 * must use the same ratio; see the note on .card__media. */
.tile__media { position: relative; aspect-ratio: 1 / 1; overflow: hidden; background: var(--bg-elev-2); }
.tile__img { width: 100%; height: 100%; object-fit: cover; object-position: center top; }

.tile__rank {
  position: absolute; top: 6px; left: 6px; z-index: 2;
  min-width: 26px; height: 26px; padding: 0 7px; border-radius: 999px;
  display: grid; place-items: center;
  font-size: .76rem; font-weight: 900; font-variant-numeric: tabular-nums;
  background: rgba(0, 0, 0, .66); color: #fff;
  backdrop-filter: blur(6px); border: 1px solid rgba(255, 255, 255, .18);
}
.tile:nth-child(1) .tile__rank { background: var(--gold); color: #241a00; border-color: transparent; }
.tile:nth-child(2) .tile__rank { background: var(--silver); color: #1a1f2b; border-color: transparent; }
.tile:nth-child(3) .tile__rank { background: var(--bronze); color: #2a1400; border-color: transparent; }

.tile__body { padding: 7px 9px 9px; }
.tile__name {
  margin: 0; font-size: .82rem; font-weight: 700; line-height: 1.28;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}
.tile__sub {
  margin: 1px 0 0; font-size: .7rem; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

@media (max-width: 520px) {
  .tile__body { padding: 5px 7px 7px; }
  .tile__name { font-size: .74rem; }
  .tile__sub  { display: none; }
}

/* ---- global statistics ------------------------------------------------ */
.statsbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 14px;
  padding: 12px 14px;
  margin-bottom: 10px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.statsbar__group { display: flex; flex-direction: column; gap: 4px; }
.statsbar__group--right { margin-left: auto; }
.statsbar__label { font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); }
.statsbar__select {
  font: inherit; font-size: .88rem; min-height: 36px;
  padding: 5px 10px; color: var(--text); background: var(--bg-elev-2);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  cursor: pointer;
}

.statsmeta { margin: 0 0 12px; font-size: .84rem; color: var(--text-muted); }

.statslist { display: flex; flex-direction: column; gap: 6px; }

.statrow {
  display: grid;
  /* A trailing column for the chevron that marks the row as a link. */
  grid-template-columns: 46px 52px 1fr auto 14px;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* The rows are <a> elements now: they open the contestant's own page. Inherit
     the text colour and drop the underline so they still read as list rows
     rather than as blue links. */
  color: inherit;
  text-decoration: none;
}
.statrow:hover { border-color: var(--accent); background: var(--bg-elev-2); }
.statrow:active { transform: translateY(1px); }
.statrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.statrow__go { font-size: 1.2rem; line-height: 1; color: var(--text-dim); text-align: right; }
.statrow:hover .statrow__go { color: var(--accent); }

.statrow--podium { border-color: var(--line-strong); }
.statrow:nth-child(1) { border-color: var(--gold); }
.statrow:nth-child(2) { border-color: var(--silver); }
.statrow:nth-child(3) { border-color: var(--bronze); }

.statrow__pos {
  font-size: 1.05rem; font-weight: 900; text-align: right;
  color: var(--text-muted); font-variant-numeric: tabular-nums;
}
.statrow:nth-child(1) .statrow__pos { color: var(--gold); }
.statrow:nth-child(2) .statrow__pos { color: var(--silver); }
.statrow:nth-child(3) .statrow__pos { color: var(--bronze); }

/* Square, like .card__media and .tile__media. It was 52x65 (4:5), a third
   inconsistent ratio that cropped the same source photo yet another way. */
.statrow__thumb {
  width: 52px; height: 52px; border-radius: var(--radius-sm);
  overflow: hidden; background: var(--bg-elev-2);
  flex: none;
}
.statrow__thumb img { width: 100%; height: 100%; object-fit: cover; object-position: center top; }

.statrow__who { min-width: 0; }
.statrow__name {
  font-weight: 700; font-size: .95rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.statrow__sub {
  font-size: .78rem; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.statrow__numbers {
  display: flex; gap: 16px; align-items: baseline;
  font-variant-numeric: tabular-nums;
}
.stat {
  display: flex; flex-direction: column; align-items: flex-end;
  min-width: 60px;
}
.stat__value { font-size: 1rem; font-weight: 800; line-height: 1.2; }
.stat__label { font-size: .68rem; letter-spacing: .04em; text-transform: uppercase; color: var(--text-dim); }
.stat--points .stat__value { color: var(--accent); font-size: 1.15rem; }

@media (max-width: 700px) {
  .statrow { grid-template-columns: 34px 44px 1fr; row-gap: 6px; }
  .statrow__numbers { grid-column: 1 / -1; justify-content: space-between; gap: 8px; }
  .stat { min-width: 0; align-items: flex-start; }
  .stat__value { font-size: .9rem; }
  .stat--points .stat__value { font-size: 1rem; }
}

.statsempty {
  padding: 32px 16px; text-align: center; color: var(--text-muted);
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
}

/* ---- global ranking --------------------------------------------------- */
.globalrank {
  max-width: 760px; margin: 0 auto;
  background: var(--bg-elev); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 16px;
}
.globalrank summary {
  cursor: pointer; font-weight: 700; font-size: .92rem;
  list-style: none; display: flex; justify-content: space-between; gap: 10px;
}
.globalrank summary::-webkit-details-marker { display: none; }
.globalrank summary::after { content: "▾"; color: var(--text-dim); }
.globalrank[open] summary::after { content: "▴"; }
.globalrank__body { margin-top: 12px; font-size: .88rem; }

.rankrow {
  display: grid; grid-template-columns: 34px 34px 1fr auto;
  align-items: center; gap: 10px;
  padding: 6px 0; border-top: 1px solid var(--line);
}
.rankrow:first-child { border-top: 0; }
.rankrow__pos { font-weight: 800; color: var(--text-dim); font-variant-numeric: tabular-nums; text-align: right; }
.rankrow img { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; }
.rankrow__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rankrow__elo { font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: .82rem; }

/* ---- toast ------------------------------------------------------------ */
.toast {
  position: fixed; left: 50%; bottom: calc(84px + var(--safe-b));
  transform: translateX(-50%) translateY(8px);
  z-index: 200; padding: 10px 20px; border-radius: 999px;
  background: var(--bg-elev-2); border: 1px solid var(--line-strong);
  box-shadow: var(--shadow); font-size: .88rem; font-weight: 600;
  opacity: 0; transition: opacity .22s ease, transform .22s ease;
  pointer-events: none; max-width: min(90vw, 420px); text-align: center;
}
.toast[data-show="1"] { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ---- share sheet ------------------------------------------------------
   A bottom sheet on phones, a centred dialog on wider screens. It exists
   because the networks are not interchangeable: X, Threads and WhatsApp accept
   prefilled text, Facebook ignores it, and Instagram has no web share API at
   all. One sheet can explain that; six separate buttons could not.
------------------------------------------------------------------------ */
.sharepanel {
  position: fixed; inset: 0; z-index: 300;
  /* A scrim, not a solid colour: on the light theme a near-black overlay would
     look like a hole punched in a pink page, so it is a translucent wash that
     darkens whatever is behind it in either theme. */
  background: rgba(43, 26, 34, .45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0;
}
.sharepanel[hidden] { display: none; }

.sharepanel__inner {
  width: 100%; max-width: 520px;
  max-height: 92dvh; overflow-y: auto;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: 16px 16px calc(16px + var(--safe-b));
  box-shadow: var(--shadow);
}

@media (min-width: 560px) {
  .sharepanel { align-items: center; }
  .sharepanel__inner { border-radius: var(--radius-lg); margin: 16px; }
}

.sharepanel__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 12px;
}
.sharepanel__title { margin: 0; font-size: 1.05rem; font-weight: 800; }
.sharepanel__close {
  border: 1px solid var(--line); background: transparent; color: var(--text-muted);
  width: 34px; height: 34px; border-radius: 50%; cursor: pointer; font-size: 1rem;
  line-height: 1; flex: none;
}
.sharepanel__close:hover { color: var(--text); border-color: var(--line-strong); }

.sharepanel__preview {
  display: block; width: 100%; max-width: 300px; margin: 0 auto 14px;
  border-radius: var(--radius); border: 1px solid var(--line);
}

.sharepanel__grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}

.sharebtn {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 11px 13px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--bg-elev-2);
  color: var(--text); cursor: pointer; text-align: left; font: inherit;
}
.sharebtn:hover { border-color: var(--accent); }
.sharebtn__label { font-weight: 700; font-size: .93rem; }
.sharebtn__hint { font-size: .76rem; color: var(--text-muted); }

/* A colour cue per network, so the list is scannable rather than six
   identical boxes. */
.sharebtn--x        { border-left: 3px solid #e7e9ea; }
.sharebtn--threads  { border-left: 3px solid #a1a1aa; }
.sharebtn--whatsapp { border-left: 3px solid #25d366; }
.sharebtn--facebook { border-left: 3px solid #1877f2; }
.sharebtn--instagram{ border-left: 3px solid #e1306c; }
.sharebtn--native   { border-left: 3px solid var(--accent); }

.sharebtn[hidden] { display: none; }

.sharepanel__note {
  margin: 12px 0 0; font-size: .82rem; line-height: 1.5; color: var(--text-muted);
  min-height: 1.2em;
}
.sharepanel__note b { color: var(--text); }

/* ---- competition picker: REMOVED ---------------------------------------
   The .compbar row and the .compchip buttons it held are gone from the markup.

   They were a full-width row under the topbar, costing 43px of an 852px phone
   screen -- one whole row of cards -- to offer a choice that is only meaningful
   once per game. The home screen offers the same choice with each competition's
   size and time beside it, and the wordmark returns there.

   The rules are deleted rather than left in place: dead CSS is not free, and a
   future element that happens to use the class would silently inherit the look
   of a control that no longer exists.
------------------------------------------------------------------------ */


/* ---- spinner ---------------------------------------------------------- */
.spinner {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid var(--line); border-top-color: var(--accent);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- reduced motion --------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}
