/* Think tank / NGO -- editorial layer.
 *
 * Sitra-like clarity above the fold, FIIA-like current output below it, but
 * deliberately less dense than FIIA. The current site is compact and the
 * brief says to keep it that way rather than imitate a much larger
 * institution.
 */

/* ------------------------------------------------------------------ hero */

/* The image rotates, the message does not. A conventional carousel makes a
   visitor wait to find out what the organisation is; here the identity is
   fixed and only the backdrop changes. */
.hero {
  position: relative;
  display: flex;
  align-items: center;
  /* Capped in vh so the whole hero -- including the slide dots pinned to
     its foot -- fits the fold on a short laptop screen. */
  min-height: min(70vh, 40rem);
  /* Heavier bottom padding than top, which lifts the block above centre
     without floating it. */
  padding-block: calc(clamp(2.5rem, 6vw, 4.5rem) * var(--ui-space))
    calc(clamp(4rem, 10vw, 7.5rem) * var(--ui-space));
  overflow: hidden;
  background: #0d110c;
  color: #fff;
}

.hero-plates {
  position: absolute;
  inset: 0;
}

.hero-plate {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 1.6s var(--ease);
}

.hero-plate.is-active {
  opacity: 1;
}

/* First plate paints immediately so there is never a black flash before
   the script runs, and so a no-JS visitor still gets an image. */
.hero-plate:first-child {
  opacity: 1;
}

.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
      100deg,
      rgba(9, 12, 9, 0.88) 0%,
      rgba(9, 12, 9, 0.66) 42%,
      rgba(9, 12, 9, 0.3) 100%
    ),
    linear-gradient(to top, rgba(9, 12, 9, 0.75), transparent 55%);
}

/* No max-width here. .wrap already sets the width and the auto margins that
   centre it; adding a narrower max-width to the same element shrank the box
   and the auto margins then centred *that*, which is why the copy sat in
   the middle of the screen instead of at the left edge. The measure belongs
   on the text inside. */
.hero-body {
  position: relative;
  z-index: 1;
}

.hero-body .eyebrow {
  color: var(--sage);
}

/* Message and image change together. The messages are stacked in one grid
   cell rather than positioned absolutely, so the hero is as tall as the
   longest of them and nothing jumps as they cycle. */
/* Wide enough that each headline sets on two lines rather than three. */
.hero-messages {
  display: grid;
  margin-top: calc(1.5rem * var(--ui-space));
  max-width: 46rem;
}

.hero-message {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
  pointer-events: none;
}

.hero-message.is-active {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.hero h1,
.hero .hero-message h2 {
  font-size: var(--step-4);
}

.hero .lede {
  margin-top: 1.25rem;
  color: rgba(255, 255, 255, 0.82);
}

/* No JavaScript: show the first message and hide the rest, rather than
   stacking all three on top of each other. */
.no-js .hero-message {
  opacity: 0;
}

.no-js .hero-message:first-child {
  opacity: 1;
  transform: none;
}

.hero-read {
  display: inline-flex;
  margin-top: 1.6rem;
  color: var(--brass);
}

/* Step controls. Kept faint until the hero is hovered or one of them takes
   focus, so they do not compete with the headline -- but always present in
   the tab order, because a control only reachable by hover is not a
   control on a touch screen or a keyboard. */
.hero-arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  transform: translateY(-50%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: rgba(9, 12, 9, 0.35);
  color: #fff;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.3s var(--ease), background 0.2s var(--ease),
    border-color 0.2s var(--ease);
}

.hero:hover .hero-arrow,
.hero-arrow:focus-visible {
  opacity: 1;
}

.hero-arrow:hover {
  background: rgba(9, 12, 9, 0.75);
  border-color: var(--brass);
}

.hero-arrow--prev {
  left: clamp(0.5rem, 2vw, 1.5rem);
}

.hero-arrow--next {
  right: clamp(0.5rem, 2vw, 1.5rem);
}

@media (hover: none) {
  .hero-arrow {
    opacity: 1;
  }
}

.hero-dots {
  position: absolute;
  bottom: 1.75rem;
  right: var(--pad);
  z-index: 1;
  display: flex;
  gap: 0.5rem;
}

.hero-dots button {
  width: 2.25rem;
  height: 2px;
  padding: 0;
  border: 0;
  background: rgba(255, 255, 255, 0.3);
  cursor: pointer;
}

.hero-dots button[aria-current='true'] {
  background: var(--brass);
}

/* --------------------------------------------------------- identity block */

/* The master tagline sits below the landing, not on it: the hero rotates
   through what the work is about, and this is where the organisation
   states who it is. Copy left, the ways in on the right. */
.identity {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
}

@media (max-width: 900px) {
  .identity {
    grid-template-columns: 1fr;
  }
}

.identity-tagline {
  font-size: var(--step-4);
  font-weight: 300;
  line-height: 1.02;
  letter-spacing: -0.02em;
}

.identity-copy .lede {
  margin-top: 1.75rem;
  color: var(--ink);
}

.identity-copy p + p {
  margin-top: 1.1rem;
  color: var(--muted);
  max-width: var(--measure);
}

/* The two buttons are deliberately the same width and a size up from the
   default: they are the only two ways into the site from here, and one
   looking smaller than the other implies a ranking that is not intended. */
.identity-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.75rem;
  padding-top: 0.6rem;
  max-width: 20rem;
}

.identity-actions .btn {
  justify-content: center;
  padding: 1.05rem 1.75rem;
  font-size: var(--step-0);
  letter-spacing: 0.08em;
}

.identity-actions .arrow-link {
  margin-top: 0.5rem;
  justify-content: center;
}

/* --- page header for interior pages --- */

/* Interior pages, not landings. The title block used to run to the full
   display scale and fill the first screen on its own, so a visitor arriving
   on a sub page saw a heading and nothing else. It is now sized to leave
   real content above the fold. */
.page-head {
  padding-block: clamp(1.75rem, 3.5vw, 3rem) clamp(1.25rem, 2.5vw, 2rem);
  border-bottom: 1px solid var(--line);
}

.page-head h1 {
  margin-top: 0.75rem;
  font-size: var(--step-3);
  max-width: 24ch;
}

.page-head .lede {
  margin-top: 0.9rem;
  font-size: var(--step-0);
}

/* ------------------------------------------------------------- page nav */

/* Articles & Media holds two different things: what we published, and
   where we were quoted. Without this a visitor scrolling into the media
   list reads it as more of the first section. */
.page-nav {
  position: sticky;
  /* Under the masthead, which is sticky too: site.js measures it. */
  top: var(--masthead-h, 0px);
  z-index: 20;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* Right-aligned and labelled. Unlabelled on the left they read as a
   breadcrumb rather than as something you can click. The alignment is a
   margin on the label rather than justify-content: flex-end, so that when
   the row is wider than a phone it overflows to the right, where it can
   be scrolled to, and not off the left edge, where it cannot. */
.page-nav-in {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 1.75rem);
  overflow-x: auto;
}

.page-nav-label {
  margin-inline-start: auto;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.page-nav a {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55rem;
  padding: 1rem 0;
  border-bottom: 2px solid transparent;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-decoration: none;
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.page-nav a:hover {
  color: var(--ink);
  border-bottom-color: var(--rule);
}

.page-nav-num {
  font-size: 0.7rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* The sticky masthead and page-nav would otherwise cover the heading
   jumped to. */
[id='publications'],
[id='media'] {
  scroll-margin-top: calc(var(--masthead-h, 0px) + 4rem);
}

.section--divided {
  border-top: 1px solid var(--line);
}

/* ------------------------------------------------------------ our focus */

/* Research programmes, not a row of bordered boxes. Each one is a standing
   programme of work, so it carries an image, a live status and the shape of
   what is being followed -- the earlier version read as three empty cells
   with a word in each. */
.programmes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: clamp(1.25rem, 2.5vw, 2rem);
}

.programme {
  display: flex;
}

.programme a {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-decoration: none;
  color: inherit;
  background: var(--surface);
  border: 1px solid var(--line);
  overflow: hidden;
  transition: border-color 0.3s var(--ease), transform 0.3s var(--ease);
}

.programme a:hover {
  border-color: var(--rule);
  transform: translateY(-3px);
}

.programme-media {
  position: relative;
  aspect-ratio: 16 / 10;
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: flex-end;
  padding: clamp(1rem, 2vw, 1.35rem);
  overflow: hidden;
}

.programme-media::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(9, 12, 9, 0.9), rgba(9, 12, 9, 0.15) 65%);
  transition: opacity 0.3s var(--ease);
}

.programme a:hover .programme-media::before {
  opacity: 0.82;
}

.programme-media h3 {
  position: relative;
  font-size: var(--step-2);
  font-weight: 300;
  letter-spacing: -0.01em;
  color: #fff;
}

.programme-status {
  position: absolute;
  top: clamp(0.85rem, 1.8vw, 1.15rem);
  left: clamp(0.85rem, 1.8vw, 1.15rem);
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.28rem 0.6rem;
  background: rgba(9, 12, 9, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.28);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
}

.programme-status::before {
  content: '';
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--brass);
}

.programme-body {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
  flex: 1;
}

.programme-body p {
  color: var(--muted);
  font-size: 0.96rem;
}

/* What the programme actually produces -- the detail that makes it read as
   ongoing work rather than a topic label. */
.programme-output {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: auto;
  padding-top: 0.35rem;
}

.programme-output li {
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--line);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.programme-body .arrow-link {
  padding-top: 0.75rem;
}

/* --------------------------------------------------------------- latest */

/* One large item and two smaller ones. The brief is explicit that a
   homepage of equally weighted cards is the thing to avoid. */
.latest {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: clamp(1.5rem, 3vw, 2.5rem);
}

.latest-secondary {
  display: flex;
  flex-direction: column;
  gap: clamp(1.5rem, 3vw, 2.5rem);
}

@media (max-width: 860px) {
  .latest {
    grid-template-columns: 1fr;
  }
}

.card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: var(--surface);
  border: 1px solid var(--line);
  overflow: hidden;
  transition: border-color 0.25s var(--ease), transform 0.25s var(--ease);
}

.card:hover {
  border-color: var(--rule);
  transform: translateY(-2px);
}

.card img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.card-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: clamp(1.25rem, 2.5vw, 2rem);
  flex: 1;
}

.card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.card-meta .tag {
  color: var(--accent);
}

.card h3 {
  font-size: var(--step-1);
  font-weight: 700;
}

.card--feature h3 {
  font-size: var(--step-2);
  font-weight: 300;
  letter-spacing: -0.01em;
}

.card--feature img {
  aspect-ratio: 16 / 9;
}

.card p {
  color: var(--muted);
  font-size: 0.95rem;
}

.card--compact {
  flex-direction: row;
}

.card--compact img {
  width: 38%;
  aspect-ratio: 1 / 1;
  flex: none;
}

@media (max-width: 520px) {
  .card--compact {
    flex-direction: column;
  }
  .card--compact img {
    width: 100%;
    aspect-ratio: 3 / 2;
  }
}

.section-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 2rem;
}

/* ------------------------------------------------------------------ map */

/* The map is a flagship public product and gets its own section rather than
   being filed inside Articles & Media. */
.map-band {
  background: var(--forest);
  color: #fff;
}

.map-band .sec-head::after {
  background: var(--brass);
}

.map-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

@media (max-width: 900px) {
  .map-layout {
    grid-template-columns: 1fr;
  }
}

.map-figure {
  margin: 0;
  padding: clamp(1rem, 2vw, 1.75rem);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.12);
}

.map-figure img {
  width: 100%;
}

/* --- the map page itself ---
   One full-width map and nothing beside it. This is the slot the real map
   implementation drops into, so the page is built around the map having
   the whole measure rather than sharing a row with a column of copy. */
.map-stage {
  padding-block: clamp(1.5rem, 3vw, 2.5rem) 0;
}

.map-frame {
  margin: 0;
  padding: clamp(1rem, 2.5vw, 2.25rem);
  border: 1px solid var(--line);
  background: var(--surface);
}

.map-frame img {
  width: 100%;
  height: auto;
}

.map-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 2rem;
  margin-bottom: 1.25rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}

.map-toolbar .map-legend {
  margin: 0;
  color: var(--muted);
}

/* The outline on the filled swatch only; the line swatches carry their
   colour in the border itself. */
.map-toolbar .swatch--occupied {
  border-color: var(--line);
}

/* The update stamp is the caption under the map: the time on the left,
   the zone control beside it, wrapping to its own line when there is no
   room. The base-map attribution it replaced still appears -- MapLibre
   draws it in the corner of the map itself. */
.map-stamp {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1.1rem;
}


/* --- the update stamp ---
   "Last updated 22:27, 3 September 2026, GMT+3", where the offset is the
   control: it is the part of the sentence a reader would want to change,
   so it is the button, and no "change" link is needed beside it. */
.map-tz {
  position: relative;
  display: inline-block;
}

.map-tz-value {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  color: var(--ink);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  cursor: pointer;
}

.map-tz-value::after {
  content: ' \25be';
  color: var(--muted);
}

.map-tz-value:hover,
.map-tz-value[aria-expanded='true'] {
  color: var(--accent);
}

.map-tz-pop {
  position: absolute;
  z-index: 5;
  top: calc(100% + 0.4rem);
  left: 0;
  width: min(19rem, 80vw);
  padding: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  text-transform: none;
  letter-spacing: normal;
}

.map-tz-search {
  width: 100%;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--bg);
  font: inherit;
  font-size: 0.85rem;
  color: var(--ink);
}

.map-tz-list {
  max-height: 15rem;
  margin: 0.45rem 0 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

/* The search hides non-matches with the hidden attribute, and an author
   `display` beats the user agent's `[hidden] { display: none }` -- so it
   has to be said again here, or filtering silently does nothing. */
.map-tz-option[hidden] {
  display: none;
}

.map-tz-option {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.35rem 0.5rem;
  font-size: 0.85rem;
  color: var(--ink);
  cursor: pointer;
}

/* The zones this map is usually read in, kept above the other four
   hundred and ruled off from them. */
.map-tz-option.is-pinned + .map-tz-option:not(.is-pinned) {
  margin-top: 0.35rem;
  border-top: 1px solid var(--line);
  padding-top: 0.6rem;
}

.map-tz-option:hover,
.map-tz-option.is-active {
  background: var(--sunken);
}

.map-tz-option.is-current {
  font-weight: 600;
}

.map-tz-name i {
  margin-left: 0.4rem;
  font-style: normal;
  font-size: 0.8em;
  color: var(--muted);
}

.map-tz-offset {
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.map-tz-none {
  margin: 0.5rem 0 0.25rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.map-updated {
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* One half of the updated stamp -- the UTC time, or the viewer's own. A
   narrow screen may break the line between them, never inside a date. */
.stamp-part {
  white-space: nowrap;
}

/* --- the live map ---
   MapLibre draws the live front line inside .live-map; the static schematic
   plate sits underneath and is what shows without scripting or when the
   data cannot be fetched (assets/map.js). The canvas fades in over the plate
   once the first frame and the data are both there, so there is never a
   grey rectangle. Absolute positioning inside a ratio box keeps it
   responsive. */
.live-map {
  position: relative;
  width: 100%;
  aspect-ratio: 10 / 7;
  overflow: hidden;
  background: #e9ece6;
}

/* The schematic plate is a fallback, not a loading screen. It carries a
   hand-drawn front line, and putting that on screen while the real one is
   on its way shows wrong data -- briefly, but this is a map whose whole
   point is where the line actually is. So it appears only where it is the
   best answer available: scripting off, or the live data failed. */
.live-map-plate {
  position: absolute;
  inset: 0;
  display: none;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.no-js .live-map-plate,
.live-map[data-state='error'] .live-map-plate {
  display: block;
}

/* Shown from the moment the page renders until the map draws or fails.
   Hidden outright without scripting, where the plate is the answer and a
   spinner would turn forever. */
.live-map-loading {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.85rem;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.no-js .live-map-loading,
.live-map.is-live .live-map-loading,
.live-map[data-state='error'] .live-map-loading {
  display: none;
}

.live-map-spinner {
  width: 2.1rem;
  height: 2.1rem;
  border: 2px solid var(--line);
  border-top-color: var(--forest);
  border-radius: 50%;
  animation: live-map-spin 0.9s linear infinite;
}

@keyframes live-map-spin {
  to {
    transform: rotate(360deg);
  }
}

/* No substitute ring: a circle that does not turn reads as a broken
   control. The label alone says what is happening. */
@media (prefers-reduced-motion: reduce) {
  .live-map-spinner {
    display: none;
  }
}

/* Two classes on purpose: MapLibre's own stylesheet, loaded later, sets
   .maplibregl-map to position: relative with overflow hidden, which would
   collapse this box to nothing and clip the canvas. */
.live-map .live-map-canvas {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.live-map.is-live .live-map-canvas {
  opacity: 1;
}

/* The map page is the map: give it the height a person can actually work
   in, but never more than the viewport, so the method note below stays
   reachable. */
.live-map--stage {
  aspect-ratio: 16 / 9;
  min-height: min(70vh, 640px);
}

@media (max-width: 700px) {
  .live-map--stage {
    aspect-ratio: 3 / 4;
    min-height: 0;
  }
}

.live-map-note,
.live-map-layers-panel {
  margin: 0;
  padding: 0.55rem 0.8rem;
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid var(--line);
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--muted);
}

.live-map-note {
  position: absolute;
  z-index: 2;
  left: 0.75rem;
  bottom: 0.75rem;
  max-width: 26rem;
}

.live-map-note a {
  color: var(--ink);
}

/* --- the layer menu ---
   Collapsed to an icon by default: the map is the point, and a permanent
   panel would sit on top of it. Anchored top left, opposite MapLibre's own
   zoom and fullscreen stack. */
.live-map-layers {
  position: absolute;
  z-index: 3;
  top: 0.75rem;
  left: 0.75rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
}

.live-map-layers-toggle {
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--ink);
  cursor: pointer;
}

.live-map-layers-toggle:hover,
.live-map-layers-toggle[aria-expanded='true'] {
  background: #fff;
  border-color: var(--muted);
}

.live-map-layers-toggle svg {
  width: 1.15rem;
  height: 1.15rem;
}

.live-map-layers-panel {
  min-width: 12.5rem;
  max-width: min(16rem, 60vw);
  color: var(--ink);
}

.live-map-layers-head {
  margin: 0 0 0.45rem;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.live-map-layers-panel ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.35rem;
}

.live-map-layers-panel label {
  display: flex;
  gap: 0.45rem;
  align-items: baseline;
  cursor: pointer;
}

/* Forest rather than the site's usual slate accent (.ms-opt input), and
   deliberately so: this control sits on top of the map, where slate and
   burgundy are data colours -- own or allied, and adversary. A slate tick
   here would read as a legend key for the Ukrainian side. Forest is the
   brand colour, carries no meaning on the map, and holds contrast on
   white where sage and brass would not. */
.live-map-layers-panel input {
  flex: none;
  width: 0.95rem;
  height: 0.95rem;
  accent-color: var(--forest);
}

.live-map-layers-status:empty {
  display: none;
}

.live-map-layers-status {
  margin: 0.5rem 0 0;
  color: var(--muted);
}

/* MapLibre's own chrome, toned to the site. */
.live-map .maplibregl-ctrl-attrib {
  font-size: 0.68rem;
  color: var(--muted);
}

.live-map .maplibregl-ctrl-group {
  border-radius: 2px;
  box-shadow: none;
  border: 1px solid var(--line);
}

.live-map .maplibregl-ctrl-scale {
  border-color: var(--muted);
  color: var(--muted);
  font-size: 0.68rem;
}

.map-source {
  margin: 0.9rem 0 0;
  font-size: 0.78rem;
  color: var(--muted);
}

/* Sits directly under the map, not a full section away from it. */
.map-method {
  padding-block: clamp(1.75rem, 3vw, 2.5rem) var(--section);
}

.map-method-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}

@media (max-width: 820px) {
  .map-method-grid {
    grid-template-columns: 1fr;
  }
}

.map-figure figcaption {
  margin-top: 0.9rem;
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.6);
}

.map-copy .lede {
  color: rgba(255, 255, 255, 0.8);
}

.map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.75rem;
  margin-top: 1.75rem;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.8);
}

.map-legend li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.swatch {
  width: 1.1rem;
  height: 0.7rem;
  flex: none;
  border: 1px solid rgba(255, 255, 255, 0.25);
}

.swatch--occupied {
  background: var(--burgundy);
}

.swatch--own {
  background: #d9dfe6;
}

/* Legend keys for the live map: the front is dashed on both sides, an
   axis of advance is solid. Lighter than the map's own burgundy and slate
   so they read on the forest band as well as on the white toolbar. */
.swatch--ru {
  height: 0;
  border: 0;
  border-top: 3px dashed #93223a;
}

.swatch--ua {
  height: 0;
  border: 0;
  border-top: 3px dashed #7d90a8;
}

/* The movement keys are arrows, because that is what the map draws: a
   solid shaft with a head at the tip, against the dashed front lines
   above them. Shaft and head are the two pseudo-elements, so the markup
   stays a bare <span> like every other swatch, and both take their colour
   from `color` -- one rule, two keys. */
.swatch--axis,
.swatch--axis-ua {
  position: relative;
  height: 0.7rem;
  border: 0;
  background: none;
}

.swatch--axis {
  color: #93223a;
}

.swatch--axis-ua {
  color: #7d90a8;
}

.swatch--axis::before,
.swatch--axis-ua::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: calc(100% - 5px);
  height: 3px;
  transform: translateY(-50%);
  background: currentColor;
}

.swatch--axis::after,
.swatch--axis-ua::after {
  content: '';
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  border-left: 6px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

.swatch--front {
  height: 0;
  border: 0;
  border-top: 3px solid #93223a;
}

.map-band .btn {
  margin-top: 2rem;
  border-color: var(--brass);
  background: var(--brass);
  color: #0b0e0b;
}

.map-band .btn:hover {
  background: transparent;
  color: var(--brass);
}

/* ---------------------------------------------------------------- media */

/* The outlet names are the filter. They read as a masthead row of titles
   and behave as buttons -- clicking one narrows the list below to that
   outlet, clicking it again clears it. */
.outlets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
  align-items: center;
  padding-block: 1.35rem;
  border-block: 1px solid var(--line);
}

.outlets button {
  padding: 0.15rem 0;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  font-size: clamp(0.95rem, 1.6vw, 1.2rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.outlets button:hover {
  color: var(--ink);
  border-bottom-color: var(--rule);
}

.outlets button[aria-pressed='true'] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.appearances {
  margin-top: 1.75rem;
}

.appearances li[hidden] {
  display: none;
}

.show-more {
  display: block;
  width: 100%;
  margin-top: 1.25rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: none;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.show-more:hover {
  color: var(--ink);
  border-color: var(--rule);
}

.appearances li + li {
  border-top: 1px solid var(--line);
}

.appearance {
  display: grid;
  grid-template-columns: 9rem 1fr auto;
  gap: 0.4rem 1.5rem;
  align-items: baseline;
  padding-block: 1.15rem;
  text-decoration: none;
  color: inherit;
}

.appearance:hover .appearance-title {
  text-decoration: underline;
}

.appearance-outlet {
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

.appearance-title {
  font-size: var(--step-0);
}

/* The analyst, under the headline rather than in a column of its own: the
   row is already outlet / title / date, and a fourth column would wrap the
   title on a laptop. */
.appearance-by {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.78rem;
  color: var(--muted);
}

.appearance-date {
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 720px) {
  .appearance {
    grid-template-columns: 1fr auto;
  }
  .appearance-outlet {
    grid-column: 1 / -1;
  }
}

/* --------------------------------------------------------------- people */

/* auto-fill, not auto-fit. auto-fit collapses the empty tracks, so
   filtering down to one person stretched that card across the whole row.
   auto-fill keeps the tracks, and a filtered card stays the size it was. */
.people {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr));
  gap: calc(clamp(1.1rem, 2vw, 1.75rem) * var(--ui-space));
}

.person {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

/* Placeholder portrait. No analyst photograph appears anywhere on this
   demo: per-person publication is an organisational security decision and
   needs individual sign-off, which a demo cannot stand in for. */
.portrait {
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  background: var(--ash);
  border: 1px solid var(--line);
}

.portrait svg {
  width: 45%;
  color: var(--sage);
}

/* A published portrait, where the organisation publishes one. The frame is
   the same square either way, so a card with a photograph and a card without
   sit on the same grid line rather than one pushing the row taller. */
.portrait--photo {
  background: none;
  overflow: hidden;
}

.portrait--photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.person-contact {
  margin-top: 0.75rem;
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--muted);
}

.person-contact span {
  display: block;
}

.person h3 {
  font-size: var(--step-1);
}

.person .role {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}

.person p {
  font-size: 0.93rem;
  color: var(--muted);
}

.expertise {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.25rem;
}

.expertise li {
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--line);
  font-size: 0.74rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ------------------------------------------------------------ work with */

.routes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}

.route {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: clamp(1.75rem, 3vw, 2.5rem);
  background: var(--surface);
}

.route h3 {
  font-size: var(--step-1);
}

.route p {
  color: var(--muted);
  font-size: 0.95rem;
}

.route a {
  margin-top: auto;
  padding-top: 0.75rem;
}

/* A commercial route is present but clearly marked as leaving the NGO. */
.route--commercial {
  background: var(--sunken);
}

/* ----------------------------------------------------------- newsletter */

.newsletter {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  padding: clamp(2rem, 5vw, 3.5rem);
  background: var(--surface);
  border: 1px solid var(--line);
}

@media (max-width: 820px) {
  .newsletter {
    grid-template-columns: 1fr;
  }
}

.signup {
  display: flex;
  gap: 0.6rem;
  margin-top: 1.5rem;
}

@media (max-width: 480px) {
  .signup {
    flex-direction: column;
  }
}

.signup input {
  flex: 1;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  color: inherit;
  font: inherit;
  font-size: 0.95rem;
}

.signup input:focus {
  outline: none;
  border-color: var(--accent);
}

.form-note {
  margin-top: 0.9rem;
  font-size: 0.8rem;
  color: var(--muted);
}

/* ------------------------------------------------------------- contact */

.contact-routes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
}

/* Exactly three routes, so they hold one row rather than reflowing to a
   2 + 1 that reads as a hierarchy they do not have. */
.contact-routes--three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

@media (max-width: 900px) {
  .contact-routes--three {
    grid-template-columns: 1fr;
  }
}

.contact-team {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem 2.5rem;
  margin-top: clamp(2rem, 4vw, 3rem);
  padding: clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid var(--line);
  border-left: 3px solid var(--rule);
  background: var(--surface);
}

.contact-team h2 {
  font-size: var(--step-2);
  margin-bottom: 0.5rem;
}

.contact-team p {
  color: var(--muted);
  font-size: 0.95rem;
  max-width: 52ch;
}

.contact-route {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  padding: clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid var(--line);
  background: var(--surface);
}

.contact-route h3 {
  font-size: var(--step-1);
}

.contact-route p {
  font-size: 0.95rem;
  color: var(--muted);
}

.contact-route .mail {
  font-weight: 600;
  color: var(--accent);
  word-break: break-word;
}

/* ------------------------------------------------------- long-form pages */

.value-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: clamp(1.25rem, 3vw, 2rem);
  margin-top: 1.5rem;
}

.value-list li {
  padding-top: 1rem;
  border-top: 2px solid var(--rule);
}

.value-list h3 {
  margin: 0 0 0.4rem;
  font-size: var(--step-0);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.value-list p {
  font-size: 0.93rem;
  color: var(--muted);
}

/* Figures table: numerals aligned and tabular so the column reads down. */
.fact-table--figures {
  margin-top: 1.75rem;
}

.fact-table--figures th {
  width: auto;
  font-size: 0.74rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.fact-table--figures td:nth-child(2),
.fact-table--figures td:nth-child(3),
.fact-table--figures th:nth-child(2),
.fact-table--figures th:nth-child(3) {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.fact-table--figures tfoot td {
  font-weight: 700;
  border-bottom: 0;
}

/* ----------------------------------------------------- governance page */

/* The page was a wall of tables and headings. These give it the structure
   the content already had: figures read as figures, the funding split is
   shown rather than tabulated, and the principles carry the same rule
   device as the rest of the site. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  gap: 1px;
  margin-top: 1.5rem;
  background: var(--line);
  border: 1px solid var(--line);
}

.stat {
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
  background: var(--surface);
}

.stat b {
  display: block;
  font-size: var(--step-3);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--accent);
}

.stat span {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.82rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Funding split, drawn and then tabulated: the pie gives the shape at a
   glance, the table gives the figures someone actually needs to cite.
   conic-gradient rather than SVG -- it is one declaration, scales cleanly
   and needs no script. */
.funding {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(1.5rem, 4vw, 3rem);
  margin-top: 1.5rem;
}

.pie {
  flex: none;
  width: clamp(9rem, 22vw, 12rem);
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(
    var(--forest) 0 30%,
    var(--slate) 30% 85%,
    var(--sage) 85% 100%
  );
  box-shadow: 0 0 0 1px var(--line);
}

.funding .split-legend {
  flex-direction: column;
  gap: 0.65rem;
  margin-top: 0;
}

.split-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.75rem;
  margin-top: 1rem;
  font-size: 0.88rem;
  color: var(--muted);
}

.split-legend li {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

.principles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1px;
  margin-top: 1.5rem;
  background: var(--line);
  border: 1px solid var(--line);
}

.principle {
  padding: clamp(1.35rem, 2.5vw, 1.9rem);
  background: var(--surface);
}

.principle h3 {
  margin-bottom: 0.55rem;
  font-size: var(--step-0);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.principle h3::after {
  content: '';
  display: block;
  width: 2.75rem;
  height: 2px;
  margin-top: 0.6rem;
  background: var(--rule);
}

.principle p {
  font-size: 0.93rem;
  color: var(--muted);
}

.doc-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.doc-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--line);
  background: var(--surface);
  text-decoration: none;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}

.doc-link:hover {
  border-color: var(--rule);
  background: var(--sunken);
}

.doc-link b {
  font-size: 0.95rem;
  font-weight: 600;
}

.doc-link span {
  font-size: 0.76rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Two-entity diagram on the governance page. */
.entity-map {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: clamp(1.25rem, 3vw, 2rem);
  margin-top: 1.5rem;
}

.entity-card {
  padding: clamp(1.5rem, 3vw, 2rem);
  border: 1px solid var(--line);
  border-top: 3px solid var(--rule);
  background: var(--surface);
}

.entity-card h3 {
  font-size: var(--step-1);
  margin-bottom: 0.5rem;
}

.entity-card p {
  font-size: 0.93rem;
  color: var(--muted);
}

.entity-card dl {
  margin: 1.25rem 0 0;
  font-size: 0.88rem;
}

.entity-card dt {
  font-weight: 600;
  color: var(--muted);
}

.entity-card dd {
  margin: 0 0 0.6rem;
}

/* ------------------------------------------------- articles index filter */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}

.filters button {
  padding: 0.45rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: none;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: all 0.2s var(--ease);
}

.filters button[aria-pressed='true'] {
  background: var(--forest);
  border-color: var(--forest);
  color: #fff;
}

/* The team list filters by subject, so the control needs saying what it
   does -- an unlabelled row of subject names reads as tags, not as a
   filter. Grouped by kind, because "Ukraine" and "Senior Analyst" are not
   the same sort of thing and a single flat row implied they were. */
/* One row of dropdowns rather than every tag on the page at once. Eighteen
   focus areas laid out flat is a wall; behind three menus it is a control.
   Each menu multi-selects, so "Ukraine or the Arctic, at analyst level" is
   expressible. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding-bottom: 1.25rem;
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
  border-bottom: 1px solid var(--line);
}

.filter-bar .filters-label {
  margin-right: 0.35rem;
}

.ms {
  position: relative;
}

.ms-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease);
}

.ms-toggle::after {
  content: '';
  width: 0.4rem;
  height: 0.4rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg);
}

.ms-toggle:hover,
.ms[data-open='true'] .ms-toggle {
  color: var(--ink);
  border-color: var(--rule);
}

.ms-toggle[data-active='true'] {
  color: var(--accent);
  border-color: var(--accent);
}

/* An explicit `display` beats the [hidden] attribute, so the empty badge
   stayed on screen as a dot until this said otherwise. */
.ms-count[hidden] {
  display: none;
}

.ms-count {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 0.66rem;
}

.ms-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  left: 0;
  z-index: 25;
  min-width: 15rem;
  max-height: 17rem;
  overflow-y: auto;
  padding: 0.4rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.ms-opt {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  font-size: 0.88rem;
  cursor: pointer;
  border-radius: var(--radius);
}

.ms-opt:hover {
  background: var(--sunken);
}

.ms-opt input {
  width: 0.95rem;
  height: 0.95rem;
  accent-color: var(--accent);
}

.ms-clear {
  padding: 0.5rem 0.85rem;
  border: 0;
  background: none;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.ms-clear:hover {
  color: var(--ink);
}

.ms-result {
  margin-left: auto;
  font-size: 0.82rem;
  color: var(--muted);
}

/* No JavaScript: the menus cannot work, so do not show a dead control. */
.no-js .filter-bar {
  display: none;
}

.filters--tags {
  align-items: center;
  gap: 0.4rem;
}

.filters-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Team intro sits beside the list rather than above it, so the first
   people are on screen when the page opens. */
.team-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(min(100%, 17rem), 0.42fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
  align-items: start;
}

@media (max-width: 900px) {
  .team-layout {
    grid-template-columns: 1fr;
  }
}

.press-card {
  padding: clamp(1.1rem, 2vw, 1.5rem);
  border: 1px solid var(--line);
  border-top: 3px solid var(--rule);
  background: var(--surface);
}

.press-card .eyebrow {
  font-size: 0.68rem;
}

.press-card h2 {
  margin: 0.5rem 0 0.6rem;
  font-size: var(--step-1);
  font-weight: 700;
}

.press-card p {
  font-size: 0.88rem;
  color: var(--muted);
  margin-bottom: 0.9rem;
}

.person[hidden] {
  display: none;
}

.filter-empty {
  margin-top: 1.5rem;
  color: var(--muted);
}

/* --------------------------------------------------------------- values */

/* Keywords stack on the left, the selected one explains itself on the
   right. One panel swapped in place rather than three stacked paragraphs:
   the section keeps a constant height and the three values read as a set
   rather than as a list to scroll through. */
.values {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.5rem);
  align-items: start;
}

@media (max-width: 760px) {
  .values {
    grid-template-columns: 1fr;
  }
}

.value-keys {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.value-key {
  padding: 1.15rem 1.25rem;
  border: 0;
  border-left: 4px solid transparent;
  border-radius: var(--radius);
  background: var(--ash);
  color: var(--stone);
  font-family: inherit;
  font-size: var(--step-1);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
  transition: background 0.25s var(--ease), color 0.25s var(--ease),
    border-color 0.25s var(--ease);
}

.value-key:hover {
  color: var(--ink);
}

/* Each value takes its own ground when selected. Forest, slate and stone
   are all brand colours that carry white text; brass and sage are not, so
   they stay out of this. */
.value-key[aria-selected='true'] {
  color: #fff;
  border-left-color: var(--brass);
}

.value-key:nth-child(1)[aria-selected='true'] {
  background: var(--slate);
}

.value-key:nth-child(2)[aria-selected='true'] {
  background: var(--forest);
}

.value-key:nth-child(3)[aria-selected='true'] {
  background: var(--stone);
}

.value-panels {
  min-height: 100%;
}

/* The panel's top edge carries the selected value's own colour, so the
   keyword box and the panel read as one object rather than two. */
.value-panel {
  padding: clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid var(--line);
  border-top: 3px solid var(--tone, var(--rule));
  background: var(--surface);
}

.value-panel:nth-child(1) {
  --tone: var(--slate);
}

.value-panel:nth-child(2) {
  --tone: var(--forest);
}

.value-panel:nth-child(3) {
  --tone: var(--stone);
}

.value-panel h3 {
  font-size: var(--step-2);
  font-weight: 300;
  letter-spacing: -0.01em;
  margin-bottom: 0.9rem;
}

.value-panel p {
  color: var(--muted);
  max-width: var(--measure);
}

/* No JavaScript: show them all rather than showing none. */
.no-js .value-panel {
  display: block !important;
  margin-bottom: 1rem;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
  gap: clamp(1.5rem, 3vw, 2.25rem);
}

.card[hidden] {
  display: none;
}

/* The policy link closes the sustainability section and is right-aligned
   so it reads as the section's exit rather than as another paragraph. */
.doc-action {
  display: flex;
  justify-content: flex-end;
  margin-top: 1.5rem;
}

/* Laptop: wide but short. The featured card's 16:9 image is the single
   biggest vertical cost on the homepage, and a wider crop buys back the
   ~50px that put "Current work" over the fold. Type is untouched. */
@media (min-width: 900px) and (max-height: 900px) {
  .card--feature img {
    aspect-ratio: 2 / 1;
  }

  .card-body {
    padding: clamp(1rem, 2vw, 1.5rem);
  }

  .programme-media {
    aspect-ratio: 16 / 9;
  }
}

/* ------------------------------------------------------------- analysis */

/* A published analysis, in full (articles/ais-drone-strikes/). The prose
   sits in the narrow measure, as on Our story; the figures take the whole
   column, as the map page does, and wear the same frame, legend and
   caption. The stage controls under a map are the article filters' buttons
   put to a new use, so a reader meets one kind of button on the site. */

.report-head h1 {
  max-width: 32ch;
}

.report-byline {
  margin-top: 1rem;
  font-size: var(--step--1);
  color: var(--muted);
}

.report-byline a {
  color: inherit;
}

.report {
  display: flex;
  flex-direction: column;
  gap: clamp(2rem, 4vw, 3rem);
  padding-block: var(--section);
}

/* The sticky masthead and contents bar would otherwise cover the heading
   jumped to. */
.report [id] {
  scroll-margin-top: calc(var(--masthead-h, 0px) + 4.5rem);
}

.report .fact-table {
  max-width: var(--measure);
  margin-bottom: 1.5rem;
}

.report .fact-table th {
  width: 45%;
}

.report code {
  font-size: 0.9em;
}

/* --- citations and references --- */

.cite {
  font-size: 0.82em;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  white-space: nowrap;
}

.cite:hover {
  text-decoration: underline;
}

.references {
  max-width: var(--measure);
  padding-left: 2.2em;
  font-size: 0.92rem;
  line-height: 1.55;
}

.references li {
  margin-bottom: 0.55em;
  overflow-wrap: anywhere;
}

.references li::marker {
  font-weight: 600;
  color: var(--accent);
}

/* Following a [n] lands here; say so. */
.references li:target {
  background: var(--sunken);
  box-shadow: 0 0 0 6px var(--sunken);
}

/* --- the author's working notes ---
   The report is carried as published, notes to self included. They are
   marked as what they are, quietly: the same sunken surface the site's
   own placeholders use, not a highlighter. */
.todo {
  padding: 0.05em 0.4em;
  background: var(--sunken);
  border-bottom: 1px dotted var(--muted);
  color: var(--muted);
  font-style: italic;
}

.todo::before {
  content: 'Author’s note: ';
  font-size: 0.72em;
  font-style: normal;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.todo-block {
  margin: 1.5em 0;
  padding: 1.1rem 1.25rem;
  background: var(--sunken);
  border-left: 3px solid var(--sage);
  font-size: 0.95rem;
  color: var(--muted);
}

.todo-block .eyebrow {
  margin-bottom: 0.5rem;
}

.todo-block a {
  color: var(--ink);
}

/* --- formulas ---
   MathML, rendered at build time (src/lib/math.ts). The browser draws it
   in a mathematics font when the system has one -- Raleway is subset to
   Latin, and a formula wants Greek, stretchy bars and real fractions --
   and falls back to a serif with the same coverage otherwise. */
.report math {
  font-family: math, 'Cambria Math', 'STIX Two Math', 'Latin Modern Math', 'Noto Serif', 'DejaVu Serif', serif;
  font-size: 1.05em;
}

/* A one-letter variable is italic. Browsers do that by swapping the
   letter for its italic code point from the mathematical alphabet, which
   only a mathematics font carries -- without one, every Greek variable is
   a box. Plain italic instead: any font with the letter draws it. */
.report math mi {
  text-transform: none;
  font-style: italic;
}

/* Δ, and names of several letters like max, stay upright. */
.report math mi[mathvariant='normal'] {
  font-style: normal;
}

/* A long formula scrolls on a phone rather than pushing the page wide. */
.report math[display='block'] {
  display: block;
  max-width: 100%;
  margin: 1.25em 0;
  overflow-x: auto;
  font-size: 1.15em;
}

/* --- figures --- */

.report-figure {
  margin: 0;
}

.report-map {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  min-height: min(60vh, 560px);
  overflow: hidden;
  background: #e9ece6;
}

/* Tracks are meant to be zoomed into: more height for that figure. */
.report-figure--tall .report-map {
  aspect-ratio: 4 / 3;
  min-height: min(75vh, 720px);
}

@media (max-width: 700px) {
  .report-map,
  .report-figure--tall .report-map {
    aspect-ratio: 3 / 4;
    min-height: 0;
  }
}

.report-map .report-map-canvas {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.report-map.is-live .report-map-canvas {
  opacity: 1;
}

.no-js .report-map .live-map-loading,
.report-map.is-live .live-map-loading,
.report-map[data-state='error'] .live-map-loading {
  display: none;
}

/* Without scripting there is no map to make room for: a short band
   carrying the note, and the stages read on below it. */
.no-js .report-map,
.no-js .report-figure--tall .report-map {
  aspect-ratio: auto;
  min-height: 7rem;
}

.report-map .maplibregl-ctrl-attrib {
  font-size: 0.68rem;
  color: var(--muted);
}

.report-map .maplibregl-ctrl-group {
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: none;
}

/* The record behind a point on the events map. */
.report-popup .maplibregl-popup-content {
  padding: 0.8rem 1rem 0.9rem;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  font: inherit;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--ink);
}

.report-popup .maplibregl-popup-content > span {
  margin-left: 0.4rem;
  font-size: 0.76rem;
  color: var(--muted);
}

.report-popup .maplibregl-popup-content p {
  margin: 0.4rem 0 0;
}

.report-popup .maplibregl-popup-close-button {
  padding: 0 0.5rem;
  font-size: 1.2rem;
  color: var(--muted);
}

/* --- stages --- */

.report-stages {
  margin-top: 1.1rem;
}

.report-stage-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}

.report-stage-controls {
  display: flex;
  gap: 0.5rem;
}

.report-stage-controls button {
  padding: 0.45rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: none;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: all 0.2s var(--ease);
}

.report-stage-controls button:hover:not(:disabled) {
  border-color: var(--forest);
  color: var(--ink);
}

.report-stage-controls button:disabled {
  opacity: 0.4;
  cursor: default;
}

.report-stage-controls button[aria-pressed='true'] {
  background: var(--forest);
  border-color: var(--forest);
  color: #fff;
}

.report-stage-count {
  margin: 0;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.report-stage-count b {
  color: var(--accent);
}

.report-stage-list {
  margin: 0.9rem 0 0;
  padding: 0;
  list-style: none;
}

.report-stage-list li {
  max-width: var(--measure);
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--muted);
}

.report-stage-list li + li {
  margin-top: 0.75rem;
}

.report-stage-list li b {
  display: block;
  margin-bottom: 0.2rem;
  color: var(--ink);
}

/* With scripting, one stage at a time. Without it there is no map to
   step through, so every stage reads as the figure's description, in
   order, and the controls go. */
html:not(.no-js) .report-stage-list li:not([aria-current='true']) {
  display: none;
}

.no-js .report-stage-bar {
  display: none;
}

.no-js .report-stage-list {
  counter-reset: stage;
}

.no-js .report-stage-list li b::before {
  counter-increment: stage;
  content: counter(stage) '. ';
}

.report-compare {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: 32rem;
  margin-top: 0.9rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.report-compare[hidden] {
  display: none;
}

.report-compare input {
  flex: 1;
  accent-color: var(--forest);
}

/* --- legend keys for the analysis --- */

.swatch--dot-ru,
.swatch--dot-ua {
  width: 0.8rem;
  height: 0.8rem;
  border: 1.5px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--line);
}

.swatch--dot-ru {
  background: var(--burgundy);
}

.swatch--dot-ua {
  background: var(--slate);
}

.swatch--ring {
  width: 0.9rem;
  height: 0.9rem;
  border: 1.5px solid var(--burgundy);
  border-radius: 50%;
  background: rgba(99, 5, 27, 0.08);
}

.swatch--fill-brass {
  background: var(--brass);
  border-color: var(--brass);
  opacity: 0.75;
}

.swatch--line-ru,
.swatch--line-ua,
.swatch--line-brass,
.swatch--line-faint {
  height: 0;
  border: 0;
  border-top: 3px solid;
}

.swatch--line-ru {
  border-color: var(--burgundy);
}

.swatch--line-ua {
  border-color: var(--slate);
}

.swatch--line-brass {
  border-color: var(--brass);
}

.swatch--line-faint {
  border-top-width: 2px;
  border-color: var(--muted);
  opacity: 0.5;
}

.swatch--dash-ru {
  height: 0;
  border: 0;
  border-top: 3px dashed var(--burgundy);
}

/* The AIS rasters are drawn upstream in their own colours. */
.swatch--ais-water {
  background: #1a98f8;
  border-color: var(--line);
}

.swatch--ais-land {
  background: #e02828;
  border-color: var(--line);
}

.swatch--ais-heat {
  background: linear-gradient(90deg, #a0e078, #f0d838, #f89820, #f83010);
  border-color: var(--line);
}

.swatch--tl {
  width: 0.8rem;
  height: 0.8rem;
  border: 0;
  border-radius: 50%;
}

.swatch--tl-attack {
  background: var(--burgundy);
}

.swatch--tl-drone_straying {
  background: var(--brass);
}

.swatch--tl-other {
  background: var(--slate);
}

.swatch--tl-media {
  width: 0.6rem;
  height: 0.6rem;
  background: var(--stone);
}

.swatch--tl-window {
  height: 0;
  border: 0;
  border-top: 2px dashed var(--muted);
}

/* --- the timeline --- */

/* On a phone the weeks would shrink to nothing; the chart keeps a
   readable width and the box scrolls sideways instead. */
.tl-scroll {
  overflow-x: auto;
}

.tl {
  display: block;
  width: 100%;
  min-width: 640px;
  height: auto;
}

.tl text {
  font-family: inherit;
  font-size: 11px;
  fill: var(--muted);
}

.tl-title {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  fill: var(--ink);
}

.tl-day {
  font-size: 10.5px;
}

.tl-lane {
  font-weight: 600;
}

.tl-grid {
  stroke: var(--line);
  stroke-width: 1;
}

.tl-base {
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}

.tl-base--media {
  stroke-dasharray: none;
}

.tl-window line {
  stroke: var(--muted);
  stroke-width: 1.5;
  stroke-dasharray: 5 4;
}

.tl-window text {
  font-size: 10px;
}

.tl-mark circle {
  stroke: #fff;
  stroke-width: 1.2;
}

.tl-mark:focus {
  outline: none;
}

.tl-mark:hover circle,
.tl-mark:focus circle {
  stroke: var(--ink);
}

.tl a .tl-mark circle {
  cursor: pointer;
}

.tl-attack circle {
  fill: var(--burgundy);
}

.tl-drone_straying circle {
  fill: var(--brass);
}

.tl-other circle {
  fill: var(--slate);
}

.tl-media circle {
  fill: var(--stone);
}

/* The hover card, one for the page, placed by assets/analysis.js. */
.report-tip {
  position: absolute;
  z-index: 30;
  max-width: 22rem;
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  background: var(--ink);
  box-shadow: var(--shadow);
  font-size: 0.82rem;
  line-height: 1.4;
  color: #fff;
  pointer-events: none;
}

/* The same events as text, under the chart. */
.report-list {
  margin-top: 1.25rem;
  font-size: 0.9rem;
}

.report-list summary {
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
}

.report-list h4 {
  margin: 1.25rem 0 0.5rem;
  font-size: 0.95rem;
}

.report-list ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.report-list li {
  display: grid;
  grid-template-columns: 11rem 8rem 1fr;
  gap: 0.25rem 1rem;
  align-items: baseline;
  padding: 0.4rem 0;
  border-top: 1px solid var(--line);
}

@media (max-width: 700px) {
  .report-list li {
    grid-template-columns: 1fr;
    gap: 0.1rem;
  }
}

.report-list time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.report-list li i {
  color: var(--muted);
}

.tl-tag {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.tl-tag--attack {
  color: var(--burgundy);
}

/* Brass fails contrast on white as a word; the lane's colour, darkened. */
.tl-tag--drone_straying {
  color: #7a6a3c;
}

.tl-tag--other {
  color: var(--slate);
}

.tl-tag--media {
  color: var(--stone);
}

/* --- MPK cooperation ------------------------------------------------------
 *
 * Mark left, statement right, and the mark holds its width rather than
 * shrinking with the column: a partner's logo has a stated minimum size
 * (25 mm, about 95px) and shrinking to fit is how that gets breached without
 * anyone noticing.
 *
 * The padding is MPK's required clear space -- the width of the shield's
 * claws, which at 180px wide is about 14px. It is padding on the mark rather
 * than a gap on the row, so the rule holds however the text wraps or where
 * the block is used. */
.mpk {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.5rem);
  align-items: center;
  padding-block: clamp(1.5rem, 3vw, 2rem);
  border-top: 1px solid var(--line);
}

.mpk-mark {
  display: block;
  padding: 14px;
  flex: none;
}

.mpk-mark img {
  display: block;
  width: clamp(140px, 14vw, 180px);
  height: auto;
}

.mpk p {
  margin: 0;
  color: var(--muted);
  max-width: 62ch;
}

/* Stacked on a phone: side by side, the mark would either breach its minimum
   width or leave the text in a two-word column.
   
   Closed with a rule underneath as well, and more air above and below. On a
   wide screen the block reads as a footnote to the section above it and one
   rule is enough; stacked, it is a tall column of its own between two other
   sections, and without a bottom edge it runs into whatever follows. */
@media (max-width: 34rem) {
  .mpk {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
    padding-block: 2.25rem;
    border-bottom: 1px solid var(--line);
  }
}

/* The footer carries the mark alone, with no statement beside it.
   
   Smaller than in the body -- it is an attribution here rather than a
   statement -- but still well clear of MPK's 25 mm (~95px) minimum. The
   padding is the required clear space, kept on all four sides now that the
   mark sits inside a column with text above it. */
.footer-mpk {
  display: inline-block;
  margin-top: 1.25rem;
  padding: 12px;
  margin-left: -12px;
}

.footer-mpk img {
  display: block;
  width: 112px;
  height: auto;
}

/* On a phone the footer columns stack, so the mark needs its own breathing
   room rather than sitting straight under the paragraph. */
@media (max-width: 34rem) {
  .footer-mpk {
    margin-top: 1.75rem;
    margin-bottom: 0.5rem;
  }

  /* 104px is about 27 mm at the CSS reference pixel, so it still clears
     MPK's 25 mm minimum with room to spare. 100 would clear it by a
     millimetre, which is not a margin worth having on someone else's rule. */
  .footer-mpk img {
    width: 104px;
  }
}
