/* Campus Ride — one column, phone first, white.
 *
 * Black on white, the way Uber's app actually is: the ground is white, the one
 * primary action on a screen is a solid black fill, and colour is spent only
 * where it carries a meaning no word could carry faster.
 *
 * Four lamps, one job each, and nothing else is allowed to be coloured:
 *   amber  — pending YOUR action, or live right now
 *   green  — confirmed
 *   blue   — a human has checked who this person is
 *   red    — one tap from something irreversible
 *
 * Amber used to be the brand colour AND the primary button AND the live state
 * at once, which is why none of the three registered. It is the pending lamp
 * only now; black took the button, and the brand mark is monochrome.
 *
 * A lamp needs two values on white, not one. Amber and green are perfectly
 * legible as a FILL with dark text on them and illegible as text on white
 * (#FFC043 on white is 1.7:1), so each has a `-solid` for fills and a darkened
 * base for text and hairlines. Using one value for both is how the pending
 * pill ends up unreadable in daylight, which is the light this app is used in.
 *
 * Every number read out loud at a kerb is set in a monospaced face.
 */

.cr {
	/* Ground. White, with a single light grey for anything recessed — the tile
	   colour behind Uber's search bar, chips and account rows. */
	--bg:          #ffffff;
	--surface:     #ffffff;
	--sunken:      #f2f2f3;
	--line:        #e6e6e7;
	--line-strong: #c4c6c8;

	/* The disc behind the countdown once it sits over map tiles. Not a
	   token that existed before because nothing in the app put text over
	   imagery; --surface cannot do this job because the veil has to be
	   partly transparent or it reads as a hole punched in the map. */
	--ring-veil: rgba(255,255,255,.88);

	--text:   #000000;
	--text-2: #56585c;
	--text-3: #85888c;

	/* The primary action. One per screen, and it is the loudest object in the
	   system precisely because it is the only black fill on it. */
	--pri:       #000000;
	--pri-ink:   #ffffff;
	--pri-hover: #26282b;

	/* Pending your action, or live right now. Uber's yellow as the fill; a
	   darkened amber for text and edges, which the fill cannot do on white. */
	--signal:       #96620a;
	--signal-solid: #ffc043;
	--signal-ink:   #241800;
	--signal-tint:  rgba(255,192,67,.20);
	--signal-edge:  rgba(214,155,25,.55);

	/* Confirmed, and only confirmed. Uber's green. */
	--go:       #1c7c4d;
	--go-solid: #3aa76d;
	--go-ink:   #ffffff;
	--go-tint:  rgba(58,167,109,.13);
	--go-edge:  rgba(58,167,109,.45);

	/* One meaning: a human checked this person's ID. Nothing else, ever. Uber's
	   blue clears 4.4:1 on white, so it needs no second value. */
	--trust:      #276ef1;
	--trust-tint: rgba(39,110,241,.10);
	--trust-edge: rgba(39,110,241,.40);

	/* Never at rest — only on the second, confirming tap. Uber's red. */
	--alert:      #c93a2b;
	--alert-tint: rgba(212,67,51,.09);
	--alert-edge: rgba(212,67,51,.35);

	/* A map symbol, not a UI colour. Survives all four tile styles.
	   §10 asks for the casing/core pair to react to the TILE style (black
	   core on light tiles, bright core on dark tiles) — a different axis
	   from the app's own light/dark palette, and one CR.tiles only resolves
	   server-side per site, not per app-palette. Reacting to it correctly
	   would mean reading CR.tiles in map.js and is real tile-logic risk for
	   a cosmetic gain; not attempted here. --route-case just tokenises the
	   casing's previous hardcoded rgba(0,0,0,.6) literal — same hygiene fix
	   as the map-chrome tokens, one value, not a tile-aware one. */
	--route:      #5b5bf0;
	--route-case: rgba(0,0,0,.6);

	/* ISSUE 4. The shared journey to the destination, kept deliberately off
	   both --route (his road TO the pickup — a different question, "how does
	   he get to me") and --go/--go-solid (her walk, and the bearing arrow —
	   green already means something on this screen). A map symbol, not a UI
	   colour, for the same reason --route is: it has to survive all four
	   tile styles, so one value is correct, not an oversight. Exempt from
	   the dark-palette check alongside --route/--route-case, same reasoning,
	   same place (Selftest::test_assets()). */
	--ride:      #0f9b8e;
	--ride-case: rgba(0,0,0,.6);

	/* Monogram avatars (§11.3) — a neutral, desaturated ramp hashed per name.
	   Deliberately far from amber/green/blue/red so none can be misread as a
	   status. Moderately dark here so white --pri-ink clears contrast. */
	--mono-1: #6b6f76;
	--mono-2: #75695f;
	--mono-3: #5c6b62;
	--mono-4: #5a6672;
	--mono-5: #6d6270;

	--r-ctl:   12px;
	--r-card:  16px;
	--r-sm:    8px;
	--r-pill:  999px;
	/* Not yet wired into .cr-sheet's own literal or any map-frame rule — that
	   substitution is component work (§7.9 / §7 generally), not a token add. */
	--r-sheet: 18px;
	--r-map:   14px;

	/* Legacy aliases. Older markup and any stray rule still referring to
	   --accent keeps working instead of falling back to unstyled. */
	--accent:  #ffc043;
	--accent2: #f0ad25;
	--danger:  #d44333;
	--card:    #ffffff;
	--muted:   #56585c;
	--radius:  16px;

	--font-ui:   -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--font-data: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas, monospace;

	/* Elevation — Uber's shadows are tight, low-opacity, and vertical-only.
	   Not yet wired into any rule (that's component work, §7); landing the
	   values now so Phase 2 has them to consume. */
	--sh-1:     0 1px 2px rgba(0,0,0,.06), 0 1px 3px rgba(0,0,0,.04);
	--sh-2:     0 2px 6px rgba(0,0,0,.07), 0 4px 12px rgba(0,0,0,.05);
	--sh-3:     0 6px 16px rgba(0,0,0,.09), 0 12px 32px rgba(0,0,0,.07);
	--sh-sheet: 0 -8px 40px rgba(0,0,0,.14);

	/* Motion — one easing for entrances, one for exits, three durations.
	   Palette-independent (a curve and a duration have no colour), so this is
	   the only place these are defined — test_assets() check 3 exempts the
	   --dur- and --ease- prefixes from the dark-palette redefinition check. */
	--ease-out:    cubic-bezier(.2,0,0,1);
	--ease-in:     cubic-bezier(.4,0,1,1);
	--ease-spring: cubic-bezier(.2,.8,.2,1);
	--dur-1: 120ms; /* state change on a control */
	--dur-2: 240ms; /* element enters or leaves */
	--dur-3: 380ms; /* sheet, screen, map reframe */

	/* Space — 4pt base, 8pt rhythm. Also palette-independent — see the motion
	   comment above. One token per line: test_assets() check 2 only
	   recognises a definition at the start of a line. */
	--sp-1: 4px;
	--sp-2: 8px;
	--sp-3: 12px;
	--sp-4: 16px;
	--sp-5: 20px;
	--sp-6: 24px;
	--sp-7: 32px;
	--sp-8: 40px;

	/* Type scale — §5.2. Palette-independent. */
	--t-display: 34px;
	--t-title:   24px;
	--t-head:    19px;
	--t-body:    16px;
	--t-sub:     14px;
	--t-cap:     12.5px;

	background: var(--bg);
	color: var(--text);
	font: 16px/1.55 var(--font-ui);
	min-height: 70vh;
	padding: 18px 14px 52px;
	box-sizing: border-box;

	/*
	 * Chrome on Android paints its own translucent rectangle over
	 * whatever was tapped unless this is set, and no :active rule
	 * anywhere in this file can override a browser default that is not
	 * itself a CSS rule on the element — a live-device review is what
	 * found this; it does not reproduce in a desktop browser, only on
	 * the hardware the app is actually used on.
	 */
	-webkit-tap-highlight-color: transparent;
}

/*
 * A second palette, for individual screens only — never the whole app.
 *
 * This started as a mock built against a screenshot of Uber's own dark app,
 * shown to the person who runs this project before any of it touched real
 * code. What came back after reading the actual screens against it: the
 * board and the meet screen are the two worth reworking, the light system
 * above is a deliberate choice everywhere else ("the way Uber's app actually
 * is", per the file header) and was never asked to change, and redoing
 * contrast across every one of this file's rules for a change nobody
 * requested on e.g. the account screen would be work spent on the wrong
 * thing. So this is values only: every rule in this file already reads its
 * colour off a --token rather than a literal one (the one exception, a mask
 * gradient's alpha stop, isn't a colour), which is what makes a second
 * palette a block of new values rather than a second copy of every rule.
 *
 * Nothing writes this class today. screenBoard() used to toggle `cr-dark`
 * on `#cr-app` for a whole screen and screenMeet()'s live-trip card used to
 * bake it straight into its own template string — both gone as of §3d.1's
 * "white throughout" ruling, the card last, since it was the one component
 * embedded inside a screen (Activity) that stays light either side of it,
 * which is exactly why grepping `root.classList` for a lingering toggle
 * never found it. test_assets() check 6 exists so a third place cannot
 * hide the same way: it fails outright if `cr-dark` ever appears inside a
 * `class="…"` string in app.js again.
 *
 * The block stays defined rather than deleted for two reasons: check 3
 * depends on a dark redefinition existing for every light colour token, and
 * a future real dark mode costs one class toggle instead of a re-audit.
 * Whichever screen re-enables it will need to sit on an element that ALSO
 * carries its own explicit `background`/`color`, not merely inherit them,
 * or the fix is invisible one level down. `#cr-app.cr` already declares
 * both, two rules up. `.cr-card` declares `background` but never `color` —
 * text inside it is normally just inherited from `#cr-app`'s already-
 * resolved light value, which a redefined --text on a *descendant* cannot
 * reach backwards. The explicit `color: var(--text)` below exists for
 * exactly that case; `background` is deliberately left to whatever
 * component rule already owns it (`.cr` or `.cr-card`), so this can't
 * out-specificity a card's own `var(--surface)` fill and flatten it to the
 * page's `--bg`.
 */
.cr.cr-dark,
.cr-dark {
	--bg:          #0b0e12;
	--surface:     #161b20;
	--sunken:      #1e242b;
	--line:        #2a3038;
	--line-strong: #3a424c;

	--ring-veil: rgba(22,27,32,.86);

	--text:   #f5f6f7;
	--text-2: #9aa3ad;
	--text-3: #6f7780;

	/* The primary action inverts too: white fill on black ground reads the
	   same way — the one loudest object on the screen — as black on white did. */
	--pri:       #f5f6f7;
	--pri-ink:   #0b0e12;
	--pri-hover: #d7dade;

	--signal:       #ffb020;
	--signal-solid: #ffb020;
	--signal-ink:   #241800;
	--signal-tint:  rgba(255,176,32,.16);
	--signal-edge:  rgba(255,176,32,.45);

	--go:       #3ddc84;
	--go-solid: #3ddc84;
	--go-ink:   #0b0e12;
	--go-tint:  rgba(61,220,132,.14);
	--go-edge:  rgba(61,220,132,.4);

	--trust:      #5aa8ff;
	--trust-tint: rgba(90,168,255,.14);
	--trust-edge: rgba(90,168,255,.4);

	--alert:      #ff6b5e;
	--alert-tint: rgba(255,107,94,.12);
	--alert-edge: rgba(255,107,94,.4);

	/* Lighter, not a token copy — dark mode's --pri-ink is near-black
	   (#0b0e12), so the ramp needs to invert lightness to keep contrasting
	   against it, the same reasoning as the shadow tokens below. */
	--mono-1: #9a9ea5;
	--mono-2: #a89d90;
	--mono-3: #92a199;
	--mono-4: #8f9aa8;
	--mono-5: #a094a3;

	/* A black shadow reads as almost nothing against a near-black ground —
	   §5.4's reason borders carry more of the separation job in dark and
	   shadows carry less. Raising the alpha (roughly x4-5 over the light
	   values, not just a token copy) is what keeps a floating surface
	   readable at all rather than a token that resolves but does nothing. */
	--sh-1:     0 1px 2px rgba(0,0,0,.32), 0 1px 3px rgba(0,0,0,.24);
	--sh-2:     0 2px 6px rgba(0,0,0,.38), 0 4px 12px rgba(0,0,0,.30);
	--sh-3:     0 6px 16px rgba(0,0,0,.46), 0 12px 32px rgba(0,0,0,.36);
	--sh-sheet: 0 -8px 40px rgba(0,0,0,.55);

	color: var(--text);
}

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

/* The host theme styles headings, buttons and form controls globally, and on
   Woostify that turns the card headings a washed out grey against the dark
   card. Take them back explicitly rather than hoping the cascade wins. */
.cr h1,
.cr h2,
.cr h3 {
	color: var(--text);
	font-family: inherit;
	text-transform: none;
	letter-spacing: -.02em;
}

/* Not amber. A link is not a pending action, and letting it wear the pending
   lamp is exactly how amber stopped meaning anything. */
.cr a { color: var(--text); }

.cr button,
.cr input,
.cr select,
.cr textarea {
	font-family: inherit;
	line-height: 1.4;
	text-transform: none;
	letter-spacing: normal;
}

/* Every number said out loud or checked against reality. Tabular so 4729 can
   never be misread as 4720 at arm's length in the dark. */
.cr-d,
.cr-pin b,
.cr-karma b,
.cr-facts b,
.cr-plate {
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	letter-spacing: -.02em;
}

/*
 * §12.2: the very first paint, server-rendered by App::render() before any
 * JS runs — App.php's own markup (`<div class="cr-boot">Loading…</div>`)
 * is outside this pass's file-touch list for that file (only share() and
 * head() are permitted there), so this has to be pure CSS against the
 * existing text node. The text stays for screen readers (still literally
 * "Loading…", just colour: transparent) while a pulsing brand mark —
 * the same two-chevron shape MARK draws in app.js, redrawn as a mask
 * data-URI the same way .cr-glyph-bike already does — takes its place
 * visually, absolutely centred so it doesn't depend on the text's own box.
 */
.cr-boot {
	position: relative;
	color: transparent;
	text-align: center;
	padding: 80px 0;
	user-select: none;
}
.cr-boot::before {
	content: '';
	position: absolute;
	left: 50%;
	top: 50%;
	width: 40px;
	height: 40px;
	margin: -20px 0 0 -20px;
	background: var(--text);
	-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Cpath d='M13 7L22 16L13 25' stroke='black' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M5 11L10 16L5 21' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round' opacity='.45'/%3E%3C/svg%3E") center / contain no-repeat;
	mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Cpath d='M13 7L22 16L13 25' stroke='black' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M5 11L10 16L5 21' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round' opacity='.45'/%3E%3C/svg%3E") center / contain no-repeat;
	animation: cr-boot-pulse 1.6s ease-in-out infinite;
}
@keyframes cr-boot-pulse {
	0%, 100% { opacity: .3; }
	50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
	.cr-boot::before { animation: none; opacity: .8; }
}

/*
 * screenCode()'s "We sent a code to X" — a typewriter cursor after the
 * email, same trick the mock uses on both its own boot mark and this exact
 * sentence. Reuses cr-boot-pulse (no new keyframe for what is the same
 * on/off idea) with a hard steps(1) timing instead of the smooth ease the
 * boot mark uses — a blinking cursor reads as "typed"; a fading one reads
 * as "loading", which is the wrong sentence here.
 */
.cr-typecursor {
	display: inline-block;
	width: 1.5px;
	height: 1em;
	margin-left: 2px;
	vertical-align: -2px;
	background: currentColor;
	animation: cr-boot-pulse 1s steps(1) infinite;
}
@media ( prefers-reduced-motion: reduce ) {
	.cr-typecursor { animation: none; opacity: .8; }
}

.cr-wrap { max-width: 460px; margin: 0 auto; }

/* ------------------------------------------------------------------ chrome */

/*
 * Persistent and close to invisible. The mark sits top left and nothing else
 * competes with it; anything global goes in the right slot as a single control,
 * never a row of them. Sign out used to live here on every screen in the app —
 * it is an Account row now, and only screens with no tab bar to reach Account
 * from still carry it.
 */
/* §7.1's "disappear on scroll" is deliberately NOT done — it needs either a
   scroll listener (ruled out by the brief itself) or CSS scroll-driven
   animations, whose support on the Android WebViews this app actually runs
   on isn't something to gamble on for real users. Static header, sized per
   spec; the hide-on-scroll behaviour is left for the dedicated motion phase. */
.cr-top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	min-height: 56px;
	box-sizing: border-box;
	margin-bottom: 14px;
}

.cr-brand {
	display: flex;
	align-items: center;
	gap: 7px;
	font-size: 17px;
	font-weight: 700;
	letter-spacing: -.025em;
}

/* Monochrome. A wordmark that wears the pending lamp tells you something is
   pending every single time the app is open. */
.cr-brand span { color: var(--text); }

/* The mark: a leading chevron carrying a smaller one behind it. Inline so it
   costs no request and cannot 404 on a slow connection. */
.cr-logo { width: 21px; height: 21px; flex: none; display: block; color: var(--text); }

/*
 * The page title on the screens that have one — Activity and Account. Uber
 * leads both with a large left-aligned name and no card around it, which is
 * what makes a tab feel like a destination rather than another panel.
 */
.cr-page {
	font-size: var(--t-display);
	font-weight: 700;
	letter-spacing: -.035em;
	line-height: 1.1;
	margin: 2px 0 16px;
}

.cr-out {
	background: none;
	border: 0;
	color: var(--text-2);
	font-size: 13.5px;
	cursor: pointer;
	padding: 6px 2px;
}
.cr-out:hover { color: var(--text); }

/* ------------------------------------------------------------------- cards */

/* Borderless by default — the reference's content cards are a flat --sunken
   fill with no hairline at all, and separation comes from the gap between
   them rather than an edge. Anything that sits ABOVE the page rather than
   in it (the action card, the sheet, a selected option) overrides this back
   to --surface plus a shadow or a border; see the modifiers below. */
/*
 * margin-bottom ON THE CARD ITSELF, NOT .cr-card + .cr-card - a live-device
 * review found zero space between Activity's #cr-board card and its
 * results card, and the cause was the selector, not a missing value:
 * #cr-board's card and #cr-results' card are in different parents
 * (#cr-live-meet sits between the two <div>s in screenRides()' own
 * template), so the two cards are never adjacent siblings and the
 * `+` selector can never match them, no matter how the markup nests
 * inside either wrapper. An unconditional bottom margin cannot miss this
 * way - it does not care what, if anything, comes after.
 */
.cr-card {
	background: var(--sunken);
	border: 1px solid transparent;
	border-radius: var(--r-card);
	padding: 16px 15px;
	margin-bottom: 16px;
}
/*
 * SCOPED TO .cr-wrap's OWN DIRECT CHILDREN, not every .cr-card anywhere -
 * a bare .cr-card:last-child would also zero the pledge card #cr-board
 * builds (drawBoard(), untouched by this pass), since that card is
 * #cr-board's OWN last child regardless of what else the screen shows
 * after #cr-board itself. :last-child only ever answers "last among my
 * own siblings" - never "last visible card on the screen" - so trimming
 * it unscoped would reopen the exact zero-gap bug this rule exists to
 * close, on the one case (a pledge with no open post) where #cr-board's
 * card is nested rather than sitting where the results card does,
 * directly in .cr-wrap. Trimming only there is where "last card on a
 * simple screen" and "last child of its own parent" actually agree.
 */
.cr-wrap > .cr-card:last-child { margin-bottom: 0; }

/* Anything the person has to act on gets a stronger edge, so "this one
   matters" registers before any colour is processed. */
/* The card that carries the main action on a screen. A MODIFIER on .cr-card,
   nothing else — worn by the composer, the confirmed-ride banner, the alert and
   the results card, and it sets exactly one property on purpose.

   In 1.37.0 the compact button row on the meeting card was also called .cr-act,
   and a bare `.cr-act { display:flex; flex-direction:column; align-items:center;
   font-size:11px; padding:8px 2px }` therefore applied to all four of these
   cards as well. They became centred flex columns styled as though each were one
   small icon button: the composer's rows shrank to fit, its WHEN chips came out
   one pixel narrower than "Later today" needs, and the labels ellipsised. One
   line of CSS, four cards, every symptom on that screen.

   The buttons are .cr-way now. Do not name anything `.cr-act` again. */
/* Sits above the page rather than in it — surface, not sunken, and a shadow
   rather than a heavy border (§5.4: never both on the same element). */
.cr-card.cr-act {
	background: var(--surface);
	border-color: transparent;
	box-shadow: var(--sh-1);
}

.cr-card.cr-quiet { background: transparent; }

/* §8.5's waiting state — centred, generous space, nothing spinning. */
.cr-review-hero { text-align: center; padding-top: 40px; padding-bottom: 40px; }
.cr-review-hero .cr-pills { justify-content: center; }
.cr-review-hero .cr-dots { max-width: 220px; margin-left: auto; margin-right: auto; }

/* .cr-card now defaults to --sunken itself, so this used to be a second grey
   on top of a grey — invisible. A step of separation the other direction. */
.cr-card.cr-sunk {
	background: var(--surface);
	border-color: transparent;
}

.cr h2 { font-size: 19px; margin: 0 0 6px; font-weight: 650; line-height: 1.25; }
.cr h3 { font-size: 15.5px; font-weight: 650; margin: 0 0 4px; }
.cr p  { margin: 0 0 14px; color: var(--text-2); font-size: 14px; }
.cr p.cr-tight { margin-bottom: 8px; }
.cr p b { color: var(--text); font-weight: 600; }

/* §5.2's eyebrow spec — shared by .cr-lab, .cr-tcard-lab, .cr-group and
   Activity's .cr-divide, which all play the identical "quiet section label"
   role. Each keeps its own structural properties (display, margin, the
   divider's trailing hairline); only the typography is unified here. */
.cr-lab {
	display: block;
	font-size: var(--t-cap);
	font-weight: 600;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--text-3);
	margin-bottom: 7px;
}

/*
 * §8.7's Home rebuild — a launcher, not a form with a heading. The
 * approved mock draws a bell in place of the wordmark, a two-line
 * headline, a 2x2 shortcut grid and this account's own board activity,
 * all sitting directly on white; the compose form below (search pill
 * through the primary button) is untouched underneath.
 */

/*
 * THE BELL REPLACES .cr-top's usual wordmark+sign-out row on Home only —
 * shell()'s 'bell' header, not a second header component. A live-device
 * review found it reserving the FULL row (.cr-top's own 56px min-height
 * plus 14px margin-bottom - 70px) for a single 40px control, leaving an
 * empty band above "Where to, X?" on both the plain and CONFIRMED-card
 * variants of Home. Zeroing both here and taking the bell out of flow
 * entirely - position: absolute, anchored to this now-relative row's own
 * top-right corner - means the row's box collapses to nothing and the
 * headline starts exactly where it would if there were no bell at all.
 */
.cr-top.cr-top-bell {
	min-height: 0;
	margin-bottom: 0;
	position: relative;
}
.cr-bell {
	position: absolute;
	top: 4px;
	right: -4px;
	display: grid;
	place-items: center;
	width: 40px;
	height: 40px;
	background: none;
	border: 0;
	color: var(--text);
	cursor: pointer;
}
.cr-bell .cr-ico { width: 24px; height: 24px; }
.cr-bell-dot {
	position: absolute;
	top: 7px;
	right: 8px;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--signal-solid);
	border: 2px solid var(--surface);
}

/*
 * THE BELL NOW OPENS A LIST, NOT A NAVIGATION. Anchored under the button
 * it belongs to rather than dropped somewhere a tap has to go find it -
 * .cr-top-bell is already position:relative for the bell itself, so this
 * shares the same containing block. Populated from the notices
 * loadNotices() already holds (see app.js) - no request of its own.
 */
.cr-bell-pop {
	position: absolute;
	top: 48px;
	right: -4px;
	z-index: 20;
	width: max-content;
	min-width: 240px;
	max-width: min( 320px, calc( 100vw - 28px ) );
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--r-card);
	box-shadow: var(--sh-2);
	overflow: hidden;
}
.cr-bell-note {
	display: block;
	width: 100%;
	padding: 12px 14px;
	background: none;
	border: 0;
	border-bottom: 1px solid var(--line);
	color: var(--text);
	font: 13.5px/1.45 var(--font-ui);
	text-align: left;
	cursor: pointer;
}
.cr-bell-note:last-child { border-bottom: 0; }
.cr-bell-note:active { background: var(--sunken); }

.cr-home-head { margin: 4px 0 20px; }
.cr-home-title {
	margin: 0;
	font-size: 44px;
	font-weight: 800;
	line-height: 1.04;
	letter-spacing: -.03em;
}
.cr-home-title span { display: block; }
.cr-home-sub {
	margin: 10px 0 0;
	color: var(--text-2);
	font-size: 16px;
	line-height: 1.4;
	max-width: 34ch;
}

.cr-homegrid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 11px;
	margin: 20px 0 24px;
}
.cr-hometile {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 10px;
	min-height: 96px;
	padding: 14px;
	background: var(--sunken);
	border: 0;
	border-radius: var(--r-card);
	color: var(--text);
	text-align: left;
	cursor: pointer;
}
.cr-hometile-ico { width: 22px; height: 22px; color: var(--text); }
.cr-hometile b {
	font-size: 15.5px;
	font-weight: 700;
	letter-spacing: -.01em;
}
.cr-hometile small {
	display: block;
	margin-top: 2px;
	color: var(--text-2);
	font: 13px var(--font-data);
}

.cr-homeboard { margin-bottom: 24px; }
.cr-homeboard-h {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	margin-bottom: 10px;
}
.cr-homeboard-h h2 { margin: 0; font-size: var(--t-title); font-weight: 700; letter-spacing: -.02em; }
.cr-homeboard-h span { color: var(--text-2); font-size: 14px; font-weight: 600; }

/* Shared by Home's "On the board" list and Activity's #cr-board open-post
   list - deliberately NOT .cr-list, whose own `.cr-list li` rule (flex row,
   space-between, a bare bottom hairline) would fight .cr-board-card's own
   padding/border on specificity, since a class+element selector outranks
   a single class. */
.cr-board-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }

/*
 * ONE POST, AS ITS OWN CARD — .cr-board-card is the shared design behind
 * both Home's "On the board" list and Activity's #cr-board (see
 * boardCardHtml() in app.js). Replaced the flat --sunken chip a live-
 * device review flagged as looking like disabled UI, not a live one: real
 * --surface, a hairline instead of a heavy fill, and the room a title/
 * subtitle journey line needs instead of one truncating <b>.
 */
.cr-board-card {
	position: relative;
	background: var(--surface);
	border: 1px solid var(--line);
	border-left: 3px solid transparent;
	border-radius: var(--r-card);
	padding: 12px 14px;
	list-style: none;
	cursor: pointer;
}
/*
 * THE BORDER BREATHES, NOTHING ELSE MOVES. Animating the row's own
 * opacity would dim the journey and fare with it, and animating
 * border-color needs a colour-to-transparent stop that varies by
 * palette. A pseudo-element sized to exactly the 3px strip the real
 * border-left reserves (kept transparent only so nothing shifts)
 * isolates the animation to the accent alone. Reuses .cr-dot's own
 * cr-blink timing rather than inventing a second cadence for the same
 * "still live" meaning. A pledge (see boardCardHtml()) never gets this
 * class-modifier - it is not live, and should not read as though it is.
 */
.cr-board-card.cr-board-live::before {
	content: '';
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
	width: 3px;
	background: var(--signal-solid);
	border-radius: var(--r-card) 0 0 var(--r-card);
	animation: cr-blink 2.4s ease-in-out infinite;
}
@media ( prefers-reduced-motion: reduce ) {
	.cr-board-card.cr-board-live::before { animation: none; }
}

/* Status row: the pill on the left, the one figure that exists on every
   post - open or pledged alike - on the right, same baseline. */
.cr-board-status {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	margin-bottom: 10px;
}
.cr-board-until {
	flex: none;
	font: 600 12px var(--font-data);
	font-variant-numeric: tabular-nums;
	color: var(--text-2);
}

/* Journey: tripViz's own dot-rail-square, sized up slightly from its
   .cr-viz-sm default (14px) so a route name reads as the point of the
   card, not a caption under the pill. */
.cr-board-journey { margin-bottom: 10px; }
.cr-board-journey .cr-viz-txt b { font-size: 15px; font-weight: 700; }
.cr-board-single {
	margin: 0 0 10px;
	font-size: 15px;
	font-weight: 700;
	letter-spacing: -.01em;
	color: var(--text);
}

/* Footer: role/time on the left in the ordinary voice, fare or seat count
   right-aligned in the data face - only when either is real, so a post
   with neither still reads as one clean left-aligned line rather than an
   empty right column. */
.cr-board-foot {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 10px;
	color: var(--text-2);
	font-size: 13px;
}
.cr-board-foot-r {
	flex: none;
	font: 700 13.5px var(--font-data);
	font-variant-numeric: tabular-nums;
	color: var(--text);
}

/* Activity only: Resume / Take down, sharing the card's own padding
   rather than bleeding to its edges - a live-device review found these
   floating unaligned under the old layout. */
.cr-board-acts {
	display: flex;
	justify-content: flex-end;
	gap: 8px;
	margin-top: 10px;
	padding-top: 10px;
	border-top: 1px solid var(--line);
}

/* ------------------------------------------------------------------- forms */

.cr label {
	display: block;
	font-size: var(--t-sub);
	font-weight: 600;
	margin: 14px 0 6px;
	color: var(--text-2);
}
.cr label .cr-opt { color: var(--text-2); font-weight: 400; }

.cr input[type=text],
.cr input[type=email],
.cr input[type=tel],
.cr input[type=number],
.cr input[type=time],
.cr select {
	width: 100%;
	height: 52px;
	/* 16px minimum, otherwise iOS Safari zooms the whole page on focus. */
	font-size: 16px;
	padding: 0 13px;
	background: var(--sunken);
	border: 2px solid transparent;
	border-radius: var(--r-ctl);
	color: var(--text);
	appearance: none;
}

/* Black, not blue and not amber (§4.5) — a focused field is not a pending
   action and blue means only one thing in this app. No glow: a border-width
   change alone is the whole affordance, held at a constant 2px so the field
   never shifts size between resting and focused. */
.cr input:focus,
.cr select:focus,
.cr textarea:focus {
	outline: none;
	border-color: var(--text);
}

.cr input::placeholder { color: var(--text-3); }

.cr textarea {
	width: 100%;
	font-size: 16px;
	font-family: inherit;
	padding: 12px 13px;
	background: var(--sunken);
	border: 2px solid transparent;
	border-radius: var(--r-ctl);
	color: var(--text);
	resize: vertical;
}

/* §8.2's onboarding question headline — bigger than a card's own h2. */
.cr-q {
	font-size: var(--t-title);
	font-weight: 700;
	letter-spacing: -.02em;
	line-height: 1.2;
}

/* §8.2: the email step's input reads bigger than an ordinary form field
   (--t-body/52px, §8.3) — this is the one question on the screen. */
#cr-email {
	height: 56px !important;
	font-size: 20px !important;
}

/*
 * Six boxed characters via letter-spacing on a single field, already the
 * right trick (§8.2 is explicit this must stay one input, never six, so
 * autocomplete="one-time-code" and paste keep working). Height matches the
 * email field's 56px rather than the general 52px form field.
 */
.cr-code {
	height: 56px !important;
	letter-spacing: 10px;
	text-align: center;
	font-family: var(--font-data) !important;
	font-size: 26px !important;
	font-weight: 700;
	padding: 0 13px !important;
}

/* ----------------------------------------------------------------- buttons */

.cr-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 52px;
	margin-top: 12px;
	padding: 0 16px;
	font-size: var(--t-body);
	font-weight: 600;
	letter-spacing: -.01em;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-ctl);
	background: transparent;
	color: var(--text);
	cursor: pointer;
	transition: transform var(--dur-1) var(--ease-out);
}
/* No ripple — Uber's press feedback is a scale, not a fill spreading from the
   tap point. */
.cr-btn:active:not(:disabled) { transform: scale(.985); }

/*
 * §13.1a: "Ask sent, waiting on their yes — an animated waiting state on
 * awaitAnswer(), never static text." The button that was just tapped
 * already reads "Asked — waiting for X" and is already the right shape
 * (a pill); this breathes it rather than building a second element to
 * say the same thing. A loop, deliberately — §13.1's rule is "decoration
 * does not loop, waiting does," and this is the waiting state itself.
 */
.cr-waitpulse { animation: cr-breathe 1.8s ease-in-out infinite; }

@keyframes cr-breathe {
	0%, 100% { opacity: 1; }
	50%      { opacity: .55; }
}

@media ( prefers-reduced-motion: reduce ) {
	.cr-waitpulse { animation: none; opacity: .78; }
}

/*
 * §13.1a: "Confirmed — a one-shot confirmation: the primary action
 * collapses to a check that pops once and settles." The button keeps its
 * full width and disabled state (nothing here is a layout animation on a
 * cheap phone, per §13.1's transform/opacity rule) — its sentence
 * collapses to the one glyph that says the same thing, and that glyph
 * pops in rather than simply appearing.
 */
.cr-btn-check { gap: 0; }
.cr-btn-check-ico {
	width: 22px;
	height: 22px;
	animation: cr-check-pop .4s var(--ease-spring) both;
}

@keyframes cr-check-pop {
	0%   { transform: scale( .3 ); opacity: 0; }
	60%  { transform: scale( 1.15 ); opacity: 1; }
	100% { transform: scale( 1 ); }
}

@media ( prefers-reduced-motion: reduce ) {
	.cr-btn-check-ico { animation: none; }
}

/* The one primary action on a screen. A white fill on ink is the loudest object
   in the system, and it appears at most once — which is the only reason it is
   loud. Amber used to do this job and also mean "live" and also be the brand. */
.cr-btn.cr-pri {
	background: var(--pri);
	border-color: var(--pri);
	color: var(--pri-ink);
	font-weight: 700;
}
.cr-btn.cr-pri:hover:not(:disabled) { background: var(--pri-hover); border-color: var(--pri-hover); }

/* Accepting something wears the colour of the state it produces. */
.cr-btn.cr-go {
	background: var(--go-solid);
	border-color: var(--go-solid);
	color: var(--go-ink);
}

/*
 * The same act, offered a second and third time down the list.
 *
 * Taking an advertised seat confirms on the spot, so it has to keep the green
 * — but four solid green buttons is four equal recommendations, which is no
 * recommendation and is how you tap the wrong stranger. The nearest one is
 * filled; the rest carry the colour without the weight.
 */
.cr-btn.cr-go-out {
	background: var(--go-tint);
	border-color: var(--go-edge);
	color: var(--go);
	font-weight: 700;
}

.cr-btn.cr-ghost {
	background: none;
	border: 1px solid var(--line);
	color: var(--text);
}

/* Solid, not dashed — a live-device review named this specifically:
   dashed reads as disabled or as a drop target, and it had landed on
   "They never came" / "No-show", real actions somebody needs to
   actually press. test_assets() check 9 exists so no .cr-btn rule can
   reintroduce a dashed border anywhere in this file. */
.cr-btn.cr-wk {
	border-color: var(--line-strong);
	color: var(--text-2);
	font-weight: 500;
}

.cr-btn:disabled { opacity: .38; cursor: default; pointer-events: none; }

.cr-link {
	background: none;
	border: 0;
	color: var(--text);
	font-size: 13.5px;
	cursor: pointer;
	padding: 12px 0 2px;
	display: block;
	width: 100%;
	text-align: center;
}
.cr-link:disabled { color: var(--text-2); cursor: default; }
/* A live-device review reported Cancel / Report or block / Past rides —
   in Account "feel like clicking on links" — .cr-link carries no
   resting background by design (it is the deliberately quiet action),
   so the fix is a press-only one: a background and a scale step that
   exist for the length of the tap and nothing longer. */
.cr-link:active:not(:disabled) {
	background: var(--sunken);
	border-radius: var(--r-ctl);
	transform: scale(.97);
}

.cr-row { display: flex; gap: 9px; }
.cr-row > * { flex: 1; }
.cr-row .cr-btn { margin-top: 0; }

.cr-stack { display: flex; flex-direction: column; gap: 9px; }
.cr-stack .cr-btn { margin-top: 0; }

/* Everything below this line is comfortably inside a right thumb's arc. It is
   where the primary action lives on any screen tall enough to have a choice. */
.cr-foot { margin-top: 16px; }

.cr-choice { display: flex; gap: 7px; margin-top: 6px; }
.cr-choice label {
	flex: 1;
	margin: 0;
	text-align: center;
	padding: 12px 6px;
	border: 1px solid var(--line);
	border-radius: var(--r-ctl);
	cursor: pointer;
	font-weight: 500;
	background: var(--sunken);
	min-height: 46px;
	display: grid;
	place-items: center;
}
.cr-choice input { position: absolute; opacity: 0; pointer-events: none; }

/*
 * A picked option is a selection, not a pending action, so it does not get the
 * pending lamp — and it does not get the white fill either, because that is the
 * primary button's and there is one of those per screen. A white edge and a
 * lifted surface says "this one" without shouting over the button below it.
 */
.cr-choice input:checked + span { color: var(--text); font-weight: 700; }
.cr-choice label:has(input:checked) {
	border-color: var(--text);
	background: var(--surface);
	box-shadow: inset 0 0 0 1px var(--text);
}
.cr-choice label:has(input:disabled) { opacity: .4; cursor: default; }

/*
 * §8.7 point 5 — the three time modes as a segmented control, scoped to
 * .cr-when only: .cr-choice is shared with the gender picker and other
 * groups that should keep the individual-pill look. One continuous
 * --sunken track; the checked segment gets --surface + a shadow rather
 * than its own border, reading as one control with a position rather than
 * three separate buttons. A literal animated sliding thumb (the reference
 * word) needs knowing which of three fixed positions is active, which is
 * motion-phase work (§13) like the stepper's fill-in and the header's
 * scroll-hide — this is the static shape, with a colour/background
 * transition standing in for the slide.
 */
.cr-choice.cr-when {
	background: var(--sunken);
	border-radius: var(--r-ctl);
	padding: 3px;
	gap: 2px;
}
.cr-choice.cr-when label {
	border: 0;
	background: transparent;
	border-radius: calc( var(--r-ctl) - 3px );
	min-height: 42px;
	font: 600 14.5px var(--font-ui);
	transition: background var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.cr-choice.cr-when label:has(input:checked) {
	background: var(--surface);
	box-shadow: var(--sh-1);
}
/* The mock's three segments hold one weight throughout — background and
   shadow alone say which is picked. .cr-choice's base rule bolds a
   checked span for the individual-pill groups this class is NOT scoped
   to; cancelled here so the segmented control doesn't double up on it. */
.cr-choice.cr-when input:checked + span { font-weight: 600; }

/*
 * `.cr label` is 0-1-1 and `.cr-check` is 0-1-0, so the generic display:block
 * quietly beat this and the tick sat on its own line above its own words. It
 * needs the type selector to win its own rule back.
 */
.cr label.cr-check,
.cr-check {
	display: flex;
	gap: 11px;
	align-items: flex-start;
	margin: 14px 0 0;
	padding: 13px;
	border: 1px solid var(--line);
	border-radius: var(--r-ctl);
	background: var(--sunken);
	cursor: pointer;
}

/* accent-color only paints the checked state; unchecked stays the platform
   default, which is a stark white square on a near-black card. Draw the whole
   control instead so both states belong to the palette. */
.cr-check input[type=checkbox] {
	appearance: none;
	-webkit-appearance: none;
	width: 20px;
	height: 20px;
	margin: 1px 0 0;
	flex: none;
	border: 1px solid var(--line-strong);
	border-radius: 5px;
	background: var(--bg);
	cursor: pointer;
	display: grid;
	place-items: center;
}
.cr-check input[type=checkbox]:checked {
	background: var(--pri);
	border-color: var(--pri);
}
.cr-check input[type=checkbox]:checked::after {
	content: '';
	width: 5px;
	height: 9px;
	border: solid var(--pri-ink);
	border-width: 0 2px 2px 0;
	transform: rotate(45deg) translate(-1px, -1px);
}
.cr-check b { display: block; font-size: 14px; font-weight: 600; }
.cr-check small { color: var(--text-2); font-size: 12.5px; line-height: 1.45; display: block; margin-top: 2px; }

.cr-fold { display: none; }
.cr-fold.cr-open { display: block; }

/*
 * §8.3's grid-template-rows fold, scoped to the Profile screen's bike fold
 * only (#cr-bike), NOT the shared .cr-fold base rule above. .cr-fold is
 * reused by Home's three time-mode folds, the Trip editor's money folds and
 * the Board's walk-distance panel — screens this phase does not touch, Home
 * above all (§15 Phase 9, deliberately last and "largest and riskiest").
 * Animating every fold in the app from one CSS-only change here would risk
 * all of them before any of them has been reviewed. The trick needs exactly
 * one child spanning the 0fr/1fr row track, hence the extra .cr-fold-inner
 * wrapper added only inside #cr-bike's markup.
 */
#cr-bike.cr-fold {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows var(--dur-3) var(--ease-spring);
}
#cr-bike.cr-fold.cr-open { grid-template-rows: 1fr; }
#cr-bike .cr-fold-inner { overflow: hidden; min-height: 0; }

/* --------------------------------------------------------------- messaging */

.cr-msg {
	margin-top: 12px;
	padding: 11px 13px;
	border-radius: var(--r-ctl);
	font-size: 13.5px;
	display: none;
}
/* §12.3: a 3px left border rather than a hairline all round, and a 200ms
   slide-in — the message appears next to the control that caused it, so
   it should read as arriving there, not as always having been there. */
.cr-msg.cr-show { display: block; animation: cr-msg-in .2s var(--ease-out) both; }
.cr-msg.cr-bad  { background: var(--alert-tint); color: var(--alert); border: 0; border-left: 3px solid var(--alert); }
.cr-msg.cr-good { background: var(--go-tint);    color: var(--go);    border: 0; border-left: 3px solid var(--go); }
@keyframes cr-msg-in {
	from { opacity: 0; transform: translateY(-4px); }
	to   { opacity: 1; transform: none; }
}
@media ( prefers-reduced-motion: reduce ) {
	.cr-msg.cr-show { animation: none; }
}

.cr-note {
	margin-top: 12px;
	padding: 11px 13px;
	background: var(--sunken);
	border-radius: var(--r-ctl);
	color: var(--text-2);
	font-size: 13px;
	line-height: 1.55;
}
.cr-note b { color: var(--text); font-weight: 600; }
.cr-note-bad { background: var(--alert-tint); color: var(--alert); border: 1px solid var(--alert-edge); }

/* ------------------------------------------------------------------- chips */

.cr-pill {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	height: 26px;
	font: 600 var(--t-cap)/1 var(--font-ui);
	letter-spacing: .04em;
	text-transform: uppercase;
	padding: 0 10px;
	border-radius: var(--r-pill);
	border: 1px solid var(--line-strong);
	color: var(--text-2);
	background: transparent;
	vertical-align: middle;
}

/* A job for the person reading it. The only solid fill among the chips, and
   never more than one on a screen. */
.cr-pill.cr-ask {
	background: var(--signal-solid);
	border-color: var(--signal-solid);
	color: var(--signal-ink);
}

/* Live right now, and will expire on its own. Tint, not fill: it is happening
   but it is not your job. */
.cr-pill.cr-live {
	background: var(--signal-tint);
	border-color: var(--signal-edge);
	color: var(--signal);
}

.cr-pill.cr-ok {
	background: var(--go-tint);
	border-color: var(--go-edge);
	color: var(--go);
}

/* The one blue thing in the app: a human checked this person's ID. */
.cr-pill.cr-trust {
	background: var(--trust-tint);
	border-color: var(--trust-edge);
	color: var(--trust);
}

.cr-pill.cr-wait { background: transparent; border-color: var(--line-strong); color: var(--text-2); }
.cr-pill.cr-free { background: var(--go-tint); border-color: var(--go-edge); color: var(--go); }

/* Money keeps its own case and its own face. The taka sign is U+09F3, which
   the uppercase transform and the UI stack between them render as something
   close to a colon on desktop — and a price nobody can read is worse than no
   pill at all. The data face has it, and tabular figures line the amounts up. */
.cr-pill.cr-money {
	text-transform: none;
	letter-spacing: 0;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-size: 11px;
}

.cr-pills { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }

/* Slow, one per screen, and gone entirely for anyone who has asked for less
   motion. It exists to prove the app is still awake, not to demand attention. */
.cr-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--signal);
	flex: none;
	animation: cr-blink 2.4s ease-in-out infinite;
}
@keyframes cr-blink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

@media ( prefers-reduced-motion: reduce ) {
	.cr-dot { animation: none; }
	.cr * { transition: none !important; }
}

.cr-asking { color: var(--signal) !important; font-weight: 600; }

.cr-badge {
	display: inline-block;
	min-width: 20px;
	padding: 2px 7px;
	margin-left: 6px;
	border-radius: 999px;
	background: var(--signal-solid);
	color: var(--signal-ink);
	font: 700 11.5px/1.4 var(--font-data);
	vertical-align: middle;
}

/* ------------------------------------------------------------------ icons */

/*
 * One family: 24-unit grid, one 2px stroke, round everything. Sized in em so a
 * glyph is always in proportion to the words beside it, and coloured by
 * currentColor so it can never disagree with its own label.
 */
.cr-ico {
	width: 1.15em;
	height: 1.15em;
	flex: none;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
	vertical-align: -.18em;
}

.cr-ico-lg { width: 1.6em; height: 1.6em; }
.cr-ico-xl { width: 26px; height: 26px; stroke-width: 1.8; }

/* Pills and tags carry their icon a touch smaller — at pill size a full-size
   glyph out-shouts the word it belongs to. */
.cr-pill .cr-ico,
.cr-tag .cr-ico { width: 14px; height: 14px; stroke-width: 2.2; }

/* ------------------------------------------------------- the route visual */

/*
 * The two-point metaphor every transport app trains people on: where you get
 * on, where you get off, and a line between them. It is a diagram, not a map —
 * it never claims to show the actual road, which is what screenRoute is for.
 */
.cr-trip-viz { display: flex; gap: 11px; align-items: stretch; }

.cr-viz-rail {
	position: relative;
	width: 16px;
	flex: none;
	display: flex;
	flex-direction: column;
	align-items: center;
	padding: 4px 0 2px;
}

/* Origin: a filled dot with a ring, not a hollow circle — the ring is what
   separates it from whatever the card underneath is (white ground, sunken
   card, sheet), so it reads the same everywhere rather than only on white. */
.cr-viz-rail::before {
	content: '';
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: var(--text);
	box-shadow: 0 0 0 3px var(--surface);
	flex: none;
}

/* Destination: a filled square. Ring for where you are, square for where you
   are going, is the pairing every trip planner has trained people on — and it
   frees the pending lamp from a job it was doing for decoration. */
.cr-viz-rail::after {
	content: '';
	width: 9px;
	height: 9px;
	flex: none;
	border-radius: 2px;
	background: var(--text);
}

.cr-viz-line {
	flex: 1 1 auto;
	width: 0;
	min-height: 44px;
	margin: 3px 0;
	border-left: 2px dashed var(--line-strong);
}

.cr-viz-txt {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: 8px;
}
.cr-viz-txt b {
	display: block;
	font-size: 14.5px;
	font-weight: 650;
	letter-spacing: -.01em;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cr-viz-txt small { display: block; color: var(--text-2); font-size: 12px; margin-top: 1px; }

/* Compact variant for list rows, where the whole card is only two lines tall. */
.cr-trip-viz.cr-viz-sm .cr-viz-line { min-height: 10px; }
.cr-trip-viz.cr-viz-sm .cr-viz-txt b { font-size: 14px; }

/*
 * §13.1a: "tripViz on entry — the rail draws origin → destination, then
 * labels fade up (§3d.5)." Opt-in via .cr-viz-draw, set only by tripViz()'s
 * own `enter` argument — see its comment in app.js for why this cannot be
 * unconditional. scaleY, not height, per §13.1's transform/opacity rule;
 * the dashed border is on the element being scaled, not a child, so it
 * scales with it rather than needing its own mask.
 */
.cr-viz-draw .cr-viz-line {
	animation: cr-viz-draw .5s var(--ease-out) both;
	transform-origin: top;
}
.cr-viz-draw .cr-viz-txt > div {
	animation: cr-viz-label-in var(--dur-2) var(--ease-out) both;
	animation-delay: .3s;
}
.cr-viz-draw .cr-viz-txt > div:last-child { animation-delay: .4s; }

@keyframes cr-viz-draw {
	from { transform: scaleY( 0 ); }
	to   { transform: scaleY( 1 ); }
}
@keyframes cr-viz-label-in {
	from { opacity: 0; transform: translateY( 6px ); }
	to   { opacity: 1; transform: translateY( 0 ); }
}

@media ( prefers-reduced-motion: reduce ) {
	.cr-viz-draw .cr-viz-line,
	.cr-viz-draw .cr-viz-txt > div { animation: none; }
}

/* --------------------------------------------------------- the ride card */

/*
 * One shape for "a ride", wherever a ride appears. Before this, the same idea
 * was a list row on one screen, a bordered card on another and a bare heading
 * on a third, so nothing in the app looked like it belonged to the same
 * product. The meta strip along the bottom is the ride-hailing readout: walk,
 * time, price, all in the data face.
 */
.cr-meta {
	display: flex;
	flex-wrap: nowrap;
	overflow-x: auto;
	scrollbar-width: none;
	gap: 6px 14px;
	margin-top: 11px;
	padding-top: 10px;
	border-top: 1px solid var(--line);
	font-size: 12.5px;
	color: var(--text-2);
}
.cr-meta::-webkit-scrollbar { display: none; }
.cr-meta span { display: inline-flex; align-items: center; gap: 5px; flex: none; }
.cr-meta .cr-ico { width: 15px; height: 15px; }
.cr-meta b {
	color: var(--text);
	font-weight: 650;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
}
.cr-meta .cr-free-txt { color: var(--go); font-weight: 650; }

/* ------------------------------------------------- the vehicle identity */

/*
 * The one physical thing a student has to recognise in the dark, at a kerb,
 * with thirty seconds to decide whether to get on. It was a sentence inside a
 * note; a car's colour and plate get a card of their own in every ride-hailing
 * app, and a bike is no different.
 */
.cr-vehicle {
	display: flex;
	align-items: center;
	gap: 13px;
	margin-top: 11px;
	padding: 13px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-card);
	background: var(--sunken);
}
.cr-vehicle .cr-ico { width: 30px; height: 30px; stroke-width: 1.7; color: var(--text-2); }
.cr-vehicle .cr-v-txt { min-width: 0; flex: 1 1 auto; }
.cr-vehicle .cr-plate {
	margin: 0 0 2px;
	font-size: 16px;
	letter-spacing: .04em;
	word-break: break-word;
}
.cr-vehicle small { display: block; color: var(--text-2); font-size: 12.5px; line-height: 1.45; }

/* --------------------------------------------------------- more/less tags */

.cr-more {
	background: none;
	border: 1px dashed var(--line-strong);
	color: var(--text-2);
	border-radius: 999px;
	padding: 4px 11px;
	font: 700 10.5px/1.35 var(--font-ui);
	letter-spacing: .07em;
	text-transform: uppercase;
	cursor: pointer;
}
.cr-more:hover { color: var(--text); }
.cr-tags .cr-tag[hidden] { display: none; }

/* ----------------------------------------------------------------- stepper */

/* Where you are in a booking, readable without reading. Green behind for what
   is done, amber for where you are, grey ahead. */
.cr-step { display: flex; gap: 4px; margin: 4px 0 0; }
.cr-step i { flex: 1; height: 4px; border-radius: var(--r-pill); background: var(--line); }
.cr-step i.cr-on  { background: var(--go-solid); }
.cr-step i.cr-now { background: var(--signal-solid); }
/* The current bar's "fills in on first paint only" (§7.6) is deliberately
   NOT done here. screenHome()/screenRides() rewrite this element's whole
   subtree on every 20s poll via innerHTML, same as the rest of the app — a
   pure-CSS animation would replay on every poll, not just first paint,
   without a JS-side flag keying it the way tripViz's one-shot entrance does
   (§3d.5). That keying is a behavioural change, out of scope for a
   presentation-only components pass; left for the dedicated motion phase (§13). */

.cr-steplab {
	display: flex;
	justify-content: space-between;
	margin-top: 6px;
	font: 600 9px/1 var(--font-ui);
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--text-3);
}

/* --------------------------------------------------------- facts and lists */

.cr-facts { list-style: none; margin: 0; padding: 0; }
.cr-facts li {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 14px;
	padding: 11px 0;
	border-bottom: 1px solid var(--line);
	font-size: 13.5px;
}
.cr-facts li:last-child { border-bottom: 0; }
.cr-facts span { color: var(--text-2); }
.cr-facts b { font-weight: 600; text-align: right; font-size: 13.5px; }

.cr-list { list-style: none; margin: 4px 0 0; padding: 0; }
.cr-list li {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 12px 0;
	border-bottom: 1px solid var(--line);
}
.cr-list li:last-child { border-bottom: 0; }
.cr-list b { font-weight: 600; font-size: 14.5px; }
.cr-list small { display: block; color: var(--text-2); font-size: 12.5px; margin-top: 3px; }

.cr-row-acts { display: flex; gap: 6px; flex: none; flex-wrap: wrap; }

/* A saved place leads with the same glyph the trip picker uses for one, so the
   two lists teach each other rather than looking like unrelated screens. */
.cr-rowmain { display: flex; align-items: center; gap: 12px; flex: 1 1 auto; min-width: 0; }
.cr-rowmain .cr-rowico { width: 19px; height: 19px; color: var(--text-2); flex: none; }

/*
 * A place row carries two buttons and sits happily beside its text. A route row
 * carries five - Leaving now, Find rides, Route, Edit, Delete - which is far
 * more than fits next to a description on a phone, so they shoved the text into
 * a two-character column and the row fell apart. Routes get their own shape:
 * description on its own line, actions wrapping underneath.
 */
.cr-list li.cr-trip-row {
	display: block;
	padding: 14px 0;
}

.cr-trip-row > div:first-child { margin-bottom: 10px; }

.cr-trip-row .cr-row-acts {
	gap: 7px;
	width: 100%;
}

/* Finger-sized, and never narrower than the word inside them. */
.cr-trip-row .cr-mini {
	min-height: 38px;
	white-space: nowrap;
}

@media ( max-width: 420px ) {
	/*
	 * Tighter, but never stretched to fill the row. Letting them flex-grow put
	 * Delete alone on the last line at full width, which made the one
	 * destructive button the largest target on the screen.
	 */
	.cr-trip-row .cr-mini { padding: 8px 9px; font-size: 12.5px; }
	.cr-list li { flex-wrap: wrap; }
	.cr-list li > .cr-row-acts { margin-left: auto; }
}

.cr-mini {
	background: none;
	border: 1px solid var(--line);
	color: var(--text-2);
	border-radius: var(--r-sm);
	padding: 7px 10px;
	font-size: 12.5px;
	font-weight: 600;
	cursor: pointer;
}
.cr-mini:hover { color: var(--text); border-color: var(--line-strong); }
.cr-mini:active:not(:disabled) { background: var(--sunken); transform: scale(.96); }

/* Destructive controls are neutral until the confirming tap. Red on screen
   always means you are one tap from doing it. */
.cr-mini.cr-danger:hover { color: var(--alert); border-color: var(--alert); }
.cr-mini[data-state=sure] { color: var(--alert); border-color: var(--alert); background: var(--alert-tint); }
.cr-btn[data-state=sure]  { color: var(--alert); border-color: var(--alert); background: var(--alert-tint); }

/* "Leaving now" is an action, not a pending state — neutral, like every other
   row button. Only what is genuinely waiting on the person wears amber. */
.cr-mini.cr-now { border-color: var(--line-strong); color: var(--text); }
.cr-mini.cr-go  { border-color: var(--line-strong); color: var(--text); }

/*
 * §12.1/§13.4: skeletons, shaped like the content that will land — "a board
 * result skeleton looks like a board result, not three grey bars." Three
 * primitives (.cr-skel-line, .cr-skel-circle, .cr-skel-block) compose into
 * a per-screen shape at each of the five "Loading…" call sites, rather than
 * one generic skeleton reused everywhere. A --sunken block with a 1.4s
 * shimmer sweep; killed entirely under reduced motion in favour of a
 * static block, per §13.4 exactly.
 */
.cr-skel-line,
.cr-skel-circle,
.cr-skel-block {
	position: relative;
	overflow: hidden;
	background: var(--sunken);
}
.cr-skel-line::after,
.cr-skel-circle::after,
.cr-skel-block::after {
	content: '';
	position: absolute;
	inset: 0;
	transform: translateX(-100%);
	background: linear-gradient( 90deg, transparent, rgba(255,255,255,.5), transparent );
	animation: cr-skel-sweep 1.4s ease-in-out infinite;
}
@keyframes cr-skel-sweep {
	100% { transform: translateX(100%); }
}
@media ( prefers-reduced-motion: reduce ) {
	.cr-skel-line::after,
	.cr-skel-circle::after,
	.cr-skel-block::after { display: none; }
}

.cr-skel-line { height: 12px; border-radius: 6px; margin: 6px 0; }
.cr-skel-line.cr-skel-w40 { width: 40%; }
.cr-skel-line.cr-skel-w55 { width: 55%; }
.cr-skel-line.cr-skel-w70 { width: 70%; }
.cr-skel-line.cr-skel-w100 { width: 100%; }
.cr-skel-circle { width: 40px; height: 40px; border-radius: 50%; flex: none; }
.cr-skel-block { height: 72px; border-radius: var(--r-card); }

.cr-skel-row { display: flex; align-items: center; gap: 12px; padding: 13px 0; border-bottom: 1px solid var(--line); }
.cr-skel-row:last-child { border-bottom: 0; }
.cr-skel-row .cr-skel-col { flex: 1 1 auto; min-width: 0; }

.cr-empty {
	color: var(--text-2);
	font-size: 13.5px;
	padding: 14px;
	border: 1px dashed var(--line);
	border-radius: var(--r-ctl);
	margin: 4px 0 0;
	line-height: 1.55;
}

/* §11.4's illustrated states — a distinct centred block, not the plain
   dashed .cr-empty box every other empty list uses. */
.cr-illo { width: 64px; height: 64px; color: var(--text-3); margin: 0 auto 12px; display: block; }
.cr-empty-hero { text-align: center; padding: 28px 16px 8px; }
.cr-empty-hero p { color: var(--text-2); font-size: 13.5px; line-height: 1.55; margin: 0; }

/*
 * The board's own empty search result — deliberately NOT .cr-empty-hero.
 * That treatment is for a rarely-seen settings screen; this one shows up
 * on every quiet search, and a live-device review found the 64px
 * illustration reading as a broken image on a state this common. One
 * small inline icon, one left-aligned sentence.
 */
.cr-board-empty {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	margin: 8px 0 0;
	color: var(--text-2);
	font-size: 13.5px;
	line-height: 1.5;
}
.cr-board-empty-ico { flex: none; width: 24px; height: 24px; margin-top: 1px; color: var(--text-3); }

.cr-divide {
	display: flex;
	align-items: center;
	gap: 9px;
	margin: 18px 0 8px;
	font-size: var(--t-cap);
	font-weight: 600;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--text-3);
}
.cr-divide::after { content: ''; flex: 1; height: 1px; background: var(--line); }

/* ------------------------------------------------------- the person's card */

.cr-match {
	border: 1px solid var(--line);
	border-radius: var(--r-card);
	padding: 15px 14px;
	margin-bottom: 11px;
	background: var(--surface);
}

/* The recommended one (§8.8) — a 2px --text border, the only .cr-pri button
   in the list (already correct in boardCard()'s own logic). Three identical
   primaries is three equal choices and no recommendation; one default and
   the rest alternatives is a decision. */
.cr-match.cr-lead { border-width: 2px; border-color: var(--text); }

.cr-match-top { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; }
.cr-match-who { display: flex; gap: 10px; align-items: center; min-width: 0; }
/* .cr-mono's own margin:0 auto is written for a single-child centring
   context (Account's avatar); auto margins in a two-item flex row would
   absorb the free space and shove the monogram away from the text. */
.cr-match-who .cr-mono { margin: 0; }
.cr-match-who > div { min-width: 0; }
.cr-match-top b { font-size: 16px; font-weight: 650; letter-spacing: -.02em; }
.cr-match-top small { display: block; color: var(--text-2); font-size: 12.5px; margin-top: 3px; }

/* §11.3's monogram pops in with the card it arrived on — .cr-arrived
   already marks exactly that card, so this needs no state of its own,
   the same reasoning tripViz's own draw-in gating uses. Reuses
   cr-check-pop rather than a new name for the same settle-once curve. */
.cr-arrived .cr-mono { animation: cr-check-pop .4s var(--ease-spring) both; }
@media ( prefers-reduced-motion: reduce ) {
	.cr-arrived .cr-mono { animation: none; }
}

/* §8.10 — a receipt figure, not one more icon+label in the meta strip. */
.cr-past-fare {
	flex: none;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	font-size: var(--t-sub);
	white-space: nowrap;
}
.cr-past-fare.cr-past-free { color: var(--go); }
.cr-past-fare.cr-past-unset { font-weight: 500; color: var(--text-3); font-family: var(--font-ui); }

/* A no_show row reads as void, not merely informational — §8.10. */
.cr-past-void .cr-match-top b,
.cr-past-void .cr-past-fare {
	color: var(--text-3);
	text-decoration: line-through;
}

/*
 * §8.8's reference frame: the fare moved onto the primary button itself
 * (`.cr-btn-fare`, further down) — this row is the meta strip alone now,
 * leave time / suggested-fare distance / walk, each an icon+figure pair
 * from the existing `meta()` helper.
 */
.cr-board-action {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 12px;
	margin-top: 11px;
}
.cr-board-action .cr-meta {
	margin-top: 0;
	padding-top: 0;
	border-top: 0;
	flex: 1 1 auto;
	min-width: 0;
}

/*
 * §8.8: "a black Take this seat button with the fare right-aligned
 * inside it" — one control, not a fare row sitting above a plain
 * button. `.cr-btn` is already `display:flex; justify-content:center`
 * for its common single-label case; `.cr-btn-split` is the modifier
 * this needs, space-between instead of centred, so the verb stays left
 * and the figure lands right without fighting the base rule.
 */
.cr-btn-split { justify-content: space-between; gap: 10px; }
.cr-btn-split span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cr-btn-fare {
	flex: none;
	font-family: var(--font-data);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
}

/*
 * The results header — "N seats left" bold, "Best match first" as a
 * caps caption on the right (§8.8). Only ever drawn on the pillion's
 * own live results; the equivalent rider-facing count keeps its old
 * plain-sentence phrasing (draw() in app.js), since nobody is offering
 * him a "seat".
 */
.cr-results-head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	margin: 4px 0 12px;
}
.cr-results-head b { font-size: var(--t-title); font-weight: 700; letter-spacing: -.02em; }
.cr-results-head span {
	font: 600 11px var(--font-ui);
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--text-2);
}

/*
 * Karma carries no colour at all, on purpose. A number that looks like a
 * measurement gets believed; a number wearing a status colour reads as a badge
 * the app is awarding — and the app cannot vouch for a stranger.
 */
.cr-karma { text-align: center; flex: none; padding-left: 6px; }
.cr-karma b { display: block; font-size: var(--t-head); font-weight: 700; color: var(--text); line-height: 1; }
.cr-karma small {
	display: block;
	margin-top: 4px;
	color: var(--text-3);
	font: 600 var(--t-cap)/1 var(--font-ui);
	text-transform: uppercase;
	letter-spacing: .11em;
}
.cr-karma.cr-new b { color: var(--text-3); font-size: 17px; }

.cr-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
/* The quiet variant — a fact, not a state. No border; --sunken ground rather
   than a coloured one is what tells a tag apart from a pill at a glance. */
.cr-tag {
	display: inline-flex;
	align-items: center;
	height: 26px;
	font: 600 var(--t-cap)/1 var(--font-ui);
	letter-spacing: .04em;
	text-transform: uppercase;
	padding: 0 10px;
	border-radius: var(--r-pill);
	border: 1px solid transparent;
	background: var(--sunken);
	color: var(--text-2);
}

.cr-walk {
	display: flex;
	justify-content: space-between;
	gap: 10px;
	margin-top: 11px;
	padding: 10px 12px;
	background: var(--sunken);
	border-radius: var(--r-ctl);
	font-size: 12.5px;
	color: var(--text-2);
}
.cr-walk b { color: var(--text); font-weight: 650; font-family: var(--font-data); font-variant-numeric: tabular-nums; }

.cr-match .cr-btn { margin-top: 12px; }

/*
 * Somebody already on the road, as opposed to somebody whose saved week says
 * they usually are. It is the one distinction this screen exists to make, so
 * it gets the live colour and a left edge — amber is "happening now" here as
 * it is everywhere else, and it is deliberately NOT green: nothing has been
 * agreed yet, and green in this app means confirmed.
 */
.cr-match.cr-onroad {
	border-color: var(--signal-edge);
	box-shadow: inset 3px 0 0 var(--signal-solid);
}

/* Somebody who was not there a moment ago.
 *
 * The whole promise of a live search is that the board keeps answering for the
 * length of the timer, which means rows appear under somebody who is already
 * reading. A row that arrives silently and identically is a row that is missed,
 * so it arrives lit and settles into the list over three seconds — long enough
 * to catch an eye that was elsewhere, short enough that it is not still glowing
 * when the thumb gets there. */
/*
 * §8.8: fade + 8px rise over --dur-3, then a 1.2s --signal-tint flash that
 * settles — amber, not green. A card appearing is new information, not a
 * confirmation (§4.5's four lamps), so the earlier green glow was actually
 * a lamp misuse this restyle corrects, not just a colour swap. Two
 * keyframes rather than one: cr-arrive is one of the eight names §13.5
 * says must survive, kept for the rise; cr-arrive-flash is new, for the
 * settling background, and picks up its own reduced-motion guard below.
 */
.cr-match.cr-arrived {
	animation: cr-arrive var(--dur-3) var(--ease-out) both,
		cr-arrive-flash 1.2s ease-out var(--dur-3);
}
@keyframes cr-arrive {
	from { transform: translateY(8px); opacity: 0; }
	to   { transform: translateY(0); opacity: 1; }
}
@keyframes cr-arrive-flash {
	from { background: var(--signal-tint); }
	to   { background: var(--surface); }
}
@media (prefers-reduced-motion: reduce) {
	.cr-match.cr-arrived { animation: none; border-color: var(--signal-edge); }
}

.cr-found {
	color: var(--text-3);
	font: 700 10px/1 var(--font-ui);
	letter-spacing: .13em;
	text-transform: uppercase;
	margin: 16px 0 9px;
}

/* Where the seats that can be taken stop and the ones that have to be asked
   for begin. Drawn once, as a rule across the list, because the difference is
   a boundary rather than a property of each row. */
.cr-rule {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 18px 0 10px;
	color: var(--text-3);
	font: 700 10px/1 var(--font-ui);
	letter-spacing: .13em;
	text-transform: uppercase;
}
.cr-rule::before,
.cr-rule::after { content: ''; flex: 1; height: 1px; background: var(--line); }

/* The sentence under a button that says what pressing it will actually cost —
   an answer straight away, or a wait on somebody else. Quiet on purpose: it
   explains the button without competing with it. */
.cr-fine {
	color: var(--text-3);
	font-size: 12px;
	margin: 7px 0 0;
	text-align: center;
}

/* ------------------------------------------------------------- T7, money */

/* The word in front of the number, and it is deliberately smaller and quieter
   than the number it qualifies. It is a caveat, not a label — sized level with
   "৳90" it reads as a heading and the row starts to look like a price list,
   which is the one thing this app must not look like. */
.cr-meta .cr-sugg {
	color: var(--text-3);
	font-size: 11px;
	font-weight: 600;
	margin-right: 4px;
	letter-spacing: .01em;
}

/* "Not stated" — the rider has never been asked, which is not free.

   Deliberately the flattest thing in the meta row: --text-3 and normal weight,
   where "Free" is --go and 650. That contrast is the whole point. Free is good
   news and reads like it; this is an absence, and styling an absence as news is
   how it got mistaken for one in the first place. Italic because it is the app
   describing a gap rather than reporting a value. */
.cr-meta .cr-unset-txt {
	color: var(--text-3);
	font-weight: 500;
	font-style: italic;
}

/* The once-ever money question, at a rider's first post. It borrows .cr-quote
   wholesale — same surface, same place in the card, above the button that
   raised it — because it is the same kind of moment: something is being put to
   the person and nothing has happened yet. The chips need room to breathe here
   in a way they do not in the settings form, where they sit under a label that
   has already framed them. */
.cr-moneyq .cr-money-choice { margin-top: 10px; }
.cr-moneyq .cr-money-choice small { white-space: normal; line-height: 1.3; }
.cr-moneyq #cr-mqmsg:empty { display: none; }

.cr-pill.cr-unset {
	background: transparent;
	border-style: dashed;
	color: var(--text-3);
}

/* The claim sheet. It is inside the card, above the button that opened it, and
   it borrows the pending surface rather than inventing one: something is being
   put to the person and nothing has happened yet, which is exactly what that
   colour already means everywhere else here. */
.cr-quote {
	margin-top: 12px;
	padding: 12px 13px 10px;
	border: 1px solid var(--signal-edge);
	border-radius: var(--r-ctl);
	background: var(--signal-tint);
	text-align: left;
}
.cr-quote-line {
	margin: 0;
	font-size: 13.5px;
	line-height: 1.5;
	color: var(--text-2);
}
.cr-quote-line b { color: var(--text); font-size: 15px; }
.cr-quote .cr-link { margin-top: 8px; }
.cr-quote-how {
	margin-top: 8px;
	padding-top: 9px;
	border-top: 1px solid var(--line);
}
.cr-quote-how p {
	margin: 0 0 7px;
	font-size: 12.5px;
	line-height: 1.55;
	color: var(--text-2);
}
.cr-quote-how p:last-child { margin-bottom: 0; }
.cr-quote .cr-fine { text-align: left; margin-top: 6px; }

/* Three chips carrying a word and a promise. The sub-line is doing real work:
   "Auto" on its own is a fact about the app, "half of Pathao" is the thing
   being undertaken, and a rider deciding whether to hand the number over reads
   the second one. It wraps rather than clipping — at 360 px "your own number"
   goes to two lines and a chip that hid half of it would be worse than a tall
   chip. */
.cr-money-choice label { padding: 9px 5px; }
.cr-money-choice span { display: block; line-height: 1.25; }
.cr-money-choice b { display: block; font-size: 13px; }
.cr-money-choice small {
	display: block;
	margin-top: 3px;
	font-size: 10.5px;
	font-weight: 400;
	color: var(--text-3);
}

/* --------------------------------------------------------- state surfaces */

/* Something is waiting on you. The ride does not happen until this is
   answered, and it used to be invisible from the home screen. */
/* §8.7 point 6: raised above the sheet with --sh-2, not .cr-act's usual
   --sh-1 — if someone is waiting on an answer, that IS the screen. */
.cr-alert {
	border-color: var(--signal-edge);
	background: linear-gradient( 180deg, var(--signal-tint), var(--surface) 70% );
	box-shadow: var(--sh-2);
}
.cr-alert h2 { color: var(--text); }

/*
 * §13.1a's "a match arriving" reaches Home too — this card only ever shows
 * up because somebody's answer just landed, which is the same "arrived"
 * moment Board's .cr-arrived already names. Scoped to .cr-act specifically
 * (the waiting branch) rather than .cr-alert generally, so the confirmed/
 * under-way branch — an already-settled state, not a new arrival — stays
 * still. screenHome() is a router target, never poll-redrawn while
 * mounted, so unlike boardCard() this needs no entering/arrived gate.
 */
.cr-alert.cr-act { animation: cr-arrive var(--dur-3) var(--ease-out) both; }
@media ( prefers-reduced-motion: reduce ) {
	.cr-alert.cr-act { animation: none; }
}

.cr-alert-who { display: flex; align-items: center; gap: 10px; }
.cr-alert-who .cr-mono { margin: 0; animation: cr-check-pop .4s var(--ease-spring) both; }
.cr-alert-who h2 { margin: 0; }
@media ( prefers-reduced-motion: reduce ) {
	.cr-alert-who .cr-mono { animation: none; }
}

.cr-alert.cr-alert-ok {
	border-color: var(--go-edge);
	background: linear-gradient( 180deg, var(--go-tint), var(--surface) 70% );
}

/* The code said out loud at the kerb. Green because it is the proof the
   confirmation was real, and big because it is read at arm's length in the
   dark, which is the only situation it is ever used in. */
.cr-pin {
	margin-top: 12px;
	padding: 16px 12px;
	border: 1px solid var(--go-edge);
	border-radius: var(--r-card);
	background: var(--surface);
	text-align: center;
}
.cr-pin b {
	display: block;
	font-size: 42px;
	font-weight: 800;
	letter-spacing: .16em;
	line-height: 1.1;
	color: var(--go);
	/* The last digit's letter-spacing would otherwise push it off centre. */
	text-indent: .16em;
}
.cr-pin small {
	display: block;
	margin-top: 8px;
	color: var(--text-2);
	font-size: 12.5px;
	line-height: 1.5;
}

.cr-plate {
	display: block;
	font-size: 15px;
	font-weight: 700;
	letter-spacing: .03em;
	color: var(--text);
	margin: 2px 0 4px;
}

/* ------------------------------------------------------ the live search */

/*
 * §8.8's reference frame: back chevron, "To X" bold, "now · from Y" grey
 * — in place of the old h2+tight-paragraph pair, and white ground behind
 * it (.cr-hot's amber gradient no longer applies to this screen at all).
 * #cr-back keeps its exact click handler; only its shape changed, from a
 * bottom-of-card text link to the chevron this row needs.
 */
/*
 * flex-start, not center — a live-device review found the chevron
 * vertically centred against the WHOLE two-line title block, which
 * reads as aligned to the second ("now · from…") line rather than
 * sharing a top baseline with "To X". A 30px button close to the h2's
 * own cap-height, nudged up 2px, keeps that baseline honest instead of
 * centred against text of a different height.
 */
.cr-board-head {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	margin-bottom: 4px;
}
.cr-board-back {
	flex: none;
	display: grid;
	place-items: center;
	width: 30px;
	height: 30px;
	margin: -2px 0 0 -6px;
	background: none;
	border: 0;
	border-radius: 50%;
	color: var(--text);
	cursor: pointer;
}
.cr-board-back .cr-ico { width: 22px; height: 22px; }
.cr-board-back:active { background: var(--sunken); }
.cr-board-head-txt { min-width: 0; }
.cr-board-head-txt h2 {
	margin: 0;
	font-size: var(--t-title);
	font-weight: 700;
	letter-spacing: -.02em;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cr-board-head-txt small { display: block; margin-top: 1px; color: var(--text-2); font-size: 13.5px; }

/*
 * The countdown, and under it the running account of what is actually being
 * done. A conic gradient rather than an animated SVG: it is one repaint a
 * second on a phone that may be three years old, and it degrades to a plain
 * ring on anything that cannot draw it.
 */
/*
 * THE SEARCH MAP, AND WHY THE ELEMENT IS BIGGER THAN THE HOLE IT SHOWS.
 *
 * render() lays tiles covering exactly the element's own box and not one
 * pixel more — x0..x1 are floors of the viewport's own edges. So sliding
 * .cr-map-plane with a transform, which is the only way to move this map
 * smoothly without re-laying every tile per frame, would drag blank ground
 * into view on the first pixel of travel.
 *
 * The answer is an element 80px larger than the window on every side, clipped
 * by .cr-boardmap-win. Everything inside 80px of travel is already drawn and
 * already paid for, so the whole watch phase costs zero tile requests and zero
 * renders — it is one composited transform. The drift below is capped at 42px
 * for that reason, and raising it past 60 is not a tuning change, it is a
 * decision to buy more tiles.
 *
 * The ring's centre sits at 150px down a 180px window, so the pickup point
 * lands just above the window's bottom edge and the ring hangs a little past
 * it onto the page's own white. That overhang is why this block carries 62px
 * of bottom margin: the ring is 118px plus #cr-radar's 20px spill, so it
 * reaches 229px against a 180px window, and the sentence underneath needs
 * clear air past that.
 */
.cr-boardmap {
	position: relative;
	margin: 14px 0 62px;
}

.cr-boardmap-win {
	position: relative;
	height: 180px;
	overflow: hidden;
	border-radius: var(--r-map);
	background: var(--sunken);
}

/*
 * height: auto is load-bearing. .cr-map declares height: 300px, and an
 * absolutely positioned box with top, bottom AND height set is
 * over-constrained — the browser keeps the height and ignores the bottom,
 * so the element would be 300px tall hanging 80px above the window instead
 * of the 340px the arithmetic above depends on.
 */
.cr-boardmap #cr-boardmap {
	position: absolute;
	inset: -80px;
	height: auto;
	width: auto;
	border: 0;
	border-radius: 0;
	cursor: default;
	touch-action: auto;
	user-select: none;
}

.cr-boardmap .cr-map-plane { will-change: transform; }

/*
 * The credit chip is dropped on THIS map only, on Abir's explicit call after
 * seeing it live: repositioned inside the visible window it read as a second
 * badge fighting the ring rather than fine print, and he chose to drop it
 * here rather than shrink or move it again. Nowhere else this app draws a
 * map is affected — the meeting screen, the place picker and every other
 * CRMap instance still carry '.cr-map-credit' untouched, so the licence
 * condition is still met everywhere a map is the actual thing being looked
 * at rather than a small live backdrop behind a countdown.
 */
.cr-boardmap .cr-map-credit { display: none; }

.cr-boardmap .cr-timer {
	position: absolute;
	left: 50%;
	top: 150px;
	transform: translate( -50%, -50% );
	margin: 0;
	z-index: 2;
}

/*
 * #cr-ring is an annulus — its middle is transparent by design, which was
 * free while the ground behind it was white and is not now. A filled disc
 * under the numerals, positioned rather than a grid item so it does not
 * push the clock out of the centre.
 */
.cr-boardmap .cr-timer::before {
	content: "";
	position: absolute;
	inset: 8px;
	border-radius: 50%;
	background: var(--ring-veil);
}

/* b is a grid item, not a positioned one, so it paints UNDER the veil
   without this. */
.cr-boardmap .cr-timer b { position: relative; z-index: 1; }

/* The search is over; the map stops being a live thing and steps back. */
.cr-boardmap.cr-boardmap-off .cr-map-plane {
	opacity: .45;
	transition: opacity .6s ease;
}

@media ( prefers-reduced-motion: reduce ) {
	.cr-boardmap .cr-map-plane { transition: none !important; }
}

/*
 * BOTTOM MARGIN IS 56px, NOT 4px, BECAUSE OF #cr-radar BELOW - a live
 * device review found the wash still sweeping over "Nobody is waiting on
 * this road yet" even at the 40px this margin was raised to previously
 * (which paired with the radar's then-26px spill for a bare 14px of
 * clearance - correct arithmetic, still not enough room on a real
 * screen). #cr-radar's own inset is down to -22px now (its own rule,
 * below), so the true spill is 22px; 56px leaves 34px of clean margin
 * past that, not just the minimum the sum implies.
 */
.cr-timer {
	position: relative;
	width: 132px;
	height: 132px;
	margin: 18px auto 56px;
	display: grid;
	place-items: center;
}

/*
 * THE RADAR SWEEP — §13.1a, "the single most-requested piece of motion in
 * the app." Not a rebuild of the ring: #cr-ring stays exactly what it was,
 * the actual countdown. This is a second, larger, unmasked layer BEHIND it —
 * a rotating conic wedge that fades from --signal-solid to nothing over 90
 * degrees, reading as a sweep passing over the search rather than a second
 * clock competing with the first. Sized past the timer's own 132px box on
 * purpose, so it reads as scanning the area around the number, not around
 * itself; .cr-timer has no overflow rule, so the overhang paints cleanly.
 *
 * Loops, and is allowed to: §13.1's rule is "decoration does not loop,
 * waiting does" — this is the waiting state for the one search this app
 * performs, not ornament.
 */
/*
 * The comet's colour sits at the HIGH-angle end of the wedge on purpose.
 * conic-gradient() measures clockwise from 12 o'clock and the sweep
 * animation also rotates clockwise, so the edge advancing onto new ground
 * each frame is the high-angle edge. Putting the bright stop there (350-
 * 360deg) makes the bright edge lead and the fade trail behind it, like a
 * beam sweeping rather than a smear being dragged backwards.
 *
 * Bolder than the round that first fixed the direction: that pass dropped
 * opacity to .28 and kept the band narrow (358-360deg) specifically
 * because the gradient was still reversed and inheriting the ring's own
 * annulus mask at the time, so anything stronger read as a second,
 * disagreeing arc. Both of those are fixed now (the direction here, the
 * mask by #cr-ring's own id selector below) - nothing is left for a quiet
 * opacity to compensate for, so .62 and a wider 350-360deg leading band
 * are the correct answer, not a reversion of the earlier fix.
 */
/*
 * LATER-TODAY-ROOT-CAUSE.md P2.3. .cr-radar alongside the id, NOT a
 * rewrite of the selector — #cr-ring's own comment below explains why a
 * generic descendant selector (`.cr-timer i`) is exactly the bug that
 * used to make the ring inherit this rule's unmasked background. Adding
 * a class here lets a pledge card reuse the identical sweep (see
 * .cr-timer-mini further down) without touching what #cr-radar already
 * means inside .cr-timer.
 */
#cr-radar, .cr-radar {
	position: absolute;
	inset: -22px;
	border-radius: 50%;
	background: conic-gradient(
		from 0deg,
		transparent 0deg,
		transparent 292deg,
		rgba( 255, 176, 32, .10 ) 320deg,
		rgba( 255, 176, 32, .55 ) 350deg,
		rgba( 255, 176, 32, .88 ) 360deg
	);
	opacity: .62;
	animation: cr-radar-sweep 2.4s linear infinite;
	pointer-events: none;
}

@keyframes cr-radar-sweep {
	from { transform: rotate( 0deg ); }
	to   { transform: rotate( 360deg ); }
}

/* Same idiom as .cr-mloc-close's reduced-motion answer: the loop stops, a
   quiet static frame says the same thing without moving anything. */
@media ( prefers-reduced-motion: reduce ) {
	#cr-radar, .cr-radar { animation: none; opacity: .16; }
}

/*
 * P2.3. THE PLEDGE CARD'S OWN SIZE — no #cr-ring, no clock text, because
 * a pledge has no countdown to sit behind. .cr-radar's inset is tuned for
 * .cr-timer's 132px box; scaled down here rather than left to overflow a
 * 20px inline icon into the row above and below it.
 */
.cr-timer-mini {
	position: relative;
	width: 20px;
	height: 20px;
	margin: 0;
	display: inline-grid;
	place-items: center;
	vertical-align: middle;
}
.cr-timer-mini .cr-radar { inset: -6px; }

/* Same idiom as .cr-timer.cr-timer-done above — a match found stops the
   sweep instead of leaving it spinning over a search that is already over. */
.cr-timer-mini.cr-timer-done .cr-radar { animation: none; opacity: 0; }

/*
 * #cr-ring specifically, not `.cr-timer i` — a live-device review found
 * the progress arc and the radar sweep compositing wrongly, and the cause
 * was exactly this generic type selector: #cr-radar is ALSO an <i> inside
 * .cr-timer, so it was silently inheriting this rule's annulus mask
 * (undeclared in #cr-radar's own rule, so no specificity contest ever
 * happened — a property either rule doesn't set falls through to
 * whatever else matches). The radar was designed as a full diffuse conic
 * wash past the ring's own edge; masked down to the ring's thin 6-7px
 * annulus at the radar's own larger box, it painted as a second,
 * disagreeing arc instead. Naming the id directly is the actual fix —
 * it cannot leak onto a sibling <i> now or later.
 */
#cr-ring {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: conic-gradient( var(--signal-solid) var(--at, 0%), var(--line) 0 );
	-webkit-mask: radial-gradient( farthest-side, transparent calc(100% - 7px), #000 calc(100% - 6px) );
	mask: radial-gradient( farthest-side, transparent calc(100% - 7px), #000 calc(100% - 6px) );
	transition: background .9s linear;
}
.cr-timer b {
	font: 700 34px/1 var(--font-data);
	font-variant-numeric: tabular-nums;
	letter-spacing: -.03em;
	color: var(--text);
}

/* The clock stops mattering the moment the search is over — the radar stops
   with it, since there is nothing left to sweep for. */
.cr-timer.cr-timer-done #cr-ring { background: var(--line); }
.cr-timer.cr-timer-done b { color: var(--text-3); }
.cr-timer.cr-timer-done #cr-radar { animation: none; opacity: 0; }

/* LOOKING stays centred under the ring — the one thing on this screen
   that is allowed to, since the ring itself is centred. Everything
   below it, including the sentence, left-aligns like the rest of the
   screen; a live-device review counted the centred sentence as one of
   five different alignments running down a single column. The
   sentence itself is no longer static — see the markup in app.js,
   where strip() rewrites it once `found` is actually known, so it
   never again claims a broadcast that reached nobody. */
.cr-timer-cap {
	margin: 2px 0 0;
	text-align: center;
	font: 600 11px var(--font-ui);
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--text-3);
}
.cr-board-sentence {
	margin: 10px 0 0;
	text-align: left;
	color: var(--text-2);
	font-size: 13.5px;
	line-height: 1.45;
}

@media ( prefers-reduced-motion: reduce ) {
	#cr-ring { transition: none; }
}

/*
 * One line per thing that happened, oldest at the top. Capped in height so a
 * long search cannot push the Stop button off the screen — the one control
 * that must never become unreachable.
 */
.cr-log {
	list-style: none;
	margin: 14px 0 0;
	padding: 0;
	max-height: 170px;
	overflow-y: auto;
	font-size: 13.5px;
	color: var(--text-2);
}
/* §8.8: a timeline — a hairline spine behind the existing dots, not a
   restructure. The dot moves to absolute positioning so the spine (::after)
   can run through its centre from one row to the next. */
.cr-log li {
	position: relative;
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 10px;
	padding: 6px 0 6px 17px;
	animation: cr-line .35s ease both;
}
.cr-log-txt { min-width: 0; }
.cr-log-time {
	flex: none;
	font: 12px var(--font-data);
	font-variant-numeric: tabular-nums;
	color: var(--text-3);
}
.cr-log li:last-child .cr-log-time,
.cr-log li.cr-good .cr-log-time { color: inherit; }
.cr-log li::before {
	content: '';
	position: absolute;
	left: 1px;
	top: 12px;
	width: 5px;
	height: 5px;
	border-radius: 50%;
	background: var(--line-strong);
}
.cr-log li::after {
	content: '';
	position: absolute;
	left: 3px;
	top: 17px;
	bottom: -6px;
	width: 1px;
	background: var(--line);
}
.cr-log li:last-child::after { display: none; }

/* The last line is where the eye should be. */
.cr-log li:last-child { color: var(--text); font-weight: 600; }
.cr-log li:last-child::before { background: var(--signal-solid); }

.cr-log li.cr-good { color: var(--go); font-weight: 600; }
.cr-log li.cr-good::before { background: var(--go-solid); }

@keyframes cr-line {
	from { opacity: 0; transform: translateY(4px); }
	to   { opacity: 1; transform: none; }
}

@media ( prefers-reduced-motion: reduce ) {
	.cr-log li { animation: none; }
}

/* Proof the app is still awake while nothing is happening. */
.cr-heartbeat {
	display: flex;
	align-items: center;
	gap: 7px;
	margin-top: 10px;
	font-size: 11.5px;
	color: var(--text-3);
}

.cr-bar { height: 5px; border-radius: 3px; background: var(--line); overflow: hidden; margin-top: 10px; }
.cr-bar i { display: block; height: 100%; background: var(--text); border-radius: 3px; transition: width .6s ease; }

.cr-contact { display: flex; gap: 8px; margin-top: 12px; }
.cr-contact a {
	flex: 1;
	text-align: center;
	padding: 13px 8px;
	border-radius: var(--r-ctl);
	font-size: 14px;
	font-weight: 650;
	text-decoration: none;
	border: 1px solid var(--line-strong);
	color: var(--text);
	min-height: 48px;
	display: grid;
	place-items: center;
}
/* Confirmed channel, and the green Bangladesh already reads as WhatsApp. */
.cr-contact a.cr-wa { background: var(--go-solid); color: var(--go-ink); border-color: var(--go-solid); }

.cr-row-links {
	display: flex;
	justify-content: space-between;
	gap: 12px;
	margin-top: 6px;
	padding-top: 8px;
	border-top: 1px dashed var(--line);
}
/* 26px tall before this, which is under a fingertip on a phone being held one
   handed - and one of the two is "Report or block". The type stays small; only
   the thing being aimed at grows. */
.cr-row-links .cr-link {
	width: auto;
	text-align: left;
	font-size: 12.5px;
	color: var(--text-2);
	padding: 13px 0 9px;
	min-height: 44px;
	display: inline-flex;
	align-items: center;
}
.cr-row-links .cr-link:hover { color: var(--text); }
.cr-row-links .cr-link[data-report]:hover { color: var(--alert); }

/* ---------------------------------------------------------- live capture */

.cr-steps { list-style: none; counter-reset: s; margin: 14px 0 0; padding: 0; }
.cr-steps li {
	counter-increment: s;
	position: relative;
	padding: 0 0 14px 38px;
	font-size: 14px;
}
.cr-steps li::before {
	content: counter(s);
	position: absolute;
	left: 0;
	top: 0;
	width: 26px;
	height: 26px;
	border-radius: 50%;
	background: var(--sunken);
	border: 1px solid var(--line-strong);
	color: var(--text);
	font: 700 13px/1 var(--font-data);
	display: grid;
	place-items: center;
}
.cr-steps b { display: block; font-weight: 650; }
.cr-steps span { color: var(--text-2); font-size: 13px; }

/*
 * §8.4 — full-bleed camera, black ground, everything else floating over it.
 * Reuses .cr-meet.cr-full's proven position:fixed;inset:0 pattern (already
 * shipping on the live-trip screen) instead of a new technique. Nothing
 * here is read by cardStep()/selfieStep()'s own logic — cam.open() attaches
 * to #cr-vid regardless of how it is laid out, and the sharpness/motion/
 * canvas pipeline reads the video element's intrinsic pixels, not its CSS
 * box.
 */
.cr-cam-full {
	position: fixed;
	inset: 0;
	z-index: 70;
	background: #000;
	overflow: hidden;
}
.cr-cam-full video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* Deliberately not mirrored: a mirrored preview makes people flip the ID
	   card round, and then the writing is backwards in the photo. */
}

.cr-cam-top {
	position: absolute;
	left: 0; right: 0; top: 0;
	padding: calc( 18px + env(safe-area-inset-top, 0px) ) 20px 30px;
	background: linear-gradient( to bottom, rgba(0,0,0,.75), rgba(0,0,0,0) );
	text-align: center;
}
.cr-cam-top h2 { color: #fff; font-size: var(--t-head); font-weight: 700; margin: 0 0 4px; }
.cr-cam-top p  { color: rgba(255,255,255,.85); font-size: var(--t-sub); margin: 0; }

.cr-cam-bottom {
	position: absolute;
	left: 0; right: 0; bottom: 0;
	padding: 22px 20px calc( 26px + env(safe-area-inset-bottom, 0px) );
	background: linear-gradient( to top, rgba(0,0,0,.75), rgba(0,0,0,0) );
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 14px;
}
.cr-cam-bottom .cr-btn { max-width: 340px; }
.cr-cam-note { color: rgba(255,255,255,.7); font-size: 12.5px; text-align: center; margin: 0; }

/* A 72px circular shutter with a 4px ring (§8.4), not a full-width label —
   #cr-shoot keeps its id and click handler; only its shape changes. */
.cr-shutter {
	width: 72px;
	height: 72px;
	flex: none;
	border-radius: 50%;
	background: #fff;
	border: 4px solid rgba(255,255,255,.45);
	box-shadow: 0 2px 14px rgba(0,0,0,.45);
	cursor: pointer;
	transition: transform var(--dur-1) var(--ease-out);
}
.cr-shutter:active { transform: scale(.92); }

.cr-cam-link { color: #fff; text-decoration: underline; font-size: 13.5px; }

/*
 * The cut-out. box-shadow with a huge spread and no blur paints outward
 * from the element's own box in every direction; .cr-cam-full's overflow:
 * hidden clips that to the screen, which is what turns "a rectangle with a
 * shadow" into "a scrim with a hole in it" using one property and no extra
 * DOM. The corner brackets are a second, independent background layer on
 * the same element.
 */
.cr-guide {
	position: absolute;
	inset: 11% 7%;
	pointer-events: none;
	border-radius: var(--r-ctl);
	box-shadow: 0 0 0 100vmax rgba(0,0,0,.6);
	background:
		linear-gradient(#fff,#fff) left  top    / 26px 3px no-repeat,
		linear-gradient(#fff,#fff) left  top    / 3px 26px no-repeat,
		linear-gradient(#fff,#fff) right top    / 26px 3px no-repeat,
		linear-gradient(#fff,#fff) right top    / 3px 26px no-repeat,
		linear-gradient(#fff,#fff) left  bottom / 26px 3px no-repeat,
		linear-gradient(#fff,#fff) left  bottom / 3px 26px no-repeat,
		linear-gradient(#fff,#fff) right bottom / 26px 3px no-repeat,
		linear-gradient(#fff,#fff) right bottom / 3px 26px no-repeat;
}

/* Lower and narrower than the ID card's guide — room for #cr-prompt above
   it without crowding .cr-cam-top's header text, and a taller aperture
   suits a face-plus-card frame better than the card's wider one. */
.cr-guide-selfie { inset: 24% 11% 16%; }

/*
 * #cr-prompt's text and its timing (1600ms per prompt, driven by
 * runPoses()'s own promise chain) are untouched — only repositioned to sit
 * above the aperture instead of pinned to the bottom, and given its own
 * scrim chip since it now floats over open video rather than a gradient
 * bar. No crossfade transition added: textContent changes do not animate
 * via CSS on their own, and adding JS-driven opacity toggling would mean
 * editing runPoses(), which §8.4 says to leave alone. Left for the motion
 * phase (§13), same reasoning as the stepper's fill-in animation (Phase 2).
 */
.cr-prompt {
	position: absolute;
	left: 50%;
	top: 16%;
	transform: translateX(-50%);
	max-width: 86%;
	padding: 10px 18px;
	border-radius: var(--r-pill);
	background: rgba(0,0,0,.55);
	backdrop-filter: blur(8px);
	-webkit-backdrop-filter: blur(8px);
	color: #fff;
	font-size: 17px;
	font-weight: 600;
	text-align: center;
	white-space: nowrap;
}

.cr-count {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	font-family: var(--font-data);
	font-size: 88px;
	font-weight: 800;
	color: #fff;
	text-shadow: 0 3px 22px rgba(0,0,0,.85);
	pointer-events: none;
}

.cr-shot {
	margin-top: 12px;
	border-radius: var(--r-card);
	overflow: hidden;
	background: #000;
	border: 1px solid var(--line);
}
.cr-shot img { display: block; width: 100%; height: auto; }

/* --------------------------------------------------------- place search */

.cr-search { position: relative; margin-top: 12px; z-index: 5; }

/* Same shape as the search pill on the home screen, so "type a place" looks
   like one thing wherever the app asks for it. */
.cr-search input[type=search] {
	width: 100%;
	font-size: 16px;
	padding: 14px 16px;
	background: var(--sunken);
	border: 1px solid var(--line);
	border-radius: var(--r-pill);
	color: var(--text);
	appearance: none;
}
.cr-search input:focus { outline: none; border-color: var(--text); box-shadow: 0 0 0 3px rgba(0,0,0,.09); }

.cr-hits {
	display: none;
	position: absolute;
	left: 0;
	right: 0;
	top: calc(100% + 4px);
	margin: 0;
	padding: 4px;
	list-style: none;
	background: var(--surface);
	border: 1px solid var(--line-strong);
	border-radius: var(--r-ctl);
	box-shadow: 0 14px 34px rgba(0,0,0,.16);
	max-height: 260px;
	overflow-y: auto;
}
.cr-hits.cr-open { display: block; }

.cr-hits li {
	padding: 11px;
	border-radius: var(--r-sm);
	cursor: pointer;
}
.cr-hits li:hover { background: var(--sunken); }
.cr-hits b { display: block; font-size: 14px; font-weight: 600; }
.cr-hits small {
	display: block;
	color: var(--text-2);
	font-size: 12.5px;
	margin-top: 1px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cr-hits li.cr-none { color: var(--text-2); font-size: 13px; cursor: default; }
.cr-hits li.cr-none:hover { background: none; }

/* ISSUE 6. The quiet line between the app's own local results and the
   geocoder's, underneath them - never interactive, never confused with a
   result. Only appears when both halves actually have something to show. */
.cr-hits li.cr-search-head {
	padding: 9px 11px 5px;
	margin-top: 2px;
	border-top: 1px solid var(--line);
	color: var(--text-2);
	font-size: 11.5px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .02em;
	cursor: default;
}
.cr-hits li.cr-search-head:hover { background: none; }

/* -------------------------------------------------------------------- map */

/*
 * §8.13 screenPlace() — full-bleed picker. .cr-map keeps its normal boxed
 * shape everywhere else (the meet screen, screenRoute()); this overrides it
 * only inside .cr-place-full, the same scoping approach as
 * .cr-meet.cr-full's own override of the same base class.
 */
.cr-place-full {
	position: fixed;
	inset: 0;
	z-index: 65;
	background: var(--sunken);
	overflow: hidden;
}
.cr-place-full .cr-map {
	position: absolute;
	inset: 0;
	height: 100%;
	border: 0;
	border-radius: 0;
}
/* Nudged above dead-centre for clearance over the sheet below (see its own
   comment) — map.js sets the pin's base position, this only overrides it
   inside the picker. */
.cr-place-full .cr-map-pin { top: 38%; }
.cr-place-top {
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	z-index: 10;
	padding: calc( 14px + env(safe-area-inset-top, 0px) ) 14px 0;
}
.cr-place-top .cr-search { margin-top: 0; }
.cr-place-top .cr-search input[type=search] { box-shadow: var(--sh-2); }
/*
 * Capped well short of 50% on purpose. .cr-map-pin (map.js's own markup,
 * unchanged) sits at the dead centre of #cr-map's box — which now fills
 * the whole screen — so a sheet allowed to grow past half the viewport
 * paints directly over the pin, hiding the one thing this screen exists to
 * show. overflow-y makes a shorter cap safe: content that needs more room
 * scrolls inside the sheet rather than being cut off. Found by testing the
 * built layout, not by inspection — the pin was invisible before this.
 */
.cr-place-sheet {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 10;
	max-height: 46%;
	overflow-y: auto;
	background: var(--surface);
	border-radius: var(--r-sheet) var(--r-sheet) 0 0;
	box-shadow: var(--sh-sheet);
	padding: 18px 16px calc( 18px + env(safe-area-inset-bottom, 0px) );
}

.cr-map {
	position: relative;
	height: 300px;
	border-radius: var(--r-card);
	overflow: hidden;
	border: 1px solid var(--line);
	background: var(--sunken);
	cursor: grab;
	/* The page keeps vertical scrolling. A map embedded in a scrolling screen
	   that declares touch-action: none swallows every thumb that lands on it,
	   and on the meeting screen that is a 260 px trap across the middle of the
	   page. Two fingers move the map; see CRMap.own. */
	touch-action: pan-y;
	user-select: none;
}
/* Where the map IS the task — placing a pin — it takes the whole gesture. */
.cr-map.cr-map-own { touch-action: none; }
.cr-map:active { cursor: grabbing; }
.cr-map:focus-visible { outline: 2px solid var(--pri); outline-offset: 2px; }

/* Said only to somebody who has just tried to move the map the other way. */
.cr-map-hint {
	position: absolute;
	left: 50%;
	bottom: 14px;
	transform: translateX(-50%);
	z-index: 5;
	padding: 7px 13px;
	border-radius: 999px;
	background: rgba(17,20,24,.82);
	color: #fff;
	font-size: 12.5px;
	font-weight: 600;
	white-space: nowrap;
	pointer-events: none;
	opacity: 0;
	transition: opacity .2s ease;
}
.cr-map-hint.cr-on { opacity: 1; }

.cr-map-plane { position: absolute; inset: 0; }

/* The route line. Casing under core so it reads on pale streets and on
   satellite imagery without needing a different colour for each. The core is
   a violet that appears in none of the four tile styles, so the corridor is
   never lost in a road — and it stays outside the semantic palette, so the map
   never looks like it is offering an action. */
.cr-map-line {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
}
.cr-line-case {
	stroke: var(--route-case);
	stroke-width: 10;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.cr-line-core {
	stroke: var(--route);
	stroke-width: 5;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* AND THE ROAD ITSELF MOVES.
 *
 * The chevrons below say which way once you look at them. This says it without
 * being looked at: a dashed overlay on the same path, sliding along it, so the
 * route reads as a direction at a glance held low in one hand. Deliberately
 * faint — it is a hint about a road, not a second road, and anything brighter
 * competes with the two markers that are the actual content of this screen.
 *
 * Off entirely for reduced motion, where the chevrons already do the job. */
.cr-line-flow {
	stroke: #fff;
	stroke-width: 3.4;
	stroke-linecap: round;
	/* A dash long enough to read as a thing travelling, and a gap long enough
	   that it is plainly one thing rather than a dashed road. 3px ticks were
	   the first attempt and they read as texture, not as motion. */
	stroke-dasharray: 11 30;
	opacity: .62;
	animation: cr-flow 1.6s linear infinite;
}
@keyframes cr-flow {
	from { stroke-dashoffset: 41; }
	to   { stroke-dashoffset: 0; }
}
@media (prefers-reduced-motion: reduce) {
	.cr-line-flow { animation: none; opacity: 0; }
}

/*
 * ISSUE 2D. BOUND THE COST ON A LONG LINE. Animating stroke-dashoffset
 * re-rasterises the WHOLE path every frame - it is not compositor-
 * accelerated - and the approach line can run to CR_MEET_ROAD_MAX_M
 * (8000 m) at CR_SAMPLE_M (60 m) spacing, well over a hundred points. On a
 * line that long the flow also says nothing useful: it runs off both edges
 * of the screen at once, and the chevrons already carry the direction.
 * map.js's drawLine() writes this class when the line has more than 60
 * points; independent of the reduced-motion guard above, both may apply
 * at once.
 */
.cr-line-long .cr-line-flow { animation: none; opacity: 0; }

/* WHICH WAY HE IS COMING DOWN IT. A road drawn as a line has two ends, and on
   a still map you work out which end he is coming from by looking at where the
   dot is — and get it wrong. Chevrons say it without a word. */
.cr-line-way {
	stroke: #fff;
	stroke-width: 2.2;
	stroke-linecap: round;
	stroke-linejoin: round;
	opacity: .9;
}

/* ISSUE 4. THE SHARED JOURNEY, DRAWN LIKE ONE.
 *
 * Before pickup this is the whole ride to come — solid, full opacity, a
 * weight between the approach (5px core) and the onward tail (3.5px core) —
 * so it reads as "the journey" rather than as context behind the approach
 * line, which is what the tail channel's own faint/dashed style said when
 * this used to share it (map.js's setTail() docblock is right about what
 * THAT channel means; it was just being asked to mean two things). Cleared
 * the moment the ride actually starts — see roads()'s own comment — because
 * .cr-map-line is carrying the same geometry in the loud channel by then. */
.cr-map-ride {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
}
.cr-ride-case {
	stroke: var(--ride-case);
	stroke-width: 8.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.cr-ride-core {
	stroke: var(--ride);
	stroke-width: 4.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* WHERE HE GOES ON ALONE. The same road, drawn quieter, past the point she
   gets off it — thinner than the shared stretch and half its opacity, so it
   reads as the rest of one journey rather than as a second route. Only ever on
   the rider's phone, and the server trims its far end short of his door. */
.cr-map-tail {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
	opacity: .5;
}
.cr-tail-case {
	stroke: rgba(0,0,0,.45);
	stroke-width: 7.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.cr-tail-core {
	stroke: var(--route);
	stroke-width: 3.5;
	stroke-dasharray: 9 7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* THE WALK — the pavements, not the bearing.
 *
 * Deliberately not the route's colour: his line is a road a bike is coming
 * down, hers is the way she has to walk, and one must not be mistaken for the
 * other. Solid, where the bearing line is dashed, because this one IS the way
 * rather than the direction of it. */
.cr-map-walk {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
}
.cr-walk-case {
	stroke: rgba(0,0,0,.5);
	stroke-width: 9;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.cr-walk-core {
	stroke: var(--go-solid);
	stroke-width: 4.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.cr-walk-head {
	fill: var(--go-solid);
	stroke: rgba(0,0,0,.5);
	stroke-width: 2;
	stroke-linejoin: round;
}

/* How far across the screen actually is. Without it, "300 m away" is a number
   with nothing on the map to measure itself against — and the judgement this
   screen exists for is a judgement about distance. */
.cr-map-scale {
	position: absolute;
	left: 10px;
	bottom: 10px;
	z-index: 4;
	pointer-events: none;
	display: flex;
	align-items: center;
	gap: 6px;
}
/* §10: the fourth map-chrome element, missed when .cr-map-zoom/-credit/-none
   were tokenised — still painting from literals, the exact trap that shipped
   a +/- at 1.04:1 in dark once already. text-shadow uses --chrome rather
   than a fixed white, so the halo is dark in dark mode instead of a light
   halo behind light text, which would have been no halo at all. */
.cr-map-scale i {
	display: block;
	height: 7px;
	border: 1.5px solid var(--chrome-edge);
	border-top: 0;
	background: var(--chrome);
}
.cr-map-scale b {
	font: 700 10.5px/1 var(--font-ui);
	color: var(--chrome-ink);
	text-shadow: 0 1px 3px var(--chrome);
}
/* WHICH WAY TO WALK. The route line says where the road goes; this says which
   end of it the other person is at, and it is the only thing on the map that
   moves along its own length — the marching dashes give it a direction before
   the arrowhead is even resolved, which at arm's length in daylight is what
   actually gets read. Drawn over the route and under the people. */
.cr-map-link {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
	z-index: 2;
}
/*
 * THE ORDER THINGS ARE PAINTED IN, stated once and in one place.
 *
 * Tiles carry a z-index of their own so a half-loaded new level can sit over
 * the old one, and that put them level with the route — which meant a tile
 * created after the line was drawn painted straight over the road. Equal
 * z-index is decided by DOM order, and tiles are created constantly.
 *
 *   0  the level below, held while this one loads
 *   1  the tiles
 *   2  his road, and under it the part of it she is not on
 *   3  the shared ride — issue 4's own fix — above the approach, below the walk
 *   4  her walk
 *   5  the bearing arrow
 *   6  the people
 */
.cr-map-tail  { z-index: 2; }
.cr-map-line  { z-index: 2; }
.cr-map-ride  { z-index: 3; }
.cr-map-walk  { z-index: 4; }
.cr-map-link  { z-index: 5; }
.cr-map-marks { z-index: 6; }

.cr-link-case {
	stroke: rgba(0,0,0,.55);
	stroke-width: 8;
	stroke-linecap: round;
}
.cr-link-core {
	stroke: var(--go-solid);
	stroke-width: 3.5;
	stroke-linecap: round;
	stroke-dasharray: 9 7;
	animation: cr-march 1s linear infinite;
}
.cr-link-head {
	fill: var(--go-solid);
	stroke: rgba(0,0,0,.55);
	stroke-width: 2;
	stroke-linejoin: round;
}
@keyframes cr-march {
	to { stroke-dashoffset: -16; }
}
@media (prefers-reduced-motion: reduce) {
	.cr-link-core { animation: none; }
}

/* Positioned by transform, so left/top stay at the origin and the browser
   never lays the page out to move one. Faded in because a tile that appears
   instantly at full strength pops, and a whole grid of them popping in a
   random order is the thing that makes a home-made map look home-made. */
.cr-tile {
	position: absolute;
	left: 0;
	top: 0;
	width: 256px;
	height: 256px;
	pointer-events: none;
	opacity: 0;
	transition: opacity .18s linear;
	/* The backdrop level is drawn scaled up from this corner, so the corner is
	   where the scaling has to happen from. */
	transform-origin: 0 0;
}
.cr-tile.cr-tile-on { opacity: 1; }

/* Asked for three times and never arrived. A plain surface reads as "no detail
   here"; the browser's broken-image mark reads as a broken app. */
.cr-tile.cr-tile-gone {
	opacity: 1;
	background: var(--sunken);
}

@media (prefers-reduced-motion: reduce) {
	.cr-tile { transition: none; }
}

/* The pin is fixed dead centre; the map moves under it. */
.cr-map-pin {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 0;
	height: 0;
	pointer-events: none;
}
.cr-map-pin span {
	position: absolute;
	left: -11px;
	top: -26px;
	width: 22px;
	height: 22px;
	border-radius: 50% 50% 50% 0;
	transform: rotate(-45deg);
	background: var(--pri);
	border: 2px solid var(--bg);
	box-shadow: 0 3px 10px rgba(0,0,0,.7);
}
.cr-map-pin::after {
	content: '';
	position: absolute;
	left: -3px;
	top: -3px;
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--pri-ink);
}

.cr-map-zoom {
	position: absolute;
	right: 10px;
	top: 10px;
	display: flex;
	flex-direction: column;
	gap: 5px;
}
.cr-map-zoom button {
	width: 36px;
	height: 36px;
	border: 1px solid var(--line-strong);
	background: rgba(255,255,255,.94);
	color: var(--text);
	font-size: 19px;
	line-height: 1;
	border-radius: var(--r-sm);
	cursor: pointer;
}

.cr-map-credit {
	position: absolute;
	right: 6px;
	bottom: 4px;
	font-size: 10px;
	color: var(--text-2);
	background: rgba(255,255,255,.85);
	padding: 1px 5px;
	border-radius: 4px;
}

/*
 * A map with no imagery is still a map — the roads, the two people and the
 * scale are all drawn on top and all still true. The surface is darkened a
 * step so the lines and dots have something to sit against once the tiles that
 * used to give them contrast are gone.
 */
.cr-map-dark { background: var(--sunken); }

/*
 * A DARK MAP UNDER A DARK CARD.
 *
 * The meet card is dark and the tiles are not — every raster basemap worth
 * using is drawn for a white page, so a card that is right at midnight sits on
 * a rectangle of daylight, and the one screen somebody uses standing in a road
 * at night is the one that flashes.
 *
 * Inverting and rotating the hue back is the standard trick and it is applied
 * to the TILES ONLY: the route, the walk line, the markers and the labels are
 * SVG and DOM on top of the tiles, so they keep their own colours and their own
 * contrast decisions. Brightness and saturation come back down afterwards
 * because a straight invert turns tarmac into a light box.
 *
 * Scoped to .cr-dark, so a light-mode map is left exactly as its cartographer
 * drew it.
 */
.cr-dark .cr-map-plane img {
	filter: invert(1) hue-rotate(180deg) brightness(.86) contrast(.92) saturate(.72);
}

.cr-map-none {
	position: absolute;
	inset: auto 12px 12px;
	font-size: 12.5px;
	color: var(--text-2);
	background: rgba(255,255,255,.94);
	border: 1px solid var(--line);
	padding: 9px 10px;
	border-radius: var(--r-sm);
	text-align: center;
}

.cr-coords {
	margin-top: 8px;
	font: 12px/1 var(--font-data);
	color: var(--text-2);
	text-align: center;
	font-variant-numeric: tabular-nums;
}
/* The name leads and the numbers follow it. Five decimal places of latitude
   confirm nothing to a person checking they have put the pin on the right
   road; "≈ Kuril" confirms it at a glance — and it is the name the place will
   actually be saved under. */
.cr-coords b {
	display: block;
	margin-bottom: 3px;
	font: 600 13.5px/1.2 var(--font-ui);
	color: var(--text);
}
.cr-coords b:empty { display: none; }

/* ---------------------------------------------------------------- ranges */

/* §8.8: a proper track+thumb rather than the bare native control. Every
   vendor pseudo-element pair styled the same way, since none of them fall
   back gracefully to each other. */
.cr-range {
	width: 100%;
	height: 20px;
	margin: 10px 0 4px;
	appearance: none;
	-webkit-appearance: none;
	background: transparent;
	cursor: pointer;
}
.cr-range::-webkit-slider-runnable-track {
	height: 4px;
	border-radius: var(--r-pill);
	background: var(--line);
}
.cr-range::-moz-range-track {
	height: 4px;
	border-radius: var(--r-pill);
	background: var(--line);
}
.cr-range::-webkit-slider-thumb {
	appearance: none;
	-webkit-appearance: none;
	width: 20px;
	height: 20px;
	margin-top: -8px;
	border-radius: 50%;
	background: var(--surface);
	border: 2px solid var(--text);
	box-shadow: var(--sh-1);
	cursor: pointer;
}
.cr-range::-moz-range-thumb {
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: var(--surface);
	border: 2px solid var(--text);
	box-shadow: var(--sh-1);
	cursor: pointer;
}
.cr-range:focus-visible::-webkit-slider-thumb,
.cr-range:focus-visible::-moz-range-thumb {
	outline: 2px solid var(--text);
	outline-offset: 2px;
}

/* ------------------------------------------------------------------ days */

/* §8.13: Uber's day selector — 40px circles rather than a row of stretched
   rectangular chips. Shared by screenTrip()'s day picker and Home's
   week-mode weekday group (#cr-weekdays), so both read consistently. */
.cr-days { display: flex; justify-content: space-between; gap: 4px; margin-top: 6px; }

/* The visible circle is 40px per §8.13; 2px of padding on the label brings
   the actual tap target to the 44px floor §14.1 requires without changing
   what's drawn. */
.cr-day { flex: none; margin: 0; padding: 2px; }
.cr-day input { position: absolute; opacity: 0; pointer-events: none; }
.cr-day span {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	border-radius: 50%;
	font-size: 12.5px;
	font-weight: 700;
	border: 1px solid var(--line);
	background: var(--sunken);
	color: var(--text-2);
	cursor: pointer;
}
/* Filled, not outlined. Six selected days each wearing a ring is six things
   shouting; a filled chip says "on" once and stays quiet. */
.cr-day input:checked + span {
	border-color: var(--pri);
	color: var(--pri-ink);
	background: var(--pri);
}

/* Three chips carrying two words each, so they get a little more room than the
   four one-word ones they replaced — and they never wrap, because a chip that
   goes to two lines stops reading as one choice. */
.cr-when label { padding: 11px 4px; font-size: 12.5px; white-space: nowrap; }

/* Inside a fold the sub-choice is a detail of the mode above it, not a second
   decision of equal weight. */
.cr-fold .cr-choice label { font-size: 12.5px; }

/* ------------------------------------------------------------ share page */

/* Public, no login, no app chrome. Nothing here is an action, so the action
   colour would be a lie — green and neutrals only. */
.cr-share .cr-top { justify-content: center; border-bottom: 0; margin-bottom: 20px; }
.cr-share .cr-card { text-align: center; }
/* §8.14: the "X is riding with Y" line, the one fact this page exists for. */
.cr-share .cr-card h2 { font-size: var(--t-title); line-height: 1.25; }
.cr-share .cr-facts { text-align: left; margin-top: 6px; }
.cr-share .cr-facts b { font-size: 13.5px; }
.cr-share-foot {
	margin-top: 18px;
	text-align: center;
	font-size: 11px;
	color: var(--text-3);
}

/* ------------------------------------------------------------ suspended */

/* Every other screen in the app is a top-anchored stack. This one is centred
   on purpose, so it does not read as a page that failed to load. */
.cr-stop {
	min-height: 62vh;
	display: flex;
	flex-direction: column;
	justify-content: center;
}
.cr-stop .cr-card { border-color: var(--alert-edge); }
.cr-stop h2 { color: var(--alert); }

/* ------------------------------------------------------------- tab bar */

/*
 * Home / Activity / Account. One tap back to a known place, from anywhere.
 * Absent on purpose during signup, the camera check and the suspended screen —
 * see shell().
 *
 * A floating bar rather than a full-width strip welded to the bottom edge: it
 * reads as a control sitting on top of the page instead of a border the page
 * ends at, which is what stops a scrolling list feeling cut off.
 */
/*
 * 61, NOT 40 - a live-render check caught what reading the CSS alone did
 * not: .cr-meet.cr-full (the live-trip screen) is ALSO position:fixed, at
 * z-index 60, and the two are siblings rather than one nested inside the
 * other's stacking context, so the higher number wins outright regardless
 * of anything the sheet or the map's own chrome declare underneath it. At
 * 40 the bar was still `display:flex` and still painted - it was simply
 * buried under the full-screen card's own opaque ground, unclickable and
 * invisible despite every computed-style check saying it was there. 61
 * clears .cr-full's 60 with room under .cr-place-full (65) and .cr-cam-full
 * (70), neither of which renders a tab bar at all, so nothing above the bar
 * loses anything by this being higher than it used to be.
 */
.cr-tabs {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 61;
	display: flex;
	max-width: 460px;
	margin: 0 auto;
	padding: 6px 8px;
	background: var(--surface);
	border-radius: var(--r-pill);
	box-shadow: var(--sh-3);
	/* Clears the iPhone home indicator, and is simply 0 everywhere else. */
	margin-bottom: calc( 10px + env(safe-area-inset-bottom, 0px) );
	inset-inline: 10px;
}

/* Room to scroll past the bar, so it never covers the last button on a page. */
/* Bar height (52px tab + 12px bar padding = 64px) + its inset (10px) + a
   little breathing room, plus the safe area on top — §7.1: floating chrome
   has to be accounted for in padding, not just its own margin, or the last
   row of a scrolling screen hides underneath it. */
.cr-wrap.cr-has-tabs { padding-bottom: calc( 86px + env(safe-area-inset-bottom, 0px) ); }

.cr-tab {
	position: relative;
	flex: 1;
	background: none;
	border: 0;
	padding: 7px 4px 6px;
	color: var(--text-3);
	font: 500 11px/1.3 var(--font-ui);
	letter-spacing: .005em;
	cursor: pointer;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 3px;
	min-height: 52px;
	border-radius: 20px;
}

/*
 * The icon sits in its own chip, and only the current tab's chip is filled.
 * One filled thing in the bar, the same rule the rest of the app follows — the
 * old bar coloured the whole active tab amber, which put a second lit lamp on
 * every screen and made the pending dot beside it meaningless.
 */
.cr-tab .cr-ico {
	width: 24px;
	height: 24px;
	padding: 5px 14px;
	box-sizing: content-box;
	border-radius: var(--r-pill);
	stroke-width: 1.9;
}
.cr-tab.cr-on { color: var(--text); font-weight: 650; }
.cr-tab.cr-on .cr-ico { background: var(--line); stroke-width: 2.1; }
.cr-tab:active { transform: scale(.96); }
.cr-tab:active .cr-ico { background: var(--sunken); }

/* Something needs answering. A dot, not a number — the count is on the card
   itself, and this only has to say "in here". */
.cr-tab-dot {
	position: absolute;
	top: 5px;
	left: 50%;
	margin-left: 12px;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--signal-solid);
	border: 2px solid var(--surface);
}

/* ------------------------------------------------------- the search pill */

/*
 * §8.7 point 2: "Where to?" — the one entry point the home screen is built
 * around, now the outer shell for two controls rather than one button.
 * Tapping the destination side expands a list of saved places in place; it
 * does not navigate, because being thrown onto a settings screen to answer
 * "where are you going" is what made this app feel like a form. "Later"
 * lives behind a hairline divider in the same bar and reveals the When
 * question independently (see setDest()/#cr-later's handler in app.js).
 */
.cr-searchpill {
	display: flex;
	align-items: stretch;
	width: 100%;
	/* Black fill, not --sunken — the mock's own search bar, and this
	   component has exactly one call site (screenHome()), so the colour
	   is set directly rather than through a modifier class. */
	background: var(--pri);
	border: 1px solid var(--pri);
	border-radius: var(--r-ctl);
	min-height: 56px;
}
.cr-searchpill:has(.cr-searchpill-main[aria-expanded=true]) { border-color: var(--text-2); }

.cr-searchpill-main {
	display: flex;
	align-items: center;
	gap: 12px;
	flex: 1 1 auto;
	min-width: 0;
	padding: 15px 17px;
	background: none;
	border: 0;
	color: var(--pri-ink);
	font: 600 16px/1.3 var(--font-ui);
	letter-spacing: -.01em;
	text-align: left;
	cursor: pointer;
}
.cr-searchpill-main .cr-ico { width: 20px; height: 20px; color: var(--pri-ink); opacity: .75; flex: none; }
.cr-searchpill-main > span {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* --pri-ink at low opacity, not a literal white — --pri inverts in the
   dark palette (light fill, dark ink), and a hardcoded white line would
   vanish against it there. */
.cr-searchpill-div { width: 1px; margin: 10px 0; background: var(--pri-ink); opacity: .25; flex: none; }

.cr-searchpill-later {
	display: flex;
	align-items: center;
	gap: 7px;
	flex: none;
	padding: 0 18px;
	background: none;
	border: 0;
	color: var(--pri-ink);
	font: 600 15px/1.3 var(--font-ui);
	cursor: pointer;
}
.cr-searchpill-later .cr-ico { width: 18px; height: 18px; opacity: .75; }
.cr-searchpill-later:hover { opacity: .85; }
.cr-searchpill-main:active,
.cr-searchpill-later:active { opacity: .78; }

/*
 * The search bar and everything it opens, at the very top of the home screen.
 * When it is shut it is one pill; when it is open it is a text box, the places
 * the geocoder found, and every route already saved.
 */
.cr-hero { margin-bottom: 16px; }

/* Not overflow:hidden, however much the rounded corners want it — the
   geocoder's dropdown is positioned against .cr-search inside here, and
   clipping the panel clips the results to nothing. */
.cr-hero #cr-wherepanel {
	margin-top: 8px;
	border: 1px solid var(--line);
	border-radius: var(--r-card);
	background: var(--surface);
}
.cr-hero .cr-search { margin: 12px 12px 4px; }
.cr-hero .cr-hits { z-index: 20; }
.cr-hero .cr-suggest { padding-bottom: 4px; }

/*
 * The two ends of a journey in one bordered box, origin above destination —
 * the shape every trip planner uses, and the reason it works is that the pair
 * reads as a single question rather than two unrelated dropdowns.
 */
.cr-pillbox {
	margin-top: 8px;
	border: 1.5px solid var(--line-strong);
	border-radius: var(--r-card);
	overflow: hidden;
	background: var(--sunken);
}
.cr-pillbox .cr-endpoint {
	display: flex;
	align-items: center;
	gap: 13px;
	width: 100%;
	padding: 14px 15px;
	background: none;
	border: 0;
	color: var(--text);
	font: 600 15.5px/1.3 var(--font-ui);
	letter-spacing: -.01em;
	text-align: left;
	cursor: pointer;
	min-height: 52px;
}
/* The expanded list sits between the two ends, so this cannot be an adjacent
   sibling rule — the second end still needs its line whether the first end's
   list is open or shut. */
.cr-pillbox > .cr-endpoint:not(:first-child) { border-top: 1px solid var(--line); }
.cr-pillbox .cr-endpoint > span {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cr-pillbox .cr-endpoint.cr-unset > span { color: var(--text-2); font-weight: 500; }

/* The end that is not a choice — the pin being placed on the map above. It
   still belongs in the box, because a destination with no origin beside it is
   not a journey. */
.cr-pillbox .cr-endpoint.cr-fixed { cursor: default; }
.cr-pillbox .cr-endpoint[aria-expanded=true] { background: var(--surface); }

/* Ring for where you are, square for where you are going. Same pairing as the
   trip diagram, so the two teach each other. */
.cr-dotmark { width: 11px; height: 11px; flex: none; border-radius: 50%; border: 2.5px solid var(--text-2); }
.cr-dotmark.cr-dest { border: 0; border-radius: 2px; background: var(--text); }

/* The list a pill expands into. In place, under the pill it belongs to. */
.cr-suggest { list-style: none; margin: 0; padding: 4px 0 0; }
.cr-suggest li {
	display: flex;
	align-items: center;
	gap: 13px;
	padding: 12px 15px;
	cursor: pointer;
	border-top: 1px solid var(--line);
	font-size: 14.5px;
}
.cr-suggest li:hover { background: var(--surface); }
.cr-suggest .cr-ico { width: 18px; height: 18px; color: var(--text-2); flex: none; }
.cr-suggest b { font-weight: 600; display: block; }
.cr-suggest small { display: block; color: var(--text-2); font-size: 12px; margin-top: 1px; }
.cr-suggest li.cr-none { color: var(--text-2); font-size: 13px; cursor: default; display: block; }
.cr-suggest li.cr-none:hover { background: none; }

/* A choice the browser has already refused this session. Dimmed rather than
   removed: it may well be fixable, and a row that vanishes reads as a feature
   that is gone rather than one that is being blocked. Still tappable, because
   the person may have just fixed it in another tab. */
.cr-suggest li.cr-off { opacity: .55; }
.cr-suggest li.cr-off small { color: var(--signal); }

/* ----------------------------------------------------------- role toggle */

/*
 * Need a ride / Offering a ride, the way Uber switches between riding and
 * driving. Not an account setting: role in this app lives on the ROUTE, so
 * this only chooses which of your routes the button below acts on. One person
 * can genuinely be both, on different days.
 */
.cr-toggle {
	display: flex;
	gap: 4px;
	padding: 4px;
	background: var(--sunken);
	border: 1px solid var(--line);
	border-radius: var(--r-pill);
	margin-bottom: 16px;
}
.cr-toggle button {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 7px;
	padding: 10px 8px;
	border: 0;
	border-radius: var(--r-pill);
	background: none;
	color: var(--text-2);
	font: 600 13.5px/1.2 var(--font-ui);
	letter-spacing: -.01em;
	cursor: pointer;
	min-height: 44px;
}
.cr-toggle button .cr-ico { width: 17px; height: 17px; }
.cr-toggle button.cr-on { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.14); }

/* -------------------------------------------------------- account rows */

/*
 * The Account tab is a plain vertical list — icon, label, no chevron (§8.12
 * point 5: chev() is reserved for a row that navigates to a destination, and
 * these are settings) — in small labelled groups. Everything that gets set
 * up once and then never thought about again lives here, which is the only
 * reason the home screen can be one decision wide.
 */
.cr-group {
	font-size: var(--t-cap);
	font-weight: 600;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--text-3);
	margin: 24px 0 8px;
	padding: 0 2px;
}

/*
 * The 2x2 quick-action grid (§8.12 point 2) — Uber's own Account shortcut
 * tiles. Safety is one of the brief's four named slots; there is no
 * screenSafety() in this codebase to link to (inventing one would be new
 * surface, not a restyle — §2), so Past rides fills the fourth slot instead.
 */
.cr-quickgrid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 8px;
	margin: 16px 0;
}
.cr-qa {
	display: flex;
	align-items: center;
	gap: 10px;
	height: 56px;
	padding: 0 14px;
	background: var(--sunken);
	border: 0;
	border-radius: var(--r-ctl);
	color: var(--text);
	font: 600 14px/1.2 var(--font-ui);
	letter-spacing: -.01em;
	text-align: left;
	cursor: pointer;
}
.cr-qa .cr-ico { flex: none; width: 20px; height: 20px; }

.cr-acct { list-style: none; margin: 0; padding: 0; }
.cr-acct > li { border-bottom: 1px solid var(--line); }
.cr-acct > li:last-child { border-bottom: 0; }

.cr-acct button,
.cr-acct .cr-static {
	display: flex;
	width: 100%;
	align-items: center;
	gap: 14px;
	background: none;
	border: 0;
	padding: 15px 2px;
	color: var(--text);
	font: 600 15px/1.35 var(--font-ui);
	letter-spacing: -.01em;
	text-align: left;
	cursor: pointer;
	min-height: 56px;
}
.cr-acct .cr-static { cursor: default; }
.cr-acct .cr-ico { width: 21px; height: 21px; flex: none; color: var(--text); stroke-width: 1.8; }
.cr-acct span.cr-acct-txt { flex: 1 1 auto; min-width: 0; }
.cr-acct small {
	display: block;
	color: var(--text-2);
	font-size: 12.5px;
	font-weight: 400;
	margin-top: 2px;
	line-height: 1.45;
}
/* §8.12 point 5 / §5.2: chev() is reserved for rows that navigate to a
   destination (a place, a route, a past ride) — none of .cr-acct's own
   settings rows qualify (Phase 5), so this is a base rule now, not scoped
   to .cr-acct, for the genuine destination rows it does appear on
   (.cr-suggest's picks/origin lists, Phase 9). */
.cr-chev { color: var(--text-3); flex: none; margin-left: auto; }
.cr-chev .cr-ico { width: 16px; height: 16px; color: var(--text-3); }
.cr-acct button:hover { color: var(--text); }
.cr-acct button:active:not(:disabled) { background: var(--sunken); }

/* Sign out reads as an ending, not an action to be tempted into. */
.cr-acct .cr-out { padding: 15px 2px; font: 600 15px/1.35 var(--font-ui); color: var(--text-2); }

/* --------------------------------------------------------- account head */

/* Name, university, karma. Lifted wholesale off the home screen, where it was
   the first thing on a screen that is supposed to ask one question. */
.cr-me {
	position: relative;
	padding-right: 68px;
	margin-bottom: 4px;
}
.cr-me .cr-mono { position: absolute; top: 0; right: 0; margin: 0; }
.cr-me .cr-me-txt { min-width: 0; }
.cr-me h2 {
	font-size: var(--t-display);
	font-weight: 700;
	letter-spacing: -.035em;
	margin: 0;
	line-height: 1.05;
	/* Wraps, deliberately — the mock draws a longer name two lines deep
	   rather than ellipsizing it, since the monogram beside it already
	   carries the truncated form (initials) for anywhere space is tight. */
	overflow: visible;
	text-overflow: clip;
	white-space: normal;
}
.cr-me small { display: block; color: var(--text-2); font-size: 13px; margin-top: 3px; }

/* "128 KARMA" plainly, on one line, rather than karmaBlock()'s usual boxed
   side-tile — that shape is for sitting beside something (a match card, a
   meet card); here it is restating a number already given its own line. */
.cr-me .cr-karma { text-align: left; flex: none; padding: 0; margin-top: 8px; }
.cr-me .cr-karma b,
.cr-me .cr-karma small { display: inline; font-size: var(--t-head); }
.cr-me .cr-karma small {
	margin: 0 0 0 6px;
	text-transform: uppercase;
	letter-spacing: .04em;
	font-weight: 600;
	color: var(--text-3);
}
/* karmaBlock()'s icon belongs beside the word in every other context this
   helper renders in; here the number already sits under a name and a
   56px monogram, and a third glyph in the same eyeline is one too many. */
.cr-me .cr-karma small .cr-ico { display: none; }

/*
 * The one monogram avatar (§11.3) — replaces this file's old .cr-mono AND
 * .cr-pcard-ava, which is why the size and colour are separate modifier
 * classes rather than baked into one rule: every call site picks a size,
 * and the colour is never chosen by the caller at all (monogram() in app.js
 * hashes it from the name). Ink is --pri-ink everywhere on purpose — never a
 * third value — which only works because the .cr-mono-c* ramp's lightness
 * inverts between palettes below to keep contrasting against it.
 */
.cr-mono {
	flex: none;
	margin: 0 auto;
	border-radius: 50%;
	display: grid;
	place-items: center;
	font-weight: 700;
	color: var(--pri-ink);
	letter-spacing: -.02em;
}
.cr-mono-32 { width: 32px; height: 32px; font-size: 13px; }
.cr-mono-44 { width: 44px; height: 44px; font-size: 16px; }
.cr-mono-52 { width: 52px; height: 52px; font-size: 19px; }
.cr-mono-56 { width: 56px; height: 56px; font-size: 21px; }

/* A neutral, desaturated ramp — hashed per name, never close enough to the
   four lamps (§4.5) to be misread as a status. Both palettes required: these
   are literal colours, not structural tokens, so test_assets() check 3
   demands the dark redefinition. */
.cr-mono-c0 { background: var(--mono-1); }
.cr-mono-c1 { background: var(--mono-2); }
.cr-mono-c2 { background: var(--mono-3); }
.cr-mono-c3 { background: var(--mono-4); }
.cr-mono-c4 { background: var(--mono-5); }

/* --------------------------------------------------------- signup dots */

.cr-dots { display: flex; gap: 5px; margin: 0 0 14px; }
.cr-dots i {
	width: 100%;
	height: 3px;
	border-radius: 2px;
	background: var(--line);
}
.cr-dots i.cr-on  { background: var(--go-solid); }
.cr-dots i.cr-now { background: var(--signal-solid); }

/* ---------------------------------------------------------- role picker */

/* §8.1's headline — a floor, not the h2 default every card headline uses. */
.cr-role-h {
	font-size: var(--t-display);
	font-weight: 700;
	letter-spacing: -.02em;
	line-height: 1.1;
	margin: 4px 0 8px;
}

/* Two tiles at ~50vh combined (§8.1), so each gets about a quarter of the
   screen — big enough that this reads as a decision, not a settings pair. */
.cr-pick { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; }

.cr-pick-one {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	gap: 6px;
	width: 100%;
	min-height: 25vh;
	padding: 20px 18px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-card);
	background: var(--sunken);
	color: var(--text);
	cursor: pointer;
	text-align: left;
	transition: transform var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.cr-pick-one .cr-illo { color: var(--text); margin: 0 0 6px; }
.cr-pick-one b { font-size: var(--t-head); font-weight: 700; letter-spacing: -.02em; }
.cr-pick-one small { color: var(--text-2); font-size: var(--t-sub); line-height: 1.45; }
/* Press state lifts to --sh-2 (§8.1) rather than just recolouring a border. */
.cr-pick-one:hover,
.cr-pick-one:active {
	border-color: var(--text);
	transform: translateY(-2px);
	box-shadow: var(--sh-2);
}

/* -------------------------------------------------------------- a11y ---- */

.cr button:focus-visible,
.cr a:focus-visible {
	outline: 2px solid var(--pri);
	outline-offset: 2px;
}

/*
 * The day pills and the When/role/price choice controls hide their actual
 * input (position: absolute; opacity: 0) so the label span can be styled as
 * a chip - which also hides the browser's own focus ring. Tabbing through
 * either control gave no visible indication of where the cursor was at
 * all. Same outline the buttons above already use, moved onto the sibling
 * span the same way the checked-state rules already do.
 */
.cr-day input:focus-visible + span,
.cr-choice input:focus-visible + span {
	outline: 2px solid var(--pri);
	outline-offset: 2px;
}

/* =========================================================================
 * MEETING UP
 *
 * Two visual jobs, and the second matters more than the first. One: draw two
 * people and a pin. Two: make CERTAIN look different from PROBABLY, at arm's
 * length, on a phone in daylight, without reading a word of it — because the
 * decision being made on this screen is whether to step off the kerb.
 *
 * That is what cr-fade and cr-stale are for. They are not decoration; they are
 * the difference between a fix from two seconds ago and one from a minute ago,
 * and they must never be tidied into looking the same.
 * ====================================================================== */

/* Taller than it was. This is the screen's subject, not an illustration beside
   it, and 260 px on a modern handset is a letterbox. Capped against the
   viewport so the read-out under it is never pushed off the bottom. */
.cr-meet-wrap { position: relative; margin: 14px 0 12px; }

/*
 * THE CAP ABOVE WAS REAL AND STILL DID NOT WORK, measured on a 320x568 screen:
 * 46vh is 261px there, which put "740 m away" at y=500 with the tab bar - a
 * fixed overlay - starting at y=482. The read-out was inside the viewport and
 * underneath the navigation, so the one number this whole card exists for was
 * invisible until you thought to scroll a screen that looks complete.
 *
 * 42vh clears it on every phone height from 568 up, and min-height comes down
 * to 200 because a 240px floor puts the same number back under the bar on
 * anything shorter than 572px. Unchanged from 810px up, where the 340 cap binds.
 */
.cr-meet-map {
	height: min(42vh, 340px);
	min-height: 200px;
}

/*
 * AND THEN 1.55.0 SPENT THE FIVE PIXELS.
 *
 * The destination line added to the header costs the card 19px, and the
 * measurement above had exactly 5px of clearance to give: on a 320x568 handset
 * the read-out landed at y=500 with the bar starting at 482, back behind the
 * navigation for the second time.
 *
 * Taken off the map, because the map is the only elastic thing on this card
 * and 204px is still a map, where a distance nobody can see is not a distance.
 * Only on the short phones where the vh cap actually binds - from 640px up the
 * read-out has tens of pixels of slack and the map keeps every one of its own.
 */
@media (max-height: 640px) {
	.cr-meet-map {
		height: min(36vh, 340px);
		min-height: 180px;
	}
}

/*
 * AND THE 340 CAP IS A 2019 PHONE'S ANSWER ON A 2026 PHONE.
 *
 * Both measurements above are about one thing: the read-out must not land under
 * the fixed tab bar. That is a real constraint and it stays untouched — the vh
 * fractions are exactly as they were, so every phone the comments measured
 * behaves exactly as it did.
 *
 * What they never revisited is the CAP. On a 844px handset 42vh is 354px and
 * the 340 cap takes 14px off a map for no reason: the read-out there has, in
 * the note's own words, tens of pixels of slack. So the cap is lifted only
 * where the vh fraction was already the binding number and the slack was
 * already measured — 780px and up, which is every phone sold in the last five
 * years and none of the ones the caps were written for.
 */
@media (min-height: 780px) {
	.cr-meet-map { height: min(52vh, 470px); }
}

/* Back to following the two of you. Only shown once the map has been dragged,
   because until then there is nothing to go back to. */
.cr-map-back {
	position: absolute;
	left: 50%;
	bottom: 12px;
	transform: translateX(-50%);
	z-index: 4;
	padding: 8px 15px;
	border-radius: 999px;
	border: 1px solid var(--line-strong);
	background: var(--bg);
	color: var(--text);
	font: 600 12.5px/1 var(--font-ui);
	box-shadow: 0 2px 10px rgba(0,0,0,.16);
	cursor: pointer;
}
.cr-map-back[hidden] { display: none; }

/* The whole journey, on request. Bottom-RIGHT, because Recentre owns the middle
   and the scale bar owns the left — and quieter than both, because it answers a
   question somebody has while waiting, not one they have in a road. */
.cr-map-wide {
	position: absolute;
	right: 10px;
	bottom: 10px;
	z-index: 4;
	padding: 6px 11px;
	border-radius: 999px;
	border: 1px solid var(--line);
	background: rgba(255,255,255,.92);
	backdrop-filter: blur(8px);
	-webkit-backdrop-filter: blur(8px);
	color: var(--text-2);
	font: 600 11.5px/1 var(--font-ui);
	box-shadow: 0 1px 6px rgba(0,0,0,.12);
	cursor: pointer;
}
.cr-map-wide[hidden] { display: none; }

.cr-map-marks { position: absolute; inset: 0; pointer-events: none; }

.cr-mloc {
	position: absolute;
	left: 0;
	top: 0;
	width: 0;
	height: 0;
	/* Linear, and matched to the paint interval. Anything eased reads as the
	   marker accelerating, which is a movement he did not make.

	   On transform rather than left/top: this is the one element on the screen
	   that animates continuously, so it is the one that must not cost a layout
	   per frame. */
	transition: transform .25s linear, opacity .3s ease;
	will-change: transform;
}
/* SMOOTH-RIDE-PLAN.md S6, and S1b found by tools/ride-lab: .25s was tuned for
   one update every few seconds - the ordinary meet-up cadence. The riding
   marker is repainted every 60ms by the dead-reckoning loop (app.js
   ridePoint() - riding gets its own faster throttle than the ordinary
   meet-up screen, for exactly this reason), and a longer transition is still
   retargeting the PREVIOUS frame's move when the next one lands, which
   leaves the glyph drawn behind the arithmetic for no benefit. Matched just
   over the frame interval instead: long enough to bridge one frame, short
   enough that the marker is where the numbers say it is. */
.cr-riding .cr-mloc-me { transition: transform .07s linear, opacity .3s ease; }
.cr-mloc i {
	position: absolute;
	left: -8px;
	top: -8px;
	width: 16px;
	height: 16px;
	border-radius: 50%;
	border: 2px solid var(--bg);
	box-shadow: 0 2px 8px rgba(0,0,0,.45);
}
.cr-mloc b {
	position: absolute;
	left: 12px;
	top: -9px;
	font-size: 11px;
	font-weight: 600;
	white-space: nowrap;
	padding: 2px 6px;
	border-radius: 6px;
	background: var(--bg);
	border: 1px solid var(--line);
	color: var(--text-2);
}

.cr-mloc-pin  i { background: var(--pri); }
.cr-mloc-me   i { background: var(--trust); }
.cr-mloc-them i { background: var(--go-solid); }

/* A suggested meeting point, which is not yet a meeting point. Hollow, in the
   colour of things waiting on somebody's answer: until it is accepted the solid
   black pin is still where both of them are going, and two solid pins on one
   map is two people walking to different corners. */
.cr-mloc-offer i {
	background: var(--bg);
	border: 3px solid var(--signal);
	box-shadow: none;
}

/* WHICH WAY YOU ARE FACING.
 *
 * The map is north-up; a person standing on a pavement is not. An arrow saying
 * "that way" is still a puzzle until you know which way you are pointed, and
 * turning on the spot to work it out is exactly the fumbling this screen exists
 * to remove. Drawn as a cone rather than a needle because a phone compass is
 * worth about fifteen degrees, and a needle claims one. */
.cr-mloc s {
	position: absolute;
	left: -22px;
	top: -22px;
	width: 44px;
	height: 44px;
	border-radius: 50%;
	/* A wedge from the centre, pointing up before rotation. */
	background: conic-gradient(from -28deg,
		rgba(37,99,235,.34) 0deg,
		rgba(37,99,235,.34) 56deg,
		transparent 56deg);
	transition: transform .2s ease-out;
	pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
	.cr-mloc s { transition: none; }
}

/* CLOSE ENOUGH TO LOOK UP.
 *
 * Inside sixty metres the number on the card has stopped being the useful
 * thing — the answer is on the pavement, not the screen. A ring spreading off
 * both dots is the one signal that reads from arm's length, on a phone held low
 * and glanced at rather than studied, and it says "stop reading this" better
 * than any sentence in the space available.
 *
 * On BOTH markers on purpose. One pulsing dot is a notification about somebody
 * else; two is the two of you converging, which is the thing actually happening.
 */
.cr-mloc-close::before {
	content: "";
	position: absolute;
	left: 50%;
	top: 50%;
	width: 18px;
	height: 18px;
	margin: -9px 0 0 -9px;
	border-radius: 50%;
	border: 2px solid currentColor;
	color: var(--trust);
	animation: cr-near 1.8s ease-out infinite;
	pointer-events: none;
}
.cr-mloc-them.cr-mloc-close::before { color: var(--go-solid); }

@keyframes cr-near {
	0%   { transform: scale( .6 ); opacity: .85; }
	100% { transform: scale( 3.4 ); opacity: 0; }
}

/* Motion is the whole mechanism here, so there is no reduced version of it —
   it is replaced with a static ring, which says the same thing quietly. */
@media (prefers-reduced-motion: reduce) {
	.cr-mloc-close::before { animation: none; opacity: .5; transform: scale( 2 ); }
}

/* THE BIKE.
 *
 * He is on a motorcycle and she is walking, and until now the map drew both as
 * the same coloured dot — throwing away the one fact about this screen that is
 * obvious in real life. A glyph costs nothing to read at arm's length and says
 * "that is the bike" faster than any label beside it.
 *
 * Only ever on HER phone. His own marker stays a dot, because a man does not
 * need a picture of what he is sitting on, and because the second the icon
 * appears on both screens it stops meaning "him" and starts meaning "a
 * vehicle", which is a different and less useful sentence.
 *
 * Drawn as a mask rather than an <img> so it takes its colour from the same
 * token as the dot it replaces, in both themes, with no second asset to ship. */
/*
 * AND THE GLYPH DOES NOT TURN. Two drafts turned it and both were wrong.
 *
 * Seen from above a motorbike is a line, and a line rotated to a heading reads
 * as an arrow — the second draft came out looking like a dagger, which is a
 * picture of nothing anybody is riding. Seen from the side it is unmistakably a
 * motorbike at 21px, and that is the whole job of an icon this size: to be
 * named correctly in the quarter of a second somebody looks at it.
 *
 * So the bike stays upright and the HEADING is carried by the facing cone that
 * this map has drawn since 1.31 — a part that already exists, is already
 * honest about being worth about fifteen degrees, and does not need the icon to
 * do its job badly in order to do its own. It is also what Pathao does, which
 * is the map every student here already knows how to read.
 */
.cr-mloc-glyph i {
	left: -17px;
	top: -17px;
	width: 34px;
	height: 34px;
	border-width: 2px;
	display: grid;
	place-items: center;
}
.cr-mloc-glyph i::before {
	content: '';
	width: 21px;
	height: 21px;
	background: var(--bg);
	-webkit-mask: var(--glyph) center / contain no-repeat;
	mask: var(--glyph) center / contain no-repeat;
}

/* Two wheels, a frame and a handlebar — the smallest drawing that is still
   plainly a motorbike and not a bicycle. Stroked rather than filled: at 21px a
   filled wheel is a blob, and the hole in the middle is most of what says
   "wheel". */
.cr-glyph-bike i {
	--glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='5.6' cy='16.8' r='3.6'/%3E%3Ccircle cx='18.4' cy='16.8' r='3.6'/%3E%3Cpath d='M18.4 16.8 15 9.6H9.9'/%3E%3Cpath d='M9.9 9.6 8.4 6.4h4.1'/%3E%3Cpath fill='black' stroke='black' stroke-width='1.4' stroke-linejoin='round' d='M7.4 12.5h6.9l1 2.6H6.2z'/%3E%3C/svg%3E");
}

/* THE ENGINE IS RUNNING.
 *
 * A ring breathing off the bike, slower and softer than the sixty-metre pulse
 * so the two never read as the same signal: this one says "he is moving and
 * this position is fresh", the other says "stop reading the screen". It stops
 * the moment the fix goes stale, which is the point — a marker that keeps
 * pulsing over a dead position is the screen lying quietly. */
.cr-mloc-glyph::after {
	content: '';
	position: absolute;
	left: 50%;
	top: 50%;
	width: 34px;
	height: 34px;
	margin: -17px 0 0 -17px;
	border-radius: 50%;
	background: currentColor;
	color: var(--go-solid);
	opacity: .28;
	animation: cr-bike-live 2.4s ease-out infinite;
	pointer-events: none;
	z-index: -1;
}
.cr-mloc-glyph.cr-fade::after,
.cr-mloc-glyph.cr-mloc-guess::after { animation: none; opacity: .12; }

/* Your own bike breathes in your own colour. The ring is a freshness signal and
   the colour is who it is about; swapping one and not the other would say a
   green thing was reporting when the green thing is no longer on the map. */
.cr-mloc-me.cr-mloc-glyph::after { color: var(--trust); }

@keyframes cr-bike-live {
	0%   { transform: scale( .75 ); opacity: .34; }
	70%  { transform: scale( 2.1 );  opacity: 0; }
	100% { transform: scale( 2.1 );  opacity: 0; }
}

/* The cone has to clear the puck it is now pointing out of — at 44px it sat
   entirely underneath a 34px marker and said nothing to anybody. */
.cr-mloc-glyph s {
	left: -31px;
	top: -31px;
	width: 62px;
	height: 62px;
}

/* And in his colour, not hers. The blue cone is the one on YOUR marker; a blue
   wedge coming off a green bike reads as a second person standing there. */
.cr-mloc-them s {
	background: conic-gradient(from -28deg,
		rgba(58,167,109,.42) 0deg,
		rgba(58,167,109,.42) 56deg,
		transparent 56deg);
}

@media (prefers-reduced-motion: reduce) {
	.cr-mloc-glyph::after { animation: none; opacity: .18; transform: scale( 1.6 ); }
}

/* A label that would land on top of one already placed goes under its dot
   instead. Two names printed over each other are worse than either. */
.cr-mloc b.cr-under { top: 9px; }

/* Stale. The ring stops being solid because the position stopped being known —
   a dashed edge and half opacity say "roughly here" without a sentence. */
.cr-mloc-them.cr-fade { opacity: .55; }
.cr-mloc-them.cr-fade i {
	background: transparent;
	border: 2px dashed var(--go);
	box-shadow: none;
}

.cr-read { min-height: 58px; }

/* §8.11: the "3 min away" line — the most-read text in the app. */
.cr-big {
	font-size: var(--t-title);
	font-weight: 700;
	letter-spacing: -.02em;
	margin: 0 0 4px;
}

/* The ETA, folded into the distance rather than given a sentence of its own.
   Same line, plainly secondary — one is measured, the other is a guess about
   traffic. */
.cr-big .cr-sub {
	font-size: var(--t-sub);
	font-weight: 600;
	color: var(--text-2);
	letter-spacing: 0;
}

/* Everything that qualifies the number above it. These were sentences, and the
   sentences were identical every time, so they stopped being read — which is
   the worst possible outcome for the three facts on this screen that say how
   much the distance may be trusted. */
/* The two contact buttons on the meeting screen are anchors, not buttons, so
   the phone opens the dialler and WhatsApp directly. They still have to sit in
   the row like everything else. */
/* Mounted at the top of Activity rather than opened as its own screen. It is
   the same card either way; this only says "you are already where this lives",
   which the missing Back link says too. */
.cr-meet-in { margin-bottom: 14px; }

.cr-meet a.cr-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	text-decoration: none;
}

/* THE WAYS OUT, SMALL.
 *
 * These were four full-width buttons stacked two by two — most of a phone
 * screen given to things used once a ride if at all, while the map and the
 * distance, which are looked at continuously, were pushed off the bottom.
 *
 * Still one tap each and still on THIS screen rather than one back, which was
 * the whole point of putting them here. What they stopped being is the biggest
 * thing on it. Five across at 360px is 64px each — comfortably past the 44px
 * a thumb needs, which is the floor that decides this row's size, not taste. */
.cr-ways {
	display: flex;
	gap: 6px;
	margin-top: 12px;
}
/* Square-ish sunken tiles (§8.11) — the same borderless-tile language as
   Account's quick-action grid (§8.12), rather than a hairline border that
   would blend into a --surface sheet background. */
.cr-way {
	flex: 1 1 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 4px;
	padding: 12px 2px 10px;
	border: 0;
	border-radius: var(--r-ctl);
	background: var(--sunken);
	color: var(--text-2);
	font: inherit;
	font-size: 11px;
	font-weight: 600;
	text-decoration: none;
	cursor: pointer;
	transition: background var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.cr-way span {
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cr-way .cr-ico { width: 19px; height: 19px; }
.cr-way:hover { color: var( --text ); }
.cr-way:active { background: var(--line); transform: scale(.96); }

/* WhatsApp keeps its colour, because it is the one people look for by colour
   rather than by reading the row. */
.cr-way-wa { color: var(--go); background: var(--go-tint); }
.cr-way-wa:hover { color: var(--go); background: var(--go-tint); }

/* Which kerb. The one line on this screen that stops somebody standing on the
   wrong side of a divider watching their lift go past, so it is given the
   weight of an instruction rather than of a note. */
.cr-side {
	display: flex;
	align-items: center;
	gap: 6px;
	margin-top: 6px;
	font-weight: 600;
	color: var(--text);
}
.cr-side .cr-ico { width: 1.05em; height: 1.05em; flex: none; color: var(--text-2); }

/* TYPING TO EACH OTHER. This replaced six fixed phrases, which were one tap and
   had nothing to moderate — a real thing to have given up. But six sentences
   cannot carry "behind the blue CNG by the pharmacy", and that is the sentence
   that ends the search rather than prefacing a phone call. */
.cr-chat { margin-top: 10px; }

/* Capped and scrolling. This card is already long and the conversation is the
   one part of it with no upper bound, so it is given a window rather than
   allowed to push the map and the buttons off the screen. */
.cr-chat-log {
	max-height: 168px;
	overflow-y: auto;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
	display: flex;
	flex-direction: column;
	gap: 5px;
	/* Room for the scrollbar to live in, on the right only.
	 *
	 * The old `padding: 2px 1px` left a right-aligned bubble ONE PIXEL from the
	 * scrollbar. That is invisible in a headless browser, which draws scrollbars
	 * as an overlay — and a 15 px trough with arrow buttons on Windows Chrome,
	 * where the arrows then sit on top of the bubble's corner. `stable` keeps
	 * the gutter reserved whether or not the bar is showing, so the thread does
	 * not reflow the moment it grows past the fourth message. */
	scrollbar-gutter: stable;
	scrollbar-width: thin;
	padding: 8px 10px;

	/* A boundary. Without one, a scrollbar appears floating in blank space
	   between two half-cut bubbles, and the whole card reads as broken layout
	   even when it is scrolling perfectly. */
	border: 1px solid var(--line);
	border-radius: var(--r-card);
	background: var(--bg);
}
.cr-chat-log:empty { display: none; }

/* A conversation taller than its window is fine; a message sliced across the
   top edge looks like a rendering fault. The fade says "there is more up
   there" in a way a hard cut cannot. */
.cr-chat-log { mask-image: linear-gradient( to bottom, transparent 0, #000 10px ); }
.cr-chat-log { -webkit-mask-image: linear-gradient( to bottom, transparent 0, #000 10px ); }

/* §8.11: 18px radius, 78% max-width. */
.cr-chat-log p {
	margin: 0;
	max-width: 78%;
	padding: 7px 11px;
	border-radius: 18px;
	font-size: 14px;
	line-height: 1.4;
	word-wrap: break-word;
	overflow-wrap: anywhere;
}
.cr-msg-them {
	align-self: flex-start;
	background: var(--sunken);
	border: 1px solid var(--line);
	border-bottom-left-radius: 5px;
	color: var(--text);
}
.cr-msg-me {
	align-self: flex-end;
	background: var(--pri);
	border-bottom-right-radius: 5px;
	color: var(--pri-ink);
}

/* Gone, not yet acknowledged. Faded rather than marked with a tick, because a
   tick claims delivery and nothing here can honestly claim it. */
.cr-msg-wait { opacity: .55; }

.cr-chat-say {
	display: flex;
	gap: 7px;
	margin-top: 8px;
}
.cr-chat-say input {
	flex: 1 1 auto;
	min-width: 0;
	min-height: 42px;
	padding: 9px 13px;
	border-radius: 999px;
	border: 1px solid var(--line);
	background: var(--sunken);
	color: var(--text);
	font: inherit;
	font-size: 15px;   /* never below 15px: iOS zooms the page on focus */
}
.cr-chat-say input:focus {
	outline: none;
	border-color: var(--pri);
	background: var(--surface);
}
.cr-chat-send {
	flex: none;
	width: 42px;
	height: 42px;
	display: grid;
	place-items: center;
	border: 0;
	border-radius: 50%;
	background: var(--pri);
	color: var(--pri-ink);
	cursor: pointer;
}
.cr-chat-send:hover { background: var(--pri-hover); }

.cr-read .cr-tags { margin-top: 8px; }
.cr-tag-warn {
	border-color: var(--signal-edge);
	color: var(--signal);
}

/* "At the pin", while you are. A pressed state rather than a second label to
   read: the button is the switch and its own indicator. */
.cr-meet .cr-btn.cr-on {
	background: var(--go-tint);
	border-color: var(--go-edge);
	color: var(--go);
}
.cr-big.cr-good-t { color: var(--go); }
.cr-big.cr-stale  { color: var(--text-3); }
p.cr-tight.cr-stale { color: var(--signal); }

/*
 * WHERE THE RIDE ENDS, in the header, without costing the read-out its place.
 *
 * The vertical budget on this card is spent to the last pixel: on a 320x568
 * handset the distance sits at y=477 with the fixed tab bar starting at 482,
 * so a second line here is five pixels away from hiding the one number the
 * screen exists for. Quieter and tighter than the line above it, and held to
 * one line - a wrap costs seventeen pixels there is no room for, and the
 * destination's first words are the part worth reading.
 */
.cr-meet-off {
	font-size: 11.5px;
	margin-top: 1px;
	color: var(--text-3);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/*
 * BLOCKAGEAUDITv1.119.0.md A2/P3(c). THE HONEST IOS LINE.
 *
 * Same semantic colour test_beacon()'s "could not measure" tag already
 * uses (.cr-tag-warn, var(--signal)) - a platform limit, not a mistake
 * anybody made, so it reads as a note to act on rather than an error.
 * Toggled by screenMeet()'s paint() in assets/app.js, hidden by default.
 */
.cr-meet-ios-note {
	margin: 10px 0 0;
	padding: 9px 11px;
	border-left: 3px solid var(--signal-edge);
	background: var(--signal-tint);
	border-radius: 0 8px 8px 0;
	font-size: 12.5px;
	line-height: 1.4;
	color: var(--text-2);
}

/*
 * BLOCKAGEAUDITv1.119.0.md D1/P2(5). Deliberately NOT the amber .cr-meet-ios-note
 * uses - this is not a limit or a warning, it is the app saying it is still
 * doing its job. --line-strong / --sunken, the same neutral pair a quiet
 * status note elsewhere on this card would use.
 */
.cr-meet-offline-note {
	margin: 10px 0 0;
	padding: 9px 11px;
	border-left: 3px solid var(--line-strong);
	background: var(--sunken);
	border-radius: 0 8px 8px 0;
	font-size: 12.5px;
	line-height: 1.4;
	color: var(--text-2);
}

/*
 * BLOCKAGEAUDITv1.119.0.md C5/C6/P4. Outside #cr-app on purpose — see
 * app.js's webviewBar() comment: every screen replaces root.innerHTML
 * wholesale, so this has to be a sibling of the app, not a child of it, or
 * it would vanish on the first navigation. Sits above everything except
 * the full-screen camera (z-index 70).
 */
#cr-webview-bar {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 60;
	display: flex;
	align-items: center;
	gap: 10px;
	padding: calc( 10px + env(safe-area-inset-top, 0px) ) 14px 10px;
	background: var(--signal-tint);
	border-bottom: 1px solid var(--signal-edge);
	color: var(--text-2);
	font-size: 12.5px;
	line-height: 1.4;
	backdrop-filter: blur(6px);
}
#cr-webview-bar span { flex: 1; }
#cr-webview-bar b { color: var(--signal); }
#cr-webview-bar button {
	flex: 0 0 auto;
	width: 24px;
	height: 24px;
	border: 0;
	border-radius: 50%;
	background: var(--sunken);
	color: var(--text-2);
	font-size: 16px;
	line-height: 1;
	cursor: pointer;
}

/*
 * BLOCKAGEAUDITv1.119.0.md C7/P5. Outside #cr-app for the same reason
 * #cr-webview-bar is - it can fire from goMeet(), immediately after
 * screenRides() has already replaced root.innerHTML, and has to survive
 * whatever screen comes after it too.
 */
#cr-oem-guide {
	position: fixed;
	inset: 0;
	z-index: 68;
	display: flex;
	align-items: flex-end;
	justify-content: center;
	padding: 16px;
	padding-bottom: calc( 16px + env(safe-area-inset-bottom, 0px) );
	background: rgba(0,0,0,.45);
}
.cr-oem-card {
	width: 100%;
	max-width: 420px;
	background: var(--surface);
	border-radius: var(--radius);
	padding: 20px;
	box-shadow: 0 -8px 30px rgba(0,0,0,.25);
}
.cr-oem-card h2 { margin: 0 0 8px; font-size: 17px; }
.cr-oem-card p { margin: 0 0 12px; font-size: 13.5px; line-height: 1.45; color: var(--text-2); }
.cr-oem-card ol {
	margin: 0 0 16px;
	padding-left: 20px;
	font-size: 13.5px;
	line-height: 1.5;
	color: var(--text);
}
.cr-oem-card ol li + li { margin-top: 8px; }
.cr-oem-card .cr-btn { width: 100%; }

/* Where the ride ends and how far that leaves somebody from where they were
   actually going. Quiet, because it is a fact about the ride rather than
   anything to act on — but the place name is bold, because on a card full of
   times and distances it is the only word. */
.cr p.cr-drop-to {
	margin: 10px 0 0;
	font-size: 12.5px;
	line-height: 1.5;
	color: var(--text-2);
}
.cr p.cr-drop-to b { color: var(--text); font-weight: 650; }

/*
 * Which of the two phones is doing the reporting, on a running ride. Quieter
 * than everything around it on purpose: it explains a battery, it is not a
 * thing anybody has to act on, and a pillion who never reads it loses nothing.
 */
p.cr-relay { color: var(--muted); font-size: 13px; margin: 6px 0 0; }

.cr-close-up {
	margin: 12px 0 4px;
	padding: 14px;
	border-radius: var(--r-card);
	background: var(--go-tint);
	border: 1px solid var(--go-edge);
	text-align: center;
}

/* The plate is the thing that actually identifies the bike, so below a hundred
   metres it gets the size the map had. */
.cr-plate-big {
	font-size: 30px;
	font-weight: 800;
	letter-spacing: .08em;
	margin: 6px 0 2px;
	font-variant-numeric: tabular-nums;
}

/* Held up across a road. Deliberately the whole screen — a coloured card in a
   crowd is a coloured card; a whole phone is a beacon. */
.cr-flash {
	position: fixed;
	inset: 0;
	z-index: 9999;
	animation: cr-pulse 1s steps(1) infinite;
}
@keyframes cr-pulse {
	50% { filter: brightness(.25); }
}

/* No drop-pin on this map. Everywhere else the centre pin means "wherever you
   drag to"; here nothing is being chosen, and a second pin-shaped thing beside
   two real markers reads as a third person. */
.cr-meet-map .cr-map-pin { display: none; }

/* Except while the map IS the question. Dragging to a better corner needs the
   same centre pin every other picker on this app uses, and needs the map to
   read as an instrument rather than a picture. */
.cr-meet-map.cr-moving .cr-map-pin { display: block; }
.cr-meet-map.cr-moving { border-color: var(--signal); box-shadow: inset 0 0 0 2px var(--signal-tint); }

/* Moving the meeting point. One block, three states, and never two at once —
   a suggestion to accept, a suggestion sent, or a map being dragged. */
.cr-move {
	margin-top: 10px;
	padding: 11px 12px;
	border-radius: var(--r-card);
	border: 1px solid var(--line);
	background: var(--sunken);
}
.cr-move p { margin: 0 0 9px; font-size: 14px; line-height: 1.45; }
.cr-move .cr-row { margin: 0; }

/* Theirs, unanswered. The only thing on this screen that requires an answer
   before the map means what it says, so it is the only one tinted. */
.cr-move-ask {
	border-color: var(--signal-edge);
	background: var(--signal-tint);
}
.cr-move-wait p { color: var(--text-2); }
.cr-move-wait .cr-link { padding: 12px 0; min-height: 44px; }

/* The pin is following her. Stated quietly rather than tinted — this is the
   normal condition of her screen, not something needing an answer, and a box
   that shouts on every ride stops being read by the second one. It still says
   it, because she did not choose it and the rider has her exact position. */
.cr-move-follow p,
.cr-move-held p { color: var(--text-2); display: flex; gap: 7px; align-items: flex-start; }
.cr-move-follow svg { flex: none; margin-top: 2px; opacity: .75; }
/* "I will wait right here" freezes the meeting point for both of them. It was a
   19px-tall strip, which is the smallest target on the screen attached to the
   largest consequence on it. */
.cr-move-follow .cr-link,
.cr-move-held .cr-link { padding: 12px 0; min-height: 44px; }

/* Held. She has stopped and said so, which is a real state of the ride and the
   one he can plan a turn into — so unlike following, it is worth a colour. */
.cr-move-held {
	border-color: var(--go-edge);
	background: var(--go-tint);
}

#cr-meet-move > .cr-link { margin-top: 6px; }

/* The pillion is usually standing ON the meeting point, so these two labels
   land on top of each other. Lift the pin's clear of the marker's. */
.cr-mloc-pin b { top: -26px; left: -18px; }

/* WHERE THE RIDE ENDS, drawn but never framed — so for most of the wait this
   sits off the edge. ISSUE 4.3: used to be hollow, on the reasoning that "it
   must not read as somewhere anybody is going now" — it IS where they are
   going, and a ring said the opposite of what was true. Filled, in --ride
   rather than --route, so it reads as the destination of the journey drawn
   in that colour rather than as a hollow variant of the approach line. Border
   and shadow left to the base .cr-mloc i rule, same as every other filled
   marker on this map — only the fill colour is this marker's own. */
.cr-mloc-far i {
	background: var(--ride);
}
.cr-mloc-far b {
	background: rgba(255,255,255,.9);
	color: var(--text-2);
	font-weight: 600;
}

/* The uncertainty band. Sized in metres by map.js and converted at the current
   zoom, so it always means the same distance on the ground.

   It is drawn on ::after rather than as a fourth marker so it can never be
   mistaken for a person, and it only appears once a reading has gone stale —
   a circle around a live fix would imply doubt that is not there. */
.cr-mloc-them::after,
.cr-mloc-me::after {
	content: '';
	position: absolute;
	left: 50%;
	top: 50%;
	width: calc(var(--haze, 0px) * 2);
	height: calc(var(--haze, 0px) * 2);
	margin: calc(var(--haze, 0px) * -1) 0 0 calc(var(--haze, 0px) * -1);
	border-radius: 50%;
	background: var(--go-tint);
	border: 1px solid var(--go-edge);
	transition: width .4s linear, height .4s linear, margin .4s linear;
	pointer-events: none;
}

/*
 * A GUESS HAS TO LOOK LIKE A GUESS.
 *
 * Past cr-dead there is no reading any more — only the last place they were
 * seen, and a circle of everywhere they could have reached since. A solid pin
 * in the middle of that says "he is here", which is precisely what it does not
 * mean, and it is the sentence that gets somebody to step off a kerb.
 *
 * So the centre goes hollow and the edge goes dashed. Nobody has to be taught
 * what a dashed circle means; they have to be taught what a confident dot in
 * the middle of a vague halo means, and they will learn it the wrong way.
 */
.cr-mloc-guess {
	background: transparent;
	border-style: dashed;
	box-shadow: none;
}

.cr-mloc-guess::after {
	border-style: dashed;
	background: transparent;
}

/* The same circle for your own fix, in your own colour. It appears only when
   the phone admits to more than about thirty metres of doubt — below that it
   would be smaller than the dot and would say nothing. */
.cr-mloc-me::after {
	background: rgba(37,99,235,.10);
	border-color: rgba(37,99,235,.34);
}

/* ---------------------------------------------------------------- notices */

/*
 * WHAT HAPPENED WHILE THEY WERE NOT LOOKING, at the top of Activity.
 *
 * Deliberately not styled as an alert. Most of what lands here is somebody
 * else's ordinary decision - a cancelled lift, a route withdrawn - and dressing
 * that in red teaches people to dread the screen. It reads as a note, and the
 * left border is the only thing that separates it from the cards below.
 */
.cr-notices {
	margin: 0 0 16px;
	padding: 2px 0 0;
}

.cr-notice {
	border-left: 3px solid var(--cr-accent, #2f6f4f);
	padding: 9px 0 9px 11px;
	margin-bottom: 7px;
	/* A live-device review found this strip reading as "too much text" —
	   mostly the un-deduplicated stack above, but the type was also a
	   size heavier than a note beside the cards it sits above needs. */
	font-size: 13px;
	line-height: 1.4;
}

.cr-notices .cr-link {
	font-size: 13px;
	padding: 2px 0;
}

/* ============================================================ the trip screen

   THE MEETING CARD AS A SCREEN, the shape every ride-hailing app converged on.

   The reference is Uber's own trip screen and the brief was to match it: a map
   filling the display, a sheet sitting over the bottom of it carrying the
   words, and the map's controls floating on the map as circles rather than
   sitting in a row underneath it.

   WHAT THIS IS NOT, and the distinction is the whole reason it is built this
   way. It is not a new screen. screenMeet() still requires a mount and still
   renders into Activity, because the full-screen MODE that used to exist here
   was deleted for a real reason - its "Back to my rides" link landed on
   Activity, which mounts this same card, so the screen after the tap was
   identical to the screen before it and there was no way out of the loop but
   the browser's own back button. Nothing below changes where this card lives
   in the document. It changes how it is painted, and the chevron collapses it
   back into the card it already was. There is no journey to get lost in
   because there is no journey.

   ---------------------------------------------------------------------------- */

/*
 * Chrome that floats ON the map, rather than in the page around it.
 *
 * These three rules were the only ones in this stylesheet that painted a
 * background from a literal instead of a token, which went unnoticed for as
 * long as there was only ever one palette. Under .cr-dark the zoom control
 * kept its hardcoded white ground and took its glyph colour from --text, which
 * is now near-white: a + and a - at about 1.04:1, which is not dim, it is
 * absent. The pair below flips with the palette like everything else does.
 */
.cr {
	--chrome:      rgba(255,255,255,.94);
	--chrome-ink:  #16181c;
	--chrome-edge: #c4c6c8;
}

.cr.cr-dark,
.cr-dark {
	--chrome:      rgba(24,28,33,.92);
	--chrome-ink:  #f5f6f7;
	--chrome-edge: rgba(255,255,255,.16);
}

.cr-map-zoom button,
.cr-map-credit,
.cr-map-none {
	background: var(--chrome);
	color: var(--chrome-ink);
	border-color: var(--chrome-edge);
}

/* -------------------------------------------------------------- full mode */

.cr-meet.cr-full {
	position: fixed;
	inset: 0;
	z-index: 60;
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: var(--sunken);
	display: block;
	overflow: hidden;
}

/* The tab bar stays visible over the map now, whether it is full-screen or
   not - Activity is one tap away either way, which is what makes
   #cr-meet-collapse safe to repurpose as a second sheet toggle rather than
   an exit. The scroll lock is still wanted: the page underneath must not
   move while a finger is on the grip. Staying on top needed .cr-tabs' own
   z-index raised past THIS rule's 60 (see the comment on .cr-tabs itself) -
   the sheet's 10 was never the thing burying it. The sheet itself is NOT
   shortened or lifted for the bar - it still reaches bottom: 0, the bar
   simply floats on top of it at the higher z-index. .cr-sheet-scroll's own
   bottom padding is what keeps scrolled content from sitting under the
   pill; see the comment there. */
body.cr-trip-open { overflow: hidden; }

.cr-meet.cr-full .cr-meet-wrap {
	position: absolute;
	inset: 0;
	margin: 0;
}

.cr-meet.cr-full .cr-meet-map {
	position: absolute;
	inset: 0;
	height: 100%;
	max-height: none;
	min-height: 0;
	border: 0;
	border-radius: 0;
}

/* The header is the sheet's job in this mode. Hidden rather than removed: the
   same nodes are still written to by heading(), and they reappear intact the
   moment the card collapses. */
.cr-meet.cr-full > .cr-match-top { display: none; }

/* --------------------------------------------------------- floating chrome */

.cr-mfab {
	position: absolute;
	z-index: 8;
	width: 46px;
	height: 46px;
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--chrome-edge);
	border-radius: 50%;
	background: var(--chrome);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	color: var(--chrome-ink);
	box-shadow: 0 3px 14px rgba(0,0,0,.4);
	cursor: pointer;
	padding: 0;
}
.cr-mfab svg { width: 21px; height: 21px; }
.cr-mfab-tl { left: 14px;  top: calc(14px + env(safe-area-inset-top)); }
.cr-mfab-br { right: 14px; }

/* #cr-meet-collapse is the sheet toggle now, reachable from the map's own
   corner - same job #cr-meet-toggle does from the sheet's own edge, same
   idiom as the sheet-only rules above it: nothing to raise or drop until
   the card has actually left Activity to become the full-screen map. */
.cr-meet:not(.cr-full) .cr-mfab-tl { display: none; }

.cr-msafety {
	position: absolute;
	z-index: 8;
	right: 14px;
	top: calc(14px + env(safe-area-inset-top));
	display: flex;
	align-items: center;
	gap: 7px;
	padding: 0 15px 0 12px;
	height: 46px;
	border: 1px solid var(--chrome-edge);
	border-radius: 999px;
	background: var(--chrome);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	color: var(--chrome-ink);
	font: 700 13.5px/1 var(--font-ui);
	box-shadow: 0 3px 14px rgba(0,0,0,.4);
	cursor: pointer;
	text-decoration: none;
}
.cr-msafety svg { width: 17px; height: 17px; color: var(--trust); }

/* The status lamp becomes one of the map's own chips in this mode - the same
   place, and the same shape, as the distance label on the reference. */
.cr-meet.cr-full #cr-meet-lamp {
	position: absolute;
	z-index: 8;
	left: 50%;
	transform: translateX(-50%);
	top: calc(20px + env(safe-area-inset-top));
	background: var(--chrome);
	border-color: var(--chrome-edge);
	color: var(--chrome-ink);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	box-shadow: 0 3px 14px rgba(0,0,0,.4);
	padding: 8px 13px;
}

/* Both map buttons already existed and both already knew when to show
   themselves. They only move. */
.cr-meet.cr-full .cr-map-back,
.cr-meet.cr-full .cr-map-wide {
	position: absolute;
	z-index: 8;
	left: 50%;
	transform: translateX(-50%);
	background: var(--chrome);
	border-color: var(--chrome-edge);
	color: var(--chrome-ink);
	box-shadow: 0 3px 14px rgba(0,0,0,.4);
	white-space: nowrap;
}

/* ---------------------------------------------------------------- the sheet */

.cr-sheet {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 10;
	display: flex;
	flex-direction: column;
	background: var(--surface);
	border-radius: 18px 18px 0 0;
	box-shadow: 0 -10px 44px rgba(0,0,0,.55);
	/* Height is set from script, because the map has to be told about it and a
	   number only CSS knows is a number the map cannot be told. The transition
	   is what turns that script-set number into a settle rather than a jump -
	   suspended by .cr-sheet-drag, below, for the length of an active drag, so
	   a dragging finger is followed 1:1 instead of chasing an easing target. */
	will-change: height;
	transition: height var(--dur-3) var(--ease-spring);
}
.cr-sheet.cr-sheet-drag { transition: none; }
.cr-meet:not(.cr-full) .cr-sheet {
	position: static;
	border-radius: 0;
	box-shadow: none;
	background: none;
	height: auto !important;
}

/* Big enough to find without looking, which is the whole job. */
.cr-sheet-grip {
	flex: none;
	padding: 11px 0 7px;
	display: flex;
	justify-content: center;
	cursor: grab;
	touch-action: none;
}
.cr-sheet-grip::before {
	content: '';
	width: 38px;
	height: 4px;
	border-radius: 2px;
	background: var(--line-strong);
}
.cr-meet:not(.cr-full) .cr-sheet-grip { display: none; }

/* Bordered, not just tinted --sunken on its own - --sunken (#f2f2f3)
   against --surface (#fff) is a contrast gap of about four values on a
   0-255 scale, which is legible in an editor's colour picker and close to
   invisible in a real photo of a real screen in real light. The kebab
   already solved exactly this problem for a control sitting on this same
   white sheet; this reuses that answer rather than re-discovering it by
   trial. #cr-meet-collapse, the map's own top-left corner button, is
   styled by .cr-mfab instead (it was already a floating map control before
   it took on this job) but shares this glyph and its rotation rule below. */
.cr-sheet-toggle {
	position: absolute;
	z-index: 11;
	top: 8px;
	right: 10px;
	width: 32px;
	height: 32px;
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--line);
	border-radius: 50%;
	background: var(--sunken);
	color: var(--text-2);
	cursor: pointer;
	padding: 0;
}
.cr-sheet-toggle svg {
	width: 18px;
	height: 18px;
	transition: transform var(--dur-2) var(--ease-out);
}
.cr-mfab-tl svg { transition: transform var(--dur-2) var(--ease-out); }
/* .cr-sheet-atfull is the SHEET's own state (peek/half vs full height) and
   is not the same flag as .cr-full (whether the map is fullscreen at all) -
   conflating the two was the bug both of these controls had at different
   times: rotate here on the former only, set by fitSheet() alongside every
   real change to `snap`. #cr-meet-collapse (.cr-mfab-tl) and
   #cr-meet-toggle (.cr-sheet-toggle) are the same promise from two
   positions, so they flip together off the one class. */
.cr-meet.cr-sheet-atfull .cr-sheet-toggle svg,
.cr-meet.cr-sheet-atfull .cr-mfab-tl svg { transform: rotate(180deg); }
.cr-meet:not(.cr-full) .cr-sheet-toggle { display: none; }

/*
 * BOTTOM PADDING RESERVES THE TAB BAR'S OWN FLOATING STRIP, not the sheet's
 * own edge - the sheet spans to bottom: 0 (unchanged, see .cr-sheet below)
 * and .cr-tabs floats on top of it at z-index 61 against this card's 60, so
 * the last thing scrolled to (We rode - done, Cancel/Report) has to clear
 * the pill itself, not just reach the sheet's own bottom. 16px is the
 * scroll area's own breathing room; 64px is the bar's height (52px tab +
 * 12px padding, same figure .cr-wrap.cr-has-tabs already uses); 10px is its
 * own margin-bottom inset; the env() is the safe area under both.
 */
.cr-sheet-scroll {
	flex: 1 1 auto;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding: 0 16px calc( 16px + 64px + 10px + env( safe-area-inset-bottom, 0px ) );
}
.cr-meet:not(.cr-full) .cr-sheet-scroll { overflow: visible; padding: 0; }

/* The one line that answers the only question being asked. It is the biggest
   thing on the sheet on the reference, and it is the biggest thing here. */
.cr-meet.cr-full .cr-read {
	text-align: center;
	font-size: 21px;
	font-weight: 700;
	letter-spacing: -.02em;
	color: var(--text);
	margin: 2px 0 14px;
	min-height: 26px;
}
.cr-meet.cr-full .cr-read .cr-big { font-size: 21px; margin: 0; }
.cr-meet.cr-full .cr-read b { font-weight: 700; }

/* ------------------------------------------------------------ sheet cards */

.cr-tcard {
	display: flex;
	align-items: flex-start;
	gap: 12px;
	background: var(--sunken);
	border: 1px solid var(--line);
	border-radius: 14px;
	padding: 15px 14px;
	margin-bottom: 11px;
}
.cr-tcard-main { flex: 1 1 auto; min-width: 0; }
.cr-tcard-lab {
	font-size: var(--t-cap);
	font-weight: 600;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--text-3);
	margin-bottom: 7px;
}
.cr-tcard-main .cr-meet-where {
	display: block;
	font-size: 16px;
	font-weight: 700;
	line-height: 1.35;
	letter-spacing: -.01em;
	color: var(--text);
}
.cr-tcard-main .cr-meet-off {
	display: block;
	font-size: 13px;
	color: var(--text-2);
	margin-top: 5px;
}
.cr-tcard-kebab {
	flex: none;
	width: 42px;
	height: 42px;
	border-radius: 11px;
	border: 1px solid var(--line);
	background: var(--surface);
	color: var(--text);
	font-size: 17px;
	line-height: 1;
	cursor: pointer;
}

.cr-pcard {
	display: flex;
	align-items: center;
	gap: 13px;
	background: var(--sunken);
	border: 1px solid var(--line);
	border-radius: 14px;
	padding: 14px;
	margin-bottom: 11px;
}
.cr-pcard-face { flex: none; text-align: center; width: 58px; }
.cr-pcard-karma {
	display: block;
	margin-top: 5px;
	font: 700 11px/1 var(--font-data);
	color: var(--text-2);
	font-variant-numeric: tabular-nums;
}
.cr-pcard-who { flex: 1 1 auto; min-width: 0; }
.cr-pcard-name {
	font-size: 15px;
	font-weight: 700;
	letter-spacing: -.01em;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cr-pcard-sub { font-size: 12.5px; color: var(--text-2); margin-top: 2px; }
.cr-pcard-rig { flex: none; text-align: right; max-width: 44%; }
/* §8.11: a chip, not bare text — a 1px --line-strong border around it. */
.cr-pcard-plate {
	display: inline-block;
	font: 700 16px/1.15 var(--font-data);
	letter-spacing: .04em;
	font-variant-numeric: tabular-nums;
	color: var(--text);
	word-break: break-word;
	padding: 4px 9px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-sm);
}
.cr-pcard-model { font-size: 12.5px; color: var(--text-2); margin-top: 3px; }

@media ( max-width: 360px ) {
	.cr-pcard-plate { font-size: 14px; }
	.cr-meet.cr-full .cr-read { font-size: 19px; }
}

@media ( prefers-reduced-motion: reduce ) {
	.cr-sheet { transition: none !important; }
	.cr-sheet-toggle svg { transition: none !important; }
	.cr-mfab-tl svg { transition: none !important; }
}

/* ==========================================================================
 * v1.130.2 - the later-today board (option 2a).
 *
 * design_handoff_later_today_panel/later-today.css, appended verbatim below
 * the existing board rules per its own header comment. Every colour, radius
 * and font here is a token already defined at the top of this file: this
 * block introduces no new palette. If a var() below does not resolve, the
 * token block above has moved - fix the reference, never hardcode the hex.
 *
 * THE SCREEN HAS TWO PARTS AND ONLY TWO. A body that may be trimmed, and a
 * foot that may not. The foot is out of flow of the body's own height, so the
 * one action on this screen ("I am leaving now" / "I am waiting here now") is
 * reachable on a 640px-tall phone in a browser with two toolbars showing,
 * without a scroll. A button somebody has to find is a button pressed late,
 * and late on this screen means a rider who has already gone.
 * ======================================================================= */

/*
 * INTEGRATION NOTE, not part of the design brief's own later-today.css
 * (which assumes a full-height container its height:100% below can resolve
 * against). .cr-wrap is plain in-flow block layout (max-width, centred) and
 * .cr itself is only min-height:70vh, sized by its own content - neither
 * establishes a percentage-resolvable height, so height:100% alone would
 * compute to auto and .cr-lt-foot would sit wherever the body's content
 * happened to end rather than pinned to the bottom of the viewport, which
 * is layout rule 1 in the design brief ("the button never needs a scroll").
 * position:fixed is .cr-meet.cr-full's own proven pattern for a screen that
 * needs the real viewport - but unlike that immersive map/camera screen,
 * this one is ordinary content, so it keeps .cr-tabs's own max-width:460px:
 * margin:0 auto trick (left/right both 0, centred by the auto margins)
 * rather than .cr-full's edge-to-edge width, so it reads as the same phone-
 * width column as every other screen on a wide desktop browser. z-index
 * kept low and explicit so this sits under #cr-webview-bar (60) and the
 * full-screen camera (70), same as the rest of the app.
 */
.cr-lt          { position:fixed; top:0; bottom:0; left:0; right:0; max-width:460px; margin:0 auto;
                  z-index:1; background:var(--surface);
                  display:flex; flex-direction:column; height:100%; min-height:0; }

/* body.cr-trip-open's own pattern (app.js's shell()/screenMeet()), applied
   here for the same reason: .cr-lt being position:fixed leaves .cr's own
   min-height:70vh sitting empty in the page underneath it, and without this
   the page can still be scrolled to reveal that blank space. app.js's
   shell() strips this unconditionally on every screen transition, the same
   way it already strips cr-dark. */
body.cr-lt-open { overflow: hidden; }
.cr-lt-body     { flex:1; min-height:0; overflow:auto; -webkit-overflow-scrolling:touch; }
.cr-lt-foot     { flex:0 0 auto; padding:14px 16px calc(16px + env(safe-area-inset-bottom));
                  background:var(--surface); border-top:1px solid var(--line);
                  box-shadow:0 -6px 16px rgba(0,0,0,.05); display:flex; flex-direction:column; gap:8px; }

/* ---------------------------------------------------------------- header */

.cr-lt-head     { display:flex; align-items:center; gap:12px; padding:16px 16px 10px; }
.cr-lt-back     { width:26px; height:26px; border:0; background:none; padding:0; color:var(--text);
                  font-size:22px; line-height:1; cursor:pointer; }
.cr-lt-head h2  { margin:0; font-size:19px; font-weight:700; line-height:1.2; letter-spacing:-.01em; color:var(--text); }
.cr-lt-head small { display:block; margin-top:3px; font-size:13px; line-height:1.2; color:var(--text-3); }

/* ------------------------------------------------------------- the rail */

.cr-lt-stages   { padding:0 16px; display:flex; flex-direction:column; }
.cr-lt-stage    { display:flex; gap:14px; }
.cr-lt-rail     { display:flex; flex-direction:column; align-items:center; width:22px; flex:0 0 22px; }
.cr-lt-rail i   { flex:1; width:2px; background:var(--line); }
.cr-lt-txt      { flex:1; min-width:0; padding-bottom:12px; }
.cr-lt-stage:last-child .cr-lt-txt { padding-bottom:0; }
.cr-lt-t        { font-size:15px; font-weight:600; line-height:1.3; color:var(--text); }
.cr-lt-s        { font-size:13px; line-height:1.4; color:var(--text-3); }

/* Done, running, not yet. Green is only ever "confirmed" and amber is only
   ever "live right now" - the same two lamps the rest of the app uses, so a
   glance at this rail reads the same way a glance at Activity does. */
.cr-lt-mark     { width:20px; height:20px; border-radius:50%; box-sizing:border-box; display:flex;
                  align-items:center; justify-content:center; position:relative; flex:0 0 auto; }
.cr-lt-done     { background:var(--go-solid); color:var(--go-ink); font-size:11px; font-weight:600; }
.cr-lt-todo     { border:2px solid var(--line); }
.cr-lt-live b   { width:12px; height:12px; border-radius:50%; background:var(--signal-solid); position:relative; }
.cr-lt-live::before { content:""; position:absolute; inset:0; border-radius:50%;
                  background:var(--signal-tint); animation:cr-lt-ping 2s ease-out infinite; }
.cr-lt-stage-live .cr-lt-rail i { background:linear-gradient(var(--signal-solid), var(--line)); }
.cr-lt-stage-todo .cr-lt-t,
.cr-lt-stage-todo .cr-lt-s      { color:var(--text-3); }

/* ------------------------------------------------------------ the sweep */

/* NOT A MAP. Ninety-two pixels of schematic: the road as one straight line,
   the people on it as buckets, and a pass moving along it. It is drawn from
   the numbers the poll already returns (a count, and metres along the route)
   and never from anybody's coordinates - putting a real position on this
   screen would make a second, longer-lived copy of the thing cr_meet is
   deliberately built to forget. If the sweep ever needs a tile behind it,
   that is CRMap's job, not this element's. */
.cr-lt-map      { position:relative; height:92px; border-radius:var(--r-map);
                  background:var(--sunken); border:1px solid var(--line); overflow:hidden; }
.cr-lt-grid     { position:absolute; inset:0;
                  background:repeating-linear-gradient(90deg, rgba(0,0,0,.035) 0 1px, transparent 1px 26px),
                             repeating-linear-gradient(0deg,  rgba(0,0,0,.035) 0 1px, transparent 1px 26px); }
.cr-lt-route    { position:absolute; left:30px; right:30px; top:52px; height:3px; border-radius:var(--r-pill); background:var(--text); }
.cr-lt-corridor { position:absolute; left:30px; right:30px; top:42px; height:26px; box-sizing:border-box;
                  border-radius:var(--r-pill); border:1px dashed var(--signal-edge); background:rgba(255,192,67,.12); }
.cr-lt-o        { position:absolute; left:24px; top:46px; width:16px; height:16px; box-sizing:border-box;
                  border-radius:50%; background:var(--text); border:3px solid var(--surface); }
.cr-lt-d        { position:absolute; right:24px; top:46px; width:16px; height:16px; box-sizing:border-box;
                  border-radius:50%; background:var(--surface); border:4px solid var(--text); }
.cr-lt-cap      { position:absolute; top:66px; font-family:var(--font-data); font-size:9px; font-weight:600;
                  letter-spacing:.08em; color:var(--text-2); }
.cr-lt-cap-o    { left:20px; }
.cr-lt-cap-d    { right:20px; }
.cr-lt-sweep    { position:absolute; top:0; bottom:0; width:48px; pointer-events:none;
                  background:linear-gradient(90deg, rgba(255,192,67,0), rgba(255,192,67,.5), rgba(255,192,67,0));
                  animation:cr-lt-scan 4.4s linear infinite; }

/* One person, bucketed. `left` is set inline from progress along the route;
   nothing else about them is drawn. */
.cr-lt-pin      { position:absolute; display:flex; flex-direction:column; align-items:center; gap:4px;
                  animation:cr-lt-dotin 4.4s ease-out infinite; }
.cr-lt-pin b    { width:10px; height:10px; border-radius:50%; background:var(--signal-solid);
                  box-shadow:0 0 0 4px rgba(255,192,67,.25); }
.cr-lt-pin span { font-family:var(--font-data); font-size:9px; font-weight:600; color:var(--signal); }
.cr-lt-pin-up   { flex-direction:column-reverse; }   /* label above the dot, when the dot sits low */

.cr-lt-chip     { position:absolute; left:8px; top:8px; display:flex; align-items:center; gap:6px;
                  padding:5px 9px; border-radius:var(--r-pill); background:var(--pri); color:var(--pri-ink);
                  font-family:var(--font-data); font-size:9px; font-weight:600; letter-spacing:.1em; }
.cr-lt-chip i   { width:5px; height:5px; border-radius:50%; background:var(--signal-solid);
                  animation:cr-lt-blink 1.6s ease-in-out infinite; }
.cr-lt-count    { position:absolute; right:8px; top:8px; display:flex; align-items:center; gap:5px;
                  padding:5px 9px; border-radius:var(--r-pill); background:var(--surface);
                  border:1px solid var(--line); font-size:10px; color:var(--text-2); }
.cr-lt-count b  { font-family:var(--font-data); font-size:11px; font-weight:600; color:var(--text); }

/* ----------------------------------------------------- progress + ticker */

.cr-lt-bar      { position:relative; height:6px; border-radius:var(--r-pill); background:var(--sunken); overflow:hidden; }
.cr-lt-bar b    { position:absolute; left:0; top:0; bottom:0; border-radius:var(--r-pill);
                  background:var(--signal-solid); animation:cr-lt-bar 5.5s ease-in-out infinite; }
.cr-lt-bar i    { position:absolute; top:0; bottom:0; width:34%;
                  background:linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.85), rgba(255,255,255,0));
                  animation:cr-lt-shim 2.2s linear infinite; }

/* The line that changes. Height is fixed at one line so a longer sentence
   arriving on a poll cannot shove the button down the screen. */
.cr-lt-tick     { height:19px; overflow:hidden; }
.cr-lt-tick div { font-family:var(--font-data); font-size:13px; line-height:19px; color:var(--signal); }

.cr-lt-live-col { flex:1; min-width:0; display:flex; flex-direction:column; gap:10px; padding-bottom:12px; }

/* -------------------------------------------------------- close-the-app */

.cr-lt-band     { margin:16px 16px 0; padding:14px; border-radius:var(--r-card); background:var(--sunken);
                  display:flex; gap:12px; align-items:flex-start; }
.cr-lt-band-ico { width:26px; height:26px; flex:0 0 auto; border-radius:50%; background:var(--surface);
                  border:1px solid var(--line); display:flex; align-items:center; justify-content:center; }
.cr-lt-band-t   { font-size:14px; font-weight:600; line-height:1.35; color:var(--text); }
.cr-lt-band-s   { margin-top:4px; font-size:13px; line-height:1.45; color:var(--text-2); text-wrap:pretty; }
.cr-lt-band-s .cr-link { font-weight:600; color:var(--trust); }

/* ------------------------------------------------------------- motion */

@keyframes cr-lt-ping  { 0% { transform:scale(.55); opacity:.5 } 100% { transform:scale(1.75); opacity:0 } }
@keyframes cr-lt-blink { 0%,100% { opacity:1 } 50% { opacity:.2 } }
@keyframes cr-lt-scan  { 0% { left:-14%; opacity:0 } 8%,92% { opacity:1 } 100% { left:106%; opacity:0 } }
@keyframes cr-lt-dotin { 0%,8% { transform:scale(0); opacity:0 } 24%,88% { transform:scale(1); opacity:1 }
                         100% { transform:scale(1); opacity:.35 } }
@keyframes cr-lt-bar   { 0% { width:6% } 70%,100% { width:88% } }
@keyframes cr-lt-shim  { 0% { transform:translateX(-110%) } 100% { transform:translateX(320%) } }

/* THE SWEEP IS A SIGN OF LIFE, NOT A REPORT. It says the app is still on the
   job; it never says a poll just returned. Somebody who has asked their phone
   to stop moving things gets the same screen with the movement removed, and
   nothing about it becomes less true. */
@media (prefers-reduced-motion: reduce) {
	.cr-lt-sweep,
	.cr-lt-pin,
	.cr-lt-chip i,
	.cr-lt-live::before,
	.cr-lt-bar b,
	.cr-lt-bar i { animation:none; }
	.cr-lt-sweep { display:none; }
	.cr-lt-bar b { width:60%; }
}

/* A poll that has stopped coming back must not leave a sweep running over a
   screen that knows nothing. Set by app.js on the same clock CR_MEET_STALE_S
   already uses for the meeting map. */
.cr-lt-stale .cr-lt-sweep      { display:none; }
.cr-lt-stale .cr-lt-map        { opacity:.55; }
.cr-lt-stale .cr-lt-chip       { background:var(--sunken); color:var(--text-2); }
.cr-lt-stale .cr-lt-chip i     { animation:none; background:var(--line-strong); }
