@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;
}

/* 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;
}

.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;
  width: 500px;
  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;
  /* Clear of the container's top edge — the chip sits 6px inside the 44px
     target, so a negative top clipped its stroke. */
  top: 0.25rem;
  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. */
.theme-toggle.is-solo {
  right: 0;
}

/* 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: calc(clamp(40px, 6vw, 100px) * 1.2 - 0.5rem);
    right: 0;
  }
}

#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;
}

/* 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;
  font-size: 90px;
  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;
}

@media (min-width: 640px) {
  .hero h1 {
    font-size: 130px;
  }
}

/* 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;
}

.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;
}

.stats-band-compare {
  font-size: 11px;
  color: var(--theme-foreground-muted);
  font-style: italic;
  margin-top: 2px;
  font-family: "Roboto", sans-serif;
}

/* 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, and the longest anywhere is deportations' "↑ 1,539%",
     which wants 129px at 32px while its half of the card is 111px at a 1280px
     laptop. So the ratio is set from that worst case rather than from the count:
     the figures reach their full 32px wherever the card can hold them — every
     two-across row, and every card on a phone — and step down together in the
     three-across deportations row, which cannot fit two 32px figures until the
     container reaches 960px.

     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 "Citizenship Subregion" at 125px,
   so there is about 25px 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;
  }
}

.about h1 {
  max-width: none;
  font-size: 30px;
  font-weight: 900;
  letter-spacing: -0.02em;
  font-family: "Big Shoulders Display", sans-serif;
}

.about p {
  max-width: 700px;
  font-size: 18px;
  font-family: var(--sans-serif);
  color: rgb(95, 95, 95);
}

.source-p {
  margin-bottom: 2rem;
}

.line-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 0.5rem 0 0.75rem;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.line-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  color: var(--theme-foreground-muted);
  user-select: none;
}

.line-legend-item:hover {
  color: var(--theme-foreground);
}

.line-legend-item.active {
  color: var(--theme-foreground);
  text-decoration: underline;
}

.line-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}

/* 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;
}

.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;
}
