
:root{--paper:#f5f5f2;--ink:#10107a;--text:#171717;--muted:#70716d;--line:#d9d9d4;--green:#16834b;--amber:#a16d00}
/* The pinned map's height lives here rather than on .mapHero, because
   .routeSection needs to measure against it and is a sibling, not a child.
   The outer min() is the hard rule: the band never takes more than half the
   view, however short the window gets.

   dvh, not vh. `vh` means the LARGE viewport — the page as it would be with
   the browser's toolbars retracted — so while a phone's URL bar is on screen
   50vh is well over half of what you can actually see. dvh tracks the real
   visible height. The vh line first as a fallback for anything without dvh. */
:root{
  --mapBand:min(50vh, clamp(280px, 46vh, 520px));
  --mapBand:min(50dvh, clamp(280px, 46dvh, 520px));
}

*{box-sizing:border-box}body{margin:0;background:var(--paper);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;color:var(--text)}main{max-width:850px;margin:auto;padding:27px 17px 60px}.mast{display:flex;gap:10px;align-items:center;margin-bottom:23px}.bikebrand{width:38px;height:38px;border:3px solid var(--ink);border-radius:50%;display:grid;place-items:center;color:var(--ink);font-size:20px}.mast b{color:var(--ink);font-size:18px}.mast small{display:block;color:var(--muted);font-size:11px;margin-top:1px}h1{color:var(--ink);font-size:clamp(31px,6vw,48px);line-height:1;letter-spacing:-.035em;margin:0 0 8px}.intro{margin:0 0 25px;color:var(--muted);font-size:14px;line-height:1.45}.section{font-size:11px;font-weight:800;letter-spacing:.11em;text-transform:uppercase;color:var(--muted);margin:23px 0 8px}.route{background:white;border:1px solid var(--line);margin-bottom:18px;overflow:hidden}
.route.line1{--route-color:#ffbe00}
.route.line2{--route-color:#0055c8}
.route.line3{--route-color:#858500}
.route.line4{--route-color:#a0006e}
.route.lineA{--route-color:#e31b23}
.route.lineB{--route-color:#3f97df}
.route.lineC{--route-color:#fdb913}
.route.running{border-color:var(--route-color)}.route.waiting{background:#fafaf8}
.route.waiting .summary > :not(.icons){filter:saturate(.5)}
.route.waiting .name,
.route.waiting .status,
.route.waiting .strip,
.route.waiting .dow{opacity:.72}
.route.proposed{background:#ecece8;color:#767771}.summary{border:0;background:transparent;color:inherit;width:100%;padding:13px 14px;display:grid;gap:12px;align-items:center;text-align:left;font:inherit}.icons{display:flex;align-items:center;gap:7px}.mode{width:43px;height:43px;border:3px solid var(--ink);border-radius:50%;display:grid;place-items:center;color:var(--ink);font-size:22px;font-weight:700}.lineNo{width:43px;height:43px;border-radius:50%;display:grid;place-items:center;font-size:23px;font-weight:850;background:var(--route-color);color:var(--route-on)}.name{font-size:21px;color:var(--ink);font-weight:820;line-height:1.05;letter-spacing:-.015em}.proposed .name{color:#73746f}.status{font-size:12px;margin-top:5px;color:var(--muted);font-weight:600}.status.live{color:var(--green)}.status.wait{color:var(--amber)}.dow{display:flex}.d{place-items:center}.d.want{border:2px solid #e5ad00}.chev{color:#8b8c87;font-size:18px}.route.open .chev{transform:rotate(180deg)}.strip{grid-column:2/4;margin-top:3px;min-width:0;width:100%}
.diagram{display:flex;justify-content:space-between;position:relative;height:12px;align-items:center}
.diagram:before{content:"";position:absolute;left:5px;right:5px;top:4px;height:4px;background:var(--route-color,var(--ink));border-radius:2px}
.station{position:relative;z-index:1;width:12px;height:12px;border:4px solid var(--route-color,var(--ink));background:#fff;border-radius:50%;justify-self:center;box-sizing:border-box}
.station:first-child{justify-self:start}
.station:last-child{justify-self:end}
.station:last-child{background:var(--route-color,var(--ink))}
.stationNames{display:flex;justify-content:space-between;align-items:flex-start;margin-top:3px;margin-bottom:10px;padding:0 5px}
.stationNames span{position:relative;width:0;flex:0 0 0;white-space:normal}
.stationNames span::after{content:attr(data-label);position:absolute;top:0;left:50%;width:120px;transform:translateX(-50%);text-align:center}
.stationNames span{text-align:center;font-size:10px;line-height:1.15;color:#62635f;font-weight:700}
.proposed .diagram:before{background:#aaa}.proposed .station{border-color:#aaa;background:#ecece8}.proposed .station:last-child{background:#aaa}.proposed .stationNames span{color:#92938e}
.detail{display:none;border-top:1px solid var(--line);padding:15px 16px 16px 128px}.open .detail{display:block}.detail p{font-size:13px;color:var(--muted);line-height:1.45;margin:0 0 12px}.actions{display:flex;gap:7px;flex-wrap:wrap}.actions button{border:0;background:var(--ink);color:white;border-radius:5px;padding:9px 12px;font:inherit;font-size:12px;font-weight:750;cursor:pointer}.actions .light{background:#eee;color:#222}.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:11px;color:var(--muted);margin:12px 3px 25px}.legend span{display:flex;align-items:center}.how{border-top:1px solid var(--line);padding-top:18px;display:flex;gap:25px}.how b{color:var(--ink);font-size:14px}.how p{font-size:12px;color:var(--muted);margin:3px 0 0;line-height:1.45}@media(max-width:650px){.dow{grid-column:2/3;margin-top:2px}.strip{grid-column:1/4;margin:5px 2px 0}.stationNames span{font-size:9px}

}
.bikeIcon{display:block;width:24px;height:auto;color:currentColor}
@media (hover:hover) and (pointer:fine){
.route.waiting:hover .summary > :not(.icons){filter:saturate(.72)}
.route.waiting:hover .name,
.route.waiting:hover .status,
.route.waiting:hover .strip,
.route.waiting:hover .dow{opacity:.86}


.route.proposed:hover{background:#f2f2ef;color:#3f403d}
.route.proposed:hover .name,
.route.proposed:hover .status,
.route.proposed:hover .strip,
.route.proposed:hover .icons{opacity:1;color:#3f403d}

.route.proposed:hover .diagram:before{background:#666}
.route.proposed:hover .station{border-color:#666;background:#f2f2ef}
.route.proposed:hover .station:last-child{background:#666}
.route.proposed:hover .stationNames span{color:#555}

}
.route.waiting:focus-within .summary > :not(.icons){filter:saturate(.72)}
.route.waiting:focus-within .name,
.route.waiting:focus-within .status,
.route.waiting:focus-within .strip,
.route.waiting:focus-within .dow{opacity:.86}


.route.proposed:focus-within{background:#f2f2ef;color:#3f403d}
.route.proposed:focus-within .name,
.route.proposed:focus-within .status,
.route.proposed:focus-within .strip,
.route.proposed:focus-within .icons{opacity:1;color:#3f403d}

.route.proposed:focus-within .diagram:before{background:#666}
.route.proposed:focus-within .station{border-color:#666;background:#f2f2ef}
.route.proposed:focus-within .station:last-child{background:#666}
.route.proposed:focus-within .stationNames span{color:#555}
.routeSection{margin-top:28px}
.routeSection:first-of-type{margin-top:0}
/* The heading is a wordmark, so the row aligns on the images' own bottoms
   rather than on a text baseline the h2 no longer has. */
.sectionHead{display:flex;align-items:flex-end;gap:10px;margin:0 0 10px 2px}
.sectionHead h2{margin:0;font-size:0;line-height:0}
.sectionLogo{display:block;width:clamp(96px,17vw,120px);height:auto}
.sectionHead span{font-size:13px;color:#777873;font-weight:600;
                  padding-bottom:2px}
.walkIcon{display:block;width:20px;height:24px;color:currentColor}

/* Day-of-week status is intentionally independent of route identity color. */
.d{background:#e7e7e4 !important;color:#777873 !important;border:2px solid transparent !important;box-sizing:border-box}
.d.on{background:#292a27 !important;color:#fff !important;border-color:#292a27 !important}
.d.want{background:#e7e7e4 !important;color:#4f504c !important;border-color:#777873 !important}

.d{display:inline-flex !important;flex-direction:column !important;align-items:center !important;justify-content:center !important;line-height:1 !important;font-style:normal}
.d::before{display:block}

.d{width:29px !important;height:40px !important;border-radius:9px !important;gap:4px !important;font-size:12px !important;font-weight:700 !important;border-width:1px !important}
.d::before{font-size:9px !important;line-height:9px !important;height:9px !important;font-weight:800 !important}
.d.on::before{content:"✓"}
.d.want::before{letter-spacing:0}
.d:not(.on):not(.want)::before{content:"×";font-size:10px !important}
.d.want::before{content:"○" !important;font-size:9px !important;line-height:9px !important;font-weight:900 !important}
.joinRide{margin-left:auto;margin-right:12px;padding:8px 13px;border:0;border-radius:8px;background:#142c4a;color:#fff;font:700 12px/1 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;white-space:nowrap;cursor:pointer}
.joinRide:hover{filter:brightness(1.12)}
.dow{margin-left:0 !important}
@media(max-width:680px){
.joinRide{padding:8px 10px;margin-right:8px}

}
.summary{min-width:0}
.summary > .dow{flex:0 0 auto}
.summary > .joinRide{margin-right:0 !important;flex:0 0 auto}
.summary > .dow{grid-column:3;margin-left:0 !important}
.summary > .joinRide{grid-column:4}
@media(max-width:650px){
.summary > .joinRide{align-self:center}
.summary > .chev{grid-column:4 !important;grid-row:1 / span 2;align-self:center}

}
/* DOW reads as one segmented control. */
.dow{gap:1px !important}
.dow .d{border-radius:0 !important}
.dow .d:first-child{border-radius:9px 0 0 9px !important}
.dow .d:last-child{border-radius:0 9px 9px 0 !important}
/* Alternate DOW treatment: weekday labels float above icon-only segments. */
.dow{padding-top:17px !important;position:relative;overflow:visible !important}
.dow .d{height:27px !important;font-size:0 !important;overflow:visible !important}
.dow .d::after{content:attr(data-day);position:absolute;top:-17px;left:50%;transform:translateX(-50%);font-size:11px;line-height:12px;font-weight:700;color:#5f605c}
/* Fine alignment: lift the DOW group, lower CTA so icon boxes and button align. */
.summary > .dow{transform:translateY(-3px)}
.summary > .joinRide{transform:translateY(5px)}
/* More breathing room between service days and CTA. */
.summary{column-gap:10px !important}
.summary > .joinRide{margin-left:7px !important}
/* Proper animated disclosure chevron, centered to the full summary box. */
.summary > .chev{border-right:1.75px solid currentColor;border-bottom:1.75px solid currentColor;transform-origin:50% 50%;transition:transform 180ms ease}
@media (prefers-reduced-motion: reduce){
.summary > .chev{transition:none}

}
.summary{position:relative !important}
.summary > .chev{position:absolute !important;top:50% !important;margin:0 !important;grid-row:auto !important;grid-column:auto !important;align-self:auto !important;justify-self:auto !important;transform:translateY(-50%) rotate(-45deg) !important}
.route.open .summary > .chev{transform:translateY(-50%) rotate(45deg) !important}
/* Chevron refinement */
.summary > .chev{border-right-width:2.75px !important;border-bottom-width:2.75px !important;opacity:1 !important;color:#142c4a !important}
/* Keep v48 chevron position; make it larger and let content extend closer to it. */
.summary{grid-template-columns:103px minmax(120px,1fr) auto auto !important}
.summary > .chev{width:11px !important;height:11px !important}
.summary > .strip{grid-column:2/5 !important;padding-right:0 !important}
.summary > .chev{right:15px !important}
.summary{padding-right:42px !important}
.summary > .joinRide{width:142px !important;box-sizing:border-box !important;text-align:center !important}
/* Proposed routes have no DOW block, so their captain CTA can use the extra room. */
.route.proposed .summary > .joinRide{width:178px !important}
/* DOW hover/focus explanations */
.dow .d::before{pointer-events:none}
.dow .d::after{pointer-events:none}

/* Preserve the weekday label while showing tooltip by moving it to a child-like
   second pseudo layer isn't possible with one ::after, so use title text via ::before
   only during hover and keep the weekday visually represented by the group context. */
.route,
.summary,
.dow{overflow:visible !important}
.route{position:relative}

/* Tooltip rebuilt as real markup so it doesn't compete with the DOW glyph pseudo-elements. */
.dow .d{position:relative}
.dow .d:hover::before,
.dow .d:focus-visible::before{color:inherit !important}
.dayTip{display:none;position:absolute;z-index:1000;left:50%;bottom:calc(100% + 10px);transform:translateX(-50%);width:220px;box-sizing:border-box;padding:8px 10px;border-radius:6px;background:#142c4a;color:#fff;text-align:left;font-size:11px;line-height:1.35;font-style:normal;white-space:normal;box-shadow:0 2px 7px rgba(0,0,0,.15);pointer-events:none}
.dow .d:hover .dayTip,
.dow .d:focus-visible .dayTip{display:block}
.dayTip b{display:block;margin-bottom:3px;font-size:11px;line-height:1.2;font-weight:800}
.dayTip b.run{color:#55d17a}
.dayTip b.build{color:#ffd34d}
.dayTip b.off{color:rgba(255,255,255,.5)}
.dayTip > span{display:block !important;color:#fff;font-size:11px;font-weight:650}
/* Disable old attribute-driven tooltip pseudo layers from v65. */

.statusCheck{display:inline-block;margin-right:2px;font-weight:800}
/* Expanded stop times. Existing stop names remain on ::before. */
.stationNames > span::after{margin-top:3px}
/* v77: restore stop titles after adding data-time. */
/* v78: station title + expanded time share the exact circle anchor. */
.stationNames > span{position:relative !important;width:0 !important;flex:0 0 0 !important}
.stationNames > span::before{content:attr(data-label) !important;position:absolute !important;top:0 !important;left:50% !important;width:120px !important;transform:translateX(-50%) !important;display:block !important;text-align:center !important;font-weight:700 !important;color:inherit !important;white-space:normal !important}
.stationNames > span::after{content:attr(data-time) !important;position:absolute !important;left:50% !important;width:120px !important;transform:translateX(-50%) !important;display:block !important;text-align:center !important;font-weight:600 !important;color:#657487 !important;white-space:nowrap !important;opacity:0;visibility:hidden}
.route.open .stationNames > span::after{opacity:1 !important;visibility:visible !important}
/* v79: restore original stop-title scale and remove reserved vertical space. */
.stationNames > span{height:0 !important}
.stationNames > span::before{font-size:10px !important;line-height:1.15 !important}
.stationNames > span::after{top:12px !important;font-size:10px !important;line-height:1.15 !important}
/* v81: move the divider + expanded detail below the stop-time row. */
.route.open .detail{margin-top:14px !important}
/* v82 debug running-state simulation */
.debugBar{max-width:980px;margin:14px auto 8px;padding:0 16px}
.debugBar button{appearance:none;border:1px solid rgba(20,44,74,.18);border-radius:7px;background:#fff;color:#142c4a;padding:7px 10px;font:700 11px/1 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.06)}
.debugBar button:hover{background:#f5f7f9}
.debugBar button:focus-visible{outline:2px solid #142c4a;outline-offset:2px}
.liveBikeIcon{display:block;width:17px;height:auto}
@keyframes bikeAlongRoute{
  from{left:0}
  to{left:100%}
}
@media (prefers-reduced-motion:reduce){
.route.simRunning .liveBike{left:100%}

}
.route .strip{position:relative}
.route.simRunning{outline:2px solid var(--c);outline-offset:3px}
/* v86: marker is anchored to the actual .diagram route line. */
.route .diagram{position:relative !important;overflow:visible !important}
.route .liveBike{display:none;position:absolute !important;z-index:100 !important;top:50% !important;width:32px !important;height:32px !important;border-radius:50% !important;background:#fff !important;border:1px solid rgba(0,0,0,.2) !important;box-shadow:0 10px 20px rgba(0,0,0,.5) !important;transform:translate(-50%,-50%) !important;align-items:center !important;justify-content:center !important;pointer-events:none !important}

.route.simRunning .liveBike{display:flex !important}
@keyframes line1BikeMoveV86{
  from{left:0}
  to{left:100%}
}
/* v87: 30s live-progress simulation with exact route bicycle icon. */
.route .liveBikeIcon{display:block !important;width:18px !important;height:auto !important;color:#142c4a !important}
/* During a run, the base route ahead is gray. A colored progress line grows
   underneath the rider from the first station to its current position. */
.route.simRunning .diagram{--p:0}
.route.simRunning .diagram::after{content:"";position:absolute;z-index:2;left:5px;top:50%;width:calc(var(--p) * (100% - 10px));height:4px;transform:translateY(-50%);pointer-events:none}
.route.simRunning .diagram .station{position:relative;z-index:3}
.route.simRunning .liveBike{z-index:10 !important}
/* v88: gray only the untraveled route line/circles, never the whole diagram box. */
.route.simRunning .diagram{background:transparent !important}
.route.simRunning .diagram::before{background:#d9dde2 !important}
.route.simRunning .diagram::after{background:var(--route-color) !important}
.route.simRunning .diagram .station{background:#fff !important;border-color:#d9dde2 !important}
.route.simRunning .diagram .station.passed{border-color:var(--route-color) !important}
/* v90: simple per-day ride signup */
.rideSignup{margin:0 0 18px;padding:16px;border:1px solid #dfe4ea;border-radius:12px;background:#fff}
.rideSignupHead{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:12px}
.rideSignupTitle{font-size:15px;font-weight:800;color:#142c4a}
.rideSignupHint{font-size:12px;color:#667386;margin-top:2px}

.rideSignupFoot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:12px}

.rideSignupSave{border:0;border-radius:8px;padding:9px 13px;background:#142c4a;color:#fff;font-weight:750;font-size:12px;cursor:pointer}
.rideSignupSave:disabled{opacity:.35;cursor:default}
@media(max-width:620px){

.rideSignupFoot{align-items:flex-start;flex-direction:column}
.rideSignupSave{width:100%}

}
/* v91: five-day participation + unlock board */
.serviceBoard{padding:0 !important;border:0 !important;background:transparent !important}
.serviceBoard .rideSignupHead{margin-bottom:10px}
.serviceRows{border:1px solid #dfe4ea;border-radius:11px;overflow:hidden;background:#fff}
.serviceRow{display:grid;grid-template-columns:112px minmax(180px,1fr) auto;gap:14px;align-items:center;padding:11px 12px;border-bottom:1px solid #e7ebef}
.serviceRow:last-child{border-bottom:0}
.serviceDay{display:flex;flex-direction:column;gap:3px}
.serviceDay b{font-size:14px;color:#142c4a}
.dayState{font-size:10px;font-weight:750}
.serviceRow.running .dayState{color:#167443}
.serviceRow.building .dayState{color:#8a6500}
.serviceRow.off .dayState{color:#7d8792}
.serviceNeed{display:flex;flex-direction:column;gap:2px;min-width:0}
.serviceNeed strong{font-size:12px;color:#142c4a}
.serviceNeed span{font-size:10px;color:#6d7886}
.serviceRow.building .serviceNeed strong{color:#745700}
.participate{min-height:31px;border:1px solid #cfd6de;border-radius:7px;background:#fff;color:#142c4a;padding:0 9px;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap}
.participate:hover{background:#f5f7f9}
.participate.selected{background:#142c4a;border-color:#142c4a;color:#fff}
.serviceRow.building .keyAction{border-color:#b88900;box-shadow:0 0 0 2px rgba(184,137,0,.10)}
.serviceRow.building .keyAction:not(.selected)::after{font-size:9px;opacity:.75}
.serviceBoard .rideSignupFoot{margin-top:10px}
@media(max-width:760px){
.serviceRow{grid-template-columns:76px minmax(0,1fr)}
.serviceActions{grid-column:1/3;justify-content:flex-start;flex-wrap:wrap}

}
/* v92: global rider setup + simpler Ride / Volunteer actions */

.riderDialog{width:min(420px,calc(100% - 32px));border:0;border-radius:14px;padding:0;box-shadow:0 20px 60px rgba(0,0,0,.24);color:#142c4a}
.riderDialog::backdrop{background:rgba(12,25,42,.34)}
.riderDialogCard{padding:18px;margin:0}
.riderDialogHead{display:flex;justify-content:space-between;gap:16px;align-items:flex-start}
.riderDialogTitle{font-size:18px;font-weight:850}
.riderDialogHint{font-size:12px;color:#6d7886;margin-top:3px}
.dialogClose{border:0;background:transparent;font-size:24px;line-height:1;color:#667386;cursor:pointer}

.riderDone{width:100%;border:0;border-radius:9px;background:#142c4a;color:#fff;padding:11px 14px;font-weight:800;cursor:pointer}
/* v93 containment repair: expanded-only UI never leaks into collapsed cards. */
.route:not(.open) .detail,
.route:not(.open) .serviceBoard,
.route:not(.open) .serviceRows,
.route:not(.open) .rideSignupFoot{display:none !important}
.route.open .detail{display:block}
/* Numeric rider setup */
.riderCounts{display:grid;gap:0;margin:16px 0;border:1px solid #dfe4ea;border-radius:10px;overflow:hidden}
.riderCountRow{display:flex;align-items:center;justify-content:space-between;gap:18px;min-height:58px;padding:10px 12px;border-bottom:1px solid #e7ebef}
.riderCountRow:last-child{border-bottom:0}
.riderCountRow>div:first-child{display:flex;flex-direction:column;gap:2px}
.riderCountRow b{font-size:13px}
.riderCountRow span{font-size:10px;color:#6d7886}
.stepper{display:grid;grid-template-columns:32px 32px 32px;align-items:center;text-align:center;flex:0 0 auto}
.stepper button{width:32px;height:32px;border:1px solid #cfd6de;background:#fff;color:#142c4a;font-size:18px;line-height:1;cursor:pointer}
.stepper button:first-child{border-radius:7px 0 0 7px}
.stepper button:last-child{border-radius:0 7px 7px 0}
.stepper output{height:32px;display:flex;align-items:center;justify-content:center;border-top:1px solid #cfd6de;border-bottom:1px solid #cfd6de;font-size:13px;font-weight:800;background:#f8fafb}
/* The captain's own row, above the mornings. Tinted so it reads as the
   condition on everything below it rather than as another weekday. */
.serviceRow.captainRow{background:#fbf7ec;border-bottom-color:#efe4cb}
.serviceRow.captainRow .serviceDay b{color:#6b5200}
.serviceRow.captainRow .dayState{color:#8a6500}
.serviceRow.captainRow.hasCaptain{background:#f2f9f4;border-bottom-color:#dceee3}
.serviceRow.captainRow.hasCaptain .serviceDay b{color:#11603a}
.serviceRow.captainRow.hasCaptain .dayState{color:#167443}
.serviceRow.captainRow .serviceNeed strong{color:#5c4a12}
.serviceRow.captainRow.hasCaptain .serviceNeed strong{color:#11603a}

.captainOffer{min-height:31px;border:1px solid #cfd6de;border-radius:7px;
  background:#fff;color:#142c4a;padding:0 9px;font:inherit;font-size:11px;
  font-weight:700;cursor:pointer;white-space:nowrap}
.captainOffer:hover{background:#f5f7f9}
.captainRow .captainOffer{border-color:#d8c48a;background:#fffdf6}
.captainRow .captainOffer:hover{border-color:#b88900;background:#fffaf0}
/* Your own captaincy, filled the way a chosen morning is — tapping it again
   is how you step back. */
.captainOffer.isMine,
.captainRow .captainOffer.isMine{background:#142c4a;border-color:#142c4a;color:#fff}
.captainRow .captainOffer.isMine:hover{background:#1d3c63;border-color:#1d3c63}
.captainOffer:disabled{opacity:.55;cursor:default}
.riderDone{min-height:48px !important;font-size:14px !important}
/* v96: exactly one family role per day. */
.serviceRow.building .keyAction:not(.selected)::after{content:none !important}
/* One action per day now that riding needs no signup, so the column is one
   wide and sits hard against the right edge — the two-column grid left over
   from Ride + Volunteer was still reserving the second slot, which held the
   button short of the edge and squeezed the text beside it. */
.serviceActions{display:grid !important;grid-template-columns:minmax(124px,max-content) !important;
                gap:6px !important;justify-content:end !important}
.serviceActions > button{width:100% !important;min-width:124px !important;padding:0 8px !important;text-align:center}
@media(max-width:760px){
.serviceActions{grid-template-columns:minmax(0,1fr) !important;width:100%}
.serviceActions > button{width:100% !important;min-width:0 !important}

}
/* v101: live route glow — clean implementation */
@keyframes liveRouteGlow{
  0%,100% {
    box-shadow:
      0 0 4px 1px rgba(255,190,0,.22),
      0 0 9px 1px rgba(255,190,0,.14);
  }
  50% {
    box-shadow:
      0 0 12px 4px rgba(255,190,0,.72),
      0 0 34px 10px rgba(255,190,0,.42);
  }
}
/* No shadow in any ordinary route state. */
.route{box-shadow:none}
/* The debug/live class is the ONLY state that gets a shadow. */
.route.simRunning{position:relative;z-index:3}
@media (prefers-reduced-motion: reduce){
.route.simRunning{animation:none;box-shadow:0 0 18px 5px rgba(255,190,0,.48)}

}
/* v104: actual moving element is .liveBike (not .liveMarker). */
@keyframes liveBikeCircleBreatheV104{
  0%,100%{scale:1}
  50%{scale:.8}
}
/* JS changes left; the existing transform centers the marker.
   Individual scale therefore animates the whole white circle independently. */
.route.simRunning .liveBike{overflow:visible !important}
/* Visible broadcast rings originate from the actual moving white circle. */
@keyframes liveBikeRingV104{
  0%{
    transform:scale(.9);
    opacity:.78;
  }
  100%{
    transform:scale(3);
    opacity:0;
  }
}
.route.simRunning .liveBike::before,
.route.simRunning .liveBike::after{content:"" !important;display:block !important;position:absolute !important;left:-3px !important;top:-3px !important;right:-3px !important;bottom:-3px !important;border:2px solid var(--route-color) !important;border-radius:50% !important;background:transparent !important;box-shadow:none !important;pointer-events:none !important;z-index:-1 !important;opacity:0;animation:liveBikeRingV104 3.2s cubic-bezier(.18,.62,.32,1) infinite !important}
.route.simRunning .liveBike::after{animation-delay:1.6s !important}
@media (prefers-reduced-motion:reduce){
.route.simRunning .liveBike{animation:none !important;scale:1}
.route.simRunning .liveBike::before,
.route.simRunning .liveBike::after{display:none !important;animation:none !important}

}
/* v105: center the live-marker breathing and outline it in route color. */
.route.simRunning .liveBike{transform-origin:center center !important;border:1px solid var(--route-color) !important;box-sizing:border-box !important}
/* Use transform scale around the circle's true center. */
@keyframes liveBikeCircleBreatheV105{
  0%,100%{transform:translate(-50%,-50%) scale(1)}
  50%{transform:translate(-50%,-50%) scale(.8)}
}
.route.simRunning .liveBike{scale:none !important;animation:liveBikeCircleBreatheV105 3.2s cubic-bezier(.45,0,.55,1) infinite !important}
/* v107: while live, expose stop times even when the route card is collapsed. */
/* Existing stop times are normally expansion-only; live state also reveals them. */
/* Give the route strip enough vertical room for the second line of labels. */
.route.simRunning .strip{margin-bottom:10px !important}
/* v108: live stop times become route-colored ETA capsules. */
.route.simRunning .stationNames > span::after{content:"ETA: " attr(data-eta) !important;display:block !important;width:max-content !important;min-width:0 !important;padding:3px 7px !important;border-radius:999px !important;background:var(--route-color) !important;color:var(--route-on) !important;font-size:9px !important;font-weight:800 !important;line-height:1.25 !important;white-space:nowrap !important;box-shadow:none !important;transform:translateX(-50%) !important;opacity:1 !important;visibility:visible !important}
/* A little more room for the live capsules in the collapsed card. */
.route.simRunning .summary{padding-bottom:32px !important}
/* v111: live card glow inherits each route's own identity color. */
@keyframes liveRouteGlowDynamicV111{
  0%,100%{
    box-shadow:
      0 0 4px 1px color-mix(in srgb, var(--route-color) 22%, transparent),
      0 0 9px 1px color-mix(in srgb, var(--route-color) 14%, transparent);
  }
  50%{
    box-shadow:
      0 0 12px 4px color-mix(in srgb, var(--route-color) 72%, transparent),
      0 0 34px 10px color-mix(in srgb, var(--route-color) 42%, transparent);
  }
}
.route.simRunning{animation:liveRouteGlowDynamicV111 6.4s ease-in-out infinite !important}
/* Live marker position is derived from --p (0..1) set by the tracker, and
   aligned to station centres rather than the raw container edges. */
.diagram{--p:0}
.route .liveBike{left:calc(6px + var(--p) * (100% - 12px)) !important}
/* Legend reuses the real weekday chips so it always matches the widget. */
.legend{align-items:center}
.legend span{gap:7px}
.legend .d{width:22px !important;height:22px !important;border-radius:6px !important;gap:0 !important}
.legend .d::after{content:none !important}
/* ------------------------------------------------------------
   Responsive rules, last in the stylesheet on purpose.
   Several unconditional `!important` grid overrides appear later
   in this file than the original media queries did. Because a
   media query adds no specificity, those later rules were winning
   at every width and the narrow-screen layout never applied —
   the CTA overflowed the card by ~66px on a phone. Keeping the
   responsive block last is what makes it take effect.
   ------------------------------------------------------------ */
@media(max-width:650px){
/* Stack the card: identity, then service days, then the CTA. */
.summary{grid-template-columns:92px minmax(0,1fr) !important;padding-right:26px !important;row-gap:9px}
.summary > .icons{grid-column:1;grid-row:1;align-self:start}
.summary > .nameBlock{grid-column:2;grid-row:1;min-width:0}
.summary > .dow{grid-column:1 / 3 !important;grid-row:2;justify-self:start;margin-top:0;transform:none !important}
.summary > .joinRide{grid-column:1 / 3 !important;grid-row:3;width:auto !important;min-width:0 !important;justify-self:stretch;text-align:center !important;padding:11px 12px !important;margin-left:0 !important;transform:none !important}
.route.proposed .summary > .joinRide{width:auto !important}
.summary > .strip{grid-column:1 / 3 !important;grid-row:4;margin-top:2px}
.mode,
.lineNo{width:39px;height:39px}
.name{font-size:19px}
.stationNames > span::before{font-size:9px !important}
.stationNames > span::after{font-size:9px !important}
/* The strip spans the full card width here, so stop labels get a
     bounded box that wraps, and the two end labels anchor inward
     instead of centring past the card edge. */
.stationNames{margin-bottom:24px !important}
.stationNames > span::before{width:74px !important;white-space:normal !important}
.stationNames > span::after{width:74px !important}
.stationNames > span:first-child::before,
.stationNames > span:first-child::after{transform:translateX(0) !important;text-align:left !important}
.stationNames > span:last-child::before,
.stationNames > span:last-child::after{transform:translateX(-100%) !important;text-align:right !important}
.detail{padding-left:15px}
.how{display:block}
.how>div{margin-bottom:14px}

}
/* ------------------------------------------------------------
   The summary is a container, not a control. The route name is
   the real disclosure button and the CTA is a real button, so
   neither is an interactive element nested inside another one.
   ------------------------------------------------------------ */
.summary{cursor:pointer}
.disclosure{all:unset;display:block;cursor:pointer;min-width:0}
.disclosure:focus-visible{outline:2px solid var(--ink);outline-offset:3px;border-radius:3px}
.joinRide{appearance:none;-webkit-appearance:none;font-family:inherit}
.joinRide:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* Day cells are descriptive, not operable: the same information is
   written out in the expanded participation board. */
.dow .d{cursor:help}


/* ------------------------------------------------------------
   Masthead photograph.
   A full-bleed band at the top of the page that dissolves into the
   page background. It fades to --paper (#f5f5f2) rather than pure
   white: the page is not white, so a fade to #fff would stop at a
   visible horizontal seam.

   The image is the FULL original frame, uncropped. Everything about
   what is visible is decided here, continuously, so there is no
   breakpoint for the composition to jump at.

   How the crop works. The riders sit at y=195-420 of the 1024-tall
   source, so the bottom of the wheels is at 420/1536 = 27.34% of the
   rendered width. We shift the image up by exactly enough to park the
   wheels at 72% of the band and no more:

       offset = max(0, 27.34vw - 0.72 x band)

   `max(0, ...)` is what makes narrowing behave correctly. As the window
   gets smaller the first term shrinks faster than the second, the offset
   falls to zero, and the band shows MORE of the top until the whole
   frame — sky, Coit Tower, everything — is in view. Widen it and the
   offset grows smoothly, trading sky away to keep the riders clear of
   the fade. Continuous in both directions, one image, no jump.

   The floor `min(235px, 66vw)` keeps the band at or under 2/3 of the
   width, which is the source's own ratio. That guarantees `cover` never
   needs to crop horizontally, so no rider is ever cut off the left or
   right edge at any width.

   Decorative only — the page states everything this says — so it is a
   CSS background with no alt text, marked role="presentation".
   ------------------------------------------------------------ */
.hero{
  position:relative;
  width:100%;
  /* Height does NOT vary with window width — that is what kept nudging the
     page content up and down as you resized. It is sized from viewport
     HEIGHT, so the text below never moves as you drag the width. The 66vw
     ceiling only guards the narrow end, where a band taller than 2/3 of the
     width would force `cover` to crop the sides. */
  /* Two ceilings. The vh term fixes the height so page content doesn't
     slide as you drag the window wider. The 46.3vw term caps the band's
     ASPECT: at 700px the vh-driven height is 324, and 324/700 = 46.3%, so
     below 700 the band starts shrinking with the width instead of staying
     tall and dominating a narrow screen. Because band and image scale
     together below that point, the framing is identical — the same crop,
     just smaller. It also subsumes the old side-crop guard, since 46.3vw
     is comfortably under the 66.67vw at which `cover` would crop the
     sides. */
  --band:min(46.3vw, clamp(235px, 36vh, 440px));
  height:var(--band);
  background-image:url(hero.jpg);
  background-repeat:no-repeat;
  background-size:cover;

  /* How much is cut off the TOP.
     The riders occupy y=195-420 of the 1024-tall source, so their centre is
     at y=307.5, which is 20.02% of the rendered width. To centre them in the
     band we lift the image by (their centre - half the band):

         20.02vw - 0.5 x band

     clamped to zero at the bottom end. That clamp is what stops the top ever
     being cropped when it doesn't need to be: below about 810px the term goes
     negative, the offset is zero, and the whole frame is on screen. Above it,
     the crop grows only as fast as centring requires, so the riders stay whole
     and centred instead of sliding down and clipping into the fade.
     The upper bound is the actual overflow (66.67vw - band), a guard so the
     image can never be lifted past its own bottom edge. */
  background-position:center calc(
    0px - clamp(0px, 20.02vw - 0.5 * var(--band), max(0px, 66.67vw - var(--band)))
  );
  pointer-events:none;
}
.hero::after{
  content:"";
  position:absolute;
  inset:0;
  background:linear-gradient(
    to bottom,
    rgba(245,245,242,0)    0%,
    rgba(245,245,242,0)   74%,
    rgba(245,245,242,.42) 83%,
    rgba(245,245,242,.84) 91%,
    rgba(245,245,242,.97) 96%,
    var(--paper)          100%
  );
}

/* The fade already supplies the breathing room above the masthead, so
   the page's usual top padding would only add a dead gap. Pull the
   content up into the tail of the fade, where the ink-blue type sits
   on what is effectively solid paper. */
main{
  position:relative;
  z-index:1;
  margin-top:calc(-1 * clamp(24px, 4vw, 52px));
  padding-top:0;
}

/* Resolution-only variant. Same full frame as hero.jpg, just fewer pixels,
   so the composition is byte-for-byte identical and nothing shifts at the
   boundary — this is NOT art direction, it only avoids shipping 328 KB to
   a phone. An unmatched media query is not fetched, so one image loads. */
@media (max-width:760px){
  .hero{background-image:url(hero-sm.jpg)}
}

/* Debug scaffolding is prototype-only, so float it over the photo rather
   than letting it push the whole page down by its own height. */
.debugBar{
  position:absolute;
  top:0;
  left:0;
  z-index:3;
  margin:10px auto 0;
}

/* Tighter vertical rhythm through the header block: the photo already
   gives the page plenty of air above the wordmark. */
.mast{margin-bottom:14px}
.intro{margin-bottom:17px}
.section{margin-top:14px}


/* ============================================================
   SHARED CHROME — masthead and section nav, on every page.
   ============================================================ */
.siteHeader{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:18px;
  flex-wrap:wrap;
  max-width:850px;
  margin:0 auto 22px;
  padding:16px 17px 0;
}
.landing > .opener + .chapter{padding-top:8px}
.siteHeader .mast{
  display:flex;
  align-items:center;
  gap:10px;
  text-decoration:none;
  color:inherit;
  margin:0;
}
.siteHeader .mast small{display:block;color:var(--muted);font-size:11px;margin-top:1px}
.siteNav{display:flex;gap:4px}
.siteNav a{
  display:block;
  padding:7px 13px;
  border-radius:999px;
  font-size:13px;
  font-weight:750;
  color:#4a5568;
  text-decoration:none;
}
.siteNav a:hover{background:rgba(20,44,74,.07);color:var(--ink)}
.siteNav a[aria-current="page"]{background:#142c4a;color:#fff}
.siteNav a:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* Who you are, third in the nav. The name can be an email address, so it is
   allowed to truncate rather than push the rest of the bar around.
   Selector matches `.siteNav a` for weight, or the display:block there wins
   and the icon sits on its own line above the name. */
.siteNav a.navMe{display:inline-flex;align-items:center;gap:6px;max-width:13em;min-width:0}
.siteNav a.navMe span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.navMeIcon{flex:0 0 auto;opacity:.75}
@media(max-width:620px){.siteNav a.navMe{max-width:8.5em}}

.siteFooter{
  max-width:850px;
  margin:64px auto 0;
  padding:22px 17px 40px;
  border-top:1px solid var(--line);
  font-size:12px;
  color:var(--muted);
}
.siteFooter p{margin:0}

/* The routes page keeps its photographic band, so its header sits over the
   tail of the fade rather than on plain paper. */
/* With the masthead and nav gone, main carries the page on its own. The
   pull-up that tucked content into the tail of the hero fade would now drag
   the first section head into the map, so both pages start at zero. */
[data-page="routes"] main{margin-top:0;padding-top:10px}
/* The header sits inside main, which already supplies the gutter. */
[data-page="routes"] .siteHeader{padding:0;margin-bottom:14px}
[data-page="overview"] main{margin-top:0}
/* The first section head rides in the header, opposite the nav, so the row
   carries the network's name instead of sitting empty on the left. */
.sectionHead.headLead{margin:0}

/* Shared page furniture for the non-landing pages. */
.page{max-width:850px;margin:auto;padding:0 17px 60px}
.pageTitle{
  color:var(--ink);
  font-size:clamp(31px,6vw,48px);
  line-height:1;
  letter-spacing:-.035em;
  margin:0 0 10px;
}
.pageLede{margin:0 0 26px;color:var(--muted);font-size:14px;line-height:1.5;max-width:60ch}
.pageFoot{margin:34px 0 0;font-size:13px;color:var(--muted)}
.pageFoot a{color:var(--ink)}

/* ============================================================
   LANDING PAGE
   Long-form, one idea per screen. Generous type, short lines.
   ============================================================ */
.landing{max-width:none;padding:0}

.btnPrimary,.btnGhost{
  display:inline-block;
  border-radius:999px;
  padding:13px 24px;
  font-size:14px;
  font-weight:800;
  text-decoration:none;
  white-space:nowrap;
}
.btnPrimary{background:#142c4a;color:#fff}
.btnPrimary:hover{filter:brightness(1.15)}
.btnGhost{background:transparent;color:var(--ink);box-shadow:inset 0 0 0 1.5px rgba(20,44,74,.3)}
.btnGhost:hover{background:rgba(20,44,74,.06)}
.btnPrimary:focus-visible,.btnGhost:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
.btnLarge{padding:16px 32px;font-size:16px}

/* ---- Opener ---- */
/* A hairline between the masthead block and the first chapter. The section
   is already full width, so a border on it runs edge to edge. */
.opener{
  position:relative;
  margin:0 0 clamp(48px,7vw,96px);
  padding-bottom:clamp(34px,5vw,68px);
  border-bottom:1px solid rgba(0,0,0,.15);
  /* Both of these are read by more than one child — the photo's height, the
     logo's placement against it, and the gap the headline needs to clear the
     logo — so they belong to the section, not to any one of them. */
  --band:min(52vw, clamp(260px, 46vh, 520px));
  --logoW:clamp(260px, 57vw, 645px);
  /* How far down the band the logo's top edge sits. It cannot simply be one
     number: the logo stops shrinking at its width floor while the band keeps
     shrinking with the viewport, so the same fraction leaves proportionally
     more of the logo past the photo the narrower the window gets. It is
     lifted in step with that. */
  --logoTop:0.72;
  --logoH:calc(var(--logoW) / 3.98);   /* the asset is 2000x502 */
}
.openerPhoto{
  position:relative;
  width:100%;
  height:var(--band);
  background-image:url(hero.jpg);
  background-repeat:no-repeat;
  background-size:cover;
  /* Same centre-the-riders rule as the routes page band. */
  background-position:center calc(
    0px - clamp(0px, 20.02vw - 0.5 * var(--band), max(0px, 66.67vw - var(--band)))
  );
  pointer-events:none;
}
.openerPhoto::after{
  content:"";
  position:absolute;
  inset:0;
  background:linear-gradient(
    to bottom,
    rgba(245,245,242,0)    0%,
    rgba(245,245,242,0)   62%,
    rgba(245,245,242,.42) 78%,
    rgba(245,245,242,.86) 90%,
    rgba(245,245,242,.98) 96%,
    var(--paper)          100%
  );
}
/* Centred on the photo's bottom edge, so it sits across the last of the fade
   with roughly a third of it carried onto the paper below.

   The asset carries its own alpha, so nothing is cut out here — an earlier
   version arrived on a solid white card and had to be flood-filled, which
   never came out clean around the type. */
.openerLogo{
  position:absolute;
  left:50%;
  /* Anchored by its TOP, at a fraction of the band rather than a fraction of
     its own height. Pinning the bottom meant the logo kept its distance from
     a photo that was itself getting shorter, so as the window narrowed it
     sat lower and lower down the fade — 72% of the band at desktop, 80% by
     the middle of the range. Measuring from the band holds that 72% at every
     width, and the logo now shrinks downward from a fixed top edge. */
  top:calc(var(--logoTop) * var(--band));
  transform:translateX(-50%);
  width:var(--logoW);
  height:auto;
  z-index:2;
  /* A soft white halo instead of a blend mode. drop-shadow follows the
     alpha channel, so it haloes the letterforms themselves rather than a
     rectangle; two passes because one at 40px is too thin to lift the type
     off the darker part of the road. No blending, so nothing here depends
     on Safari agreeing about compositing. */
  filter:drop-shadow(0 0 14px rgba(255,255,255,.95))
         drop-shadow(0 0 40px rgba(255,255,255,.85));
  pointer-events:none;
  user-select:none;
}
.openerText{
  position:relative;
  z-index:1;
  max-width:850px;
  /* Clears the logo. This used to be a negative margin, floating the
     headline up onto the fade — which is where the logo now is.

     Measured from where the logo actually ends — 0.72 of the band plus its
     own height — rather than from its height alone, which no longer says
     anything about how far down the page it reaches. max() keeps the
     headline off the photo if the arithmetic ever goes negative. */
  margin:max(clamp(24px,3vw,40px),
             calc(var(--logoH) - (1 - var(--logoTop)) * var(--band)
                  + clamp(48px,6vw,92px)))
         auto 0;
  padding:0 17px;
}
/* --h1Size is declared on .opener so the rider's offset can read the very
   same value the headline is set in. They were two separate clamps before,
   and drifted apart. */
.opener{--h1Size:clamp(44px, 9vw, 92px)}
.openerText h1{
  color:var(--ink);
  font-size:var(--h1Size);
  line-height:.95;
  letter-spacing:-.045em;
  margin:0 0 18px;
}
.lede{
  font-size:clamp(16px,2.1vw,21px);
  line-height:1.45;
  color:#3f4b5c;
  max-width:34ch;
  margin:0 0 28px;
  font-weight:500;
}
.openerActions{display:flex;gap:10px;flex-wrap:wrap}

@media (max-width:760px){ .opener{--logoTop:0.60} }

/* ---- Chapters ---- */
.chapter{
  padding-block:clamp(56px,8vw,112px);
  /* Centre the content column with PADDING rather than auto margins on the
     children. Several of them (.kicker, h2) set a `margin` shorthand of their
     own, which quietly resets margin-inline:auto and left them flush at 17px
     while the opener's text began at the centred column. Doing it here is
     immune to that, works for inline-block buttons too, and leaves the
     section itself full width so the bands and the photograph still bleed.
     816px = the opener's 850px box minus its own 17px gutters. */
  padding-inline:max(17px, calc((100% - 816px) / 2));
}
.chapter > *{max-width:850px;margin-left:auto;margin-right:auto}
.chapterAlt{background:#fff;border-block:1px solid var(--line)}
/* Line 1's yellow is the brand note here, but as TYPE on this paper it is
   1.52:1 — unreadable. So the yellow becomes a bar and the words take a gold
   dark enough to read (5.3:1), which still belongs to the same family. */
.kicker{
  font-size:14px;
  font-weight:800;
  letter-spacing:.13em;
  text-transform:uppercase;
  color:#8a5c00;
  margin:0 0 12px;
  display:flex;
  align-items:center;
  gap:9px;
}
.kicker::before{
  content:"";
  flex:0 0 auto;
  width:22px;
  height:4px;
  border-radius:2px;
  background:#ffbe00;
}
@media(max-width:650px){.kicker{font-size:12.5px;gap:7px}.kicker::before{width:18px}}
.chapter h2,.closer h2{
  color:var(--ink);
  font-size:clamp(32px,5.2vw,58px);
  line-height:1.02;
  letter-spacing:-.04em;
  margin:0 0 22px;
  max-width:18ch;
}
.body{
  font-size:clamp(15px,1.7vw,19px);
  line-height:1.6;
  color:#3f4b5c;
  max-width:62ch;
  margin:0 0 18px;
}
.chapter .btnPrimary{margin-top:10px;display:inline-block}

/* ---- Who rides ---- */
.cards{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:16px;
  margin:10px 0 26px;
}
.card{
  background:var(--paper);
  border:1px solid var(--line);
  border-radius:14px;
  padding:20px;
}
.chapterAlt .card{background:#fbfbf9}
.card h3{margin:0 0 8px;font-size:17px;color:var(--ink);letter-spacing:-.01em}
.card p{margin:0;font-size:14px;line-height:1.55;color:#4a5668}
.threshold{font-size:15px;color:#4a5668}
.threshold b{color:var(--ink)}

/* Two calls to action side by side, wrapping rather than squashing. The
   chapters centre their content with padding, so this centres itself. */
.chapterActions{display:flex;gap:10px;flex-wrap:wrap;align-items:center}

/* ---- FAQ ----
   Collapsed by default: the page is already long, and a wall of answers to
   questions you did not ask is worse than a list of questions you can scan.
   The triangle is drawn rather than left to the browser so it sits where the
   rest of the site's disclosures put theirs. */
.faqItem{
  border-top:1px solid rgba(0,0,0,.12);
  padding:2px 0;
}
.faqItem:last-of-type{border-bottom:1px solid rgba(0,0,0,.12)}
.faqItem summary{
  display:flex;
  align-items:flex-start;
  gap:12px;
  padding:16px 2px;
  cursor:pointer;
  list-style:none;
  font-size:clamp(16px,1.9vw,19px);
  font-weight:750;
  line-height:1.35;
  color:var(--ink);
}
.faqItem summary::-webkit-details-marker{display:none}
.faqItem summary::before{
  content:"";
  flex:0 0 auto;
  margin-top:.45em;
  border-left:6px solid currentColor;
  border-top:5px solid transparent;
  border-bottom:5px solid transparent;
  opacity:.45;
  transition:transform .15s ease;
}
.faqItem[open] summary::before{transform:rotate(90deg)}
.faqItem summary:hover{color:#0d1f36}
.faqItem summary:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
.faqItem p{
  margin:0 0 18px;
  padding-left:18px;
  max-width:62ch;
  font-size:clamp(15px,1.7vw,17px);
  line-height:1.6;
  color:#3f4b5c;
}
.faqItem a{color:var(--ink);text-decoration:underline;text-underline-offset:3px}

/* ---- Closer ---- */
.closer{
  padding:clamp(64px,9vw,120px) 17px clamp(48px,7vw,96px);
  text-align:center;
}
.closer > *{max-width:850px;margin-left:auto;margin-right:auto}
.closer .body{margin-left:auto;margin-right:auto;margin-bottom:26px}
.closer h2{max-width:20ch;margin-left:auto;margin-right:auto}
.closer .chapterActions{justify-content:center}

/* The WhatsApp invitation under the route list — for everyone the board
   itself has no row for. */
.joinGroup{
  max-width:850px;
  margin:clamp(28px,4vw,48px) auto 0;
  padding:clamp(18px,2.6vw,26px) clamp(16px,2.4vw,24px);
  border:1px solid var(--line);
  border-radius:12px;
  background:#fff;
  display:flex;
  gap:16px;
  align-items:center;
  justify-content:space-between;
  flex-wrap:wrap;
}
.joinGroup p{
  margin:0;
  max-width:52ch;
  font-size:14.5px;
  line-height:1.5;
  color:#3f4b5c;
}
.joinGroup .btnPrimary{flex:0 0 auto}

/* ============================================================
   MAP PAGE — schematic network diagram
   ============================================================ */
.mapFrame{
  background:#fff;
  border:1px solid var(--line);
  border-radius:14px;
  padding:10px 4px;
  overflow-x:auto;
}
.networkMap svg{display:block;width:100%;min-width:620px;height:auto}

.mapTrunk{stroke:#142c4a;stroke-width:5;stroke-linecap:round}
.mapPath{stroke:var(--route-color);stroke-width:5;stroke-linecap:round}
.mapBadge{fill:var(--route-color)}
.mapBadgeText{
  font:800 15px/1 system-ui,-apple-system,"Segoe UI",sans-serif;
  text-anchor:middle;
}
.mapRouteName{
  font:800 13px/1 system-ui,-apple-system,"Segoe UI",sans-serif;
  fill:var(--ink);
  text-anchor:end;
}
.mapStop{fill:#fff;stroke:var(--route-color);stroke-width:3.5}
.mapStop.isInterchange{stroke:#142c4a;stroke-width:3.5}
.mapStopName{
  font:700 10px/1 system-ui,-apple-system,"Segoe UI",sans-serif;
  fill:#62635f;
  text-anchor:middle;
}
.mapStopName.isInterchange{fill:#142c4a}
.mapSchool{fill:#142c4a}
.mapSchoolName{
  font:800 14px/1 system-ui,-apple-system,"Segoe UI",sans-serif;
  fill:var(--ink);
}
/* Maturity reads as desaturation here too, exactly as on the route cards. */
.mapLine.isBuilding{opacity:.72}
.mapLine.isProposed{opacity:.4;filter:grayscale(1)}

.mapKey{
  display:flex;
  gap:18px;
  flex-wrap:wrap;
  margin:14px 2px 0;
  font-size:11px;
  color:var(--muted);
  align-items:center;
}
.mapKey span{display:flex;align-items:center;gap:6px}
.keyDot{width:11px;height:11px;border-radius:50%;background:#142c4a;display:block}
.keyDot.isBuilding{opacity:.72}
.keyDot.isProposed{opacity:.4;background:#8b8c87}
.keyRing{
  width:12px;height:12px;border-radius:50%;
  background:#fff;box-shadow:inset 0 0 0 3px #142c4a;display:block;
}

.interchanges{margin-top:clamp(36px,5vw,64px)}
.interchanges h2{font-size:22px;color:var(--ink);margin:0 0 8px;letter-spacing:-.02em}
.interchangeList{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:8px;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.interchangeList li{
  background:#fff;border:1px solid var(--line);border-radius:11px;
  padding:12px 14px;display:flex;flex-direction:column;gap:3px;
}
.interchangeList b{font-size:14px;color:var(--ink)}
.interchangeList span{font-size:11px;color:var(--muted)}

@media(max-width:650px){
  .siteHeader{gap:10px;padding-top:12px}
  .siteNav a{padding:6px 11px;font-size:12px}
  .cards{grid-template-columns:1fr}
}


/* ============================================================
   MAP PAGE — geographic
   ============================================================ */
.geoMapFrame{
  border:1px solid var(--line);
  border-radius:14px;
  overflow:hidden;
  background:#fff;
}
.geoMap{
  height:clamp(380px, 62vh, 680px);
  width:100%;
}
.mapError{padding:28px 18px;margin:0;color:var(--muted);font-size:14px}
.mapError a{color:var(--ink)}

/* Leaflet's own chrome, toned down to match the page. */
.leaflet-container{font:inherit;background:var(--paper)}
.leaflet-control-attribution{font-size:10px;background:rgba(255,255,255,.82)}
.leaflet-popup-content-wrapper{border-radius:11px;box-shadow:0 6px 22px rgba(0,0,0,.16)}
.leaflet-popup-content{margin:11px 13px;font:inherit}
.leaflet-bar a{color:var(--ink)}

.stopPop b{display:block;font-size:14px;color:var(--ink);margin-bottom:7px}
.stopPopRoute{
  display:flex;align-items:center;gap:7px;
  font-size:12px;color:#4a5668;padding:3px 0;
}
.stopPopRoute i{
  width:19px;height:19px;border-radius:50%;
  display:grid;place-items:center;
  font:800 11px/1 system-ui,-apple-system,"Segoe UI",sans-serif;
  font-style:normal;flex:0 0 auto;
}
.stopPopRoute em{margin-left:auto;font-style:normal;color:var(--muted);font-size:11px}

/* Route switcher under the map. */
.mapRoutes{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px;margin:18px 0 0}
.mapRouteGroup h3{
  margin:0 0 9px;font-size:15px;color:var(--ink);letter-spacing:-.01em;
}
.mapRouteGroup h3 span{font-weight:600;font-size:12px;color:var(--muted);margin-left:5px}
.mapRouteBtn{
  display:flex;align-items:center;gap:10px;width:100%;
  border:1px solid var(--line);border-radius:10px;background:#fff;
  padding:9px 11px;margin-bottom:6px;cursor:pointer;text-align:left;
  font:inherit;
}
.mapRouteBtn:hover{border-color:#b9c2cc}
.mapRouteBtn:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
.mapRouteBtn.isOff{opacity:.42}
.mapRouteBtn.isOff .mapRouteChip{filter:grayscale(1)}
.mapRouteBtn.is-building .mapRouteChip{opacity:.78}
.mapRouteBtn.is-proposed .mapRouteChip{filter:grayscale(1);opacity:.6}
.mapRouteChip{
  width:27px;height:27px;border-radius:50%;flex:0 0 auto;
  display:grid;place-items:center;font-style:normal;
  font:850 14px/1 system-ui,-apple-system,"Segoe UI",sans-serif;
}
.mapRouteLabel{display:flex;flex-direction:column;gap:2px;min-width:0}
.mapRouteLabel b{font-size:13px;color:var(--ink)}
.mapRouteLabel em{font-style:normal;font-size:11px;color:var(--muted)}

/* Desaturate the basemap so the route colours read as the subject.
   Tuned against the alternatives: CARTO Positron and Stadia both want an API
   key now, and Esri's Light Gray Canvas is keyless but so pale that street
   names disappear. Plain OSM tiles filtered here give the same quiet look with
   OSM's much better labelling, and depend on no one's billing decisions.
   Opacity is on the tile pane alone, so the route lines (which live in
   .leaflet-overlay-pane, a sibling) keep their full strength. Half-opacity
   tiles let the container's --paper background show through, which is what
   keeps the basemap warm — so the sepia nudge that used to do that job is no
   longer needed and full grayscale(1) is fine. */
.leaflet-tile-pane{
  opacity:.5;
  filter:grayscale(1) brightness(1.18) contrast(.88);
}


/* ---------------------------------------------------------------
   PédiBus marks: bars, not bullets.

   Walking lines are drawn as a rule above and below the mark rather
   than a ring around it, matching the printed network map. Both marks
   keep the 43px box the circles used (39px in the narrow layout), so
   the VéloBus and PédiBus cards still line up in the same column.
   Sits after the responsive block on purpose — it sets only colour and
   borders, never the box size, so the narrow-width rule still wins
   where it matters.
   --------------------------------------------------------------- */
.route.isWalk .mode{
  border:0;
  border-radius:0;
  border-top:3px solid var(--ink);
  border-bottom:3px solid var(--ink);
}
.route.isWalk .lineNo{
  background:transparent;
  color:#111;
  border-radius:0;
  border-top:7px solid var(--route-color);
  border-bottom:7px solid var(--route-color);
}


/* ------------------------------------------------------------------
   THE NETWORK, FULL-BLEED

   Map and routes are one page now, and the map is its headline: it runs
   edge to edge above the masthead and fades into the paper, the same
   move the photo band makes on the overview page.

   The band geometry is deliberately the hero's: height driven by
   viewport HEIGHT, not width, so the route cards below never slide up
   and down as you drag the window wider, with a vw ceiling so the band
   shrinks on narrow screens instead of dominating them.
   ------------------------------------------------------------------ */
.mapHero{
  /* Pinned: the network stays on screen and the timetables ride up
     underneath it. z-index beats main's 1 so the cards pass BEHIND the map
     rather than over it, and any non-auto value still makes this the
     stacking context that contains Leaflet's own 1000-level corners. */
  position:sticky;
  top:0;
  z-index:2;
  width:100%;
  /* Height only, no vw term. The photo band needs a vw ceiling because
     cropping its sides would cut the riders out of frame; a map has no
     fixed aspect to protect, and the ceiling only squashed it to 210px on
     a phone. Driving it purely from viewport height also means the cards
     below never move as you drag the window wider. */
  height:var(--mapBand);
  background:var(--paper);

  /* The band dissolves ITSELF rather than being covered by a sheet of paper.
     An overlay is opaque, so anything scrolling underneath was cut off at the
     edge. Masking makes the band's own tail genuinely transparent, so a card
     rising behind it shows through, drifts up onto the map, and is swallowed
     as the mask closes. Over empty paper it looks the same as the overlay did
     — these stops are its alpha, inverted. It also still dissolves Leaflet's
     attribution, which the overlay was doing. */
  -webkit-mask-image:linear-gradient(to bottom,
    #000 0%, #000 70%,
    rgba(0,0,0,.58) 80%,
    rgba(0,0,0,.16) 89%,
    rgba(0,0,0,.03) 95%,
    transparent 100%);
  mask-image:linear-gradient(to bottom,
    #000 0%, #000 70%,
    rgba(0,0,0,.58) 80%,
    rgba(0,0,0,.16) 89%,
    rgba(0,0,0,.03) 95%,
    transparent 100%);
}
.mapHero .geoMap{
  width:100%;
  height:100%;
}



/* The debug bar is absolutely positioned across the top of the page, so drop
   Leaflet's controls below it. If the debug bar is removed before launch this
   offset can go too. */
.mapHero .leaflet-top{top:40px}

/* Interchange node: a white rounded square with a black outline, both sized
   from the line's stroke width by the renderer. The marker's anchor is a
   zero-size box, so the inner element is centred with a transform and can be
   resized on zoom without the anchor drifting. */
.mapNode i{
  position:absolute;
  display:block;
  box-sizing:border-box;
  background:#fff;
  border-style:solid;
  border-color:#111;
  left:0;
  top:0;
  transform-origin:50% 50%;
  transform:translate(-50%,-50%);
}

/* Stop names, in black. The white halo is what keeps them readable where they
   cross a route line or a busy patch of basemap; --gap is set per zoom so the
   text clears a symbol that is itself growing. */
/* Direct child only: a descendant selector also caught .campusTagLine, which
   then inherited position:absolute and the -50% transform, so the badge sat
   on top of the name instead of under it and added no height. */
.stopLabel > span{
  position:absolute;
  left:0;
  top:0;
  transform:translate(var(--gap,10px), -50%);
  /* Wrapping is decided per label by the renderer, which measures the name
     first and only breaks it when it is long enough to need two lines. */
  white-space:nowrap;
  line-height:1.2;
  color:#111;
  font-size:11px;
  font-weight:700;
  letter-spacing:-.01em;
  text-shadow:0 0 3px #fff, 0 0 3px #fff, 0 0 2px #fff,
              1px 1px 2px #fff, -1px -1px 2px #fff;
}

/* The stop's cross-streets, under its name in the tap popup. */
.stopPopLoc{display:block;margin:2px 0 7px;color:var(--muted);font-size:11.5px;font-weight:600}

/* The outer end of a line: the interchange symbol made round, with the
   line's own colour as a disc at half its size. Sized per zoom by the
   renderer, like every other symbol. */
.mapTerm i{
  position:absolute;
  left:0;
  top:0;
  display:block;
  box-sizing:border-box;
  background:#fff;
  border-style:solid;
  border-color:#111;
  border-radius:50%;
  transform:translate(-50%,-50%);
}
.mapTerm i b{
  position:absolute;
  left:50%;
  top:50%;
  display:block;
  border-radius:50%;
  transform:translate(-50%,-50%);
}

/* The line's number, sitting just beyond its outer terminus. The white ring
   is a box-shadow rather than a border so the renderer's width and height
   stay the diameter of the disc itself. */
.mapEndBadge i{
  position:absolute;
  left:0;
  top:0;
  display:grid;
  place-items:center;
  box-sizing:border-box;
  border-radius:50%;
  font-style:normal;
  font-weight:850;
  line-height:1;
  letter-spacing:-.02em;
  box-shadow:0 0 0 1.5px #fff;
  transform:translate(-50%,-50%);
}

/* The campus is the one label that outranks the rest. */
.stopLabel.campus span{font-size:22px}

/* The readiness checklist on the overview page. */
.checklist{margin:0 0 18px;padding:0;list-style:none;display:grid;gap:7px}
.checklist li{position:relative;padding-left:26px;font-size:16px;line-height:1.5;color:var(--text)}
.checklist li::before{content:"";position:absolute;left:3px;top:.56em;width:9px;height:5px;
  border-left:2.5px solid var(--green);border-bottom:2.5px solid var(--green);
  transform:rotate(-45deg)}
@media(max-width:650px){.checklist li{font-size:15px}}

/* A deck line: the sentence directly under a chapter heading that says what
   the heading is actually about, sized between the heading and the body. */
.deck{font-size:20px;line-height:1.45;color:var(--ink);font-weight:650;
      margin:0 0 14px;letter-spacing:-.01em}
@media(max-width:650px){.deck{font-size:17px}}

/* ------------------------------------------------------------------
   The PédiBus chapter, over a photograph.

   .chapter is already full width — .landing has no max-width, only the
   children are constrained — so the picture bleeds without any 100vw
   trickery. The photograph is busy from edge to edge, so the type sits
   right and a scrim darkens that side to carry it, leaving the children
   on the left in the clear.
   ------------------------------------------------------------------ */
.chapterPhoto{
  position:relative;
  background:url(pedibus.jpg) center 42% / cover no-repeat;
  padding-block:clamp(78px,11vw,146px);
  border-block:0;
}
.chapterPhoto::before{
  content:"";
  position:absolute;
  inset:0;
  background:linear-gradient(to right,
    rgba(10,12,28,0)   0%,
    rgba(10,12,28,.12) 32%,
    rgba(10,12,28,.58) 60%,
    rgba(10,12,28,.86) 100%);
}
/* Always more air on the left than the right, so the block reads as a column
   pushed to one side rather than text pressed against both edges — which is
   what full-width right-aligned copy looks like once the window narrows. */
.photoInner{
  position:relative;
  text-align:right;
  padding-left:clamp(34px,16vw,170px);
}
/* The heading is capped at 18ch, so text-align alone only moves the words
   inside that box — the box itself has to be pushed over too. */
.photoInner h2{color:#fff;margin-left:auto}
.photoInner .body{
  font-size:clamp(17px,2.05vw,22px);
  color:rgba(255,255,255,.95);
  max-width:30em;
  margin-left:auto;
  text-shadow:0 1px 14px rgba(0,0,0,.5);
}
/* On dark, line 1's yellow finally gets to be the yellow. Bar to the right
   of the words, since the block is right-aligned. */
.photoInner .kicker{
  color:#ffbe00;
  flex-direction:row-reverse;
  justify-content:flex-start;
}

/* Narrow: the text spans most of the width, so the scrim has to cover it. */
@media(max-width:700px){
  .chapterPhoto::before{
    background:linear-gradient(to right,
      rgba(10,12,28,.30) 0%,
      rgba(10,12,28,.62) 45%,
      rgba(10,12,28,.88) 100%);
  }
}

/* On the photograph the navy button disappears into the scrim, so it flips:
   white plate, ink type. */
.photoInner .btnPrimary{background:#fff;color:var(--ink);margin-top:18px}
.photoInner .btnPrimary:hover{background:#fff;filter:brightness(.93)}

/* Jumping to a section shouldn't land it flush against the top edge. */
.routeSection{scroll-margin-top:calc(var(--mapBand) + 22px)}

/* Subtitle under the campus label on the map. Sized like an ordinary stop
   name, not like the 22px label it hangs from.

   The badge is wrapped in a block line of its own: the label carries
   white-space:nowrap so the badge text never breaks, and a block child is
   what puts it on the next line regardless. Keeping the badge itself
   inline-block lets it hug its text and follow the label's alignment. */
/* Both rows are blocks. Leaving the name as inline text meant the badge's
   block sat in the same line box and ended up beside it, overflowing the
   label's height rather than stacking under it. */
.campusName{display:block;line-height:1.1}
/* font-size/line-height zero kills the line's text strut: the badge is an
   inline-block, so the strut was reserving a full line of space above it and
   opening an 8px gap that no margin on the badge could close. */
.campusTagLine{display:block;font-size:0;line-height:0}
.campusTag{
  display:inline-block;
  /* Negative, to pull the tag up under the 22px name's line box. */
  vertical-align:top;
  margin-top:2px;
  white-space:nowrap;
  padding:1px 5px;
  border-radius:2px;
  background:var(--green);
  color:#fff;
  font-size:11px;
  font-weight:700;
  letter-spacing:0;
  line-height:1.35;
  text-shadow:none;
}

/* A centred chapter. The heading carries max-width:18ch and the body its own
   measure, so both need auto side margins as well as text-align to sit in
   the middle rather than just centring their text inside a left-hugging box. */
.chapterCentre{text-align:center}
.chapterCentre h2{margin-left:auto;margin-right:auto}
/* A third larger than body copy elsewhere: clamp(15,1.7vw,19) -> (20,2.26vw,25). */
.chapterCentre .body{
  max-width:36em;
  margin-left:auto;
  margin-right:auto;
  font-size:clamp(20px,2.26vw,25px);
}

/* ------------------------------------------------------------------
   The rider in the opener.

   Bottom-aligned to the divider, starting just right of the text column
   and running off the right edge, which .opener crops.

   The source photograph had a solid white studio background and no alpha.
   mix-blend-mode:multiply hid it in Chromium but did nothing at all in
   Safari, which will not blend reliably inside a stacking context like this
   one. So the background is cut out in the asset itself instead — a flood
   fill from the corners, which leaves white INSIDE the subject alone — and
   no blending is needed anywhere. WebP because alpha costs 65KB there
   against 567KB as a PNG.
   ------------------------------------------------------------------ */
.opener{overflow:hidden}
.openerKid{
  position:absolute;
  bottom:0;
  /* The text column's left edge, plus the width of the text beside it.

     The second term tracks the HEADLINE: the lede's box is far wider than
     its wrapped lines, so measuring that left a dead gap. "Your new ride"
     measures about 5.7x its own font size, and that size is clamped, so
     multiplying it keeps the rider tucked against the text at every width.

     It reads --h1Size, the same custom property the headline is sized from.
     This used to be its own clamp(31px, 6vw, 48px) — the generic h1 rule,
     not the opener's — so the offset was derived from a font size that
     appears nowhere on the page and stopped tracking the headline at all. */
  left:calc(max(17px, (100% - 816px) / 2) + 5.7 * var(--h1Size) + 16px);
  width:clamp(420px, 52vw, 820px);
  height:auto;
  pointer-events:none;
  user-select:none;
}
/* No breakpoint: its left edge is pinned just past the text column, so as the
   window narrows it simply slides off the right and .opener crops it. */

/* Shorter than this and half the view is less than the band's 280px floor —
   there is no height left to scroll the cards through. So the map stops being
   pinned and the whole page scrolls past it in the ordinary way, and the
   anchor offset drops with it since there is no band to clear.

   This lives at the END of the file on purpose: `.mapHero{position:sticky}`
   has the same specificity, so whichever is declared last wins, and from the
   top of the file this query was losing. */
@media (max-height: 559px){
  /* The cap is NOT lifted here. It used to be — the band was pinned to a flat
     280px on the reasoning that an unpinned map needs no room to scroll cards
     through. That holds for a squashed laptop window, but a phone in landscape
     is ~390px tall and lands in exactly this branch, where 280px is 72% of the
     screen. So the half-view rule applies everywhere and only the pinning
     stops. */
  .mapHero{position:static}
  .routeSection{scroll-margin-top:18px}
}

/* ------------------------------------------------------------------
   Narrow opener: make room beside the text for the rider.

   Below this width the two buttons side by side are the widest thing in
   the column — wider than the headline or the lede — so they are what
   pushes the image off screen. Stacking them frees about 150px. The lede
   then shrinks with the viewport to roughly two-thirds of its size, and
   because its measure is set in `ch` the block narrows with the type.
   ------------------------------------------------------------------ */
@media (max-width: 570px){
  /* The headline is left alone — it is sized right and should not gain lines.
     What gives way instead is the buttons, which stack. Type size is left
     alone throughout — the lede keeps its usual clamp — and the rider scales
     down only slightly.

     Shrinking the rider matters even though its left edge is fixed by the
     headline: the visible strip is the same either way, but a smaller image
     fits the girl and the bike into that strip instead of just a wheel. */
  .openerActions{flex-direction:column;align-items:flex-start}

  /* The rider is bottom-anchored and short here, so vertically it sits beside
     the LEDE's last line and the stacked buttons — it never reaches the
     headline at all. Deriving its offset from the headline was therefore
     holding it much further right than anything it could actually collide
     with. Below the breakpoint it tracks the viewport instead, with a floor
     that keeps it clear of the buttons. */
  .openerKid{
    /* 74vw is 422px at the 570px breakpoint, so it meets the 420px it was
       already at and the transition is continuous. The floor is 324: 10%
       below the 360 it used to bottom out at, reached around 438px wide. */
    width:clamp(324px, 74vw, 420px);
    left:calc(17px + max(150px, 42vw));
  }

  /* The headline's 44px floor is what pinned the rider: below ~489px the
     type stops shrinking, so the offset derived from it stops moving too.
     A lower floor lets it keep easing down — no extra wrapping, just a
     slightly smaller headline on a phone — and because the offset reads the
     same variable, the rider follows it left automatically. */
  .opener{--h1Size:clamp(30px, 9vw, 92px)}
}

/* ---- One route, on its own page ---- */
/* The map is the first thing to go: on a phone it is most of the screen, and
   the point of this view is the card. */
body.isSoloRoute .mapHero{display:none}
body.isSoloRoute main{margin-top:0;padding-top:14px}

/* Nothing collapses here, so the affordances that said it might are gone:
   the card is always open and the chevron and hover lift come off with it. */
.route.soloRoute{cursor:default}
.route.soloRoute .summary{cursor:default}
.route.soloRoute:hover{transform:none;box-shadow:none}
/* The name is an h1 on this page rather than a button. .name already sets
   the size, weight and colour, so only the heading's own margin has to go —
   the previous reset to `inherit` threw the card's typography away with it. */
.route.soloRoute h1.name{margin:0}

.backToRoutes{display:inline-flex;align-items:center;gap:6px;
  margin:0 0 12px;padding:6px 2px;font-size:13px;font-weight:750;
  color:var(--muted);text-decoration:none}
.backToRoutes::before{content:"\2039";font-size:17px;line-height:1}
.backToRoutes:hover{color:var(--ink)}

/* ---- Volunteer dialog fields ---- */
.vField{margin-bottom:12px}
/* The head block sets its own spacing; the first field needs to clear it. */
.vField:first-of-type{margin-top:16px}
.vField label{display:block;font-size:12px;font-weight:700;color:var(--muted);
              letter-spacing:.04em;text-transform:uppercase;margin-bottom:5px}
.vField input{width:100%;padding:10px 12px;border:1px solid var(--line);
              border-radius:9px;font:inherit;font-size:15px;background:#fff}
.vField input:focus{outline:2px solid var(--ink);outline-offset:1px;border-color:var(--ink)}
/* Off-screen rather than display:none — some bots skip hidden fields. */
.vTrap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.vPrivacy{font-size:11.5px;line-height:1.45;color:var(--muted);margin:14px 0 4px}
.vError{margin:8px 0 0;color:#b00;font-size:13px;font-weight:600}

/* ---- "Your volunteering" page ---- */
[data-page="me"] main{max-width:760px;padding-top:40px;margin-top:0}
.meTitle{font-size:clamp(28px,4.4vw,40px);color:var(--ink);line-height:1.02;
         letter-spacing:-.03em;margin:0 0 10px}
.meLede{font-size:16px;line-height:1.55;color:#3f4b5c;max-width:46ch;margin:0 0 26px}
.meLookup{background:#fff;border:1px solid var(--line);border-radius:12px;
          padding:18px 18px 20px;margin-bottom:26px;max-width:380px}
.meGreeting{font-size:16px;color:var(--ink);font-weight:650;margin:0 0 18px;
            display:flex;align-items:center;gap:10px}
/* Signing in hides the lookup form, so this is the way back to it — for a
   shared computer, or for somebody who signed up twice. */
/* Nothing here was compulsory at signup, so the asking has to happen
   somewhere. This is it: present until the record is complete, and loud
   enough to notice without looking like an error. */
.meNag{margin:0 0 16px;padding:12px 14px;border-radius:10px;
  background:#fff6d6;border:1px solid #f0d478;color:#4a3a06;
  font-size:14px;line-height:1.45;font-weight:600}
.meMineGap{color:#8a6a06;font-weight:750}
.meSwitch{margin-left:10px;border:0;background:none;padding:0;font:inherit;
  font-size:13px;font-weight:650;color:var(--muted);cursor:pointer}
.meSwitch:hover{color:var(--ink);text-decoration:underline}
.meSaved{font-size:12px;font-weight:700;color:var(--green);
         background:#e8f5ee;border-radius:999px;padding:3px 9px}
.meGroup{font-size:15px;color:var(--ink);margin:22px 0 8px}
.meBadge{width:24px;height:24px;border-radius:50%;display:grid;place-items:center;
         font-size:11.5px;font-weight:850;flex:0 0 auto}

.meRoute{background:#fff;border:1px solid var(--line);border-radius:10px;
         margin-bottom:8px;overflow:hidden}
.meRoute summary{display:flex;align-items:center;gap:10px;padding:11px 14px;
                 cursor:pointer;list-style:none;font-weight:650;color:var(--ink)}
/* The triangle is drawn rather than left to the browser, so it sits on the
   left at a consistent size in every engine. */
.meRoute summary::-webkit-details-marker{display:none}
.meRoute summary::before{content:"";width:0;height:0;flex:0 0 auto;
  border-left:6px solid var(--muted);border-top:5px solid transparent;
  border-bottom:5px solid transparent;transition:transform .15s ease}
.meRoute[open] summary::before{transform:rotate(90deg)}
.meRouteName{flex:1}
.meMine{font-size:12px;font-weight:600;color:var(--muted)}

.meRow{display:flex;align-items:center;gap:12px;padding:9px 14px 9px 40px;
       border-top:1px solid #f0f0ee;font-size:13.5px}
.meRowCaptain{background:#fafaf9}
.meRowLabel{flex:0 0 96px;font-weight:650;color:var(--ink)}
/* Two stacked lines: where the morning stands, then who is on it. */
.meRowInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.meRowStatus{font-size:12.5px;font-weight:650;line-height:1.35}
.meRowStatus.isOk{color:var(--green)}
.meRowStatus.isWait{color:#a15b00}
.meRowStatus.isNone{color:var(--muted)}
.meRowWho{color:#3f4b5c;min-width:0;line-height:1.35}
.meRowCaptain .meRowWho{flex:1}
.meRowWho em{color:#9a9b96;font-style:italic}
.meCheck{display:flex;align-items:center;gap:7px;cursor:pointer;flex:0 0 auto;
         font-size:12.5px;font-weight:600;color:var(--muted)}
.meCheck input{width:19px;height:19px;accent-color:var(--ink);cursor:pointer}

/* The padlock. Everybody sees the state; only a captain gets a button, and
   the two look deliberately alike so the glyph reads as the same thing. */
.meLock{display:flex;align-items:center;gap:5px;flex:0 0 auto;
        font:inherit;font-size:12px;font-weight:700;
        border:1px solid transparent;border-radius:999px;padding:4px 9px 4px 7px;
        background:none;color:#a15b00}
.meLock.isOpen{color:var(--green)}
button.meLock{cursor:pointer;border-color:#e6e2d6;background:#fff}
button.meLock:hover:not(:disabled){border-color:currentColor;background:#fffaf0}
button.meLock.isOpen:hover:not(:disabled){background:#f1faf4}
button.meLock:disabled{color:#c8c9c4;cursor:default;background:#fafaf9;border-color:#f0f0ee}
.meLock.isStatic{padding-left:9px;padding-right:9px}
.meLockIcon{flex:0 0 auto}

@media(max-width:560px){
  .meRow{padding-left:26px;gap:8px;flex-wrap:wrap}
  .meRowLabel{flex:0 0 74px}
  .meCheck span,button.meLock span{display:none}
  button.meLock{padding:4px 7px}
}

/* The whole cell is the target, so it is usable with a thumb. */
.meDay{display:grid;place-items:center;cursor:pointer}
.meDay input{width:20px;height:20px;accent-color:var(--ink);cursor:pointer}
.meDay span{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
.meNote{margin:12px 0 0;color:var(--green);font-size:14px;font-weight:650}
[data-page="me"] .btnPrimary{margin-top:20px}

/* The Route Captain offer, inside the volunteer dialog. */
.vCaptain{display:flex;gap:10px;align-items:flex-start;margin:16px 0 4px;
          padding:12px;border:1px solid var(--line);border-radius:10px;
          background:#fafaf9;cursor:pointer}
/* An author `display` beats the UA's [hidden]{display:none}, so hiding this
   block from script needs saying explicitly. */
.vCaptain[hidden]{display:none}
.vCaptain input{width:18px;height:18px;margin-top:1px;accent-color:var(--ink);flex:0 0 auto}
.vCaptain span{font-size:12.5px;line-height:1.5;color:var(--muted)}
.vCaptain b{display:block;color:var(--ink);font-size:13.5px;margin-bottom:2px}

/* Captain column and roster on the "Your volunteering" page. */
/* Seven columns will not fit a phone, so the grid scrolls sideways rather
   than letting the Captain column fall off the edge unseen. */
.meTableWrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.meTable{min-width:420px}
.meCapCell{border-left:1px solid #ececea;background:#fafaf9}
.meRoster{background:#fff;border:1px solid var(--line);border-radius:10px;
          padding:14px 16px;margin-bottom:12px}
.meRoster h3{display:flex;align-items:center;gap:8px;font-size:14.5px;
             color:var(--ink);margin:0 0 8px}
.meCaptains{margin:0 0 10px;font-size:13.5px;color:#3f4b5c}
.meCaptains b{color:var(--ink)}
.meRosterDays{list-style:none;margin:0;padding:0;display:grid;gap:4px}
.meRosterDays li{display:flex;gap:10px;font-size:13.5px;color:#3f4b5c}
.meRosterDays .rd{flex:0 0 38px;font-weight:700;color:var(--ink)}
.meRosterDays em{color:#9a9b96;font-style:italic}

/* ---- Admin (shares the /me/ route blocks) ---- */
.adminBar{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.adminOut{font-size:12.5px;color:var(--muted);text-decoration:none}
.adminOut:hover{color:var(--ink);text-decoration:underline}
.adminWarn{color:#a15b00;font-weight:700}
/* Name over contact details, so a row stays scannable by name while the
   phone number is still there when you need to call somebody. */
.adminWho{display:inline-block;margin:0 14px 4px 0;vertical-align:top}
.adminWho small{display:block;font-size:11.5px;color:var(--muted);font-weight:500}
.adminCount{flex:0 0 auto;font-size:12.5px;color:var(--muted)}
.adminDate{flex:0 0 74px;text-align:right;font-size:12px;color:#9a9b96}
.adminPeople .meRow{padding-left:14px}
.adminPeople .meRow:first-child{border-top:0}

/* Remove buttons in admin. Quiet until hovered, because they are destructive
   and sit next to names you are usually only reading. */
.adminDrop{border:0;background:transparent;color:#c0c1bc;cursor:pointer;
           font-size:15px;line-height:1;padding:0 6px 0 0;vertical-align:baseline}
.adminDrop:hover{color:#b00}
.adminDrop:disabled{opacity:.4;cursor:default}

/* What they typed in step one, echoed back in step two. */
.vGiven{margin:4px 0 14px;font-size:13px;color:var(--muted)}

/* The confirmation at the top of the optional second step. The signup is
   already saved by the time this is on screen, so it says so rather than
   looking like a form still waiting to be sent. */
.vDone{margin:14px 0 2px;font-size:14px;font-weight:750;color:var(--green)}

/* Leaving without filling anything in is a normal way to finish here, so it
   gets a real button — just a quieter one than Save. */
.riderSkip{width:100%;margin-top:8px;border:0;background:none;color:var(--muted);
  padding:8px;font:inherit;font-size:13px;font-weight:650;cursor:pointer}
.riderSkip:hover{color:var(--ink);text-decoration:underline}

/* "Your details" block on /me/. */
.meProfile summary .meRouteName{flex:0 0 auto;margin-right:10px}
.meProfile .meMine{flex:1;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.meProfileBody{padding:14px 16px 18px;border-top:1px solid #f0f0ee}
.meProfileBody .btnPrimary{margin-top:14px}
/* Label beside field, three rows. One grid for all of them, so the inputs
   line up with each other however long the longest label is. */
.meFields{display:grid;grid-template-columns:max-content minmax(0,1fr);
          align-items:center;gap:10px 12px;max-width:440px}
.meFields label{margin:0;font-size:12.5px;font-weight:700;color:var(--muted)}
.meFields input{width:100%;padding:9px 12px;border:1px solid var(--line);
                border-radius:9px;font:inherit;font-size:15px;background:#fff}
.meFields input:focus{outline:2px solid var(--ink);outline-offset:1px;border-color:var(--ink)}
.meFields .isMissing{border-color:#e0c05a;background:#fffdf3}
