/* =============================================================================
   Airside Crew — app mockup tokens
   -----------------------------------------------------------------------------
   The phone and dashboard pictures on the site are reproduced from
   apps/site/design/App*.dc.html with their per-element inline styles intact,
   so they cannot drift from the approved designs. What those inline styles
   cannot carry is the theme: the design canvas substituted `{{c.bg}}` before
   rendering, and a static page has to resolve it in the browser instead.

   So every `{{c.x}}` in the designs became `var(--m-x)`, and the two theme
   objects from App*.dc.html's renderVals() are the two blocks below. An
   inline `style` attribute does resolve var(); an SVG presentation attribute
   such as `fill="…"` does not, so those became `style="fill: var(--m-…)"`.

   Day is the default; night follows the device unless the visitor chose day,
   and follows the switch whenever they chose. Same rule as site.css.
   ========================================================================== */

:root {
	--m-bg: #eef2f8;
	--m-surface: #ffffff;
	--m-ink: #02113a;
	--m-muted: #3d4b68;
	--m-line: #d3dae6;
	--m-brand: #0461f8;
	--m-on-brand: #ffffff;
	--m-chip: #dde5f3;

	--m-card-tint: #fff3d6;
	--m-card-ink: #1f1300;
	--m-card-muted: #6b5320;
	--m-card-line: #e8c77a;
	--m-card-border: #e59a00;

	--m-map-bg: #edeff2;
	--m-apron: #e2e5ea;
	--m-bldg: #d5d9e0;
	--m-twy: #ffffff;
	--m-twy-edge: #c9ced8;
	--m-center: #e2b93b;
	--m-rwy: #7b8291;
	--m-haul: #bca27a;
	--m-label: #5b6475;
	--m-other: #7a8599;
	--m-stopped: #2c3a5c;
	--m-plane: #c2410c;
	--m-ring: #0461f8;
	--m-compass: #d92d20;

	--m-ptt: #02113a;
	--m-ptt-ink: #ffffff;
	--m-btn-line: #8e9cb8;

	--m-nav-bg: rgba(255, 255, 255, 0.94);
	--m-nav-line: rgba(2, 17, 58, 0.1);
	--m-nav-shadow: 0 6px 24px rgba(2, 17, 58, 0.16);
	--m-nav-sel: #e3ecff;
	--m-nav-sel-ink: #0452d0;

	--m-badge: #e59a00;
	--m-badge-ink: #1f1300;

	--m-glass: rgba(255, 255, 255, 0.9);
	--m-glass-line: rgba(2, 17, 58, 0.12);
	--m-shadow: 0 2px 12px rgba(2, 17, 58, 0.18);
	--m-sel: #0461f8;
	--m-sel-ink: #ffffff;

	--m-talk-card: #0461f8;
	--m-talk-card-ink: #ffffff;

	/* Dashboard only. */
	--m-side: #02113a;
	--m-side-ink: #e6ecf8;
	--m-side-muted: #a7b6d3;
	--m-side-sel: rgba(4, 97, 248, 0.4);
	/* The red and amber below are duplicated in site.css as `--red-*` and
	   `--amber-*`, on purpose. site.css owns those copies and they track the
	   page chrome; these track App*.dc.html, and keeping them separate lets
	   either set change without dragging the other with it. Match the mockups
	   to the design files when you change these, not to site.css. */
	--m-amber-bg: #fff3d6;
	--m-amber-ink: #5c3b00;
	--m-amber-line: #e59a00;
	--m-red-bg: #fde7e5;
	--m-red-ink: #8a1a10;
	--m-red-line: #d9534a;
	--m-blue-bg: #e3ecff;
	--m-blue-ink: #0442b0;
}

/* The night values, written once and applied by both selectors below. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="day"]) {
		--m-bg: #030a1c;
		--m-surface: #0e1b38;
		--m-ink: #e6ecf8;
		--m-muted: #a7b6d3;
		--m-line: #22335e;
		--m-brand: #5b9bff;
		--m-on-brand: #02113a;
		--m-chip: #13244a;
		--m-card-tint: #241a06;
		--m-card-ink: #fcebc7;
		--m-card-muted: #d9be86;
		--m-card-line: #5a4416;
		--m-card-border: #e39b22;
		--m-map-bg: #0a1226;
		--m-apron: #101b33;
		--m-bldg: #18233d;
		--m-twy: #2a3858;
		--m-twy-edge: #1c2945;
		--m-center: #9c7f2a;
		--m-rwy: #3a4766;
		--m-haul: #6b5b3e;
		--m-label: #93a2c2;
		--m-other: #6f7fa3;
		--m-stopped: #b9c6e2;
		--m-plane: #ff8a4c;
		--m-ring: #5b9bff;
		--m-compass: #ff6b5e;
		--m-ptt: #1e5bd8;
		--m-ptt-ink: #ffffff;
		--m-btn-line: #3a5085;
		--m-nav-bg: rgba(14, 27, 56, 0.94);
		--m-nav-line: rgba(230, 236, 248, 0.14);
		--m-nav-shadow: 0 6px 24px rgba(0, 0, 0, 0.6);
		--m-nav-sel: #1b2e5c;
		--m-nav-sel-ink: #9cc2ff;
		--m-badge: #e39b22;
		--m-badge-ink: #1a0f00;
		--m-glass: rgba(14, 27, 56, 0.9);
		--m-glass-line: rgba(230, 236, 248, 0.16);
		--m-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
		--m-sel: #5b9bff;
		--m-sel-ink: #02113a;
		--m-talk-card: #1e5bd8;
		--m-talk-card-ink: #ffffff;
		--m-side: #020714;
		--m-side-sel: rgba(91, 155, 255, 0.28);
		--m-amber-bg: #2a1f08;
		--m-amber-ink: #f5c56b;
		--m-amber-line: #e39b22;
		--m-red-bg: #3a1210;
		--m-red-ink: #ffb3a8;
		--m-red-line: #e0614f;
		--m-blue-bg: #13244a;
		--m-blue-ink: #9cc2ff;
	}
}

:root[data-theme="night"] {
	--m-bg: #030a1c;
	--m-surface: #0e1b38;
	--m-ink: #e6ecf8;
	--m-muted: #a7b6d3;
	--m-line: #22335e;
	--m-brand: #5b9bff;
	--m-on-brand: #02113a;
	--m-chip: #13244a;
	--m-card-tint: #241a06;
	--m-card-ink: #fcebc7;
	--m-card-muted: #d9be86;
	--m-card-line: #5a4416;
	--m-card-border: #e39b22;
	--m-map-bg: #0a1226;
	--m-apron: #101b33;
	--m-bldg: #18233d;
	--m-twy: #2a3858;
	--m-twy-edge: #1c2945;
	--m-center: #9c7f2a;
	--m-rwy: #3a4766;
	--m-haul: #6b5b3e;
	--m-label: #93a2c2;
	--m-other: #6f7fa3;
	--m-stopped: #b9c6e2;
	--m-plane: #ff8a4c;
	--m-ring: #5b9bff;
	--m-compass: #ff6b5e;
	--m-ptt: #1e5bd8;
	--m-ptt-ink: #ffffff;
	--m-btn-line: #3a5085;
	--m-nav-bg: rgba(14, 27, 56, 0.94);
	--m-nav-line: rgba(230, 236, 248, 0.14);
	--m-nav-shadow: 0 6px 24px rgba(0, 0, 0, 0.6);
	--m-nav-sel: #1b2e5c;
	--m-nav-sel-ink: #9cc2ff;
	--m-badge: #e39b22;
	--m-badge-ink: #1a0f00;
	--m-glass: rgba(14, 27, 56, 0.9);
	--m-glass-line: rgba(230, 236, 248, 0.16);
	--m-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
	--m-sel: #5b9bff;
	--m-sel-ink: #02113a;
	--m-talk-card: #1e5bd8;
	--m-talk-card-ink: #ffffff;
	--m-side: #020714;
	--m-side-sel: rgba(91, 155, 255, 0.28);
	--m-amber-bg: #2a1f08;
	--m-amber-ink: #f5c56b;
	--m-amber-line: #e39b22;
	--m-red-bg: #3a1210;
	--m-red-ink: #ffb3a8;
	--m-red-line: #e0614f;
	--m-blue-bg: #13244a;
	--m-blue-ink: #9cc2ff;
}

/* The small Airside Crew mark inside a mockup, one file per theme. */
.m-mark--night,
:root[data-theme="night"] .m-mark--day {
	display: none;
}
:root[data-theme="night"] .m-mark--night {
	display: inline;
}
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="day"]) .m-mark--day {
		display: none;
	}
	:root:not([data-theme="day"]) .m-mark--night {
		display: inline;
	}
}

/* The approaching aircraft inside the depicted map. It is STILL, and the
   `.m-ac-moving` class on it in the pages is now only a hook with nothing
   attached to it.
   -----------------------------------------------------------------------------
   It used to carry `animation: m-taxi 8s linear infinite`, written before
   docs/ui-rules.md had a Motion section. That section now says a depicted app
   screen "plays once when it is scrolled into view, then settles. Not a
   loop", and this was a loop, so the two could not both stand. Removed rather
   than argued down, for three reasons that are not about taste:

     1. WCAG 2.2.2 Pause, Stop, Hide, at LEVEL A, fails it on its own terms:
        moving information that starts automatically, lasts more than five
        seconds and is presented in parallel with other content needs a way to
        pause, stop or hide it unless the movement is essential. It started
        automatically, it never stopped, it sat beside the copy that explains
        the screen, there was no control for it, and a still picture of a
        flagger's screen conveys the product completely — so the movement was
        not essential. (The reduced-motion query in site.css covered the
        visitors who had asked; this is about everyone else.)
     2. It made the picture claim more than the product can. The map it moved
        inside is a mockup, labelled "Design mockup" on the page, drawn from a
        design file — and a marker gliding in it for ever reads as a live feed
        of real positions. The HCI skill's rule is that visual confidence must
        match data confidence; the buyers who matter most are the ones who
        would check.
     3. The motion was spent where nobody was looking. Three of these sit on
        the Home page and six on Screens, all of them below the fold at both
        widths, all looping from the moment the page loaded. The visitor
        scrolls down into a cycle already in progress.

   Also removed: the `@keyframes m-taxi` block. The next task animates these
   screens deliberately, once, on arrival, and those keyframes were built for
   a loop — they fade the aircraft back out at 100% so the cycle can restart,
   which is the opposite of settling. Keeping them would have invited reuse of
   the wrong shape. The hook to start from is the `.reveal--shown` class
   scripts/site.js puts on an arriving section (site.css section 24).

   src/html/motion.test.ts now fails any animation in either stylesheet that
   repeats for ever, asserted over the declaration rather than over this
   file's history, because a test naming m-taxi would not catch the next one. */

/* =============================================================================
   Dashboard mockup: one markup, two shapes
   -----------------------------------------------------------------------------
   AppDashboard.dc.html takes a `mobile` prop and renders a different tree for
   each. Here there is one tree and a container query, because the two shapes
   are picked by the frame's own width (1280 px in the wide frame, 390 px in
   the phone frame), not by the browser window.
   ========================================================================== */
.m-dash {
	position: relative;
	width: 100%;
	display: flex;
	background: var(--m-bg);
	color: var(--m-ink);
	font-family: "Atkinson Hyperlegible Next", "Segoe UI", system-ui, sans-serif;

	/* Wide shape (inside the 1280 px frame). */
	min-height: 900px;
	flex-direction: row;
}
.m-dash__main {
	flex: 1 1 auto;
	min-width: 0;
	min-height: 0;
	overflow: hidden;
	padding: 28px 32px;
	display: flex;
	flex-direction: column;
	gap: 18px;
}
.m-dash__h1 {
	margin: 0;
	font-size: 32px;
	font-weight: 800;
}
.m-dash__tiles {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 12px;
}
.m-dash__row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) minmax(0, 1.6fr) minmax(0, 1.2fr);
	gap: 6px 14px;
	align-items: center;
	padding: 12px 16px;
	border-bottom: 1px solid var(--m-line);
}
.m-dash__head {
	padding: 10px 16px;
	border-bottom: 1px solid var(--m-line);
	font-size: 12px;
	font-weight: 800;
	color: var(--m-muted);
	letter-spacing: 0.05em;
	text-transform: uppercase;
}
.m-dash__mobile-only {
	display: none;
}
/* These two are classes rather than inline styles precisely so the container
   query below can switch them off: an inline `style` attribute beats any
   stylesheet rule, and both of these have to disappear in the other shape. */
.m-dash__side {
	display: flex;
}
.m-dash__nav {
	display: none;
	position: absolute;
	left: 12px;
	right: 12px;
	bottom: 12px;
	z-index: 2;
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: 2px;
	padding: 5px;
	border-radius: 24px;
	border: 1px solid var(--m-nav-line);
	background: var(--m-nav-bg);
	box-shadow: var(--m-nav-shadow);
}

/* Phone shape (the 390 px frame). */
@container (max-width: 700px) {
	.m-dash {
		min-height: 844px;
		height: 844px;
		overflow: hidden;
		flex-direction: column;
	}
	.m-dash__side {
		display: none;
	}
	.m-dash__mobile-only {
		display: flex;
	}
	.m-dash__main {
		padding: 18px 16px 96px 16px;
	}
	.m-dash__h1 {
		font-size: 28px;
	}
	.m-dash__tiles {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
	.m-dash__row,
	.m-dash__head {
		grid-template-columns: minmax(0, 1fr);
	}
	.m-dash__head {
		display: none;
	}
	.m-dash__nav {
		display: grid;
	}
}

/* =============================================================================
   The Home hero's phone — apps/site/design/NewFlagger.dc.html
   =============================================================================
   The older App*.dc.html mockups above keep the designs' per-element inline
   styles. This one does NOT: Dylan's instruction for the Oct 27 rebuild was no
   inline script tags "or stuff like that", and apps/site/README.md records the
   conversion. Every `{{c.x}}` in NewFlagger.dc.html became `var(--n-x)`, and
   the two theme objects from its `_rv()` are the two blocks below.

   `--n-*` and not `--m-*`: the two palettes are NOT the same. NewFlagger's
   night surface is #0E1B38 where App*'s is #0B1633, its night brand is #5B9BFF
   where App*'s is #0461F8, and its line, plane and stopped colours all differ
   too. The Screens page still draws the App* mockups, so one set may not be
   edited into the other until that page converts.

   SVG presentation attributes (`fill="…"`, `stroke="…"`) do not resolve
   `var()`, so every themed fill and stroke is a class here instead: `nf-f-*`
   sets a fill, `nf-s-*` sets a stroke.

   WHAT IS DELIBERATELY NOT REPRODUCED: the design's `.hb-dot` rule, which
   pulses the "Live" status dot for ever. docs/ui-rules.md allows no looping
   motion on this site, Dylan's approval for this rebuild names four loops and
   that is not one of them, and src/html/motion.test.ts fails on a fifth. The
   dot is drawn, and it is still.
   ========================================================================== */

.nf {
	--n-bg: #eef2f8;
	--n-surface: #ffffff;
	--n-ink: #02113a;
	--n-muted: #3d4b68;
	--n-line: #d3dae6;
	--n-brand: #0461f8;
	--n-on-brand: #ffffff;
	--n-chip: #dde5f3;

	--n-alert-ink: #1f1300;
	--n-card-tint: #fff3d6;
	--n-card-ink: #1f1300;
	--n-card-muted: #6b5320;
	--n-card-border: #e59a00;

	--n-map-bg: #edeff2;
	--n-apron: #e2e5ea;
	--n-bldg: #d5d9e0;
	--n-twy: #ffffff;
	--n-twy-edge: #c9ced8;
	--n-center: #e2b93b;
	--n-rwy: #7b8291;
	--n-haul: #bca27a;
	--n-label: #5b6475;
	--n-other: #7a8599;
	--n-stopped: #2c3a5c;
	--n-plane: #c2410c;
	--n-compass: #d92d20;

	--n-ptt: #02113a;
	--n-ptt-ink: #ffffff;
	--n-btn-line: #8e9cb8;

	--n-nav-bg: rgba(255, 255, 255, 0.94);
	--n-nav-line: rgba(2, 17, 58, 0.1);
	--n-nav-sel: #e3ecff;
	--n-nav-sel-ink: #0452d0;

	--n-glass: rgba(255, 255, 255, 0.9);
	--n-glass-line: rgba(2, 17, 58, 0.12);
	--n-shadow: 0 2px 12px rgba(2, 17, 58, 0.18);
	--n-lift: 0 1px 2px rgba(2, 17, 58, 0.06), 0 6px 18px rgba(2, 17, 58, 0.09);
	--n-nav-float: 0 14px 34px rgba(2, 17, 58, 0.22), 0 3px 8px rgba(2, 17, 58, 0.1),
		inset 0 1px 0 rgba(255, 255, 255, 0.8);
	--n-sel-lift: 0 2px 6px rgba(2, 17, 58, 0.12);
}

/* Night, by the device unless the visitor explicitly chose day. Same rule as
   site.css and the App* mockups above. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="day"]) .nf {
		--n-bg: #030a1c;
		--n-surface: #0e1b38;
		--n-ink: #e6ecf8;
		--n-muted: #a7b6d3;
		--n-line: #22335e;
		--n-brand: #5b9bff;
		--n-on-brand: #02113a;
		--n-chip: #13244a;
		--n-alert-ink: #1a0f00;
		--n-card-tint: #241a06;
		--n-card-ink: #fcebc7;
		--n-card-muted: #d9be86;
		--n-card-border: #e39b22;
		--n-map-bg: #0a1226;
		--n-apron: #101b33;
		--n-bldg: #18233d;
		--n-twy: #2a3858;
		--n-twy-edge: #1c2945;
		--n-center: #9c7f2a;
		--n-rwy: #3a4766;
		--n-haul: #6b5b3e;
		--n-label: #93a2c2;
		--n-other: #6f7fa3;
		--n-stopped: #b9c6e2;
		--n-plane: #ff8a4c;
		--n-compass: #ff6b5e;
		--n-ptt: #1e5bd8;
		--n-btn-line: #3a5085;
		--n-nav-bg: rgba(14, 27, 56, 0.94);
		--n-nav-line: rgba(230, 236, 248, 0.14);
		--n-nav-sel: #1b2e5c;
		--n-nav-sel-ink: #9cc2ff;
		--n-glass: rgba(14, 27, 56, 0.9);
		--n-glass-line: rgba(230, 236, 248, 0.16);
		--n-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
		--n-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 22px rgba(0, 0, 0, 0.45);
		--n-nav-float: 0 16px 38px rgba(0, 0, 0, 0.65), 0 3px 8px rgba(0, 0, 0, 0.4),
			inset 0 1px 0 rgba(255, 255, 255, 0.08);
		--n-sel-lift: 0 2px 6px rgba(0, 0, 0, 0.4);
	}
}

/* An explicit choice wins over the device, in both directions. */
:root[data-theme="night"] .nf {
	--n-bg: #030a1c;
	--n-surface: #0e1b38;
	--n-ink: #e6ecf8;
	--n-muted: #a7b6d3;
	--n-line: #22335e;
	--n-brand: #5b9bff;
	--n-on-brand: #02113a;
	--n-chip: #13244a;
	--n-alert-ink: #1a0f00;
	--n-card-tint: #241a06;
	--n-card-ink: #fcebc7;
	--n-card-muted: #d9be86;
	--n-card-border: #e39b22;
	--n-map-bg: #0a1226;
	--n-apron: #101b33;
	--n-bldg: #18233d;
	--n-twy: #2a3858;
	--n-twy-edge: #1c2945;
	--n-center: #9c7f2a;
	--n-rwy: #3a4766;
	--n-haul: #6b5b3e;
	--n-label: #93a2c2;
	--n-other: #6f7fa3;
	--n-stopped: #b9c6e2;
	--n-plane: #ff8a4c;
	--n-compass: #ff6b5e;
	--n-ptt: #1e5bd8;
	--n-btn-line: #3a5085;
	--n-nav-bg: rgba(14, 27, 56, 0.94);
	--n-nav-line: rgba(230, 236, 248, 0.14);
	--n-nav-sel: #1b2e5c;
	--n-nav-sel-ink: #9cc2ff;
	--n-glass: rgba(14, 27, 56, 0.9);
	--n-glass-line: rgba(230, 236, 248, 0.16);
	--n-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
	--n-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 22px rgba(0, 0, 0, 0.45);
	--n-nav-float: 0 16px 38px rgba(0, 0, 0, 0.65), 0 3px 8px rgba(0, 0, 0, 0.4),
		inset 0 1px 0 rgba(255, 255, 255, 0.08);
	--n-sel-lift: 0 2px 6px rgba(0, 0, 0, 0.4);
}

/* ---------------------------------------------------------------------------
   Frame
   ------------------------------------------------------------------------ */
.nf {
	position: relative;
	width: 390px;
	height: 844px;
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	background: var(--n-bg);
	color: var(--n-ink);
	font-family: "Atkinson Hyperlegible Next", "Segoe UI", system-ui, sans-serif;
	overflow: hidden;
}

/* ---------------------------------------------------------------------------
   Header
   ------------------------------------------------------------------------ */
.nf__head {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 14px 16px 10px 16px;
}

.nf__mark {
	width: 40px;
	height: 40px;
	object-fit: contain;
	flex-shrink: 0;
}
/* The app mark has a light-background and a dark-background file, and the
   depicted phone follows the site's theme so the picture matches the page it
   sits on. */
.nf__mark--night {
	display: none;
}
:root[data-theme="night"] .nf__mark--day {
	display: none;
}
:root[data-theme="night"] .nf__mark--night {
	display: block;
}
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="day"]) .nf__mark--day {
		display: none;
	}
	:root:not([data-theme="day"]) .nf__mark--night {
		display: block;
	}
}

.nf__post {
	display: flex;
	flex-direction: column;
	min-width: 0;
	flex-grow: 1;
}
.nf__post-label {
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.nf__post-name {
	font-size: 28px;
	font-weight: 800;
	line-height: 1.1;
}

.nf__avatar {
	flex-shrink: 0;
	width: 44px;
	height: 44px;
	border-radius: 22px;
	background: var(--n-chip);
	color: var(--n-ink);
	font-size: 16px;
	font-weight: 800;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* ---------------------------------------------------------------------------
   Status row: Live · 3 on post · Mic off
   ------------------------------------------------------------------------ */
.nf__status {
	margin: 0 16px 12px 16px;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	border-radius: 16px;
	background: var(--n-surface);
	border: 1px solid var(--n-line);
	box-shadow: var(--n-lift);
}

.nf__status-cell {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 10px 4px;
	font-size: 14px;
	font-weight: 700;
}
.nf__status-cell--mid {
	border-left: 1px solid var(--n-line);
	border-right: 1px solid var(--n-line);
}
.nf__status-cell--mic {
	border-radius: 0 13px 13px 0;
	color: var(--n-ink);
}

/* Drawn, and still. See the note at the top of this section. */
.nf__live-dot {
	fill: var(--n-brand);
}

/* ---------------------------------------------------------------------------
   The alert row. docs/alerts.md "C2a": a compact list row, soft amber, two
   text sizes, a chevron that would open details. Amber here means aircraft
   and nothing else.
   ------------------------------------------------------------------------ */
.nf__alert {
	margin: 0 16px;
	padding: 10px 14px;
	border-radius: 16px;
	background: var(--n-card-tint);
	color: var(--n-card-ink);
	border: 1.5px solid var(--n-card-border);
	display: flex;
	align-items: center;
	gap: 12px;
}

.nf__alert-icon {
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	border-radius: 18px;
	background: var(--n-card-border);
	color: var(--n-alert-ink);
	display: flex;
	align-items: center;
	justify-content: center;
}

.nf__alert-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}
.nf__alert-head {
	font-size: 17px;
	font-weight: 700;
	line-height: 1.3;
}
.nf__alert-sub {
	font-size: 14px;
	font-weight: 500;
	line-height: 1.35;
	color: var(--n-card-muted);
	font-variant-numeric: tabular-nums;
}
.nf__secs {
	display: inline-block;
	font-variant-numeric: tabular-nums;
}

.nf__alert-chevron {
	flex-shrink: 0;
	opacity: 0.6;
}

/* ---------------------------------------------------------------------------
   The glance map
   ------------------------------------------------------------------------ */
.nf__map {
	position: relative;
	flex-grow: 1;
	margin: 12px 16px 0 16px;
	border-radius: 20px;
	overflow: hidden;
	border: 1px solid var(--n-line);
	box-shadow: var(--n-lift);
	min-height: 0;
}

.nf__map-svg {
	width: 100%;
	height: 100%;
}

/* Themed fills and strokes. SVG presentation attributes cannot resolve
   var(), so each one is a class. */
.nf-f-map { fill: var(--n-map-bg); }
.nf-f-apron { fill: var(--n-apron); }
.nf-f-bldg { fill: var(--n-bldg); }
.nf-f-rwy { fill: var(--n-rwy); }
.nf-f-twy { fill: var(--n-twy); }
.nf-f-stopped { fill: var(--n-stopped); }
.nf-f-plane { fill: var(--n-plane); }
.nf-f-other { fill: var(--n-other); }
.nf-f-brand { fill: var(--n-brand); }
.nf-f-on-brand { fill: var(--n-on-brand); }
.nf-f-border { fill: var(--n-card-border); }
.nf-f-compass { fill: var(--n-compass); }
.nf-s-twy { stroke: var(--n-twy); }
.nf-s-twy-edge { stroke: var(--n-twy-edge); }
.nf-s-center { stroke: var(--n-center); }
.nf-s-haul { stroke: var(--n-haul); }
.nf-s-stopped { stroke: var(--n-stopped); }
.nf-s-brand { stroke: var(--n-brand); }
.nf-s-map { stroke: var(--n-map-bg); }

/* The trail behind the moving aircraft, and the aircraft itself, at the frame
   the design opens on: its tick counter at 0, which is the "about 50 s"
   reading the row above shows and the pill beside the phone quotes. */
.nf__path {
	transform-origin: 150px 200px;
	transform: scaleY(1.364);
}
.nf__move {
	transform: translateY(-16px);
}

.nf__map-letter {
	font-size: 15px;
	font-weight: 800;
}

.nf__chip {
	position: absolute;
	top: 8px;
	display: flex;
	align-items: center;
	padding: 6px 10px;
	border-radius: 14px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	-webkit-backdrop-filter: blur(12px);
	backdrop-filter: blur(12px);
	color: var(--n-ink);
}
.nf__chip--north {
	left: 8px;
	gap: 4px;
	font-size: 13px;
	font-weight: 800;
}
.nf__chip--age {
	right: 8px;
	gap: 5px;
	font-size: 12px;
	font-weight: 700;
}

.nf__big-map {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: flex-end;
	justify-content: flex-end;
	padding: 8px;
}
.nf__big-map-pill {
	display: flex;
	align-items: center;
	gap: 4px;
	height: 44px;
	padding: 0 16px;
	border-radius: 22px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	color: var(--n-ink);
	font-size: 15px;
	font-weight: 800;
}

.nf__map-credit {
	position: absolute;
	left: 10px;
	bottom: 6px;
	font-size: 10px;
	color: var(--n-label);
}

/* ---------------------------------------------------------------------------
   The verify line and the two field controls
   ------------------------------------------------------------------------ */
.nf__foot {
	padding: 10px 16px 0 16px;
	display: flex;
	flex-direction: column;
	gap: 10px;
}

/* root CLAUDE.md rule 1. It belongs to the DEPICTED app screen and is the
   only place this sentence appears on the website — the site never says it in
   its own voice. */
.nf__verify {
	text-align: center;
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}

.nf__actions {
	display: flex;
	gap: 10px;
}

.nf__repeat {
	width: 76px;
	height: 72px;
	flex-shrink: 0;
	border-radius: 20px;
	border: 1.5px solid var(--n-btn-line);
	background: var(--n-surface);
	color: var(--n-ink);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 3px;
	font-size: 13px;
	font-weight: 700;
	box-shadow: var(--n-lift);
	box-sizing: border-box;
}

.nf__ptt {
	flex: 1;
	min-width: 0;
	height: 72px;
	border-radius: 20px;
	background: var(--n-ptt);
	color: var(--n-ptt-ink);
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	font-size: 21px;
	font-weight: 800;
	box-shadow: 0 0 0 0 transparent, var(--n-lift);
}
.nf__ptt > svg {
	flex-shrink: 0;
}
.nf__ptt-labels {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	line-height: 1.15;
	min-width: 0;
}
.nf__ptt-main {
	white-space: nowrap;
}
.nf__ptt-sub {
	font-size: 13px;
	font-weight: 600;
	opacity: 0.9;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 100%;
}

/* ---------------------------------------------------------------------------
   The floating tab bar, and the space kept clear beneath the controls for it
   ------------------------------------------------------------------------ */
.nf__nav-gap {
	height: 98px;
	flex-shrink: 0;
}

.nf__nav {
	position: absolute;
	left: 20px;
	right: 20px;
	bottom: 14px;
	z-index: 5;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 4px;
	padding: 6px;
	border-radius: 30px;
	border: 1px solid var(--n-nav-line);
	background: var(--n-nav-bg);
	box-shadow: var(--n-nav-float);
	-webkit-backdrop-filter: blur(20px) saturate(1.6);
	backdrop-filter: blur(20px) saturate(1.6);
}

.nf__tab {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 2px;
	padding: 6px 0;
	border-radius: 19px;
	color: var(--n-muted);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.02em;
}
.nf__tab--on {
	border-radius: 24px;
	box-shadow: var(--n-sel-lift);
	background: var(--n-nav-sel);
	color: var(--n-nav-sel-ink);
	font-weight: 800;
}

/* ---------------------------------------------------------------------------
   The screen arriving. One shot, every one of them — the phone is on screen
   when the page loads, so "plays once when it is scrolled into view"
   (docs/ui-rules.md) means on load here. Section 25 of site.css cancels all
   of it under prefers-reduced-motion: reduce.
   ------------------------------------------------------------------------ */
.runA > * {
	animation: fmUpA 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}
.runA > *:nth-child(2) { animation-delay: 0.06s; }
.runA > *:nth-child(3) { animation-delay: 0.12s; }
.runA > *:nth-child(4) { animation-delay: 0.18s; }
.runA > *:nth-child(5) { animation-delay: 0.24s; }
.runA > *:nth-child(6) { animation-delay: 0.3s; }
.runA > *:nth-child(7) { animation-delay: 0.36s; }
@keyframes fmUpA {
	from {
		opacity: 0;
		transform: translateY(14px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* The alert arrives and its edge brightens once, which docs/alerts.md allows
   for a sharp change and forbids as a repeating pulse.

   THE RING'S DELAY IS DERIVED, NOT THE DESIGN'S LITERAL 1s, and that is the
   whole repair here. design/NewFlagger.dc.html fires it 1s after the screen
   appears, because on the design canvas the screen IS there at t=0. On this
   page the same markup lives inside `.hr-phone`, which is held at opacity 0
   until `--hr-lights-end` and only reaches opacity 1 a further
   `--hr-phone-dur` later — so a 1s delay put the whole 1.2s ring inside the
   window where the phone cannot be seen. Measured, not reasoned: at the
   ring's start the phone's computed opacity was 0 and at its end 0.638, in
   docs/evidence/2026-10-28-step0.5-ring-parity.md.

   This is the hero shine's defect (df2ec18) in a second place, so it takes
   the same derivation the shine now uses — `--hr-lights-end` plus
   `--hr-phone-dur`, the moment the phone has landed — and the next retune of
   the light run carries the ring with it instead of leaving it behind.
   src/html/animation-parity.test.ts section (3) pins the expression.

   The design's extra 1s is deliberately NOT carried over. It measured from
   the start of a screen entrance that this page replaces with the phone's
   own fly-in; adding it on top would put the ring a second after the thing
   it points at had already settled. The row's own `fmUpA` is left at load
   time on purpose — see the note below the ripple. */
.runA > .fm-alert {
	animation: fmUpA 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.12s backwards,
		fmRingA 1.2s ease-out calc(var(--hr-lights-end) + var(--hr-phone-dur));
}
@keyframes fmRingA {
	0% {
		outline: 3px solid var(--n-card-border);
		outline-offset: 0;
	}
	100% {
		outline: 3px solid transparent;
		outline-offset: 12px;
	}
}

/* The trail draws itself behind the aircraft, and the crossing marker takes
   one ripple as the aircraft's position lands. */
.runA .fm-draw {
	animation: fmDrawA 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0.9s backwards;
}
.fm-draw {
	transform-origin: 150px 200px;
}
@keyframes fmDrawA {
	from {
		transform: scaleY(0);
	}
	to {
		transform: none;
	}
}

.fm-ripple {
	opacity: 0;
	transform-origin: 150px 200px;
}
.runA .fm-ripple {
	animation: fmRipA 1.1s ease-out 1.1s backwards;
}
@keyframes fmRipA {
	from {
		opacity: 0.9;
		transform: scale(1);
	}
	to {
		opacity: 0;
		transform: scale(2.6);
	}
}

/* WHY THE RING WAS RE-TIMED AND THESE THREE WERE NOT. All four of this
   phone's inner animations finish inside the window where `.hr-phone` holds
   the phone at opacity 0 (`fmUpA` 0–0.96s across the staggered children,
   0.12–0.72s on the alert row itself, `fmDrawA` 0.9–1.6s, `fmRipA`
   1.1–2.2s, and the ring was 1–2.2s, against a phone that is invisible until
   2.02s). The ring was the one that could be moved WITHOUT CHANGING ANY FRAME
   A VISITOR ALREADY SEES, because its fill is `none`: before its delay the
   element carries no outline at all, which is exactly what it looks like
   today once the animation is over. The other three fill `backwards`, so
   delaying them holds their FIRST frame on screen for the whole fly-in — the
   rows at opacity 0 (a phone that arrives empty), the trail at scaleY(0) (a
   map that arrives with no trail) and the ripple at opacity 0.9 (a static
   disc sitting on the crossing marker). Each of those is a different arrival
   than the one Dylan approved on screen, so it is his call and not this
   stylesheet's. Measurements for all four are in
   docs/evidence/2026-10-28-step0.5-ring-parity.md. */

/* =============================================================================
   The "Try the app" screens — apps/site/design/Emu*.dc.html
   =============================================================================
   The Home page's interactive mockups: the flagger (R2a,
   design/EmuFlagger.dc.html), the escort (R2b, design/EmuEscort.dc.html) and
   the crew board (R2c, design/EmuCrew.dc.html). Each task extends this block
   rather than restating it.

   `.emu` AND NOT `.nf`, AND THE TWO ARE NOT MERGED. The hero phone above is
   design/NewFlagger.dc.html and the screen in "Try the app" is
   design/EmuFlagger.dc.html. They look alike and they are not the same
   drawing: this one's alert row is a button with a chevron, it has a Big map
   control, and its status strip changes while hold-to-talk is held. One set of
   classes for two design files means an edit to either design silently
   changes the other, which is the same reason `--m-*` and `--n-*` are kept
   apart higher up this sheet.

   THE PALETTE IS THE SAME, AND THAT IS MEASURED RATHER THAN ASSUMED. Every
   one of the 55 tokens in EmuFlagger's and EmuEscort's `_rv()` theme objects
   is byte-identical to NewFlagger's, in both day and night. EmuCrew declares a
   smaller set — 35 tokens, because it draws no map — and every token it SHARES
   with the other three is byte-identical too: 186 shared values compared, 0
   differ. It adds four of its own: amberTint, amberInk, amberLine and rowLift.
   The comparisons are in docs/evidence/2026-10-27-step0.5-R2a-try-the-app.md
   and docs/evidence/2026-10-28-step0.5-R2c-crew-board.md §3.

   THAT FIGURE WAS 180 AND THE 180 WAS STALE, which is worth leaving visible
   because of HOW it went wrong rather than by how much. 180 is 30 shared
   tokens x 3 files x 2 themes, and it was measured against EmuCrew BEFORE the
   authorised `{{c.onBrand}}` resolution was written into the design file.
   Resolving it added a 31st shared token — EmuFlagger, EmuEscort and
   NewFlagger all declare `onBrand` already — so the same comparison now
   compares 186 values. Nothing about the claim it supports changed: 0 still
   differ. A design-file edit moved a constant derived FROM that file, and the
   constant was not re-derived afterwards, which is exactly the drift
   docs/coding.md's "the design edits and the re-derived constants land in the
   same commit" exists to catch. §3 of the evidence file carries both numbers
   and the command that separates them.

   So WHY DUPLICATE THE BLOCK? Because `--n-*` is declared on `.nf`, not on
   `:root`, and widening that selector would edit task R1's committed rules,
   which docs/agents.md does not let a later task do — R1's audit was written
   against them. The duplication is pinned instead:
   src/html/emu-palette.test.ts fails if any shared token's value differs
   between the two blocks, so the copies cannot drift apart silently. When the
   last page converts and section 26's `.page` scoping comes off, these two
   blocks should become one selector list.

   SOME TOKENS ARE DECLARED HERE AND NOT ON `.nf`, and that is not an
   inconsistency. `ptt-talk` and `talk-ring` exist in NewFlagger's theme object
   but never reach its markup, because the hero phone is a still picture and
   nothing in it is ever talking; these screens are, so they become properties.
   The same test applies to every later addition: a token becomes a property
   here when some screen's MARKUP reaches it, and not merely because a design
   file declares it.

   SVG presentation attributes (`fill="…"`, `stroke="…"`) do not resolve
   `var()`, so every themed fill and stroke is a class: `emu-f-*` sets a fill,
   `emu-s-*` sets a stroke.
   ========================================================================== */

.emu {
	--n-bg: #eef2f8;
	--n-surface: #ffffff;
	--n-ink: #02113a;
	--n-muted: #3d4b68;
	--n-line: #d3dae6;
	--n-brand: #0461f8;
	--n-on-brand: #ffffff;
	--n-chip: #dde5f3;

	--n-alert-ink: #1f1300;
	--n-card-tint: #fff3d6;
	--n-card-ink: #1f1300;
	--n-card-muted: #6b5320;
	--n-card-border: #e59a00;

	--n-map-bg: #edeff2;
	--n-apron: #e2e5ea;
	--n-bldg: #d5d9e0;
	--n-twy: #ffffff;
	--n-twy-edge: #c9ced8;
	--n-center: #e2b93b;
	--n-rwy: #7b8291;
	--n-haul: #bca27a;
	--n-label: #5b6475;
	--n-other: #7a8599;
	--n-stopped: #2c3a5c;
	--n-plane: #c2410c;
	--n-compass: #d92d20;
	/* Three the escort screen's map needs and the flagger's does not: the
	   dashed awareness ring around the crossing, and the fill and ink of the
	   selected Close/Area preset. Value-identical to EmuEscort's,
	   EmuFlagger's and NewFlagger's own theme objects in both themes — the
	   comparison is in docs/evidence/2026-10-27-step0.5-R2b-escort-screen.md.
	   They are absent from the `.nf` block because NewFlagger's MARKUP never
	   reaches them, which is the same reason `ptt-talk` and `talk-ring` are
	   only here. */
	--n-ring: #0461f8;
	--n-sel: #0461f8;
	--n-sel-ink: #ffffff;

	/* Four the crew board needs and the other two screens do not (task R2c).
	   `amber-tint` and `amber-line` paint the unstaffed-end marker, which
	   docs/alerts.md requires to be a DASHED amber outline or dot and never
	   solid amber; `row-lift` is the softer shadow its list rows carry, where
	   `--n-lift` is the card shadow. All four are in EmuCrew's own theme
	   objects and in no other design file's — the comparison is in
	   docs/evidence/2026-10-28-step0.5-R2c-crew-board.md §3. A fifth,
	   `amberInk`, is declared in the design and its markup never reaches it,
	   so it is not a property here — the same reason `--n-ptt-talk` is absent
	   from the `.nf` block above. */
	--n-amber-tint: #fff3d6;
	--n-amber-line: #e59a00;
	--n-row-lift: 0 1px 2px rgba(2, 17, 58, 0.06), 0 3px 8px rgba(2, 17, 58, 0.06);

	--n-ptt: #02113a;
	--n-ptt-ink: #ffffff;
	--n-ptt-talk: #0461f8;
	--n-talk-ring: #04a5f8;
	--n-btn-line: #8e9cb8;

	--n-nav-bg: rgba(255, 255, 255, 0.94);
	--n-nav-line: rgba(2, 17, 58, 0.1);
	--n-nav-sel: #e3ecff;
	--n-nav-sel-ink: #0452d0;
	/* The tab bar's word badges, which only the crew board draws (R2c).
	   `live` is brand blue and `badge` is amber. docs/alerts.md asks for an
	   "ALERT/OFFLINE word badge on the Crossing tab" in so many words, which
	   is what `badge` paints. */
	--n-badge: #e59a00;
	--n-badge-ink: #1f1300;
	--n-live: #0461f8;
	--n-live-ink: #ffffff;

	--n-glass: rgba(255, 255, 255, 0.9);
	--n-glass-line: rgba(2, 17, 58, 0.12);
	--n-shadow: 0 2px 12px rgba(2, 17, 58, 0.18);
	--n-lift: 0 1px 2px rgba(2, 17, 58, 0.06), 0 6px 18px rgba(2, 17, 58, 0.09);
	--n-nav-float: 0 14px 34px rgba(2, 17, 58, 0.22), 0 3px 8px rgba(2, 17, 58, 0.1),
		inset 0 1px 0 rgba(255, 255, 255, 0.8);
	--n-sel-lift: 0 2px 6px rgba(2, 17, 58, 0.12);
}

/* Night, by the device unless the visitor explicitly chose day. Same rule as
   site.css and the mockups above. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="day"]) .emu {
		--n-bg: #030a1c;
		--n-surface: #0e1b38;
		--n-ink: #e6ecf8;
		--n-muted: #a7b6d3;
		--n-line: #22335e;
		--n-brand: #5b9bff;
		--n-on-brand: #02113a;
		--n-chip: #13244a;
		--n-alert-ink: #1a0f00;
		--n-card-tint: #241a06;
		--n-card-ink: #fcebc7;
		--n-card-muted: #d9be86;
		--n-card-border: #e39b22;
		--n-map-bg: #0a1226;
		--n-apron: #101b33;
		--n-bldg: #18233d;
		--n-twy: #2a3858;
		--n-twy-edge: #1c2945;
		--n-center: #9c7f2a;
		--n-rwy: #3a4766;
		--n-haul: #6b5b3e;
		--n-label: #93a2c2;
		--n-other: #6f7fa3;
		--n-stopped: #b9c6e2;
		--n-plane: #ff8a4c;
		--n-compass: #ff6b5e;
		--n-ring: #5b9bff;
		--n-sel: #5b9bff;
		--n-sel-ink: #02113a;
		--n-amber-tint: #241a06;
		--n-amber-line: #e39b22;
		--n-row-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 10px rgba(0, 0, 0, 0.35);
		--n-ptt: #1e5bd8;
		--n-ptt-ink: #ffffff;
		--n-ptt-talk: #3f7bff;
		--n-talk-ring: #04a5f8;
		--n-btn-line: #3a5085;
		--n-nav-bg: rgba(14, 27, 56, 0.94);
		--n-nav-line: rgba(230, 236, 248, 0.14);
		--n-nav-sel: #1b2e5c;
		--n-nav-sel-ink: #9cc2ff;
		--n-badge: #e39b22;
		--n-badge-ink: #1a0f00;
		--n-live: #5b9bff;
		--n-live-ink: #02113a;
		--n-glass: rgba(14, 27, 56, 0.9);
		--n-glass-line: rgba(230, 236, 248, 0.16);
		--n-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
		--n-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 22px rgba(0, 0, 0, 0.45);
		--n-nav-float: 0 16px 38px rgba(0, 0, 0, 0.65), 0 3px 8px rgba(0, 0, 0, 0.4),
			inset 0 1px 0 rgba(255, 255, 255, 0.08);
		--n-sel-lift: 0 2px 6px rgba(0, 0, 0, 0.4);
	}
}

/* An explicit choice wins over the device, in both directions. */
:root[data-theme="night"] .emu {
	--n-bg: #030a1c;
	--n-surface: #0e1b38;
	--n-ink: #e6ecf8;
	--n-muted: #a7b6d3;
	--n-line: #22335e;
	--n-brand: #5b9bff;
	--n-on-brand: #02113a;
	--n-chip: #13244a;
	--n-alert-ink: #1a0f00;
	--n-card-tint: #241a06;
	--n-card-ink: #fcebc7;
	--n-card-muted: #d9be86;
	--n-card-border: #e39b22;
	--n-map-bg: #0a1226;
	--n-apron: #101b33;
	--n-bldg: #18233d;
	--n-twy: #2a3858;
	--n-twy-edge: #1c2945;
	--n-center: #9c7f2a;
	--n-rwy: #3a4766;
	--n-haul: #6b5b3e;
	--n-label: #93a2c2;
	--n-other: #6f7fa3;
	--n-stopped: #b9c6e2;
	--n-plane: #ff8a4c;
	--n-compass: #ff6b5e;
	--n-ring: #5b9bff;
	--n-sel: #5b9bff;
	--n-sel-ink: #02113a;
	--n-amber-tint: #241a06;
	--n-amber-line: #e39b22;
	--n-row-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 10px rgba(0, 0, 0, 0.35);
	--n-ptt: #1e5bd8;
	--n-ptt-ink: #ffffff;
	--n-ptt-talk: #3f7bff;
	--n-talk-ring: #04a5f8;
	--n-btn-line: #3a5085;
	--n-nav-bg: rgba(14, 27, 56, 0.94);
	--n-nav-line: rgba(230, 236, 248, 0.14);
	--n-nav-sel: #1b2e5c;
	--n-nav-sel-ink: #9cc2ff;
	--n-badge: #e39b22;
	--n-badge-ink: #1a0f00;
	--n-live: #5b9bff;
	--n-live-ink: #02113a;
	--n-glass: rgba(14, 27, 56, 0.9);
	--n-glass-line: rgba(230, 236, 248, 0.16);
	--n-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
	--n-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 22px rgba(0, 0, 0, 0.45);
	--n-nav-float: 0 16px 38px rgba(0, 0, 0, 0.65), 0 3px 8px rgba(0, 0, 0, 0.4),
		inset 0 1px 0 rgba(255, 255, 255, 0.08);
	--n-sel-lift: 0 2px 6px rgba(0, 0, 0, 0.4);
}

/* ---------------------------------------------------------------------------
   The screen
   ------------------------------------------------------------------------ */
.emu {
	position: relative;
	width: 390px;
	height: 844px;
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	background: var(--n-bg);
	color: var(--n-ink);
	font-family: "Atkinson Hyperlegible Next", "Segoe UI", system-ui, sans-serif;
	overflow: hidden;
}

/* `hidden` has to beat the `display: flex` above it — the same trap the
   captions and the checklists have in site.css section 27. Without this, the
   roles with no screen yet would show the flagger's. */
.emu[hidden] {
	display: none;
}

/* Every button in here is a real button and inherits nothing by default. */
.emu button {
	font-family: inherit;
	cursor: pointer;
}

/* The designs' `cr-liftb`, which reads `var(--lift)`. */
.emu-lift {
	box-shadow: var(--n-lift);
}

/* ---------------------------------------------------------------------------
   Header
   ------------------------------------------------------------------------ */
.emu__head {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 14px 16px 10px 16px;
}

.emu__mark {
	width: 40px;
	height: 40px;
	object-fit: contain;
	flex-shrink: 0;
}
/* The app mark has a light-background and a dark-background file, and the
   depicted phone follows the site's theme so the picture matches the page it
   sits on. Same swap as the hero phone's. */
.emu__mark--night {
	display: none;
}
:root[data-theme="night"] .emu__mark--day {
	display: none;
}
:root[data-theme="night"] .emu__mark--night {
	display: block;
}
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="day"]) .emu__mark--day {
		display: none;
	}
	:root:not([data-theme="day"]) .emu__mark--night {
		display: block;
	}
}

.emu__post {
	display: flex;
	flex-direction: column;
	min-width: 0;
	flex-grow: 1;
}
.emu__post-label {
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu__post-name {
	font-size: 28px;
	font-weight: 800;
	line-height: 1.1;
}

.emu__avatar {
	flex-shrink: 0;
	width: 44px;
	height: 44px;
	border-radius: 22px;
	border: 0;
	background: var(--n-chip);
	color: var(--n-ink);
	font-size: 16px;
	font-weight: 800;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* ---------------------------------------------------------------------------
   Status strip
   ------------------------------------------------------------------------ */
.emu__status {
	margin: 0 16px 12px 16px;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	border-radius: 16px;
	background: var(--n-surface);
	border: 1px solid var(--n-line);
	box-shadow: var(--n-lift);
}
.emu__status-cell {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 10px 4px;
	font-size: 14px;
	font-weight: 700;
}
.emu__status-cell--mid {
	border-left: 1px solid var(--n-line);
	border-right: 1px solid var(--n-line);
}
/* The mic cell fills while hold-to-talk is held, so the strip says what the
   button is doing. The radius matches the strip's own right corners. */
.emu__status-cell--mic {
	border-radius: 0 13px 13px 0;
	background: transparent;
	color: var(--n-ink);
}
.emu--talking .emu__status-cell--mic {
	background: var(--n-ptt-talk);
	color: var(--n-ptt-ink);
}
/* The slash across the mic is there when the mic is off, and gone when it is
   live — which is how the icon and the word agree. */
.emu--talking .emu__mic-slash {
	display: none;
}

/* ---------------------------------------------------------------------------
   The alert row (docs/alerts.md's banner: airline, then taxiway, then time)
   ------------------------------------------------------------------------ */
/* `box-sizing: border-box` IS REQUIRED and it is the design's own value, from
   the other file. design/EmuEscort.dc.html's identical `.c2-banner` carries
   it and design/EmuFlagger.dc.html's omits it; with `width: calc(100% - 32px)`
   plus 16px margins, 14px padding each side and a 1.5px border, a
   content-box banner is 421px wide inside a 390px screen and its right edge
   is clipped. The measurement is in this task's evidence file. */
.emu__alert {
	box-sizing: border-box;
	appearance: none;
	width: calc(100% - 32px);
	margin: 0 16px;
	padding: 10px 14px;
	border-radius: 16px;
	background: var(--n-card-tint);
	color: var(--n-card-ink);
	border: 1.5px solid var(--n-card-border);
	display: flex;
	align-items: center;
	gap: 12px;
	text-align: left;
	font: inherit;
}
.emu__alert-icon {
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	border-radius: 18px;
	background: var(--n-card-border);
	color: var(--n-alert-ink);
	display: flex;
	align-items: center;
	justify-content: center;
}
.emu__alert-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}
.emu__alert-head {
	font-size: 17px;
	font-weight: 700;
	line-height: 1.3;
}
.emu__alert-sub {
	font-size: 14px;
	font-weight: 500;
	line-height: 1.35;
	color: var(--n-card-muted);
	font-variant-numeric: tabular-nums;
}
/* Tabular figures and an inline-block so the row does not reflow when the
   number changes width. docs/alerts.md: the number and unit never split. */
.emu__secs {
	display: inline-block;
}
.emu__alert-chevron {
	flex-shrink: 0;
	opacity: 0.6;
}

/* ---------------------------------------------------------------------------
   The glance map
   ------------------------------------------------------------------------ */
.emu__map {
	position: relative;
	flex-grow: 1;
	margin: 12px 16px 0 16px;
	border-radius: 20px;
	overflow: hidden;
	border: 1px solid var(--n-line);
	box-shadow: var(--n-lift);
	min-height: 0;
}
.emu__map-svg {
	width: 100%;
	height: 100%;
	display: block;
}
.emu__map-letter {
	font-size: 15px;
	font-weight: 800;
}

.emu__chip {
	position: absolute;
	top: 8px;
	display: flex;
	align-items: center;
	padding: 6px 10px;
	border-radius: 14px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	backdrop-filter: blur(12px);
	color: var(--n-ink);
}
.emu__chip--north {
	left: 8px;
	gap: 4px;
	font-size: 13px;
	font-weight: 800;
}
.emu__chip--age {
	right: 8px;
	gap: 5px;
	font-size: 12px;
	font-weight: 700;
}

/* THE TARGET IS THE PILL, NOT THE WHOLE MAP. The design draws this as an
   anchor with `inset: 0` and the pill aligned to its bottom-right corner,
   which is a canvas convenience: that anchor has no href and cannot be
   focused, so in the drawing it is only ever a way to place the pill. Built
   as a real control on an interactive screen it would be a 358x300 button
   sitting over the map, in the tab order, announced as "Open the big map" and
   swallowing the two chips. The pill renders in exactly the same place;
   docs/ui-rules.md's 44px floor is met by the pill itself. */
.emu__big-map {
	position: absolute;
	right: 8px;
	bottom: 8px;
	padding: 0;
	border: 0;
	background: transparent;
	display: flex;
}
.emu__big-map-pill {
	display: flex;
	align-items: center;
	gap: 4px;
	height: 44px;
	padding: 0 16px;
	border-radius: 22px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	color: var(--n-ink);
	font-size: 15px;
	font-weight: 800;
}

.emu__map-credit {
	position: absolute;
	left: 10px;
	bottom: 6px;
	font-size: 10px;
	color: var(--n-label);
}

/* ---------------------------------------------------------------------------
   The bottom: the verify line, then Repeat and hold-to-talk
   -----------------------------------------------------------------------------
   docs/phone-app.md, "Bottom of the flagger screen": the verify line, then
   one row with a square Repeat on the left and hold-to-talk filling the rest.
   The verify line is root CLAUDE.md rule 1, inside the depicted screen and in
   that screen's own wording.
   ------------------------------------------------------------------------ */
.emu__foot {
	padding: 10px 16px 0 16px;
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.emu__verify {
	text-align: center;
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu__row {
	display: flex;
	gap: 10px;
}

.emu__repeat {
	width: 76px;
	height: 72px;
	flex-shrink: 0;
	border-radius: 20px;
	border: 1.5px solid var(--n-btn-line);
	background: var(--n-surface);
	color: var(--n-ink);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 3px;
	font-size: 13px;
	font-weight: 700;
}

.emu__ptt {
	flex: 1;
	min-width: 0;
	height: 72px;
	border-radius: 20px;
	border: 0;
	background: var(--n-ptt);
	color: var(--n-ptt-ink);
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	font-size: 21px;
	font-weight: 800;
	/* The ring is a box-shadow layer that is transparent at rest, so it can
	   be transitioned in rather than appearing in one frame. Sky blue, never
	   amber: docs/alerts.md reserves amber for aircraft. */
	box-shadow: 0 0 0 0 transparent, var(--n-lift);
}
.emu--talking .emu__ptt {
	background: var(--n-ptt-talk);
	box-shadow: 0 0 0 4px var(--n-talk-ring), var(--n-lift);
}
/* The mic icon becomes level bars while the button is held. */
.emu--talking .emu__ptt-mic {
	display: none;
}
.emu__ptt-bars {
	display: none;
	align-items: center;
	gap: 3px;
	height: 26px;
	flex-shrink: 0;
}
.emu--talking .emu__ptt-bars {
	display: flex;
}
.emu-bar {
	display: inline-block;
	width: 4px;
	border-radius: 2px;
	background: currentColor;
	transform-origin: center;
}
.emu-bar:nth-child(1) {
	height: 14px;
}
.emu-bar:nth-child(2) {
	height: 24px;
}
.emu-bar:nth-child(3) {
	height: 18px;
}
.emu-bar:nth-child(4) {
	height: 26px;
}
.emu-bar:nth-child(5) {
	height: 12px;
}

.emu__ptt-labels {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	line-height: 1.15;
	min-width: 0;
}
.emu__ptt-main {
	white-space: nowrap;
}
.emu__ptt-sub {
	font-size: 13px;
	font-weight: 600;
	opacity: 0.9;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 100%;
}

/* ---------------------------------------------------------------------------
   The floating tab bar
   -----------------------------------------------------------------------------
   docs/phone-app.md: a glass capsule inset from the edges and lifted off the
   bottom, selected tab in a soft blue pill. The gap above it is a spacer
   element rather than padding, because the bar is positioned and the content
   above it must not slide under it.
   ------------------------------------------------------------------------ */
.emu__nav-gap {
	height: 98px;
	flex-shrink: 0;
}
.emu__nav {
	position: absolute;
	left: 20px;
	right: 20px;
	bottom: 14px;
	z-index: 5;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 4px;
	padding: 6px;
	border-radius: 30px;
	border: 1px solid var(--n-nav-line);
	background: var(--n-nav-bg);
	box-shadow: var(--n-nav-float);
	backdrop-filter: blur(20px) saturate(1.6);
}
.emu__tab {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 2px;
	padding: 6px 0;
	border-radius: 19px;
	color: var(--n-muted);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.02em;
}
.emu__tab--on {
	border-radius: 24px;
	box-shadow: var(--n-sel-lift);
	background: var(--n-nav-sel);
	color: var(--n-nav-sel-ink);
	font-weight: 800;
}

/* ---------------------------------------------------------------------------
   Themed SVG fills and strokes
   ------------------------------------------------------------------------ */
.emu-f-map {
	fill: var(--n-map-bg);
}
.emu-f-apron {
	fill: var(--n-apron);
}
.emu-f-bldg {
	fill: var(--n-bldg);
}
.emu-f-rwy {
	fill: var(--n-rwy);
}
.emu-f-twy {
	fill: var(--n-twy);
}
.emu-f-stopped {
	fill: var(--n-stopped);
}
.emu-f-plane {
	fill: var(--n-plane);
}
.emu-f-other {
	fill: var(--n-other);
}
.emu-f-brand {
	fill: var(--n-brand);
}
.emu-f-on-brand {
	fill: var(--n-on-brand);
}
.emu-f-compass {
	fill: var(--n-compass);
}
/* The amber remainder of the aircraft's path. Amber here is an aircraft, which
   is the only thing docs/alerts.md allows it to be. */
.emu-f-border {
	fill: var(--n-card-border);
}
.emu-s-twy {
	stroke: var(--n-twy);
}
.emu-s-twy-edge {
	stroke: var(--n-twy-edge);
}
.emu-s-center {
	stroke: var(--n-center);
}
.emu-s-haul {
	stroke: var(--n-haul);
}
.emu-s-stopped {
	stroke: var(--n-stopped);
}
.emu-s-brand {
	stroke: var(--n-brand);
}
.emu-s-map {
	stroke: var(--n-map-bg);
}

/* ---------------------------------------------------------------------------
   Motion
   -----------------------------------------------------------------------------
   Cancelled whole by site.css section 25's `animation: none !important` and
   `transition: none !important`, which is the reduced-motion gate
   docs/ui-rules.md allows no exceptions to.

   EVERY FINISHED STATE HERE IS A PLAIN DECLARATION, not a keyframe. That is
   the defect class task R1b found in the hero's approach lights: 34 of them
   sat at `opacity: 0` and were brought up BY the animation, so cancelling the
   animation left a visitor who asked for less motion with no diagram at all.
   Checked element by element for this screen, and the two that genuinely rest
   invisible are the ripple (which ends at opacity 0 — invisible IS its
   finished state) and the level bars (which only exist while the button is
   held, and which get the design's own static 0.7 scale below).

   NOTHING LOOPS. `hbPulse` on the "Live" dot and `infinite` on the level bars
   are both in the design and neither is reproduced: docs/ui-rules.md permits
   four looping animations by name, these are not among them, and
   src/html/motion.test.ts fails on a fifth. The bars run a finite count
   instead, long enough that no realistic press reaches the end.

   A/B PAIRS. `emuUpA`/`emuUpB` and the rest are identical pairs, alternated
   by the script when the run restarts. Re-adding a class that is already on
   an element does not restart a CSS animation; swapping between two does.
   That is how the design's `showA`/`showB` remount is reproduced as an effect
   without a remount.
   ------------------------------------------------------------------------ */

/* Press feedback. */
.emu-press,
.emu__big-map-pill,
.emu__repeat {
	transition: transform 0.12s ease-out, background-color 0.2s ease-out;
}
.emu-press:active,
.emu__repeat:active {
	transform: scale(0.94);
}
.emu__alert {
	transition: background-color 0.45s ease-out, border-color 0.45s ease-out,
		color 0.45s ease-out, transform 0.12s ease-out;
}
.emu__alert:active {
	transform: scale(0.98);
}
.emu__ptt {
	transition: transform 0.06s ease-out, box-shadow 0.2s ease-out,
		background-color 0.2s ease-out;
}
.emu__ptt:active {
	transform: scale(0.97);
}
.emu__status-cell,
.emu__status-cell--mic {
	transition: background-color 0.25s ease-out, color 0.25s ease-out;
}

/* The screen arrives: each child rises in turn. */
.emu > * {
	animation-fill-mode: backwards;
}
.emu-run-a > * {
	animation: emuUpA 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}
.emu-run-b > * {
	animation: emuUpB 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}
@keyframes emuUpA {
	from {
		opacity: 0;
		transform: translateY(14px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}
@keyframes emuUpB {
	from {
		opacity: 0;
		transform: translateY(14px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}
.emu > *:nth-child(2) {
	animation-delay: 0.06s;
}
.emu > *:nth-child(3) {
	animation-delay: 0.12s;
}
.emu > *:nth-child(4) {
	animation-delay: 0.18s;
}
.emu > *:nth-child(5) {
	animation-delay: 0.24s;
}
.emu > *:nth-child(6) {
	animation-delay: 0.3s;
}
.emu > *:nth-child(7) {
	animation-delay: 0.36s;
}
.emu > *:nth-child(8) {
	animation-delay: 0.42s;
}

/* The alert row rises and then gives one amber edge flash — docs/alerts.md's
   "a sharp change brightens the amber edge once". An outline, so no layout
   box moves and nothing beside it shifts. */
.emu-run-a > .fm-alert {
	animation: emuUpA 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.12s backwards,
		emuRingA 1.2s ease-out 1s;
}
.emu-run-b > .fm-alert {
	animation: emuUpB 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.12s backwards,
		emuRingB 1.2s ease-out 1s;
}
@keyframes emuRingA {
	0% {
		outline: 3px solid var(--n-card-border);
		outline-offset: 0;
	}
	100% {
		outline: 3px solid transparent;
		outline-offset: 12px;
	}
}
@keyframes emuRingB {
	0% {
		outline: 3px solid var(--n-card-border);
		outline-offset: 0;
	}
	100% {
		outline: 3px solid transparent;
		outline-offset: 12px;
	}
}

/* The remaining path draws itself down toward the crossing. */
.emu-draw {
	transform-origin: 150px 200px;
}
.emu-run-a .emu-draw {
	animation: emuDrawA 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0.9s backwards;
}
.emu-run-b .emu-draw {
	animation: emuDrawB 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0.9s backwards;
}
@keyframes emuDrawA {
	from {
		transform: scaleY(0);
	}
	to {
		transform: none;
	}
}
@keyframes emuDrawB {
	from {
		transform: scaleY(0);
	}
	to {
		transform: none;
	}
}

/* One ripple at the crossing marker as the alert lands. It ENDS invisible, so
   opacity 0 is its finished state and cancelling the animation is correct. */
.emu-ripple {
	opacity: 0;
	transform-origin: 150px 200px;
}
.emu-run-a .emu-ripple {
	animation: emuRipA 1.1s ease-out 1.1s backwards;
}
.emu-run-b .emu-ripple {
	animation: emuRipB 1.1s ease-out 1.1s backwards;
}
@keyframes emuRipA {
	from {
		opacity: 0.9;
		transform: scale(1);
	}
	to {
		opacity: 0;
		transform: scale(2.6);
	}
}
@keyframes emuRipB {
	from {
		opacity: 0.9;
		transform: scale(1);
	}
	to {
		opacity: 0;
		transform: scale(2.6);
	}
}

/* The aircraft rolls, and the path it has left shrinks with it, for the whole
   twelve seconds of the run.
   -----------------------------------------------------------------------------
   The design moves both with a per-render inline `transform` and a one-second
   linear transition, stepping twelve times. Twelve one-second linear steps are
   a linear ramp, so this is one 12s linear animation — identical on screen,
   and it keeps every runtime style attribute out of the page. The escort
   design already uses exactly this technique for its own marker.

   The numbers are the design's own endpoints: `r.dy` runs -16 to 0, and
   `r.pathS` is (44 - dy)/44, so scaleY runs 60/44 = 1.3636 down to 1.
   `both`, so the finished state is held — which is also what a cancelled
   animation leaves, because the `to` state is the element's natural one. */
.emu-run-a .emu__move {
	animation: emuMoveA 12s linear both;
}
.emu-run-b .emu__move {
	animation: emuMoveB 12s linear both;
}
@keyframes emuMoveA {
	from {
		transform: translateY(-16px);
	}
	to {
		transform: none;
	}
}
@keyframes emuMoveB {
	from {
		transform: translateY(-16px);
	}
	to {
		transform: none;
	}
}
.emu__path {
	transform-origin: 150px 200px;
}
.emu-run-a .emu__path {
	animation: emuPathA 12s linear both;
}
.emu-run-b .emu__path {
	animation: emuPathB 12s linear both;
}
@keyframes emuPathA {
	from {
		transform: scaleY(1.3636);
	}
	to {
		transform: none;
	}
}
@keyframes emuPathB {
	from {
		transform: scaleY(1.3636);
	}
	to {
		transform: none;
	}
}

/* The post name fades down on arrival (the designs' `hb-in`). */
.emu__post-name {
	animation: emuIn 0.35s ease-out both;
}
@keyframes emuIn {
	from {
		opacity: 0;
		transform: translateY(-6px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* The time fades over on the one tick where it changes — and only then.
   docs/alerts.md: "when the time steps down, only the changed numbers fade
   over (~0.3 s)"; never a per-second countdown. */
.emu__secs--step {
	animation: emuTickFade 0.45s ease-out;
}
@keyframes emuTickFade {
	from {
		opacity: 0.25;
	}
	to {
		opacity: 1;
	}
}

/* Repeat turns its arrow once and reads "Playing" while the alert replays
   (docs/phone-app.md). */
.emu__repeat-icon--spin {
	animation: emuSpin 0.7s ease-in-out both;
}
@keyframes emuSpin {
	from {
		transform: rotate(0);
	}
	to {
		transform: rotate(-360deg);
	}
}
.emu__repeat-label--in {
	animation: emuLabelIn 0.25s ease-out both;
}
@keyframes emuLabelIn {
	from {
		opacity: 0;
		transform: translateX(-6px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* The hold-to-talk label fades between "Hold to talk" and "Talking…" on every
   press and release, so it needs an A/B pair. */
.emu__ptt-labels--a {
	animation: emuFadeA 0.18s ease-out both;
}
.emu__ptt-labels--b {
	animation: emuFadeB 0.18s ease-out both;
}
@keyframes emuFadeA {
	from {
		opacity: 0;
		transform: translateY(3px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}
@keyframes emuFadeB {
	from {
		opacity: 0;
		transform: translateY(3px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* The level bars, while the button is held.
   -----------------------------------------------------------------------------
   A FINITE COUNT AND NOT `infinite`, which is the one place this screen does
   not copy its design literally. docs/ui-rules.md permits four looping
   animations by name and these are not among them; adding a fifth is Dylan's
   decision and nobody else's (src/html/motion.test.ts says so where the list
   is defined). 12 iterations of the longest bar is 13.2s of holding, which no
   realistic press on a mockup reaches, so what a visitor sees is the design.
   Said out loud here rather than left in a diff. */
.emu--talking .emu-bar {
	animation: emuBar 0.9s ease-in-out 12;
}
.emu--talking .emu-bar:nth-child(2) {
	animation-duration: 0.7s;
	animation-delay: -0.2s;
}
.emu--talking .emu-bar:nth-child(3) {
	animation-duration: 1.1s;
	animation-delay: -0.5s;
}
.emu--talking .emu-bar:nth-child(4) {
	animation-duration: 0.8s;
	animation-delay: -0.1s;
}
.emu--talking .emu-bar:nth-child(5) {
	animation-duration: 1s;
	animation-delay: -0.35s;
}
@keyframes emuBar {
	0%,
	100% {
		transform: scaleY(0.3);
	}
	50% {
		transform: scaleY(1);
	}
}

/* Reduce Motion: the bars stand still at the design's own static height
   rather than at full scale, which is the design's value in its own
   reduced-motion block. Section 25 has already cancelled the animation; this
   only says where they rest. */
@media (prefers-reduced-motion: reduce) {
	.emu-bar {
		transform: scaleY(0.7);
	}
}

/* =============================================================================
   The escort screen — apps/site/design/EmuEscort.dc.html
   =============================================================================
   Task R2b. Everything above this point that is shared — the screen root, the
   header, the alert row, the map container, the verify line, Repeat and
   hold-to-talk, the tab bar, and the `emu-f-*` / `emu-s-*` fills and strokes —
   is reused rather than restated, and only what this design draws differently
   is here.

   WHY THE ESCORT'S SCREEN IS NOT THE FLAGGER'S WITH A BIGGER MAP. An escort
   drives between crossings, so the map IS the screen and the alert rides on
   top of it: docs/alerts.md gives escorts "one alert strip, at the top of the
   map" and "the same row in compact form", where the flagger's sits inside a
   status strip below the header. That compact row is `.emu__alert--compact`,
   and it is the one place a shared component is modified rather than reused
   whole.

   THREE NEW THEME TOKENS, and they are additions rather than changes:
   `--n-ring`, `--n-sel` and `--n-sel-ink`. The ring is the dashed awareness
   circle around the crossing; the other two paint the selected Close/Area
   preset. All three sit in EmuFlagger's and NewFlagger's theme objects too
   with identical values, and are absent from `.nf` only because that markup
   never reaches them.

   FONT SIZES ON SVG TEXT STAY AS PRESENTATION ATTRIBUTES here, where
   `.emu__map-letter` above made one a class. They are literal numbers, not
   theme values, so `var()` never has to resolve in them — which is the whole
   reason the fills and strokes had to become classes (apps/site/README.md
   item 5). This map draws text at five sizes and five classes carrying one
   declaration each would be worse than the attributes the design already has.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   Themed SVG fills and strokes this map adds
   ------------------------------------------------------------------------ */
.emu-f-surface {
	fill: var(--n-surface);
}
.emu-f-ink {
	fill: var(--n-ink);
}
.emu-f-label {
	fill: var(--n-label);
}
.emu-s-plane {
	stroke: var(--n-plane);
}
.emu-s-line {
	stroke: var(--n-line);
}
/* The dashed circle around the crossing. It marks the area the screen is
   about; it is not a range ring and nothing is read off its radius. */
.emu-s-ring {
	stroke: var(--n-ring);
}

/* ---------------------------------------------------------------------------
   Header: the crossing the phone's GPS picked, and the tap that changes it
   -----------------------------------------------------------------------------
   The HCI skill (ch. 29) is explicit that an inferred context must be both
   SHOWN and correctable in one tap, because sensing intent fails often and
   the user pays for every failure. So the design wraps the crossing name in a
   button with a chevron, where the flagger's — which is assigned, not
   inferred — is plain text. The negative margin is the design's: it lets the
   44px target overhang the two text lines without pushing them apart.
   ------------------------------------------------------------------------ */
.emu__post-pick {
	align-self: flex-start;
	min-height: 44px;
	margin: -6px 0;
	padding: 0;
	border: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	display: flex;
	align-items: center;
	gap: 6px;
}
.emu__post-chevron {
	flex-shrink: 0;
	opacity: 0.7;
}

/* ---------------------------------------------------------------------------
   The alert row, compact (docs/alerts.md: "Escorts get the same row in
   compact form")
   ------------------------------------------------------------------------ */
.emu__alert--compact {
	padding: 8px 12px;
	gap: 10px;
}
.emu__alert--compact .emu__alert-icon {
	width: 32px;
	height: 32px;
	border-radius: 16px;
}
.emu__alert--compact .emu__alert-text {
	line-height: 1.25;
}
/* One line-height for both lines, which is what makes the row compact rather
   than merely smaller. The headline also truncates: the map below it is the
   screen's subject and the row may not grow a second line and eat into it.
   docs/alerts.md allows this for the headline and NOT for the time — "the
   number and unit never split" — and the time is on the line below. */
.emu__alert--compact .emu__alert-head {
	font-size: 16px;
	line-height: inherit;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.emu__alert--compact .emu__alert-sub {
	font-size: 13px;
	line-height: inherit;
}

/* ---------------------------------------------------------------------------
   The map
   -----------------------------------------------------------------------------
   The container is `.emu__map`'s, with the design's own 10px top margin in
   place of the flagger's 12px — the compact alert row above it is shorter, so
   the gap is tightened to match.
   ------------------------------------------------------------------------ */
.emu__map--escort {
	margin-top: 10px;
}

/* THE TWO PRESETS, AS PLAIN DECLARATIONS KEYED OFF ONE CLASS.
   -----------------------------------------------------------------------------
   `Close` is the resting state and carries no class; `Area` adds `.emu--area`
   to the screen root and three things change together:

     * `.emu__zoom`  scales the whole map to 0.5 about the crossing;
     * `.emu__area`  fades the wider airfield in;
     * `.emu__area-cover` fades the flat backdrop that hides it out;
     * `.emu__callout` fades the aircraft callout out, because at half size
       its 11px text is unreadable and a label nobody can read is clutter
       (the HCI skill's clutter test, ch. 3).

   WHY DECLARATIONS AND NOT AN ANIMATION. The preset is a STATE a visitor
   chose, not an event, so both ends of it have to be true with the
   transitions cancelled. site.css section 25 does exactly that for anyone who
   asked for less motion, and a visitor in the Area preset must still see the
   area. This is the same shape as `.try__tick`'s not-done state and the
   opposite of the defect task R1b found in the hero's approach lights. */
.emu__zoom {
	transform-origin: 150px 290px;
	transform: scale(1);
}
.emu--area .emu__zoom {
	transform: scale(0.5);
}
.emu__area {
	opacity: 0;
}
.emu--area .emu__area {
	opacity: 1;
}
.emu__area-cover {
	opacity: 1;
}
.emu--area .emu__area-cover {
	opacity: 0;
}
.emu__callout {
	opacity: 1;
}
.emu--area .emu__callout {
	opacity: 0;
}

/* Status, over the top-left corner of the map. Glass rather than the
   flagger's solid strip, because here it sits on the map instead of above it.
   HCI skill §0.6: box connected, data age, crossing, room and mic are all
   visible without a tap. */
.emu__mapstat {
	position: absolute;
	top: 8px;
	left: 8px;
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 6px 10px;
	border-radius: 14px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	backdrop-filter: blur(12px);
	font-size: 12px;
	font-weight: 700;
	color: var(--n-ink);
	line-height: 1.2;
}
.emu__mapstat-row {
	display: flex;
	align-items: center;
	gap: 5px;
}

/* The Close / Area presets.
   -----------------------------------------------------------------------------
   docs/maps.md: "large + / - buttons and Close/Area presets for gloves".
   Each is a real <button> with `aria-pressed`, so Tab reaches it and Enter or
   Space works it — the WAI-ARIA toggle-button pattern, which needs no key
   handling of its own because a native button already has it. Both are 44px
   tall, which docs/ui-rules.md makes the floor.

   The selected one is painted by its own ARIA state rather than by a class
   the script maintains, so the thing a screen reader is told and the thing a
   sighted visitor sees cannot drift apart: there is one source for both. */
.emu__preset {
	position: absolute;
	top: 44px;
	right: 8px;
	display: flex;
	padding: 3px;
	border-radius: 25px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	backdrop-filter: blur(12px);
}
.emu__preset-opt {
	height: 44px;
	padding: 0 12px;
	border-radius: 22px;
	border: 0;
	background: transparent;
	color: var(--n-ink);
	font-size: 14px;
	font-weight: 700;
}
.emu__preset-opt[aria-pressed="true"] {
	background: var(--n-sel);
	color: var(--n-sel-ink);
}

/* The + / - stack, bottom right. */
.emu__zoomer {
	position: absolute;
	right: 8px;
	bottom: 8px;
	display: flex;
	flex-direction: column;
	border-radius: 16px;
	overflow: hidden;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	backdrop-filter: blur(12px);
}
.emu__zoomer-btn {
	width: 48px;
	height: 48px;
	border: 0;
	background: transparent;
	color: var(--n-ink);
	display: flex;
	align-items: center;
	justify-content: center;
}
.emu__zoomer-btn:first-child {
	border-bottom: 1px solid var(--n-glass-line);
}

/* The compass and "My crossing", bottom left. docs/maps.md gives every map a
   "My crossing" button and the HCI skill a stable default view with one tap
   back to it. */
.emu__mapleft {
	position: absolute;
	left: 8px;
	bottom: 8px;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 8px;
}
.emu__compass {
	width: 48px;
	height: 48px;
	padding: 0;
	border-radius: 24px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	backdrop-filter: blur(12px);
	color: var(--n-ink);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
}
.emu__compass-letter {
	font-size: 11px;
	font-weight: 800;
	line-height: 1.1;
}
.emu__recentre {
	display: flex;
	align-items: center;
	gap: 6px;
	height: 48px;
	padding: 0 16px;
	border-radius: 24px;
	border: 1px solid var(--n-glass-line);
	background: var(--n-glass);
	box-shadow: var(--n-shadow);
	backdrop-filter: blur(12px);
	color: var(--n-ink);
	font-size: 15px;
	font-weight: 700;
}

/* The attribution clears the + / - stack rather than sitting at the left
   edge, which is where the flagger's is. 62px = the stack's 8px inset plus
   its 48px width plus 6px of air, and it is the design's own figure.
   docs/maps.md: the Protomaps basemap is OpenStreetMap data and attribution
   is required. */
.emu__map-credit--escort {
	left: auto;
	right: 62px;
}

/* ---------------------------------------------------------------------------
   Motion
   -----------------------------------------------------------------------------
   The same rules as the flagger's: every finished state is a plain
   declaration, nothing loops, and the A/B pairs exist because re-adding a
   class an element already has does not restart a CSS animation.

   The three twelve-second runs below are the design's own: the marker rolls
   12 map units toward the crossing, the amber stretch it has left shrinks
   from 1.2308 to 1, and "You" slides 36 units. The design drives all three
   with a 12s linear animation already — this screen is where R2a's flagger
   borrowed the technique from — so these are copied rather than converted.
   ------------------------------------------------------------------------ */

/* The preset cross-fade and the scale. Cancelled whole under reduced motion,
   which leaves both states correct because both are plain declarations. */
.emu__zoom {
	transition: transform 0.7s cubic-bezier(0.3, 0.7, 0.25, 1);
}
.emu__area,
.emu__area-cover,
.emu__callout {
	transition: opacity 0.45s ease-out;
}

/* The crossing is at (150, 290) on this map where it is at (150, 200) on the
   flagger's, so the three shared transform origins are re-pointed. Each of
   these rules is one class deep, like the rule it overrides, and wins by
   coming later in the sheet. */
.emu__es-draw,
.emu__es-ripple,
.emu__es-path {
	transform-origin: 150px 290px;
}
.emu__es-roll,
.emu__es-you,
.emu__es-path {
	will-change: transform;
	backface-visibility: hidden;
}
/* Tabular figures in the callout, so the time does not shift the text beside
   it when it steps. The design's own declaration. */
.emu__es-roll text {
	font-variant-numeric: tabular-nums;
	text-rendering: geometricPrecision;
}

.emu-run-a .emu__es-roll {
	animation: emuEsRollA 12s linear both;
}
.emu-run-b .emu__es-roll {
	animation: emuEsRollB 12s linear both;
}
@keyframes emuEsRollA {
	from {
		transform: translateY(-12px);
	}
	to {
		transform: none;
	}
}
@keyframes emuEsRollB {
	from {
		transform: translateY(-12px);
	}
	to {
		transform: none;
	}
}

.emu-run-a .emu__es-path {
	animation: emuEsPathA 12s linear both;
}
.emu-run-b .emu__es-path {
	animation: emuEsPathB 12s linear both;
}
@keyframes emuEsPathA {
	from {
		transform: scaleY(1.2308);
	}
	to {
		transform: none;
	}
}
@keyframes emuEsPathB {
	from {
		transform: scaleY(1.2308);
	}
	to {
		transform: none;
	}
}

.emu-run-a .emu__es-you {
	animation: emuEsYouA 12s linear both;
}
.emu-run-b .emu__es-you {
	animation: emuEsYouB 12s linear both;
}
@keyframes emuEsYouA {
	from {
		transform: translateX(-36px);
	}
	to {
		transform: none;
	}
}
@keyframes emuEsYouB {
	from {
		transform: translateX(-36px);
	}
	to {
		transform: none;
	}
}

/* =============================================================================
   The 3D carousel — design/MainScreens-Crew.dc.html and MainScreens-Lead.dc.html
   =============================================================================
   Both designs put their posts on the same carousel: one card per post, the
   centre one flat and lifted, the rest turned away into the distance. Its
   geometry is the designs' own, and is in one place here because the two
   files share it to the decimal.

   THE DESIGNS WRITE EACH CARD'S TRANSFORM INTO A `style` ATTRIBUTE; HERE IT
   IS ARITHMETIC ON THREE NUMBERS. public/scripts/emulator.js gives each card
   its integer offset from the centre (`data-cc-d`) and gives the stage the
   drag in progress (`data-cc-dc` in 40px steps and `data-cc-df` in 2px
   steps). The rules below turn those into custom properties and then into
   the designs' formula, term for term:

     x = offset - drag / 190                 the designs' `d`
     translateX  = 172x within one card of the centre, 172 + 64 per card
                   beyond it — which is 64x + 108 * clamp(-1, x, 1)
     translateZ  = -170px * min(|x|, 2)
     rotateY     = -44deg * clamp(-1.3, x, 1.3)
     scale       = 1 - 0.06 * min(|x|, 2)
     opacity     = 1 - 0.3|x| within one card, then 0.7 down to 0 at two —
                   which is max(0, min(1 - 0.3|x|, 1.4 - 0.7|x|))
     z-index     = 100 - 10|x|, rounded, as calc() does for an integer

   |x| is max(x, -x). Nothing here writes a `style` attribute, so nothing
   depends on how a browser treats CSSOM writes under `style-src-attr`
   (docs/security.md section 7).

   MOTION. A card glides to its new place by a `transition`, and the whole
   card turns in on arrival by an `animation`, both the designs' values. Under
   `prefers-reduced-motion: reduce` site.css section 25 cancels every
   animation and transition on the page, and the block at the end of this
   section says so again for these two by name: the centre still moves when
   an arrow is pressed, and the card is simply there, unturned.
   ========================================================================== */
.emu-cc__stage {
	--cc-dc: 0;
	--cc-df: 0;
	--cc-f: calc((var(--cc-dc) * 40 + var(--cc-df) * 2) / 190);
	position: relative;
	perspective: 1000px;
	perspective-origin: 50% 45%;
	touch-action: pan-y;
	-webkit-user-select: none;
	user-select: none;
	cursor: grab;
}
.emu-cc__stage:active {
	cursor: grabbing;
}

.emu-cc__card {
	--cc-d: 0;
	--cc-x: calc(var(--cc-d) - var(--cc-f));
	--cc-a: max(var(--cc-x), -1 * var(--cc-x));
	position: absolute;
	left: 50%;
	width: 260px;
	margin-left: -130px;
	transform-style: preserve-3d;
	transform: translateX(calc((64 * var(--cc-x) + 108 * clamp(-1, var(--cc-x), 1)) * 1px))
		translateZ(calc(-170px * min(var(--cc-a), 2))) rotateY(calc(-44deg * clamp(-1.3, var(--cc-x), 1.3)))
		scale(calc(1 - 0.06 * min(var(--cc-a), 2)));
	opacity: max(0, min(1 - 0.3 * var(--cc-a), 1.4 - 0.7 * var(--cc-a)));
	z-index: calc(100 - 10 * var(--cc-a));
	transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.45s ease-out;
	will-change: transform;
}
.emu-cc__card[hidden] {
	display: none;
}
/* While a finger is down the cards follow it with no lag, as the designs set
   `transition: none` for a drag. */
.emu-cc__stage--drag .emu-cc__card {
	transition: none;
}

/* One rule per offset. Nine crossings is the most either board can hold, so
   eight either side of the centre is every offset a card can have. */
.emu-cc__card[data-cc-d="-8"] { --cc-d: -8; }
.emu-cc__card[data-cc-d="-7"] { --cc-d: -7; }
.emu-cc__card[data-cc-d="-6"] { --cc-d: -6; }
.emu-cc__card[data-cc-d="-5"] { --cc-d: -5; }
.emu-cc__card[data-cc-d="-4"] { --cc-d: -4; }
.emu-cc__card[data-cc-d="-3"] { --cc-d: -3; }
.emu-cc__card[data-cc-d="-2"] { --cc-d: -2; }
.emu-cc__card[data-cc-d="-1"] { --cc-d: -1; }
.emu-cc__card[data-cc-d="1"] { --cc-d: 1; }
.emu-cc__card[data-cc-d="2"] { --cc-d: 2; }
.emu-cc__card[data-cc-d="3"] { --cc-d: 3; }
.emu-cc__card[data-cc-d="4"] { --cc-d: 4; }
.emu-cc__card[data-cc-d="5"] { --cc-d: 5; }
.emu-cc__card[data-cc-d="6"] { --cc-d: 6; }
.emu-cc__card[data-cc-d="7"] { --cc-d: 7; }
.emu-cc__card[data-cc-d="8"] { --cc-d: 8; }
/* Beyond two cards out a card cannot be pressed, the designs' `ad > 2.2`. It
   is fully transparent from two out in any case. */
.emu-cc__card[data-cc-d="-8"],
.emu-cc__card[data-cc-d="-7"],
.emu-cc__card[data-cc-d="-6"],
.emu-cc__card[data-cc-d="-5"],
.emu-cc__card[data-cc-d="-4"],
.emu-cc__card[data-cc-d="-3"],
.emu-cc__card[data-cc-d="3"],
.emu-cc__card[data-cc-d="4"],
.emu-cc__card[data-cc-d="5"],
.emu-cc__card[data-cc-d="6"],
.emu-cc__card[data-cc-d="7"],
.emu-cc__card[data-cc-d="8"] {
	pointer-events: none;
}

/* The drag, in two parts: whole 40px steps and the 2px steps between them.
   public/scripts/emulator.js keeps the drag between -400px and 398px, which
   is every value these rules cover. */
.emu-cc__stage[data-cc-dc="-10"] { --cc-dc: -10; }
.emu-cc__stage[data-cc-dc="-9"] { --cc-dc: -9; }
.emu-cc__stage[data-cc-dc="-8"] { --cc-dc: -8; }
.emu-cc__stage[data-cc-dc="-7"] { --cc-dc: -7; }
.emu-cc__stage[data-cc-dc="-6"] { --cc-dc: -6; }
.emu-cc__stage[data-cc-dc="-5"] { --cc-dc: -5; }
.emu-cc__stage[data-cc-dc="-4"] { --cc-dc: -4; }
.emu-cc__stage[data-cc-dc="-3"] { --cc-dc: -3; }
.emu-cc__stage[data-cc-dc="-2"] { --cc-dc: -2; }
.emu-cc__stage[data-cc-dc="-1"] { --cc-dc: -1; }
.emu-cc__stage[data-cc-dc="1"] { --cc-dc: 1; }
.emu-cc__stage[data-cc-dc="2"] { --cc-dc: 2; }
.emu-cc__stage[data-cc-dc="3"] { --cc-dc: 3; }
.emu-cc__stage[data-cc-dc="4"] { --cc-dc: 4; }
.emu-cc__stage[data-cc-dc="5"] { --cc-dc: 5; }
.emu-cc__stage[data-cc-dc="6"] { --cc-dc: 6; }
.emu-cc__stage[data-cc-dc="7"] { --cc-dc: 7; }
.emu-cc__stage[data-cc-dc="8"] { --cc-dc: 8; }
.emu-cc__stage[data-cc-dc="9"] { --cc-dc: 9; }
.emu-cc__stage[data-cc-df="1"] { --cc-df: 1; }
.emu-cc__stage[data-cc-df="2"] { --cc-df: 2; }
.emu-cc__stage[data-cc-df="3"] { --cc-df: 3; }
.emu-cc__stage[data-cc-df="4"] { --cc-df: 4; }
.emu-cc__stage[data-cc-df="5"] { --cc-df: 5; }
.emu-cc__stage[data-cc-df="6"] { --cc-df: 6; }
.emu-cc__stage[data-cc-df="7"] { --cc-df: 7; }
.emu-cc__stage[data-cc-df="8"] { --cc-df: 8; }
.emu-cc__stage[data-cc-df="9"] { --cc-df: 9; }
.emu-cc__stage[data-cc-df="10"] { --cc-df: 10; }
.emu-cc__stage[data-cc-df="11"] { --cc-df: 11; }
.emu-cc__stage[data-cc-df="12"] { --cc-df: 12; }
.emu-cc__stage[data-cc-df="13"] { --cc-df: 13; }
.emu-cc__stage[data-cc-df="14"] { --cc-df: 14; }
.emu-cc__stage[data-cc-df="15"] { --cc-df: 15; }
.emu-cc__stage[data-cc-df="16"] { --cc-df: 16; }
.emu-cc__stage[data-cc-df="17"] { --cc-df: 17; }
.emu-cc__stage[data-cc-df="18"] { --cc-df: 18; }
.emu-cc__stage[data-cc-df="19"] { --cc-df: 19; }

/* The card's face. The centre card carries the deeper lift, the rest the
   softer row shadow — the designs' `center ? c.lift : c.rowLift`. */
.emu-cc__in {
	width: 100%;
	height: 100%;
	box-sizing: border-box;
	background: var(--n-surface);
	box-shadow: var(--n-row-lift);
	display: flex;
	flex-direction: column;
	overflow: hidden;
}
.emu-cc__card--centre .emu-cc__in {
	box-shadow: var(--n-lift);
}

/* The arrival: each card swings in from the right, the nearest first. The
   delay is the designs' `0.2s + 0.07s` per card from the centre (0.25s on
   the lead channel), read from the same offset the position uses. A/B so a
   replay restarts it. */
.emu-run-a .emu-cc__in {
	animation: emuCcInA 0.75s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
	animation-delay: calc(var(--cc-dl, 0.2s) + 0.07s * max(var(--cc-d), -1 * var(--cc-d)));
}
.emu-run-b .emu-cc__in {
	animation: emuCcInB 0.75s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
	animation-delay: calc(var(--cc-dl, 0.2s) + 0.07s * max(var(--cc-d), -1 * var(--cc-d)));
}
@keyframes emuCcInA {
	from {
		opacity: 0;
		transform: translateX(120px) rotateY(-35deg) scale(0.88);
	}
	to {
		opacity: 1;
		transform: none;
	}
}
@keyframes emuCcInB {
	from {
		opacity: 0;
		transform: translateX(120px) rotateY(-35deg) scale(0.88);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* The arrows, the dots and the "N of M" line under the stage. */
.emu-cc__nav {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	padding: 2px 16px 0 16px;
}
.emu-cc__arrow {
	flex-shrink: 0;
	width: 44px;
	height: 44px;
	border-radius: 22px;
	border: 0;
	background: var(--n-surface);
	box-shadow: var(--n-row-lift);
	color: var(--n-ink);
	display: flex;
	align-items: center;
	justify-content: center;
}
/* Dimmed at the end of the row, as drawn; still a button and still in the
   tab order, so focus is not thrown off it. */
.emu-cc__arrow[aria-disabled="true"] {
	opacity: 0.4;
}
.emu-cc__where {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
}
.emu-cc__dots {
	display: flex;
	align-items: center;
	gap: 6px;
}
.emu-cc__dot {
	width: 8px;
	height: 8px;
	box-sizing: border-box;
	border-radius: 4px;
	background: var(--n-line);
	border: 0;
	transition: width 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), background-color 0.3s ease-out;
}
.emu-cc__dot[hidden] {
	display: none;
}
.emu-cc__dot--on {
	width: 22px;
	background: var(--n-brand);
}
/* A post somebody is talking on, on the lead channel. */
.emu-cc__dot--live {
	background: var(--n-talk-ring);
}
.emu-cc__pos {
	font-size: 12px;
	font-weight: 700;
	color: var(--n-muted);
	font-variant-numeric: tabular-nums;
}

/* Said again by name, though site.css section 25 already does it for the
   whole page: the turn and the arrival are motion, and a visitor who asked
   for less gets the card where it ends up. */
@media (prefers-reduced-motion: reduce) {
	.emu-cc__card,
	.emu-cc__dot {
		transition: none !important;
	}
	.emu-cc__in {
		animation: none !important;
	}
}

/* The two pulses the designs loop for ever: `hbPulse` on the crew board's
   "Live" dot and `crPulse` on every open-end ring, built as `infinite` as
   drawn (Dylan, Oct 28, 2026; docs/ui-rules.md, which names both). The dash,
   not the pulse, is what marks an open end (docs/alerts.md), and the dash is
   there with every animation cancelled, as it is under reduced motion. */
.emu-live-pulse {
	transform-origin: center;
	animation: emuLivePulse 2.4s ease-in-out infinite;
}
@keyframes emuLivePulse {
	0%,
	100% {
		opacity: 1;
		transform: scale(1);
	}
	50% {
		opacity: 0.45;
		transform: scale(0.8);
	}
}
.emu-open-pulse {
	transform-box: fill-box;
	transform-origin: center;
	animation: emuOpenPulse 1.6s ease-in-out infinite;
}
@keyframes emuOpenPulse {
	0%,
	100% {
		opacity: 1;
		transform: scale(1);
	}
	50% {
		opacity: 0.35;
		transform: scale(0.75);
	}
}

/* A number that changes pops as it does, the designs' `cm-cnt`. Played by
   the engine adding the class on the tick the number changes. */
.emu-pop {
	display: inline-block;
	animation: emuVmCount 0.45s cubic-bezier(0.3, 1.4, 0.5, 1);
}

/* =============================================================================
   The crew board — apps/site/design/MainScreens-Crew.dc.html
   =============================================================================
   One card per crossing on the carousel above, open posts first, and the
   crossing sheet from MainScreens-Crew-Sheet.dc.html. Everything shared — the
   screen root, the header, the compact alert row, the verify line, Repeat,
   hold-to-talk and the tab bar — is reused rather than restated.

   NO HOURS, NO HISTORY, ANYWHERE — docs/voice.md ("Not a timekeeping app: no
   clock-in/out, and never store assignments as a record of hours worked").
   Nothing in this screen carries a time, a duration, a shift or a previous
   occupant of a post, and the design says so on screen: "Shows who is on
   each post now. No history, no hours."

   AMBER IS THE UNSTAFFED END, AND IT IS DASHED. docs/alerts.md allows amber
   for an aircraft, and its "one other use is a dashed, pulsing amber outline
   or dot for an unstaffed crossing end on crew screens ... never solid
   amber". Every open-end marker here is dashed: an open card's border and
   chip (`2px dashed`), the rings on a card's empty end (`6 5`), the strip's
   dot (`2.5 2`) and the badge in the detail sheet (`3 2.5`).

   TWO SOLID-AMBER USES THE DESIGNS DRAW ARE AN OPEN QUESTION FOR DYLAN, not
   a decision taken here: the "1 OPEN" tab-bar badge (`--n-badge`, a solid
   fill for an open-post count) and "End board…"'s `2px solid` amber border (a
   destructive action, neither an aircraft nor an open end). Both are built
   exactly as drawn and named in src/html/crew-board.test.ts, because
   changing an approved design on a reading of a rule is not this build's
   call. A third was resolved: the carousel dot for a crossing with an open
   end, which MainScreens-Crew.dc.html draws as a `2px solid` amber ring, is
   built `2px dashed` (Dylan, Oct 28, 2026; docs/web-app.md), like every other
   open-end marker here. The "ALERT" badge is not in question: docs/alerts.md asks for an
   "ALERT/OFFLINE word badge on the Crossing tab" by name.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   The status strip
   -----------------------------------------------------------------------------
   `.emu__status`'s, with this design's own 6px bottom margin. The third cell
   is a button here, where the design draws `<a href="#">`: it moves the
   carousel to the first open post and goes nowhere. It keeps the cell's look.
   ------------------------------------------------------------------------ */
.emu__status--crew {
	margin-bottom: 6px;
}
/* The third cell is the only one the design weights at 800, because an open
   post is the one thing on this strip that needs doing. */
.emu__status-cell--open {
	font-weight: 800;
}
/* The design draws the cell about 37px tall, and as a control it now falls
   under docs/ui-rules.md's 44px floor. The target is raised to 44px and the
   extra is given back as negative margin, so the strip keeps its drawn height
   — the floor's "extra spent on transparent padding" — rather than every cell
   growing with it. */
.emu-cr__jump {
	min-height: 44px;
	margin: -4px 0;
	border: 0;
	background: transparent;
	color: var(--n-ink);
	font-size: 14px;
	text-decoration: none;
}
/* The strip's own "N open" dot, which has no staffed state to swap to. */
.emu-cr__stat-dot {
	fill: none;
	stroke: var(--n-amber-line);
	stroke-dasharray: 2.5 2;
}

/* ---------------------------------------------------------------------------
   "Posts · N crossings · open first" and "Add post"
   -----------------------------------------------------------------------------
   44px, not the design's 36px. Dylan was shown the measurement on the
   earlier board and chose the floor over the drawing ("build the buttons at
   44px", Oct 28, 2026; docs/ui-rules.md). MainScreens-Crew.dc.html draws it
   at 36px again, and is not edited.
   ------------------------------------------------------------------------ */
.emu-cr__posts {
	padding: 4px 16px 0 20px;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
}
.emu-cr__postslabel {
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-cr__add {
	height: 44px;
	padding: 0 12px;
	border-radius: 18px;
	border: 0;
	background: var(--n-chip);
	color: var(--n-brand);
	display: flex;
	align-items: center;
	gap: 4px;
	font-size: 14px;
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
   One crossing's card
   ------------------------------------------------------------------------ */
.emu-cr__stage {
	height: 292px;
	margin-top: 4px;
}
.emu-cr__card {
	top: 8px;
	height: 276px;
	border-radius: 26px;
}
.emu-cr__face {
	gap: 8px;
	padding: 16px 16px 14px 16px;
	border-radius: 26px;
	border: 1px solid var(--n-line);
}
/* An open post. Dashed, never solid (docs/alerts.md). */
.emu-cr__card--open .emu-cr__face {
	border: 2px dashed var(--n-amber-line);
}
/* Crossing D flashes once when Pat fills its end, the design's `cm-flash`.
   An outline, so no layout box moves; brand blue, never amber. */
.emu-cr__card--flash {
	animation: emuCrFlash 1.1s ease-out;
}
@keyframes emuCrFlash {
	0% {
		outline: 3px solid var(--n-brand);
		outline-offset: 0;
	}
	100% {
		outline: 3px solid transparent;
		outline-offset: 10px;
	}
}

.emu-cr__cardhead {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 8px;
}
.emu-cr__cardtitle {
	display: flex;
	flex-direction: column;
	min-width: 0;
}
.emu-cr__cardname {
	font-size: 22px;
	font-weight: 800;
	line-height: 1.15;
}
.emu-cr__carddir {
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-cr__chip {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	gap: 5px;
	height: 28px;
	padding: 0 10px;
	box-sizing: border-box;
	border-radius: 14px;
	background: var(--n-chip);
	border: 0;
	color: var(--n-ink);
	font-size: 13px;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}
.emu-cr__card--open .emu-cr__chip {
	background: transparent;
	border: 2px dashed var(--n-amber-line);
}

/* The two ends and the taxiway between them. A north–south crossing stacks
   its ends with the taxiway running down between them; a west–east one sets
   them side by side with the taxiway across. Fixed per crossing, so it is a
   class in the file and nothing writes it. */
.emu-cr__lane {
	flex: 1;
	display: grid;
	padding: 2px 0;
}
.emu-cr__lane--ns {
	grid-template-columns: 48px minmax(0, 1fr);
	grid-template-rows: 48px 1fr 48px;
	column-gap: 12px;
	align-items: center;
}
.emu-cr__lane--we {
	grid-template-columns: minmax(0, 1fr) 36px minmax(0, 1fr);
	grid-template-rows: 48px auto;
	row-gap: 8px;
	align-content: center;
	justify-items: center;
}
.emu-cr__mark {
	position: relative;
	width: 48px;
	height: 48px;
}
.emu-cr__who {
	display: flex;
	flex-direction: column;
	line-height: 1.2;
	min-width: 0;
}
.emu-cr__lane--ns .emu-cr__mark--a,
.emu-cr__lane--ns .emu-cr__who--a {
	grid-row: 1;
}
.emu-cr__lane--ns .emu-cr__mark--b,
.emu-cr__lane--ns .emu-cr__who--b {
	grid-row: 3;
}
.emu-cr__lane--ns .emu-cr__mark {
	grid-column: 1;
}
.emu-cr__lane--ns .emu-cr__who {
	grid-column: 2;
}
.emu-cr__lane--we .emu-cr__mark {
	grid-row: 1;
}
.emu-cr__lane--we .emu-cr__who {
	grid-row: 2;
	align-items: center;
	text-align: center;
}
.emu-cr__lane--we .emu-cr__mark--a,
.emu-cr__lane--we .emu-cr__who--a {
	grid-column: 1;
}
.emu-cr__lane--we .emu-cr__mark--b,
.emu-cr__lane--we .emu-cr__who--b {
	grid-column: 3;
}
.emu-cr__wholabel {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--n-muted);
}
.emu-cr__whoname {
	font-size: 17px;
	font-weight: 800;
	color: var(--n-ink);
}
.emu-cr__whoname--open {
	color: var(--n-muted);
}
/* The person on the end, as their initial on a brand disc; it pops in when
   somebody is put there (`.emu-vm__pop`, the design's `vm-pop`). */
.emu-cr__av {
	position: absolute;
	inset: 0;
	border-radius: 24px;
	background: var(--n-brand);
	color: var(--n-live-ink);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 19px;
	font-weight: 800;
	box-shadow: 0 6px 14px rgba(4, 97, 248, 0.3);
}
.emu-cr__av[hidden],
.emu-cr__openmark[hidden] {
	display: none;
}
.emu-cr__openmark {
	position: absolute;
	inset: 0;
}
/* An empty end. Dashed, for the reason at the top of this section. */
.emu-cr__openring {
	fill: var(--n-surface);
	stroke: var(--n-amber-line);
	stroke-dasharray: 6 5;
}
.emu-cr__openplus {
	fill: none;
	stroke: var(--n-muted);
}
/* The taxiway: a chip-coloured strip with the centreline dashed down or
   across it, drawn as a repeating background exactly as the design does. */
.emu-cr__road {
	border-radius: 5px;
	background-color: var(--n-chip);
	background-repeat: no-repeat;
}
.emu-cr__lane--ns .emu-cr__road {
	grid-column: 1;
	grid-row: 2;
	justify-self: center;
	align-self: stretch;
	width: 10px;
	margin: 4px 0;
	background-image: linear-gradient(var(--n-line), var(--n-line));
	background-size: 2px 8px;
	background-repeat: repeat-y;
	background-position: center;
}
.emu-cr__lane--we .emu-cr__road {
	grid-column: 2;
	grid-row: 1;
	align-self: center;
	justify-self: stretch;
	height: 10px;
	background-image: linear-gradient(90deg, var(--n-line), var(--n-line));
	background-size: 8px 2px;
	background-repeat: repeat-x;
	background-position: center;
}
.emu-cr__roadtext {
	font-size: 12px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-cr__lane--ns .emu-cr__roadtext {
	grid-column: 2;
	grid-row: 2;
}
.emu-cr__lane--we + .emu-cr__roadtext {
	text-align: center;
}

/* "Assign someone" on an open post, "Details" on a staffed one. 48px, as
   drawn. */
.emu-cr__act {
	flex-shrink: 0;
	height: 48px;
	border-radius: 16px;
	border: 0;
	background: var(--n-chip);
	color: var(--n-brand);
	box-shadow: none;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	font-size: 16px;
	font-weight: 800;
}
.emu-cr__card--open .emu-cr__act {
	background: var(--n-brand);
	color: var(--n-live-ink);
	box-shadow: 0 10px 24px rgba(4, 97, 248, 0.35), 0 2px 6px rgba(2, 17, 58, 0.15);
}

/* The dot under the carousel for a crossing with an open end. DASHED, which is
   not as drawn: MainScreens-Crew.dc.html gives it a `2px solid` amber ring,
   and docs/alerts.md says an unstaffed end is never solid amber. Dylan chose
   the dash (Oct 28, 2026; docs/web-app.md). */
.emu-cr .emu-cc__dot--open {
	background: transparent;
	border: 2px dashed var(--n-amber-line);
}

/* ---------------------------------------------------------------------------
   The two buttons under the carousel, and the no-history line
   ------------------------------------------------------------------------ */
.emu-cr__tail {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px;
	padding: 10px 16px 0 16px;
}
.emu-cr__tail-btn {
	height: 46px;
	border-radius: 16px;
	border: 1.5px solid var(--n-btn-line);
	background: var(--n-surface);
	color: var(--n-ink);
	font-size: 15px;
	font-weight: 800;
}
/* Ending the board is the one destructive thing on this screen, and the design
   marks it out rather than leaving it looking like the button beside it — the
   HCI skill's "make rare risky actions look different from routine ones"
   (ch. 41). Its amber is the open question named in this section's header. */
.emu-cr__tail-btn--end {
	border: 2px solid var(--n-amber-line);
	background: transparent;
}
.emu-cr__note {
	margin: 6px 16px 0 16px;
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
	text-align: center;
}

/* The spacer that pushes the alert row and hold-to-talk to the bottom. The
   design's own `flex-grow: 1; min-height: 8px`. */
.emu-cr__grow {
	flex-grow: 1;
	min-height: 8px;
}

/* ---------------------------------------------------------------------------
   The tab bar's word badges
   -----------------------------------------------------------------------------
   Only this design draws them. Each is `aria-hidden`: the tab it sits on
   already carries its own name, and a badge read out as a separate word
   ("Crossing, ALERT") says nothing the status strip above has not already said
   in a sentence.
   ------------------------------------------------------------------------ */
.emu-cr__tabbadge {
	position: absolute;
	top: 0;
	left: calc(50% + 6px);
	padding: 1px 5px;
	border-radius: 9px;
	background: var(--n-badge);
	color: var(--n-badge-ink);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 0.04em;
	line-height: 1.4;
}
.emu-cr__tabbadge--live {
	background: var(--n-live);
	color: var(--n-live-ink);
}

/* ---------------------------------------------------------------------------
   The two modal sheets
   -----------------------------------------------------------------------------
   A sheet is a layer over the whole screen: a dimmed backdrop that is itself
   the Close button, and the sheet sitting on the bottom edge. The design draws
   both the same way.

   THE DESIGN GIVES NO FOCUS CUE AT ALL — it contains no `inert`, no `tabindex`
   and no `focus` — so the contract is the WAI-ARIA Authoring Practices modal
   dialog pattern, which is what `role="dialog"` in the design names:
   `aria-modal="true"`, focus moved into the sheet on open, Escape closes it,
   focus returns to the control that opened it, and the rest of the screen is
   unreachable while it is open. That last part is `inert`, which the engine
   puts on every other child of the screen root — MDN: an inert element and its
   descendants cannot be focused, fire no click events, and are excluded from
   the accessibility tree. The backdrop supplies the APG's other condition,
   that the content behind is visually obscured.

   `inert` on the siblings rather than on one wrapper, because the screen root
   staggers its children's entrance by `nth-child` and a wrapper would collapse
   that to one.
   ------------------------------------------------------------------------ */
.emu-cr__layer {
	position: absolute;
	inset: 0;
	z-index: 10;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
}
.emu-cr__layer[hidden] {
	display: none;
}
/* The backdrop IS the Close control, which is the design's own choice and a
   good one: the gesture a visitor already makes to dismiss a sheet is tapping
   away from it, and this makes that gesture the button rather than a dead
   area. `rgba(2,17,58,0.35)` flat in both themes — the design's value, navy at
   35%, not a themed surface. */
.emu-cr__scrim {
	position: absolute;
	inset: 0;
	border: 0;
	background: rgba(2, 17, 58, 0.35);
}
.emu-cr__sheet {
	position: relative;
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	background: var(--n-bg);
}
/* The grabber. Decorative — the sheet is not draggable here and the design
   does not make it one, so it says nothing and is hidden from assistive
   technology. */
.emu-cr__grab {
	align-self: center;
	width: 40px;
	height: 5px;
	border-radius: 3px;
	background: var(--n-line);
}
/* The heading takes focus when the sheet opens, so what a screen reader hears
   first is which sheet this is rather than the first button in it. `tabindex`
   is -1: focusable by script, never a tab stop of its own (the APG's "focus
   moves to an element inside the dialog", with the choice of element left to
   the content). It needs a visible ring of its own, because a heading has no
   focus style from the browser. */
.emu-cr__sheet-title:focus-visible {
	outline: 2px solid var(--n-brand);
	outline-offset: 4px;
}

/* --- the "Add a post" sheet ------------------------------------------- */
.emu-cr__sheet--add {
	gap: 10px;
	padding: 8px 16px 20px 16px;
	border-radius: 24px 24px 0 0;
}
.emu-cr__addhead {
	display: flex;
	align-items: center;
	justify-content: space-between;
}
.emu-cr__addtitle {
	font-size: 20px;
	font-weight: 800;
}
.emu-cr__done {
	height: 44px;
	padding: 0 12px;
	border-radius: 18px;
	border: 0;
	background: var(--n-chip);
	color: var(--n-ink);
	font-size: 14px;
	font-weight: 700;
}
.emu-cr__sheet-sub {
	font-size: 13px;
	font-weight: 500;
	color: var(--n-muted);
	line-height: 1.35;
}
.emu-cr__sheet-foot {
	font-size: 12px;
	font-weight: 500;
	color: var(--n-muted);
	line-height: 1.35;
}
.emu-cr__addlist {
	list-style: none;
	margin: 0;
	padding: 0;
	border-radius: 18px;
	border: 1px solid var(--n-line);
	background: var(--n-surface);
	overflow: hidden;
}
.emu-cr__addlist li[hidden] {
	display: none;
}
.emu-cr__addopt {
	width: 100%;
	box-sizing: border-box;
	min-height: 52px;
	padding: 6px 12px;
	border: 0;
	border-bottom: 1px solid var(--n-line);
	background: transparent;
	color: var(--n-ink);
	display: flex;
	align-items: center;
	gap: 12px;
	text-align: left;
	font: inherit;
}
.emu-cr__addopt-text {
	flex: 1;
	display: flex;
	flex-direction: column;
	line-height: 1.2;
}
.emu-cr__addopt-name {
	font-size: 16px;
	font-weight: 700;
}
.emu-cr__addopt-sub {
	font-size: 13px;
	font-weight: 500;
	color: var(--n-muted);
}
.emu-cr__addopt-plus {
	display: flex;
	color: var(--n-brand);
}

/* --- the crossing detail sheet ---------------------------------------- */
.emu-cr__sheet--detail {
	gap: 12px;
	padding: 8px 16px 24px 16px;
	border-radius: 28px 28px 0 0;
	box-shadow: 0 -8px 30px rgba(2, 17, 58, 0.18);
}
.emu-cr__dethead {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 12px;
}
.emu-cr__dettext {
	display: flex;
	flex-direction: column;
	line-height: 1.2;
}
.emu-cr__dettitle {
	font-size: 24px;
	font-weight: 800;
}
.emu-cr__detsub {
	font-size: 14px;
	font-weight: 500;
	color: var(--n-muted);
}
.emu-cr__detdone {
	flex-shrink: 0;
	height: 44px;
	padding: 0 14px;
	border-radius: 18px;
	border: 0;
	background: var(--n-chip);
	color: var(--n-ink);
	font-size: 15px;
	font-weight: 700;
}
.emu-cr__ends {
	list-style: none;
	margin: 0;
	padding: 0;
	border-radius: 18px;
	background: var(--n-surface);
	box-shadow: var(--n-row-lift);
	overflow: hidden;
}
.emu-cr__end {
	min-height: 56px;
	padding: 8px 10px 8px 14px;
	border-bottom: 1px solid var(--n-line);
	display: flex;
	align-items: center;
	gap: 12px;
}
/* The person on this end, as their initial on a brand disc. 36px and NOT
   raised to 44: it is a badge, not a control, and Dylan's 44px authorisation
   covers four named buttons and nothing else (docs/ui-rules.md). */
.emu-cr__ini {
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	border-radius: 18px;
	background: var(--n-brand);
	color: var(--n-on-brand);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 14px;
	font-weight: 800;
}
.emu-cr__ini[hidden],
.emu-cr__endopen[hidden],
.emu-cr__endwho[hidden],
.emu-cr__off[hidden] {
	display: none;
}
.emu-cr__endopen {
	flex-shrink: 0;
}
.emu-cr__end-wash {
	fill: var(--n-amber-tint);
}
/* Dashed, for the same reason every other open-end marker here is. */
.emu-cr__end-ring {
	fill: none;
	stroke: var(--n-amber-line);
	stroke-dasharray: 3 2.5;
}
.emu-cr__endtext {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	line-height: 1.2;
}
.emu-cr__endlabel {
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-cr__endwho {
	font-size: 17px;
	font-weight: 700;
}
.emu-cr__off {
	flex-shrink: 0;
	height: 44px;
	padding: 0 12px;
	border-radius: 18px;
	border: 0;
	background: var(--n-chip);
	color: var(--n-ink);
	font-size: 14px;
	font-weight: 700;
}
.emu-cr__freelabel {
	padding: 0 4px;
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-cr__freelabel[hidden],
.emu-cr__free[hidden],
.emu-cr__pick[hidden] {
	display: none;
}
.emu-cr__free {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px;
}
.emu-cr__pick {
	min-height: 52px;
	padding: 6px 10px;
	border-radius: 16px;
	border: 0;
	background: var(--n-surface);
	box-shadow: var(--n-row-lift);
	color: var(--n-ink);
	display: flex;
	align-items: center;
	gap: 10px;
	text-align: left;
	font: inherit;
}
.emu-cr__pick-ini {
	flex-shrink: 0;
	width: 32px;
	height: 32px;
	border-radius: 16px;
	background: var(--n-chip);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 13px;
	font-weight: 800;
}
.emu-cr__pick-name {
	font-size: 16px;
	font-weight: 700;
}
/* Taking a crossing off the board. 48px, as drawn. */
.emu-cr__drop {
	height: 48px;
	border-radius: 16px;
	border: 0;
	background: transparent;
	color: var(--n-brand);
	font-size: 16px;
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
   Motion
   -----------------------------------------------------------------------------
   The same rules as the other two screens: every finished state is a plain
   declaration, and nothing loops.

   Both sheets arrive as the design draws them: the backdrop fades and the
   sheet slides up from the bottom edge. Each plays ONCE, when the sheet is
   first displayed, and both end at the element's natural state — so cancelling
   them under reduced motion leaves an open sheet open and a dimmed backdrop
   dim, which is the property that matters. No A/B pair is needed here, unlike
   the run animations: a sheet goes from `display: none` to displayed, and an
   animation on an element that was not being rendered starts when it is.
   ------------------------------------------------------------------------ */
.emu-cr__add,
.emu-cr__done,
.emu-cr__detdone,
.emu-cr__off,
.emu-cr__pick,
.emu-cr__addopt,
.emu-cr__tail-btn,
.emu-cr__drop {
	transition: transform 0.12s ease-out, background-color 0.2s ease-out;
}
.emu-cr__add:active,
.emu-cr__done:active,
.emu-cr__detdone:active,
.emu-cr__off:active,
.emu-cr__pick:active,
.emu-cr__addopt:active,
.emu-cr__tail-btn:active,
.emu-cr__drop:active {
	transform: scale(0.94);
}

.emu-cr__scrim {
	animation: emuCrFade 0.25s ease-out both;
}
@keyframes emuCrFade {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}
.emu-cr__sheet {
	animation: emuCrSheet 0.32s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes emuCrSheet {
	from {
		transform: translateY(100%);
	}
	to {
		transform: none;
	}
}

/* =============================================================================
   The voice room — apps/site/design/EmuVoice.dc.html
   =============================================================================
   Task R4b, and the fourth of the Screens page's seven tabs. Everything above
   that is shared — the screen root and its entrance stagger, the header, the
   compact alert row, the verify line, Repeat, hold-to-talk, the tab bar and
   its word badges — is reused rather than restated, and only what this design
   draws differently is here.

   NO NEW PALETTE TOKENS. Every colour this screen reaches is already a
   `--n-*` property on `.emu`: the pill is `--n-ptt` at rest and
   `--n-ptt-talk` while the button is held, the level bars are `--n-talk-ring`
   and the rest is surface, line, chip, brand and muted. That matters
   mechanically as well as tidily — src/html/emu-palette.test.ts reads the
   palette by taking every rule whose selector ENDS IN `.emu` and expects
   exactly three such blocks, so a token added here would have had to be added
   to five blocks at once, and no selector below may end in `.emu`.

   THE BARS RUN A FINITE COUNT. The design draws them `infinite`; Dylan ruled
   against that on Oct 28, 2026 ("no loop on the bars") and docs/ui-rules.md
   records it with the four looping animations that ARE approved. The shared
   `.emu-bar` above already runs 12 iterations under `.emu--talking`; these
   are a second set, in the pill and in the North-end card, which are talking
   whether or not the visitor is holding anything — so they are started by the
   run class instead, which is what lets them replay when the tab is reopened.

   THE WHITE ON THE PILL IS A LITERAL AND NOT A TOKEN, exactly as the design
   writes it (`pillInk: '#FFFFFF'`, `avInk: '#FFFFFF'`, in both themes). It is
   not `--n-on-brand`, which is #02113a at night — the pill is navy or brand
   blue in both themes and its text is white in both.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   Now talking
   ------------------------------------------------------------------------ */
.emu-vm__pill {
	margin: 0 16px;
	padding: 6px 6px 6px 8px;
	border-radius: 34px;
	background: var(--n-ptt);
	color: #ffffff;
	display: flex;
	align-items: center;
	gap: 10px;
	transition: background-color 0.25s ease-out;
}
/* Held: the room is hearing you instead of Chris, and the pill says so in
   colour as well as in words. */
.emu--talking .emu-vm__pill {
	background: var(--n-ptt-talk);
}
.emu-vm__pill-ini {
	flex-shrink: 0;
	width: 44px;
	height: 44px;
	border-radius: 22px;
	background: var(--n-brand);
	color: #ffffff;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 18px;
	font-weight: 800;
}
.emu--talking .emu-vm__pill-ini {
	background: var(--n-ptt-talk);
}
.emu-vm__pill-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	line-height: 1.2;
}
.emu-vm__pill-name {
	font-size: 18px;
	font-weight: 700;
}
.emu-vm__pill-sub {
	font-size: 13px;
	font-weight: 500;
	opacity: 0.8;
}
.emu-vm__pill-bars {
	display: flex;
	align-items: center;
	gap: 3px;
	height: 24px;
	flex-shrink: 0;
	color: var(--n-talk-ring);
}
.emu-vm__replay {
	flex-shrink: 0;
	width: 52px;
	height: 52px;
	border-radius: 26px;
	border: 0;
	background: rgba(255, 255, 255, 0.14);
	color: inherit;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* The two sets of bars this screen adds. `.emu-bar` above is hold-to-talk's,
   sized for a 26px button and animated only while it is held; these are the
   design's own smaller sets, and they move because somebody else is talking.
   Heights are per-position, as in the drawing. */
.emu-vm__bar {
	display: inline-block;
	border-radius: 2px;
	background: currentColor;
	transform-origin: center;
}
.emu-vm__pill-bars .emu-vm__bar {
	width: 4px;
}
.emu-vm__pill-bars .emu-vm__bar:nth-child(1) {
	height: 12px;
}
.emu-vm__pill-bars .emu-vm__bar:nth-child(2) {
	height: 24px;
}
.emu-vm__pill-bars .emu-vm__bar:nth-child(3) {
	height: 16px;
}
.emu-vm__pill-bars .emu-vm__bar:nth-child(4) {
	height: 21px;
}
.emu-vm__pill-bars .emu-vm__bar:nth-child(5) {
	height: 10px;
}
.emu-vm__bars-sm {
	display: flex;
	align-items: center;
	gap: 2px;
	height: 14px;
}
.emu-vm__bars-sm .emu-vm__bar {
	width: 3px;
}
.emu-vm__bars-sm .emu-vm__bar:nth-child(1) {
	height: 7px;
}
.emu-vm__bars-sm .emu-vm__bar:nth-child(2) {
	height: 14px;
}
.emu-vm__bars-sm .emu-vm__bar:nth-child(3) {
	height: 9px;
}
.emu-vm__bars-sm .emu-vm__bar:nth-child(4) {
	height: 12px;
}
.emu-vm__bars-sm .emu-vm__bar:nth-child(5) {
	height: 6px;
}

/* Started by the run class, so re-entering the tab replays them: the engine
   removes both run classes, forces a reflow and adds one back, which is the
   only thing that restarts an animation on an element that was never removed
   from the document. Twelve iterations of the longest is 13.2s, longer than
   anyone looks at one tab, so what a visitor sees is the design. */
.emu-run-a .emu-vm__bar,
.emu-run-b .emu-vm__bar {
	animation: emuBar 0.9s ease-in-out 12;
}
.emu-run-a .emu-vm__bar:nth-child(2),
.emu-run-b .emu-vm__bar:nth-child(2) {
	animation-duration: 0.7s;
	animation-delay: -0.2s;
}
.emu-run-a .emu-vm__bar:nth-child(3),
.emu-run-b .emu-vm__bar:nth-child(3) {
	animation-duration: 1.1s;
	animation-delay: -0.5s;
}
.emu-run-a .emu-vm__bar:nth-child(4),
.emu-run-b .emu-vm__bar:nth-child(4) {
	animation-duration: 0.8s;
	animation-delay: -0.1s;
}
.emu-run-a .emu-vm__bar:nth-child(5),
.emu-run-b .emu-vm__bar:nth-child(5) {
	animation-duration: 1s;
	animation-delay: -0.35s;
}
/* Where they rest when the animation is cancelled — the design's own
   reduced-motion value. Section 25 of site.css does the cancelling. */
@media (prefers-reduced-motion: reduce) {
	.emu-vm__bar {
		transform: scaleY(0.7);
	}
}

/* ---------------------------------------------------------------------------
   Who hears you
   ------------------------------------------------------------------------ */
.emu-vm__heard {
	margin: 10px 16px 0 16px;
	padding: 8px 12px;
	border-radius: 18px;
	background: var(--n-surface);
	border: 1px solid var(--n-line);
	display: flex;
	align-items: center;
	gap: 10px;
}
.emu-vm__faces {
	display: flex;
	flex-shrink: 0;
}
.emu-vm__face {
	box-sizing: border-box;
	width: 30px;
	height: 30px;
	border-radius: 15px;
	margin-left: -9px;
	background: var(--n-brand);
	color: var(--n-on-brand);
	border: 2px solid var(--n-surface);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 12px;
	font-weight: 800;
}
/* `display: flex` above would beat the browser's own `[hidden]` rule, and the
   escort who has not arrived yet would be on the strip from the first frame.
   This is the trap site.css carries nine overrides for. */
.emu-vm__face[hidden] {
	display: none;
}
.emu-vm__face:first-child {
	margin-left: 0;
}
/* You, set apart from the room by a gap and by the hold-to-talk navy. */
.emu-vm__face--me {
	margin-left: 6px;
	background: var(--n-ptt);
	color: #ffffff;
}
.emu-vm__heard-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	line-height: 1.2;
}
.emu-vm__heard-label {
	font-size: 12px;
	font-weight: 500;
	color: var(--n-muted);
}
.emu-vm__heard-val {
	font-size: 15px;
	font-weight: 700;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.emu-vm__chip {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 3px;
	padding: 5px 8px;
	border-radius: 12px;
	background: var(--n-chip);
	font-size: 12px;
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
   The two ends of the crossing
   ------------------------------------------------------------------------ */
.emu-vm__ends {
	margin: 14px 16px 0 16px;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 10px;
}
.emu-vm__end {
	min-width: 0;
	padding: 12px;
	border-radius: 18px;
	background: var(--n-surface);
	border: 1px solid var(--n-line);
	display: flex;
	flex-direction: column;
	gap: 8px;
}
.emu-vm__end-label {
	font-size: 12px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-vm__end-who {
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
}
.emu-vm__end-ini {
	box-sizing: border-box;
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	border-radius: 18px;
	background: var(--n-chip);
	color: var(--n-ink);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 14px;
	font-weight: 800;
}
.emu-vm__end-ini--on {
	background: var(--n-brand);
	color: var(--n-on-brand);
}
.emu-vm__end-name {
	font-size: 16px;
	font-weight: 700;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.emu-vm__end-state {
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-vm__end-state--talk {
	display: flex;
	align-items: center;
	gap: 6px;
	font-weight: 700;
	color: var(--n-brand);
}

/* ---------------------------------------------------------------------------
   The escort passing through
   -----------------------------------------------------------------------------
   docs/voice.md: an escort near a crossing hears that crossing's room while
   she is near it. The wrapper is what collapses, so the row's own padding and
   shadow are never what is being animated — a card that grows out of its own
   padding reads as broken at every intermediate frame. The negative margins
   are the design's: they let the wrapper's padding sit inside the screen's
   16px gutter without double-counting it.
   ------------------------------------------------------------------------ */
.emu-vm__join {
	margin: 0 -16px -8px -16px;
	padding: 0 16px;
	max-height: 0;
	opacity: 0;
	transform: translateX(28px);
	overflow: hidden;
	transition: max-height 0.55s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.4s ease-out,
		transform 0.55s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* The first frame of a run, so reopening the tab does not play the collapse
   backwards before playing it forwards. The engine takes it off on the first
   tick. */
.emu-vm__join--snap {
	transition: none;
}
.emu-vm__join--in {
	max-height: 96px;
	opacity: 1;
	transform: none;
}
/* Once it has finished growing, the row's shadow is allowed outside the
   wrapper again. The design does the same, a second after the join. */
.emu-vm__join--vis {
	overflow: visible;
}
.emu-vm__pass {
	margin: 10px 16px 8px 16px;
	padding: 10px 12px;
	border-radius: 18px;
	background: var(--n-surface);
	border: 1px solid var(--n-line);
	display: flex;
	align-items: center;
	gap: 12px;
}
.emu-vm__pass-icon {
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	border-radius: 18px;
	background: var(--n-chip);
	color: var(--n-brand);
	display: flex;
	align-items: center;
	justify-content: center;
}
.emu-vm__pass-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	line-height: 1.25;
}
.emu-vm__pass-name {
	font-size: 16px;
	font-weight: 700;
}
.emu-vm__pass-sub {
	font-size: 13px;
	font-weight: 500;
	color: var(--n-muted);
}
.emu-vm__tag {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 4px;
	padding: 5px 9px;
	border-radius: 12px;
	background: var(--n-chip);
	font-size: 12px;
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
   Handover
   ------------------------------------------------------------------------ */
.emu-vm__acts {
	margin: 16px 16px 0 16px;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 10px;
}
.emu-vm__act {
	min-height: 64px;
	padding: 10px 12px;
	border-radius: 18px;
	border: 1px solid var(--n-line);
	background: var(--n-surface);
	color: var(--n-ink);
	display: flex;
	align-items: center;
	gap: 10px;
	text-align: left;
	font-size: 15px;
	font-weight: 700;
	line-height: 1.2;
}
.emu-vm__act-icon {
	color: var(--n-brand);
	display: flex;
}

.emu-vm__grow {
	flex-grow: 1;
}

/* ---------------------------------------------------------------------------
   The two entrances inside the screen
   -----------------------------------------------------------------------------
   Both play once, when the thing they are about actually happens: the escort's
   face appears on the strip, and her "In room" tag follows it. They are not
   paired A/B like the crew board's, because each element is hidden or
   classless until the moment it plays — removing the class IS the reset, and
   the engine does that in `start()`.
   ------------------------------------------------------------------------ */
.emu-vm__pop {
	animation: emuVmPop 0.45s cubic-bezier(0.3, 1.4, 0.5, 1) both;
}
.emu-vm__pop--late {
	animation-delay: 0.35s;
}
@keyframes emuVmPop {
	from {
		opacity: 0;
		transform: scale(0.5);
	}
	to {
		opacity: 1;
		transform: none;
	}
}
/* The count rises as it changes, so the number is seen to move rather than
   silently differing from the one that was there a moment ago. */
.emu-vm__count {
	display: inline-block;
	animation: emuVmCount 0.45s cubic-bezier(0.3, 1.4, 0.5, 1);
}
@keyframes emuVmCount {
	from {
		opacity: 0;
		transform: translateY(6px) scale(0.8);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* =============================================================================
   The lead channel — apps/site/design/MainScreens-Lead.dc.html
   =============================================================================
   The lead's one screen for every post at once, with the posts on the 3D
   carousel the crew board uses (the shared block above the crew board). It
   shares the "now talking" pill and the level bars with the voice room.

   NO NEW PALETTE TOKENS. `--n-row-lift`, `--n-amber-line`, `--n-talk-ring`
   and `--n-live-ink` already exist, and this design's `rowLift`, `amberLine`,
   `talkRing` and `liveInk` are those values; everything else is surface,
   line, chip, brand, muted and ink.

   THE DESIGN'S INLINE `style` VALUES BECOME CSS. Its engine writes each
   card's transform, the segment indicator's `translateX`, each knob's
   `translateX` and each track's colour into `style` attributes. Here the
   cards read `data-cc-d`, the indicator reads `data-target` on its group,
   and the knob and track read the switch's own `aria-checked` — which is the
   state anyway, so there is nothing to keep in step.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   What was missed
   -----------------------------------------------------------------------------
   docs/voice.md: the lead gets the latest missed message only, it is never
   replayed late, and the row clears itself. The row says all three on its
   face rather than relying on the reader knowing the rule.
   ------------------------------------------------------------------------ */
.emu-ld__missed {
	margin: 10px 16px 0 16px;
	padding: 8px 8px 8px 12px;
	border-radius: 18px;
	background: var(--n-surface);
	border: 1px solid var(--n-line);
	box-shadow: var(--n-row-lift);
	display: flex;
	align-items: center;
	gap: 12px;
}
.emu-ld__missed-icon {
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	border-radius: 18px;
	background: var(--n-chip);
	color: var(--n-brand);
	display: flex;
	align-items: center;
	justify-content: center;
}
.emu-ld__missed-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	line-height: 1.25;
}
.emu-ld__missed-head {
	display: flex;
	align-items: center;
	gap: 6px;
	font-size: 15px;
	font-weight: 700;
}
.emu-ld__missed-count {
	padding: 1px 7px;
	border-radius: 10px;
	background: var(--n-chip);
	color: var(--n-ink);
	font-size: 12px;
	font-weight: 800;
}
.emu-ld__missed-sub {
	font-size: 13px;
	font-weight: 500;
	color: var(--n-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* 44px, where the design draws 40px. docs/ui-rules.md's floor for anything
   tappable, and the same call the Day/Night control above made. */
.emu-ld__reply {
	flex-shrink: 0;
	min-height: 44px;
	padding: 0 14px;
	border-radius: 22px;
	border: 0;
	background: var(--n-chip);
	color: var(--n-brand);
	font-size: 14px;
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
   Where your voice goes, and which posts you hear
   ------------------------------------------------------------------------ */
.emu-ld__label {
	padding: 14px 20px 6px 20px;
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-ld__label--split {
	display: flex;
	justify-content: space-between;
}

.emu-ld__seg {
	position: relative;
	margin: 0 16px;
	padding: 3px;
	border-radius: 15px;
	background: var(--n-chip);
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 3px;
}
/* One third of the track, less the two 3px gaps, travelling between three
   stops. The design computes `calc(n * (100% + 3px))` into a style
   attribute; `data-target` carries the same `n`. */
.emu-ld__seg-ind {
	position: absolute;
	top: 3px;
	left: 3px;
	width: calc((100% - 12px) / 3);
	height: 44px;
	border-radius: 12px;
	background: var(--n-surface);
	box-shadow: 0 1px 3px rgba(2, 17, 58, 0.16);
	transition: transform 0.32s cubic-bezier(0.3, 0.7, 0.3, 1);
}
.emu-ld__seg[data-target="post"] .emu-ld__seg-ind {
	transform: translateX(calc(100% + 3px));
}
.emu-ld__seg[data-target="all"] .emu-ld__seg-ind {
	transform: translateX(calc(200% + 6px));
}
/* 44px, where the design draws 38px — docs/ui-rules.md's floor again. The
   indicator above is raised with them so it still fills its third. */
.emu-ld__seg-opt {
	position: relative;
	height: 44px;
	border-radius: 12px;
	border: 0;
	background: transparent;
	color: var(--n-ink);
	font-size: 14px;
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
   One post's card, on the carousel
   -----------------------------------------------------------------------------
   The carousel itself is the shared block above the crew board. What is
   here is what a lead channel card says: who is on each end, whether anyone
   is talking, whether hold-to-talk reaches this post, and the listen switch.
   ------------------------------------------------------------------------ */
.emu-ld__stage {
	--cc-dl: 0.25s;
	height: 210px;
}
.emu-ld__card {
	top: 6px;
	height: 196px;
	border-radius: 24px;
}
.emu-ld__face {
	gap: 10px;
	padding: 14px 14px 10px 14px;
	border-radius: 24px;
	border: 1px solid var(--n-line);
}
/* Somebody is talking on this post and the lead is listening to it: a sky
   blue edge, the talk ring's colour and never amber (docs/alerts.md). */
.emu-ld__card--talking .emu-ld__face {
	border: 2px solid var(--n-talk-ring);
}
.emu-ld__cardhead {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
}
.emu-ld__cardtitle {
	display: flex;
	flex-direction: column;
	min-width: 0;
}
.emu-ld__cardname {
	font-size: 20px;
	font-weight: 800;
	line-height: 1.15;
	white-space: nowrap;
}
.emu-ld__cardsub {
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
}
.emu-ld__chip {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 28px;
	padding: 0 10px;
	box-sizing: border-box;
	border-radius: 14px;
	background: var(--n-chip);
	color: var(--n-muted);
	font-size: 13px;
	font-weight: 800;
}
.emu-ld__chip--talking {
	background: var(--n-brand);
	color: var(--n-live-ink);
}
/* The chip's four talking bars, the design's smallest set. */
.emu-ld__chipbars {
	display: flex;
	align-items: center;
	gap: 2px;
	height: 12px;
}
.emu-ld__chipbars[hidden] {
	display: none;
}
.emu-ld__chipbars .emu-vm__bar {
	width: 3px;
}
.emu-ld__chipbars .emu-vm__bar:nth-child(1) {
	height: 6px;
}
.emu-ld__chipbars .emu-vm__bar:nth-child(2) {
	height: 12px;
}
.emu-ld__chipbars .emu-vm__bar:nth-child(3) {
	height: 8px;
}
.emu-ld__chipbars .emu-vm__bar:nth-child(4) {
	height: 10px;
}

.emu-ld__ends {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px;
}
/* A muted post's crew is dimmed, the design's `dim`. */
.emu-ld__card--muted .emu-ld__ends {
	opacity: 0.5;
}
.emu-ld__end {
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
}
.emu-ld__av {
	flex-shrink: 0;
	width: 36px;
	height: 36px;
	border-radius: 18px;
	background: var(--n-brand);
	color: var(--n-live-ink);
	box-shadow: 0 4px 10px rgba(4, 97, 248, 0.25);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 15px;
	font-weight: 800;
}
/* The person talking, ringed in the talk colour. */
.emu-ld__av--talking {
	box-shadow: 0 0 0 3px var(--n-surface), 0 0 0 5px var(--n-talk-ring);
}
.emu-ld__openmark {
	flex-shrink: 0;
}
/* An unstaffed end: dashed amber, never solid (docs/alerts.md). */
.emu-ld__openring {
	fill: var(--n-surface);
	stroke: var(--n-amber-line);
	stroke-dasharray: 5 4;
}
.emu-ld__endtext {
	display: flex;
	flex-direction: column;
	line-height: 1.2;
	min-width: 0;
}
.emu-ld__endlabel {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--n-muted);
}
.emu-ld__endwho {
	font-size: 15px;
	font-weight: 800;
	color: var(--n-ink);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.emu-ld__endwho--open {
	color: var(--n-muted);
}
/* Empty unless hold-to-talk reaches this post; its height is held so the
   card does not move when the line comes and goes. */
.emu-ld__note {
	min-height: 17px;
	font-size: 13px;
	font-weight: 700;
	color: var(--n-brand);
}
.emu-ld__cardfoot {
	margin-top: auto;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	padding-top: 8px;
	border-top: 1px solid var(--n-line);
}
.emu-ld__state {
	font-size: 15px;
	font-weight: 700;
	color: var(--n-brand);
}
.emu-ld__card--muted .emu-ld__state {
	color: var(--n-muted);
}
.emu-ld__nav {
	padding-top: 0;
}

/* The listen switch. The TRACK keeps the design's 51x31; the BUTTON around it
   is 44px tall, which is docs/ui-rules.md's "extra spent on transparent
   padding where a visible box that large would be wrong" — a 44px-tall iOS
   switch would be wrong, and a 31px tap target in gloves is worse. */
.emu-ld__switch {
	flex-shrink: 0;
	width: 51px;
	height: 44px;
	padding: 0;
	border: 0;
	background: transparent;
	display: flex;
	align-items: center;
	justify-content: center;
}
.emu-ld__track {
	position: relative;
	display: block;
	width: 51px;
	height: 31px;
	border-radius: 16px;
	background: var(--n-chip);
	transition: background-color 0.2s ease-out;
}
.emu-ld__switch[aria-checked="true"] .emu-ld__track {
	background: var(--n-brand);
}
.emu-ld__knob {
	position: absolute;
	top: 2px;
	left: 2px;
	width: 27px;
	height: 27px;
	border-radius: 14px;
	background: #ffffff;
	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
	transition: transform 0.2s ease-out;
}
.emu-ld__switch[aria-checked="true"] .emu-ld__knob {
	transform: translateX(20px);
}
/* On a card the footer is drawn for the 31px track, and the card's height is
   fixed. The 44px target keeps its height and gives the extra back as
   negative margin, so it reaches into the card's bottom padding rather than
   pushing the footer down — the same transparent extra, spent where there is
   room for it. */
.emu-ld__cardfoot .emu-ld__switch {
	margin: -6.5px 0;
}

.emu-ld__grow {
	flex-grow: 1;
	min-height: 8px;
}

/* ---------------------------------------------------------------------------
   The newer missed message
   -----------------------------------------------------------------------------
   When a newer missed message replaces the one on the row, the row flashes a
   brand edge, the post's name fades over and the count pops — the design's
   `cm-flash`, `fm-txt` and `cm-cnt`, each played once by the engine on that
   tick. The cards' own arrival is the carousel's. All of them END at the
   element's natural state — no base rule hides anything here, which is what makes site.css
   section 25's reduced-motion backstop safe to apply.
   ------------------------------------------------------------------------ */
/* An outline, so no layout box moves and nothing beside it shifts — the same
   shape as the alert row's amber flash. Brand blue and never amber:
   docs/alerts.md reserves amber for aircraft, and a missed radio message is
   not one. */
.emu-ld__missed--flash {
	animation: emuLdFlash 1.1s ease-out;
}
.emu-ld__txt {
	animation: emuLdTxt 0.45s ease-out;
}
@keyframes emuLdTxt {
	from {
		opacity: 0;
		transform: translateY(4px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}
@keyframes emuLdFlash {
	0% {
		outline: 3px solid var(--n-brand);
		outline-offset: 0;
	}
	100% {
		outline: 3px solid transparent;
		outline-offset: 10px;
	}
}

/* =============================================================================
   Sign-in — apps/site/design/EmuSignIn.dc.html
   =============================================================================
   Task R4b. The only depicted screen with no header, no tab bar and no
   hold-to-talk: it is what somebody sees before any of that exists.

   FIVE VALUES THIS SCREEN NEEDS AND THE SHARED PALETTE DOES NOT HAVE — the
   two background blobs, the dashed taxiway line, the call to action's
   coloured shadow and that shadow at night. They are declared as `--si-*` on
   `.emu-si` rather than as `--n-*` on `.emu`, for a mechanical reason as well
   as a tidy one: src/html/emu-palette.test.ts reads the palette by taking
   every rule whose selector ENDS IN `.emu` and expects exactly three, and
   src/html/screens-shot-theme.test.ts compares the two forced-theme blocks
   declaration-for-declaration against them. A token added to `.emu` has to be
   added to five blocks at once and keeps both of those honest; a token only
   this screen reaches does not belong there at all. The same test the comment
   at the head of the "Try the app" block states: a token becomes a `--n-*`
   property when some screen's markup reaches it AND it is part of the shared
   palette, and these are neither.

   The four theme blocks below are the same four states the rest of this sheet
   handles: day by default, night by the device unless Day was chosen, night
   by an explicit page theme, and either forced by the Screens page's own
   Day/Night control.

   THE BACKGROUND MOTION LOOPS FOR EVER, AS DRAWN. The design draws `pfDrift`
   and `pfRun` as `infinite`; the first build ran a finite count by analogy
   with the level bars, and Dylan chose the drawing (Oct 28, 2026: "Loop
   forever"). `emuSiDrift` and `emuSiRun` are therefore approved loops by name,
   alongside `hrDrift`, `hrPing`, `hrBreath` and `hrRun`, in docs/ui-rules.md
   and in APPROVED_LOOPS in src/html/motion.test.ts. They depict nothing (no
   alert, no aircraft, no state). Section 25 of site.css cancels them under
   prefers-reduced-motion: reduce.
   ========================================================================== */

.emu-si {
	position: relative;
	overflow: hidden;
	padding: 56px 24px 28px 24px;

	--si-blob1: rgba(4, 165, 248, 0.3);
	--si-blob2: rgba(4, 97, 248, 0.18);
	--si-dash: rgba(2, 17, 58, 0.5);
	--si-cta: 0 10px 24px rgba(4, 97, 248, 0.35), 0 2px 6px rgba(2, 17, 58, 0.15);
}
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="day"]) .emu-si {
		--si-blob1: rgba(4, 97, 248, 0.35);
		--si-blob2: rgba(4, 165, 248, 0.25);
		--si-dash: rgba(230, 236, 248, 0.6);
		--si-cta: 0 10px 24px rgba(91, 155, 255, 0.35), 0 2px 6px rgba(0, 0, 0, 0.4);
	}
}
:root[data-theme="night"] .emu-si {
	--si-blob1: rgba(4, 97, 248, 0.35);
	--si-blob2: rgba(4, 165, 248, 0.25);
	--si-dash: rgba(230, 236, 248, 0.6);
	--si-cta: 0 10px 24px rgba(91, 155, 255, 0.35), 0 2px 6px rgba(0, 0, 0, 0.4);
}
/* The Screens page's own control, over any page theme. Same 0-3-0-plus-order
   argument as the `--n-*` blocks at the foot of this sheet. */
:root .emu-si[data-emu-theme="day"] {
	--si-blob1: rgba(4, 165, 248, 0.3);
	--si-blob2: rgba(4, 97, 248, 0.18);
	--si-dash: rgba(2, 17, 58, 0.5);
	--si-cta: 0 10px 24px rgba(4, 97, 248, 0.35), 0 2px 6px rgba(2, 17, 58, 0.15);
}
:root .emu-si[data-emu-theme="night"] {
	--si-blob1: rgba(4, 97, 248, 0.35);
	--si-blob2: rgba(4, 165, 248, 0.25);
	--si-dash: rgba(230, 236, 248, 0.6);
	--si-cta: 0 10px 24px rgba(91, 155, 255, 0.35), 0 2px 6px rgba(0, 0, 0, 0.4);
}

/* The background sits under everything and takes no pointer. */
.emu-si > *:not(.emu-si__bg) {
	position: relative;
	z-index: 1;
}
/* The shared `.emu-run-* > *` entrance does not apply to the background: the
   design excludes `.pf-bg` from its own stagger, and a blurred shape that
   slides up with the content reads as a glitch rather than as depth. */
.emu-run-a > .emu-si__bg,
.emu-run-b > .emu-si__bg {
	animation: none;
}
/* THE STAGGER IS THIS SCREEN'S OWN, not the shared one. The shared
   `.emu > *:nth-child(n)` delays stop at the eighth child, and this screen
   has twelve, so without these the four below it — the points, the spacer,
   the invitation line and the verify line — would rise at 0s, ahead of the
   logo. The figures are design/EmuSignIn.dc.html's own
   (`.pf-root > *:nth-child(n)`, 0.02s then +0.06s a step), shifted by one
   from the second logo on: the design draws one logo `<img>` whose source
   changes with the theme, and this markup carries a day file and a night
   file, so both take the design's logo delay. Same specificity as the shared
   rules, and later in the sheet, so these win. */
.emu-si > *:nth-child(2),
.emu-si > *:nth-child(3) {
	animation-delay: 0.02s;
}
.emu-si > *:nth-child(4) {
	animation-delay: 0.08s;
}
.emu-si > *:nth-child(5) {
	animation-delay: 0.14s;
}
.emu-si > *:nth-child(6) {
	animation-delay: 0.2s;
}
.emu-si > *:nth-child(7) {
	animation-delay: 0.26s;
}
.emu-si > *:nth-child(8) {
	animation-delay: 0.32s;
}
.emu-si > *:nth-child(9) {
	animation-delay: 0.38s;
}
.emu-si > *:nth-child(10) {
	animation-delay: 0.44s;
}
.emu-si > *:nth-child(11) {
	animation-delay: 0.5s;
}
.emu-si > *:nth-child(12) {
	animation-delay: 0.56s;
}
.emu-si__bg {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	overflow: hidden;
}
.emu-si__blob {
	position: absolute;
	border-radius: 50%;
	filter: blur(40px);
	opacity: 0.55;
}
.emu-si__blob--a {
	width: 260px;
	height: 260px;
	left: -80px;
	top: -60px;
	background: var(--si-blob1);
}
.emu-si__blob--b {
	width: 220px;
	height: 220px;
	right: -70px;
	top: 260px;
	background: var(--si-blob2);
}
.emu-si__dash {
	position: absolute;
	left: 50%;
	bottom: 0;
	width: 6px;
	height: 200px;
	margin-left: -3px;
	background: repeating-linear-gradient(
		to top,
		var(--si-dash) 0 22px,
		transparent 22px 44px
	);
	opacity: 0.25;
	-webkit-mask-image: linear-gradient(to top, #000, transparent);
	mask-image: linear-gradient(to top, #000, transparent);
}
/* Started by the run class so reopening the tab replays them; they loop for
   ever for the reason at the head of this section. */
.emu-run-a .emu-si__blob,
.emu-run-b .emu-si__blob {
	animation: emuSiDrift 14s ease-in-out infinite alternate;
}
.emu-run-a .emu-si__blob--b,
.emu-run-b .emu-si__blob--b {
	animation-duration: 18s;
}
@keyframes emuSiDrift {
	from {
		transform: translate(0, 0) scale(1);
	}
	to {
		transform: translate(30px, 40px) scale(1.15);
	}
}
.emu-run-a .emu-si__dash,
.emu-run-b .emu-si__dash {
	animation: emuSiRun 2.4s linear infinite;
}
@keyframes emuSiRun {
	from {
		background-position: 0 0;
	}
	to {
		background-position: 0 -44px;
	}
}

/* ---------------------------------------------------------------------------
   The screen itself
   ------------------------------------------------------------------------ */
.emu-si__logo {
	align-self: flex-start;
	height: 48px;
	width: auto;
}
.emu-si__title {
	margin: 40px 0 8px 0;
	font-size: 34px;
	font-weight: 800;
	line-height: 1.1;
}
.emu-si__lead {
	margin: 0;
	font-size: 18px;
	font-weight: 600;
	color: var(--n-muted);
	line-height: 1.35;
}
.emu-si__label {
	margin-top: 32px;
	font-size: 15px;
	font-weight: 800;
}
/* A span wearing the field's clothes, not an `<input>`. The reason is in the
   banner comment over this screen's markup: nothing here submits anything,
   and a credential-shaped box that swallows what a visitor types is the
   dishonest control root CLAUDE.md rule 6 forbids. */
.emu-si__field {
	margin-top: 8px;
	height: 60px;
	box-sizing: border-box;
	padding: 0 16px;
	border-radius: 16px;
	border: 2px solid var(--n-brand);
	background: var(--n-surface);
	box-shadow: var(--n-lift);
	color: var(--n-ink);
	font-size: 20px;
	font-weight: 600;
	display: flex;
	align-items: center;
}
.emu-si__cta {
	position: relative;
	overflow: hidden;
	margin-top: 16px;
	height: 60px;
	border-radius: 18px;
	background: var(--n-brand);
	color: var(--n-on-brand);
	box-shadow: var(--si-cta);
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	font-size: 20px;
	font-weight: 800;
}
/* One sweep across the button as the screen settles — the design's `pfShine`,
   which already plays once and is left as drawn. */
.emu-run-a .emu-si__cta::after,
.emu-run-b .emu-si__cta::after {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	width: 40%;
	left: -60%;
	background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.35), transparent);
	animation: emuSiShine 1.1s ease-out 0.9s both;
}
@keyframes emuSiShine {
	to {
		left: 130%;
	}
}

.emu-si__points {
	list-style: none;
	margin: 28px 0 0 0;
	padding: 16px;
	border-radius: 16px;
	background: var(--n-surface);
	box-shadow: var(--n-lift);
	border: 1px solid var(--n-line);
	display: flex;
	flex-direction: column;
	gap: 12px;
}
.emu-si__point {
	display: flex;
	gap: 10px;
	align-items: flex-start;
	font-size: 16px;
	font-weight: 600;
	line-height: 1.3;
}
/* The icons' themed stroke is the shared `.emu-s-brand` from the flagger
   block above, not a second copy of it: SVG presentation attributes do not
   resolve `var()`, which is why a themed stroke is a class at all. */
.emu-si__point svg {
	flex-shrink: 0;
}

.emu-si__grow {
	flex-grow: 1;
}
.emu-si__foot {
	margin: 0;
	text-align: center;
	font-size: 14px;
	font-weight: 600;
	color: var(--n-muted);
	line-height: 1.4;
}
/* root CLAUDE.md rule 1, which names sign-in explicitly. Added to the drawing
   as well as to the build — docs/ui-rules.md, Dylan, Oct 28 2026. */
.emu-si__verify {
	margin: 12px 0 0 0;
	text-align: center;
	font-size: 13px;
	font-weight: 600;
	color: var(--n-muted);
	line-height: 1.4;
}

/* =============================================================================
   The Screens page forces a depicted screen's theme
   =============================================================================
   Added by step 0.5 task R4a. Everything above themes a depicted screen off
   the PAGE's theme: the base `.emu` block is day, and two blocks override it
   at night — one for a dark device, one for an explicit Night choice.

   design/Screens.dc.html puts a Day/Night control beside the phone and passes
   its value into the screen (`shotTheme`), so on that page the depicted screen
   must be able to disagree with the page around it. That is what these two
   blocks are for. public/scripts/screens.js writes `data-emu-theme` onto each
   `.emu` element; nothing else on the site sets it, so no other page changes.

   WHY THE TOKENS ARE WRITTEN OUT AGAIN INSTEAD OF REUSED. A custom property
   has to be declared on the element that consumes it or on an ancestor it
   outranks, and `.emu` declares the whole day palette on itself — so a block
   on any ancestor loses to it. There is no CSS form for "apply that other
   rule's declarations", so the values are duplicated, and the duplication is
   guarded rather than trusted: src/html/screens-shot-theme.test.ts asserts
   that this day block is declaration-for-declaration identical to the base
   `.emu` block and this night block to `:root[data-theme="night"] .emu`. The
   values below were GENERATED by reading those two blocks, not typed.

   WHY THE SELECTORS END IN `]` AND NOT IN `.emu`. src/html/emu-palette.test.ts
   finds the palette by taking every rule whose selector ends in `.emu`, and it
   expects exactly three such blocks in three theme states. A fourth and fifth
   ending in `.emu` would be read as more "day" blocks and would corrupt that
   comparison. Writing them as `.emu[data-emu-theme="…"]` keeps them out of its
   reach — which also means that test does not cover them, which is why the
   guard named above exists.

   SPECIFICITY. `:root .emu[data-emu-theme="…"]` is 0-3-0, the same as the two
   night blocks it has to beat, so these win on source order and must stay
   below them in this file. The leading `:root` is there for that reason
   alone: without it the selector is 0-2-0 and an explicit Night page theme
   would override a Day choice here.
   ========================================================================== */

/* An explicit Day choice on the Screens page, over any page theme. */
:root .emu[data-emu-theme="day"] {
	--n-bg: #eef2f8;
	--n-surface: #ffffff;
	--n-ink: #02113a;
	--n-muted: #3d4b68;
	--n-line: #d3dae6;
	--n-brand: #0461f8;
	--n-on-brand: #ffffff;
	--n-chip: #dde5f3;
	--n-alert-ink: #1f1300;
	--n-card-tint: #fff3d6;
	--n-card-ink: #1f1300;
	--n-card-muted: #6b5320;
	--n-card-border: #e59a00;
	--n-map-bg: #edeff2;
	--n-apron: #e2e5ea;
	--n-bldg: #d5d9e0;
	--n-twy: #ffffff;
	--n-twy-edge: #c9ced8;
	--n-center: #e2b93b;
	--n-rwy: #7b8291;
	--n-haul: #bca27a;
	--n-label: #5b6475;
	--n-other: #7a8599;
	--n-stopped: #2c3a5c;
	--n-plane: #c2410c;
	--n-compass: #d92d20;
	--n-ring: #0461f8;
	--n-sel: #0461f8;
	--n-sel-ink: #ffffff;
	--n-amber-tint: #fff3d6;
	--n-amber-line: #e59a00;
	--n-row-lift: 0 1px 2px rgba(2, 17, 58, 0.06), 0 3px 8px rgba(2, 17, 58, 0.06);
	--n-ptt: #02113a;
	--n-ptt-ink: #ffffff;
	--n-ptt-talk: #0461f8;
	--n-talk-ring: #04a5f8;
	--n-btn-line: #8e9cb8;
	--n-nav-bg: rgba(255, 255, 255, 0.94);
	--n-nav-line: rgba(2, 17, 58, 0.1);
	--n-nav-sel: #e3ecff;
	--n-nav-sel-ink: #0452d0;
	--n-badge: #e59a00;
	--n-badge-ink: #1f1300;
	--n-live: #0461f8;
	--n-live-ink: #ffffff;
	--n-glass: rgba(255, 255, 255, 0.9);
	--n-glass-line: rgba(2, 17, 58, 0.12);
	--n-shadow: 0 2px 12px rgba(2, 17, 58, 0.18);
	--n-lift: 0 1px 2px rgba(2, 17, 58, 0.06), 0 6px 18px rgba(2, 17, 58, 0.09);
	--n-nav-float: 0 14px 34px rgba(2, 17, 58, 0.22), 0 3px 8px rgba(2, 17, 58, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.8);
	--n-sel-lift: 0 2px 6px rgba(2, 17, 58, 0.12);
}

/* An explicit Night choice on the Screens page, over any page theme. */
:root .emu[data-emu-theme="night"] {
	--n-bg: #030a1c;
	--n-surface: #0e1b38;
	--n-ink: #e6ecf8;
	--n-muted: #a7b6d3;
	--n-line: #22335e;
	--n-brand: #5b9bff;
	--n-on-brand: #02113a;
	--n-chip: #13244a;
	--n-alert-ink: #1a0f00;
	--n-card-tint: #241a06;
	--n-card-ink: #fcebc7;
	--n-card-muted: #d9be86;
	--n-card-border: #e39b22;
	--n-map-bg: #0a1226;
	--n-apron: #101b33;
	--n-bldg: #18233d;
	--n-twy: #2a3858;
	--n-twy-edge: #1c2945;
	--n-center: #9c7f2a;
	--n-rwy: #3a4766;
	--n-haul: #6b5b3e;
	--n-label: #93a2c2;
	--n-other: #6f7fa3;
	--n-stopped: #b9c6e2;
	--n-plane: #ff8a4c;
	--n-compass: #ff6b5e;
	--n-ring: #5b9bff;
	--n-sel: #5b9bff;
	--n-sel-ink: #02113a;
	--n-amber-tint: #241a06;
	--n-amber-line: #e39b22;
	--n-row-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 10px rgba(0, 0, 0, 0.35);
	--n-ptt: #1e5bd8;
	--n-ptt-ink: #ffffff;
	--n-ptt-talk: #3f7bff;
	--n-talk-ring: #04a5f8;
	--n-btn-line: #3a5085;
	--n-nav-bg: rgba(14, 27, 56, 0.94);
	--n-nav-line: rgba(230, 236, 248, 0.14);
	--n-nav-sel: #1b2e5c;
	--n-nav-sel-ink: #9cc2ff;
	--n-badge: #e39b22;
	--n-badge-ink: #1a0f00;
	--n-live: #5b9bff;
	--n-live-ink: #02113a;
	--n-glass: rgba(14, 27, 56, 0.9);
	--n-glass-line: rgba(230, 236, 248, 0.16);
	--n-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
	--n-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 22px rgba(0, 0, 0, 0.45);
	--n-nav-float: 0 16px 38px rgba(0, 0, 0, 0.65), 0 3px 8px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.08);
	--n-sel-lift: 0 2px 6px rgba(0, 0, 0, 0.4);
}

/* The app mark has a light-background and a dark-background file, and the
   swap above follows the page. On the Screens page it has to follow the
   control instead. Same 0-3-0-plus-order argument as the palette. */
:root .emu[data-emu-theme="day"] .emu__mark--day {
	display: block;
}
:root .emu[data-emu-theme="day"] .emu__mark--night {
	display: none;
}
:root .emu[data-emu-theme="night"] .emu__mark--day {
	display: none;
}
:root .emu[data-emu-theme="night"] .emu__mark--night {
	display: block;
}
