/* ---------------------------------------------------------------------------
   orenbj.com — shared brand layer
   ---------------------------------------------------------------------------
   The single source of truth for the site's visual identity. Project pages keep
   their own interior design; this only supplies the chrome that makes them read
   as one site.

   Applied at build time, not by a runtime script: scripts/build.mjs stamps the
   header markup and a link to this stylesheet into every page's <head>/<body>,
   driven by each page's `mode` in site.config.mjs (doc | app | none — "app"
   shifts a fixed, full-bleed #map down by the bar height instead of padding
   the body, which does nothing for a fixed element). A page dropped into
   src/pages/ with no config entry fails the build rather than shipping
   unstamped — see check.mjs.

   Re-run `npm run build` after replacing a page's content — the stamping is
   how the chrome gets there, so there is nothing to re-apply by hand.
   --------------------------------------------------------------------------- */

:root {
  /* Dark unless the page is light from top to bottom (the light set below).
     Declared so native form controls and scrollbars match the page rather
     than the visitor's OS setting. */
  color-scheme: dark;

  /* Identity — matches the home page and 404. */
  --ob-bg:        #030712;
  --ob-fg:        #f9fafb;
  --ob-muted:     #9ca3af;
  --ob-border:    #1f2937;
  --ob-orange:    #fb923c;
  --ob-purple:    #c084fc;
  --ob-on-accent: #1a0b2e;   /* text on a purple fill; a page that deepens the purple sets it light */
  --ob-blue:      #60a5fa;
  --ob-gradient:  linear-gradient(135deg, #fb923c 0%, #c084fc 50%, #60a5fa 100%);

  --ob-bar-h:     44px;
  --ob-font:      system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* --- tokens for project pages ------------------------------------------
     The bar above only needs the values already listed. These exist so a page
     rebuilt from its own source can adopt the site's look rather than
     approximate it: build against these names and it matches by construction.

     Metric Equinox is deliberately exempt — its own visual world is the point
     of the project, and the shared bar is enough to place it on this site.
     -------------------------------------------------------------------- */
  --ob-surface:      rgba(17, 24, 39, 0.65);   /* card / panel over --ob-bg   */
  --ob-surface-flat: #0c1220;                  /* same, composited & opaque   */
  --ob-radius:       16px;                     /* cards                       */
  --ob-radius-sm:    8px;                      /* controls                    */
  --ob-shadow:       0 4px 6px -1px rgba(0, 0, 0, 0.4);
  --ob-shadow-hover: 0 12px 20px -3px rgba(0, 0, 0, 0.6);
  --ob-particle-alpha: 0.24;   /* opacity of the drifting background field */

  /* Filter and legend panels. One set for every web app, so a map's floating
     cards, a dashboard's sidebar and a legend read as the same kit. They live
     here rather than in ui.css because the two maps never load ui.css:
     the bus lanes map's own styles and the census map's override sheet point their
     markup at these. */
  --ob-panel:         rgba(17, 24, 39, 0.72);    /* floating over a map      */
  --ob-panel-border:  rgba(148, 163, 184, 0.22);
  --ob-panel-shadow:  0 8px 30px rgba(0, 0, 0, 0.55);
  --ob-radius-panel:  12px;
  /* A control's outline. --ob-border is 1.27:1 against a control's fill,
     which reads as no border at all; WCAG 1.4.11 wants 3:1 for the edge that
     identifies a control. Slate-500 is 3.9:1 on --ob-surface-flat. */
  --ob-control-border: #64748b;

  /* Map chrome. Controls sit --ob-map-inset from the screen edge; the credits
     pill is --ob-credits-h tall; a legend that shares the bottom edge with it
     on a phone is lifted by --ob-credits-clear, pill and an 8px gap. */
  --ob-map-inset:     12px;
  --ob-credits-h:     24px;
  --ob-credits-bg:    rgba(3, 7, 18, 0.82);   /* the pill over the map; a light map theme may lighten it */
  --ob-credits-clear: calc(var(--ob-map-inset) + var(--ob-credits-h) + 8px);

  /* One height for a control: the segmented switch, the zoom buttons and the
     header's links (the Layers button and the search box are 36px, a step
     above). Under a coarse pointer both are 44px, below. */
  --ob-control-h:     32px;
  --ob-control-h-lg:  36px;

  /* A hovered or open item in the header, its menu and lists. */
  --ob-hover:         rgba(255, 255, 255, 0.06);

  /* Every pair below clears WCAG AA on --ob-bg: fg 19.3:1, muted 7.9:1,
     and each accent above 7:1. Substituting other values forfeits that. */
}

/* A finger needs more than a mouse: on a touch screen every control that takes
   its height from these tokens is 44px (WCAG 2.5.5). Pages size their own
   controls from the same tokens, so they follow. */
@media (pointer: coarse) {
  :root {
    --ob-control-h:    44px;
    --ob-control-h-lg: 44px;
  }
}

/* The light theme, for a page that is light from top to bottom (`theme:
   'full'` in site.config.mjs stamps ob-theme-full on <html>): the header,
   panels, controls and text all take these. Taken from the Traffic Violence
   dashboard, its first full light theme. Every pair clears WCAG AA on
   --ob-bg and on white: fg 17:1, muted 7.6:1, purple 7:1 (check.mjs). */
html.ob-theme-full[data-map-theme="light"] {
  color-scheme: light;
  --ob-bg:            #f5f6f8;
  --ob-fg:            #111827;
  --ob-muted:         #4b5563;
  --ob-border:        #e3e6eb;
  --ob-purple:        #7e22ce;   /* deepened from #c084fc to 7:1 for text and focus on white */
  --ob-on-accent:     #ffffff;
  --ob-surface:       rgba(255, 255, 255, 0.85);
  --ob-surface-flat:  #ffffff;
  --ob-shadow:        0 1px 3px rgba(15, 23, 42, 0.12);
  --ob-shadow-hover:  0 10px 18px -4px rgba(15, 23, 42, 0.18);
  --ob-panel:         rgba(255, 255, 255, 0.95);
  --ob-panel-border:  rgba(15, 23, 42, 0.14);
  --ob-panel-shadow:  0 6px 24px rgba(15, 23, 42, 0.12);
  --ob-control-border: #858d9b;  /* 3.3:1: a control's edge must clear 3:1 */
  --ob-credits-bg:    rgba(255, 255, 255, 0.9);
  --ob-hover:         rgba(15, 23, 42, 0.06);
}

/* A page not yet converted keeps its dark interface over a light basemap; the
   translucent panel would read as gray haze there, so it turns nearly solid. */
html:not(.ob-theme-full)[data-map-theme="light"] {
  --ob-panel:        rgba(17, 24, 39, 0.94);
  --ob-panel-shadow: 0 6px 24px rgba(15, 23, 42, 0.35);
}

/* One keyboard focus ring on the full-screen maps, the site's 2px purple.
   It replaces the libraries' own: Leaflet leaves the browser's thin default,
   MapLibre and the census bundle draw blue. Controls that need a different
   offset (the segmented buttons below) set it after this rule. */
html.ob-app :focus-visible {
  outline: 2px solid var(--ob-purple);
  outline-offset: 2px;
  box-shadow: none;
}
/* The map fills the screen, so its ring goes inside the edge. */
/* The zoom buttons are the control height too. */
html.ob-app .leaflet-control-zoom a { width: var(--ob-control-h); height: var(--ob-control-h); line-height: var(--ob-control-h); }
@media (pointer: coarse) {
  html.ob-app .maplibregl-ctrl-group button { width: var(--ob-control-h); height: var(--ob-control-h); }
}
html.ob-app .leaflet-container:focus-visible,
html.ob-app #map:focus-visible { outline-offset: -2px; }

/* A segmented control for map panels (the Dark / Light basemap switch on
   both maps), here with the panel tokens since the maps never load ui.css. */
.ob-seg {
  display: inline-flex;
  border: 1px solid var(--ob-control-border);
  border-radius: var(--ob-radius-sm);
  overflow: hidden;
}
.ob-seg button {
  min-height: var(--ob-control-h);
  padding: 5px 14px;
  border: 0;
  border-radius: 0;
  background: var(--ob-surface-flat);
  color: var(--ob-fg);
  font: 12px var(--ob-font);
  cursor: pointer;
}
.ob-seg button + button { border-left: 1px solid var(--ob-control-border); }
.ob-seg button[aria-pressed="true"] { background: var(--ob-purple); color: var(--ob-on-accent); font-weight: 600; }
.ob-seg button:focus-visible { outline: 2px solid var(--ob-purple); outline-offset: -2px; }
/* The pressed button is filled with the same purple, so its ring is drawn in the ink
   that sits on that fill instead (a purple ring on purple was invisible). */
.ob-seg button[aria-pressed="true"]:focus-visible { outline-color: var(--ob-on-accent); }

/* Map credits, folded to a pill in the bottom-right corner. Closed it still
   names the sources whose licenses ask for visible credit (OpenStreetMap's
   ODbL, and the basemap's provider), so the notice never depends on a tap;
   the full line, with links and the page's own sources, opens above it. It
   replaces a credit bar that took three lines along the bottom of a phone.
   Here with the panel tokens since the maps never load ui.css. */
.ob-credits {
  position: fixed;
  right: var(--ob-map-inset);
  bottom: var(--ob-map-inset);
  z-index: 9990;
  display: flex;
  flex-direction: column-reverse;     /* the body opens upward, above the pill */
  align-items: flex-end;
  max-width: min(380px, calc(100vw - 2 * var(--ob-map-inset)));
  font: 12px/1.4 var(--ob-font);
  color: var(--ob-muted);
}
.ob-credits summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  box-sizing: border-box;   /* the whole pill, edge and padding, is this tall */
  height: var(--ob-credits-h);
  padding: 0 10px 0 3px;
  border: 1px solid var(--ob-panel-border);
  border-radius: 999px;
  background: var(--ob-credits-bg);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  white-space: nowrap;
  cursor: pointer;
  list-style: none;
}
.ob-credits summary::-webkit-details-marker { display: none; }
/* The pill stays 24px tall; its tap area reaches 4px past it on every side, and
   on a touch screen 10px above and below, which makes it 44px. */
.ob-credits summary { position: relative; }
.ob-credits summary::before { content: ""; position: absolute; inset: -4px; }
@media (pointer: coarse) {
  .ob-credits summary::before { inset: -10px -4px; }
  .ob-credits-body { line-height: 2; }   /* room between the links for a finger */
}
.ob-credits summary:hover { color: var(--ob-fg); }
.ob-credits summary:focus-visible { outline: 2px solid var(--ob-purple); outline-offset: 2px; }
.ob-credits .ob-credits-i {
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border: 1px solid currentColor;
  border-radius: 50%;
  font-weight: 700;
  font-size: 10px;
  line-height: 1;
}
.ob-credits[open] .ob-credits-i { background: var(--ob-purple); border-color: var(--ob-purple); color: var(--ob-on-accent); }
.ob-credits-body {
  margin-bottom: 6px;       /* the column is reversed: this is the gap above the pill */
  padding: 8px 10px;
  border: 1px solid var(--ob-panel-border);
  border-radius: var(--ob-radius-sm);
  background: var(--ob-panel);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--ob-panel-shadow);
  color: var(--ob-fg);
}
.ob-credits-body a { color: var(--ob-purple); }
.ob-credits-body a:focus-visible { outline: 2px solid var(--ob-purple); outline-offset: 1px; }

.ob-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--ob-bar-h);
  z-index: 10000;

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0 clamp(0.75rem, 3vw, 1.25rem);

  /* Opaque rather than translucent: it sits over maps and dashboards alike,
     and has to stay legible against both. It takes the page's theme. */
  background: var(--ob-bg);
  border-bottom: 1px solid var(--ob-border);
  font-family: var(--ob-font);
  font-size: 0.875rem;
  line-height: 1;
  box-sizing: border-box;
}

.ob-bar *,
.ob-bar *::before,
.ob-bar *::after { box-sizing: border-box; }

.ob-bar a.ob-home,
.ob-bar a.ob-back {
  display: inline-flex !important;
  align-items: center;
  gap: 0.55rem;
  text-decoration: none !important;
  color: var(--ob-fg) !important;
  font-weight: 600;
  letter-spacing: -0.01em;
  padding: 0.4rem 0.5rem;
  margin: -0.4rem -0.5rem;
  min-height: var(--ob-control-h);
  border-radius: 6px;
  transition: background-color 0.2s ease;
}

.ob-bar a.ob-home:hover,
.ob-bar a.ob-back:hover,
.ob-bar a:focus-visible { background: var(--ob-hover); }

.ob-bar a:focus-visible {
  outline: 2px solid var(--ob-purple);
  outline-offset: 1px;
}

.ob-bar .ob-mark { flex: none; display: block; }

.ob-bar .ob-name { color: inherit; }

.ob-bar a.ob-back {
  color: var(--ob-muted) !important;
  font-weight: 500;
}

.ob-bar a.ob-back:hover,
.ob-bar a.ob-back:focus-visible { color: var(--ob-purple) !important; }

/* The "All projects" menu: a <details> whose summary looks like the old link. */
.ob-bar .ob-menu { position: relative; }
.ob-bar summary.ob-back {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  list-style: none;
  cursor: pointer;
  color: var(--ob-muted);
  font-weight: 500;
  padding: 0.4rem 0.5rem;
  margin: -0.4rem -0.5rem;
  min-height: var(--ob-control-h);
  border-radius: 6px;
  transition: background-color 0.2s ease;
}
.ob-bar summary.ob-back::-webkit-details-marker { display: none; }
.ob-bar summary.ob-back:hover,
.ob-bar summary.ob-back:focus-visible,
.ob-bar .ob-menu[open] > summary.ob-back { background: var(--ob-hover); color: var(--ob-purple); }
.ob-bar summary.ob-back:focus-visible { outline: 2px solid var(--ob-purple); outline-offset: 1px; }
.ob-bar .ob-caret { flex: none; transition: transform 0.2s ease; }
.ob-bar .ob-menu[open] .ob-caret { transform: rotate(180deg); }
.ob-bar .ob-menu-list {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: -0.5rem;
  width: max-content;
  min-width: 14rem;
  max-width: calc(100vw - 1.5rem);
  /* A short screen (a phone held sideways) cannot fit every project: the list
     stops above the bottom of the screen and scrolls. */
  max-height: calc(100vh - var(--ob-bar-h) - 1rem);
  max-height: calc(100dvh - var(--ob-bar-h) - 1rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0;
  padding: 0.35rem;
  list-style: none;
  background: var(--ob-bg);
  border: 1px solid var(--ob-border);
  border-radius: 10px;
  box-shadow: var(--ob-panel-shadow);
}
.ob-bar .ob-menu-list a {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: var(--ob-control-h);
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  color: var(--ob-fg);
  font-weight: 500;
  text-decoration: none;
}
.ob-bar .ob-menu-list a:hover { background: var(--ob-hover); }
@media (pointer: coarse) { .ob-bar .ob-menu-list a { min-height: 44px; } }
.ob-bar .ob-menu-list a[aria-current="page"] { color: var(--ob-purple); font-weight: 600; }
.ob-bar .ob-ext { color: var(--ob-muted); }
.ob-bar .ob-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Narrow screens: the wordmark alone carries the identity. */
@media (max-width: 420px) {
  .ob-bar .ob-name { display: none; }
  .ob-bar .ob-back { font-size: 0.82rem; }
}

@media (prefers-reduced-motion: reduce) {
  .ob-bar a, .ob-bar summary.ob-back, .ob-bar .ob-caret { transition: none; }

  /* Safety net for branded pages whose own CSS predates the setting — the
     map bundles and dashboards this file cannot reach into. Collapses CSS
     transitions and animations rather than removing them, which keeps any
     code that waits on transitionend/animationend working. */
  html.ob-doc *,
  html.ob-app * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* The particle field sits behind everything and is decorative only. */
#obParticles {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
}

/* --- layout compensation ------------------------------------------------- */

/* Document pages: push the page's own content below the bar. */
html.ob-doc body { padding-top: var(--ob-bar-h); }

/* Full-viewport apps: the map is fixed, so padding the body does nothing.
   Shift the map itself instead. The bar may overlap the top of the map
   canvas — maps pan, so that costs nothing. */
html.ob-app body { padding-top: 0; }

html.ob-app #map,
html.ob-app .folium-map {
  top: var(--ob-bar-h) !important;
}

/* A folium map is sized height:100% rather than pinned top and bottom, so
   moving it down also pushed its bottom 44px off the screen, credit line
   included. It is shortened by the same amount. Leaflet's control stacks sit
   inside the map, so they move down with it and need no offset of their own.
   The old margin moved them twice. */
html.ob-app .folium-map { height: calc(100% - var(--ob-bar-h)) !important; }

/* Anything else a page pins near the top names its own selectors in
   site.config.mjs's `offset` key; the build emits a one-line stylesheet for
   them against --ob-bar-h. Page-specific selectors do not belong in this
   file, which is why there is no rule for them here. */

@media print {
  .ob-bar { display: none; }
  html.ob-doc body { padding-top: 0; }
}

/* The dark basemap is lifted a little: CARTO's Dark Matter land is near black
   (about 6 of 255), which hid the street grid and merged with the page. Roads
   and land rise about 1.5x with the floor raised; applied to the base tiles
   only, so labels and the data on top keep their own colors. The census map's
   vector basemap gets the same lift in its map-theme.js. */
html:not([data-map-theme="light"]) .ob-tiles-base { filter: brightness(1.6) contrast(0.93); }
