@charset "UTF-8";
/* ==========================================================================
   Component grammar — the primitives from docs/PAGE_TEMPLATES.md.
   Semantics live here; a destination's art direction only supplies colour and
   media through --art-accent and the pattern layer.
   ========================================================================== */

/* --- Art direction -------------------------------------------------------- */

/*
 * `--art` is "this Destination's accent, in the theme the reader is actually
 * in". Everything that carries a destination's colour reads this and never
 * `--art-accent` directly, because `--art-accent` is one fixed hex chosen
 * against warm paper: on the dark theme it put a mid-dark dot on a near-black
 * sea inside a near-black stroke, which is a country's identity rendered
 * invisible. `artDirection()` supplies the dark value beside the light one.
 *
 * It has to be redeclared on `.art` as well as on `:root` because the hex
 * itself arrives in an inline style on the destination wrapper, deep in the
 * document: a `var()` resolved at `:root` cannot see it. `.art` is that
 * wrapper, so the alias is computed exactly where the accent is set.
 */
:root, .art { --art: var(--art-accent, var(--brand-2)); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]),
  :root:not([data-theme="light"]) .art { --art: var(--art-accent-dark, var(--brand-2)); }
}
:root[data-theme="dark"],
:root[data-theme="dark"] .art { --art: var(--art-accent-dark, var(--brand-2)); }

/*
 * `--art-mark` is the same accent with a floor under it: what a marker may be
 * painted, as opposed to what a keyline or a chapter number may be.
 *
 * An accent is chosen against a photograph, and nothing about that choice makes
 * it legible as a five-pixel dot on a map. Measured against the land tone set
 * below, the raw accents run from 5.14:1 (United Kingdom) down to 1.71:1
 * (Malta), with Spain at 2.11, Australia 2.23 and Italy 2.40 — six Destinations
 * whose markers sat under WCAG 1.4.11's 3:1 minimum for a non-text graphic, on
 * a map whose whole job is to be looked at.
 *
 * Pushing the accent 35% of the way towards the theme's ink keeps the hue —
 * Portugal is still terracotta, Greece still sea-blue — and lifts the worst case
 * to **3.32:1 in light and 5.01:1 in dark**. The same expression works in both
 * themes because `--ink` inverts with them: in light it darkens the accent
 * against pale land, in dark it lightens it against dark land.
 *
 * Art direction has not lost anything it was ever allowed: it still supplies
 * colour, and colour is still all it supplies. What it cannot do is supply a
 * colour nobody can see.
 */
:root, .art { --art-mark: color-mix(in oklab, var(--art) 65%, var(--ink)); }

/* A destination's accent, on its own page rather than only inside its map. One
   keyline under the hero, and the same colour on the rules that separate the
   page's sections — enough that Portugal and Finland are visibly different
   pages, and never enough to be mistaken for a signal. Colour only: it marks
   nothing, ranks nothing and hides nothing, which is the standing rule for
   everything art direction is allowed to touch. */
.art > .hero { border-bottom: 3px solid var(--art); }
.art .section--rule { border-top-color: color-mix(in oklab, var(--art) 35%, var(--rule)); }

.pattern {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .1;
}
.pattern--latitude {
  background-image: repeating-linear-gradient(
    to bottom,
    currentColor 0 1px,
    transparent 1px 56px
  );
}
.pattern--grid {
  background-image:
    repeating-linear-gradient(to bottom, currentColor 0 1px, transparent 1px 48px),
    repeating-linear-gradient(to right, currentColor 0 1px, transparent 1px 48px);
}
.pattern--wave {
  background-image: radial-gradient(circle at 50% 120%, currentColor 0 1px, transparent 1.5px);
  background-size: 34px 34px;
}

/* --- WorldWindow ---------------------------------------------------------- */

/* The durations here are the site's own motion tokens from assets/css/motion.css,
   which already answer both the operating-system setting and the in-product
   toggle. Nothing in this file redefines them: a second set of numbers would be
   a second answer to "how long is a change in the set?". */
.world {
  margin: 0;
  display: grid;
  gap: var(--s5);
}

/*
 * The stage is where the globe lands, and no other map is ever drawn in it
 * (ADR 0007): only, on a page that rests on the desk, a still of that same
 * globe (`.world__poster`, below). It is transparent in both themes — no box, no border, no night
 * sky — so the globe floats on the page's own paper (the owner, #53: "the
 * Globe to be floating and just land on the page"). Its size is reserved from
 * the first paint, so the page does not move when the globe arrives.
 */
.world__stage {
  position: relative;
  background: transparent;
  border: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  /* 2:1 on a wide screen, square on a phone: a globe needs a stage that is
     not a letterbox, and a thumb needs a stage it can work with. */
  aspect-ratio: 2 / 1;
  max-height: 78vh;
}
@media (max-width: 44rem) {
  .world__stage { aspect-ratio: 1 / 1; }
}
.world[data-poster] .world__stage { container-type: size; }

/*
 * The still of the desk globe at rest (scripts/make-globe-poster.mjs), where
 * the globe will be drawn: globe.js deskAltFor() sizes the globe so that the
 * ring and stand (1.13 radii above its centre, 1.62 below, 1.16 either side)
 * fill 92% of the stage's height, or 90% of its width — less the two
 * button columns on a stage under 700px that keeps its buttons inside — and
 * centres that assembly. The image spans −1.35…1.35 radii across and
 * −1.35…1.8 down (the halo and the stand's shadow included), so --r, the
 * globe's radius, places it exactly. The canvas fades in over it; then it
 * fades out.
 */
.world__poster {
  --r: min(calc(.92 * 100cqh / 2.75), calc(.9 * 100cqw / 2.32));
  position: absolute;
  left: calc(50% - 1.35 * var(--r));
  top: calc(50% - 1.595 * var(--r));
  width: calc(2.7 * var(--r));
  height: calc(3.15 * var(--r));
  max-width: none;
  pointer-events: none;
  transition: opacity .4s ease .7s;
}
@container (max-width: 699.98px) {
  .world__poster { --r: min(calc(.92 * 100cqh / 2.75), calc((100cqw - 116px) / 2.32)); }
}
@media (max-width: 44rem) {
  /* On a phone the buttons are above the stage, so the globe has its width. */
  .world__poster { --r: min(calc(.92 * 100cqh / 2.75), calc(.9 * 100cqw / 2.32)); }
}
.world[data-globe="on"] .world__poster { opacity: 0; }
/* One still per theme (a <picture> by the system's theme: the globe's halo
   and shadow are the theme's), fetched eagerly and preloaded on home. */
.world__poster > img { display: block; width: 100%; height: 100%; max-width: none; }
.world[data-close="on"] .world__poster { display: none; }

/*
 * Where the globe cannot run — JavaScript off (`data-js` is set in the head
 * before first paint), no WebGL, a software renderer, a lost context — the
 * list is the world window, and one line says so. A page with a still of its
 * globe keeps the still in the stage; any other page shows no stage at all.
 * Nothing different is drawn in the globe's place: that was the flash the
 * owner asked to be rid of.
 */
.world__off { display: none; margin: 0; font-size: .875rem; color: var(--ink-soft); }
:root:not([data-js]) .world:not([data-poster]) .world__stage,
.world:not([data-poster])[data-globe="off"] .world__stage { display: none; }
:root:not([data-js]) .world__off,
.world[data-globe="off"] .world__off { display: block; }
:root:not([data-js]) .world__caption,
.world[data-globe="off"] .world__caption { display: none; }

/* --- The camera ----------------------------------------------------------- */


.world__controls {
  position: absolute;
  top: var(--s3);
  right: var(--s3);
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
}
.world__btn {
  min-width: 2rem;
  min-height: 2rem;
  padding: 0 .4rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font: 600 .875rem/1 var(--sans);
  cursor: pointer;
  box-shadow: var(--lift-1);
}
.world__btn--wide { font-size: var(--type-floor); letter-spacing: .04em; text-transform: uppercase; }
.world__btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
/* A disabled control is dimmed, not made translucent. `opacity` applies to the
   whole element including its background, so a disabled button sitting on a map
   let the map show through it and read as a rendering fault rather than as a
   control that is currently unavailable. Dim the ink and the border instead. */
.world__btn:disabled {
  cursor: default;
  color: var(--ink-mute);
  border-color: var(--rule);
  box-shadow: none;
}

/* The 2rem above is this button's mouse size and stays its mouse size. What
   used to sit here was the site's only touch-target rule — "a control under a
   thumb is at least 44px square. These were 52x32, which is fine under a mouse
   and not under a thumb" — applied to this one component in #26 and never
   generalised, so every other control went on deciding for itself and all of
   them decided wrong. It has moved, unchanged in intent, to the control policy
   at site.css "2b", where `--tap` is declared once and `.world__btn` inherits
   it along with every other control on the site. Nothing here restates 44. */

/* Stacked, three 44px controls take 140px of a 274px panel. Along the top they
   take 44. */
@media (max-width: 44rem) {
  .world__controls { flex-direction: row; }
}

/* A folded list (worldWindow foldList): one line until opened. */
.world__fold { margin-top: var(--s3); }
.world__fold > summary { cursor: pointer; min-height: 24px; font-size: .875rem; color: var(--ink-soft); }
.world__fold[open] > summary { margin-bottom: var(--s2); }
.world__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}
.world__list a {
  display: inline-flex;
  align-items: baseline;
  gap: .4em;
  padding: .3rem .7rem;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  font-size: .8125rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--ink-soft);
  background: var(--paper);
  /* A hover is not a completed action, and this used to borrow the
     `action-complete` token for one. Hover gets the same .15s the rest of the
     site's links get; the token went to the things a student actually did. */
  transition: border-color .15s ease, color .15s ease;
}
/* `data-on` is the same state the marker shows, set from the other end: the
   pointer over a dot lights the entry, the keyboard on an entry lights the dot. */
.world__list a:hover,
.world__list a:focus-visible,
.world__list a[data-on] {
  border-color: var(--art, var(--accent));
  color: var(--ink);
}
.world__list a[aria-current="true"] {
  border-color: var(--accent);
  background: var(--accent-tint);
  color: var(--ink);
}
.world__list a[data-dim] { opacity: .55; }
.world__count {
  font-variant-numeric: tabular-nums;
  color: var(--ink-mute);
  font-weight: 400;
}

.world__caption {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  font-size: .75rem;
  color: var(--ink-mute);
}
.world__legend { display: inline-flex; align-items: center; gap: .4em; }
.world__legend-dot {
  display: inline-block;
  border-radius: 50%;
  background: var(--art-mark);
}
.world__legend-dot--sm { width: .4rem; height: .4rem; }
.world__legend-dot--lg { width: .75rem; height: .75rem; }

/* --- The globe (ADR 0005, ADR 0007) -------------------------------------
 *
 * `assets/js/globe.js` lays a WebGL canvas, a layer of pins, a card and the
 * camera buttons into the empty stage and fades the canvas in once its first
 * frame is drawn. There is nothing under it: the flat map that used to be the
 * first paint was removed (ADR 0007), so the page shows its own paper until
 * the globe lands on it.
 *
 * The canvas is transparent and the stage has no background in either theme:
 * the globe floats. The pins and their labels carry their own dark pill, so
 * they read on light paper and dark alike; the card and the buttons are the
 * site's own surfaces and follow the theme like any other.
 */
.world__globe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity .6s var(--ease-enter, ease);
}
.world[data-globe="on"] .world__globe { opacity: 1; }
/* Whatever the camera does, the imagery never ends in a hard edge: the last
   40px of the stage fade into the paper on every side (the owner, #53: "a
   sphere and not … a sphere with background"). At rest the desk globe sits
   well inside that margin; leaned in, a country's view is a soft-edged
   window rather than a slab. The pins, buttons and card are not masked. */
.world__globe,
.world__stage > .world__close {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 40px, #000 calc(100% - 40px), transparent), linear-gradient(to bottom, transparent, #000 40px, #000 calc(100% - 40px), transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(to right, transparent, #000 40px, #000 calc(100% - 40px), transparent), linear-gradient(to bottom, transparent, #000 40px, #000 calc(100% - 40px), transparent);
  mask-composite: intersect;
}
/* The close map (MapLibre, globe-close.js) sits between the globe's canvas
   and the pins, and cross-fades in when the camera is handed to it. Its own
   attribution replaces the globe's NASA credit while it is showing. */
/* Two classes deep because MapLibre's own stylesheet, loaded later, sets
   `.maplibregl-map { position: relative }` — which collapsed the map to 300px
   and put its centre 150px from the globe's. */
.world__stage > .world__close {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .45s var(--ease-enter, ease), visibility 0s .45s;
}
.world[data-close="on"] .world__close { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity .45s var(--ease-enter, ease); }
.world[data-close="on"] .world__credit { display: none; }
.world__close .maplibregl-ctrl-attrib { font: var(--type-floor)/1.4 var(--sans); }
/* The site's tap-target policy makes MapLibre's ⓘ button 44px square, and its
   24px icon then tiled four times (round 2). One icon, centred. */
.world__close .maplibregl-ctrl-attrib-button { background-repeat: no-repeat; background-position: center; }
@media (prefers-reduced-motion: reduce) { .world__close { transition: none; } }
:root[data-motion="reduced"] .world__close { transition: none; }
.world[data-globe="on"] .world__stage { cursor: grab; }
.world[data-globe="on"] .world__stage[data-dragging] { cursor: grabbing; }
.world[data-globe="on"] .world__stage:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

.world__pins {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  --pin: #E3B863;           /* the site's sand, lifted for a night sky */
  --pin-hot: #F4935F;       /* the site's accent, the same way */
  --pin-ring: #FBF7F0;      /* paper */
  --pin-ink: #F3ECE0;
}
.world__pin, .world__cluster {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  --r: 6px;
  will-change: transform;
}
.world__pin[hidden], .world__cluster[hidden] { display: none; }
/* The target is the size of a fingertip whatever the dot is, and invisible. */
.world__pin::before, .world__cluster::before {
  content: "";
  position: absolute;
  left: -16px;
  top: -16px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  pointer-events: auto;
  cursor: pointer;
}
@media (pointer: coarse) {
  .world__pin::before, .world__cluster::before {
    left: calc(var(--tap) / -2);
    top: calc(var(--tap) / -2);
    width: var(--tap);
    height: var(--tap);
  }
}
.world__pin-dot {
  position: absolute;
  left: calc(var(--r) * -1);
  top: calc(var(--r) * -1);
  width: calc(var(--r) * 2);
  height: calc(var(--r) * 2);
  border-radius: 50%;
  background: var(--pin);
  box-shadow: 0 0 0 1.5px var(--pin-ring), 0 0 10px 2px rgba(227, 184, 99, .55), 0 1px 3px rgba(0, 0, 0, .6);
  pointer-events: none;
  transition: background-color .15s ease, transform .15s ease;
}
/* Hollow means we only know the city, not the campus. */
.world__pin[data-approx] .world__pin-dot {
  background: rgba(8, 12, 20, .35);
  box-shadow: inset 0 0 0 2.5px var(--pin), 0 0 0 1px var(--pin-ring), 0 0 10px 2px rgba(227, 184, 99, .45);
}
.world__pin[data-on] .world__pin-dot,
.world__pin[data-selected] .world__pin-dot {
  background: var(--pin-hot);
  transform: scale(1.35);
  box-shadow: 0 0 0 2px var(--pin-ring), 0 0 16px 5px rgba(244, 147, 95, .7);
}
.world__pin[data-dim] .world__pin-dot { background: #8A8F99; box-shadow: 0 0 0 1px rgba(251, 247, 240, .6); }
/* One institution inside a country that has opened (globe.js schoolsOpen):
   a small, solid dot without the country light's glow, so the country level
   and the school level read as two levels. */
.world__pin[data-school]:not([data-on]):not([data-dim]) .world__pin-dot { box-shadow: 0 0 0 1.5px var(--pin-ring), 0 1px 3px rgba(0, 0, 0, .6); }
.world__pin-label {
  position: absolute;
  left: calc(var(--r) + 5px);
  top: 0;
  transform: translateY(-50%);
  display: none;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(6, 10, 18, .74);
  color: var(--pin-ink);
  font: 600 12px/1.35 var(--sans);
  white-space: nowrap;
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
}
/* The route between two choices and the vehicle on it (globe.js drawRoute):
   the way already travelled is a solid trail, the way ahead dotted; paper on
   an ink under-shadow so it reads on ocean, land and cloud alike. */
.world__route {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  filter: drop-shadow(0 1px 1.5px rgba(26, 22, 19, .55));
}
.world__route path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.world__route-done { stroke: var(--pin-ring); stroke-width: 2.5; }
.world__route-left { stroke: var(--pin-ring); stroke-width: 3; stroke-dasharray: .5 7; }
.world__vehicle path, .world__vehicle circle { fill: var(--pin-hot); stroke: var(--pin-ring); stroke-width: 1.3; paint-order: stroke; }
.world__vehicle .world__vehicle-window { fill: var(--pin-ring); stroke: none; }
/* The desk globe's ring and stand (globe.js drawDesk): brass and wood with a
   cartoon ink line, in globe radii (so strokes are fractions of a radius). The
   layer sits under the globe's canvas, and shows only once the globe is
   drawing, so the stand never arrives before the globe it holds. */
.world__desk {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  display: none;
}
.world[data-globe="on"] .world__desk { display: block; }
.world__desk-shine { stop-color: #F7DC8E; }
.world__desk-metal { stop-color: #D3A13C; }
.world__desk-deep { stop-color: #9A6A22; }
.world__desk-band { fill: none; stroke-width: .06; }
.world__desk-ink { fill: none; stroke: #3A2A18; stroke-width: .011; }
.world__desk-ticks { fill: none; stroke: #3A2A18; stroke-width: .008; stroke-opacity: .55; }
.world__desk-metal-fill { stroke: #3A2A18; stroke-width: .011; }
.world__desk-wood { fill: #8A5530; stroke: #3A2A18; stroke-width: .011; }
.world__desk-wood--top { fill: #A96C3B; }
.world__desk-shadow { fill: rgba(26, 22, 19, .22); stroke: none; }
:root[data-theme="dark"] .world__desk-shadow { fill: rgba(0, 0, 0, .45); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .world__desk-shadow { fill: rgba(0, 0, 0, .45); } }
/* A label that would run off the stage's right edge goes to the left. */
.world__pin[data-flip] .world__pin-label { left: auto; right: calc(var(--r) + 5px); }
/* A label shows only where globe.js placed it (data-label): a lit pin's
   label used to show regardless, across whatever group it hit (#53 round 2:
   "Michigan" on a "4"). The lit pin is placed first, so it rarely loses. */
.world__pin[data-label] .world__pin-label { display: block; }
.world__pin[data-on] .world__pin-label { background: rgba(6, 10, 18, .9); box-shadow: 0 0 0 1px var(--pin-hot); }
.world__pin[data-on], .world__pin[data-selected] { z-index: 2; }

.world__cluster-n {
  position: absolute;
  left: calc(var(--r) * -1);
  top: calc(var(--r) * -1);
  width: calc(var(--r) * 2);
  height: calc(var(--r) * 2);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--pin);
  color: #1A1613;
  font: 700 13px/1 var(--sans);
  box-shadow: 0 0 0 2px var(--pin-ring), 0 0 0 7px rgba(227, 184, 99, .28), 0 0 14px 4px rgba(227, 184, 99, .4);
  pointer-events: none;
}
.world__cluster[data-on] .world__cluster-n,
.world__cluster[data-selected] .world__cluster-n { background: var(--pin-hot); }
.world__cluster[data-dim] .world__cluster-n { background: #8A8F99; }
/* A group of countries or schools on a page that counts degrees: hollow, as
   a country's own light is, so its number is not read as degrees. */
.world__cluster[data-places]:not([data-on]):not([data-selected]) .world__cluster-n {
  background: rgba(8, 12, 20, .55);
  color: #FBF7F0;
  box-shadow: inset 0 0 0 2.5px var(--pin), 0 0 0 1.5px var(--pin-ring), 0 0 12px 3px rgba(227, 184, 99, .35);
}

/* The card: what was chosen, how much is there, and the one way on. A site
   surface, so it follows the theme. */
.world__card {
  position: absolute;
  z-index: 3;
  top: var(--s3);
  left: var(--s3);
  width: min(19rem, calc(100% - 5.5rem));
  max-height: calc(100% - 1.5rem);
  overflow: auto;
  padding: var(--s3) var(--s4) var(--s4);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-lg);
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .35);
  cursor: auto;
  animation: world-card-in var(--motion-overview-to-detail) both;
}
.world__card[hidden] { display: none; }
@keyframes world-card-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
/* Over the corner of the card — and of its photograph, when it has one — on
   a small disc of the card's own paper, so it reads on either. */
.world__card-close {
  position: absolute;
  z-index: 1;
  top: .35rem;
  right: .35rem;
  min-width: 2rem;
  min-height: 2rem;
  border: 0;
  border-radius: 50%;
  background: color-mix(in oklab, var(--paper) 88%, transparent);
  color: var(--ink-soft);
  font: 400 1.3rem/1 var(--sans);
  cursor: pointer;
}
.world__card-close:hover { color: var(--accent); }
.world__card-img {
  display: block;
  width: calc(100% + 2 * var(--s4));
  max-width: none;
  margin: calc(var(--s3) * -1) calc(var(--s4) * -1) var(--s3);
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  /* The space is the paper's own tone while the photograph loads, not a
     blank grey box (round 3). */
  background: var(--paper-3);
  opacity: 0;
  transition: opacity .15s ease;
}
.world__card-img[data-in] { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .world__card-img { transition: none; } }
:root[data-motion="reduced"] .world__card-img { transition: none; }
.world__card-title { margin: 0 0 .25rem; padding-right: 1.75rem; font-size: 1.05rem; line-height: 1.25; }
.world__card-img + .world__card-title { padding-right: 0; }
.world__card-title:focus { outline: none; }
.world__card-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.world__card-meta { margin: 0 0 .35rem; font-size: .8125rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.world__card-cue, .world__card-where { margin: 0 0 .35rem; font-size: .75rem; color: var(--ink-mute); }
/* The card is the link (#53): the title's link stretches over the whole card,
   so a click anywhere on it follows the link. Its one name is the title and
   the way on; nothing interactive is inside it. The close button and a list
   of places sit above the cover and stay buttons of their own. */
.world__card-link { color: inherit; text-decoration: none; }
.world__card-link::after { content: ""; position: absolute; inset: 0; z-index: 0; border-radius: inherit; }
.world__card[data-link] { cursor: pointer; }
.world__card[data-link]:hover { border-color: var(--accent); }
/* The one sign that it goes somewhere: an arrow after the title, which
   moves a little under the pointer. No "Open … →" line (the owner, #53). */
.world__card-arrow { display: inline-block; margin-left: .35em; color: var(--accent); font-weight: 600; transition: transform .15s ease; }
.world__card[data-link]:hover .world__card-arrow { transform: translateX(3px); }
.world__card-link:focus-visible { outline: none; }
.world__card-link:focus-visible::after { outline: 3px solid var(--accent); outline-offset: -3px; }
.world__card-list { position: relative; z-index: 1; list-style: none; margin: .25rem 0 .35rem; padding: 0; display: flex; flex-wrap: wrap; gap: .35rem; }
.world__card-list button {
  padding: .2rem .6rem;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  font: 600 .75rem/1.4 var(--sans);
  cursor: pointer;
}
.world__card-list button:hover { border-color: var(--accent); color: var(--ink); }
/* On a phone (globe.js placeFurniture) the card is a sheet below the stage,
   full width, so the places just chosen own the whole stage (#53 round 1:
   the card covered 61% of it), and the camera buttons are a row above it,
   off the ring. */
.world > .world__card {
  position: relative;
  top: auto; left: auto;
  width: auto;
  max-height: none;
  overflow: visible;
  margin-top: calc(var(--s2) - var(--s5));
  box-shadow: var(--lift-1, 0 2px 8px rgba(0, 0, 0, .12));
}
.world > .world__card .world__card-img { aspect-ratio: 5 / 2; }
/* Until the globe has put its buttons above the stage, their row's room is
   kept, so the stage does not jump down when they arrive. */
@media (max-width: 44rem) {
  :root[data-js] .world:not([data-furniture]):not([data-globe="off"]) .world__stage { margin-top: calc(var(--tap) + var(--s2)); }
}
.world > .world__controls {
  position: static;
  flex-direction: row;
  justify-content: flex-end;
  /* A row of its own, not tucked into the stage's top: on a stage whose
     height sets the globe's size (/countries/ on a phone) the ring reaches
     the top, and "+ − Reset" sat on it (round 5, bug 4). */
  margin-bottom: var(--s2);
}

.world__hint {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: var(--s4);
  transform: translateX(-50%);
  margin: 0;
  padding: .35rem .8rem;
  border-radius: 999px;
  background: rgba(6, 10, 18, .82);
  color: #F3ECE0;
  font: 600 .8125rem/1.3 var(--sans);
  white-space: nowrap;
  pointer-events: none;
}
.world__hint[hidden] { display: none; }
.world__credit {
  position: absolute;
  right: var(--s2);
  bottom: var(--s1);
  margin: 0;
  font-size: var(--type-floor);
  color: var(--ink-mute);
}
.world__credit a { color: inherit; text-decoration: none; }
.world__credit a:hover { text-decoration: underline; }

/* Reduced motion: the globe still arrives, it just does not fade in, and the
   card does not slide. The camera itself is instant in globe.js. */
@media (prefers-reduced-motion: reduce) {
  .world__globe, .world__pin-dot { transition: none; }
  .world__card { animation: none; }
}
:root[data-motion="reduced"] .world__globe,
:root[data-motion="reduced"] .world__pin-dot { transition: none; }
:root[data-motion="reduced"] .world__card { animation: none; }

/* --- MapChapter ----------------------------------------------------------- */

/* A journey is a run of chapters and nothing else. The alternation is scoped to
   it rather than to `:nth-child`, so a chapter used on its own somewhere never
   silently flips sides because of what happens to sit above it. */
.journey { display: grid; }

.chapter {
  display: grid;
  gap: var(--s6);
  align-items: center;
  padding-block: clamp(var(--s7), 6vw, var(--s9));
  border-top: 1px solid var(--rule);
}
@media (min-width: 60rem) {
  .chapter { grid-template-columns: 24rem minmax(0, 1fr); gap: var(--s8); }
  .journey > .chapter:nth-of-type(even) { grid-template-columns: minmax(0, 1fr) 24rem; }
  .journey > .chapter:nth-of-type(even) .chapter__text { order: 2; }
}
/* A chapter with no camera and no picture is one reading column, centred,
   rather than a text column with a hole beside it. */
.chapter--solo { justify-items: center; text-align: center; }
.chapter--solo .chapter__text { max-width: var(--measure); }
.chapter--solo .chapter__q { justify-content: center; }
@media (min-width: 60rem) {
  .chapter--solo, .journey > .chapter--solo:nth-of-type(even) { grid-template-columns: minmax(0, 1fr); }
}

.chapter__num {
  font: 600 .75rem/1 var(--mono);
  letter-spacing: .12em;
  color: var(--art, var(--accent));
  margin: 0 0 var(--s3);
}
/* The question this chapter asks. It is the chapter's eyebrow, and it is set as
   a question because the editorial rhythm is a sequence of questions — where,
   what, what does it cost, when do I act — rather than a sequence of topics. */
.chapter__q {
  font: 600 .75rem/1.3 var(--sans);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: 0 0 var(--s3);
  display: flex; align-items: center; gap: var(--s3);
}
.chapter h2 { font-size: clamp(1.6rem, 1.2rem + 1.5vw, 2.3rem); }
/* What the lights currently add up to, in a sentence. The count and the picture
   change together, and this is the sentence that says what changed. */
.chapter__scope {
  font-size: .875rem;
  color: var(--ink-soft);
  border-left: 2px solid var(--art, var(--rule-strong));
  padding-left: var(--s4);
  margin-bottom: var(--s5);
}
.chapter__go { margin: 0; }
.chapter__figure { margin: 0; }
.chapter__stage img {
  width: 100%;
  border-radius: var(--radius-lg);
}
/* The credit travels with the picture rather than living only on /credits/.
   A photographer whose name is one click away on a page nobody opens has not
   really been credited. */
.chapter__figure figcaption {
  margin-top: var(--s2);
  font-size: var(--type-floor);
  color: var(--ink-mute);
}

/*
 * A chapter's map does not animate, and two rules from trying to make it are
 * worth keeping.
 *
 * **Do not animate a transform on the world window.** This briefly carried the
 * `geographic` motion token — each chapter's frame settling into its camera
 * target once on load. A CSS transform promotes its element to a composited
 * layer, and here that layer holds a thousand coastline paths, rasterised again
 * for the animation; the performance contract is explicit that visual ambition
 * must not cost the core tool on modest mobile hardware, and this cost it on
 * first paint for an effect triggered by nothing the reader did. The token went
 * with it; `src/lib/motion.mjs` carries the full record.
 *
 * **Nothing that carries information fades in from nothing.** An entrance
 * animation written `from { opacity: 0 }` with `fill: both` hides its own
 * content until the animation has run, and an animation is not a thing you get
 * to assume has run — a backgrounded tab pauses them, which was caught here by
 * reading a computed style that still said `playState: running, opacity: 0` two
 * and a half seconds after load. EXPERIENCE_PRINCIPLES.md already says it for
 * the cases that matter most: never make eligibility, deadlines or source links
 * depend on animation completion. So the keyframes below move things and never
 * reveal them, and at worst a reader sees a panel a few pixels short of where
 * it settles with every word of it legible.
 *
 * `world-in` above is the one exception and a deliberate one. It fades markers,
 * which are `aria-hidden` decoration whose every fact — name, count, placement
 * cue — is already in the list underneath, so a marker that has not arrived yet
 * costs a reader a picture and no information. It also cannot move a transform
 * instead, because the script writes transforms on those same nodes.
 */

/* --- OpportunityTeaser ---------------------------------------------------- */

.teaser__where {
  margin: 0;
  font-size: .875rem;
  color: var(--ink-mute);
}
.teaser__fit {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s2);
}
.teaser__reason {
  font-size: .8125rem;
  color: var(--ink-soft);
}
.teaser__evidence[data-level="needs-review"],
.teaser__evidence[data-level="stale"],
.teaser__evidence[data-level="conflicting"],
.teaser__evidence[data-level="none"] { color: var(--warn); }

/* --- FilterQuestion ------------------------------------------------------- */

.filter-q { border: 0; padding: 0; margin: 0; display: grid; gap: var(--s2); }
.filter-q legend,
.filter-q label {
  font: 650 .8125rem/1.4 var(--sans);
  letter-spacing: .01em;
  text-transform: none;
  color: var(--ink);
  padding: 0;
}
.filter-q__help {
  margin: 0;
  font-size: .75rem;
  color: var(--ink-mute);
}

/* The same question asked as a scene rather than as a control. It is the first
   thing a student is asked on the site, so it is set in the display face at a
   size that says so — and the answers are links, sized like something you press
   with a thumb rather than like a filter in a rail. */
.filter-q--links { gap: var(--s4); }
.filter-q__question {
  font-family: var(--display);
  font-size: clamp(1.375rem, 1.1rem + 1vw, 1.9rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.012em;
  font-variation-settings: "SOFT" 0, "WONK" 1, "opsz" 28;
  margin: 0;
  color: var(--ink);
}
.filter-q--links .filter-q__help { font-size: .875rem; max-width: 62ch; }
.filter-q--links .chips { gap: var(--s3); }
.filter-q--links .chip { padding: .5rem 1rem; font-size: .9375rem; }
.chip__count {
  font-variant-numeric: tabular-nums;
  opacity: .65;
  font-weight: 400;
}

/* --- FitExplanation ------------------------------------------------------- */

.fit {
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--s5);
  margin: var(--s6) 0;
  background: var(--paper);
  animation: fit-in var(--motion-overview-to-detail) both;
}
/* Transform only — see the note on camera-arrive. This panel carries the
   eligibility outcome, and the principle that outcome must never wait on an
   animation is written down rather than implied. */
@keyframes fit-in {
  from { transform: translateY(6px); }
  to   { transform: none; }
}

.fit__head { margin-bottom: var(--s4); }
.fit__outcome {
  font-family: var(--display);
  font-size: 1.375rem;
  font-weight: 600;
  margin: 0;
  font-variation-settings: "SOFT" 0, "WONK" 1, "opsz" 24;
}
.fit[data-outcome="meets"] { border-left: 3px solid var(--ok); }
.fit[data-outcome="meets"] .fit__outcome { color: var(--ok); }
.fit[data-outcome="possible-with-action"] { border-left: 3px solid var(--sand); }
.fit[data-outcome="needs-review"] { border-left: 3px solid var(--warn); }
.fit[data-outcome="needs-review"] .fit__outcome { color: var(--warn); }
.fit[data-outcome="does-not-currently-meet"] { border-left: 3px solid var(--rule-strong); }

.fit__blocked {
  margin: var(--s2) 0 0;
  font-size: .875rem;
  color: var(--warn);
}
.fit__group { margin-bottom: var(--s5); }
.fit__group h4 {
  font: 650 .75rem/1.4 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: var(--s2);
}
.fit__group ul { margin: 0; padding-left: 1.3em; font-size: .9375rem; }
.fit__group li { margin-bottom: var(--s2); }
.fit__group--met li::marker { color: var(--ok); }
.fit__group--gap li::marker { color: var(--warn); }
.fit__group--unknown li::marker { color: var(--ink-mute); }
.fit__actionable { color: var(--ok); font-size: max(var(--type-floor), .875em); }
.fit__caveat,
.fit__provenance {
  font-size: .8125rem;
  color: var(--ink-mute);
  margin: var(--s4) 0 0;
}
.fit__caveat { color: var(--warn); }
.fit__assumptions ul { margin: 0; padding-left: 1.3em; font-size: .8125rem; color: var(--ink-soft); }
.fit__selection {
  border-top: 1px solid var(--rule);
  padding-top: var(--s4);
  margin-top: var(--s4);
  font-size: .9375rem;
}
.fit__selection h4 {
  font: 650 .75rem/1.4 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

/* --- EvidenceBlock -------------------------------------------------------- */

.evidence {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  margin: var(--s5) 0;
  font-size: .875rem;
  background: var(--paper-2);
}
.evidence > summary {
  cursor: pointer;
  padding: var(--s3) var(--s4);
  font-weight: 600;
  list-style: none;
  position: relative;
  padding-right: 2.2rem;
}
.evidence > summary::-webkit-details-marker { display: none; }
.evidence > summary::after {
  content: "+";
  position: absolute; right: var(--s4); top: 50%; transform: translateY(-50%);
  font-weight: 400; color: var(--ink-mute);
}
.evidence[open] > summary::after { content: "–"; }
.evidence > :not(summary) { padding-inline: var(--s4); }
/* The audit drawer opening is `overview-to-detail` by the token's own wording —
   "moving from a card or marker into the thing itself, a panel opening". It
   runs when the panel is rendered, which is when the student opened it, and
   under reduced motion the token is 1ms, so the sources are simply there. */
.evidence[open] > :not(summary) {
  animation: detail-open var(--motion-overview-to-detail) both;
}
/* Transform only — see the note on camera-arrive. This drawer is where the
   source links live. */
@keyframes detail-open {
  from { transform: translateY(-4px); }
  to   { transform: none; }
}
.evidence > :last-child { padding-bottom: var(--s4); }
.evidence--conflict { border-color: var(--warn); background: var(--warn-tint); }
.evidence--none { border-style: dashed; }
.evidence__claim { font-weight: 600; }
.evidence__warning { color: var(--warn); font-weight: 500; }
.evidence__list { margin: 0; padding-left: 1.2em; }
.evidence__list > li { margin-bottom: var(--s4); }
.evidence__source { margin: 0 0 var(--s2); font-weight: 600; }
.evidence__type {
  font-size: .75rem; font-weight: 400; color: var(--ink-mute);
  border: 1px solid var(--rule-strong); border-radius: 999px; padding: 0 .45em;
}
.evidence__excerpt {
  margin: 0 0 var(--s2);
  padding-left: var(--s4);
  border-left: 2px solid var(--rule-strong);
  font-family: var(--display);
  font-size: .9375rem;
  font-style: italic;
  color: var(--ink-soft);
}
.evidence__meta,
.evidence__interpretation { margin: 0 0 var(--s2); font-size: .8125rem; color: var(--ink-mute); }

/* --- PreparationPath ------------------------------------------------------ */

.prep {
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--s5);
  margin-bottom: var(--s4);
  background: var(--paper);
}
.prep__head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3);
  margin-bottom: var(--s3);
}
.prep__timing { font-size: .8125rem; color: var(--ink-mute); }
/* Where a Preparation Action actually applies. Loud enough that a student
   aiming somewhere else stops reading it as advice about their own
   application, quiet enough not to compete with the relevance tag beside it. */
.prep__scope {
  font-size: .75rem; font-weight: 600; letter-spacing: .01em;
  color: var(--ink-soft);
  padding: .2em .5em;
  border: 1px dashed var(--rule-strong);
  border-radius: var(--radius);
}
.prep h4 { margin-bottom: var(--s2); }
.prep__caveat,
.prep__evidence { font-size: .8125rem; color: var(--ink-mute); margin: var(--s3) 0 0; }
.prep[data-relevance="required"] { border-left: 3px solid var(--warn); }
.prep[data-relevance="selection"] { border-left: 3px solid var(--sand); }
.prep[data-relevance="preparation"] { border-left: 3px solid var(--brand-2); }

/* --- ComparisonTray ------------------------------------------------------- */

.tray__limit,
.tray__note { font-size: .8125rem; color: var(--ink-mute); }
.tray__table th[scope="row"] { width: 13rem; font-weight: 600; }
.tray__table tr[data-same="true"] td { color: var(--ink-mute); }
.tray__missing { color: var(--ink-mute); font-style: italic; }

/* Below the width where a wide table stops being legible, the comparison
   becomes one card per opportunity rather than a squeezed grid.

   This transform used to be written out here against `.tray__table`. It is not
   a fact about the comparison tray; it is a fact about any table whose cells
   carry the column they came from, and #34 found a second wide table on the
   same page that wanted it and could not have it. It now lives in site.css
   section 12, keyed on `td[data-label]` — which is the thing the tray actually
   does that makes the transform possible — so this component gets it by
   satisfying the condition rather than by being named. */

/* --- States --------------------------------------------------------------- */

.state {
  padding: var(--s4) var(--s5);
  border-radius: var(--radius);
  font-size: .9375rem;
  margin: var(--s5) 0;
}
.state p { margin: 0 0 var(--s2); }
.state p:last-child { margin-bottom: 0; }
.state--loading { color: var(--ink-mute); }
.state--empty {
  border: 1px dashed var(--rule-strong);
  color: var(--ink-mute);
  text-align: center;
  padding-block: var(--s7);
}
.state--stale { background: var(--warn-tint); border-left: 3px solid var(--warn); }
.state--conflict { background: var(--warn-tint); border-left: 3px solid var(--warn); }
.state--error { background: var(--accent-tint); border-left: 3px solid var(--accent); }
.state--nomap { color: var(--ink-mute); font-size: .8125rem; }

/* --- Exploration shell ---------------------------------------------------- */

.shell__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3) var(--s5);
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--s5);
  font-size: .8125rem;
  color: var(--ink-mute);
}
.shell__views { display: flex; gap: var(--s2); margin-left: auto; }
.shell__active {
  display: flex; flex-wrap: wrap; gap: var(--s2);
  list-style: none; margin: 0; padding: 0;
}
/* An active-filter chip is the receipt for an action: it appears because the
   student narrowed something, and it disappears when they clear it. Same token
   as the chip that produced it, for the same reason — and under reduced motion
   it simply appears, already readable, next to a live count that says the new
   total out loud. */
.shell__active li {
  display: inline-flex; align-items: center; gap: .3em;
  background: var(--brand-tint); color: var(--brand-2);
  border-radius: 999px; padding: var(--pill);
  font-size: .75rem; font-weight: 600;
  animation: action-landed var(--motion-action-complete) both;
}
/* Background only, for the reason in the note on camera-arrive: this chip says
   which constraint is currently narrowing the results, and a chip that has not
   faded in yet beside a count that has already changed is a page telling a
   reader two different things. Unfilled, it is still a legible word. */
@keyframes action-landed {
  from { background-color: transparent; }
  to   { background-color: var(--brand-tint); }
}

/* --- Motion preference control -------------------------------------------- */

.motion-toggle {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-size: .75rem; color: var(--ink-mute);
}

/* A place with nothing in the current filter set stays visible but recedes —
   disappearing markers make the map feel broken rather than filtered. */
.world__list a[data-dim="true"] {
  opacity: .28;
}
.world__list a[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--ink);
  background: var(--accent-tint);
}
.chip--inline { padding: var(--pill); font-size: .75rem; }

.shell__active button {
  border: 0;
  background: none;
  padding: 0 0 0 .2em;
  cursor: pointer;
  color: inherit;
  font-size: 1em;
  line-height: 1;
}
.shell__active button:hover { color: var(--accent); }

/*
 * The one control that keeps its ink and takes its target as an overlay.
 *
 * This is the × that dismisses an active-filter chip. It sits *inside* a pill
 * that is 24px tall, and the control policy would make it 44 — which does not
 * make the chip tappable, it makes the chip 44px tall with a word floating in
 * it, and the receipt row that reads as a line of small confirmations becomes a
 * stack of boxes. The whole pill is only about 90x24.
 *
 * So the button stays the size of the glyph and grows a transparent 44px
 * square, centred on itself, that nothing can see and a thumb can hit. Two
 * neighbouring × targets can overlap where chips are tight; each is still
 * centred on its own chip, so the nearest one wins, which is the right answer.
 *
 * It has to outrank the policy: the policy is `:root :is(…)` at (0,2,0), and
 * `:root .shell__active button` is (0,2,1). If a future edit lowers the policy
 * back to a bare selector list this rule keeps working; if it raises the policy
 * this rule stops, silently, and the × goes back to being 44px tall. That is
 * the failure mode worth knowing about.
 */
@media (pointer: coarse) {
  :root .shell__active button {
    min-height: 0;
    min-width: 0;
    position: relative;
  }
  :root .shell__active button::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: var(--tap); height: var(--tap);
  }
}

/* The selected place's programmes are marked in the list, so the connection
   between the two views is visible rather than implied. */
.prog[data-place][data-focus="true"] {
  background: var(--accent-tint);
  margin-inline: calc(var(--s4) * -1);
  padding-inline: var(--s4);
  border-radius: var(--radius);
}

/* Coverage is how much we know, never a quality score. */
.coverage {
  display: inline-block;
  padding: var(--pill);
  border-radius: 999px;
  font: 600 .75rem/1.5 var(--mono);
  border: 1px solid var(--rule-strong);
}
.coverage--good { color: var(--ok); border-color: var(--ok); }
.coverage--part { color: var(--warn); border-color: var(--warn); }
.coverage--thin { color: var(--ink-mute); }

.tray__coverage { color: var(--warn); }
.tray__uncertain { color: var(--ink-mute); font-style: italic; }
.tray__table th[scope="row"] small { font-weight: 400; color: var(--ink-mute); }

/* Academic eligibility answers a different question from the rest, so it is
   set apart rather than sitting in a list of equals. */
.tray__table tr[data-dimension="academic"] {
  border-bottom: 2px solid var(--rule-strong);
}
.tray__table tr[data-dimension="academic"] th[scope="row"] { color: var(--brand-2); }

/* =========================================================================
   Deadlines: "next up" (docs/research/ia/plan.md, Batch 4)
   -------------------------------------------------------------------------
   The page opens on which countries, a month strip, and the next ten dates;
   every other date is one tap down. Appended here rather than in site.css,
   which another piece of work is editing.
   ========================================================================= */

.cal-scope { gap: var(--s3); }
.cal-scope__q { margin: 0; font: 600 .9375rem/1.4 var(--sans); color: var(--ink); }

/* The country chips. A wrapped block on a wide screen; on a phone one row
   that scrolls sideways, so thirty-six countries cost one line, not a screen. */
.cal-chips { display: flex; flex-wrap: wrap; gap: var(--s2); }
@media (max-width: 40rem) {
  .cal-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding-bottom: var(--s2);
    margin-inline: calc(var(--s4) * -1);
    padding-inline: var(--s4);
  }
  .cal-chip { flex: none; scroll-snap-align: start; }
  /* Nothing chosen yet: the chips are the whole page, so all of them show. */
  .cal-scope[data-chosen="false"] .cal-chips { flex-wrap: wrap; overflow-x: visible; }
}
.cal-chip { position: relative; align-items: center; white-space: nowrap; }
/* The box is the state, the chip is the target: the checkbox stays in the
   accessibility tree and takes focus, and the chip shows both. */
.cal-chip > input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.cal-chip:has(> input:checked) { background: var(--brand); border-color: var(--brand); color: var(--paper); }
:root[data-theme="dark"] .cal-chip:has(> input:checked) { color: #10201D; }
.cal-chip:has(> input:focus-visible) { outline: 2px solid var(--focus, var(--accent)); outline-offset: 2px; }

/* The month strip: one bar per month, its height the number of dates in scope. */
.cal-months { margin: var(--s5) 0 var(--s4); overflow-x: auto; }
.cal-months ol {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(2.5rem, 1fr);
  gap: var(--s1); list-style: none; margin: 0; padding: 0;
}
.cal-months a {
  display: grid; justify-items: center; gap: 2px;
  padding: var(--s1) 0;
  text-decoration: none; color: var(--ink-soft);
  border-radius: var(--radius);
}
.cal-months a:hover { background: var(--paper-2); color: var(--ink); }
.cal-months__bar { display: flex; align-items: flex-end; width: 60%; height: 2.5rem; }
.cal-months__bar > span {
  display: block; width: 100%;
  height: max(2px, calc(var(--h, 0) * 100%));
  background: var(--brand-2); border-radius: 2px 2px 0 0;
}
.cal-months__m { font: 600 .75rem/1.2 var(--sans); }
.cal-months__n { font: 400 .75rem/1.2 var(--mono); color: var(--ink-mute); }
.cal-months li[data-empty="true"] .cal-months__bar > span { background: var(--rule); }
.cal-months li.is-now .cal-months__m { color: var(--accent); }
.cal-months li.is-now .cal-months__m::after { content: " ·"; }

.cal-next__title { margin-top: var(--s5); }
.cal-next__count { font: 400 .875rem/1.4 var(--sans); color: var(--ink-mute); margin-left: var(--s2); }
#cal-next .timeline { margin-top: var(--s4); }

.cal-group { margin: var(--s4) 0; border-top: 1px solid var(--rule); padding-top: var(--s3); }
.cal-group > summary { cursor: pointer; font: 600 1rem/1.5 var(--sans); color: var(--accent); }
.cal-group__n { font-weight: 400; color: var(--ink-mute); }
.cal-month { margin-top: var(--s5); font-size: 1.125rem; }

/* =========================================================================
   Denmark sub-pages (docs/research/ia/plan.md, Batch 5)
   -------------------------------------------------------------------------
   The conversion tools sit first; the apply steps carry their date as a
   label above the action; the SU routes are a short stack of disclosures.
   ========================================================================= */

.conv-tool { margin-bottom: var(--s4); }
.conv-tool__stamp { margin: 0 0 var(--s5); }

.apply-steps { margin-top: var(--s4); }
.apply-steps > li { padding-bottom: var(--s5); }
.apply-steps h3 { font-size: 1.125rem; }
.apply-step__when {
  display: block;
  font: 600 .8125rem/1.4 var(--sans);
  letter-spacing: .02em;
  color: var(--accent);
  margin-bottom: 2px;
}
.apply-steps .topic__more { margin-top: var(--s2); }

.su-routes { margin: var(--s3) 0 var(--s4); }
.su-routes > .topic__more { margin-top: 0; }

/* The Denmark money table: two columns at every width. It opts out of the
   phone card-stack (`table.data`) on purpose, because each row is a name and a
   number, and a stacked row would put a label in front of each. */
.cost-table { width: 100%; border-collapse: collapse; font-size: .9375rem; margin: var(--s3) 0 var(--s5); }
.cost-table caption { caption-side: bottom; text-align: left; padding-top: var(--s2); font-size: .8125rem; color: var(--ink-mute); }
.cost-table th { text-align: left; font: 600 .8125rem/1.4 var(--sans); color: var(--ink-mute); padding: var(--s2) var(--s3) var(--s2) 0; border-bottom: 1px solid var(--rule-strong); }
.cost-table td { vertical-align: top; padding: var(--s3) var(--s3) var(--s3) 0; border-bottom: 1px solid var(--rule); }
.cost-table td:first-child small { display: block; color: var(--ink-mute); font-size: .8125rem; line-height: 1.4; margin-top: 2px; }
.cost-table td:last-child { font-variant-numeric: tabular-nums; width: 42%; }

/* =========================================================================
   Counsellors (docs/research/ia/plan.md, Batch 7): the conversation order as
   a four-step strip, and the four things students get wrong as cards.
   ========================================================================= */

.prose .conv-strip {
  counter-reset: conv; list-style: none; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: var(--s2);
  margin: var(--s4) 0 var(--s3);
}
.conv-strip > li {
  counter-increment: conv;
  display: grid; gap: var(--s1); align-content: start;
  margin: 0; padding: var(--s3);
  background: var(--paper-2); border-radius: var(--radius);
  font-size: .875rem; line-height: 1.45; color: var(--ink-soft);
}
.conv-strip > li > strong { font-size: 1rem; color: var(--ink); }
.conv-strip > li > strong::before { content: counter(conv) " · "; color: var(--accent); }

.prose .pitfalls {
  list-style: none; padding: 0; margin: var(--s4) 0 var(--s6);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--s3);
}
.pitfalls > li {
  margin: 0; padding: var(--s4);
  border: 1px solid var(--rule); border-radius: var(--radius-lg); background: var(--paper);
}
.pitfalls > li > p { margin: var(--s2) 0 0; font-size: .9375rem; color: var(--ink-soft); }
.pitfalls details { margin-top: var(--s2); }
.pitfalls summary { cursor: pointer; font: 600 .8125rem/1.5 var(--sans); color: var(--accent); }
.pitfalls details > p { margin: var(--s2) 0 0; font-size: .9375rem; }

/* Trust: the report button and its no-account alternative, above the numbers. */
.trust-actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin: 0 0 var(--s5); }

/* =========================================================================
   Reference lists: glossary and credits (docs/research/ia/plan.md, Batch 7).
   A filter box, A–Z jumps, and one line per term with the rest on tap.
   ========================================================================= */

.lookup-filter { margin: 0 0 var(--s4); max-width: 28rem; }
.lookup-filter input { width: 100%; }
.prose .az { margin: 0 0 var(--s5); }
.az .chip { min-width: 2.25rem; justify-content: center; }

.gloss {
  display: block;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule);
  font-size: .9375rem; line-height: 1.5;
}
.gloss > summary { cursor: pointer; list-style: none; }
.gloss > summary::-webkit-details-marker { display: none; }
.gloss > summary::after { content: " +"; color: var(--accent); font-weight: 600; }
.gloss[open] > summary::after { content: " −"; }
.gloss dfn { font-style: normal; font-weight: 650; color: var(--ink); }
.gloss__where {
  font: 600 .75rem/1.4 var(--sans); letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-mute); margin: 0 var(--s1);
}
.gloss__def { color: var(--ink-soft); }
.gloss__more { margin-top: var(--s2); color: var(--ink-soft); }

/* =========================================================================
   Compare: the whole set as compact rows (docs/research/ia/plan.md, Batch 8).
   One line per Destination — name, first clause of EU/EEA tuition, coverage
   as dots — with the full cells behind the row. Three columns on a wide
   screen, two lines on a phone; never a stacked card per cell.
   ========================================================================= */

.set { margin-top: var(--s4); }
.set__sort { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin: 0 0 var(--s3); font-size: .875rem; color: var(--ink-mute); }
.set__head,
.set__row > summary {
  display: grid;
  grid-template-columns: minmax(10rem, 1.2fr) minmax(0, 2fr) 6.5rem;
  gap: var(--s3); align-items: baseline;
}
.set__head {
  margin: 0; padding: 0 var(--s3) var(--s2);
  border-bottom: 1px solid var(--rule-strong);
  font: 600 .75rem/1.4 var(--sans); letter-spacing: .04em; text-transform: uppercase; color: var(--ink-mute);
}
.set__row { border-bottom: 1px solid var(--rule); }
.set__row > summary {
  cursor: pointer; list-style: none;
  padding: var(--s3);
  font-size: .9375rem;
}
.set__row > summary::-webkit-details-marker { display: none; }
.set__row > summary:hover { background: var(--paper-2); }
.set__row[open] > summary { background: var(--paper-2); }
.set__name { font-weight: 650; color: var(--ink); }
.set__fee { color: var(--ink-soft); }
.set__cov { font: 400 .8125rem/1 var(--mono); letter-spacing: .08em; color: var(--brand-2); white-space: nowrap; }
.set__more { margin: 0; padding: 0 var(--s3) var(--s2); display: grid; gap: var(--s2); }
.set__more > div { display: grid; grid-template-columns: 11rem minmax(0, 1fr); gap: var(--s3); font-size: .875rem; }
.set__more dt { font-weight: 600; color: var(--ink-mute); }
.set__more dd { margin: 0; }
.set__links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); margin: 0; padding: 0 var(--s3) var(--s4); }

@media (max-width: 40rem) {
  .set__head { display: none; }
  .set__row > summary { grid-template-columns: minmax(0, 1fr) auto; row-gap: 2px; }
  .set__fee { grid-column: 1 / -1; grid-row: 2; font-size: .875rem; }
  .set__more > div { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

/* =========================================================================
   Deadlines & sessions: one school's dates, beside the school
   (src/lib/school-dates.mjs). On a wide screen it takes the top of the side
   column and the page's own aside sits under it; on a phone it comes first,
   before the prose, as a short block: three dates, the rest one tap away.
   ========================================================================= */

.dates-panel {
  border: 1px solid var(--rule);
  border-top: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  background: var(--paper);
  padding: var(--s4);
  font-size: .9375rem;
}
.dates-panel__title { font-size: 1.0625rem; margin: 0 0 var(--s3); }
.dates-panel__sub {
  font: 600 .75rem/1.4 var(--sans); letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-mute); margin: var(--s4) 0 var(--s2);
}
.dates-panel__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.dates-panel__item { padding-bottom: var(--s3); border-bottom: 1px solid var(--rule); }
.dates-panel__list > .dates-panel__item:last-child { border-bottom: 0; padding-bottom: 0; }
.dates-panel__item > p { margin: 0; }
/* The older school panel in site.css rules a line above every item; this
   panel rules one below, so one of the two goes. */
.dates-panel .dates-panel__list > li { border-top: 0; padding-top: 0; display: block; }
.dates-panel__when { font-weight: 650; color: var(--ink); }
.dates-panel__what { color: var(--ink); line-height: 1.4; }
.dates-panel__meta { margin-top: 2px !important; font-size: .8125rem; color: var(--ink-mute); }
/* The whole note: it is already behind its own "Note" disclosure, so opening
   it shows all of it rather than a clipped two lines. */
.dates-panel__note { font-size: .875rem; color: var(--ink-soft); line-height: 1.45; margin: var(--s1) 0 0; }
/* A date carried over or inferred says so on its date line, as /timeline/ does. */
.dates-panel__prov { font-weight: 400; color: var(--warn); }
.dates-panel__src { font-size: .8125rem; }
/* One row under each date: what missing it costs, the note behind a
   disclosure, and the source. Three short lines a date on a phone (about
   90px), not five. An opened note takes a line of its own under the row. */
.dates-panel__foot {
  display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--s3); row-gap: 0;
  margin-top: 2px; font-size: .8125rem;
}
.dates-panel__foot > .timeline__badge { margin: 0; }
/* Links a thumb has to hit: 24px tall at least (WCAG 2.5.8). The older school
   panel's rule in site.css strips the underline from every link in the list;
   these are links and look like links. */
.dates-panel .dates-panel__foot > .dates-panel__src,
.dates-panel .dates-panel__more a {
  display: inline-flex; align-items: center; min-height: 24px; min-width: 24px;
  color: var(--accent-2); text-decoration: underline; text-underline-offset: 2px;
}
/* A date's note is one tap away: the same country-wide line on every school. */
.dates-panel__why { font-size: .8125rem; }
.dates-panel__why > summary { cursor: pointer; color: var(--ink-mute); width: max-content; min-height: 24px; line-height: 24px; }
.dates-panel__why[open] { order: 1; flex-basis: 100%; }
.dates-panel__why[open] > summary { color: var(--ink-soft); }
.dates-panel__all { margin-top: var(--s3); border-top: 1px solid var(--rule); padding-top: var(--s3); }
.dates-panel__all > summary { cursor: pointer; font-weight: 600; color: var(--accent-2); }
.dates-panel__all > .dates-panel__list { margin-top: var(--s3); }
.dates-panel__empty { margin: 0; color: var(--ink-soft); }
.dates-panel__more { margin: var(--s4) 0 0; font-size: .875rem; }

@media (min-width: 62rem) {
  .layout-aside--dates { grid-template-rows: auto 1fr; }
  .layout-aside--dates > .dates-panel { grid-column: 2; grid-row: 1; }
  .layout-aside--dates > :not(.dates-panel):not(.layout-aside__side) { grid-column: 1; grid-row: 1 / span 2; }
  .layout-aside--dates > .layout-aside__side { grid-column: 2; grid-row: 2; }
}

/* --- Globe round 6 (#53): flags, per-nation bubbles, the hovered outline ---
 *
 * Flags are small SVG pictures (src/assets/img/flags, scripts/make-flags.mjs),
 * never emoji: Windows draws a flag emoji as two letters ("GB United
 * Kingdom"). A thin ring keeps a white flag's edge on white paper.
 */
.flag-img, .world__flag {
  display: inline-block;
  width: 18px;
  height: 12px;
  max-width: none;
  border-radius: 2px;
  object-fit: cover;
  vertical-align: -1px;
  box-shadow: 0 0 0 1px rgba(26, 22, 19, .22);
}
.world__name .flag-img { margin-right: .35em; }
.world__pin-label .world__flag { margin-right: 5px; vertical-align: -2px; box-shadow: 0 0 0 1px rgba(251, 247, 240, .55); }
.world__card-title .world__flag { width: 21px; height: 14px; margin-right: .4em; }
/* A bubble of one country, or of two or three, shows their flags under its
   number; one country's bubble whose name has room shows it beside it, like
   a pin's label, with the flag in the label instead. */
.world__cluster-flags {
  position: absolute;
  left: 0;
  top: calc(var(--r) + 3px);
  transform: translateX(-50%);
  display: none;
  gap: 2px;
  pointer-events: none;
}
.world__cluster[data-flags] .world__cluster-flags { display: flex; }
.world__cluster[data-label] .world__cluster-flags { display: none; }
.world__cluster-flags .world__flag { width: 15px; height: 10px; box-shadow: 0 0 0 1px rgba(251, 247, 240, .9), 0 1px 3px rgba(0, 0, 0, .5); }
.world__cluster[data-label] .world__cluster-label { display: block; }
.world__cluster[data-flip] .world__cluster-label { left: auto; right: calc(var(--r) + 5px); }
.world__cluster[data-on] .world__cluster-label { background: rgba(6, 10, 18, .9); box-shadow: 0 0 0 1px var(--pin-hot); }
.world__cluster[data-on], .world__cluster[data-label] { z-index: 1; }
/* The hovered country's outline (globe.js drawOutline): an ink under-stroke
   and a paper line, and a faint wash when the whole country faces the camera,
   so it reads on pastel land, sea and cloud alike. Decorative, like the pins.
   Over the bubbles, not under them: a small country is mostly under its own
   bubble, and the outline is what says which country the bubble is. */
.world__outline {
  position: absolute;
  z-index: 3;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.world__outline path { fill: none; stroke-linejoin: round; stroke-linecap: round; }
.world__outline-under { stroke: rgba(26, 22, 19, .6); stroke-width: 4; }
.world__outline-over { stroke: #FBF7F0; stroke-width: 2; }
.world__outline:not([data-part]) .world__outline-over { fill: rgba(251, 247, 240, .1); }
.world__pin[data-plain] .world__pin-label .world__flag { display: none; }
/* Flags are a detail, not the map (owner, 30 Sep): a label names its place in
   words, and its country's flag appears only on the one hovered or chosen. */
.world__pin-label .world__flag { display: none; }
.world__pin:is(:hover, [data-on], [data-selected]):not([data-plain]) .world__pin-label .world__flag,
.world__cluster:is(:hover, [data-on], [data-selected]) .world__pin-label .world__flag { display: inline-block; }
.world__cluster[data-dim] .world__cluster-n { transform: scale(.55); opacity: .7; }

/* --- Globe levels (owner, 6 October 2026) ----------------------------------
 *
 * A page of whole countries reads the globe in levels (globe.js "Levels"):
 * continents on the desk, a continent's countries, a country's universities.
 * Three kinds of node, so the level is legible at a glance: a continent is a
 * big brass-ringed coin with its name under it; a country a smaller coin with
 * its name beside it; a university a dot. Nothing groups; a node nudged off
 * its spot keeps a thread back to it.
 */
/* The target grows with the bubble: a continent's coin is bigger than a fingertip. */
.world[data-levels] .world__cluster::before {
  left: calc(-1 * max(16px, var(--r) + 4px));
  top: calc(-1 * max(16px, var(--r) + 4px));
  width: calc(2 * max(16px, var(--r) + 4px));
  height: calc(2 * max(16px, var(--r) + 4px));
}
@media (pointer: coarse) {
  .world[data-levels] .world__cluster::before {
    left: calc(-1 * max(var(--tap) / 2, var(--r) + 4px));
    top: calc(-1 * max(var(--tap) / 2, var(--r) + 4px));
    width: calc(2 * max(var(--tap) / 2, var(--r) + 4px));
    height: calc(2 * max(var(--tap) / 2, var(--r) + 4px));
  }
}
.world__cluster[data-kind] .world__cluster-n { transition: background-color .15s ease, transform .18s ease; }
.world__cluster[data-kind]:hover .world__cluster-n { transform: scale(1.08); }
.world__cluster[data-kind="region"] .world__cluster-n {
  font: 800 16px/1 var(--sans);
  letter-spacing: -.01em;
  background: radial-gradient(circle at 35% 30%, #F7DC8E, #E3B863 55%, #C9953A);
  box-shadow:
    0 0 0 2.5px #FBF7F0,
    0 0 0 4px rgba(58, 42, 24, .55),
    0 0 0 10px rgba(227, 184, 99, .22),
    0 4px 14px rgba(0, 0, 0, .35);
}
/* Its name, always under it: the bubble is the continent. */
.world__cluster[data-kind="region"] .world__cluster-label {
  left: 0;
  right: auto;
  top: calc(var(--r) + 7px);
  transform: translateX(-50%);
  padding: 3px 10px;
  font: 700 13px/1.3 var(--sans);
  letter-spacing: .02em;
}
.world__cluster[data-kind="country"] .world__cluster-n {
  font: 700 12px/1 var(--sans);
  box-shadow: 0 0 0 2px #FBF7F0, 0 0 0 3.25px rgba(58, 42, 24, .45), 0 2px 8px rgba(0, 0, 0, .35);
}
.world__cluster[data-kind="country"][data-selected] .world__cluster-n { background: var(--pin-hot); color: #1A1613; }
.world__cluster[data-kind][data-on] .world__cluster-n { background: var(--pin-hot); }
.world__cluster[data-kind][data-dim] .world__cluster-n { background: #8A8F99; transform: scale(.7); }
/* A university: a solid dot with an ink edge, in the cartoon's line. */
.world[data-levels] .world__pin[data-school] .world__pin-dot {
  box-shadow: 0 0 0 1.75px #FBF7F0, 0 0 0 2.75px rgba(58, 42, 24, .5), 0 1px 3px rgba(0, 0, 0, .45);
}
.world[data-levels] .world__pin[data-school][data-selected] .world__pin-dot,
.world[data-levels] .world__pin[data-school][data-on] .world__pin-dot {
  box-shadow: 0 0 0 2px #FBF7F0, 0 0 0 3px rgba(58, 42, 24, .55), 0 0 14px 4px rgba(244, 147, 95, .6);
}
/* The thread from a nudged node back to its true spot, and a seed at the spot. */
.world__threads {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  filter: drop-shadow(0 1px 1px rgba(26, 22, 19, .5));
}
.world__thread { fill: none; stroke: rgba(251, 247, 240, .9); stroke-width: 1.25; stroke-linecap: round; }
.world__thread-dot { fill: #E3B863; stroke: rgba(58, 42, 24, .7); stroke-width: 1; }

/* Where the globe is: "World › Europe › Denmark". Inside the stage's top-left
   corner on a wide stage (the card then sits under it); in the row with the
   camera buttons above the stage on a phone. */
.world__trail[hidden] { display: none; }
.world__stage > .world__trail { position: absolute; z-index: 3; top: var(--s3); left: var(--s3); max-width: calc(100% - 7rem); }
.world__controls > .world__trail { margin-right: auto; min-width: 0; }
.world__trail-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin: 0;
  padding: 2px 4px;
  list-style: none;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: var(--paper);
  box-shadow: var(--lift-1);
}
.world__trail-list li { display: inline-flex; align-items: center; }
.world__trail-list li + li::before { content: "›"; margin: 0 1px; color: var(--ink-mute); font-weight: 600; }
.world__trail-btn {
  min-height: 2rem;
  padding: 0 .55rem;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--accent);
  font: 600 .8125rem/1 var(--sans);
  cursor: pointer;
}
.world__trail-btn { display: inline-flex; align-items: center; gap: .35em; white-space: nowrap; }
.world__trail-icon { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.4; }
.world__trail-btn:hover .world__trail-word { text-decoration: underline; text-underline-offset: .2em; }
.world__trail-list { flex-wrap: nowrap; min-width: 0; }
.world__trail-here { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* On a levels page the trail's "World" is the way home: no Reset. */
.world[data-levels] .world__btn--wide { display: none; }
/* +/− as brass knobs, of a piece with the desk globe's ring (round 7). */
.world .world__btn:not(.world__btn--wide) {
  width: 2.25rem; height: 2.25rem; padding: 0; border-radius: 50%;
  border: 1.5px solid #3A2A18;
  background: radial-gradient(circle at 35% 30%, #F7DC8E, #D3A13C 60%, #9A6A22);
  color: #2A1C0E; font: 800 1.05rem/1 var(--sans);
  box-shadow: 0 2px 5px rgba(0, 0, 0, .3), inset 0 1px 0 rgba(255, 255, 255, .45);
}
.world .world__btn:not(.world__btn--wide):hover:not(:disabled) { color: #000; border-color: #3A2A18; filter: brightness(1.06); }
.world .world__btn:not(.world__btn--wide):disabled { filter: grayscale(.8) brightness(.95); opacity: 1; color: #6b5b48; }
@media (max-width: 44rem) {
  .world__trail-btn, .world__trail-here { padding: 0 .4rem; }
  .world__trail-list li + li::before { margin: 0; }
}
/* A country's or a continent's name: a pill centred under its coin, with the
   country's flag; they move as one (globe.js lvBox). A crowded stage
   (data-compact) shows the flag alone; the chosen or pointed-at one, its name. */
.world[data-levels] .world__cluster[data-kind] .world__cluster-label {
  left: 0;
  right: auto;
  top: calc(var(--r) + 5px);
  transform: translateX(-50%);
  display: none;
  padding: 2px 8px;
  font: 600 12px/1.35 var(--sans);
}
.world[data-levels] .world__cluster[data-kind][data-label] .world__cluster-label { display: inline-flex; align-items: center; gap: 5px; }
.world[data-levels] .world__cluster[data-kind="country"] .world__cluster-label .world__flag { display: inline-block; margin: 0; }
/* Where the name went (globe.js LABEL_SPOTS): under, beside or over its
   coin; or, with no room, the flag alone (data-short) until pointed at. */
.world[data-levels] .world__cluster[data-kind="country"][data-pos="above"] .world__cluster-label { top: auto; bottom: calc(var(--r) + 5px); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="right"] .world__cluster-label { left: calc(var(--r) + 5px); top: 0; transform: translateY(-50%); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="left"] .world__cluster-label { left: auto; right: calc(var(--r) + 5px); top: 0; transform: translateY(-50%); }
/* A short label is the flag and the country's code; a full one, the flag
   and its name. */
.world__cluster-code { display: none; font-weight: 700; letter-spacing: .04em; }
.world[data-levels] .world__cluster[data-kind="country"][data-short]:not(:hover, [data-on], [data-selected]) .world__cluster-name { display: none; }
.world[data-levels] .world__cluster[data-kind="country"][data-short]:not(:hover, [data-on], [data-selected]) .world__cluster-code { display: inline; }
.world[data-levels] .world__cluster[data-kind="country"][data-short] .world__cluster-label { padding: 3px 5px; }
/* A continent round the back, waiting at the rim: a smaller coin. */
.world[data-levels] .world__cluster[data-behind] .world__cluster-n { transform: scale(.78); box-shadow: 0 0 0 2px #FBF7F0, 0 0 0 3.5px rgba(58, 42, 24, .55), 0 3px 10px rgba(0, 0, 0, .3); }
.world[data-levels] .world__cluster[data-behind]:hover .world__cluster-n { transform: scale(.86); }
.world[data-levels] .world__cluster:hover, .world[data-levels] .world__cluster[data-on] { z-index: 2; }
/* The hovered outlines sit under the coins on a levels page: a coin is
   what you click (round 1: Europe's 26 outlines covered its "243"). */
.world[data-levels] .world__outline { z-index: auto; }
/* The hop between two choices: a dotted brass arc, gone soon after landing. */
.world[data-levels] .world__route-done { stroke: #E3B863; stroke-width: 2.5; stroke-dasharray: 1 6; }
.world[data-levels] .world__route-left { stroke: #E3B863; stroke-width: 2.5; stroke-dasharray: 1 6; opacity: .55; }
.world[data-levels] .world__route { overflow: hidden; }
/* The postcard line on a country's card: its own hook. */
.world__card-hook { margin: 0 0 .3rem; font-size: .875rem; line-height: 1.35; color: var(--ink-soft); }
/* A softer edge on a levels page: the fade into the paper is half as deep. */
.world[data-levels] .world__globe {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent), linear-gradient(to bottom, transparent, #000 20px, #000 calc(100% - 20px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent), linear-gradient(to bottom, transparent, #000 20px, #000 calc(100% - 20px), transparent);
}
/* The credit on a paper pill, readable on any map in either theme: in the
   bottom corner on a wide stage, above the docked card; at the top on a
   phone, out of the pins' way (round 5: a Rome pin lay under it; round 6:
   it met the + button and vanished on a dark sea). */
.world[data-levels] .world__credit { z-index: 4; right: var(--s3); bottom: var(--s2); padding: 0 .45em; border-radius: 999px; background: color-mix(in oklab, var(--paper) 82%, transparent); color: var(--ink-soft); }
@media (max-width: 44rem) { .world[data-levels] .world__credit { top: var(--s2); bottom: auto; } }
.world__trail-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.world__trail-here { display: inline-flex; align-items: center; gap: .4em; padding: 0 .55rem; min-height: 2rem; color: var(--ink); font: 700 .8125rem/1 var(--sans); }
/* On a wide stage the card docks along the bottom, photograph beside the
   words: a stage is taller than it is wide, so the country keeps the room
   above it (globe.js frames it there, cardBelow). */
.world[data-levels] .world__stage > .world__card {
  top: auto;
  bottom: var(--s3);
  width: min(27rem, calc(100% - 2 * var(--s3)));
  max-height: calc(100% - 5rem);
}
.world[data-levels] .world__stage > .world__card:has(> .world__card-img) {
  display: grid;
  grid-template-columns: 8.5rem 1fr;
  column-gap: var(--s4);
  align-content: start;
  min-height: 8.5rem;
  padding-left: 0;
}
.world[data-levels] .world__stage > .world__card > .world__card-img {
  grid-column: 1;
  grid-row: 1 / span 6;
  align-self: stretch;
  width: 8.5rem;
  height: auto;
  min-height: 8.5rem;
  margin: calc(var(--s3) * -1) 0 calc(var(--s4) * -1);
  aspect-ratio: auto;
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
}
.world[data-levels] .world__stage > .world__card > :not(.world__card-img, .world__card-close) { grid-column: 2; }
.world[data-levels] .world__stage > .world__card > .world__card-title { padding-right: 1.75rem; }
.world[data-levels] .world__cluster[data-kind="region"] .world__cluster-label { top: calc(var(--r) + 7px); padding: 3px 10px; font: 700 13px/1.3 var(--sans); letter-spacing: .02em; }
.world__card-more { display: inline-flex; align-items: center; font-size: .8125rem; color: var(--ink-mute); }
/* In the docked card the universities are one line each: a postcard, not a list. */
.world[data-levels] .world__stage > .world__card .world__card-list button { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.world[data-levels] .world__stage > .world__card .world__card-list li { max-width: 100%; }
/* On a levels page a name is part of its node: pointing at it, or pressing
   it, is pointing at or choosing its node (round 2, M1). */
.world[data-levels] .world__pin[data-label] .world__pin-label,
.world[data-levels] .world__cluster[data-label] .world__pin-label { pointer-events: auto; cursor: pointer; }
/* A continent's countries on a phone (globe.js regionCard): a rail of chips,
   flag, name and number, scrolled sideways. */
.world__card[data-rail] { padding-bottom: var(--s3); }
.world__card-rail { flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: thin; padding-bottom: var(--s1); }
.world__card-rail button { display: inline-flex; align-items: center; gap: .4em; white-space: nowrap; }
.world__card-rail button[data-dim] { opacity: .5; }
.world__rail-n { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); }
/* A country's card waits, unseen, for the camera to land (globe.js goToCountry). */
.world__card[data-waiting] { visibility: hidden; }
/* Corner spots for a country's name (globe.js LABEL_SPOTS). */
.world[data-levels] .world__cluster[data-kind="country"][data-pos="below-right"] .world__cluster-label { top: calc(var(--r) * .7); left: calc(var(--r) * .4); transform: none; }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="below-left"] .world__cluster-label { top: calc(var(--r) * .7); left: auto; right: calc(var(--r) * .4); transform: none; }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="above-right"] .world__cluster-label { top: auto; bottom: calc(var(--r) * .7); left: calc(var(--r) * .4); transform: none; }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="above-left"] .world__cluster-label { top: auto; bottom: calc(var(--r) * .7); left: auto; right: calc(var(--r) * .4); transform: none; }
/* A phone's numbered university pins and their key in the rail. */
.world[data-levels] .world__pin[data-num] .world__pin-dot { display: grid; place-items: center; color: #1A1613; font: 700 12px/1 var(--sans); }
.world__key-n { display: inline-grid; place-items: center; min-width: 1.4em; height: 1.4em; margin-right: .4em; border-radius: 999px; background: #E3B863; color: #1A1613; font-weight: 700; font-size: .75rem; }
/* A name on the far ring (globe.js LABEL_SPOTS, with a leader). */
.world[data-levels] .world__cluster[data-kind="country"][data-pos="far-right"] .world__cluster-label { left: calc(var(--r) + 20px); top: 0; transform: translateY(-50%); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="far-left"] .world__cluster-label { left: auto; right: calc(var(--r) + 20px); top: 0; transform: translateY(-50%); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="far-below"] .world__cluster-label { top: calc(var(--r) + 20px); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="far-above"] .world__cluster-label { top: auto; bottom: calc(var(--r) + 20px); }
/* Corner spots sit clear of their own coin. */
.world[data-levels] .world__cluster[data-kind="country"][data-pos="below-right"] .world__cluster-label { top: calc(var(--r) * .8 + 2px); left: calc(var(--r) * .8 + 2px); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="below-left"] .world__cluster-label { top: calc(var(--r) * .8 + 2px); right: calc(var(--r) * .8 + 2px); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="above-right"] .world__cluster-label { bottom: calc(var(--r) * .8 + 2px); left: calc(var(--r) * .8 + 2px); }
.world[data-levels] .world__cluster[data-kind="country"][data-pos="above-left"] .world__cluster-label { bottom: calc(var(--r) * .8 + 2px); right: calc(var(--r) * .8 + 2px); }
/* A numbered university pin is a tag, not a counting coin. */
.world[data-levels] .world__pin[data-num] .world__pin-dot { border-radius: 5px; }
/* A phone's key: every university of the country, by its pin's number. */
.world__key[hidden] { display: none; }
.world__key { list-style: none; margin: var(--s2) var(--s4, 16px) 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 8px; }
.world__key button { display: flex; align-items: center; width: 100%; padding: 4px 6px; border: 1px solid var(--rule); border-radius: var(--radius-lg); background: var(--paper); color: var(--ink); font: 600 .8125rem/1.25 var(--sans); text-align: left; cursor: pointer; }
.world__key button:hover { border-color: var(--accent); }
.world__key .world__key-n { border-radius: 5px; flex: none; }
.world__key button[aria-current] { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 12%, var(--paper)); }
.world__key button[aria-current] .world__key-n { background: #F4935F; }
/* A country's map: its Reset is a brass pill, of a piece with the knobs (round 8). */
.world:not([data-levels]) .world__btn--wide {
  border-radius: 999px; padding: 0 .8rem; height: 2.25rem;
  border: 1.5px solid #3A2A18;
  background: radial-gradient(circle at 35% 30%, #F7DC8E, #D3A13C 60%, #9A6A22);
  color: #2A1C0E; font: 800 .75rem/1 var(--sans); letter-spacing: .06em;
  box-shadow: 0 2px 5px rgba(0, 0, 0, .3), inset 0 1px 0 rgba(255, 255, 255, .45);
}
