/* =========================================================================
   CHEP site styles
   Tokens below are a direct translation of the project's brand.yml
   (Bootstrap/flexdashboard brand tokens) into CSS custom properties.
   Custom properties cascade into the Shadow DOM used by <site-header>
   and <site-footer>, so those components reference these same variables
   rather than redefining colors/fonts.
   ========================================================================= */

@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;200;300;400;500;600;700;800&display=swap');

:root {
  /* --- brand.yml: color.palette ------------------------------------- */
  --brand-norange: #ec712e;
  --brand-norange-dark: #ca500a;
  --brand-white: #ffffff;
  --brand-grey-1: #e5e3df;
  --brand-grey-2: #d5d0ca;
  --brand-grey-3: #ada39d;
  --brand-grey-4: #7d7470;
  --brand-grey-5: #615753;
  --brand-grey-6: #353435;
  --brand-tan-bg: #f6f4f1;

  /* --- brand.yml: color.primary / color.secondary --------------------- */
  --brand-primary: var(--brand-grey-6);
  --brand-secondary: var(--brand-norange-dark);

  /* --- brand.yml: typography ------------------------------------------ */
  --font-body: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    sans-serif;

  /* --- brand.yml: defaults.bootstrap.defaults -------------------------- */
  --color-body-bg: var(--brand-white);
  --color-link: var(--brand-norange-dark);
  --color-nav-link: var(--brand-grey-5);
  --color-navbar-bg: var(--brand-tan-bg);

  /* Nav hover/active: per direct instruction, grey-3 fill rather than
     the brand.yml orange -- applies to top-level links and dropdown */
  --color-nav-hover-bg: var(--brand-grey-2);
  --color-nav-hover-text: var(--brand-grey-6);

  /* Dropdown matches the navbar's background + hover treatment */
  --color-dropdown-bg: var(--color-navbar-bg);
  --color-dropdown-text: var(--color-nav-link);
  --color-dropdown-hover-bg: var(--color-nav-hover-bg);
  --color-dropdown-hover-text: var(--color-nav-hover-text);

  /* Semantic aliases used throughout the site ------------------------- */
  --color-header-bg: var(--color-navbar-bg);
  --color-header-text: var(--color-nav-link);
  --color-footer-bg: var(--color-navbar-bg);
  --color-footer-text: var(--color-nav-link);
  --color-body-text: var(--brand-grey-6);
  --color-text-muted: var(--brand-grey-4);
  --color-rule: var(--brand-grey-2);

  /* Layout (kept in px so it doesn't scale with font-size-root below) - */
  --nav-height: 96px;
  --content-width: 72rem;
  --gutter: 1.5rem;
}

/* === Reset ============================================================ */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* The `hidden` attribute is only a UA-stylesheet default (`display:
   none`), which authored rules of equal specificity -- e.g. any class
   selector also setting `display` -- silently override. Force it back
   to the highest priority so `el.hidden = true` always works regardless
   of what else targets that element. */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
  /* brand.yml: defaults.bootstrap.defaults.font-size-root */
  font-size: 20px;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--color-body-bg);
  color: var(--color-body-text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
}

site-header,
site-footer {
  display: block;
}

main {
  flex: 1 0 auto;
}

img,
svg {
  max-width: 100%;
  display: block;
}

/* Default paragraph/list-item weight -- anything with its own
   class-level font-weight (e.g. .chartcard-title, .hero__eyebrow)
   still wins, since a class selector outranks these bare element
   selectors. */
p,
li {
  font-weight: 300;
}

/* Explicit weight for strong/b -- the browser default is the
   relative keyword "bolder", which steps up from whatever weight it
   inherits. Stepping up from the 300 above lands around 400, not a
   real bold, so this pins it to an absolute value instead. */
strong,
b {
  font-weight: 700;
}

/* brand.yml: link-color / link-decoration (applies to body content
   links; header/footer set their own link color inside Shadow DOM) */
a {
  color: var(--color-link);
  font-weight: 700;
  text-decoration: none;
}

a:hover,
a:focus-visible {
  color: var(--brand-norange-dark);
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--font-body);
  font-weight: 700;
  margin: 0 0 0.6em;
}

/* brand.yml: defaults.bootstrap.defaults.h1-font-size / h3-font-size */
h1 {
  font-size: 3.5em;
}

h3 {
  font-size: 3.125em;
}

:focus-visible {
  outline: 2px solid var(--brand-norange-dark);
  outline-offset: 2px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* === Page shell ======================================================= */

.wrap {
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* === Placeholder main content (scaffold only) ========================= */

.hero {
  padding-block: 3.5rem 0.5rem;
}

.hero__eyebrow {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand-norange-dark);
  margin: 0 0 0.75rem;
}

.hero__title {
  font-size: 2.25em;
  font-weight: 100;
  line-height: 1.15;
  margin: 0 0 1rem;
  max-width: 25ch;
}

.hero__lede {
  font-size: 1rem;
  font-weight: 300;
  color: #000000;
  margin: 0 0 1rem;
}

.hero__lede:last-of-type {
  margin-bottom: 0;
}

/* === Hero layout: single centered column =============================== */

.hero__column {
  max-width: 42rem;
  margin-inline: auto;
}

.chartcard-house {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem 1.5rem;
  margin-top: 2.5rem;
}

@media (max-width: 40rem) {
  .chartcard-house {
    grid-template-columns: 1fr;
  }
}

.chartcard {
  display: block;
  color: inherit;
  font-weight: normal;
  text-decoration: none;
  padding: 1.25rem 1.5rem;
  background: var(--brand-tan-bg);
  border: 1px solid var(--color-rule);
  border-radius: 2px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.chartcard:hover,
.chartcard:focus-visible {
  border-color: var(--brand-norange-dark);
  box-shadow: 0 4px 14px rgb(53 52 53 / 0.1);
}

.chartcard:hover .chartcard-title,
.chartcard:focus-visible .chartcard-title {
  color: var(--brand-norange-dark);
}

.chartcard__icon {
  display: flex;
  width: 2.5rem;
  height: 2.5rem;
  margin-bottom: 0.75rem;
  color: var(--brand-norange-dark);
}

.chartcard__icon svg {
  width: 100%;
  height: 100%;
}

.chartcard-title {
  margin: 0 0 0.35rem;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--brand-grey-6);
  transition: color 0.15s ease;
}

.chartcard__desc {
  margin: 0;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

/* =========================================================================
   Site header (<site-header>) and footer (<site-footer>)
   Formerly Shadow-DOM-scoped component styles -- moved here as plain
   rules since this is a standalone site with no embedding-page CSS to
   isolate against. See NAV_LINKS in components.js for the link data
   these selectors are styling.
   ========================================================================= */

site-header a,
site-footer a {
  text-decoration: none;
}

site-header button,
site-footer button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

site-header :focus-visible,
site-footer :focus-visible {
  outline: 2px solid var(--brand-norange-dark);
  outline-offset: 2px;
}

/* === Header / masthead ================================================ */

.masthead {
  background: var(--color-header-bg);
  color: var(--color-header-text);
  border-bottom: 1px solid var(--color-rule);
}

.masthead__bar {
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
  height: var(--nav-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.masthead__brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 1;
  min-width: 0;
  color: var(--color-header-text);
}

.masthead__logo {
  height: 64px;
  width: auto;
  flex-shrink: 0;
}

.masthead__divider {
  font-weight: 300;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--brand-grey-4);
  flex-shrink: 0;
}

.masthead__title {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--color-header-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 60rem) {

  .masthead__divider,
  .masthead__title {
    display: none;
  }
}

.masthead__toggle {
  display: none;
  flex-direction: column;
  gap: 4px;
  padding: 0.5rem;
  margin-inline: -0.5rem;
}

.masthead__toggle span {
  width: 22px;
  height: 2px;
  background: var(--color-header-text);
  transition: transform 0.15s ease, opacity 0.15s ease;
}

.masthead__toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}

.masthead__toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}

.masthead__toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

/* === Primary nav ======================================================= */

.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.nav-list>li {
  position: relative;
}

.nav-list a,
.nav-list__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--color-nav-link);
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.55rem 0.85rem;
  border-radius: 2px;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.nav-list a:hover,
.nav-list a:focus-visible,
.nav-list__trigger:hover,
.nav-list__trigger:focus-visible {
  background: var(--color-nav-hover-bg);
  color: var(--color-nav-hover-text);
}

.chevron {
  width: 10px;
  height: 10px;
  transition: transform 0.15s ease;
}

.nav-list__trigger[aria-expanded="true"] .chevron {
  transform: rotate(180deg);
}

/* === Explore Data dropdown ============================================= */

.dropdown-menu {
  list-style: none;
  margin: 0;
  padding: 0.4rem;
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  background: var(--color-dropdown-bg);
  border: 0px solid var(--brand-grey-5);
  border-radius: 2px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.25);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
  /* Above in-page controls (e.g. .drilldown-combo__panel,
     .searchable-select__listbox at z-index: 20) so the desktop hover
     dropdown never renders underneath page content it happens to
     overlap. */
  z-index: 30;
}

.nav-list__dropdown.is-open .dropdown-menu {
  opacity: 1;
  visibility: visible;
}

.dropdown-menu li+li {
  margin-top: 0.1rem;
}

.dropdown-menu a {
  display: block;
  width: 100%;
  padding: 0.55rem 0.85rem;
  color: var(--color-nav-link);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  border-radius: 2px;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.dropdown-menu a:hover,
.dropdown-menu a:focus-visible {
  background: var(--color-dropdown-hover-bg);
  color: var(--color-dropdown-hover-text);
}

/* === Mobile nav ========================================================= */

@media (max-width: 46rem) {
  .masthead__toggle {
    display: flex;
  }

  .masthead__nav {
    display: none;
    position: absolute;
    top: var(--nav-height);
    left: 0;
    right: 0;
    background: var(--color-header-bg);
    border-top: 1px solid var(--brand-grey-5);
    /* Without an explicit z-index this box has z-index: auto, so any
       later-in-DOM element that sets its own positive z-index (e.g. a
       page's .drilldown-combo__panel or .searchable-select__listbox,
       both z-index: 20) stacks above it regardless of paint order --
       that's what let interactive-map.html's inputs show through the
       expanded mobile menu. Set higher than every in-page z-index
       (currently 10-20) so the open menu always wins. */
    z-index: 50;
  }

  .masthead__nav.is-open {
    display: block;
  }

  .nav-list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.5rem var(--gutter) 1rem;
  }

  .nav-list a,
  .nav-list__trigger {
    width: 100%;
    padding: 0.75rem 0.1rem;
    justify-content: space-between;
  }

  .dropdown-menu {
    position: static;
    transform: none;
    display: none;
    box-shadow: none;
    border: none;
    background: transparent;
    border-left: 2px solid var(--brand-grey-5);
    margin-left: 0.5rem;
    padding: 0 0 0 0.75rem;
    min-width: 0;
    opacity: 1;
    visibility: visible;
  }

  .nav-list__dropdown.is-open .dropdown-menu {
    display: block;
  }

  .dropdown-menu a:hover,
  .dropdown-menu a:focus-visible {
    background: var(--color-dropdown-hover-bg);
    color: var(--color-dropdown-hover-text);
  }
}

/* === Footer / colophon ================================================= */

.colophon {
  margin-top: 2rem;
  background: var(--color-footer-bg);
  color: var(--color-footer-text);
  border-top: 1px solid var(--color-rule);
}

.colophon__row {
  max-width: var(--content-width);
  margin-inline: auto;
  min-height: var(--nav-height);
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  font-size: 0.75rem;
}

.colophon__row a {
  color: var(--color-footer-text);
  font-size: 0.65rem;
  font-weight: 600;
  padding: 0.55rem 0.85rem;
  margin: -0.55rem -0.85rem;
  border-radius: 2px;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.colophon__row a:hover,
.colophon__row a:focus-visible {
  background: var(--color-nav-hover-bg);
  color: var(--color-nav-hover-text);
}

.colophon__copyright {
  margin-left: auto;
  color: var(--brand-grey-5);
  font-weight: 600;
}

/* === Voter Type filter (<voter-type-filter>) =========================== */

/* No border/background/padding here by default -- a bordered, tinted
   "card" per control group was the main source of wasted height in
   the controls band above each results area, since that padding+
   border was paid once per group (Voter Type, Map Variable, each
   Filter/Question) rather than once for the whole band.

   The legend sits to the *left* of its fields rather than stacked
   above them, for the same reason: fieldset's children (legend +
   whatever field markup follows it) become flex items here, so the
   label costs horizontal space it already has rather than a whole
   extra line. Below 55rem there usually isn't spare horizontal room
   for that, so it drops back to stacking above. */
.filter-group {
  display: flex;
  flex-wrap: wrap;
  /* flex-start rather than baseline: <variable-drilldown>'s
     drilldown-combo is two stacked rows (crumbs above the input), so
     a text-baseline alignment against the legend has no single clear
     line to match. */
  align-items: flex-start;
  gap: 0.4rem 1rem;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: 0;
  background: none;
}

.filter-group__legend {
  flex: 0 0 auto;
  padding: 0;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-body-text);
  white-space: nowrap;
}

.filter-group__options {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.5rem;
  min-width: 0;
}

@media (max-width: 55rem) {
  .filter-group {
    flex-direction: column;
    align-items: stretch;
  }
}

.filter-group__option {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9rem;
  font-weight: 300;
  color: var(--color-body-text);
  cursor: pointer;
}

/* Without this, a checkbox's checked-state fill defaults to whatever
   accent color the OS/browser theme provides (system blue, theme
   color, etc.) rather than anything from brand.yml -- accent-color is
   the standards-based way to recolor native checkboxes/radios without
   rebuilding them from scratch, and is supported by every current
   evergreen browser. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--brand-secondary);
}

.filter-group__error {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

/* === API error messages =================================================
   Shown in place of a page's results area (crosstab table, compare
   states table, or map) when its plumber API POST fails -- see
   postToApi() in js/page-common.js. */

.results-error {
  margin: 0;
  padding: 1rem;
  border: 1px solid var(--color-rule);
  border-radius: 4px;
  background: var(--brand-tan-bg);
  color: var(--brand-norange-dark);
  font-size: 0.9rem;
}

/* === <searchable-select> ================================================
   Branded replacement for a native <select> (js/components.js) -- the
   native element's open dropdown list can't be restyled with CSS in
   any current browser, so this renders its own listbox instead. */

searchable-select {
  display: block;
}

.searchable-select {
  position: relative;
}

.searchable-select__input {
  width: 100%;
  font: inherit;
  font-size: 0.9rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--color-rule);
  border-radius: 2px;
  background: var(--brand-white);
  color: var(--color-body-text);
}

.searchable-select--disabled .searchable-select__input {
  background: var(--brand-grey-1);
  color: var(--color-text-muted);
  cursor: not-allowed;
}

.searchable-select__listbox {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.2rem);
  left: 0;
  right: 0;
  max-height: 14rem;
  overflow-y: auto;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  border: 1px solid var(--color-rule);
  border-radius: 2px;
  background: var(--brand-white);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

.searchable-select__option {
  padding: 0.4rem 0.5rem;
  border-radius: 2px;
  font-size: 0.9rem;
  color: var(--color-body-text);
  cursor: pointer;
}

.searchable-select__option[aria-selected="true"] {
  background: var(--brand-tan-bg);
  font-weight: 600;
}

/* Keyboard-highlighted and mouse-hovered options share the same
   treatment, and both take precedence over the plain "currently
   selected" tint above so the highlight stays visible even when it
   lands on the selected option. */
.searchable-select__option--active,
.searchable-select__option:hover {
  background: var(--color-nav-hover-bg);
}

.searchable-select__empty {
  padding: 0.4rem 0.5rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

/* === Checkbox-row treatment (<variable-drilldown>'s Response screen,
   <geography-filter>'s Custom States screen) ==============================
   Both render their checklist inside a .drilldown-combo__panel popover
   (which already scrolls as a whole), so there's no separate static
   box like there used to be for Custom States. */

/* .filter-group__option is `display: inline-flex` by default (right
   for Voter Type's checkboxes, meant to wrap side by side) -- each row
   here needs its own block-level `display: flex` instead, so they
   stack top to bottom rather than wrapping inline. */
.drilldown-combo__panel .filter-group__option {
  display: flex;
  width: 100%;
  padding: 0.3rem 0.4rem;
  border-radius: 2px;
  transition: background-color 0.12s ease, font-weight 0.12s ease;
}

.drilldown-combo__panel .filter-group__option:hover {
  background: var(--color-nav-hover-bg);
}

/* Checked rows get a persistent fill + bold label, independent of the
   JS-toggled .is-checked class below, so the state still reads clearly
   even before that class is applied (e.g. in browsers where :has() is
   unsupported). */
.drilldown-combo__panel .filter-group__option:has(input:checked),
.drilldown-combo__panel .filter-group__option.is-checked {
  background: var(--brand-tan-bg);
  font-weight: 600;
}

.drilldown__hint {
  margin: 0;
  padding: 0.3rem 0.1rem;
  font-size: 0.85rem;
  font-weight: 300;
  color: var(--color-text-muted);
}

/* === Variable drilldown combobox (<variable-drilldown>) ================
   One control, three screens: opening it browses topics; picking a
   topic narrows the *same* list to that topic's variables; picking a
   variable (unless `hide-response`) narrows it again to that
   variable's response checklist. A breadcrumb trail above the input,
   plus a back button inside it, move up a level -- a second or third
   field never appears alongside this one. */

.drilldown-combo {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

.drilldown-combo__crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem;
  min-height: 1rem;
  margin-bottom: 0.25rem;
}

.drilldown-combo__crumb {
  font: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-link);
  background: none;
  border: none;
  padding: 0.05rem 0.2rem;
  border-radius: 2px;
  cursor: pointer;
}

.drilldown-combo__crumb:hover {
  background: var(--color-nav-hover-bg);
}

.drilldown-combo__crumb:disabled {
  color: var(--color-body-text);
  cursor: default;
}

.drilldown-combo__crumb:disabled:hover {
  background: none;
}

.drilldown-combo__crumb-sep {
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

.drilldown-combo__trigger {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--color-rule);
  border-radius: 2px;
  background: var(--brand-white);
  overflow: hidden;
}

.drilldown-combo__back {
  display: none;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  flex: none;
  background: none;
  border: none;
  border-right: 1px solid var(--color-rule);
  color: var(--color-text-muted);
  cursor: pointer;
}

.drilldown-combo__back.is-visible {
  display: flex;
}

.drilldown-combo__back:hover {
  background: var(--color-nav-hover-bg);
  color: var(--color-body-text);
}

.drilldown-combo__input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 0.9rem;
  padding: 0.4rem 0.5rem;
  border: none;
  background: none;
  color: var(--color-body-text);
}

.drilldown-combo__input:disabled {
  color: var(--color-text-muted);
  cursor: not-allowed;
}

.drilldown-combo__input[readonly] {
  cursor: pointer;
}

.drilldown-combo__caret {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  flex: none;
  background: none;
  border: none;
  color: var(--color-text-muted);
  cursor: pointer;
}

.drilldown-combo__caret:hover {
  color: var(--color-body-text);
}

.drilldown-combo__caret svg {
  transition: transform 0.12s ease;
}

.drilldown-combo.is-open .drilldown-combo__caret svg {
  transform: rotate(180deg);
}

/* Visibility is driven entirely by the `hidden` attribute (see
   _openPanel()/_closePanel() in components.js), which the global
   `[hidden] { display: none !important; }` reset guarantees always
   wins -- no separate .is-open-based display rule needed here. */
.drilldown-combo__panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.2rem);
  left: 0;
  right: 0;
  max-height: 16rem;
  overflow-y: auto;
  padding: 0.25rem;
  border: 1px solid var(--color-rule);
  border-radius: 2px;
  background: var(--brand-white);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

/* Topic/Variable option rows reuse <searchable-select>'s option
   styling (same look: padded row, hover/active highlight), plus a
   trailing muted "meta" note (a variable count on Topic rows) that
   plain <searchable-select> options don't have. Scoped to this panel
   so it doesn't change <searchable-select>'s own (meta-less) options
   elsewhere, e.g. <geography-filter>'s mode field. */
.drilldown-combo__panel .searchable-select__option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.drilldown-combo__option-meta {
  flex: none;
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

.drilldown-combo__response-question {
  margin: 0.2rem 0.3rem 0.5rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-body-text);
}

.drilldown-combo__select-all {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.4rem;
  margin-bottom: 0.2rem;
  border-bottom: 1px dashed var(--color-rule);
}

.drilldown-combo__select-all label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-muted);
  cursor: pointer;
}

.drilldown-combo__footer {
  display: flex;
  justify-content: flex-end;
  padding: 0.5rem 0.2rem 0.1rem;
  margin-top: 0.2rem;
  border-top: 1px solid var(--color-rule);
}

@media (max-width: 40rem) {
  .colophon__copyright {
    margin-left: 0;
  }
}

/* === Buttons ============================================================ */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.65rem 1.25rem;
  border: 1px solid transparent;
  border-radius: 2px;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease,
    color 0.12s ease;
}

.button--primary {
  background: var(--brand-secondary);
  color: var(--brand-white);
}

.button--primary:hover,
.button--primary:focus-visible {
  background: var(--brand-norange-dark);
}

.button--secondary {
  background: transparent;
  color: var(--brand-secondary);
  border-color: var(--brand-secondary);
}

.button--secondary:hover,
.button--secondary:focus-visible {
  background: var(--brand-tan-bg);
}

.button:disabled {
  background: var(--brand-grey-1);
  border-color: var(--brand-grey-1);
  color: var(--color-text-muted);
  cursor: not-allowed;
}

.button-row {
  display: flex;
  /* nowrap so the group never breaks up one button at a time -- below
     the breakpoint the media query stacks all of them together. */
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.75rem;
}

/* Equal width regardless of label length ("Generate Map" vs "Reset"):
   flex-basis: 0 means the labels don't seed the sizing, so the row is
   divided evenly. A *descendant* selector (not >) on purpose --
   <export-buttons> is `display: contents` (below), which makes its
   buttons render as if they were direct flex children, but the `>`
   child combinator still only matches actual DOM children, so it
   never matched Download Excel/PNG and they didn't grow like the
   other two. white-space: nowrap plus the default (not zeroed) auto
   min-width keeps a button from being squeezed narrower than its own
   label needs, so "Generate Map" can't wrap mid-word either. */
.button-row .button {
  /* flex: 1 1 0 alone only equalizes widths if .button-row itself has
     *extra* space to hand out -- but .button-row has no flex-grow of
     its own, so it's sized to fit its content (~the sum of these
     three buttons' own natural widths), leaving nothing extra to
     distribute. That's why they kept coming out unequal, matched to
     each label. An explicit shared min-width sidesteps that: all three
     are pinned to the same floor regardless of label/icon, and still
     grow together if the row ever does have slack. */
  flex: 1 1 0;
  min-width: 9rem;
  white-space: nowrap;
}

/* All-or-nothing wrap: below the same breakpoint the rest of the page
   stacks at, every button in the group goes full width rather than one
   of them dropping to a second line on its own. */
@media (max-width: 55rem) {
  .button-row {
    flex-wrap: wrap;
  }

  .button-row .button {
    flex: 1 1 100%;
  }
}

/* <export-buttons> renders its own <button>s straight into the light
   DOM with no wrapper element of its own; `display: contents` keeps
   the custom element itself out of the layout so those buttons become
   direct flex items of whatever `.button-row` contains it, sitting
   flush with the page's own "Generate..." button. */
export-buttons {
  display: contents;
}

share-buttons {
  display: block;
}

/* === Share row =========================================================== */

.share-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}

.share-row__label {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
}

.share-row__icons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.share-row__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border: none;
  border-radius: 50%;
  background: var(--brand-grey-6);
  color: var(--brand-white);
  text-decoration: none;
  font: inherit;
  padding: 0;
  cursor: pointer;
  transition: background-color 0.12s ease;
}

.share-row__icon svg {
  width: 0.95rem;
  height: 0.95rem;
  fill: currentColor;
}

.share-row__icon:hover,
.share-row__icon:focus-visible {
  background: var(--brand-norange-dark);
  color: var(--brand-white);
}

/* === Share-disabled dialog =============================================
   Shown in place of every share icon's real link while the dashboard is
   VPN-only (see <share-buttons> in js/components.js). Styled as a plain
   <dialog> -- its ::backdrop gets the dimming treatment for free, so no
   extra overlay element is needed. */

.share-disabled-dialog {
  max-width: 22rem;
  margin: auto;
  padding: 1.5rem;
  border: none;
  border-radius: 4px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  color: var(--color-body-text);
  font-family: var(--font-body);
}

.share-disabled-dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.share-disabled-dialog__title {
  margin: 0 0 0.75rem;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--brand-primary);
}

.share-disabled-dialog__body {
  margin: 0 0 1.25rem;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--color-body-text);
}

.share-disabled-dialog__close {
  width: 100%;
}

/* === Info dialog button ==================================================
   Reusable "trigger button + <dialog>" pair (see <info-dialog-button> in
   js/components.js) used for homepage features that don't exist yet, like
   the video tutorial and updates sign-up form. Styled like the
   share-disabled dialog above so any future "coming soon" dialog looks
   consistent site-wide. */

.info-dialog-button {
  display: inline-flex;
}

.info-dialog-button__trigger {
  white-space: nowrap;
}

.info-dialog-button__icon {
  display: inline-flex;
  width: 0.95rem;
  height: 0.95rem;
}

.info-dialog-button__icon svg {
  width: 100%;
  height: 100%;
  fill: currentColor;
}

.button--link {
  background: transparent;
  border-color: transparent;
  color: var(--color-link);
  padding: 0.25rem 0;
  margin-bottom: 1em;
}

.button--link:hover,
.button--link:focus-visible {
  background: transparent;
  text-decoration: underline;
}

.info-dialog {
  max-width: 24rem;
  margin: auto;
  padding: 1.5rem;
  border: none;
  border-radius: 4px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  color: var(--color-body-text);
  font-family: var(--font-body);
}

.info-dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.info-dialog__title {
  margin: 0 0 0.75rem;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--brand-primary);
}

.info-dialog__body p {
  margin: 0 0 0.75rem;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--color-body-text);
}

.info-dialog__body p:last-child {
  margin-bottom: 1.25rem;
}

.info-dialog__close {
  width: 100%;
}

/* === Homepage "stay updated" banner ===================================== */

.updates-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  margin-top: 2.5rem;
}

.updates-banner__title {
  margin: 0 0 0.25rem;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--brand-grey-6);
}

.updates-banner__desc {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

.updates-banner__cta {
  flex-shrink: 0;
}

/* === Homepage "ai policy"  ===================================== */

.ai-policy {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  margin-top: 2.5rem;
}

.ai-policy__title {
  margin: 0 0 0.25rem;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--brand-grey-6);
}

.ai-policy__desc {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

/* === Homepage "partner logos"  ===================================== */

.partner-logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  margin-top: 2.5rem;
}

.partner-logos__title {
  margin: 0 0 0.25rem;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--brand-grey-6);
}

.partner-logos__desc {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-text-muted);
}


/* === About page callout ==================================================
   Pull-quote style treatment for a single key sentence on the About page
   (see about.html). A left accent bar + tan fill + oversized decorative
   quote mark set it apart from the surrounding body copy. */

.about-callout {
  position: relative;
  margin: 2.5rem 0;
  padding: 1.75rem 2.25rem 1.75rem 4rem;
  background: var(--brand-tan-bg);
  border-left: 4px solid var(--brand-norange-dark);
  border-radius: 4px;
}

.about-callout__mark {
  position: absolute;
  top: 1.5rem;
  left: 1.25rem;
  width: 1.75rem;
  height: 1.75rem;
  fill: var(--brand-norange-dark);
  opacity: 0.85;
  display: none
}

.about-callout p {
  margin: 0;
  font-size: 1.1em;
  font-weight: 500;
  line-height: 1.35;
  color: var(--brand-grey-5);
}

/* === Collapsible optional slots =========================================
   Shared by any page with a chain of optional modules revealed one at a
   time behind "+ Add..." (Filter 1/2 on interactive-map.html, Question
   2/3 on crosstabs.html, and so on); each slot removes itself via its
   own "×" (.filter-remove-btn, below).
   ========================================================================= */

.add-filter-link {
  align-self: flex-start;
  text-align: left;
  background: none;
  border: none;
  padding: 0.05rem 0;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-link);
  cursor: pointer;
}

.add-filter-link:hover,
.add-filter-link:focus-visible {
  color: var(--brand-norange-dark);
  text-decoration: underline;
}

/* Puts the "×" (below) inline beside the drilldown rather than
   floating over a corner of it. */
.optional-field {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  /* The drilldown's trigger is its last element, so this box's bottom
     edge *is* the trigger's bottom edge no matter how many lines the
     breadcrumb above it wraps to -- which is what lets the "×" align
     itself off the bottom rather than off a fixed top offset. */
}

.optional-field>variable-drilldown {
  flex: 1 1 auto;
  min-width: 0;
}

/* === Compact remove-filter control (interactive-map.html) ==============
   "+ Add a Filter" (.add-filter-link, shared with crosstabs.html's
   "+ Add question") reveals the next filter; each open filter gets its
   own "×" badge instead of a single shared "− Remove..." link, so
   removing a specific filter doesn't require it to be the last one
   open (though removing one still collapses any filter after it too
   -- see createSlotChain() in page-common.js). */

.filter-remove-btn {
  flex: none;
  /* Bottom-aligned to the field, then lifted by half the difference
     between the trigger's height (2.35rem) and this button's (1.3rem)
     so it centers on the trigger. Anchoring off the bottom rather than
     a fixed top offset keeps it centered when the breadcrumb wraps to
     a second line and pushes the trigger down. */
  align-self: flex-end;
  margin-bottom: 0.525rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.3rem;
  height: 1.3rem;
  padding: 0;
  border: 1px solid var(--color-rule);
  border-radius: 50%;
  background: var(--brand-white);
  color: var(--color-text-muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.filter-remove-btn:hover,
.filter-remove-btn:focus-visible {
  background: var(--brand-norange-dark);
  border-color: var(--brand-norange-dark);
  color: var(--brand-white);
}

/* === State picker (<geography-filter>'s "Custom States" screen) ======== */

.state-picker__clear-all {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid var(--color-rule);
  margin-bottom: 0.2rem;
  padding: 0.3rem 0.4rem 0.5rem;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-link);
  cursor: pointer;
}

.state-picker__clear-all:hover,
.state-picker__clear-all:focus-visible {
  color: var(--brand-norange-dark);
}

/* === Controls-above-results layout ======================================
   Shared by every Explore Data page: a horizontal band of input controls
   above a results area (map, crosstab table, or state-comparison table).

   Wide control groups (a variable drilldown, Geography) each get their
   own full-width row in .controls-layout__inputs, so newly added
   controls (Filter/Question 2, 3, ...) always appear *below* what's
   already there rather than squeezing in alongside it. Rows lay their
   own fields out horizontally (see .filter-group__options and
   .drilldown), so a group is one or two field-heights tall rather than
   a stack of full-width blocks.

   A control that's narrow by nature (Voter Type: two checkboxes) can
   instead live in .controls-layout__actions, sharing a row with the
   Generate/Export/Reset/Share controls instead of leaving an otherwise-
   empty row of its own.
   ========================================================================= */

.controls-layout {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  margin-top: 0.5rem;
  padding-bottom: 3.5rem;
}

/* One control group per row by default. Custom elements
   (<voter-type-filter>, <geography-filter>, <variable-drilldown>) have
   no display rule of their own, but as flex items they're blockified
   and so fill the row width, which is what the horizontal field
   layouts inside them need. */
.controls-layout__inputs {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

/* A .controls-layout__row groups a few narrow/related controls onto
   one shared row instead of each getting its own -- e.g. Voter Type
   beside Map Variable, or Filter 1/2 beside "+ Add a Filter", on
   interactive-map.html. */
.controls-layout__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 2rem 3rem;
}

/* Voter Type is sized to its own (short) content and never grows,
   whatever it's paired with (Map Variable on interactive-map.html,
   Geography on crosstabs.html); the other field takes whatever width
   that leaves. */
.controls-layout__row>voter-type-filter {
  flex: 0 0 auto;
}

.controls-layout__row>variable-drilldown,
.controls-layout__row>geography-filter {
  flex: 1 1 auto;
  min-width: 0;
}

/* Voter Type's caption stacks above its checkboxes -- rather than
   sitting inline to their left, like every other .filter-group
   legend -- and is styled like a breadcrumb (see
   .drilldown-combo__crumb) rather than the usual small-caps legend,
   so it reads as a companion to its sibling's own breadcrumb trail.
   Both <variable-drilldown> and <geography-filter> render a
   .drilldown-combo now, so this applies to either pairing (Map
   Variable on interactive-map.html, Geography on crosstabs.html). */
.controls-layout__row:has(>variable-drilldown, >geography-filter)>voter-type-filter {
  /* Stretched to the row's full height (and the fieldset with it) so
     the checkboxes below can bottom-anchor within it: the legend
     stays put at the top, level with "All topics", while the
     checkboxes follow the trigger down if the breadcrumb wraps to
     another line. */
  align-self: stretch;
}

.controls-layout__row:has(>variable-drilldown, >geography-filter)>voter-type-filter .filter-group {
  flex-direction: column;
  height: 100%;
}

.controls-layout__row:has(>variable-drilldown, >geography-filter)>voter-type-filter .filter-group__legend {
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-body-text);
  /* Matches .drilldown-combo__crumbs' margin-bottom, so this header
     line is the same height and top-aligns with "All topics" (row
     stays align-items: flex-start). Centering the checkboxes against
     the dropdown is then handled below by giving them a min-height
     matching the trigger box's own rendered height, rather than by
     centering the whole row (which pushed this legend down instead). */
  margin-bottom: 0.25rem;
}

.controls-layout__row:has(>variable-drilldown, >geography-filter)>voter-type-filter .filter-group__options {
  /* Pushed to the bottom of the stretched fieldset, then given a box
     approximating .drilldown-combo__trigger's height (0.9rem input
     line-height + 0.4rem top/bottom padding + 1px border). Since
     .filter-group__options already centers its contents, the
     checkboxes land on the trigger's middle -- and because the anchor
     is the row's bottom rather than a fixed top offset, they stay
     there when a wrapped breadcrumb pushes the trigger down. */
  margin-top: auto;
  min-height: 2.35rem;
}

/* Below this, there's no longer room for Voter Type's static width
   beside the other field's minimum usable width, so they stack
   instead -- same breakpoint .drilldown/.controls-layout__row--filters
   use elsewhere. */
@media (max-width: 55rem) {

  .controls-layout__row>voter-type-filter,
  .controls-layout__row>variable-drilldown,
  .controls-layout__row>geography-filter {
    flex: 1 1 100%;
  }
}

/* The filters row itself grows to fill whatever width the "Filters"
   legend beside it leaves (see .filter-group > .controls-layout__row
   below), and each open filter takes half of *that* -- accounting for
   the row's own gap so two 50% filters plus the gap between them
   don't overflow it. */
/* .controls-layout__row sets flex-wrap: wrap, which would otherwise
   let this row wrap on its own (e.g. if "+ Add a Filter" needs more
   room than two 50% filters leave) independently of whether Map
   Variable/Voter Type have stacked -- overridden to nowrap so the two
   filters only ever stack in the same media query below, keeping both
   rows' breakpoints in sync rather than two separately-triggered ones. */
.controls-layout__row--filters {
  flex-wrap: nowrap;
  /* The doubled gap on .controls-layout__row above was sized for Map
     Variable/Voter Type, not this tighter row -- at this size it read
     as too much space above "+ Add a Filter" once it wraps onto its
     own line on narrow viewports. */
  gap: 1rem 1.5rem;
}

.controls-layout__row--filters>.optional-field {
  /* No grow: pinned at 50% (not "50%, or more if the row has slack
     left over from a short '+ Add a Filter' link beside it"). Shrinks
     only as a fallback before the mobile breakpoint below takes over. */
  flex: 0 1 calc(50% - 0.75rem);
  min-width: 0;
  /* Both filters take the row's full height even when one has a
     taller (wrapped) breadcrumb, so the rules below can pin each
     trigger to the bottom and line the two dropdowns up with each
     other -- otherwise each box is only as tall as its own content and
     the shorter filter's dropdown sits a line high. */
  align-self: stretch;
}

/* Full-height chain from the stretched .optional-field down to the
   combo, so .drilldown-combo__trigger's margin-top: auto below has
   slack to push against. */
.controls-layout__row--filters .optional-field {
  align-items: stretch;
}

.controls-layout__row--filters .optional-field>variable-drilldown,
.controls-layout__row--filters .optional-field .filter-group,
.controls-layout__row--filters .drilldown-combo {
  height: 100%;
}

/* Breadcrumbs stay at the top of the box (so they line up across both
   filters) while the trigger is pushed to the bottom (so *those* line
   up too, whatever the breadcrumb above each one wrapped to). The
   floating panel is absolutely positioned, so it's unaffected. */
.controls-layout__row--filters .drilldown-combo {
  display: flex;
  flex-direction: column;
}

.controls-layout__row--filters .drilldown-combo__trigger {
  margin-top: auto;
}

/* When "+ Add a Filter" sits inline beside Filter 1 (and so has no
   breadcrumb line of its own to offset past), bottom-align it to the
   row and give it a box exactly the trigger's height with its text
   centered inside -- landing it on the trigger's middle however many
   lines Filter 1's breadcrumb wraps to. Scoped with :has() to only
   when a filter is actually open, since otherwise this also padded the
   link when it sits alone in the row (0 filters). */
.controls-layout__row--filters:has(.optional-field:not([hidden]))>.add-filter-link {
  align-self: flex-end;
  min-height: 2.35rem;
  display: flex;
  align-items: center;
}

@media (max-width: 55rem) {
  .controls-layout__row--filters {
    flex-wrap: wrap;
  }

  .controls-layout__row--filters>.optional-field {
    flex: 1 1 100%;
  }

  /* Stacked, "+ Add a Filter" is no longer beside a dropdown to align
     with -- back to the same spacing above it as when no filters are
     open at all. Matches the base rule's :has() so this actually wins
     over it (same specificity, later in source) rather than losing to
     a more-specific selector despite being in a narrower media query. */
  .controls-layout__row--filters:has(.optional-field:not([hidden]))>.add-filter-link {
    align-self: flex-start;
    min-height: 0;
  }
}

.filter-group>.controls-layout__row {
  flex: 1 1 auto;
  min-width: 0;
}

.controls-layout__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
}

.controls-layout__results {
  margin-top: 3rem;
  min-height: 28rem;
  min-width: 0;
}

/* These min-heights assume a roughly-square map/table area; on mobile
   the map itself renders shorter (narrower width -> shorter height,
   following the SVG's aspect ratio), so the same floor just pads the
   box with empty space above/below the now-smaller map. */
@media (max-width: 55rem) {
  .controls-layout__results {
    min-height: 0;
  }
}

/* === Interactive map panel (interactive-map.html) =======================
   Deliberately no card/box styling here -- the map sits flat directly
   on the page.
   ========================================================================= */

.map-panel {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  height: 100%;
  min-height: 28rem;
}

@media (max-width: 55rem) {
  .map-panel {
    height: auto;
    min-height: 0;
  }
}

.map-canvas {
  position: relative;
  flex: 1;
  min-height: 22rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  padding: 0.5rem 0;
  color: var(--color-text-muted);
  font-size: 0.9rem;
  text-align: center;
}

@media (max-width: 55rem) {
  .map-canvas {
    flex: none;
    min-height: 0;
  }
}

/* The off-screen copy the PNG export renders into (see
   withExportSizedMap() in js/render-map.js). It needs to be laid out
   for real -- display: none or visibility: hidden would give it no
   geometry and nothing to rasterize -- so it's parked far to the left
   instead. Its width is set inline; the overrides here undo the flex
   sizing that only makes sense inside .map-panel. */
.map-canvas--export {
  position: absolute;
  top: 0;
  left: -20000px;
  flex: none;
  min-height: 0;
  background: var(--brand-white);
  pointer-events: none;
  /* The capture is cropped to this element's scroll height, which ends
     flush with the last thing in it -- leaving the legend's labels
     with nothing to spare at the bottom edge. Slack here rather than
     on .map-canvas so it only affects the exported copy, not the
     spacing on screen. */
  padding-bottom: 2rem;
}

/* Shared "title + subtitle" header (js/render-results-header.js),
   used above both the Interactive Map's choropleth and the Compare
   States table. */
.results-header {
  margin-bottom: 0.75rem;
  text-align: left;
}

/* Carries the gap between the title and the map, which used to be a
   margin-top on .map-svg -- see why that had to go in .map-svg's rule. */
.map-canvas .results-header {
  margin-bottom: 1.5rem;
}

.results-title {
  margin: 0 0 0.35rem;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--color-body-text);
}

.results-subtitle {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.results-subtitle p {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 300;
  color: var(--color-text-muted);
}

.results-subtitle em {
  font-style: italic;
  font-weight: 600;
}

/* Deliberately no `width` and no horizontal centering here. This
   element spans .map-canvas edge to edge; js/render-map.js sets its
   width/height attributes in pixels and draws the desktop frame as
   empty space inside the viewBox. Reintroducing a percentage width or
   an auto margin would put a horizontal offset back between the <svg>
   and its container, which is what the PNG export mis-renders (see
   drawStates()). `max-width`/`height: auto` are kept only so the map
   degrades gracefully if the window is narrowed after it's drawn,
   rather than overflowing until the next render. */
.map-svg {
  display: block;
  max-width: 100%;
  height: auto;
  /* No margins here, in any direction. The PNG export displaces this
     element by its own margin and then clips it at its box, so a
     margin-top shaved that many pixels off the bottom of the map --
     the same failure the horizontal centering margin caused off the
     right edge, and just as immune to adjusting the spacing around it.
     The gap above the map is .results-header's margin-bottom instead
     (see below), which is an ordinary block element and unaffected. */
  /* .map-canvas is a flex column inside a .map-panel with a fixed
     height, so without this the map is a shrinkable flex item: when
     the header + map + legend don't fit, flex compresses this box and
     the viewBox letterboxes inside it. On screen that just looks
     slightly small, but the PNG export draws the <svg> at its
     intrinsic height regardless, so the map spilled over the legend.
     Holding the box at its intrinsic height keeps the two in
     agreement; if anything has to give, the panel overflows and the
     legend moves down instead. */
  flex-shrink: 0;
}

.map-state {
  /* A grey (rather than white) border stays visible regardless of
     fill color -- important since low values fill nearly white, which
     a white border would disappear into right where two low-value
     states are adjacent. Now that the map sits directly on the page's
     white background (no boxed panel), --brand-grey-4 reads clearly
     against it without going as dark/harsh as --brand-grey-6. */
  stroke: var(--brand-grey-5);
  stroke-width: 0.75;
  transition: stroke 0.12s ease, stroke-width 0.12s ease;
}

.map-state:hover {
  stroke-width: 1.5;
}

/* Shared floating tooltip, JS-positioned via mousemove -- used by the
   Interactive Map (js/render-map.js) and the Crosstabs suppressed-cell
   icon (js/render-crosstab.js). Whatever element it's appended to must
   be `position: relative` so its `left`/`top` are relative to that
   container. */
.floating-tooltip {
  position: absolute;
  z-index: 10;
  max-width: 14rem;
  padding: 0.5rem 0.65rem;
  border-radius: 2px;
  background: var(--brand-grey-6);
  color: var(--brand-white);
  font-size: 0.75rem;
  line-height: 1.4;
  pointer-events: none;
  transition: opacity 0.1s ease;
}

/* Holds the legend's spacing and centering so the <svg> itself can
   stay margin-free -- see the note in renderLegend(). .map-canvas sets
   no `gap`, so without the margin here the legend butts straight up
   against the bottom of the map. */
.map-legend-wrap {
  align-self: center;
  margin-top: 1.5rem;
  /* Same reasoning as .map-svg's: letting flex compress a box with an
     explicit height puts it out of step with the export. */
  flex-shrink: 0;
  /* The <svg> is display: block below, but this keeps any stray inline
     leading out of the wrapper's height either way. */
  line-height: 0;
}

.map-legend {
  display: block;
}

.map-legend text {
  font-size: 0.65rem;
  fill: var(--color-text-muted);
}

/* === Crosstab results table (crosstabs.html) ============================ */

.crosstab-panel {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  height: 100%;
  min-height: 28rem;
}

.crosstab-table {
  width: 100%;
  /* Fixed layout + an explicit <colgroup> (see buildColgroup() in
     js/render-crosstab.js) rather than relying on auto layout's
     content-based sizing: a long variable label (e.g. a full survey
     question) in one header would otherwise claim most of the row's
     width and squeeze every other column toward its bare minimum.
     With `fixed`, the Sample Size/Percent columns get their own <col>
     width and every remaining column (each Question) splits what's
     left evenly, independent of how long its header text is -- it
     just wraps onto multiple lines instead. */
  table-layout: fixed;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.crosstab-table th,
.crosstab-table td {
  padding: 0.5rem 0.75rem;
  text-align: left;
  /* Plain-text cells default to baseline alignment, but the percent
     cell is a flex container with its own effective baseline -- pin
     every cell to `middle` so a row's text and bar+label line up. */
  vertical-align: middle;
  border-bottom: 1px solid var(--color-rule);
}

.crosstab-table thead th {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  border-bottom: 2px solid var(--brand-grey-4);
}

/* Click-to-sort headers (js/sortable-table.js) */
.crosstab-table__sortable-header {
  cursor: pointer;
  user-select: none;
}

.crosstab-table__sortable-header:hover {
  color: var(--brand-grey-6);
}

.crosstab-table__sortable-header[aria-sort="ascending"],
.crosstab-table__sortable-header[aria-sort="descending"] {
  color: var(--brand-norange-dark);
}

.crosstab-table__sort-indicator {
  display: inline-block;
  margin-left: 0.35rem;
  font-size: 0.65rem;
}

.crosstab-table__row--group-start td {
  border-top: 2px solid var(--brand-grey-4);
}

/* Widths now live on the <col> elements (see buildColgroup() in
   js/render-crosstab.js) so they take effect under `table-layout:
   fixed` -- a width on the <th> itself would be ignored there once a
   <colgroup> is present. */
.crosstab-table__sample-col {
  width: 6.5rem;
}

.crosstab-table__percent-col {
  width: 12rem;
}

/* Compare States only: rather than squeezing every response-option
   column to fit the viewport (some variables have a lot of response
   options), each data column gets a sensible minimum width and the
   table is allowed to grow past its container -- .table-scroll (on
   the wrapping div in compare-states.html) is what turns that overflow
   into a horizontal scrollbar instead of the browser trying to shrink
   columns below their content.

   `table-layout: fixed` was tried here to force every non-State
   column to an identical width, but that also clips/wraps long column
   headers that don't fit the fixed width -- reverted back to
   `min-width` (auto layout), which still keeps columns reasonably
   even in the common case without ever cutting off a header. */
.compare-states-table {
  width: 100%;
}

/* National row (js/render-compare-states.js): a weighted-average
   aggregate across every state. It's a normal row in the sortable
   list -- defaulting to the top, but free to move up/down among the
   states once a column is sorted -- so borders go on both edges
   (rather than just below, back when it was always first) to keep it
   visually set off from its neighbors wherever it lands. */
.compare-states-table__national-row {
  font-weight: 700;
  background: var(--brand-grey-1);
  border-top: 2px solid var(--brand-grey-4);
  border-bottom: 2px solid var(--brand-grey-4);
}

.compare-states-table th:first-child,
.compare-states-table td:first-child {
  width: 9rem;
}

.compare-states-table th:not(:first-child),
.compare-states-table td:not(:first-child) {
  min-width: 7rem;
}

.table-scroll {
  overflow-x: auto;
  /* Flex items have the same default `min-width: auto` issue as grid
     items (see .controls-layout__results above) -- without this, this
     element would grow to fit the table instead of scrolling it. */
  min-width: 0;
}

/* Deliberately NOT display:flex on the <td> itself -- a flex-
   formatted table cell doesn't reliably grow to match a taller
   sibling cell (e.g. one with wrapped multi-line text) the way a
   plain cell does. The flex layout instead lives on
   .crosstab-table__percent-inner, a normal block-level child that
   vertical-align: middle (on the <td>) can center within whatever
   height the row ends up being. */
.crosstab-table__percent-cell {
  white-space: nowrap;
}

.crosstab-table__percent-inner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.crosstab-bar {
  flex: 1;
  max-width: 8rem;
  height: 0.6rem;
  border-radius: 1px;
  background: var(--brand-grey-1);
  overflow: hidden;
}

.crosstab-bar__fill {
  height: 100%;
  background: var(--brand-norange-dark);
}

.crosstab-bar__label {
  font-variant-numeric: tabular-nums;
  color: var(--color-body-text);
}

.crosstab-table__percent-cell--na {
  color: var(--color-text-muted);
  font-weight: 300;
}

/* Suppressed-row info icon (js/render-crosstab.js): hover to see the
   row-group's own size via .floating-tooltip -- safe to disclose since
   it says nothing about the still-hidden per-response-level counts
   within that group. */
.crosstab-suppressed-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  margin-left: 0.4rem;
  border-radius: 50%;
  background: var(--brand-grey-6);
  color: var(--brand-white);
  font-size: 0.6rem;
  font-weight: 700;
  vertical-align: middle;
  cursor: default;
}

/* The <div id="crosstab-table"> that js/render-crosstab.js renders
   into needs to be the positioning context for .floating-tooltip. */
.results-table-container {
  position: relative;
}

/* "Downloading PNG…"/"Downloading Excel…" pill toast (js/export-
   helpers.js's showDownloadPill()), shown while a PNG is being
   rasterized or an .xlsx workbook built -- both can visibly take a
   few seconds with no other feedback otherwise. Fixed to the bottom
   center of the viewport, fading/sliding in and out via the
   `--visible` modifier. */
.download-pill {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  z-index: 1000;
  padding: 0.6rem 1.25rem;
  border-radius: 999px;
  background: var(--brand-primary);
  color: var(--brand-white);
  font-size: 0.85rem;
  font-weight: 600;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
  opacity: 0;
  transform: translate(-50%, 0.5rem);
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}

.download-pill--visible {
  opacity: 1;
  transform: translate(-50%, 0);
}