@import url("https://fonts.googleapis.com/css2?family=Big+Shoulders+Display:wght@900&family=Roboto:ital,wght@0,400;0,500;0,700;1,400&display=swap");

/* Override Observable's default sans-serif with Roboto */
:root {
  --sans-serif: "Roboto", sans-serif;

  /* Chrome the page styles in CSS. The chart colors live in
     components/theme.js instead, because Plot needs real values rather than
     custom properties — see the note there. Anything appearing in both places
     has to be kept in step by hand; the accents are the overlap. */
  --accent-arrests: #1662a9;
  --accent-detentions: #efb118;
  --accent-deportations: #f75b41;
  --chart-divider: #e5e7eb;
  --date-label: #606360;
  --filter-bar-background: #ffffff6e;
  --hero-band: #f5f2ed;
  --hero-highlight: #de9f00;
  --lead-in-accent: #0d6ce0;
}

/* The pages ask Framework for a light-only theme, so the only --theme-*
   variables it ships are the light ones. Dark mode is driven by the toggle
   rather than by prefers-color-scheme — a media query would fight an explicit
   choice — so the dark values are restated here against [data-theme="dark"].
   These are near-midnight's own values and its structure: --theme-background-b
   is the base and -a the raised card surface, which is the reverse of the light
   theme, where -a is the base. theme.js sets the attribute. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --theme-foreground: #dfdfd6;
  --theme-background-b: #161616;
  --theme-background-a: color-mix(
    in srgb,
    var(--theme-foreground) 4%,
    var(--theme-background-b)
  );
  --theme-background: var(--theme-background-a);
  --theme-background-alt: var(--theme-background-b);
  --theme-foreground-alt: color-mix(
    in srgb,
    var(--theme-foreground) 90%,
    var(--theme-background-b)
  );
  --theme-foreground-muted: color-mix(
    in srgb,
    var(--theme-foreground) 60%,
    var(--theme-background-b)
  );
  --theme-foreground-faint: color-mix(
    in srgb,
    var(--theme-foreground) 50%,
    var(--theme-background-b)
  );
  --theme-foreground-fainter: color-mix(
    in srgb,
    var(--theme-foreground) 30%,
    var(--theme-background-b)
  );
  --theme-foreground-faintest: color-mix(
    in srgb,
    var(--theme-foreground) 14%,
    var(--theme-background-b)
  );

  /* Dark counterparts of the chrome above. The two accents that need lifting
     match theme.js: #1662a9 is too dark to carry a chart on #212120, and the
     coral gains a little headroom. The brand yellow already clears 8:1 on a
     dark surface, so it stays put. */
  --accent-arrests: #4c9be0;
  --accent-detentions: #efb118;
  --accent-deportations: #ff7259;
  --chart-divider: #3a3a38;
  --date-label: #b4b4ab;
  --filter-bar-background: #ffffff0f;
  --hero-band: #23211e;
  --hero-highlight: #efb118;
  --lead-in-accent: #e7b116;
}

.corner-logo {
  position: absolute;
  top: -1rem;
  right: 0rem;
  width: clamp(40px, 6vw, 100px);
  height: auto;
  z-index: 1000;
}

/* The default wordmarks are dark grey and all but vanish on a dark page, so
   each one ships alongside its lightest-grey counterpart and the theme picks.
   Both variants are real <img> tags in the markup rather than a CSS
   `content: url(...)` swap, because Framework only copies an asset into the
   build when something in the page references it — a url() inside this
   stylesheet isn't tracked, and the dark artwork would 404 once deployed.
   display:none also keeps the inactive one out of the accessibility tree. */
/* The hero wordmark's layout lives here rather than in a style attribute: an
   inline `display: block` outranks the .logo-light rule below, which left both
   variants on screen at once in dark mode. */
.hero-logo {
  display: block;
  margin: 0 auto;
  /* Narrow screens only — the wordmark at 75% of the column reads as a
     signature above the title rather than a second headline beside it. The
     desktop width is in the media query at the end of this file, which tracks
     the title instead of capping. .hero is the query container both rules
     measure against. */
  width: min(320px, 75cqw);
  max-width: 100%;
}

/* Declared after .hero-logo so the equal-specificity display wins on order. */
.logo-dark {
  display: none;
}

:root[data-theme="dark"] .logo-light {
  display: none;
}

:root[data-theme="dark"] .logo-dark {
  display: block;
}

/* The toggle shares the top-right chrome band with the corner logo. The logo is
   position: absolute against #observablehq-center with `right: 0`, so this sits
   at the same top and is offset left by the logo's own clamped width plus a
   gutter. Anchoring both to the same edge keeps them from drifting apart as the
   logo resizes with the viewport. */
.theme-toggle {
  position: absolute;
  /* Level with the logo, so the toggle sits in the chrome band rather than in the
     title's line. At 0.25rem its lower half came down 25px into that line, and at
     768px — where the title runs the full width of the card on one line — it
     landed squarely on "Detentions", 44px of overlap. The chip sits 6px inside
     the 44px target, so this leaves the stroke clear of the container's edge. */
  top: -1rem;
  right: calc(clamp(40px, 6vw, 100px) + 1.5rem);
  z-index: 1000;
  /* 44px min target (WCAG 2.5.5) while the visible chip stays 32px. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: none;
  color: var(--theme-foreground-muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.theme-toggle::before {
  content: "";
  position: absolute;
  width: 32px;
  height: 32px;
  border: 1px solid var(--theme-foreground-faintest);
  border-radius: 999px;
  background: var(--theme-background);
  transition: border-color 120ms ease;
}

.theme-toggle svg {
  position: relative;
}

.theme-toggle:hover,
.theme-toggle:focus-visible {
  color: var(--theme-foreground);
}

.theme-toggle:hover::before,
.theme-toggle:focus-visible::before {
  border-color: var(--theme-foreground-muted);
}

/* The home page leads with a centred hero logo instead of a corner one, so
   there's nothing to sit beside and the toggle takes the corner itself. Only
   `right` changes: mountThemeToggle puts this in the same container the corner
   logo would occupy, so `top` above still measures from the same edge. */
.theme-toggle.is-solo {
  right: 0;
}

/* Where the toggle sits once it drops below the logo, measured from the top of
   the chrome band. The logo is clamp(40px, 6vw, 100px) wide and about 1.2 times
   that tall, and the half-rem comes back off so the two nearly touch. A token
   because two rules need it and they must agree: the toggle positions itself by
   it, and .text-page reserves height by it. */
:root {
  --dropped-toggle-top: calc(clamp(40px, 6vw, 100px) * 1.2 - 0.5rem);
}

/* At 375px the logo lands at x=303 and the sidebar drawer owns the leftmost
   32px, so the toggle drops below the logo rather than trying to share the row
   with it. Under 360px there isn't a clear 44px of runway beside the wordmark. */
@media (max-width: 400px) {
  .theme-toggle:not(.is-solo) {
    top: var(--dropped-toggle-top);
    right: 0;
  }

  /* Except on the prose pages, where it stays in the logo's row. Dropping it
     costs vertical space that only the dashboards can absorb: they are
     absolutely positioned, so neither the logo nor the toggle takes any height,
     and a dropped toggle came down onto the opening lines of About's first
     paragraph. Reserving that height pushed the whole page down by 84px, which
     read as a hole above the title.

     These pages can keep it in the row because what it shares the row with is a
     title, not a body paragraph, and a title can be made to wrap short of it —
     see .text-page h1 below. The dashboards can't: their hero runs at 7vw and
     would still be too tall to clear it. */
  #observablehq-main:has(.text-page) .theme-toggle:not(.is-solo) {
    top: -1rem;
    right: calc(clamp(40px, 6vw, 100px) + 1.5rem);
  }

  /* The title's half of that bargain: wrap before the chrome rather than run
     under it. About's headings are single words and never come close, but the
     privacy notice's title is long enough to reach across, and what stopped it
     at 375px was Framework's `text-wrap: balance` — which happened to end its
     first line 2px short of the toggle. Balance is a suggestion the browser is
     free to interpret, and Safari is where this page is being read, so the room
     is reserved outright: the logo's width, the gutter between the two, and the
     toggle's own 44px target. */
  .text-page h1 {
    padding-right: calc(clamp(40px, 6vw, 100px) + 1.5rem + 44px);
  }
}

#observablehq-sidebar {
  font-family: "Big Shoulders Display", sans-serif;
}

/* Framework sets the sidebar to 14px with its title at 16px. Big Shoulders is a
   condensed face and reads small at those sizes, so both step up together —
   the title stays ahead of the page links so the hierarchy holds. */
#observablehq-sidebar > ol:first-child {
  font-size: 20px;
}

#observablehq-sidebar > ol:not(:first-child) a {
  font-size: 16px;
}

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: var(--sans-serif);
  margin: 4rem 0 4rem;
  text-wrap: balance;
  text-align: center;
  /* Makes .hero the query container the h1 below sizes itself against. The
     sidebar takes 288px out of the viewport at desktop and nothing at mobile,
     so vw units would need two different coefficients either side of that
     breakpoint; cqw is a share of this box, which is the width the title
     actually has to fit. */
  container-type: inline-size;
}

/* Set main page title to Big Shoulders */
.hero h1 {
  margin-bottom: 1rem;
  margin-top: -0.25rem;
  padding: 0;
  max-width: none;
  font-family: "Big Shoulders Display", sans-serif;
  /* The line breaks in the title are hard <br>s in index.md, so the only job
     left here is keeping the longest line from wrapping again on its own. In
     Big Shoulders Display "PACIFIC ISLANDER" measures 6.48× its font size and
     "ASIAN & PACIFIC ISLANDER" 9.75×, so the widest line fits at 15.42cqw and
     10.26cqw respectively; both are rounded down for margin. Retune these if
     the wording of the title or where it breaks changes — "PACIFIC ISLANDER"
     stays whole on one line, which is what sets the narrow-screen figure. */
  font-size: min(90px, 15.3cqw);
  line-height: 1;
  background: linear-gradient(
    30deg,
    var(--theme-foreground-focus),
    currentColor
  );
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.hero h2 {
  margin: 0;
  max-width: 36em;
  font-size: 20px;
  font-style: initial;
  font-weight: 500;
  line-height: 1.5;
  color: var(--theme-foreground-alt);
  /* The `balance` inherited from .hero is ignored past about four lines, and
     this paragraph runs six — which left "available." stranded on its own line.
     `pretty` has no such limit and targets exactly that last-line case, so the
     orphan can't come back at some other window width. */
  text-wrap: pretty;
}

/* The intro is two paragraphs now, and .hero h2 zeroes its margins so the first
   one can sit tight under the title. Only the seam between them opens up. */
.hero h2 + h2 {
  margin-top: 1rem;
}

@media (min-width: 640px) {
  .hero h1 {
    font-size: min(130px, 10.2cqw);
  }

  /* Holds the wordmark at the same share of the title it has on a laptop, so
     the pair keeps its proportions as the window grows rather than the title
     running away from a fixed-width logo. 49cqw is the 320-against-656 the
     design was signed off at; the 620px ceiling is that same 49% of the
     title's widest line, which the 130px cap above pins at 1267px — without it
     the logo would carry on growing after the title had stopped. */
  .hero-logo {
    width: min(620px, 49cqw);
  }

  /* Above 640px the title is two lines — "ASIAN & PACIFIC ISLANDER" over "ICE
     TRACKER" — so the break after the ampersand goes away. Hiding it keeps the
     space that precedes it in the markup, which is what holds "&" and
     "PACIFIC" apart once they share a line. */
  .hero h1 br.hero-break-narrow {
    display: none;
  }
}

/* Set all other dashboard titles to Roboto */
.dashboard-hero {
  display: flex;
  flex-direction: column;
  margin: 1.5rem 0 2rem;
  font-family: var(--sans-serif);
  text-wrap: balance;
}

.dashboard-hero h1 {
  max-width: none;
  font-size: 7vw;
  font-weight: 900;
  letter-spacing: -0.02em;
  font-family: "Big Shoulders Display", sans-serif;
  /* Keeps the title out of the top-right chrome band so it wraps before reaching
     the logo rather than running under it. Lifting the toggle takes it out of the
     title's line, but the logo hangs lower than the toggle does and only cleared
     "Detentions" by 11px at 768px — a longer page name closes that. Reserving the
     logo's own clamped width plus its gutter means no title can reach it, and at
     laptop width there is still room for this one to stay on a single line. */
  padding-right: calc(clamp(40px, 6vw, 100px) + 1.5rem);
}

.dashboard-hero h2 {
  font-size: 1rem;
  font-style: initial;
  font-weight: 400;
  color: var(--theme-foreground-muted);
}

@media (min-width: 640px) {
  .dashboard-hero h1 {
    font-size: 42px;
  }

  .dashboard-hero h2 {
    font-size: 16px;
  }
}

/* Set rest of dashboard to Roboto */
.dashboard {
  font-family: var(--sans-serif);
}

/* Set chart text, incl tooltips, axis labels, to Roboto */
svg text {
  font-family: "Roboto", sans-serif;
}

/* Remove extra space between body and footer */
#observablehq-main {
  margin-bottom: -125px !important;
}

/* Style minimized bands in detentions dashboard */
.stats-band {
  display: flex;
  align-items: stretch;
  background: var(--hero-band, #f5f2ed);
  border: 1px solid var(--theme-foreground-faintest);
  border-radius: 8px;
  padding: 0.65rem 1.25rem;
  margin-top: 0.75rem;
  font-family: "Roboto", sans-serif;
}

.stats-band-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0 1.25rem;
  border-right: 1px solid var(--chart-divider, gainsboro);
}

.stats-band-item:first-child {
  padding-left: 0;
}
.stats-band-item:last-child {
  border-right: none;
}

.stats-band-label {
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--theme-foreground-muted);
  font-family: "Roboto", sans-serif;
}

.stats-band-value {
  font-size: 1.1rem;
  font-weight: 500;
  color: var(--theme-foreground);
  font-family: "Roboto", sans-serif;
}

.stats-band-unit {
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--theme-foreground-muted);
  font-family: "Roboto", sans-serif;
}

/* Defines a term the figure above it uses, for the one band item whose unit is
   jargon. Upright rather than italic like .stats-band-compare below — a
   definition is part of the label, not an aside about the number — and small
   enough to sit under the value without competing with it.

   A line of its own, rather than a parenthetical in the label, because the label
   cannot hold one. At the 861px viewport where the band is still three columns
   (the breakpoint below) a label gets about 207px, and at 13px Roboto with this
   letter-spacing "Avg # of stints per detention" measures 178px — one line — while
   "Avg # of stints (facilities) per detention" measures 246px and wraps to two.
   One label wrapping while its two neighbours do not is the ragged row that
   breakpoint exists to prevent, so the gloss goes here instead, where it has the
   column to itself. */
.stats-band-define {
  font-size: 11px;
  color: var(--theme-foreground-muted);
  font-family: "Roboto", sans-serif;
}

/* Puts the gloss directly under the label it defines, rather than under the
   figure, by sending the figure past it — the markup keeps its label, figure,
   gloss order, which is the order to read them in and so the order to announce
   them in. Set on the value in every item, not just this one: the other two have
   no gloss, so ordering their figure last leaves them exactly as they were. The
   narrow layout below puts it back, because there the label and figure share the
   top line and the gloss already falls under the label.

   margin-top keeps the three figures on one line. Without it the glossed item's
   figure sits a line below its neighbours' — the gloss above has pushed it down
   — and three numbers meant to be read across come out on two levels. Pushing
   every figure to the bottom of its item instead lands them all together,
   because the items are already stretched to a common height. */
.stats-band-value {
  order: 1;
  margin-top: auto;
}

.stats-band-compare {
  font-size: 11px;
  color: var(--theme-foreground-muted);
  font-style: italic;
  margin-top: 2px;
  font-family: "Roboto", sans-serif;
}

/* Three figures side by side stop being a row well before a phone: the columns
   came out 80, 100 and 104px wide there, which broke the labels over three and
   four lines and the first figure — which carries two numbers — over fifteen, for
   a band 242px tall.

   So it becomes a list instead, one figure per line with its label against the
   left edge and its number against the right. Reading down a column of numbers
   suits a narrow screen better than reading across three, and it gives the long
   first figure the width of the card rather than a third of it.

   The breakpoint is this band's own rather than the 640px the grid and the
   filters use, because what it depends on is that first figure: "81 days | 45
   days longer than non-A/PI" wants about 260px of column to stay on one line,
   so three of them need roughly 860px of viewport. Matching the grid instead
   left tablets in the row layout at 230px a column, where the first cell wrapped
   to three lines while its neighbours wrapped to two and the row read ragged. */
@media (max-width: 860px) {
  .stats-band {
    flex-direction: column;
  }

  .stats-band-item {
    /* Label and figure on one line, and the figure free to drop below its label
       rather than be squeezed when it is the long one. */
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.25rem 1rem;
    padding: 0.5rem 0;
    /* The rules between the figures turn with them. */
    border-right: none;
    border-bottom: 1px solid var(--chart-divider, gainsboro);
  }

  .stats-band-item:first-child {
    padding-top: 0;
  }

  .stats-band-item:last-child {
    padding-bottom: 0;
    border-bottom: none;
  }

  /* Here the item is a row of label-then-figure, and a third child would be
     pushed into the gap between them by space-between — the definition landing
     mid-row, reading as though it belonged to the number. Full width sends it to
     a line of its own under both, where it is already directly under the label
     the wide layout has to reorder to achieve. */
  .stats-band-define {
    flex: 0 0 100%;
  }

  /* And so the reorder above is dropped: the figure belongs on the top line
     beside its label here, not after the gloss. The auto margin goes with it —
     these items are rows, where it would push the figure rightwards instead. */
  .stats-band-value {
    order: 0;
    margin-top: 0;
  }
}

/* The two-figure cards at the top of each dashboard: a headline count on one
   side, its change against the previous administration on the other.
   container-type lets the figures size themselves against the card instead of
   the viewport, which is what they actually have to fit inside — the same card is
   441px wide in a two-across row and 232px at the width where the deportations
   row first becomes three columns. */
.ban-card {
  container-type: inline-size;
  display: flex;
  align-items: stretch;
  gap: 1rem;
}

/* A flex-basis of 0 needs min-width: 0 beside it to mean anything: the default
   min-width:auto stops an item shrinking below its own content, so the half
   holding the longest figure claimed extra room and the divider sat somewhere
   different in every card (measured 90/99/87/105px across one row of three). */
.ban-figure {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  text-align: center;
}

/* Labels run to one, two or three lines depending on their wording and the
   width, so the figures are pinned to the bottom of the card rather than pushed
   down by whatever sits above them. Cards in a grid row are equal height, so
   that lines the figures up across the row. */
.ban-figure h2 {
  margin: 0 0 0.25rem;
}

.ban-divider {
  flex: none;
  width: 1px;
  background: var(--chart-divider);
}

/* An arrow and its number are one token and must never be split across lines.
   formatPct on each page joins them with a non-breaking space for the same
   reason; this is the half of the fix that survives the number getting longer. */
.ban-figure .big {
  white-space: nowrap;
  /* One rule for both figures, so a card's count and its comparison are always
     the same size as each other. That means the size has to suit whichever of
     the two is longest. This ratio was set from deportations' "↑ 1,539%", which
     wanted 129px at 32px while its half of the card was 111px at a 1280px
     laptop. That figure was an error — unreported destinations were counting as
     third-country deportations — and correcting it took the page's longest value
     down to "↑ 829%", so the ratio now carries more margin than it was cut for.
     Left as it is because a step-down is still needed for a three-across row,
     and because the next data release moves these numbers again; re-measure
     before tightening it.

     A step-down is what has to happen either way: nowrap on its own would spill
     a figure over the card edge instead of wrapping, and a count has no break
     opportunity in it to wrap at — toLocaleString uses commas, not spaces.

     Note cqw resolves against the card's content box, not its border box, so
     10% of a 288px card is 25px rather than 29px. */
  font-size: clamp(1.15rem, 10cqw, 2rem);
}

.filter-bar {
  margin-bottom: 1rem;
  background: var(--filter-bar-background, #ffffff6e);
}
.filter-bar button {
  background-color: rgb(160, 159, 159);
  border: 1px solid;
  border-radius: 4px;
  padding: 4px 8px;
  cursor: pointer;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: white;
  float: right;
}
.filter-label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--theme-foreground-muted);
}
/* Every select is the same width regardless of how long its own label runs, or
   how long its longest option is. Framework puts an `inputs-*` class on the
   select as well as on the form around it, so the `[class^="inputs-"]` rule
   below used to match both — and `width: auto !important` on a select makes it
   shrink to its own longest option. That is what left the four filters at
   137/107/37/37px, and why the width here needs the wrapper rule scoped to
   `form` to have any effect at all. */
.filter-inputs select {
  flex: none;
  width: 150px;
}
.filter-inputs {
  display: flex;
  gap: 0.9rem 1.5rem;
  align-items: flex-start;
  flex-wrap: wrap;
  margin-top: 0.5rem;
}

/* The filters that can be hidden are wrapped in a div and the rest are not, so
   the flex children here are a mix of form and div. Size them by position rather
   than by element, or half of them are missed. */
.filter-inputs > * {
  flex: 0 0 auto;
}

/* Every filter is one fixed-width column with its label above its select, which
   is what makes them all the same size and keeps their selects on a common left
   edge however the row wraps. The label used to sit beside the select instead,
   and that cost more than alignment: at 136px of label plus a 150px select, a
   filter was 294px wide and only two of them fitted the 863px card — so the
   third wrapped to a row of its own. Stacked, a filter is as wide as its select,
   and all four fit one row with room to spare. */
.filter-inputs form[class^="inputs-"] {
  width: 150px !important;
  flex-direction: column;
  align-items: stretch;
}

/* nowrap with the fixed width above means a label longer than its column would
   spill rather than wrap. The longest here is "Subregion of Origin" at 111px,
   so there is nearly 40px of headroom; a longer one would need this widened. */
.filter-inputs form[class^="inputs-"] > label {
  width: auto !important;
  white-space: nowrap;
  margin: 0 0 0.2rem !important;
}

/* A filter with nothing to offer yet — subregion before a region is picked —
   collapses rather than holding its place. Reserving the space is what put an
   empty band inside the card that read as a second, blank filter bar: the
   placeholder was the only thing on the wrapped row, so the row rendered with
   nothing visible in it at all. Collapsing cannot do that, and because the
   filters now fit a single row with slack, one appearing takes a free slot
   beside the others without moving anything below it. */
.filter-hidden {
  display: none;
}

/* Below the width where the dashboard itself drops to a single column, each
   filter takes a row of its own and runs the width of the card. */
@media (max-width: 640px) {
  .filter-inputs > * {
    flex: 1 1 100%;
  }
  .filter-inputs form[class^="inputs-"] {
    width: 100% !important;
  }
  .filter-inputs select {
    width: 100%;
    /* Stops the control running the full width of a 600px-wide card. */
    max-width: 20rem;
  }
}

/* The wrapper for a page that is nothing but prose — About and the privacy
   notice both use it, so nothing below here may assume either one. It was
   called .about while About was the only such page; anything genuinely specific
   to one of them belongs on a class of its own, the way .updated-note and
   .last-updated below split the two pages' date stamps. */
/* 34px rather than the 30px this started at, to open the gap over About's 24px
   section heads. Six points between two headings in the same face was not
   enough to tell them apart, and the colour step .section-head takes has only
   about 45 units of light-theme grey to work in before it starts matching the
   body text — so the size carries half of the separation and the colour the
   other half. The privacy notice's single h1 rides along, which is fine: it is
   that page's title, and nothing there competes with it. */
.text-page h1 {
  max-width: none;
  font-size: 34px;
  font-weight: 900;
  letter-spacing: -0.02em;
  font-family: "Big Shoulders Display", sans-serif;
}

.text-page p {
  max-width: 700px;
  font-size: 18px;
  font-family: var(--sans-serif);
  color: rgb(95, 95, 95);
}

/* That grey is a deliberate step back from black on a white page, but it is a
   fixed value: left alone it sits at 2.6:1 on the dark background, well under
   the 4.5:1 body text needs, and this page is nothing but body text. The muted
   token is the same idea expressed against whichever background is in play,
   and reaches 5.1:1 here against the light page's 5.9:1. */
:root[data-theme="dark"] .text-page p {
  color: var(--theme-foreground-muted);
}

/* The subregion list is body text that happens to be itemised, so it takes the
   paragraph's measure, size and colour rather than the browser's list defaults.
   It carries its own copy of the grey and of the dark-theme override above:
   inheriting from .text-page p is not an option, since the list is a sibling of
   those paragraphs, not a child. */
.text-page ul.subregions {
  max-width: 700px;
  margin: 0.75rem 0 0;
  padding-left: 1.35rem;
  font-size: 18px;
  font-family: var(--sans-serif);
  color: rgb(95, 95, 95);
}

:root[data-theme="dark"] .text-page ul.subregions {
  color: var(--theme-foreground-muted);
}

.text-page ul.subregions li {
  margin-bottom: 0.4rem;
}

/* Same exemption as .last-updated below: the accent marks the lead-in that
   heads a section, and these label items inside one. Left blue, seven of them
   in a row read as seven new sections rather than as one list. */
.text-page ul.subregions strong {
  color: inherit;
}

/* The bold lead-ins are the only wayfinding on a page that is otherwise
   unbroken body text, so they carry an accent rather than just weight. The
   brand yellow does that job on a dark page but falls to 1.7:1 on a white one,
   so the light theme borrows a blue instead.

   This is the privacy notice's rule now: About used to be built the same way,
   but its lead-ins became real headings, and .section-head below opts out of
   the accent because at that size and in that face it no longer needs one. */
.text-page :is(strong, h2) {
  color: var(--lead-in-accent);
}

/* The privacy notice's dateline is bold for emphasis, not because it heads a
   section, so it opts out of the accent and stays body text. */
.text-page .last-updated strong {
  color: inherit;
}

/* About's date stamp closes the page instead of heading it, which is the whole
   difference from the privacy notice's dateline above: it steps down a size
   from the body text and takes a wider gap above than the paragraphs leave
   between themselves. Roboto and the 700px measure come from .text-page p,
   since this is a paragraph too. It keeps the body grey rather than going
   lighter still — at 14px the contrast floor is the same 4.5:1, and this grey
   clears it at 6.4:1 on the light page. */
.text-page .updated-note {
  margin-top: 2.5rem;
  font-size: 14px;
}

/* The privacy notice's headings are real <h2>s where About's are inline
   lead-ins, so they have to be talked back down to the size and weight of the
   text they introduce, then pulled tight against the paragraph they belong
   to — the gap above the heading is what separates the sections. */
.text-page h2 {
  max-width: 700px;
  margin: 2rem 0 0;
  font-size: 18px;
  font-weight: 700;
  font-family: var(--sans-serif);
  line-height: 1.5;
}

.text-page h2 + p {
  margin-top: 0.25rem;
}

/* About's three heading levels, which have to read as three sizes in order:
   the h1s above (Sources, Methodology), then these section heads (Counts, Age,
   Disclaimers…), then the subsection heads below them (Asia, Pacific Islands).

   Section heads take the h1s' condensed Big Shoulders a few points smaller, so
   the level below the page's own headings still looks like the same family of
   thing. Weight 900 because that is the only Big Shoulders face the @import at
   the top of this file asks for. They override the size, weight and family of
   .text-page h2 above and the accent colour of the :is(strong, h2) rule below:
   at this size and in this face the heading carries itself, and it needs to
   read darker than the body grey underneath it.

   Not the full-strength foreground the h1s take, though — six points of size is
   not much separation between two headings in the same face, and at a glance
   they ran together. Stepping the colour back opens the gap the size alone
   didn't. It is mixed towards the page rather than set to a fixed grey so that
   "back" means lighter on the light theme and darker on the dark one; a literal
   grey would have gone the wrong way on one of them. 78% keeps it clear of the
   body text below (rgb(95, 95, 95) on the light page) as well as of the h1s. */
.text-page h2.section-head {
  margin: 2.25rem 0 0.35rem;
  font-family: "Big Shoulders Display", sans-serif;
  font-size: 24px;
  font-weight: 900;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: color-mix(in srgb, var(--theme-foreground) 78%, var(--theme-background));
}

/* Subsection heads stay in Roboto with the rest of the body text, which leaves
   them competing with the bold subregion labels directly beneath — same face,
   same weight. Small caps and letter-spacing are what separate the two: the
   labels are sentence-case bold inside a sentence, these are a short word set
   apart above the list. Below the section heads on size, as the order requires,
   but caps and tracking keep them from reading as merely smaller.

   Colour is mixed towards the page the same way .section-head is, but further —
   73% against its 78%. These took the full-strength foreground at first, which
   inverted the hierarchy: at rgb(27, 30, 35) they were the darkest thing on a
   light page, out-shouting the section heads above them and even the h1s. Set
   between the section heads and the body text they read as subordinate to one
   and still ahead of the other, and because the mix is towards the background
   rather than towards a fixed grey, "further back" comes out lighter on the
   light page and darker on the dark one, which is the same move in both.

   The band is narrow — 75 to 95 on the light page — so there is not much room
   either side of this. Much lighter and it matches the paragraph it heads. */
.text-page h3.subsection-head {
  max-width: 700px;
  margin: 1.5rem 0 0.4rem;
  font-family: var(--sans-serif);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--theme-foreground) 73%, var(--theme-background));
}

/* The heading already opens the gap the list would otherwise open for itself. */
.text-page h3.subsection-head + ul.subregions {
  margin-top: 0;
}

.source-p {
  margin-bottom: 2rem;
}

/* The row gap is smaller than the column gap because each item now carries its
   own vertical padding — see .line-legend-item. Together they come to the same
   ~30px from one row of the legend to the next that a 1rem gap gave when the
   items were bare text, so the wrapping doesn't change and a legend that ran to
   four rows still runs to four. It grows by the padding on the first and last
   of them, about 14px on that four-row legend, which is what nearly doubling
   the tap targets costs. */
.line-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  margin: 0.5rem 0 0.75rem;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* Buttons rather than the spans these started as: they are the page's only
   interactive charting control, and a span gives no keyboard focus, no role and
   no pressed state to a screen reader. Hence the reset — a button brings its
   own border, background, font and alignment, none of which suit a legend.
   `aria-pressed` on the element says which one is highlighted.

   The padding is the point of the exercise. These were 59x13 hit targets, well
   under the 24px WCAG 2.2 asks for, and on a phone a good share of taps missed.
   0.35rem top and bottom takes them to about 24px without moving anything, per
   the row gap above.

   `touch-action: manipulation` is the other half, and the likeliest cure for
   the scrolling: it tells WebKit this element will never be double-tapped, so
   the browser stops watching for that gesture. Without it, two quick taps on
   neighbouring legend items — which is exactly how someone reads through the
   categories — can be taken for a double-tap-to-zoom and answered by moving the
   viewport. The page's `maximum-scale=1` does not prevent that; iOS has ignored
   it for accessibility since iOS 10. */
.line-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.25rem;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  cursor: pointer;
  color: var(--theme-foreground-muted);
  user-select: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.line-legend-item:hover {
  color: var(--theme-foreground);
}

/* Now that these are buttons they can be tabbed to, so the focus has to show. */
.line-legend-item:focus-visible {
  color: var(--theme-foreground);
  outline: 2px solid var(--theme-foreground-focus);
  outline-offset: 1px;
  border-radius: 3px;
}

.line-legend-item.active {
  color: var(--theme-foreground);
  text-decoration: underline;
}

.line-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}

/* The state maps cap the width they draw at, so past a certain card width the map
   is narrower than the card holding it — and a block-level SVG left to itself sits
   against the left edge with all the slack piling up on the right. This centres it
   in whatever room the card has. It is scoped to the map cards rather than every
   chart because the rest fill their cards, where `auto` margins would do nothing
   and only invite the question of why they were there.

   `[class^="plot-"]` is Plot's own generated class on the SVG it returns, which
   distinguishes the map from the ramp legend above it and the download icon
   below — both also SVGs in this card. */
.map-card svg[class^="plot-"] {
  margin-inline: auto;
}

/* Stands in for Plot's ramp legend on the heatmaps and maps. */
.ramp-legend {
  display: block;
  margin: 6px 0 10px;
  overflow: visible;
}

/* Lets the footer below sit at the bottom of the card. */
.dashboard .card {
  display: flex;
  flex-direction: column;
}

/* Bottom row of a chart card: on-page caveat on the left, the control that
   downloads the chart as a branded image on the right. */
.chart-footer {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  /* Sits as low in the card as it can without leaving it. The clearance above
     is padding rather than margin so `auto` can't collapse it, and is raised at
     runtime for charts whose axis labels spill below the plot box. */
  margin-top: auto;
  padding-top: 12px;
}

/* Sits between a card's title and its chart, saying what the marks count before
   the reader reads one. Deliberately an <h3>, so its size, weight and colour are
   the ones the line chart's "Click to highlight" subtitle already establishes —
   14px against the note's 11.9px, which is the point of putting this here rather
   than in the note.

   max-width is overridden for the reason .chart-note overrides it below:
   Framework caps every paragraph at 640px, which held this to three lines in a
   card half again as wide. Nothing shares its row, so it takes the full width.
   The extra bottom margin separates it from the colour legend beneath, which the
   h3 default 4px left looking attached to.

   text-wrap is overridden because this is a heading element carrying a
   sentence. Framework balances heading lines, which is what you want for a
   title and wrong here: balance equalises the lines instead of filling them, so
   the subtitle ran to about two thirds of the width the note below it uses, and
   every line got shorter as the text wrapped to more of them — at three lines
   each sat about a third across the card. It also meant the layout moved
   whenever the wording changed length, which is how this was found: three
   characters more ("facilities" for "centers") crossed a line boundary and
   re-balanced the whole thing. `pretty` fills each line like the note does and
   only intervenes to avoid leaving a single word on the last one. */
.chart-subtitle {
  max-width: none;
  text-wrap: pretty;
  margin-bottom: 10px;
}

.chart-note {
  margin: 0;
  font-size: 0.85em;
  color: gray;
  /* Framework caps every paragraph at 640px, which wrapped the longer map notes
     onto three lines in a card twice that wide. These run the width of the card
     instead, with the flex gap keeping them clear of the download button. */
  max-width: none;
  flex: 1 1 auto;
  min-width: 0;
}

/* Hides the redraw an export needs — see showExportScrim in
   components/chartDownload.js. Its background and text colour are set inline
   from the live page, because the theme variables are the thing changing
   underneath it. Above .corner-logo at 1000 so nothing shows through. */
.chart-export-scrim {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--sans-serif);
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  cursor: progress;
}

.chart-download-button {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--theme-foreground-faintest);
  border-radius: 4px;
  background: transparent;
  color: var(--theme-foreground-muted);
  cursor: pointer;
}

.chart-download-button:hover:not(:disabled),
.chart-download-button:focus-visible {
  color: var(--theme-foreground);
  border-color: var(--theme-foreground-muted);
}

.chart-download-button.is-busy {
  cursor: progress;
  opacity: 0.5;
}

.chart-download-button.is-error {
  color: #f75b41;
  border-color: #f75b41;
}
