/* Town Guide - readability first. 19px base, high contrast, 48px+ targets. */
:root{
  /* Soft page, white cards. Pure white at full brightness causes halation - text
     appears to shimmer - which is worse with astigmatism and cataracts. This keeps
     contrast far above AAA while cutting the glare, and lets cards lift off the page. */
  --bg:#edeff4; --surface:#ffffff; --surface-2:#e2e5ec;
  --ink:#14181f; --ink-soft:#3d4654; --line:#cfd5e0; --line-strong:#848da0;
  --accent:#1d4ed8; --accent-ink:#fff;
  --ok:#14682f; --warn:#8a4b00; --danger:#a3121f;
  --radius:12px; --wrap:1180px;
}
:root[data-accent="blue"]{--bg:#dbe6f8;--surface:#f7fafe;--surface-2:#c3d6f2}
:root[data-accent="green"]{--bg:#daeee2;--surface:#f6fdf8;--surface-2:#bfe2cd}
:root[data-accent="maroon"]{--bg:#f6dde3;--surface:#fff9fa;--surface-2:#eec2cc}
:root[data-accent="purple"]{--bg:#e4daf6;--surface:#faf8ff;--surface-2:#cfbdee}
:root[data-accent="slate"]{--bg:#dee4ec;--surface:#f9fafc;--surface-2:#c8d1dd}

:root[data-theme="dark"]{
  --bg:#10141c; --surface:#1a212d; --surface-2:#242e3d;
  --ink:#f2f5fa; --ink-soft:#c3ccdb; --line:#3d4859; --line-strong:#6b7a91;
  --ok:#7ed99a; --warn:#f0b357; --danger:#ff9aa5;
}
@media (prefers-color-scheme:dark){
  :root[data-theme="system"]{
    --bg:#10141c; --surface:#1a212d; --surface-2:#242e3d;
    --ink:#f2f5fa; --ink-soft:#c3ccdb; --line:#3d4859; --line-strong:#6b7a91;
    --ok:#7ed99a; --warn:#f0b357; --danger:#ff9aa5;
  }
}
:root[data-accent="blue"][data-theme="dark"]{--bg:#0d1421;--surface:#162034;--surface-2:#22314f}
@media (prefers-color-scheme:dark){:root[data-accent="blue"][data-theme="system"]{--bg:#0d1421;--surface:#162034;--surface-2:#22314f}}
:root[data-accent="green"][data-theme="dark"]{--bg:#0b1710;--surface:#14241a;--surface-2:#1e3628}
@media (prefers-color-scheme:dark){:root[data-accent="green"][data-theme="system"]{--bg:#0b1710;--surface:#14241a;--surface-2:#1e3628}}
:root[data-accent="maroon"][data-theme="dark"]{--bg:#1a0d12;--surface:#28161e;--surface-2:#3b202b}
@media (prefers-color-scheme:dark){:root[data-accent="maroon"][data-theme="system"]{--bg:#1a0d12;--surface:#28161e;--surface-2:#3b202b}}
:root[data-accent="purple"][data-theme="dark"]{--bg:#130e21;--surface:#1e1732;--surface-2:#2c2049}
@media (prefers-color-scheme:dark){:root[data-accent="purple"][data-theme="system"]{--bg:#130e21;--surface:#1e1732;--surface-2:#2c2049}}
:root[data-accent="slate"][data-theme="dark"]{--bg:#0f1318;--surface:#191f27;--surface-2:#252d38}
@media (prefers-color-scheme:dark){:root[data-accent="slate"][data-theme="system"]{--bg:#0f1318;--surface:#191f27;--surface-2:#252d38}}
:root[data-accent="blue"]{--accent:#1d4ed8;--accent-ink:#fff}
:root[data-accent="blue"][data-theme="dark"]{--accent:#93b4ff;--accent-ink:#0b1020}
@media (prefers-color-scheme:dark){:root[data-accent="blue"][data-theme="system"]{--accent:#93b4ff;--accent-ink:#0b1020}}
:root[data-accent="green"]{--accent:#14682f;--accent-ink:#fff}
:root[data-accent="green"][data-theme="dark"]{--accent:#7ed99a;--accent-ink:#0b1020}
@media (prefers-color-scheme:dark){:root[data-accent="green"][data-theme="system"]{--accent:#7ed99a;--accent-ink:#0b1020}}
:root[data-accent="maroon"]{--accent:#9b1c31;--accent-ink:#fff}
:root[data-accent="maroon"][data-theme="dark"]{--accent:#f79db0;--accent-ink:#0b1020}
@media (prefers-color-scheme:dark){:root[data-accent="maroon"][data-theme="system"]{--accent:#f79db0;--accent-ink:#0b1020}}
:root[data-accent="purple"]{--accent:#5b21b6;--accent-ink:#fff}
:root[data-accent="purple"][data-theme="dark"]{--accent:#c9aaff;--accent-ink:#0b1020}
@media (prefers-color-scheme:dark){:root[data-accent="purple"][data-theme="system"]{--accent:#c9aaff;--accent-ink:#0b1020}}
:root[data-accent="slate"]{--accent:#334155;--accent-ink:#fff}
:root[data-accent="slate"][data-theme="dark"]{--accent:#cbd5e1;--accent-ink:#0b1020}
@media (prefers-color-scheme:dark){:root[data-accent="slate"][data-theme="system"]{--accent:#cbd5e1;--accent-ink:#0b1020}}

/* Maximum-contrast mode for low vision: pure black on white, heavy borders, and a
   link colour dark enough to stay legible. Declared after the accent rules so it wins
   regardless of which highlight colour is selected. */
:root[data-theme="contrast"]{
  --bg:#ffffff; --surface:#ffffff; --surface-2:#ffffff;
  --ink:#000000; --ink-soft:#000000; --line:#000000; --line-strong:#000000;
  --accent:#0000bb; --accent-ink:#ffffff;
  --ok:#005500; --warn:#5a3200; --danger:#a00000;
}
:root[data-theme="contrast"] .card,
:root[data-theme="contrast"] .catbtn,
:root[data-theme="contrast"] .status-list a,
:root[data-theme="contrast"] .res-group{border-width:3px}
:root[data-theme="contrast"] .section-note,
:root[data-theme="contrast"] .sl-note,
:root[data-theme="contrast"] .card-addr{color:#000000}

html{font-size:19px; -webkit-text-size-adjust:100%}
html[data-text="smaller"]{font-size:16px}
html[data-text="larger"]{font-size:23px}

/* The hidden attribute is only a user-agent rule, and ANY author rule beats it.
   Without this, .settings{display:flex} and .btn{display:inline-flex} make elements
   marked [hidden] visible anyway. This must stay. */
[hidden]{display:none!important}

*,*::before,*::after{box-sizing:border-box}
body{margin:0; background:var(--bg); color:var(--ink);
  font-family:'Segoe UI',system-ui,-apple-system,Roboto,Helvetica,Arial,sans-serif;
  line-height:1.65; font-size:1rem;
  /* Inherited by everything. Content comes from a web admin, so a pasted URL or a
     run-together list like "2nd,3rd,9th" - which a browser treats as one 40-character
     word, because it will not break after a comma with no space - would otherwise push
     straight out through the side of its card. */
  overflow-wrap:break-word}
.wrap{width:100%; max-width:var(--wrap); margin:0 auto; padding:0 1rem}
main{display:block; padding-bottom:3rem}
img{max-width:100%; height:auto}

h1,h2,h3{line-height:1.25; margin:0 0 .5rem}
h1{font-size:1.9rem} h2{font-size:1.5rem; margin-top:0} h3{font-size:1.15rem}
p{margin:0 0 .75rem}
/* Opt-in reading measure. This used to be on every <p>, which meant short notes,
   taglines and button rows all stopped short of the margin for no reason. */
.prose{max-width:72ch}

a{color:var(--accent); text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:2px}
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{
  outline:4px solid var(--accent); outline-offset:2px; border-radius:4px}
.skip{position:absolute; left:-9999px; top:0; background:var(--accent); color:var(--accent-ink); padding:1rem; z-index:100}
.skip:focus{left:0}

.site-head{background:var(--surface); border-bottom:3px solid var(--line);
  border-top:5px solid var(--accent)}
.head-inner{display:flex; flex-wrap:wrap; gap:.6rem 1rem; align-items:center;
  justify-content:space-between; padding:.6rem 1rem}
.brand{text-decoration:none; color:var(--ink); display:block; margin:0; flex:0 0 auto}
.brand-name{display:block; font-size:1.1rem; font-weight:800; line-height:1.15; margin:0}
@media (min-width:520px){.brand-name{font-size:1.2rem}}
.brand-sub{display:block; font-size:.8rem; color:var(--ink-soft); font-weight:600; line-height:1.3}
.brand-loc{text-transform:uppercase; letter-spacing:.05em}
.brand-tag{display:block}
/* The separator is a real character in the markup, not a ::before. As generated CSS content
   it was invisible to copy-and-paste and to reader modes, so the two lines came out welded
   together - "Ione, CaliforniaEverything Ione". It is hidden, not removed, on a phone,
   where the tagline sits on its own line and needs no separator. */
.brand-sep{display:none}
@media (min-width:700px){
  .brand-tag{display:inline; text-transform:none; letter-spacing:0}
  .brand-sep{display:inline}
}
@media (min-width:700px){.brand-name{font-size:1.4rem}}

/* search lives in the header on every page */
.headsearch{flex:1 1 0; order:2; display:flex; gap:.45rem; align-items:center; margin:0; min-width:0}

.headsearch label{position:absolute; left:-9999px}
.headsearch input{flex:1 1 auto; min-width:0; min-height:48px; padding:.3rem .75rem;
  font:inherit; font-size:1.02rem; color:var(--ink); background:var(--surface);
  border:3px solid var(--line-strong); border-radius:var(--radius)}
.headsearch input:focus{border-color:var(--accent)}
.headsearch button{min-height:48px; padding:.3rem .85rem}
.settings-toggle{flex:0 0 auto; min-height:48px; padding:.3rem .75rem; font-size:.92rem}

/* Only this row is pinned. The title block scrolls away, so search and navigation
   stay reachable without a tall sticky header swallowing a phone screen. */
.navbar{background:var(--surface-2); border-top:1px solid var(--line);
  border-bottom:2px solid var(--line-strong); position:sticky; top:0; z-index:50}
.navrow{display:flex; flex-wrap:wrap; gap:.45rem; align-items:center; padding:.45rem .75rem}
/* Phone: the sticky bar is ONE compact row - Menu plus search - and the links drop
   below it as a full-width list at full size. Laid out as a grid of six cells it was
   ~100px of every screen, with short labels floating in wide cells. Descendant selectors
   on purpose - .navlinks .navlink outranks the plain .navlink further down the file
   whatever order these two end up in. */
/* narrow, not short - the 48px tap height stays, only the width comes down so the
   search box can share the row instead of being pushed onto one of its own */
.btn.menu-toggle{flex:0 0 auto; order:1; min-height:48px; padding:.3rem .45rem; gap:.3rem; font-size:.85rem}
.menu-icon{font-size:.95rem; line-height:1}
.headsearch input{font-size:1rem; padding:.3rem .55rem}
.headsearch button{padding:.3rem .6rem; font-size:.9rem}
.navlinks{display:none; order:3; flex:1 1 100%; flex-direction:column; gap:0; margin-top:.5rem;
  max-height:min(62vh, 430px); overflow-y:auto}
.navrow.nav-open .navlinks{display:flex}
.navlinks .navlink{width:100%; justify-content:flex-start; text-align:left; min-height:52px;
  padding:.55rem .9rem; font-size:1.02rem; border:0; border-top:2px solid var(--line); border-radius:0}
.navlinks .navlink:first-child{border-top:0}
/* the last row is a button, so it needs the styling a link gets for free */
button.navlink{background:none; font:inherit; font-weight:700; color:var(--ink); cursor:pointer}
@media (min-width:700px){
  .btn.menu-toggle{display:none}       /* everything fits inline, nothing to open */
  .navlinks{display:flex; flex-direction:row; flex-wrap:wrap; gap:.4rem; order:0;
    max-height:none; overflow:visible; margin-top:0}
  .navlinks .navlink{width:auto; min-height:48px; padding:.3rem .7rem; font-size:1rem;
    border:2px solid transparent; border-radius:var(--radius)}
  .headsearch{flex:1 1 100%; order:0; gap:.5rem}
  .headsearch input{font-size:1.02rem; padding:.3rem .75rem}
  .headsearch button{padding:.3rem .85rem; font-size:1rem}
  .navrow{gap:.45rem .9rem; padding:.45rem 1rem}
  .navlink.nav-settings{display:none}  /* the header carries it on a wide screen */
}
/* Must come AFTER the .navlinks rule above: a media query carries no extra specificity,
   so if this sat earlier in the file the base flex:1 1 auto would win, the link group
   would stretch, and the search box would be shoved to the far right. */
@media (min-width:900px){
  .navrow{flex-wrap:nowrap}
  .navlinks{flex:0 1 auto}
  .headsearch{flex:1 1 230px; min-width:230px}
}
.navlink{display:flex; align-items:center; min-height:48px; padding:.3rem .7rem; font-weight:700;
  text-decoration:none; color:var(--ink); border-radius:var(--radius); border:2px solid transparent;
  justify-content:center; text-align:center}
@media (hover:hover){.navlink:hover{background:var(--bg)}}
.navlink.is-active{background:var(--accent); color:var(--accent-ink); border-color:var(--accent)}

.settings-close{margin-left:auto; align-self:flex-start; min-height:44px; padding:.2rem .8rem;
  cursor:pointer; font:inherit; font-size:.88rem; font-weight:700; color:var(--ink);
  background:var(--surface); border:2px solid var(--line-strong); border-radius:var(--radius)}
@media (hover:hover){.settings-close:hover{border-color:var(--accent)}}
.settings{position:relative; display:flex; flex-wrap:wrap; gap:1.25rem; padding:1rem; margin-bottom:1rem;
  background:var(--bg); border:2px solid var(--line); border-radius:var(--radius)}
.setting{display:flex; flex-direction:column; gap:.4rem; align-items:flex-start}
.setting-label{font-weight:700; font-size:.95rem}
.seg{display:flex; flex-wrap:wrap; gap:.4rem}
/* All of these sit on one row, so they must share a height. The larger "A" inside the
   text-size buttons was making those taller than the Light/Dark ones; line-height:1 on
   the glyph stops it pushing the box open. */
.seg-btn{display:inline-flex; align-items:center; justify-content:center; gap:.4rem;
  min-height:52px; min-width:56px; padding:.4rem 1rem; cursor:pointer; font:inherit; font-weight:700;
  color:var(--ink); background:var(--surface); border:2px solid var(--line-strong); border-radius:var(--radius)}
.seg-btn[aria-pressed="true"]{background:var(--accent); color:var(--accent-ink); border-color:var(--accent)}
.seg-a{font-weight:900; line-height:1}
.seg-sm .seg-a{font-size:.85rem}
.seg-lg .seg-a{font-size:1.25rem}
.a-icon{font-weight:900; line-height:1; letter-spacing:-.02em}
.a-icon .a-sm{font-size:.72em}
.lbl-long{display:none}
@media (min-width:900px){.lbl-long{display:inline} .lbl-short{display:none}}
/* On a phone the header toggle ate a chunk of the top bar for a control pinch-to-zoom
   already covers. It is hidden there, but the panel also holds dark mode and the colour
   schemes, so the footer carries a way in rather than losing them on mobile. */
/* Compound .btn.x selectors on purpose. As plain .head-settings these lost to the later
   .btn{display:inline-flex} on equal specificity and the button stayed visible on phones.
   Tying them to .btn outranks it wherever either rule ends up in the file. */
.btn.head-settings{display:none}
@media (min-width:700px){.btn.head-settings{display:inline-flex}}
.color-btn{display:flex; align-items:center; gap:.5rem}
.dot{width:1.1rem; height:1.1rem; border-radius:50%; border:2px solid var(--line); display:inline-block}

.btn{display:inline-flex; align-items:center; justify-content:center; gap:.6rem; min-height:52px;
  padding:.6rem 1.25rem; cursor:pointer; font:inherit; font-weight:700; text-decoration:none; text-align:center;
  border-radius:var(--radius); border:2px solid var(--accent); background:var(--accent); color:var(--accent-ink)}
.btn-ghost{background:var(--surface); color:var(--ink); border-color:var(--line-strong)}
.card .btn-call{padding:.6rem .4rem; gap:.45rem}
.btn-call{width:100%; justify-content:center; margin:.5rem 0; background:var(--bg); color:var(--ink); border:3px solid var(--accent)}
.call-icon{width:1.5rem; height:1.5rem; flex:0 0 auto; color:var(--accent)}
.call-text{display:flex; align-items:center; line-height:1.2}
/* A number written "(209) 245-8899" has a space in it and was breaking onto a second
   line inside the button on narrow cards; "209-245-8899" has none, which is why only
   some listings showed it. Never break a phone number. */
/* No blanket nowrap. The digits are held together by non-breaking spaces instead, which
   leaves a trailing "ext. 110" free to wrap rather than pushing out of the button. */
/* No blanket nowrap: it held "(209) 245-8899" together but also stopped
   "209-274-2412 ext. 110" breaking anywhere, so it ran out of its button. Digits are
   joined with non-breaking spaces instead, and the size is capped the way cards do it -
   at the Larger setting 1.2rem is 27.6px, which no phone-width button can hold. */
.call-num{font-size:min(1.2rem, 5.1vw); font-weight:800; color:var(--accent)}
/* At the Larger setting a 14-digit number needs 232px inside a button with only 205px to
   give, once the emergency page puts three cards across a desktop. The vw cap above does
   not help there - the screen is wide, the column is not. So at that setting the phone
   icon goes, the padding tightens, and the number stops growing: it is already bold and
   in the accent colour, and legibility was never the thing that was short. */
html[data-text="larger"] .call-icon{display:none}
html[data-text="larger"] .btn-call{padding-left:.6rem; padding-right:.6rem; gap:0}
html[data-text="larger"] .call-num{font-size:1rem}

.hero{padding-top:1.75rem; padding-bottom:1.25rem}
/* A live summary, not a link list - the outage links live on their own page, and
   repeating them here was three copies of the same thing on one screen. */
/* Same tinted band as the nav above it. The separation comes from the nav's darker
   bottom border rather than a different colour - making this near-white read as blank. */
.statusstrip{background:var(--surface-2); border-bottom:3px solid var(--line-strong)}
.statusstrip.has-alerts{border-bottom-width:0}
/* Centred: the bar holds three or four short live facts, so left-aligning them left a
   long empty stretch under a full-width nav. Centring keeps it balanced however many
   items are present - a failed feed hides its item and the rest stay centred. */
/* Phone: date, then the three weather facts together, then alerts. The three are wrapped
   in .strip-group so they hold one line as a unit and wrap among themselves if the air
   quality label runs long ("Unhealthy for sensitive groups"), instead of overflowing. */
.wx-stamp{color:var(--ink-soft); font-weight:600; margin:.6rem 0 0}
/* Jump links across the top of the outages page. scroll-margin-top is the important bit:
   the nav bar is sticky, so without it an anchor lands with its heading tucked underneath. */
.jumpbar{display:flex; flex-wrap:wrap; justify-content:center; gap:.5rem; margin:1rem 0 0}
.jumpbar .btn{min-height:48px; padding:.4rem .9rem; font-size:.95rem}
.section[id]{scroll-margin-top:5rem}
/* no heading above it any more, so the temperature sits up against the rule
   instead of leaving a gap where the words used to be */
.wx-current{padding-top:.8rem}
.wx-now{text-align:center}
.wx-temp{font-size:3.2rem; font-weight:800; line-height:1; margin:0 0 .2rem; color:var(--accent)}
.wx-desc{font-size:1.25rem; font-weight:700; margin:0 0 .6rem}
.wx-facts{list-style:none; display:flex; flex-wrap:wrap; justify-content:center; gap:.4rem .9rem;
  margin:0; padding:0; font-weight:600; color:var(--ink-soft)}
.wx-list{list-style:none; margin:0; padding:0}
/* Phone: two rows. Day name and high/low share the first row (the temperature is what
   people look for, so it sits at the right edge every time); the description and any
   rain chance go underneath. Putting the temperature beside the description instead left
   it wherever the description happened to end - a different spot on every row. */
.wx-day{display:grid; grid-template-columns:minmax(0,1fr) auto; grid-template-areas:"name temp" "desc rain";
  gap:.1rem .8rem; align-items:baseline; padding:.7rem .2rem; border-bottom:2px solid var(--line)}
.wx-day:last-child{border-bottom:0}
.wx-dayname{grid-area:name; font-weight:800; font-size:1.05rem}
.wx-daytemp{grid-area:temp; font-weight:800; font-size:1.05rem; text-align:right; white-space:nowrap;
  font-variant-numeric:tabular-nums}
.wx-low{color:var(--ink-soft); font-weight:700}
.wx-daydesc{grid-area:desc; color:var(--ink-soft); font-weight:600}
.wx-rain{grid-area:rain; color:var(--ink-soft); font-weight:600; text-align:right; white-space:nowrap}
.wx-rain:empty{display:none}
@media (min-width:560px){
  .wx-day{grid-template-columns:8rem minmax(0,1fr) auto auto; grid-template-areas:"name desc temp rain"; padding:.6rem .2rem}
  .wx-rain{min-width:6rem}
  .wx-rain:empty{display:block}
}
.striprow{display:flex; flex-direction:column; align-items:center;
  gap:.35rem; padding:.5rem 1rem; font-size:clamp(.78rem, 4.3vw, .95rem)}
.strip-group{display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:.2rem .35rem}
.strip-item{font-weight:700; text-align:center}
.strip-item:empty{display:none}
/* separators only between the grouped facts; a dangling one at the start of a line reads
   as a typo, which is what the old per-id overrides were working around */
.strip-group > .strip-item + .strip-item::before{content:"·"; color:var(--ink-soft);
  margin-right:.35rem; font-weight:400}
@media (min-width:700px){
  .striprow{flex-direction:row; flex-wrap:wrap; justify-content:center; gap:.35rem .9rem}
  .strip-group{gap:.35rem .9rem}
  .striprow > * + *::before,
  .strip-group > .strip-item + .strip-item::before{content:"·"; color:var(--ink-soft);
    margin-right:.9rem; font-weight:400}
}
/* "Air quality:" is the longest label on the line; phones get "Air:" so all three
   facts fit at a readable size. Screen readers still hear the full wording. */
.aq-long{display:none}
@media (min-width:700px){.aq-long{display:inline}}
.aq-good{color:var(--ok)} .aq-mod{color:var(--warn)} .aq-bad{color:var(--danger)}

/* Notice bar. Normally nothing is here; entries expire by date so it empties itself. */
.bulletins:empty{display:none}
.bulletin{border-bottom:3px solid var(--line-strong); background:var(--surface-2)}
.bulletin-inner{display:flex; flex-wrap:wrap; gap:.5rem .85rem; align-items:center; padding:.6rem 1rem}
.bulletin p{margin:0; max-width:80ch; font-size:1rem}
.bulletin-msg{flex:1 1 240px}
.bulletin-btn{flex:0 0 auto; min-height:38px; padding:.2rem .75rem; cursor:pointer;
  font:inherit; font-size:.85rem; font-weight:700; color:inherit;
  background:transparent; border:2px solid currentColor; border-radius:999px; opacity:.85}
@media (hover:hover){.bulletin-btn:hover{opacity:1}}
.bulletin-detail{padding:0 1rem .8rem}
/* Both states reserve the same width. "View" and "Hide" are the same letter count but
   not the same pixel width (w is wider than e), so without this the button - and every
   strip item after it - shifted a few pixels on each press. tabular-nums keeps the
   count from nudging it too. The quiet state matches so nothing jumps on load. */
.strip-quiet{display:inline-flex; align-items:center; justify-content:center;
  min-width:13.5ch; color:var(--ink-soft); font-weight:600}
.strip-alert-btn{display:inline-flex; align-items:center; justify-content:center;
  min-width:13.5ch; min-height:34px; padding:.1rem .7rem; cursor:pointer; font:inherit;
  font-size:.88rem; font-weight:800; font-variant-numeric:tabular-nums;
  color:var(--accent); background:var(--surface);
  border:2px solid var(--accent); border-radius:999px}
@media (hover:hover){.strip-alert-btn:hover{background:var(--accent); color:var(--accent-ink)}}
.alerts-collapsed .bulletin-optional{display:none}
.bulletin-detail p{font-size:.95rem}
.bulletin-tag{flex:0 0 auto; font-size:.74rem; font-weight:800; text-transform:uppercase;
  letter-spacing:.06em; padding:.2rem .55rem; border-radius:999px; white-space:nowrap}
.bulletin-info{background:var(--surface-2); border-bottom-color:var(--line-strong)}
.bulletin-info .bulletin-tag{background:var(--line); color:var(--ink)}
.bulletin-warning{background:#fdf3e0; border-bottom-color:#a5690a}
.bulletin-warning .bulletin-tag{background:#a5690a; color:#fff}
.bulletin-warning p{color:#4a2f00}
.bulletin-urgent{background:#fdeaec; border-bottom-color:#a3121f}
.bulletin-urgent .bulletin-tag{background:#a3121f; color:#fff}
.bulletin-urgent p{color:#5c0009}
:root[data-theme="dark"] .bulletin-warning{background:#3a2a08; border-bottom-color:#f0b357}
:root[data-theme="dark"] .bulletin-warning p{color:#ffe6b8}
:root[data-theme="dark"] .bulletin-urgent{background:#3d0f14; border-bottom-color:#ff9aa5}
:root[data-theme="dark"] .bulletin-urgent p{color:#ffd9dd}
@media (prefers-color-scheme:dark){
  :root[data-theme="system"] .bulletin-warning{background:#3a2a08; border-bottom-color:#f0b357}
  :root[data-theme="system"] .bulletin-warning p{color:#ffe6b8}
  :root[data-theme="system"] .bulletin-urgent{background:#3d0f14; border-bottom-color:#ff9aa5}
  :root[data-theme="system"] .bulletin-urgent p{color:#ffd9dd}
}

.hero-compact{padding-top:1rem; padding-bottom:.2rem}
.dir-title{margin:0; font-size:1.5rem}
.savebox{background:var(--surface); border:3px solid var(--accent); border-radius:var(--radius); padding:1.1rem}
.savebox ol{margin:.5rem 0 0; padding-left:1.4rem}
.savebox li{margin-bottom:.4rem}
.savebox .key{display:inline-block; padding:.1rem .5rem; font-weight:800;
  background:var(--surface-2); border:2px solid var(--line); border-radius:6px}
.hero p.tagline{font-size:1.1rem; color:var(--ink-soft)}

/* margin-top sits ABOVE the divider line, padding-top below it. Keeping the margin
   small tucks the rule up close to whatever it is separating from. */
.section{padding-top:1.4rem; padding-bottom:1.4rem; border-top:3px solid var(--line); margin-top:.6rem}
.section-head{display:flex; flex-wrap:wrap; gap:.75rem; align-items:baseline; justify-content:space-between}
/* Exempt from the global p{max-width:70ch} reading cap. These are short notes, not
   prose, and the cap made them wrap early with dead space to the right. */
.section-note{color:var(--ink-soft); font-size:.95rem; max-width:none}

.grid{display:grid; gap:1rem; grid-template-columns:minmax(0,1fr)}
@media (min-width:640px){.grid-2,.grid-3{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1000px){.grid-3{grid-template-columns:repeat(3,minmax(0,1fr))}}

/* Cards stretch to the tallest in their row (grid default), so making each one a
   column and pushing the footer down with margin-top:auto lines the phone buttons and
   "Hours & more" links up across the row however long the descriptions are. */
.card{min-width:0; background:var(--surface); border:2px solid var(--line); border-radius:var(--radius);
  padding:1.1rem; display:flex; flex-direction:column; height:100%}
.card-foot{margin-top:auto; padding-top:.6rem}
/* Image above the name, both centred - the logo sitting to the left while the name
   centred in the leftover space read as lopsided, and everything below it (badge,
   address, phone, link) was centred already. */
.card-head{display:flex; flex-direction:column; align-items:center; gap:.5rem}
.card-logo{flex:0 0 auto; width:84px; height:84px; object-fit:contain; background:var(--surface);
  border:2px solid var(--line); border-radius:10px; padding:5px}
/* Two lines reserved for the name, then the badge on its own centred line. A one-line
   name leaves a gap above the badge, a two-line name butts against it - either way every
   badge sits at the same height across a row, and it doubles as a divider between the
   name and the description. The category is gone; these cards sit under a heading. */
.card-head-text{width:100%}
.card-title{font-size:1.2rem; margin-bottom:0; min-height:2.5em; text-align:center}
.card-status{text-align:center; min-height:1.9rem; margin:.1rem 0 .6rem}
.card-status .badge-open,.card-status .badge-closed{margin-left:0}
.card-title a{color:var(--ink)}
/* Exactly four lines, ellipsis added by the browser. Counting characters could not do
   this: word lengths vary, and the bold venue lead-in is part of the same paragraph. */
.card-desc{font-size:.98rem; text-align:left; display:-webkit-box; -webkit-line-clamp:4; line-clamp:4;
  -webkit-box-orient:vertical; overflow:hidden} /* Sits inside the pinned footer, so address, phone and the link form one block at the
   bottom of every card - separate from the description, and level across a row. */
.card-addr{font-size:.95rem; color:var(--ink-soft); text-align:center; margin:0 0 .4rem}
/* One fixed phrase, centred, so card heights do not vary with the length of a business
   name. The name stays in the link for screen readers - 29 identical "Hours & More Info"
   links would be unusable read out of context. */
.card-more{margin-bottom:0; font-size:.95rem; text-align:center}
.card.is-hidden{display:none}

.cat-icon{font-size:1.9rem; line-height:1}

.open-flag{margin-left:.5rem; font-weight:800}
.is-open{color:var(--ok)} .is-closed{color:var(--ink-soft)}

.switch{display:flex; align-items:center; gap:.75rem; min-height:52px; cursor:pointer; font-weight:700; font-size:1.05rem}
.switch input{width:28px; height:28px; accent-color:var(--accent); cursor:pointer}

.res-group{background:var(--surface); border:2px solid var(--line); border-radius:var(--radius); padding:1.1rem}
.res-group h3{border-bottom:2px solid var(--line); padding-bottom:.4rem}
.res-item{padding:.6rem 0; border-bottom:1px solid var(--line)}
.res-item:last-child{border-bottom:0}
.res-label{font-weight:700; display:block}
.res-note{font-size:.9rem; color:var(--ink-soft); display:block}

.status-list{list-style:none; margin:0; padding:0; display:grid; gap:.6rem}
.status-list a{display:block; min-height:52px; padding:.7rem 1rem; font-weight:700; text-decoration:none;
  background:var(--surface); border:2px solid var(--line-strong); border-radius:var(--radius); color:var(--ink)}
@media (hover:hover){.status-list a:hover{border-color:var(--accent)}}
.status-list .sl-note{display:block; font-weight:500; font-size:.9rem; color:var(--ink-soft)}

.event{display:flex; gap:1rem; align-items:flex-start; background:var(--surface);
  border:2px solid var(--line); border-radius:var(--radius); padding:1.1rem}
.event-date{flex:0 0 auto; text-align:center; min-width:74px; padding:.5rem;
  background:var(--accent); color:var(--accent-ink); border-radius:var(--radius)}
.event-mon{display:block; font-size:.85rem; font-weight:800; text-transform:uppercase; letter-spacing:.05em}
.event-day{display:block; font-size:1.7rem; font-weight:800; line-height:1}
.event-yr{display:block; font-size:.78rem; font-weight:700; opacity:.85}
.event-thru{display:block; font-size:.7rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; opacity:.8}
.event-also{margin:.15rem 0 0; font-size:.88rem; color:var(--ink-soft)}
.event-also-label{font-weight:700}
.event-also-d[hidden]{display:none}
.event-body h3{margin-bottom:.25rem}
.event-compact{padding:.7rem .9rem; align-items:stretch}
.event-compact .event-date{min-width:60px; padding:.35rem}
.event-compact .event-day{font-size:1.35rem}
.event-compact h3{font-size:1.05rem; margin:0}
.event-compact .event-meta{margin:.15rem 0 0}
.event-summary{margin:.25rem 0 0; font-size:.95rem}
/* The compact card used to be one big link, with an ::after overlay covering it. That
   overlay would have swallowed every click on these buttons and on the posters, so it
   went when Details started showing the whole event inline. */
.event-actions{margin:.45rem 0 0; position:relative; z-index:1}
/* Both buttons, not just Details. Close had no rule at all and fell back to the browser
   default, which is a fraction of the size and nowhere near a usable tap target. */
.event-details,.event-close{min-height:44px; padding:.35rem 1rem; font:inherit; font-size:.9rem; font-weight:700;
  cursor:pointer; color:var(--accent); background:var(--surface);
  border:2px solid var(--line-strong); border-radius:var(--radius)}
.event-full{position:relative; z-index:1; margin:.5rem 0 0; font-size:.95rem}
.event-full .event-credit,
.event-full .event-link,
.event-full .event-actions{text-align:center}
.event-full p{margin:0}
/* Positioned so the Details and Close buttons can sit above the card background. */
/* not cursor:pointer any more - the card itself is no longer a link, its buttons are */
.event-compact{position:relative}
@media (hover:hover){.event-compact:hover{border-color:var(--accent)}}
/* Pushed to the bottom of the card with margin-top:auto, so the cue sits on the same
   line across every card in a row however long each description is. */
.event-compact .event-body{display:flex; flex-direction:column; flex:1 1 auto}
.event-more{margin-top:auto; padding-top:.35rem; text-align:right; font-size:.9rem;
  font-weight:700; color:var(--accent); text-decoration:underline;
  text-underline-offset:3px; text-decoration-thickness:2px}
.event-meta{font-size:.95rem; color:var(--ink-soft); margin-bottom:.4rem}
/* Posters and the link form one block pinned to the bottom of the card and centred, so
   they line up across a row however long each description runs. */
.event{align-items:stretch}
.event-body{display:flex; flex-direction:column; flex:1 1 auto}
.event-foot{margin-top:auto; padding-top:.6rem; text-align:center}
.event-link{margin:.5rem 0 0}
.event-posters{display:flex; flex-wrap:wrap; justify-content:center; gap:.5rem; margin:0 0 .3rem}
.event-posters img{height:150px; width:auto; border-radius:8px; border:2px solid var(--line); display:block}
.event-posters a:focus-visible img{outline:4px solid var(--accent)}
.event-credit{font-size:.82rem; color:var(--ink-soft); margin:0}
/* Phone: the date runs along the top as a short "SEP 11" strip. Down the left it stretched
   to the full height of the card - a tall coloured band beside the text, holding 74px of
   width the card cannot spare. Side by side again as soon as there is room for it. */
.event{flex-direction:column; gap:.6rem}
.event-date{align-self:stretch; min-width:0; padding:.28rem .7rem}
.event-mon,.event-day,.event-yr{display:inline}
.event-mon::after{content:" "}
.event-day{font-size:1.25rem}
.event-compact .event-date{min-width:0}
.event-compact .event-day{font-size:1.15rem}
@media (min-width:560px){
  .event{flex-direction:row; gap:1rem}
  .event-date{align-self:auto; min-width:74px; padding:.5rem}
  .event-mon,.event-day,.event-yr{display:block}
  .event-mon::after{content:none}
  .event-day{font-size:1.7rem}
  .event-compact .event-date{min-width:60px; padding:.35rem}
  .event-compact .event-day{font-size:1.35rem}
}

.spotlight{background:var(--surface-2); border:3px solid var(--accent); border-radius:var(--radius); padding:1.25rem}
.spotlight .card{background:var(--surface); margin-bottom:0}
.spotlight #spotlight-next{display:flex; margin-left:auto; margin-right:auto}
.spotlight #spotlight-card{margin-bottom:1rem}

.detail-top{display:flex; gap:1.25rem; align-items:flex-start; flex-wrap:wrap}
/* Uses the SAME two-column grid as the content below, so the call button starts exactly
   where the Hours column starts rather than being shoved to the page edge. On a phone it
   collapses to one column and the button sits under the name, where it is wanted most. */
.detail-head{display:grid; gap:1rem; grid-template-columns:minmax(0,1fr); align-items:center}
@media (min-width:640px){.detail-head{grid-template-columns:repeat(2,minmax(0,1fr))}}
.detail-head h1{margin-bottom:.25rem}
.detail-head-call .btn-call{margin:0}
.detail-pic{margin:.25rem 0 1rem}
.detail-pic img{width:100%; max-width:460px; height:auto; border:2px solid var(--line);
  border-radius:var(--radius); display:block}
.verified-line{text-align:center; margin-top:1.25rem}
.detail-address{margin-top:.75rem}
.part-of{margin:.15rem 0 0; font-size:.95rem; color:var(--ink-soft)}
/* Listings with no fixed hours - seasonal tours, 24/7 emergency cover - show their note
   here instead of a table, so it needs to read as the answer, not as a stray sentence. */
.sun-today{display:block; font-size:.92rem; color:var(--ink-soft); font-weight:600; margin-top:.3rem}
.hours-varies{font-size:1.05rem; background:var(--surface-2); border-left:5px solid var(--accent);
  border-radius:8px; padding:.7rem .9rem; margin:0 0 .6rem; max-width:460px}
.detail-hours{border-collapse:collapse; width:100%; max-width:460px}
.detail-hours th,.detail-hours td{text-align:left; padding:.5rem .75rem; border-bottom:1px solid var(--line)}
.detail-hours th{font-weight:700}
.verified{display:inline-block; padding:.35rem .75rem; font-size:.9rem; font-weight:700;
  background:var(--surface-2); border:2px solid var(--line); border-radius:999px; color:var(--ink-soft)}
.backlink{display:inline-flex; align-items:center; min-height:48px; font-weight:700}
.photos{display:grid; gap:.75rem; grid-template-columns:minmax(0,1fr)}
@media (min-width:640px){.photos{grid-template-columns:repeat(2,minmax(0,1fr))}}
.photos img{border:2px solid var(--line); border-radius:var(--radius)}
.factlist{list-style:none; margin:0 0 1rem; padding:0}
.factlist li{padding:.45rem 0; border-bottom:1px solid var(--line)}
.factlist li:last-child{border-bottom:0}
.detail-groups{margin:.75rem 0 1rem}
.detail-group h3{font-size:.95rem; margin:.6rem 0 .3rem; color:var(--ink-soft)}
.chips{display:flex; flex-wrap:wrap; gap:.35rem; list-style:none; margin:0; padding:0}
.chips li{padding:.2rem .6rem; font-size:.9rem; background:var(--surface-2);
  border:2px solid var(--line); border-radius:999px}
.checklist{margin:0 0 1rem; padding-left:1.5rem; max-width:70ch}
.checklist li{margin-bottom:.85rem}

.form-field{margin-bottom:1.25rem; max-width:640px}
.form-field label{display:block; font-weight:700; margin-bottom:.35rem}
.form-field .hint{display:block; font-weight:500; font-size:.9rem; color:var(--ink-soft); margin-bottom:.35rem}
.form-field input,.form-field select,.form-field textarea{width:100%; min-height:52px; padding:.6rem .8rem;
  font:inherit; color:var(--ink); background:var(--surface); border:3px solid var(--line-strong); border-radius:var(--radius)}
.form-field textarea{min-height:150px; resize:vertical}
.form-field input:focus,.form-field select:focus,.form-field textarea:focus{border-color:var(--accent)}
.hp{position:absolute; left:-9999px}

/* No max-height and no object-fit: a cap meant cover cropped roughly a third of the
   picture away on a wide screen. It scales to whatever width it is given instead. */
.foot-banner{max-width:var(--wrap); margin:2rem auto 0; padding:0 1rem}
.foot-banner img{display:block; width:100%; height:auto;
  border-radius:var(--radius); border:2px solid var(--line)}
.site-foot{margin-top:1.25rem; background:var(--surface); border-top:3px solid var(--line); padding:1.35rem 0; margin-top:2rem}
/* The global p{max-width:70ch} keeps body prose readable, but footer lines are short
   labels, not prose - the cap made them wrap early and look broken on wide screens. */
.site-foot{text-align:center}
/* every footer child is centred on the same axis, whatever its type */
.site-foot p,.site-foot div{max-width:none; margin-left:auto; margin-right:auto}
.site-foot p{font-size:.98rem}
/* the how-to-save steps are a list, so they stay left-aligned inside a centred footer */
.save-help{text-align:left; margin:0 auto 1rem; max-width:48ch}
.updated{font-size:1.05rem}
/* stacked, not side by side - squeezed onto one line on a phone they were unreadable */
.save-line{display:flex; flex-direction:column; align-items:center; gap:.55rem;
  justify-content:center; margin:.85rem 0}
.foot-note{font-size:.9rem; color:var(--ink-soft); margin-bottom:.4rem}
.credit-by{font-size:.98rem; margin:0; padding-top:.75rem; border-top:2px solid var(--line)}
.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}
.empty{background:var(--surface); border:2px dashed var(--line); border-radius:var(--radius); padding:1.25rem; color:var(--ink-soft)}

/* ---- live status strip ---- */

/* ---- category bar (stays put, always) ---- */
.catbar{display:grid; gap:.5rem; grid-template-columns:minmax(0,1fr); margin:.4rem 0 .9rem}
@media (min-width:360px){.catbar{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:680px){.catbar{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (min-width:890px){.catbar{grid-template-columns:repeat(4,minmax(0,1fr))}}
/* six across returns above 1140px: without icons at the Larger setting the labels fit,
   and twelve categories fall into two even rows */
@media (min-width:1140px){.catbar{grid-template-columns:repeat(6,minmax(0,1fr))}}
.catbtn{display:flex; align-items:center; gap:.35rem; min-width:0; text-decoration:none; min-height:52px; padding:.4rem .35rem; cursor:pointer;
  font:inherit; font-weight:700; font-size:.98rem; text-align:left; color:var(--ink);
  background:var(--surface); border:3px solid var(--line-strong); border-radius:var(--radius)}
@media (hover:hover){.catbtn:hover{border-color:var(--accent)}
.event-details:hover,.event-close:hover{border-color:var(--accent)}}
/* "All businesses" is the last tile and stretches to finish the row, so the grid always
   comes out even however many categories there are. The spans are written by the build
   from the live category count (see catAllSpans). */
.catbtn-all{justify-content:center; text-decoration:none;
  background:var(--surface-2); border-color:var(--accent); color:var(--accent)}
@media (min-width:360px){.catbtn-all{grid-column:1 / -1}}
@media (min-width:680px){.catbtn-all{grid-column:2 / span 2}}
@media (min-width:890px){.catbtn-all{grid-column:1 / -1}}
@media (min-width:1140px){.catbtn-all{grid-column:3 / span 2}.catbar > .catbtn:nth-last-child(-n+3):not(.catbtn-all){order:1}}
/* Every :hover is behind (hover:hover). A tap on a touch screen leaves the hover state
   stuck on the element until something else is tapped, and this rule paints exactly what
   the selected state paints - so "Show all businesses" still looked selected after being
   switched off. The button was fine; the styling was lying about it. */
@media (hover:hover){.catbtn-all:hover{background:var(--accent); color:var(--accent-ink)}}
.cat-icon{width:1.2rem; height:1.2rem; flex:0 0 auto; color:var(--accent)}
.catbtn[aria-current="true"] .cat-icon{color:var(--accent-ink)}
.h2-icon{display:flex; align-items:center; gap:.6rem}
.cat-icon-h{width:1.5em; height:1.5em}
/* max-width:none matters here: this is a <p>, and the global p{max-width:70ch} reading
   cap would box it to ~665px, so justify-content:center would centre inside that box
   and land visibly left of the real centre. */

.section-centered{text-align:center; max-width:none}
.section-more{display:flex; justify-content:center; margin:1.25rem 0 0; max-width:none}
.dash-links{display:flex; flex-wrap:wrap; justify-content:center; gap:.6rem; margin:.5rem 0 0; max-width:none}
/* Selection swaps the category icon for a checkmark rather than inserting one before
   the label. Adding a tick made the button wider mid-click, which wrapped the longest
   labels onto a second line and shifted the whole grid. Same footprint either way. */
.cat-mark{position:relative; flex:0 0 auto; width:1.2rem; height:1.2rem}
.cat-mark .cat-icon,.cat-mark .cat-check{position:absolute; inset:0; width:100%; height:100%}
.cat-check{display:none; color:var(--accent-ink)}
.catbtn[aria-current="true"] .cat-icon{display:none}
.catbtn[aria-current="true"] .cat-check{display:block}
html[data-text="larger"] .cat-mark{display:none}
html[data-text="larger"] .cat-label{font-size:min(.98rem, 5.4vw)}
/* one-word labels like "Contractors" cannot wrap, so the label is what has to give when
   the column is narrow; full size wherever there is room for it */
.cat-label{min-width:0; font-size:min(.98rem, 4vw)}
.catbtn[aria-current="true"]{background:var(--accent); color:var(--accent-ink); border-color:var(--accent)}
.catbtn[aria-current="true"] .tick{display:inline}

/* ---- results region ---- */
.result-group{font-size:1.15rem; margin:1.25rem 0 .6rem; padding-bottom:.3rem;
  border-bottom:2px solid var(--line)}
.result-group:first-child{margin-top:0}
.results-head{display:flex; flex-wrap:wrap; gap:.6rem .9rem; align-items:center;
  justify-content:space-between; margin-bottom:.75rem}
/* its own line at every width, left-aligned under the heading - beside the heading it
   read as a stray control rather than something that acts on the list below */
.results-tools{display:flex; align-items:center; gap:.6rem; flex:1 1 100%;
  margin-left:0; justify-content:flex-start}
.results-head .switch{min-height:40px; font-size:.95rem}
.results-head h2{margin:0}
#results-clear{min-height:48px}

/* ---- open now: green only, never red ---- */
/* Both badges carry a 2px border so they are the same size; the open one just matches
   its own fill, so it reads as solid. Without it the bordered "Closed" badge was 4px
   bigger in every direction. */
.badge-open{display:inline-block; margin-left:.5rem; padding:.15rem .6rem; border-radius:999px;
  font-size:.82rem; font-weight:800; background:var(--ok); color:#fff; white-space:nowrap;
  border:2px solid var(--ok)}
:root[data-theme="dark"] .badge-open{color:#04210f}
@media (prefers-color-scheme:dark){:root[data-theme="system"] .badge-open{color:#04210f}}
/* A badge too, so it reads as a status rather than part of the business name when the
   two share a line. Deliberately neutral, not red - being closed at 9pm is normal. */
.badge-closed{display:inline-block; margin-left:.5rem; padding:.15rem .6rem; border-radius:999px;
  font-size:.82rem; font-weight:800; color:var(--ink-soft); white-space:nowrap;
  background:var(--surface-2); border:2px solid var(--line-strong)}
/* A listing with no opening hours can carry a short label instead - "Online only",
   "By appointment", "Seasonal" - so the status slot says what it IS rather than sitting
   empty or, worse, claiming it is closed. */
.badge-info{display:inline-block; margin-left:.5rem; padding:.15rem .6rem; border-radius:999px;
  font-size:.82rem; font-weight:800; color:var(--accent); white-space:nowrap;
  background:var(--surface-2); border:2px solid var(--accent)}
.card-status .badge-info{margin-left:0}

@media print{.site-head,.navbar,.settings,.site-foot,.filterbar{display:none}}
@media (prefers-reduced-motion:reduce){*{animation:none!important; transition:none!important}}
