/* ================================================================
   ZIPTEL SHARED FOUNDATION — Slice 2 (S2)
   Font pipeline, global product shell, and shared component library.
   Depends on ziptel-tokens.css being loaded first (values only, no rules).
   Scope per SLICE2_FOUNDATION_SONNET_SPEC_2026-08-22.md: styling +
   structural shell only. No synthesis-dependent components, no Value
   Assessment anywhere, no map-palette change, no layout restructuring.
   ================================================================ */

/* ---------------------------------------------------------------
   Fonts
   --------------------------------------------------------------- */
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/newsreader-variable.woff2") format("woff2-variations"),
       url("/assets/fonts/newsreader-variable.woff2") format("woff2");
}
@font-face {
  font-family: "Newsreader";
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/newsreader-italic-variable.woff2") format("woff2-variations"),
       url("/assets/fonts/newsreader-italic-variable.woff2") format("woff2");
}
@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/instrumentsans-variable.woff2") format("woff2-variations"),
       url("/assets/fonts/instrumentsans-variable.woff2") format("woff2");
}
@font-face {
  font-family: "Instrument Sans";
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/instrumentsans-italic-variable.woff2") format("woff2-variations"),
       url("/assets/fonts/instrumentsans-italic-variable.woff2") format("woff2");
}
/* FONT-1 resolved 2026-08-23 (Karan): Instrument Sans replaces the
   unresolved Anthropic Sans requirement as the primary UI/body face — see
   frontend/assets/fonts/PROVENANCE.md "Resolution" section. Anthropic Sans
   is no longer requested anywhere in this product. */

/* Tabular numerals — analytical tables, scores, rankings, and any figure
   meant to be compared at a glance. Both packaged faces (Instrument Sans,
   Newsreader) support the OpenType `tnum` feature via font-variant-numeric;
   applied via this shared class rather than a blanket rule so ordinary
   prose numerals (e.g. inline body copy) are unaffected. Already wired
   into .z-table cells and .z-stat-value in the component library below —
   this class exists for any other numeral context a page needs it for. */
.z-tabular-nums { font-variant-numeric: tabular-nums; }

/* Visually-hidden utility. Used to give chat.html/tables.html a real, single <h1>
   identifying the page's content once the shared header's wordmark moves off the
   h1 role (an <a>, matching the shared-shell contract) — a small, targeted a11y
   improvement, not a visible change. */
.z-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;
}

/* ---------------------------------------------------------------
   Base chrome — applied narrowly via the .z-shell wrapper class added
   to <body> on each migrated page, so nothing outside the six pages'
   own pre-existing rules is touched by accident.
   --------------------------------------------------------------- */
.z-shell { background: var(--z-ivory); color: var(--z-ink); }
.z-shell h1, .z-shell h2, .z-shell h3 { font-family: var(--z-font-display); }

/* ---------------------------------------------------------------
   Global header shell (W3)
   Markup contract (identical skeleton on all six pages, nav items vary):

   <header class="z-header">
     <a class="z-wordmark" href="/">ZIPTEL</a>
     <span class="z-region">New Jersey</span>
     <nav class="z-nav" aria-label="Primary">
       <a href="/explore" class="z-navlink" data-active>Explore</a>
       <a href="/chat" class="z-navlink">Ask Zippy</a>
       <a href="/tables" class="z-navlink">Data</a>
       <a href="/about" class="z-navlink">About</a>
     </nav>
     <button class="z-navtoggle" aria-expanded="false" aria-controls="...">Menu</button>
   </header>

   No "Compare" item: the route does not exist yet (CMP-01 prohibits
   navigating to a nonexistent destination); it is added with the
   comparison slice. --------------------------------------------------- */
.z-header {
  display: flex;
  align-items: center;
  gap: var(--z-space-4);
  padding: var(--z-space-3) var(--z-space-5);
  background: var(--z-navy);
  color: #fff;
  flex-wrap: wrap;
  /* M5/S5 correction #4 (2026-08-24): custom property tracking this rule's own OWN top
     padding token, so index.html's safe-area override (which must ADD an inset on top of
     the correct ordinary padding, not replace it) can reference "whatever ziptel.css says
     the ordinary padding is right now" instead of hardcoding a single value that would be
     wrong at some breakpoints. Custom properties cascade normally across stylesheets/media
     queries, unlike the shorthand `padding` property above, so each block below can override
     just this one value and index.html's calc() picks up whichever one is active. */
  --z-header-top-pad: var(--z-space-3);
}
.z-wordmark {
  font-family: var(--z-font-display);
  font-weight: 600;
  font-size: var(--z-text-lg);
  letter-spacing: 0.06em;
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
}
.z-wordmark:hover { color: #fff; opacity: 0.85; }
.z-region {
  font-family: var(--z-font-ui);
  font-size: var(--z-text-xs);
  color: rgba(255,255,255,0.65);
  border-left: 1px solid rgba(255,255,255,0.25);
  padding-left: var(--z-space-4);
  white-space: nowrap;
}
/* Market switcher (CT publishing brief WP6, 2026-11-04): "New Jersey · Connecticut" in the .z-region slot, the
   current market marked (aria-current). Phones (<=640px, where the region label was hidden) show "NJ · CT" with
   44px targets, so the header never wraps into the nav. */
.z-market { display: inline-flex; align-items: center; gap: var(--z-space-2); }
.z-mkt {
  color: rgba(255,255,255,0.65);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  border-bottom: 2px solid transparent;
}
.z-mkt:hover { color: #fff; }
.z-mkt[aria-current] { color: #fff; font-weight: 600; border-bottom-color: var(--z-accent-raw-sampled); }
.z-mkt-s { display: none; }
.z-mkt-sep { color: rgba(255,255,255,0.4); }
.z-nav {
  display: flex;
  align-items: center;
  gap: var(--z-space-5);
  margin-left: auto;
  font-family: var(--z-font-ui);
}
.z-navlink {
  color: rgba(255,255,255,0.82);
  font-size: var(--z-text-sm);
  font-weight: 500;
  text-decoration: none;
  padding: var(--z-space-2) var(--z-space-1);
  border-bottom: 2px solid transparent;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.z-navlink:hover { color: #fff; }
.z-navlink[data-active] {
  color: #fff;
  border-bottom-color: var(--z-accent-raw-sampled);
  font-weight: 600;
}
.z-navtoggle {
  display: none;
  margin-left: auto;
  background: transparent;
  border: 1px solid rgba(255,255,255,0.4);
  color: #fff;
  border-radius: var(--z-radius-sm);
  padding: var(--z-space-2) var(--z-space-3);
  font-family: var(--z-font-ui);
  font-size: var(--z-text-sm);
  min-height: 44px;
  min-width: 44px;
}
@media (max-width: 640px) {
  .z-header { padding: var(--z-space-2) var(--z-space-3); gap: var(--z-space-2); }
  /* M5/S5 correction #3 (2026-08-24, Opus STOP_CONCRETE_DEFECT on d989e8c): town.html sets
     viewport-fit=cover but had zero env(safe-area-*) handling anywhere -- Explore's own
     top-inset rule lives only in index.html's inline styles, so town.html never inherited
     it. These three longhand properties come after the shorthand `padding` above in this
     same rule, so they win for top/left/right; bottom is deliberately left alone (the header
     has no bottom-edge safe-area concern). calc() ADDS the inset to the existing token value
     rather than replacing it, so a zero inset (every non-cutout device, and desktop) leaves
     the padding exactly as it was -- verified live before this shipped. */
  .z-header {
    --z-header-top-pad: var(--z-space-2);
    padding-top: calc(var(--z-space-2) + env(safe-area-inset-top,0px));
    padding-left: calc(var(--z-space-3) + env(safe-area-inset-left,0px));
    padding-right: calc(var(--z-space-3) + env(safe-area-inset-right,0px));
  }
  .z-region { display: none; }
  .z-region.z-market { display: inline-flex; padding-left: var(--z-space-2); gap: 0; }
  .z-mkt { min-height: 44px; min-width: 44px; justify-content: center; }
  .z-mkt-l { display: none; }
  .z-mkt-s { display: inline; }
  .z-nav {
    display: none;
    width: 100%;
    order: 5;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    margin-left: 0;
  }
  .z-nav.z-nav-open { display: flex; }
  .z-navlink { width: 100%; min-height: 44px; }
  .z-navtoggle { display: inline-flex; align-items: center; justify-content: center; }
  /* M5/S5 correction (2026-08-24, Opus STOP_CONCRETE_DEFECT on 4053873): the wordmark link
     was the only header control never given a real touch target — .z-navlink and
     .z-navtoggle both already had min-height:44px next to it. Same technique used here:
     min-height + inline-flex centering grows the BOX only; font-size/letter-spacing/text
     are untouched, so the visible "ZIPTEL" lettering is pixel-identical. Shared file, so
     Explore and the accepted town profile both get this consistently. */
  .z-wordmark { display: inline-flex; align-items: center; min-height: 44px; }
}

/* M5/S5 correction #3 (2026-08-24, Opus STOP_CONCRETE_DEFECT on d989e8c): a landscape phone
   (e.g. 812x375) is wider than 640px, so it never enters the block above -- confirmed live:
   the wordmark measured 44px on Explore only there because index.html carries a second,
   Explore-only copy of the same rule under its own (max-height:560px) trigger; town.html has
   no equivalent, so its wordmark stayed ~30px in that orientation. min-width:641px keeps this
   from ever overlapping the block above (which already covers everything <=640px regardless
   of orientation), so there is no rule-order ambiguity about which one wins at any given
   viewport -- each viewport matches exactly one of the two blocks, never both. This one
   deliberately uses the BASE header padding tokens (--z-space-3/--z-space-5), not the
   640px-block's smaller mobile-collapsed ones (--z-space-2/--z-space-3): a wide landscape
   phone has NOT crossed the nav-collapse breakpoint, so its ordinary padding should still be
   the base amount, just with any real inset added on top. */
@media (min-width:641px) and (max-height:560px) and (orientation:landscape) {
  .z-wordmark { display: inline-flex; align-items: center; min-height: 44px; }
  .z-header {
    --z-header-top-pad: var(--z-space-3);
    padding-top: calc(var(--z-space-3) + env(safe-area-inset-top,0px));
    padding-left: calc(var(--z-space-5) + env(safe-area-inset-left,0px));
    padding-right: calc(var(--z-space-5) + env(safe-area-inset-right,0px));
  }
}

/* Focus ring. Deliberately scoped to .z-header only: each of the six
   pages already ships Slice-1's reviewed, deployed, production
   :focus-visible rule (a:focus-visible,button:focus-visible,...{outline:
   2px solid var(--navy-500);outline-offset:2px}) — left byte-for-byte
   untouched by this slice rather than risking a behavior change to an
   already-shipped a11y feature. The new navy .z-header needs its own
   light-on-dark ring (a navy-colored ring would be invisible on a navy
   background), so it gets one scoped rule here; --z-focus-outline exists
   as a token for S3+ new surfaces to adopt, but nothing in S2 applies it
   globally. */
.z-header :focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* ---------------------------------------------------------------
   Component library (W4) — styling + static contract only in S2.
   Not wired into live surfaces yet (that is S3+); demonstrated with
   illustrative markup in design/S2_COMPONENT_PREVIEW.html only.
   --------------------------------------------------------------- */

/* tier-badge: 7 ordered stops + unavailable. Soft tonal fill, tone-
   matched/transparent border — never a generic gray outline. */
.z-tier-badge {
  display: inline-flex;
  align-items: center;
  width: max-content;
  border-radius: var(--z-radius-pill);
  padding: 0.3em 0.75em;
  font-family: var(--z-font-ui);
  font-weight: 600;
  font-size: var(--z-text-sm);
  white-space: nowrap;
  border: 1px solid transparent;
}
.z-tier-exceptional, .z-tier-leading {
  color: var(--z-tier-exceptional-fg); background: var(--z-tier-exceptional-bg);
}
.z-tier-excellent { color: var(--z-tier-excellent-fg); background: var(--z-tier-excellent-bg); }
.z-tier-strong    { color: var(--z-tier-strong-fg);    background: var(--z-tier-strong-bg); }
.z-tier-typical   { color: var(--z-tier-typical-fg);   background: var(--z-tier-typical-bg); }
.z-tier-limited   { color: var(--z-tier-limited-fg);   background: var(--z-tier-limited-bg); }
.z-tier-low       { color: var(--z-tier-low-fg);       background: var(--z-tier-low-bg); }
.z-tier-verylow   { color: var(--z-tier-verylow-fg);   background: var(--z-tier-verylow-bg); }
.z-tier-unavailable { color: var(--z-unavailable-fg); background: var(--z-unavailable-bg); }

.z-tier-legend {
  display: flex;
  flex-direction: column;
  gap: var(--z-space-1);
  font-family: var(--z-font-ui);
}
.z-tier-legend-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-space-3);
  padding-block: var(--z-space-1);
}
.z-tier-legend-note { color: var(--z-navy-500); font-size: var(--z-text-xs); }

/* Buttons */
.z-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--z-space-2);
  min-height: 44px;
  padding: var(--z-space-2) var(--z-space-4);
  border-radius: var(--z-radius-sm);
  font-family: var(--z-font-ui);
  font-weight: 600;
  font-size: var(--z-text-sm);
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
}
.z-btn-primary { background: var(--z-navy); color: #fff; }
.z-btn-primary:hover { background: var(--z-navy-700); }
.z-btn-accent { background: var(--z-accent); color: #fff; }
.z-btn-accent:hover { filter: brightness(1.08); }
.z-btn-secondary {
  background: transparent; color: var(--z-navy); border-color: var(--z-border);
}
.z-btn-secondary:hover { background: var(--z-fill); }

/* Card — border-defined, minimal/no shadow */
.z-card {
  background: var(--z-ivory);
  border: 1px solid var(--z-border);
  border-radius: var(--z-radius-md);
  padding: var(--z-space-4);
}

/* Stat / feature numeral — bare, no pill */
.z-stat-label {
  font-family: var(--z-font-ui);
  font-size: var(--z-text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--z-navy-500);
}
.z-stat-value {
  font-family: var(--z-font-display);
  font-size: var(--z-text-feature);
  font-weight: 500;
  color: var(--z-accent);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.z-stat-context {
  font-family: var(--z-font-ui);
  font-size: var(--z-text-sm);
  color: var(--z-navy-500);
}

/* Source superscript + footnote list */
.z-source-sup {
  font-size: 0.65em;
  vertical-align: super;
  color: var(--z-navy-500);
  margin-left: 0.15em;
  text-decoration: none;
}
.z-source-footer {
  font-family: var(--z-font-ui);
  font-size: var(--z-text-xs);
  color: var(--z-navy-500);
  border-top: 1px solid var(--z-border);
  padding-top: var(--z-space-3);
  margin-top: var(--z-space-4);
  display: flex;
  flex-direction: column;
  gap: var(--z-space-1);
}

/* Vintage chip — real date or honest absence, never synthesized */
.z-vintage-chip {
  font-family: var(--z-font-ui);
  font-size: var(--z-text-xs);
  color: var(--z-navy-500);
  font-style: italic;
}

/* Reason note — grouped unavailability, one statement not four repeats */
.z-reason-note {
  border-left: 3px solid var(--z-accent-raw-sampled);
  padding-left: var(--z-space-3);
  font-family: var(--z-font-ui);
  font-size: var(--z-text-sm);
  color: var(--z-navy-700);
}

/* Dimension bar — single shared center-split bar per row (comparison,
   pillar rows). THE SCORE ↔ BAR ↔ TIER RULE (binds S3+, blueprint
   §13.4.4): a dimension's bar-fill fraction is always the same
   normalized 0–100 value that produced its numeral and its tier badge —
   all three are computed from one stored value, never independently, so
   they can never visually disagree. This rule is declared here as the
   single source of truth; S3+ implementations must derive fill%, numeral,
   and badge from one shared function/value, not three separate ones. */
.z-dimension-bar {
  position: relative;
  height: 8px;
  border-radius: var(--z-radius-pill);
  background: var(--z-fill);
  overflow: hidden;
}
.z-dimension-bar-fill {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  border-radius: var(--z-radius-pill);
  background: var(--z-navy-700);
}
.z-dimension-bar-fill.z-fill-accent { background: var(--z-accent); }

/* Data table — centered headers/cells, tabular numerals (owner
   preference; see blueprint/handoff §10.3) */
.z-table { width: 100%; border-collapse: collapse; font-family: var(--z-font-ui); }
.z-table th, .z-table td {
  text-align: center;
  padding: var(--z-space-2) var(--z-space-3);
  border-bottom: 1px solid var(--z-border);
  font-variant-numeric: tabular-nums;
}
.z-table th { font-weight: 600; color: var(--z-navy); font-size: var(--z-text-sm); }

/* Skeleton loading state */
.z-skeleton {
  background: linear-gradient(90deg, var(--z-fill) 25%, var(--z-border) 50%, var(--z-fill) 75%);
  background-size: 200% 100%;
  border-radius: var(--z-radius-sm);
  animation: z-skeleton-shimmer 1.4s ease infinite;
}
@media (prefers-reduced-motion: reduce) {
  .z-skeleton { animation: none; }
}
@keyframes z-skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
