:root {
    --paper: #f4ece0;
    --paper-2: #efe4d4;
    --card: #fffdf8;
    --ink: #22303a;
    --ink-soft: #6a7681;
    --rule: #ddd0ba;
    --stamp: #c0452c;
    --air: #2f6ea8;
    /* Tile and key colours; colorblind mode swaps green/yellow for orange/blue. */
    --tile-correct: #3f7a45;
    --tile-present: #9d7118;
    --absent: #7c7466;
    --key-unused: #fbf6ec;
    --key-unused-text: #22303a;
    --key-edge: #d9cdb8;
    --key-absent: #cfc4b0;
    --key-absent-text: #4a4238;
    --accent: #2f6ea8;
    --gap: 6px;
}

* { box-sizing: border-box; }

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

body {
    margin: 0;
    background: var(--paper);
    background-image: repeating-linear-gradient(0deg, rgba(34, 48, 58, .025) 0 1px, transparent 1px 3px);
    color: var(--ink);
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
    min-height: 100vh;
    min-height: 100dvh;
    padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

button {
    font: inherit;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    /* Stops iOS/Android from popping a text-selection or "save/copy" menu on a long press,
       which otherwise interrupts play whenever a guess is held a beat too long. */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

.hidden { display: none !important; }

.airmail-strip {
    height: 6px;
    flex: none;
    border-radius: 0 0 3px 3px;
    background: repeating-linear-gradient(135deg, var(--stamp) 0 10px, #fffdf8 10px 20px, var(--air) 20px 30px, #fffdf8 30px 40px);
}
.airmail-rule {
    height: 6px;
    margin: -1rem -1rem 0.75rem;
    background: repeating-linear-gradient(135deg, var(--stamp) 0 10px, #fffdf8 10px 20px, var(--air) 20px 30px, #fffdf8 30px 40px);
}

/* The app shell is a grid whose rows are: airmail strip, header, mode nav, the play area, the
   keyboard, and the action buttons. Because the keyboard and legal/footer text used to share the
   same flex column, a tall keyboard could grow past the bottom of `main` and paint over the
   footer beneath it (see the screenshot that prompted this). Giving the keyboard its own grid row
   - with the legal text moved out of the game screen entirely - means nothing can ever be laid
   out on top of it again.
   The shell used to be pinned to exactly one viewport tall (`height: 100dvh` with clipped
   overflow), which forced the photo, board and keyboard to fight over a fixed amount of space and
   squeezed all three down on anything but a tall phone. It now only sets a *minimum* height and
   grows past the viewport when its content needs more room - the page scrolls instead of
   clipping. */
.app {
    width: 100%;
    max-width: 1080px;
    /* The postcard is rotated a fraction of a degree for the paper look, which
       makes its bounding box a few pixels wider than the column. That was
       enough to give the whole page a horizontal scrollbar on a phone. `clip`
       rather than `hidden` so it does not become a scroll container and break
       the fixed consent banner. */
    overflow-x: clip;
    min-height: 100vh;
    min-height: 100dvh;
    margin: 0 auto;
    /* The consent banner is fixed to the bottom of the viewport, so it paints
       over whatever is beneath it -- on a first visit that is the bottom
       keyboard row (Enter / Z-M / backspace), leaving a new player unable to
       type. --consent-h is set from JS to the banner's measured height while
       it is open (0 when closed), reserving exactly enough room beneath the
       keyboard. box-sizing is border-box globally, so this eats into the
       100dvh min-height rather than forcing the page taller. */
    padding: 0 1rem calc(0.5rem + var(--consent-h, 0px));
    display: grid;
    /* minmax(0, 1fr), not the default auto: an auto column sizes to its widest
       child, so one over-wide row (a twelve-letter board) silently stretches
       the header and the photo with it. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto auto auto auto;
    gap: 0;
}

.play { display: flex; flex-direction: column; }

.top {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.5rem 0 0.4rem;
    border-bottom: 1px solid var(--rule);
}
h1 {
    margin: 0;
    font-family: ui-serif, Georgia, "Times New Roman", serif;
    font-size: clamp(1.3rem, 5.5vw, 2rem);
    line-height: 1;
}
.tagline {
    display: block;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
    font-size: 0.7rem;
    font-weight: 400;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin-top: 0.3rem;
}
h2, h3 { margin: 0.5rem 0; font-family: ui-serif, Georgia, "Times New Roman", serif; }

.top-actions { display: flex; gap: 0.5rem; }

.icon-button, .secondary, .primary, .mode-button, .link-button {
    min-height: 44px;
    border-radius: 8px;
    border: 1px solid var(--rule);
    background: var(--card);
    color: var(--ink);
    padding: 0 1rem;
    cursor: pointer;
    box-shadow: 0 1px 0 var(--rule);
}
.icon-button.round { width: 44px; padding: 0; font-weight: 700; color: var(--ink-soft); }
.primary { background: var(--stamp); border-color: var(--stamp); color: #fff; font-weight: 700; }
.link-button { min-height: 0; padding: 0; border: 0; background: none; box-shadow: none; color: var(--air); text-decoration: underline; }
button:disabled { opacity: 0.6; cursor: default; }

/* Wraps rather than overflowing. Three mode buttons plus "Colorblind: Off"
   need about 430px and a phone gives 343, so the toggle used to hang 36px off
   the right edge and put a horizontal scrollbar on the whole page. */
.modes { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.4rem 0; }
/* A real minimum, so the row genuinely runs out of space and the toggle wraps
   to its own line. With min-width 0 the three just shrink instead, the line
   never overflows, wrap never triggers, and "Colorblind: Off" gets clipped. */
.mode-button { flex: 1 1 auto; min-width: 5.5rem; font-weight: 600; min-height: 38px; }
.toggle-button {
    /* Takes a line of its own once the modes have filled the first one, and
       sits at the right of it rather than stretching across. */
    flex: 0 0 auto;
    margin-left: auto;
    min-height: 38px;
    padding: 0 0.6rem;
    border-radius: 8px;
    border: 1px solid var(--rule);
    background: var(--card);
    color: var(--ink-soft);
    font-size: 0.8rem;
    cursor: pointer;
}
.toggle-button[aria-pressed="true"] { background: var(--stamp); border-color: var(--stamp); color: #fff; }

.practice-country-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.6rem;
    margin-bottom: 0.4rem;
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: 8px;
    font-size: 0.85rem;
}
.practice-picker { display: flex; flex-direction: column; gap: 0.3rem; margin: 0.8rem 0; }
.practice-picker select {
    min-height: 40px;
    border-radius: 8px;
    border: 1px solid var(--rule);
    background: var(--card);
    color: var(--ink);
    padding: 0 0.6rem;
    font-size: 0.95rem;
}
.mode-button.active { background: var(--air); border-color: var(--air); color: #fff; }

/* ---- postcard photo frame ---- */
/* Sized generously and on its own terms (not "whatever's left after the board and keyboard"),
   since the page can now scroll instead of everything fighting for one fixed viewport. */
.photo-panel { flex: none; width: 100%; min-height: 220px; max-height: min(440px, 52dvh); margin-bottom: 0.5rem; display: flex; }
.postcard {
    margin: 0;
    width: 100%;
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 8px 8px 0;
    box-shadow: 0 10px 24px rgba(34, 48, 58, .16), 0 2px 4px rgba(34, 48, 58, .10);
    transform: rotate(-.4deg);
    display: flex;
    flex-direction: column;
}
.photo-window {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    border-radius: 2px;
    background: #2b3540;
}
.photo-window img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    -webkit-user-drag: none;
    -webkit-touch-callout: none;
}
/* "contain" (not "cover") so the landmark is never cropped, no matter the photo's aspect
   ratio or the phone's screen shape - the whole clue must stay visible to be fair to guess. */
#cityImage { object-fit: contain; }
/* The blurred copy of the same photo fills the dead space behind the letterboxed original
   instead of leaving flat black bars. */
.photo-blur { object-fit: cover; filter: blur(22px) saturate(1.25) brightness(.72); transform: scale(1.15); }
.postcard-caption {
    font-family: ui-serif, Georgia, serif;
    font-style: italic;
    color: var(--ink-soft);
    padding: 0.35rem 0.1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 0.85rem;
}
.stamp {
    width: 22px;
    height: 27px;
    background: var(--stamp);
    border: 3px solid var(--card);
    box-shadow: 0 0 0 1px var(--rule);
    transform: rotate(4deg);
    flex: none;
}

.play-right { flex: none; display: flex; flex-direction: column; }

/* The board has to hold anything from ANNECY to SIMFEROPOL. Both the gap and
   the tile are derived from the column count so a twelve-letter answer still
   fits a 320px phone, and the font is derived from the tile so the letter
   never outgrows the box it sits in. */
.board {
    /* Squeeze the gutter as the word grows: six tiles can afford 6px between
       them, twelve cannot. */
    --board-gap: clamp(2px, calc(32px / var(--cols, 5)), 6px);
    /* Sized from the space the board is actually given, never from the
       viewport: 100vw includes the scrollbar and ignores the page gutters, so
       a board built from it overflows its own column. */
    container-type: inline-size;
    width: 100%;
    /* Never wider than the tiles can actually fill. Without this the board
       keeps the full column on a desktop while the tiles stop at 46px, so the
       container the font is measured against is wider than the row it
       describes and long words get needlessly small letters. */
    max-width: calc(var(--cols, 5) * 46px + (var(--cols, 5) - 1) * var(--board-gap));
    display: grid;
    gap: var(--board-gap);
    margin: 0.5rem auto 0.2rem;
    flex: none;
}
.board-row { display: flex; flex-wrap: wrap; gap: var(--board-gap); justify-content: center; width: 100%; }
.tile {
    /* Share the row evenly, never wider than the original tile. */
    flex: 1 1 0;
    min-width: 0;
    max-width: 46px;
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px solid var(--rule);
    border-radius: 3px;
    background: var(--card);
    /* Scaled to the board's own width rather than the viewport's: a 5vw letter
       sits fine in a 46px tile and overflows a 26px one. The cap keeps short
       words from looking shouty; the fallback covers browsers without
       container query units. */
    font-size: clamp(0.8rem, 3.2vw, 1.5rem);
    font-size: min(1.5rem, calc(52cqw / var(--cols, 5)));
    line-height: 1;
    font-weight: 700;
    text-transform: uppercase;
}
.tile.filled { border-color: #a99a80; }
.tile.correct, .tile.present, .tile.absent { color: #fff; }
.tile.correct { background: var(--tile-correct); border-color: var(--tile-correct); }
.tile.present { background: var(--tile-present); border-color: var(--tile-present); }
.tile.absent { background: var(--absent); border-color: var(--absent); }
.shake { animation: shake 0.4s; }
@keyframes shake {
    20%, 60% { transform: translateX(-5px); }
    40%, 80% { transform: translateX(5px); }
}

.hints { display: flex; flex-wrap: wrap; gap: 0.3rem; justify-content: center; min-height: 1.4rem; }
.hint { padding: 2px 10px; border-radius: 999px; background: var(--tile-present); color: #fff; font-size: 0.8rem; line-height: 1.4; }
.hint.locked { background: var(--card); border: 1px dashed var(--rule); color: var(--ink-soft); }
.feedback { min-height: 1.3em; text-align: center; font-size: 0.9rem; color: var(--ink-soft); }
.feedback.correct { color: var(--tile-correct); }
.feedback.incorrect { color: var(--stamp); }
.game-info { text-align: center; font-size: 0.8rem; color: var(--ink-soft); margin-bottom: 0.15rem; }

/* The keyboard is its own row of the `.app` grid (flex: none, no positioning) so no sibling
   element - and nothing that used to sit below it, like the removed legal footer - can ever be
   laid out on top of it. */
.keyboard { display: flex; flex-direction: column; gap: 5px; margin-top: 0.3rem; flex: none; width: 100%; }
.key-row { display: flex; justify-content: center; gap: 4px; }
.key {
    flex: 1 1 0;
    min-width: 0;
    height: clamp(42px, 6.4dvh, 54px);
    padding: 0;
    border: 1px solid var(--key-edge);
    border-radius: 7px;
    /* Unused keys are clearly light; eliminated keys (.absent) drop to a muted tan. */
    background: var(--key-unused);
    color: var(--key-unused-text);
    font-weight: 700;
    font-size: clamp(0.8rem, 3.6vw, 1rem);
    box-shadow: 0 1px 0 var(--key-edge);
    cursor: pointer;
}
.key.wide { flex: 1.6 1 0; font-size: clamp(0.65rem, 3vw, 0.85rem); }
.key.correct { background: var(--tile-correct); border-color: var(--tile-correct); color: #fff; }
.key.present { background: var(--tile-present); border-color: var(--tile-present); color: #fff; }
.key.absent { background: var(--key-absent); border-color: var(--key-absent); color: var(--key-absent-text); }
/* Once the round ends the keys are disabled, but they must keep their colours at full strength. */
.key:disabled { opacity: 1; cursor: default; }

/* Colorblind mode: orange = right spot, blue = wrong spot. */
body.colorblind { --tile-correct: #b8531a; --tile-present: #2f6ea8; }

.actions { display: flex; gap: 0.5rem; justify-content: center; margin-top: 0.4rem; flex: none; }
.actions .secondary { min-height: 34px; }

/* Says what the game does not have yet. Deliberately quiet -- it should be
   readable when looked for and invisible when playing -- and it stays off the
   board's vertical budget on a phone, where every row of pixels is spoken for. */
.coverage-note {
  flex: none;
  margin: 0.5rem auto 0;
  max-width: 34rem;
  padding: 0 0.75rem;
  font-size: 0.68rem;
  line-height: 1.5;
  text-align: center;
  color: var(--ink-soft);
  text-wrap: balance;
}
.coverage-note:empty { display: none; }
.practice-picker .coverage-note { margin-top: 0.6rem; text-align: left; padding: 0; font-size: 0.72rem; }

/* ---- results / about overlay ("postcard back") ---- */
.overlay {
    position: fixed;
    inset: 0;
    background: rgba(34, 48, 58, 0.6);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    overflow-y: auto;
    padding: 1rem;
    z-index: 10;
}
.results-card {
    position: relative;
    width: 100%;
    max-width: 520px;
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: 16px;
    padding: 1rem 1.2rem 1.2rem;
    margin: auto;
    text-align: center;
    box-shadow: 0 26px 60px rgba(0, 0, 0, .28);
    overflow: hidden;
}
.close {
    position: absolute; top: 10px; right: 8px;
    width: 44px; height: 44px;
    background: none; border: 0; color: var(--ink-soft);
    font-size: 1.8rem; cursor: pointer;
}
.results-head { display: flex; align-items: center; justify-content: center; gap: 1rem; }
.postmark {
    width: 64px; height: 64px; flex: none;
    border: 2px dashed var(--stamp);
    border-radius: 50%;
    color: var(--stamp);
    display: flex; align-items: center; justify-content: center;
    transform: rotate(-8deg);
    font-size: 0.6rem;
    letter-spacing: 0.06em;
    text-align: center;
    line-height: 1.3;
    opacity: 0.85;
}
.answer-city {
    font-family: ui-serif, Georgia, serif;
    font-size: clamp(1.8rem, 9vw, 2.4rem);
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--ink);
}
.answer-place { color: var(--ink-soft); margin-bottom: 0.4rem; }
.fun-fact {
    text-align: left;
    line-height: 1.55;
    font-size: 0.93rem;
    border: 1px solid var(--rule);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    background-image: repeating-linear-gradient(0deg, transparent 0 22px, rgba(47, 110, 168, .13) 22px 23px);
}
.fun-fact-label { display: block; font-size: 0.65rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--air); margin-bottom: 0.2rem; }
.credit { font-size: 0.7rem; color: var(--ink-soft); }

.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.35rem; }
.stat { border: 1px dashed var(--rule); border-radius: 6px; background: var(--paper-2); padding: 0.4rem 0.2rem; }
.stat-value { font-family: ui-serif, Georgia, serif; font-size: 1.5rem; font-weight: 700; }
.stat-label { font-size: 0.62rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft); }
.distribution { margin-top: 0.5rem; }
.dist-row { display: flex; align-items: center; gap: 0.4rem; margin: 3px 0; font-size: 0.8rem; }
.dist-n { width: 1em; color: var(--ink-soft); }
.dist-bar { flex: 1; text-align: left; }
.dist-bar span { display: inline-block; background: var(--air); color: #fff; border-radius: 3px; padding: 1px 6px; min-width: 1.5em; text-align: right; box-sizing: border-box; }

.community { border: 1px solid var(--rule); border-radius: 8px; background: var(--paper-2); padding: 0.6rem 0.8rem; text-align: left; margin-top: 0.5rem; }
.community p { margin: 0.1rem 0 0.5rem; font-size: 0.88rem; color: var(--ink); }
.community-bar { display: flex; height: 12px; border-radius: 999px; overflow: hidden; border: 1px solid var(--rule); background: var(--card); }
.community-bar i { display: block; height: 100%; }

.results-actions { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; margin: 0.75rem 0; }
.results-actions .primary, .results-actions .secondary { width: 100%; }
.share-status { min-height: 1.2em; font-size: 0.85rem; color: var(--tile-correct); }
.countdown { margin: 0; color: var(--ink-soft); font-size: 0.88rem; }
.countdown strong { font-variant-numeric: tabular-nums; color: var(--ink); }

.ad-slot { min-height: 100px; margin: 0.75rem 0; overflow: hidden; }
.travel { border-top: 1px solid var(--rule); padding-top: 0.5rem; }
.travel-link {
    display: block;
    margin: 0.4rem 0;
    padding: 0.75rem 0.9rem 0.75rem 1.5rem;
    border-radius: 8px;
    background: var(--paper-2);
    border: 1px solid var(--rule);
    color: var(--air);
    text-decoration: none;
    text-align: left;
    position: relative;
}
.travel-link::before {
    content: "";
    position: absolute; left: 0.7rem; top: 50%;
    transform: translateY(-50%);
    width: 8px; height: 8px; border-radius: 50%;
    border: 2px solid var(--rule);
}

/* Legal text (affiliate disclosure, privacy / cookie settings) lives inside the About sheet
   and at the bottom of the results postcard - never on the game screen, where it used to sit
   directly beneath (and get covered by) the on-screen keyboard. */
.legal { border-top: 1px solid var(--rule); margin-top: 1rem; padding-top: 0.7rem; font-size: 0.7rem; line-height: 1.5; color: var(--ink-soft); text-align: left; }
.legal a, .legal button.link-button { color: var(--air); font-size: 0.7rem; }
.legal p { margin: 0.15rem 0; }

.about-card { text-align: left; }
.about-sub { color: var(--ink-soft); font-size: 0.8rem; letter-spacing: 0.05em; text-transform: uppercase; margin: 0 0 0.5rem; }
.about-list { padding-left: 1.1rem; margin: 0.4rem 0; line-height: 1.6; font-size: 0.93rem; }
.about-caption { font-size: 0.85rem; line-height: 1.55; color: var(--ink); }
.about-done { width: 100%; margin-top: 1rem; }

/* ---- cookie banner ---- */
.cookie-banner {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 20;
    background: var(--card);
    border-top: 1px solid var(--rule);
    padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom));
    font-size: 0.85rem;
}
.cookie-banner p { margin: 0 0 0.5rem; }
.cookie-banner a { color: var(--air); }
.cookie-buttons { display: flex; gap: 0.5rem; justify-content: flex-end; }
.cookie-buttons button { flex: 1; max-width: 160px; }

/* ---- privacy page ---- */
.doc { max-width: 700px; margin: 0 auto; padding: 1rem; line-height: 1.55; }
.doc a { color: var(--air); }

@media (max-height: 600px) {
    .top { padding: 0.15rem 0; }
    .icon-button { min-height: 34px; }
    .hints { min-height: 1.3rem; }
}

/* Short/landscape phones: trim the surrounding chrome (padding, the tagline, button heights) so
   there's less to scroll past. The photo panel and board no longer get squashed here - the page
   scrolls instead, so the photo stays large and legible and the last board row is never cut off. */
@media (max-height: 520px) {
    .app { padding-bottom: 0.2rem; }
    .top { padding: 0.1rem 0; }
    .tagline { display: none; }
    .modes { padding: 0.15rem 0; }
    .mode-button, .toggle-button { min-height: 28px; }
    .photo-panel { min-height: 180px; }
    .board { margin: 0.2rem auto 0.1rem; }
    .hints { min-height: 0; }
    .feedback { min-height: 0; }
    .game-info { margin-bottom: 0; }
    .actions { margin-top: 0.2rem; }
    .actions .secondary { min-height: 28px; }
}

@media (max-height: 500px) and (orientation: landscape) {
    .app { padding-bottom: 0.2rem; }
    .top { padding: 0.1rem 0; }
    .tagline { display: none; }
    .modes { padding: 0.15rem 0; }
    .mode-button, .toggle-button { min-height: 28px; }
    .photo-panel { min-height: 160px; }
    .board { margin: 0.2rem auto 0.1rem; }
    .hints { min-height: 0; }
    .feedback { min-height: 0; }
    .game-info { margin-bottom: 0; }
    .actions { margin-top: 0.2rem; }
    .actions .secondary { min-height: 28px; }
}

/* Desktop / tablet: two columns (postcard photo alongside the board and keyboard) instead of a
   tall single column, and everything gets to be a bit bigger. */
@media (min-width: 880px) {
    .play { flex-direction: row; gap: 1.75rem; align-items: center; padding: 0.5rem 0; }
    .photo-panel { flex: 1 1 55%; max-height: none; height: 100%; }
    .postcard { height: 440px; }
    .play-right { flex: 1 1 45%; align-items: center; }
    /* Bigger tiles where there is room. Expressed as a cap rather than a fixed
       width so a long answer still shrinks to fit instead of overflowing the
       column, and lifted on the board too or the board would stay sized for
       46px tiles and squeeze them back down. */
    .tile { max-width: 56px; }
    .board { max-width: calc(var(--cols, 5) * 56px + (var(--cols, 5) - 1) * var(--board-gap)); }
    .keyboard { max-width: 640px; margin-left: auto; margin-right: auto; }
}

/* ---- Learn globe ---- */
.globe-wrap { margin: 0.75rem 0 0.5rem; }
/* The globe and its controls only. The country card sits below this, so the
   zoom buttons cannot end up floating over its text. */
.globe-stage { position: relative; }
.globe-host {
    width: 100%;
    /* Square-ish on phones, wider on desktop; the renderer reads this box. */
    height: min(58vh, 460px);
    min-height: 260px;
    border-radius: 12px;
    background: radial-gradient(circle at 50% 40%, #f4f7fa 0%, #e7edf3 60%, #dbe4ec 100%);
    border: 1px solid var(--rule);
    overflow: hidden;
    touch-action: none; /* dragging spins the globe rather than scrolling the sheet */
    cursor: grab;
}
.globe-host:active { cursor: grabbing; }
.globe-host canvas { display: block; width: 100%; height: 100%; }
.globe-loading {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9rem;
    color: var(--ink-soft);
    pointer-events: none;
}
/* Below the globe, not over it. Selecting a country now moves the camera in
   and centres that country, which put it directly behind a card floating in
   the corner -- you clicked Italy and the card hid Italy, city markers and
   all. */
.globe-card {
    position: static;
    margin-top: 0.5rem;
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: 10px;
    padding: 0.7rem 2rem 0.75rem 0.85rem;
    box-shadow: 0 6px 20px rgba(34, 48, 58, 0.18);
}
.globe-card h3 { margin: 0 0 0.15rem; font-size: 1.05rem; }
.globe-card .close { top: 0.35rem; right: 0.45rem; }
.globe-meta { margin: 0 0 0.35rem; font-size: 0.8rem; color: var(--air); font-weight: 600; }
.globe-fact { margin: 0 0 0.35rem; font-size: 0.88rem; line-height: 1.45; }
.globe-playable { margin: 0; font-size: 0.78rem; color: var(--ink-soft); }
.globe-tools { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; margin-bottom: 0.9rem; }
.globe-tools input {
    flex: 1 1 200px;
    min-width: 0;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--rule);
    border-radius: 8px;
    font: inherit;
    font-size: 0.9rem;
    background: var(--card);
    color: var(--ink);
}
.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;
}
@media (min-width: 720px) {
    /* Full width under the globe rather than a floating panel: the globe is
       the thing being read now, so nothing should sit on top of it. */
    .globe-card { width: 100%; }
}

/* ---- progress: what the player has actually found ---- */
/* One bar shape, reused by Learn, the country card and the practice picker, so
   the same "found / still out there" idea reads the same in all three. */
.progress-bar {
    height: 6px;
    border-radius: 3px;
    background: var(--rule);
    overflow: hidden;
    margin: 0.45rem 0 0.35rem;
}
.progress-bar > span {
    display: block;
    height: 100%;
    width: 0;
    border-radius: 3px;
    background: linear-gradient(90deg, var(--air), #f0a500);
    transition: width 320ms ease;
}

.learn-progress { margin: 0 0 0.9rem; }
.learn-progress .about-caption { margin: 0; color: var(--ink-soft); }

.globe-bar { margin: 0.4rem 0 0.3rem; }
.globe-found { margin: 0; font-size: 0.76rem; line-height: 1.4; color: var(--ink-soft); }
.globe-practice { width: 100%; margin-top: 0.5rem; padding: 0.42rem; font-size: 0.82rem; }

.practice-found { margin: 0.2rem 0 0; color: var(--ink-soft); }

.answer-progress { margin: 0.4rem 0 0; font-size: 0.85rem; color: var(--ink-soft); }
.answer-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.45rem 0 0; }
/* Offers, not commands: the round is over and the primary button is Next City. */
.link-button {
    background: none;
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 0.3rem 0.75rem;
    font: inherit;
    font-size: 0.8rem;
    color: var(--air);
    cursor: pointer;
}
.link-button:hover { border-color: var(--air); background: var(--paper-2); }
.link-button:focus-visible { outline: 2px solid var(--air); outline-offset: 2px; }

/* The city card replaces the country card in the same corner, so opening a
   city never covers the globe twice over. */
.globe-city-card { border-color: var(--air); }
.globe-city-card h3 { color: var(--air); }

/* Distance and direction, under each guessed row. Deliberately quiet: it is a
   clue to reason from, not a scoreboard, and it must not pull the eye off the
   letters above it. */
.board-row { position: relative; }
.near {
    flex-basis: 100%;
    text-align: center;
    font-size: 0.72rem;
    line-height: 1.3;
    color: var(--ink-soft);
    letter-spacing: 0.02em;
    margin-top: 1px;
}
@media (min-width: 880px) {
    .near { font-size: 0.78rem; }
}

/* Zoom controls, for the people who have no scroll wheel and the phones where
   pinching competes with the page scrolling. */
/* Bottom-left, deliberately: the Learn dialog's close button is at the top
   right of the card and was sitting on top of these. */
.globe-zoom {
    position: absolute;
    bottom: 0.6rem;
    left: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.globe-zoom-button {
    width: 34px;
    height: 34px;
    border-radius: 8px;
    border: 1px solid var(--rule);
    background: rgba(255, 253, 248, 0.92);
    color: var(--ink);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}
.globe-zoom-button:hover { border-color: var(--air); color: var(--air); }
.globe-zoom-button:focus-visible { outline: 2px solid var(--air); outline-offset: 2px; }
