/**
 * Hello World — visual language carried over from Alpha Drop: warm paper
 * ground, thick ink outlines, glossy gummy fills, chunky Fredoka.
 * The .gummy mixin below is the same recipe as Alpha Drop's .bubble-glyph.
 */

:root {
  --paper: #f4ebdd;
  --paper-deep: #e7d9c4;
  --ink: #1c1916;
  --ink-soft: #5c564e;
  --sky: #7ec4e8;
  --sky-lit: #c2e8f7;
  --coral: #e85d4c;
  --hill: #8fbf84;
  --slot: #fff8ef;
  --sun: #f2b544;
  --space: #0d1b3a;
  --space-edge: #060d21;
  /* Set from JS so the panel width and the globe's centring can never drift
     apart; see geometry() in app.js. */
  --panel-w: 20rem;
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  background: var(--paper);
  color: var(--ink);
  font-family: "Fredoka", ui-rounded, system-ui, sans-serif;
  font-weight: 600;
  -webkit-font-smoothing: antialiased;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

button {
  font: inherit;
  color: inherit;
  cursor: pointer;
}

#stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
  /* Space. The globe hangs in it; the star field itself is drawn on the
     canvas so it can fade out as the world is zoomed into. */
  background:
    radial-gradient(115% 85% at 50% 8%, #1b3068 0%, #101f47 42%, var(--space) 72%, var(--space-edge) 100%);
}

#globe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: grab;
}

#globe.dragging {
  cursor: grabbing;
}

/* ---------------------------------------------------------------- pins */

#pins {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.pin {
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  border: none;
  padding: 0;
  border-radius: 50%;
  background: var(--slot);
  box-shadow:
    inset 0 2px 2px rgb(255 255 255 / 0.7),
    inset 0 -3px 4px rgb(28 25 22 / 0.12),
    0 3px 0 var(--paper-deep),
    0 7px 10px rgb(28 25 22 / 0.28);
  outline: 3px solid var(--ink);
  pointer-events: auto;
  will-change: transform, opacity;
  transition: box-shadow 0.15s ease;
  /* No animation on .pin itself: its transform and opacity are driven from
     JS every frame, and a filled CSS animation would override the inline
     styles and strand hidden markers on screen. The entry pop lives two
     levels down, inside the wrapper span, which JS never touches. */
}

/*
 * Two levels, two jobs. The wrapper does the hover grow, as a transition —
 * so it eases both ways and can be interrupted half-way by a pointer that
 * changes its mind. The thing inside does the entry pop and the hover sway,
 * as animations. Kept apart they run at the same time; on one element,
 * whichever was applied second would cancel the other.
 */
.pin span {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  line-height: 1;
  /* Emoji get a touch of lift so they read as objects sitting in the bubble */
  filter: drop-shadow(0 2px 1px rgb(28 25 22 / 0.28));
  transform: translateY(1px);
}

.pin span > * {
  animation: pin-pop 0.42s cubic-bezier(0.34, 1.5, 0.64, 1) both;
}

/* <i> for the emoji so both kinds of pin have something inside the wrapper
   to animate; it is a text node otherwise, and you cannot transform one. */
.pin-glyph {
  display: block;
  font-style: normal;
  line-height: 1;
}

.pin-art {
  display: block;
  width: 78%;
  height: 78%;
  object-fit: contain;
  pointer-events: none;
}

/*
 * A marker with art carries its emoji too, and shows whichever it has.
 *
 * The art is fetched only once the marker is on screen (loadPinArt), so
 * between a marker appearing and its image decoding there is a gap. `has-art`
 * is added when the image lands, and until then the marker is an ordinary
 * emoji capsule instead of the empty space it would otherwise be — the
 * capsule-stripping rules further down all key off the same class, so they
 * arrive together with the thing they are stripping the capsule for.
 */
.pin:not(.has-art) .pin-art {
  display: none;
}

.pin.has-art .pin-glyph {
  display: none;
}

.pin.is-collapsed .pin-art {
  width: 72%;
  height: 72%;
}


.pin:active span {
  animation: pin-squish 0.22s ease both;
}

/*
 * Hover: the bubble gets out of the way and the thing inside it grows.
 *
 * The white capsule is scaffolding — it is what makes a 38px emoji legible
 * against a map. Under the pointer that job is already done, so it fades out
 * entirely and leaves the lion, or the mountain, sitting on the country by
 * itself, a size and a half bigger. Nothing else on the map does this, which
 * is what makes it read as "this one you can press".
 *
 * `animation: none` is load-bearing: pin-pop is a filled animation, and a
 * filled animation outranks a plain transform declaration no matter how
 * specific the selector is. Cancelling it is the only way to hand the
 * transform back to CSS.
 */
@media (hover: hover) and (pointer: fine) {
  .pin.is-poi:hover {
    background: transparent;
    outline-color: transparent;
    box-shadow: none;
    z-index: 4;
  }

  /* Grows AND lifts, over twice the time it used to, on a curve that
     overshoots by a hair rather than snapping. The lift is what stops it
     reading as a zoom: the thing gets up off the map rather than just
     getting bigger where it sat. */
  .pin.is-poi:hover span {
    transform: translateY(-4px) scale(1.55);
    filter: drop-shadow(0 7px 4px rgb(28 25 22 / 0.34));
  }

  /* And then it keeps moving. A hovered thing frozen at its new size is
     what made this feel like a step rather than a gesture — the sway is
     small enough to be barely nameable and does all the work.

     pin-pop stays first in the list on purpose. CSS matches animations by
     position, so leaving it at index 0 means adding the sway beside it does
     not restart it — and taking the sway away again on mouse-out does not
     replay the pop. */
  .pin.is-poi:hover span > * {
    animation:
      pin-pop 0.42s cubic-bezier(0.34, 1.5, 0.64, 1) both,
      pin-sway 2.6s ease-in-out 0.24s infinite;
  }

  /* Art pins never had a capsule to fade, so hover is growth alone. The
     drop-shadow lives on the image rather than the span for these, so it has
     to be lifted here too or the thing grows and its shadow does not. */
  .pin.has-art.is-poi:hover .pin-art {
    filter: drop-shadow(0 8px 6px rgb(28 25 22 / 0.42));
  }

  /* The rule above is more specific than .pin.has-art span, so without this
     the wrapper's shadow comes back on hover and stacks on the art's again. */
  .pin.has-art.is-poi:hover span {
    filter: none;
  }
}

/* The capsule leaves at the same pace the thing inside arrives, so the two
   read as one movement rather than a fade and then a jump. */
.pin {
  transition: background 0.34s ease, outline-color 0.34s ease, box-shadow 0.34s ease;
}

.pin span {
  transition:
    transform 0.38s cubic-bezier(0.2, 1.12, 0.34, 1),
    filter 0.34s ease;
}

.pin .pin-art {
  transition: filter 0.34s ease;
}

/*
 * The one whose fact is open. Deliberately NOT the hover look: hover takes
 * the capsule away so you can see the thing under your finger, and this puts
 * a lit capsule on instead, so "the pointer is here" and "this one is open"
 * never look like the same state. Lives outside the hover media query
 * because it has to work on a screen that has no pointer at all.
 */
.pin.is-poi.is-lit {
  z-index: 5;
  background: linear-gradient(180deg, #ffe6ab 0%, var(--sun) 100%);
  outline-color: var(--ink);
  box-shadow:
    0 0 0 4px rgb(242 181 68 / 0.4),
    0 3px 0 #b87c22,
    0 8px 13px rgb(28 25 22 / 0.3);
}

/* An art pin has no capsule to light, so it gets a glow behind it instead. */
.pin.has-art.is-poi.is-lit {
  background: radial-gradient(circle, rgb(242 181 68 / 0.6) 34%, rgb(242 181 68 / 0) 72%);
  outline: none;
  box-shadow: none;
}

.pin.is-poi.is-lit span {
  transform: translateY(-2px) scale(1.16);
}

.pin.is-poi.is-lit span > * {
  animation:
    pin-pop 0.42s cubic-bezier(0.34, 1.5, 0.64, 1) both,
    pin-sway 2.6s ease-in-out infinite;
}

/* Collapsed marker: the country's headline emoji with a count of what else
   is waiting inside it. Zooming in swaps this for the individual pins. */
.pin.is-collapsed {
  outline-width: 2.5px;
}

.pin-more {
  position: absolute;
  right: -6px;
  bottom: -5px;
  min-width: 1.15rem;
  height: 1.15rem;
  padding: 0 0.22rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 2px solid var(--ink);
  background: linear-gradient(180deg, #ffd98a 0%, var(--sun) 60%, #d8952c 100%);
  color: var(--ink);
  font-size: 0.62rem;
  font-weight: 700;
  font-style: normal;
  line-height: 1;
  box-shadow: 0 1px 0 #b87c22;
}

/* Points of interest in the surrounding countries: present, clearly
   secondary, and not in the way of a tap meant for the country itself. */
.pin.is-poi {
  outline-width: 2px;
  box-shadow: 0 2px 0 var(--paper-deep), 0 5px 7px rgb(28 25 22 / 0.22);
}

.pin.is-globe {
  /* Country markers on the sphere: smaller, and they must never eat a drag */
  outline-width: 2.5px;
}

/*
 * A pin that is a painted object needs no bubble.
 *
 * The white capsule is scaffolding for an emoji: a 38px glyph is unreadable
 * against a map without something behind it. A piece of art is already an
 * object with its own edges and its own shadow, and putting it in a bubble
 * reads as a sticker of a thing rather than the thing. So the capsule goes,
 * and the art takes the whole marker — which is also what keeps it the same
 * visual weight as the emoji pins it sits beside.
 *
 * This block sits AFTER .pin.is-poi and .pin.is-collapsed on purpose. Those
 * carry their own box-shadow at the same specificity as this one, so with
 * the order the other way round they handed the capsule's shadow back to an
 * element with nothing in it — which is exactly what a faint circle floating
 * behind the artwork was.
 */
.pin.has-art {
  background: transparent;
  outline: none;
  box-shadow: none;
}

.pin.has-art .pin-art {
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 3px 3px rgb(28 25 22 / 0.4));
}

/* One shadow, not two: the wrapper casts one for emoji, the art casts its
   own, and stacking both on an art pin muddies its edges. */
.pin.has-art span {
  filter: none;
}

/* A collapsed marker has a count sitting on its corner, so it gives that
   corner back rather than being painted over. Spelled out with both classes
   because .pin.is-collapsed .pin-art is exactly as specific as the rule
   above, and source order is a poor thing to depend on. */
.pin.is-collapsed.has-art .pin-art {
  width: 92%;
  height: 92%;
}

/* The count badge loses the capsule's rim to sit against, so it tucks in to
   where the art actually is rather than floating off the corner. */
.pin.has-art .pin-more {
  right: 0;
  bottom: 0;
}

/* On the inner element now, so it no longer has to carry the wrapper's
   resting offset. */
@keyframes pin-pop {
  from { transform: scale(0.3); }
  to { transform: scale(1); }
}

/* The hover idle. Deliberately tiny — at any larger amplitude it stops
   reading as a thing that noticed you and starts reading as a thing that is
   wobbling. */
@keyframes pin-sway {
  0%, 100% { transform: rotate(-3.2deg) translateY(0); }
  50% { transform: rotate(3.2deg) translateY(-2px); }
}

@keyframes pin-squish {
  50% { transform: translateY(1px) scale(0.8); }
}

/* ---------------------------------------------------------------- hint */

.hint {
  position: absolute;
  left: 50%;
  /* Clear of the "take me somewhere" button, which owns the bottom edge. */
  bottom: calc(max(1.2rem, env(safe-area-inset-bottom)) + 4.6rem);
  transform: translateX(-50%);
  margin: 0;
  padding: 0.55rem 1.3rem;
  border-radius: 999px;
  background: var(--slot);
  border: 3px solid var(--ink);
  box-shadow: 0 4px 0 var(--paper-deep), 0 9px 14px rgb(28 25 22 / 0.2);
  font-size: 1.05rem;
  white-space: nowrap;
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}

.hint[hidden] {
  display: block;
  opacity: 0;
  transform: translateX(-50%) translateY(14px);
}

/* --------------------------------------------------------------- panel */

/*
 * Wide screens: a card down the right, with the globe re-centred in the
 * space that's left (see layout() in app.js — the canvas centre shifts, the
 * panel never covers the country).
 * Narrow screens: the same card as a bottom sheet.
 */
.panel {
  position: absolute;
  left: max(0.9rem, env(safe-area-inset-left));
  /* Starts below the home button, which owns the top-left corner in every
     layout. Same top edge as the fact card that unfolds beside it. */
  top: calc(max(0.9rem, env(safe-area-inset-top)) + 3.9rem);
  /* Hugs its content rather than stretching the full height — a country with
     three facts shouldn't produce a card of mostly empty paper. */
  max-height: calc(100% - max(0.9rem, env(safe-area-inset-top)) - 5rem);
  width: var(--panel-w);
  display: flex;
  flex-direction: column;
  gap: clamp(1rem, 1.5vw, 1.5rem);
  padding: clamp(1.15rem, 1.7vw, 1.7rem);
  overflow-y: auto;
  border-radius: 1.8rem;
  background: var(--slot);
  border: 3px solid var(--ink);
  box-shadow: 0 6px 0 var(--paper-deep), 0 16px 26px rgb(28 25 22 / 0.24);
  animation: panel-in 0.45s cubic-bezier(0.34, 1.42, 0.64, 1) both;
}

.panel[hidden] {
  display: none;
}

.panel-top {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

.panel-flag {
  font-size: clamp(2.9rem, 3.6vw, 4.4rem);
  line-height: 1;
  filter: drop-shadow(0 2px 1px rgb(28 25 22 / 0.25));
}

.panel-id {
  min-width: 0;
  flex: 1;
}

.panel h1 {
  margin: 0;
  font-size: clamp(1.3rem, 2vw, 2rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.01em;
  /* "Bosnia and Herzegovina" has to fit beside a flag and a speaker button.
     Without this the name simply ran on underneath the button and the last
     few letters were painted over. */
  overflow-wrap: anywhere;
}

.panel-region {
  margin: 0.1rem 0 0;
  font-size: clamp(0.85rem, 1.05vw, 1.1rem);
  font-weight: 600;
  color: var(--ink-soft);
}

.panel-say {
  flex: none;
  width: clamp(2.6rem, 3.2vw, 3.6rem);
  height: clamp(2.6rem, 3.2vw, 3.6rem);
  border-radius: 50%;
  display: grid;
  place-items: center;
  padding: 0;
  font-size: 1.15rem;
  border: 2.5px solid var(--ink);
  background: linear-gradient(180deg, #ffd98a 0%, var(--sun) 55%, #d8952c 100%);
  box-shadow: inset 0 2px 2px rgb(255 255 255 / 0.5), 0 3px 0 #b87c22;
}

.panel-say:active {
  transform: translateY(2px);
  box-shadow: inset 0 2px 2px rgb(255 255 255 / 0.5), 0 1px 0 #b87c22;
}

/* A wrapper only so the bars have something to swap with — it must not
   become a box of its own, or the icon inside it would start sizing itself
   against the wrapper rather than against the button. */
.panel-say .say-ico {
  display: contents;
}

.panel-say .eq {
  color: var(--ink);
  height: 1.2em;
}

/* Same size and row as the speaker so the two read as a pair, but in paper
   rather than sun: closing is the quieter of the two things you can do to a
   country. */
.panel-close {
  flex: none;
  width: clamp(2.6rem, 3.2vw, 3.6rem);
  height: clamp(2.6rem, 3.2vw, 3.6rem);
  border-radius: 50%;
  display: grid;
  place-items: center;
  padding: 0;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
  color: var(--ink);
  border: 2.5px solid var(--ink);
  background: var(--paper-deep);
  box-shadow: 0 3px 0 rgb(28 25 22 / 0.18);
}

.panel-close:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 rgb(28 25 22 / 0.18);
}

.panel-h {
  margin: 0 0 0.55rem;
  font-size: clamp(0.7rem, 0.85vw, 0.95rem);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* The row of things found in this country */
.panel-things {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.thing {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: clamp(0.4rem, 0.55vw, 0.6rem) clamp(0.7rem, 0.95vw, 1rem) clamp(0.4rem, 0.55vw, 0.6rem) clamp(0.45rem, 0.6vw, 0.65rem);
  border-radius: 999px;
  border: 2.5px solid var(--ink);
  background: var(--paper);
  font-size: clamp(0.82rem, 1vw, 1.08rem);
  font-weight: 600;
  box-shadow: 0 3px 0 var(--paper-deep);
}

.thing span {
  display: grid;
  place-items: center;
  font-size: clamp(1.75rem, 2.3vw, 2.6rem);
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgb(28 25 22 / 0.25));
}

/* Art gets more room than an emoji does: a painted object carries detail an
   emoji glyph has none of, and at emoji size that detail is simply lost. */
.thing .pin-art {
  /* em against the span above, which just grew — so this number is smaller
     than the emoji's and the art still comes out the larger of the two. */
  width: 1.45em;
  height: 1.45em;
  filter: drop-shadow(0 2px 2px rgb(28 25 22 / 0.3));
}

/* The chip whose fact is open, lit to match its marker on the map. */
.thing.is-on {
  background: linear-gradient(180deg, #ffe6ab 0%, var(--sun) 100%);
  box-shadow: 0 0 0 3px rgb(242 181 68 / 0.35), 0 3px 0 #b87c22;
}

/* A celebration — tinted cool so the three kinds of chip (place, festival,
   food) are each recognisable at a glance. */
.thing.is-event {
  background: linear-gradient(180deg, #e9e2ff 0%, #cfc2f7 100%);
  border-color: var(--ink);
}

/* The national dish, tinted warm so it reads as a different kind of thing
   from a mountain or an animal. */
.thing.is-dish {
  background: linear-gradient(180deg, #fff0d2 0%, #ffe1a8 100%);
  border-color: var(--ink);
}

.thing:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

.panel-stats {
  margin: 0;
  display: grid;
  gap: 0.55rem;
}

.panel-stats > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  padding-bottom: 0.5rem;
  border-bottom: 2px dotted var(--paper-deep);
}

.panel-stats > div:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.panel-stats dt {
  font-size: clamp(0.78rem, 0.95vw, 1.02rem);
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}

.panel-stats dd {
  margin: 0;
  font-size: clamp(0.92rem, 1.15vw, 1.24rem);
  font-weight: 700;
  text-align: right;
}

@keyframes panel-in {
  from { opacity: 0; transform: translateX(-28px) scale(0.97); }
  to { opacity: 1; transform: translateX(0) scale(1); }
}

/* Narrow: the panel becomes a bottom sheet and the globe sits above it. */
@media (max-width: 859px) {
  .panel {
    left: max(0.7rem, env(safe-area-inset-left));
    right: max(0.7rem, env(safe-area-inset-right));
    top: auto;
    /* Bottom sheet: nothing above it to clear, so it takes the height back. */
    bottom: max(0.7rem, env(safe-area-inset-bottom));
    width: auto;
    max-width: none;
    max-height: 46%;
    gap: 0.85rem;
    padding: 1rem 1rem 1.1rem;
  }

  .panel-flag { font-size: 2.4rem; }

  @keyframes panel-in {
    from { opacity: 0; transform: translateY(30px) scale(0.97); }
    to { opacity: 1; transform: translateY(0) scale(1); }
  }
}

/* ---------------------------------------------------------------- sound */

/*
 * Beside home in the top-left, because the two are the same kind of control:
 * settings for the whole game rather than anything to do with the country
 * you happen to have open. Parked up there it also stays clear of the bottom
 * sheet, so it never has to move when a country opens.
 */
.sound {
  position: absolute;
  left: calc(max(0.9rem, env(safe-area-inset-left)) + 3.7rem);
  top: max(0.9rem, env(safe-area-inset-top));
  width: 3.2rem;
  height: 3.2rem;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  border: 3px solid var(--ink);
  background: var(--slot);
  box-shadow: 0 4px 0 var(--paper-deep), 0 9px 12px rgb(28 25 22 / 0.2);
  font-size: 1.3rem;
  z-index: 6;
}

/*
 * Language, third along the top-left row. Same disc as Home and Sound so the
 * three read as one row of whole-game settings, but lettered rather than
 * drawn: it has to say which language you are in as well as open the menu of
 * the others, and no icon does both.
 *
 * The wrapper carries the position so the menu can hang off the button; the
 * disc itself is the same size and shape it always was.
 */
.lang-wrap {
  position: absolute;
  left: calc(max(0.9rem, env(safe-area-inset-left)) + 7.4rem);
  top: max(0.9rem, env(safe-area-inset-top));
  /* Level with Home rather than with Sound: the open menu hangs down over
     the globe, and has to sit above anything it covers. */
  z-index: 9;
}

.lang {
  position: relative;
  width: 3.2rem;
  height: 3.2rem;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  border: 3px solid var(--ink);
  background: var(--slot);
  box-shadow: 0 4px 0 var(--paper-deep), 0 9px 12px rgb(28 25 22 / 0.2);
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ink);
}

.lang:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

/* Open: the disc inverts, so it is obvious which control the menu belongs
   to even with the panel covering the buttons below it. */
.lang[aria-expanded="true"] {
  background: var(--ink);
  color: var(--paper);
}

/*
 * The menu itself. Hangs from the left edge of the disc, wide enough to name
 * every language in full — the two-letter code is the reminder, the name is
 * what you pick by.
 */
.lang-menu {
  position: absolute;
  left: 0;
  top: calc(100% + 0.55rem);
  min-width: 11rem;
  padding: 0.3rem;
  border-radius: 1.1rem;
  border: 3px solid var(--ink);
  background: var(--slot);
  box-shadow: 0 4px 0 var(--paper-deep), 0 9px 12px rgb(28 25 22 / 0.2);
  animation: lang-menu-in 140ms ease-out;
}

.lang-menu[hidden] { display: none; }

@keyframes lang-menu-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.lang-opt {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 0;
  border-radius: 0.8rem;
  background: none;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}

.lang-opt .code {
  flex: none;
  width: 1.7rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
}

.lang-opt .tick {
  margin-left: auto;
  font-size: 0.9rem;
  opacity: 0;
}

.lang-opt[aria-checked="true"] {
  background: var(--ink);
  color: var(--paper);
}

.lang-opt[aria-checked="true"] .code { color: var(--paper); }
.lang-opt[aria-checked="true"] .tick { opacity: 1; }

.lang-opt:active { transform: translateY(1px); }

.sound.is-muted {
  background: var(--paper-deep);
  opacity: 0.75;
}


.sound:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

/*
 * A ring around the speaker for as long as the voice is going. The fact card
 * is not always open and the country panel is not always open, but this
 * button is always there — so wherever a child is when the talking starts,
 * something on screen is moving with it.
 *
 * It pulses out from the button rather than filling it in: this is a report,
 * not a control. Pressing it still means mute (which does stop the voice,
 * the long way round); the button that means stop is the one that started
 * the sentence.
 */
.sound.is-speaking::after {
  content: '';
  position: absolute;
  inset: -0.4rem;
  border-radius: 50%;
  border: 3px solid var(--coral);
  opacity: 0.85;
  pointer-events: none;
  animation: sound-ping 1.5s ease-out infinite;
}

@keyframes sound-ping {
  0% { transform: scale(0.86); opacity: 0.9; }
  70%, 100% { transform: scale(1.14); opacity: 0; }
}

/* ------------------------------------------------------------ voice bars */

/*
 * Three bars keeping time with the voice, shown inside whichever button
 * started it, in place of that button's speaker mark.
 *
 * Sound is the one thing in this game with nothing to look at, and a
 * three-year-old who cannot yet read a label can still see that these are
 * moving and that they stop when the talking stops. It also answers the
 * question the label cannot: which of the buttons on screen is the one
 * making the noise.
 */
.eq {
  display: none;
  align-items: flex-end;
  justify-content: center;
  gap: 0.16em;
  height: 1.05em;
}

.is-playing .eq {
  display: flex;
}

.is-playing .say-ico {
  display: none;
}

/*
 * Deliberately out of step with each other — three bars rising and falling
 * together is a machine, three bars crossing is a voice. The heights here
 * are also the resting shape: with animation off (reduced motion) the bars
 * hold this staggered outline rather than collapsing to nothing.
 */
.eq i {
  display: block;
  width: 0.19em;
  border-radius: 999px;
  background: currentColor;
  transform-origin: 50% 100%;
  animation: eq-bar 0.6s ease-in-out infinite alternate;
}

.eq i:nth-child(1) { height: 62%; animation-duration: 0.52s; }
.eq i:nth-child(2) { height: 100%; animation-duration: 0.68s; animation-delay: -0.22s; }
.eq i:nth-child(3) { height: 78%; animation-duration: 0.58s; animation-delay: -0.4s; }

@keyframes eq-bar {
  from { transform: scaleY(0.32); }
  to { transform: scaleY(1); }
}

/* ----------------------------------------------------------------- home */

/*
 * Back out to the /kids launcher. A kid who taps into the globe from the
 * launcher has no other way out — there's no site chrome here and the
 * browser's back button isn't something they'll find.
 *
 * Top-left, in every layout and every state, because that is where Alpha
 * Drop puts its own. Two games handed over on the same shelf should not
 * disagree about where "out" is, and a control that moves depending on what
 * is open is one a child has to find again each time. Everything else on
 * screen — the panel, the sound button — is positioned around this corner
 * rather than competing for it.
 */
.home {
  position: absolute;
  left: max(0.9rem, env(safe-area-inset-left));
  top: max(0.9rem, env(safe-area-inset-top));
  width: 3.2rem;
  height: 3.2rem;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  border: 3px solid var(--ink);
  background: var(--slot);
  box-shadow: 0 4px 0 var(--paper-deep), 0 9px 12px rgb(28 25 22 / 0.2);
  font-size: 1.3rem;
  text-decoration: none;
  z-index: 9;
}

.home:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

/* ---------------------------------------------------------------- quiz */

/*
 * Along the bottom, not over the middle: the world has to stay visible
 * behind the question, because watching it swing round to the right answer
 * is the reward for getting one. Same card language as the country panel.
 */
.quiz {
  position: absolute;
  left: 50%;
  bottom: max(0.9rem, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  width: min(34rem, calc(100% - 1.8rem));
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  padding: 0.9rem 1rem 1.05rem;
  border-radius: 1.8rem;
  background: var(--slot);
  border: 3px solid var(--ink);
  box-shadow: 0 6px 0 var(--paper-deep), 0 16px 26px rgb(28 25 22 / 0.3);
  z-index: 8;
  animation: quiz-in 0.42s cubic-bezier(0.34, 1.42, 0.64, 1) both;
}

.quiz[hidden] {
  display: none;
}

@keyframes quiz-in {
  from { opacity: 0; transform: translateX(-50%) translateY(26px) scale(0.97); }
  to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

.quiz-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 1;
  min-width: 0;
}

.quiz-step {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

.quiz-step[hidden] {
  display: none;
}

/* One dot per question: filled as you go, so "five of them" is something a
   child can see rather than a number they have to hold. */
.quiz-dots {
  display: flex;
  gap: 0.35rem;
  flex: 1;
}

.quiz-dot {
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  border: 2.5px solid var(--ink);
  background: var(--paper-deep);
}

.quiz-dot.is-now {
  background: var(--sun);
  transform: scale(1.15);
}

.quiz-dot.is-right { background: #6cc39c; }
.quiz-dot.is-wrong { background: #e88a7d; }

/* Out of the flow in card mode so the head can use the full width; back in
   the flow, at the end of the row, as a banner. */
.quiz-quit {
  position: absolute;
  right: 0.7rem;
  top: 0.7rem;
  flex: none;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 50%;
  border: 2.5px solid var(--ink);
  background: var(--paper-deep);
  display: grid;
  place-items: center;
  padding: 0;
  font-size: 1.3rem;
  font-weight: 700;
  line-height: 1;
  color: var(--ink);
}

.quiz-quit:active {
  transform: translateY(2px);
}

.quiz-ask {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

.quiz-emoji {
  font-size: 3.1rem;
  line-height: 1;
  flex: none;
  filter: drop-shadow(0 2px 1px rgb(28 25 22 / 0.25));
}

.quiz-art {
  display: block;
  width: 3.6rem;
  height: 3.6rem;
  object-fit: contain;
}

.quiz-ask-id {
  min-width: 0;
  flex: 1;
}

.quiz-q {
  margin: 0;
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  font-weight: 700;
  line-height: 1.15;
  overflow-wrap: anywhere;
}

.quiz-frame {
  margin: 0.1rem 0 0;
  font-size: clamp(0.85rem, 1.05vw, 1rem);
  font-weight: 600;
  color: var(--ink-soft);
}

/* The scoreboard has no second line, so the name sits on its own. */
.quiz-frame[hidden] {
  display: none;
}

/* Three across at every width. A flag is recognisable at this size long
   before its name can be read, which is the point — the name underneath is
   for whoever is reading it out. */
.quiz-options {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
}

.quiz-opt {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  padding: 0.6rem 0.35rem 0.5rem;
  border-radius: 1.1rem;
  border: 3px solid var(--ink);
  background: var(--paper);
  box-shadow: 0 4px 0 var(--paper-deep);
  font-size: 0.82rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
  text-align: center;
  overflow-wrap: anywhere;
}

.quiz-opt .quiz-flag {
  font-size: 2rem;
  line-height: 1;
}

.quiz-opt:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

/* Answered: the right one is always shown, whether or not it was the one
   picked. Getting it wrong and never being told which was right is the one
   outcome worth designing out. */
.quiz-opt.is-right {
  background: linear-gradient(180deg, #b7e6cf 0%, #7fcfa8 100%);
  box-shadow: 0 4px 0 #4e9d78;
}

.quiz-opt.is-wrong {
  background: linear-gradient(180deg, #f6c4bb 0%, #e8998b 100%);
  box-shadow: 0 4px 0 #b96a5c;
}

.quiz-opt.is-dim {
  opacity: 0.45;
}

.quiz-options[hidden] {
  display: none;
}

/* ------------------------------------------------- quiz: the answer banner */

/*
 * While the globe is off showing you the answer the card does not leave, it
 * shrinks: one row at the top of the screen, out of the way of both the
 * country panel (which owns the left, or the bottom on a narrow screen) and
 * the fact card (which owns the middle). Being dropped back to a bare map
 * with no sign the quiz was still running is what made five questions feel
 * like five unrelated events.
 */
.quiz.is-banner {
  top: max(0.9rem, env(safe-area-inset-top));
  bottom: auto;
  width: auto;
  max-width: min(32rem, calc(100% - 1.8rem));
  flex-direction: row;
  align-items: center;
  gap: 0.7rem;
  padding: 0.45rem 0.55rem 0.45rem 0.95rem;
  border-radius: 999px;
  animation: banner-in 0.32s cubic-bezier(0.34, 1.42, 0.64, 1) both;
}

@keyframes banner-in {
  from { opacity: 0; transform: translateX(-50%) translateY(-18px) scale(0.96); }
  to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

.quiz.is-banner .quiz-ask,
.quiz.is-banner .quiz-options,
.quiz.is-banner .quiz-stars {
  display: none;
}

.quiz.is-banner .quiz-head {
  flex: none;
}

.quiz.is-banner .quiz-quit {
  position: static;
  order: 9;
}

.quiz-next-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 1;
  min-width: 0;
}

.quiz-next-row[hidden] {
  display: none;
}

.quiz-note {
  margin: 0;
  flex: 1;
  min-width: 0;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.quiz-next {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.85rem;
  border-radius: 999px;
  border: 2.5px solid var(--ink);
  background: linear-gradient(180deg, #ffd98a 0%, var(--sun) 55%, #d8952c 100%);
  box-shadow: inset 0 2px 2px rgb(255 255 255 / 0.5), 0 3px 0 #b87c22;
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
}

.quiz-next:active {
  transform: translateY(2px);
  box-shadow: inset 0 2px 2px rgb(255 255 255 / 0.5), 0 1px 0 #b87c22;
}

/* The banner has to share the top of the screen with home, sound and the
   mode pills, so it sheds its own furniture before it crowds theirs. */
@media (max-width: 760px) {
  .quiz.is-banner .quiz-note { display: none; }
}

@media (max-width: 620px) {
  .quiz.is-banner .quiz-dots { display: none; }
  .quiz-next-label { display: none; }
}

/* ---------------------------------------------------- quiz: the scoreboard */

/*
 * One star per question, gold for the ones you got. A five-star row is a
 * score a child reads by counting rather than by reading, and each star is
 * also a button onto what the question was — so "which one did I get wrong?"
 * is answerable instead of something you had to have remembered.
 */
.quiz-stars {
  position: relative;   /* the all-five-right burst is thrown from here */
  display: flex;
  justify-content: center;
  gap: 0.3rem;
}

.quiz-stars[hidden] {
  display: none;
}

.quiz-star {
  position: relative;
  padding: 0.15rem 0.2rem;
  border: none;
  background: none;
  font-size: 2.2rem;
  line-height: 1;
}

.quiz-star-glyph {
  display: block;
  animation: star-land 0.5s cubic-bezier(0.34, 1.5, 0.64, 1) both;
}

.quiz-star.is-right .quiz-star-glyph {
  filter: drop-shadow(0 2px 3px rgb(242 181 68 / 0.75));
}

.quiz-star.is-wrong .quiz-star-glyph {
  opacity: 0.5;
}

@keyframes star-land {
  from { opacity: 0; transform: translateY(-14px) scale(0.4) rotate(-40deg); }
  to { opacity: 1; transform: translateY(0) scale(1) rotate(0); }
}

/* The card behind a star. Hover opens it on a mouse; a tap opens it on a
   screen with no hover, which is most of the ones this game is played on. */
.quiz-star-info {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 0.5rem);
  transform: translateX(-50%) translateY(6px);
  width: max(13rem, 42vw);
  max-width: 17rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.6rem 0.7rem;
  border-radius: 1rem;
  border: 2.5px solid var(--ink);
  background: var(--paper);
  box-shadow: 0 4px 0 var(--paper-deep), 0 10px 18px rgb(28 25 22 / 0.28);
  text-align: left;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
  z-index: 3;
}

.quiz-star.is-open .quiz-star-info,
.quiz-star:focus-visible .quiz-star-info {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

@media (hover: hover) and (pointer: fine) {
  .quiz-star:hover .quiz-star-info {
    opacity: 1;
    visibility: visible;
    transform: translateX(-50%) translateY(0);
  }
}

.quiz-star-head {
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.15;
}

.quiz-star-q {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-soft);
  line-height: 1.2;
}

.quiz-star-a {
  margin-top: 0.15rem;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1.2;
}

/* ------------------------------------------------------- quiz: the fanfare */

/*
 * Stars thrown out of whatever was just tapped. Angle, distance and size come
 * in as custom properties so the keyframes are the same for all twelve and
 * the whole burst is one compositor job rather than a per-frame loop.
 */
.quiz-spark-layer {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 5;
}

.quiz-spark {
  position: absolute;
  left: 0;
  top: 0;
  font-size: 1.05rem;
  line-height: 1;
  will-change: transform, opacity;
  animation: spark 0.85s cubic-bezier(0.16, 0.9, 0.3, 1) both;
}

@keyframes spark {
  from {
    opacity: 0;
    transform: rotate(var(--a)) translateX(0) rotate(calc(-1 * var(--a))) scale(0.2);
  }
  25% { opacity: 1; }
  to {
    opacity: 0;
    transform: rotate(var(--a)) translateX(var(--d)) rotate(calc(-1 * var(--a))) scale(var(--s));
  }
}

/* The card itself gives a little at a right answer, so the celebration is
   not only happening in one corner of it. */
.quiz.is-cheering {
  animation: quiz-cheer 0.6s cubic-bezier(0.34, 1.45, 0.64, 1) both;
}

@keyframes quiz-cheer {
  0% { transform: translateX(-50%) scale(1); }
  35% { transform: translateX(-50%) scale(1.035); }
  100% { transform: translateX(-50%) scale(1); }
}

/* The scoreboard reuses the same card: one wide button instead of three. */
.quiz-options.is-end {
  grid-template-columns: 1fr 1fr;
}

.quiz-again {
  padding: 0.75rem 1rem;
  border-radius: 1.1rem;
  border: 3px solid var(--ink);
  background: linear-gradient(180deg, #ffd98a 0%, var(--sun) 55%, #d8952c 100%);
  box-shadow: inset 0 3px 3px rgb(255 255 255 / 0.5), 0 4px 0 #b87c22;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
}

.quiz-done {
  padding: 0.75rem 1rem;
  border-radius: 1.1rem;
  border: 3px solid var(--ink);
  background: var(--paper);
  box-shadow: 0 4px 0 var(--paper-deep);
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
}

.quiz-again:active,
.quiz-done:active {
  transform: translateY(3px);
}

/* --------------------------------------------------------------- space */

/*
 * The mini globe: bottom-right, diagonally opposite home, because it is the
 * same kind of move one step smaller — home leaves the game, this leaves the
 * country. It shows the real world turned to wherever you are looking, so
 * the button is also a "you are here" at a glance.
 */
.space {
  position: absolute;
  right: max(0.9rem, env(safe-area-inset-right));
  bottom: max(1.2rem, env(safe-area-inset-bottom));
  width: 5.2rem;
  height: 5.2rem;
  padding: 0;
  border-radius: 50%;
  border: 3px solid var(--ink);
  background: var(--space);
  box-shadow: 0 5px 0 var(--paper-deep), 0 11px 16px rgb(28 25 22 / 0.28);
  overflow: hidden;
  z-index: 7;
  animation: space-in 0.4s cubic-bezier(0.34, 1.45, 0.64, 1) both;
}

.space[hidden] {
  display: none;
}

.space canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.space:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

@keyframes space-in {
  from { opacity: 0; transform: translateY(20px) scale(0.85); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/*
 * With the bottom sheet up, the bottom-right corner belongs to the panel.
 * The sheet hugs its content but is never taller than 46%, so sitting just
 * above that line clears it whatever country is open. #stage.sheet is set
 * from app.js.
 */
#stage.sheet .space {
  bottom: calc(46% + 0.8rem);
}

/* ---------------------------------------------------------------- fact */

/*
 * Wide screens: the fact unfolds out of the right-hand edge of the country
 * panel, as a second page of the same card.
 *
 * It used to float at the bottom centre of the map, which put it nowhere in
 * particular — a long way from the panel it belongs to, on top of the
 * country it was describing, and in a different place depending on how wide
 * the window was. Here it reads as the panel opening out, and the country is
 * framed to the right of both (see geometry() in app.js), so the card never
 * covers the thing it is talking about.
 */
.fact {
  position: absolute;
  left: calc(max(0.9rem, env(safe-area-inset-left)) + var(--panel-w, 20rem) + 0.85rem);
  top: calc(max(0.9rem, env(safe-area-inset-top)) + 3.9rem);
  width: var(--fact-w, 20rem);
  max-height: calc(100% - max(0.9rem, env(safe-area-inset-top)) - 5rem);
  overflow-y: auto;
  z-index: 8;
  padding: 1.4rem 1.2rem 1.2rem;
  border-radius: 1.8rem;
  background: var(--slot);
  border: 3px solid var(--ink);
  box-shadow: 0 6px 0 var(--paper-deep), 0 16px 26px rgb(28 25 22 / 0.28);
  text-align: center;
  animation: fact-in 0.42s cubic-bezier(0.34, 1.45, 0.64, 1) both;
}

.fact[hidden] {
  display: none;
}

.fact-emoji {
  /* Decorative only — it's big enough to sit under the close button, and
     must never swallow that tap. */
  pointer-events: none;
  font-size: 5.4rem;
  line-height: 1;
  filter: drop-shadow(0 4px 2px rgb(28 25 22 / 0.28));
  animation: bob 2.2s ease-in-out infinite;
}

/* The painted version of whatever was tapped, at the size the card can
   actually afford — this is the one place in the game where you are looking
   at one thing and nothing else. */
.fact-art {
  display: block;
  width: min(9.5rem, 46vw);
  height: min(9.5rem, 46vw);
  margin: 0 auto;
  object-fit: contain;
  pointer-events: none;
}

.fact h2 {
  margin: 0.5rem 0 0.35rem;
  font-size: clamp(1.3rem, 5.4vw, 1.8rem);
  font-weight: 700;
}

.fact p {
  margin: 0;
  font-size: clamp(0.98rem, 3.7vw, 1.12rem);
  font-weight: 500;
  line-height: 1.45;
  color: var(--ink-soft);
}

/*
 * Replay in the middle, a step either side of it. The arrows are deliberately
 * plainer than the replay button: pressing on through the world should feel
 * like turning a page, not like the main event.
 */
.fact-nav {
  margin-top: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.fact-step {
  flex: none;
  width: 2.9rem;
  height: 2.9rem;
  display: grid;
  place-items: center;
  padding: 0 0 0.14rem;
  border-radius: 50%;
  border: 3px solid var(--ink);
  background: var(--paper);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  box-shadow: 0 4px 0 var(--paper-deep);
}

.fact-step[hidden] {
  display: none;
}

.fact-step:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

.fact-again {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.6rem 1.1rem;
  border-radius: 999px;
  border: 3px solid var(--ink);
  background: linear-gradient(180deg, #f9a396 0%, var(--coral) 55%, #c24738 100%);
  color: var(--slot);
  font-weight: 700;
  font-size: 1rem;
  box-shadow: inset 0 2px 2px rgb(255 255 255 / 0.4), 0 4px 0 #a63a2d, 0 9px 12px rgb(28 25 22 / 0.24);
}

.fact-again:active {
  transform: translateY(3px);
  box-shadow: inset 0 2px 2px rgb(255 255 255 / 0.4), 0 1px 0 #a63a2d, 0 3px 6px rgb(28 25 22 / 0.24);
}

/* Fixed square for the mark so the label sits in the same place whether it is
   showing the speaker or the bars. */
.fact-again-mark {
  display: grid;
  place-items: center;
  width: 1.15em;
  height: 1.15em;
  line-height: 1;
}

/*
 * Both labels live in the same grid cell, one of them hidden. The button is
 * therefore always as wide as "Say it again", whichever word is showing:
 * sizing to the live text would have it shrink onto "Stop" and pull the two
 * arrows inwards, which is the card moving under a child's finger at the one
 * moment they are aiming at it. Stacking rather than reserving a fixed width
 * also means the button still gives way on a narrow card exactly as it did
 * before.
 */
.fact-again-label {
  display: grid;
  /* Nudged off the mark's optical centre rather than the box's. */
  padding-bottom: 0.05em;
}

.fact-again-label > span {
  grid-area: 1 / 1;
}

.fact-again .lbl-stop,
.fact-again.is-playing .lbl-say {
  visibility: hidden;
}

.fact-again.is-playing .lbl-stop {
  visibility: visible;
}

.fact-close {
  position: absolute;
  z-index: 2;
  right: 0.6rem;
  top: 0.6rem;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 50%;
  border: 2.5px solid var(--ink);
  background: var(--paper-deep);
  font-size: 1.3rem;
  font-weight: 700;
  line-height: 1;
  display: grid;
  place-items: center;
  padding: 0;
}

/* Slides out from behind the panel rather than appearing beside it. */
@keyframes fact-in {
  from { opacity: 0; transform: translateX(-26px) scale(0.96); }
  to { opacity: 1; transform: translateX(0) scale(1); }
}

/* Narrow screens: no panel to unfold from, so the bubble is the focus and
   covers the bottom sheet, centred on the screen. */
@media (max-width: 859px) {
  .fact {
    left: 50%;
    top: auto;
    bottom: max(1.2rem, env(safe-area-inset-bottom));
    width: min(92vw, 30rem);
    max-height: 72%;
    transform: translateX(-50%);
    border-radius: 2rem;
    padding: 1.5rem 1.4rem 1.3rem;
  }

  @keyframes fact-in {
    from { opacity: 0; transform: translateX(-50%) translateY(28px) scale(0.92); }
    to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
  }
}

@keyframes bob {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50% { transform: translateY(-7px) rotate(2deg); }
}

/* ------------------------------------------------------------ surprise */

/*
 * The whole world's "just take me somewhere" button.
 *
 * Sits low and centred, the widest and warmest thing on the globe screen,
 * because it is the only control here that needs no reading and no aim. The
 * hint pill moves up to sit above it rather than beside it — two pills
 * sharing the bottom edge on a phone is one pill too many.
 */
.surprise {
  position: absolute;
  left: 50%;
  bottom: max(1.2rem, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.7rem 1.5rem 0.7rem 1.2rem;
  border-radius: 999px;
  border: 3px solid var(--ink);
  background: linear-gradient(180deg, #ffd98a 0%, var(--sun) 55%, #d8952c 100%);
  box-shadow:
    inset 0 3px 3px rgb(255 255 255 / 0.5),
    0 5px 0 #b87c22,
    0 11px 16px rgb(28 25 22 / 0.26);
  font-size: 1.05rem;
  font-weight: 700;
  white-space: nowrap;
  z-index: 6;
  animation: surprise-in 0.5s cubic-bezier(0.34, 1.45, 0.64, 1) both;
}

.surprise[hidden] {
  display: none;
}

.surprise:active {
  transform: translateX(-50%) translateY(4px);
  box-shadow: inset 0 3px 3px rgb(255 255 255 / 0.5), 0 1px 0 #b87c22, 0 4px 8px rgb(28 25 22 / 0.3);
}

.surprise-emoji {
  font-size: 1.4rem;
  line-height: 1;
  animation: bob 2.6s ease-in-out infinite;
}

@keyframes surprise-in {
  from { opacity: 0; transform: translateX(-50%) translateY(22px) scale(0.9); }
  to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

/* Narrow: the label is the only thing on it worth keeping short. */
@media (max-width: 420px) {
  .surprise {
    font-size: 0.95rem;
    padding: 0.6rem 1.15rem 0.6rem 0.95rem;
  }
}

/* ------------------------------------------------------------- loading */

.loading {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.8rem;
  background: var(--space);
  z-index: 10;
  transition: opacity 0.4s ease;
}

.loading span {
  font-size: 4.5rem;
  animation: spin-in 1.5s ease-in-out infinite;
  display: block;
}

.loading p {
  margin: 0;
  color: #9fb3d9;
  font-weight: 600;
}

.loading.gone {
  opacity: 0;
  pointer-events: none;
}

@keyframes spin-in {
  0%, 100% { transform: rotate(-12deg) scale(1); }
  50% { transform: rotate(12deg) scale(1.08); }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---------------------------------------------------------------- tooltip */

/*
 * Hover readout on the globe. Positioned by transform from JS in canvas
 * coordinates, so it tracks the pointer without a layout pass per frame.
 */
.tip {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.34rem 0.75rem 0.34rem 0.5rem;
  border-radius: 999px;
  background: var(--slot);
  border: 2.5px solid var(--ink);
  box-shadow: 0 3px 0 var(--paper-deep), 0 7px 12px rgb(28 25 22 / 0.24);
  font-size: 0.88rem;
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
  z-index: 7;
  will-change: transform;
}

.tip[hidden] {
  display: none;
}

.tip span {
  font-size: 1.2rem;
  line-height: 1;
}

/* Touch devices get no hover, and a tooltip chasing a finger is just noise. */
@media (hover: none) {
  .tip {
    display: none !important;
  }
}

/* Name of whatever has drifted into the middle while turning the world. */
.centre-label {
  position: absolute;
  left: 50%;
  top: max(1.1rem, env(safe-area-inset-top));
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 1rem 0.45rem 0.7rem;
  border-radius: 999px;
  background: var(--slot);
  border: 3px solid var(--ink);
  box-shadow: 0 4px 0 var(--paper-deep), 0 9px 14px rgb(28 25 22 / 0.26);
  font-size: 1rem;
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
  z-index: 7;
}

.centre-label[hidden] {
  display: none;
}

.centre-label span {
  font-size: 1.4rem;
  line-height: 1;
}

/* ----------------------------------------------------------- fat cursor */

/*
 * Super Labs' oversized pointer, the same glyph set and 54px scale as Alpha
 * Drop. Only on real pointers — a touch device has nothing to draw it for.
 */
@media (pointer: fine) {
  html.fat-cursor,
  html.fat-cursor * {
    cursor: none !important;
  }
}

/*
 * Two DIFFERENT names on purpose: `fat-cursor` is the mode flag on <html>,
 * `fat-cursor-node` is the element being drawn. Using one name for both
 * makes `display: none` match the <html> element and blanks the entire
 * document — which is exactly what happened the first time.
 */
.fat-cursor-node {
  position: fixed;
  top: 0;
  left: 0;
  display: none;
  /* Keep in step with SIZE in js/app.js. Half the old 108px: a pointer that
     covers the country it is pointing at is not a helpful pointer. */
  width: 54px;
  height: 68px;
  z-index: 80;
  pointer-events: none;
  filter: drop-shadow(0 1.5px 0 rgb(28 25 22 / 0.18));
  will-change: transform;
}

html.fat-cursor .fat-cursor-node {
  display: block;
}

.fat-cursor-node svg {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: scale(0.82);
  transform-origin: 20% 12%;
  transition: opacity 0.12s ease-out, transform 0.12s ease-out;
}

.fat-cursor-node svg.is-on {
  opacity: 1;
  transform: scale(1);
}

/* Lucide-style line icons in the round chrome buttons, matching Alpha Drop. */
.ico {
  width: 45%;
  height: 45%;
  display: block;
}

.sound .ico,
#home .ico {
  color: var(--ink);
}

.panel-say .ico {
  width: 50%;
  height: 50%;
  color: var(--ink);
}

/* --------------------------------------------------------------- autopilot */

/*
 * Hands-free tour. There is a real button as well as the Enter shortcut
 * because the audience is on an iPad with no keyboard — a key-only toggle
 * would be invisible to everyone this game is actually for.
 */
.modes {
  position: absolute;
  right: max(0.9rem, env(safe-area-inset-right));
  top: max(0.9rem, env(safe-area-inset-top));
  display: flex;
  align-items: center;
  gap: 0.5rem;
  z-index: 9;
}

.mode {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.5rem 0.8rem;
  border-radius: 999px;
  border: 3px solid var(--ink);
  background: var(--slot);
  box-shadow: 0 4px 0 var(--paper-deep), 0 9px 14px rgb(28 25 22 / 0.24);
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--ink);
}

.mode:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--paper-deep);
}

/* The quiz pill's own mark, sized to sit on the same line as the auto tour's
   dot so the two pills are the same height whatever is inside them. */
.quizmode-mark {
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--paper-deep);
  display: grid;
  place-items: center;
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1;
}

.quizmode.is-on {
  background: linear-gradient(180deg, #a8e0c8 0%, #6cc39c 55%, #46a37c 100%);
}

.quizmode.is-on .quizmode-mark {
  background: var(--slot);
}

.autopilot-dot {
  width: 0.72rem;
  height: 0.72rem;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--paper-deep);
}

.autopilot.is-on {
  background: linear-gradient(180deg, #ffd98a 0%, var(--sun) 55%, #d8952c 100%);
}

.autopilot.is-on .autopilot-dot {
  background: #e85d4c;
  animation: autopilot-pulse 1.5s ease-in-out infinite;
}

@keyframes autopilot-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.35); opacity: 0.6; }
}

/* The icon only has a job on a narrow screen; the pill says it in words. */
.mode-icon {
  display: none;
}

/*
 * Narrow: the words go, and the pill becomes a round button.
 *
 * It used to just lose its label and keep its padding, which left the auto
 * tour as a 2.2rem lozenge with a 0.72rem dot rattling around inside it —
 * smaller than Home and Sound in the opposite corner, a different shape from
 * them, and with nothing in it that said what it did. Same 3.2rem circle as
 * those two now, and a real icon in place of the dot: the state that dot was
 * carrying is in the play/pause swap and the lit background instead.
 */
@media (max-width: 560px) {
  .mode-label,
  .autopilot-dot,
  .quizmode-mark {
    display: none;
  }

  .mode {
    width: 3.2rem;
    height: 3.2rem;
    padding: 0;
    border-radius: 50%;
    display: grid;
    place-items: center;
    box-shadow: 0 4px 0 var(--paper-deep), 0 9px 12px rgb(28 25 22 / 0.2);
  }

  .mode-icon {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
  }

  /* The pulse lived on the dot, which is gone here. Without it the only sign
     the tour is running would be the background, so it moves to the icon. */
  .autopilot.is-on .mode-icon {
    animation: autopilot-pulse 1.5s ease-in-out infinite;
  }
}


/*
 * A badge standing in for several countries rather than describing one.
 *
 * Same shape, different promise: "+3" on a lone badge is three sights
 * inside that country, "+7" here is seven more countries behind this one.
 * Ink rather than the sights colour, so the two are never read as the same
 * number — and a hair larger, because a group should look heavier than the
 * single thing it replaced.
 */
.pin.is-cluster .pin-more {
  background: var(--ink);
  color: var(--paper, #f4ebdd);
  border-color: var(--ink);
  min-width: 1.3rem;
  height: 1.3rem;
  font-size: 0.72rem;
}
