/* Type 3 Wildland Caterer Directory — single stylesheet.
   Mobile-first. System fonts only. Badges are border/text based so they
   survive printing (browsers strip background colors by default).

   Layout rule for the index: the roster is the page. Everything above it —
   heading, filters, count — is budgeted to stay under one screen so the first
   operator card is visible without scrolling. Explanatory prose lives on
   about.html, never here. */

:root {
  --fg: #1c1c1c;
  --muted: #555;
  --bg: #fdfcfa;
  --panel: #f4f1ec;
  --line: #cdc7bc;         /* panel + card edges (decorative) */
  --line-strong: #8a8172;  /* edges that carry meaning: chips, toggles.
                              >=3:1 (WCAG 1.4.11) against white, --panel and --bg */
  --ctl-border: #767676;   /* form controls need >=3:1 non-text contrast (WCAG 1.4.11) */
  --accent: #8c3b1b;       /* deep rust — links, accents */
  --ok: #1d6b33;           /* verified */
  --warn: #8a5a00;         /* stale */
  --danger: #a01c1c;       /* sample banner */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --space: 1rem;

  /* One shared page width. Header, main and footer all align to it — before,
     the header was full-bleed while main capped at 60rem, which read as a
     narrow column floating under an edge-to-edge bar on a wide monitor.
     Wide enough for the 17rem filter rail plus three card tracks; prose is
     capped separately and much narrower (.prose), so reading measure on
     about/get-listed is unaffected by this number. */
  --wide: 82rem;

  /* Type scale — three steps below body. Nothing else. */
  --fs-sm: .9375rem;   /* 15px — secondary text, labels */
  --fs-xs: .8125rem;   /* 13px — chips, badges, meta */

  /* Radius scale — two steps plus pill. Controls sm, containers md. */
  --r-sm: 6px;
  --r-md: 8px;
  --r-pill: 999px;

  --tap: 44px;         /* minimum interactive target */
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* Author display rules (flex/grid) would otherwise defeat the hidden attribute,
   which filters.js and the no-JS filter bar rely on. */
[hidden] { display: none !important; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.55;
  color: var(--fg);
  background: var(--bg);
}
img { max-width: 100%; }
a { color: var(--accent); }
a:hover { text-decoration-thickness: 2px; }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

main {
  max-width: var(--wide);
  margin: 0 auto;
  padding: 0 var(--space) 2rem;
}

/* Visually hidden but read by assistive tech. */
.vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--accent); color: #fff;
  padding: .6rem 1rem; border-radius: 0 0 var(--r-sm) 0;
  z-index: 10; font-weight: 600; text-decoration: none;
}
.skip-link:focus { left: 0; }

/* ---------- header / footer ---------- */
.site-head {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem 1.25rem;
  align-items: center;
  /* Bar stays full-bleed so the panel background and rule still span the
     viewport; the padding indents its contents to line up with main's
     centred column. max() collapses to plain --space below --wide, where
     the calc goes negative. */
  padding: .15rem max(var(--space), calc((100% - var(--wide)) / 2));
  border-bottom: 2px solid var(--line);
  background: var(--panel);
}
.site-name {
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--fg);
  text-decoration: none;
  display: inline-flex; align-items: center;
  min-height: var(--tap);
}
.site-nav { display: flex; gap: .35rem 1rem; flex-wrap: wrap; }
.site-nav a {
  text-decoration: none;
  display: inline-flex; align-items: center;
  min-height: var(--tap); padding: 0 .15rem;
}
.site-nav a[aria-current="page"] {
  color: var(--fg);
  font-weight: 700;
  box-shadow: inset 0 -3px 0 0 var(--accent);
}

.site-foot {
  border-top: 2px solid var(--line);
  background: var(--panel);
  padding: .75rem var(--space) 1.25rem;
  font-size: var(--fs-sm);
  color: var(--muted);
}
.site-foot > * { max-width: var(--wide); margin-inline: auto; }
/* margin-block, not margin — a shorthand here would reset the margin-inline:auto
   set by `.site-foot > *` above and drop this row out of alignment with the rest
   of the footer. */
.foot-actions { display: flex; flex-wrap: wrap; gap: 0 1.25rem; margin-block: 0; }
.foot-cta {
  display: inline-flex; align-items: center;
  min-height: var(--tap); font-weight: 600;
}
.foot-meta { font-size: var(--fs-xs); margin: .2rem auto; }

/* ---------- index: heading strip ---------- */
.hero h1 { margin: .7rem 0 .15rem; font-size: 1.5rem; line-height: 1.2; }
.sub { margin: 0 0 .5rem; color: var(--muted); font-size: var(--fs-sm); }
/* Same inline-padding trick as the card titles: bigger target, no reflow.
   Applied only to single-line contexts — on a wrapping paragraph the padded
   boxes of successive lines would overlap. Values differ because an inline
   box's height follows font-size, not line-height. */
.sub a, .verify-line a, .form-fallback a { padding-block: .75rem; }  /* 15px text */
.foot-meta a { padding-block: .85rem; }                              /* 13px text */

/* Static coverage line shown only when JS is off (filters.js hides it). */
.coverage-strip {
  margin: 0 0 .9rem; font-size: var(--fs-sm); color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: .5rem .75rem; background: var(--panel);
}

/* ---------- browse layout ----------
   At >=900px the filters move into a left rail so they and the roster share one
   screen: per NN/g, users read the relationship between a filter and its results
   far better when both are visible at once. Below that it stacks, unchanged. */
@media (min-width: 900px) {
  .browse { display: grid; grid-template-columns: 17rem 1fr; gap: 1.25rem; align-items: start; }
  .browse .filter-bar { position: sticky; top: .75rem; margin-bottom: 0; }
}

/* ---------- filter bar ---------- */
.filter-bar {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: .6rem .75rem;
  margin-bottom: 1rem;
  background: var(--panel);
  display: grid;
  gap: .5rem;
}
.filter-bar fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.filter-bar legend { font-weight: 600; font-size: var(--fs-sm); padding: 0 0 .25rem; }

/* Region toggles — the primary control, always open. Doubles as the coverage
   readout: each chip carries its operator count. */
.region-chips { display: flex; flex-wrap: wrap; gap: .35rem; }
.chip-toggle {
  display: inline-flex; align-items: center; gap: .4rem;
  min-height: var(--tap); padding: .1rem .6rem .1rem .5rem;
  border: 1px solid var(--line-strong); border-radius: var(--r-pill);
  background: #fff; font-size: var(--fs-sm); cursor: pointer;
}
.chip-toggle input { width: 1.05rem; height: 1.05rem; margin: 0; flex: none; }
.chip-n {
  font-size: var(--fs-xs); color: var(--muted);
  border-left: 1px solid var(--line); padding-left: .4rem;
}
/* State cue that works without :has() — the checkbox itself is always visible. */
.chip-toggle input:checked ~ .chip-name { font-weight: 700; }
.chip-toggle:has(input:checked) { border-color: var(--accent); border-width: 2px; padding: .1rem .55rem .1rem .45rem; }
/* Below ~480px the chips wrap one-per-row on their natural width, costing five
   rows of vertical budget. A fixed two-column grid holds them to three. */
@media (max-width: 479px) {
  .region-chips { display: grid; grid-template-columns: 1fr 1fr; }
  .chip-toggle { font-size: var(--fs-xs); padding-inline: .4rem .45rem; gap: .3rem; }
  .chip-n { padding-left: .3rem; }
}

.more-filters { border-top: 1px solid var(--line); padding-top: .4rem; }
.more-filters > summary {
  display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
  min-height: var(--tap); cursor: pointer;
  font-weight: 600; font-size: var(--fs-sm);
  list-style-position: inside;
}
/* Without this the summary reads identically whether 0 or 5 filters are set. */
.applied-n {
  font-weight: 700; font-size: var(--fs-xs);
  color: #fff; background: var(--accent);
  border-radius: var(--r-pill); padding: .05rem .5rem;
}
.more-filters > summary::-webkit-details-marker { color: var(--accent); }
.more-filters[open] > summary { margin-bottom: .5rem; }
.more-filters .fields { display: grid; gap: .75rem; }

.checkbox-grid { display: flex; flex-wrap: wrap; gap: .25rem .9rem; }
.checkbox-grid label {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: var(--fs-sm); min-height: var(--tap);
}
.checkbox-grid input[type="checkbox"] { width: 1.15rem; height: 1.15rem; }
.field-hint { font-weight: 400; color: var(--muted); font-size: var(--fs-xs); }
.field-note { font-size: var(--fs-xs); color: var(--warn); margin: .1rem 0 0; font-weight: 600; }

/* Per-option result counts. A greyed zero tells the user a combination is a
   dead end BEFORE they pick it, which the no-match panel can only do after. */
.opt-n {
  font-size: var(--fs-xs); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.checkbox-grid .opt-n::before { content: "("; }
.checkbox-grid .opt-n::after { content: ")"; }
.opt-n.opt-zero { color: var(--danger); }
.checkbox-grid label:has(.opt-zero) .opt-name { color: var(--muted); text-decoration: line-through; }

/* ---------- applied filter chips ---------- */
.applied-bar { border-top: 1px solid var(--line); padding-top: .5rem; }
.applied-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .35rem; }
.applied-chip {
  display: inline-flex; align-items: center; gap: .4rem;
  min-height: var(--tap); padding: .1rem .5rem .1rem .6rem;
  font-size: var(--fs-xs); font-weight: 600;
  background: #fff; color: var(--accent);
  border: 1.5px solid var(--accent); border-radius: var(--r-pill);
}
.applied-chip:hover { background: var(--accent); color: #fff; }
.applied-x { font-size: 1.05rem; line-height: 1; }

.inline-field {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .4rem .6rem; font-size: var(--fs-sm); margin: 0; font-weight: 600;
}
#cap-input, #state-input, #mob-input {
  font: inherit; padding: .45rem .5rem;
  border: 1px solid var(--ctl-border); border-radius: var(--r-sm);
  background: #fff; min-height: var(--tap);
}
#cap-input { width: 7rem; }

/* Result count sits with the controls that change it — the feedback for a
   filter action must never be off-screen. */
.filter-status {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .5rem 1rem; border-top: 1px solid var(--line); padding-top: .5rem;
}
.count { font-weight: 700; margin: 0; }
.count-order { font-weight: 400; color: var(--muted); font-size: var(--fs-sm); }

button, .std-form button {
  font: inherit;
  padding: .5rem 1rem;
  border: 1px solid var(--accent);
  border-radius: var(--r-sm);
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  min-height: var(--tap);
}
button[type="reset"], #zero-reset, .btn-quiet {
  background: transparent; color: var(--accent);
}
#filter-reset { margin-left: auto; }

/* ---------- sticky result bar ----------
   The roster is ~3000px tall on a phone; without this the count is off-screen
   for most of the session, which is the problem we just fixed at the top. */
.sticky-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 5;
  display: flex; align-items: center; gap: .75rem;
  padding: .4rem var(--space);
  background: var(--panel); border-top: 2px solid var(--line-strong);
  font-weight: 700; font-size: var(--fs-sm);
  box-shadow: 0 -2px 8px rgb(0 0 0 / .08);
}
.sticky-bar button { margin-left: auto; padding: .35rem .9rem; }
@media (min-width: 900px) { .sticky-bar { display: none; } }

/* ---------- motion ----------
   Two micro-interactions only, both purely feedback: the count acknowledges that
   a filter did something, and cards ease rather than snap. Off by default for
   anyone who has asked for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .count-flash { animation: countFlash .7s ease-out; border-radius: var(--r-sm); }
  @keyframes countFlash {
    0%   { background: #f6e3b8; box-shadow: 0 0 0 .35rem #f6e3b8; }
    100% { background: transparent; box-shadow: 0 0 0 .35rem transparent; }
  }
  .card-shift { animation: cardIn .18s ease-out; }
  @keyframes cardIn {
    from { opacity: 0; transform: translateY(-.25rem); }
    to   { opacity: 1; transform: none; }
  }
}

/* ---------- roster cards ---------- */
/* Column count follows the space available rather than fixed breakpoints:
   1 up on phones, 2 at tablet/laptop, 3 once the rail and three 18rem tracks
   fit. 18rem is the floor at which a card's phone number, badges and service
   chips still sit on sensible lines. */
.roster {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
}

.card {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: .9rem 1rem;
  background: #fff;
  display: flex;
  flex-direction: column;
  gap: .45rem;
}
.card-head h3 { margin: 0; font-size: 1.15rem; }
/* The operator name is the largest target on the card — it has to read as a
   link, not as a heading that happens to be clickable. */
.card-head h3 a {
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
  /* Vertical padding on an INLINE element grows the hit area to 44px without
     touching the line box — the target gets bigger, the layout does not move. */
  padding-block: .6rem;
}
.card-head h3 a:hover { text-decoration-thickness: 2px; }
.badge-row { margin: .2rem 0 0; display: flex; flex-wrap: wrap; gap: .35rem; }
.card-meta { margin: 0; color: var(--muted); font-size: var(--fs-sm); }
.card-services { margin: 0; display: flex; flex-wrap: wrap; gap: .3rem; }
.chip {
  font-size: var(--fs-xs);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  padding: .05rem .55rem;
  color: var(--muted);
  background: var(--panel);
}
.card-contact { margin-top: .2rem; }
.card-more { margin: 0; font-size: var(--fs-sm); }
.card-more a { display: inline-flex; align-items: center; min-height: var(--tap); }

/* ---------- badges ---------- */
.badge {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 600;
  padding: .1rem .5rem;
  border-radius: var(--r-sm);
  border: 1.5px solid currentColor;
}
.badge-verified { color: var(--ok); }
.badge-stale { color: var(--warn); border-style: dashed; }
.badge-sample { color: var(--danger); text-transform: uppercase; letter-spacing: .04em; }
/* A dashed 1.4:1 border was not a perceivable state change — stale cards get a
   real rule in the warn colour instead. */
.card-stale { border-left: 5px solid var(--warn); }

/* ---------- phones ---------- */
.phone, .phone-big {
  display: inline-block;
  font-weight: 700;
  text-decoration: none;
  border: 1.5px solid var(--accent);
  border-radius: var(--r-md);
  padding: .55rem .8rem;
  color: var(--accent);
}
.phone-big { font-size: 1.35rem; padding: .6rem 1rem; }
.phone-label { display: block; font-size: var(--fs-xs); color: var(--muted); margin-top: .25rem; }
/* Whether there is a 24-hour dispatch line is the most decision-relevant field
   on a card, and a dashed border alone did not survive a scan of six cards.
   A filled pill reads as "someone answers at 0300"; outlined reads as "office". */
.phone:not(.phone-office-only),
.phone-big:not(.phone-office-only) { background: var(--accent); color: #fff; }
.phone-office-only { border-style: dashed; }

/* ---------- zero state ---------- */
.zero-state {
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--r-md);
  padding: 1.25rem;
  margin-top: 1rem;
}
.zero-state > p:first-child { margin-top: 0; }
.zero-suggest { list-style: none; margin: .5rem 0; padding: 0; display: grid; gap: .4rem; justify-items: start; }
.zero-suggest button {
  background: transparent; color: var(--accent);
  text-align: left; font-size: var(--fs-sm);
}

/* ---------- detail pages ---------- */
.breadcrumb { margin: .5rem 0; font-size: var(--fs-sm); }
.breadcrumb a { display: inline-flex; align-items: center; min-height: var(--tap); }
.listing h1 { margin: .2rem 0 .3rem; font-size: 1.6rem; }
.dba { font-size: 1rem; font-weight: 400; color: var(--muted); }
.stale-note { color: var(--warn); font-weight: 600; max-width: 44rem; }

.sample-banner {
  border: 2px solid var(--danger);
  color: var(--danger);
  border-radius: var(--r-md);
  padding: .7rem 1rem;
  margin: 1rem 0 0;
  font-size: var(--fs-sm);
}

.contact-block {
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: var(--r-md);
  padding: 1rem;
  margin: 1rem 0;
}
.contact-rest { margin: .8rem 0 0; }
.contact-rest dd a { padding-block: .75rem; }  /* one value per row — safe to pad */

.facts, .contact-rest { display: grid; gap: .35rem; }
.row { display: grid; grid-template-columns: 10rem 1fr; gap: .5rem; }
@media (max-width: 540px) { .row { grid-template-columns: 1fr; gap: 0; } }
.row dt { font-weight: 600; color: var(--muted); }
.row dd { margin: 0; }

.listing h2 { font-size: 1.1rem; margin: 1.3rem 0 .4rem; }
.svc-list, .qual-list { margin: 0; padding-left: 1.2rem; }
.svc-list li, .qual-list li { margin: .15rem 0; }
.pdf-link { margin: 1rem 0 0; }
.pdf-link a { display: inline-flex; align-items: center; min-height: var(--tap); }

.listing-foot { border-top: 1px solid var(--line); margin-top: 1.5rem; padding-top: .9rem; }
.verify-line { font-size: var(--fs-sm); color: var(--muted); max-width: 46rem; }
.listing-actions { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
.listing-actions a { display: inline-flex; align-items: center; min-height: var(--tap); }

/* ---------- prose pages / forms ---------- */
.prose { max-width: 44rem; }
.prose h1 { margin: 1.2rem 0 .5rem; font-size: 1.6rem; }
.prose h2 { font-size: 1.15rem; margin: 1.4rem 0 .4rem; }
.coverage-list { margin: 0; padding-left: 1.2rem; }
.coverage-list li { margin: .15rem 0; }
.coverage-note { font-size: var(--fs-sm); color: var(--muted); margin: .5rem 0 0; }

.std-form { display: grid; gap: .8rem; margin: 1.2rem 0; }
.std-form label { display: grid; gap: .25rem; font-weight: 600; font-size: var(--fs-sm); }
.std-form input, .std-form textarea {
  font: inherit;
  font-weight: 400;
  padding: .5rem .6rem;
  border: 1px solid var(--ctl-border);
  border-radius: var(--r-sm);
  background: #fff;
}
.std-form .checkbox-grid label { display: inline-flex; font-weight: 400; }
.form-notice {
  border: 1.5px solid var(--warn);
  color: var(--warn);
  border-radius: var(--r-md);
  padding: .6rem .9rem;
  margin: 1rem 0;
  font-size: var(--fs-sm);
}
.std-form fieldset { border: 1px solid var(--line); border-radius: var(--r-md); padding: .6rem .9rem .9rem; }
.std-form legend { font-weight: 600; font-size: var(--fs-sm); padding: 0 .3rem; }
.form-pair { display: grid; gap: .8rem; }
@media (min-width: 540px) { .form-pair { grid-template-columns: 1fr 1fr; } }
.form-fallback { color: var(--muted); }

/* ---------- print ---------- */
.print-source { display: none; }

@media print {
  .site-head, .site-foot, .filter-bar, .coverage-strip, .breadcrumb, .listing-actions,
  .card-more, .zero-state, #filter-bar, .filter-status, .sub, .skip-link { display: none !important; }
  body { background: #fff; color: #000; }
  .card, .contact-block { background: #fff; }
  a { color: #000; }
  .card-head h3 a { text-decoration: none; }
  .listing a[href^="http"]::after,
  .listing a[href^="mailto"]::after { content: " (" attr(href) ")"; font-size: .85em; }
  .listing a[href^="tel"] { text-decoration: none; border: none; padding: 0; }
  .phone-big, .phone { border: none; padding: 0; color: #000; background: transparent; }
  .sticky-bar, .applied-bar { display: none !important; }
  .listing section, .contact-block { break-inside: avoid; }
  .sample-banner { border-width: 3px; }
  .card-stale { border-left-width: 5px; }
  .print-source {
    display: block;
    margin-top: 1.5rem;
    padding-top: .5rem;
    border-top: 1px solid #000;
    font-size: .8rem;
  }
}
