/*
 * fm-public.css
 * Styles for [fm_schedule], [fm_pilot], [fm_model] on the front-end.
 *
 * Schedule layout: hairline list with progressive-disclosure drawer per row.
 * Tokens scoped to .fm-schedule so the rest of the page is untouched.
 */

/* Standalone /flights/ body — no theme chrome, the schedule fills the
   viewport like /control/. Only applied when the body carries the
   class set by templates/public-page.php; the shortcode embedded
   inside a regular page is unaffected. */
.fm-public-body {
	margin: 0;
	padding: 0;
	background: #ffffff;
	font-family: "Geist", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	-webkit-font-smoothing: antialiased;
}
.fm-public-body:has(.fm-schedule[data-theme="dark"]) { background: #1b1b1a; }
@media (prefers-color-scheme: dark) {
	.fm-public-body:not(:has(.fm-schedule[data-theme="light"])) { background: #1b1b1a; }
}

/* When the schedule shortcode is embedded in a regular WP page (e.g.
   the auto-created Flights page used as homepage), the surrounding
   <body> belongs to the active theme and ignores the schedule's own
   data-theme. Paint the body dark too so the dark mode bleeds to the
   page edges instead of stopping at the section boundary. The
   selector is scoped via :has() so light-mode pages, and pages that
   don't host a schedule at all, stay untouched. */
body:has(.fm-schedule[data-theme="dark"]) {
	background: #1b1b1a;
}
@media (prefers-color-scheme: dark) {
	body:has(.fm-schedule):not(:has(.fm-schedule[data-theme="light"])) {
		background: #1b1b1a;
	}
}

/* When the schedule shortcode is embedded inside a block theme home
   page, the surrounding .wp-site-blocks container injects its own
   padding from theme.json. Zero it out so the schedule can sit flush
   against the viewport edges like our standalone templates. */
.wp-site-blocks:has(.fm-schedule) {
	padding: 0;
}

.fm-schedule {
	--fm-bg: #ffffff;
	--fm-line: #ececec;
	--fm-text: #111111;
	--fm-text-soft: #555555;
	--fm-text-mute: #8a8a8a;
	--fm-hover: #f6f6f6;

	--fm-status-done: #16a34a;
	--fm-status-done-bg: #ecfdf5;
	--fm-status-prog: #2563eb;
	--fm-status-prog-bg: #eff6ff;
	--fm-status-wait: #b45309;
	--fm-status-wait-bg: #fef3c7;
	--fm-status-cancel: #dc2626;
	--fm-status-cancel-bg: #fef2f2;
	--fm-status-mute: #6b7280;
	--fm-status-mute-bg: #f3f4f6;

	--fm-row-pad-y: 14px;
	--fm-row-pad-x: 28px;
	--fm-cell-gap: 32px;

	--fm-duration: 180ms;
	--fm-ease: cubic-bezier(.2,.7,.2,1);

	max-width: 1180px;
	margin: 0 auto;
	padding: 0;
	background: var(--fm-bg);
	color: var(--fm-text);
	font-family: "Geist", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	-webkit-font-smoothing: antialiased;
	font-feature-settings: "ss01", "cv11";
	transition: background-color var(--fm-duration) var(--fm-ease), color var(--fm-duration) var(--fm-ease);
}

@media (prefers-color-scheme: dark) {
	.fm-schedule:not([data-theme="light"]) {
		--fm-bg: #1b1b1a;
		--fm-line: #1f1f22;
		--fm-text: #f5f5f7;
		--fm-text-soft: #b8b8bd;
		--fm-text-mute: #6e6e75;
		--fm-hover: #161618;

		--fm-status-done: #4ade80;
		--fm-status-done-bg: rgba(74,222,128,0.10);
		--fm-status-prog: #60a5fa;
		--fm-status-prog-bg: rgba(96,165,250,0.12);
		--fm-status-wait: #fbbf24;
		--fm-status-wait-bg: rgba(251,191,36,0.10);
		--fm-status-cancel: #f87171;
		--fm-status-cancel-bg: rgba(248,113,113,0.10);
		--fm-status-mute: #9aa0a6;
		--fm-status-mute-bg: rgba(154,160,166,0.10);
	}
}

.fm-schedule[data-theme="dark"] {
	--fm-bg: #1b1b1a;
	--fm-line: #1f1f22;
	--fm-text: #f5f5f7;
	--fm-text-soft: #b8b8bd;
	--fm-text-mute: #6e6e75;
	--fm-hover: #161618;

	--fm-status-done: #4ade80;
	--fm-status-done-bg: rgba(74,222,128,0.10);
	--fm-status-prog: #60a5fa;
	--fm-status-prog-bg: rgba(96,165,250,0.12);
	--fm-status-wait: #fbbf24;
	--fm-status-wait-bg: rgba(251,191,36,0.10);
	--fm-status-cancel: #f87171;
	--fm-status-cancel-bg: rgba(248,113,113,0.10);
	--fm-status-mute: #9aa0a6;
	--fm-status-mute-bg: rgba(154,160,166,0.10);
}

.fm-schedule[data-density="compact"] {
	--fm-row-pad-y: 11px;
	--fm-row-pad-x: 24px;
	--fm-cell-gap: 24px;
}

/* Chrome bar — pills sit above the schedule head, mirroring /control/. */
.fm-schedule__chrome {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: 10px;
	margin-bottom: 20px;
	flex-wrap: wrap;
}

/* Footer — discreet cross-portal nav. One row, primary destinations
   flush left, Sign out flush right. Same shape on every page
   (operator / pilot / public) so the chrome feels consistent. The
   horizontal padding matches the row content's --fm-row-pad-x so the
   footer aligns with the schedule rows above on the public page;
   the operator/pilot wrappers already provide outer padding, so this
   extra inset just gives the labels breathing room from the edge. */
.fm-schedule__foot {
	margin-top: 32px;
	padding: 16px 0 0;
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 8px 16px;
}
.fm-schedule__foot-nav {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px;
	font-size: 12px;
	letter-spacing: 0.04em;
	color: var(--fm-text-mute, #8a8a8a);
}
.fm-schedule__foot-nav .fm-schedule__foot-link {
	white-space: nowrap;
}
.fm-schedule__foot-nav--secondary {
	margin-left: auto; /* push Sign out flush right when both navs share a row */
	justify-content: flex-end;
}
.fm-schedule__foot-link {
	color: inherit;
	text-decoration: none;
	padding: 4px 6px;
	border-radius: 4px;
	transition: color 120ms var(--fm-ease), background-color 120ms var(--fm-ease);
}
.fm-schedule__foot-link:hover,
.fm-schedule__foot-link:focus-visible {
	color: var(--fm-text, #111111);
	background: var(--fm-hover, #f6f6f6);
	outline: none;
}
.fm-schedule__foot-sep {
	opacity: 0.4;
}
/* Current zone highlighted in the app's blue (desktop footer + mobile bar). */
.fm-schedule__foot-link.is-current {
	color: var(--fm-status-prog);
}
/* Left-hand credit: © Benjamin Magits YYYY + "made with love by MUFY". */
.fm-schedule__foot-credit {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 4px 24px;
	font-size: 12px;
	letter-spacing: 0.04em;
	color: var(--fm-text-mute, #8a8a8a);
}
.fm-schedule__foot-made .fm-schedule__foot-link,
.fm-schedule__foot-made .fm-schedule__foot-link:hover,
.fm-schedule__foot-made .fm-schedule__foot-link:focus-visible {
	white-space: nowrap;
	font-weight: 700;
	color: #fff;
	background: #f20034;
}
/* Right-hand link group: Live · Operator · Pilot · Admin   Sign out. */
.fm-schedule__foot-links {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 8px 16px;
}
.fm-schedule__foot-links .fm-schedule__foot-nav--secondary {
	margin-left: 0; /* keep the whole group together, flush right */
}

@media (max-width: 820px) {
	/* Authenticated cross-portal nav: only the LINKS group becomes a thin
	   black top bar pinned to the viewport. The credit row stays in normal
	   flow and falls to the bottom of the page (see below). Anonymous pages
	   keep the normal footer (no --bar class). */
	.fm-schedule__foot--bar {
		display: block;
		margin: 0;
		padding: 0;
	}
	.fm-schedule__foot--bar .fm-schedule__foot-links {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		z-index: 50;
		margin: 0;
		min-height: 44px;
		padding: env(safe-area-inset-top, 0px)
		         max(10px, env(safe-area-inset-right, 0px) + 6px)
		         0
		         max(10px, env(safe-area-inset-left, 0px) + 6px);
		background: #111;
		flex-wrap: nowrap;
		gap: 0;
		align-items: stretch;
	}
	.fm-schedule__foot--bar .fm-schedule__foot-nav {
		gap: 0;
		font-size: 13px;
		letter-spacing: 0.02em;
		text-transform: none;
		color: #cfcfcf;
		align-items: stretch;
		flex-wrap: nowrap;
	}
	.fm-schedule__foot--bar .fm-schedule__foot-sep { display: none; }
	.fm-schedule__foot--bar .fm-schedule__foot-link {
		display: inline-flex;
		align-items: center;
		height: 44px;
		padding: 0 12px;
		border-radius: 0;
		color: #cfcfcf;
	}
	.fm-schedule__foot--bar .fm-schedule__foot-link:hover,
	.fm-schedule__foot--bar .fm-schedule__foot-link:focus-visible {
		color: #fff;
		background: rgba(255, 255, 255, 0.08);
	}
	.fm-schedule__foot--bar .fm-schedule__foot-link.is-current {
		color: #fff;
		box-shadow: inset 0 -2px 0 var(--fm-status-prog);
	}
	.fm-schedule__foot--bar .fm-schedule__foot-nav--secondary { margin-left: auto; }
	/* The pinned bar is out of flow, so the credit is the footer's only
	   in-flow child — it lands centered at the very bottom of the page. */
	.fm-schedule__foot--bar .fm-schedule__foot-credit {
		justify-content: center;
		text-align: center;
		margin-top: 28px;
		padding: 16px 0 0;
	}
	/* Mobile: MUFY credit reads as red text on a white chip. */
	.fm-schedule__foot-made .fm-schedule__foot-link {
		color: #f20034;
		background: #fff;
	}

	/* Reserve room so the fixed top bar never covers the page title.
	   :has() scopes the inset to the public schedule only when the bar is
	   actually rendered (logged-in), leaving anonymous pages untouched.
	   Operator/pilot add their own inset in their stylesheets. */
	.fm-schedule:has( > .fm-schedule__foot--bar ) {
		padding-top: calc(env(safe-area-inset-top, 0px) + 56px);
	}
}

/* Header */
.fm-schedule__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 24px;
	margin-bottom: 32px;
	flex-wrap: wrap;
}
.fm-schedule__title {
	margin: 0;
	font-size: 40px;
	font-weight: 600;
	letter-spacing: -0.02em;
	line-height: 1;
	color: var(--fm-text);
}
.fm-schedule__meta {
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: 18px;
	color: var(--fm-text-mute);
	font-size: 13px;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	flex-wrap: wrap;
}
.fm-schedule__date { font-variant-numeric: tabular-nums; }
.fm-schedule__clock { font-variant-numeric: tabular-nums; }

.fm-schedule__count {
	color: var(--fm-text-soft);
	font-weight: 500;
}
.fm-schedule__count b {
	color: var(--fm-text);
	font-weight: 600;
}
.fm-schedule__ends {
	color: var(--fm-text-mute);
	font-size: 12px;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	letter-spacing: 0;
}
.fm-schedule__event {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-size: 12px;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	color: var(--fm-text-mute);
	letter-spacing: 0;
}
.fm-schedule__event-window { color: var(--fm-text-soft); }
.fm-schedule__event-state {
	display: inline-block;
	padding: 2px 8px;
	border-radius: 4px;
	font-size: 10.5px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	font-weight: 500;
}
.fm-schedule__event-state--running {
	color: var(--fm-status-done);
	background: var(--fm-status-done-bg);
}
.fm-schedule__event-state--upcoming {
	color: var(--fm-status-prog);
	background: var(--fm-status-prog-bg);
}
.fm-schedule__event-state--ended {
	color: var(--fm-status-wait);
	background: var(--fm-status-wait-bg);
}
.fm-schedule__event-state--preview {
	color: #7c3aed;
	background: rgba(124, 58, 237, 0.10);
}
.fm-schedule__past-toggle {
	appearance: none;
	background: transparent;
	border: 0;
	padding: 0;
	margin: 0;
	color: var(--fm-text-mute);
	font: inherit;
	font-size: 12px;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	cursor: pointer;
	text-decoration: underline;
	text-decoration-color: var(--fm-line);
	text-underline-offset: 3px;
	transition: color 120ms var(--fm-ease);
}
.fm-schedule__past-toggle:hover,
.fm-schedule__past-toggle:focus-visible {
	color: var(--fm-text-soft);
	outline: none;
}

.fm-schedule__row--past-hidden { display: none; }
.fm-schedule.is-showing-past .fm-schedule__row--past-hidden { display: block; }

/* Pending row whose cascaded slot end falls past event_end — flag the
   time cell in the cancel/red palette so the operator notices the
   schedule can't fit the deadline. */
.fm-schedule__row--overrun .fm-schedule__cell--time {
	color: var(--fm-status-cancel);
}
.fm-schedule__row--overrun .fm-schedule__cell--time::after {
	content: " !";
	color: var(--fm-status-cancel);
	font-weight: 600;
	margin-left: 2px;
}

/* List + grid */
.fm-schedule__list {
	border-top: 1px solid var(--fm-line);
}

.fm-schedule__grid {
	display: grid;
	grid-template-columns:
		32px           /* # */
		90px           /* time */
		1fr            /* pilot */
		1.4fr          /* model */
		130px          /* status */
		20px;          /* chevron */
	gap: var(--fm-cell-gap);
	align-items: center;
}

.fm-schedule__headrow {
	padding: 14px var(--fm-row-pad-x);
	border-bottom: 1px solid var(--fm-line);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fm-text-mute);
}

/* Rows */
.fm-schedule__row {
	border-bottom: 1px solid var(--fm-line);
}
/* Inside slot sections the last row's bottom border doubles up with
   the next section's slot-line above, producing a heavy grey gap.
   Drop the border on the last row of a section so the chip floats
   cleanly over the rule. */
.fm-schedule__slot-section .fm-schedule__row:last-child {
	border-bottom: 0;
}
/* Linked flights: a child row inherits the parent's cascade time
   and lifecycle, so we dim the time, surface a "↳" hint, and tint
   the row faintly so the group reads as one block. The continuity
   bracket along the left edge is drawn on the parent (.has-children)
   and every child sharing the same parent. */
.fm-schedule__row--child .fm-schedule__cell--time {
	color: var(--fm-text-mute);
}
.fm-schedule__link-icon {
	display: inline-block;
	margin-right: 4px;
	color: var(--fm-text-mute);
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}
.fm-schedule__row--parent.has-children,
.fm-schedule__row--child {
	border-left: 3px solid var(--fm-status-prog, #3b82f6);
}
.fm-schedule__row--child .fm-schedule__main {
	background: var(--fm-hover);
}
.fm-schedule__main {
	padding: var(--fm-row-pad-y) var(--fm-row-pad-x);
	cursor: pointer;
	user-select: none;
	transition: background-color 120ms var(--fm-ease);
}
.fm-schedule__main:hover,
.fm-schedule__main:focus-visible {
	background: var(--fm-hover);
	outline: none;
}

.fm-schedule__cell {
	min-width: 0;
}
.fm-schedule__cell--num {
	color: var(--fm-text-mute);
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 12px;
	font-variant-numeric: tabular-nums;
}
.fm-schedule__cell--time {
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 14px;
	font-weight: 500;
	font-variant-numeric: tabular-nums;
	color: var(--fm-text);
}
.fm-schedule__cell--pilot {
	font-size: 14px;
	font-weight: 500;
	color: var(--fm-text);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* flag-icons sprite rendered just before the pilot's name. Sized to
   sit on the cap height of the surrounding text with a tiny gap. */
.fm-schedule__pilot-flag {
	display: inline-block;
	width: 1.25em;
	height: 0.9em;
	margin-right: 6px;
	vertical-align: -0.05em;
	border-radius: 1px;
	box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.fm-schedule__cell--model {
	display: flex;
	align-items: baseline;
	gap: 10px;
	min-width: 0;
}
.fm-schedule__model-name {
	font-size: 14px;
	font-weight: 500;
	color: var(--fm-text);
	white-space: nowrap;
}
.fm-schedule__model-specs {
	font-size: 13px;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	color: var(--fm-text-mute);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	min-width: 0;
}
.fm-schedule__sep {
	display: inline-block;
	margin: 0 8px;
	opacity: 0.55;
}

/* Status pastille */
.fm-schedule__status {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-size: 13px;
	color: var(--fm-text-soft);
}
.fm-schedule__dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--fm-status-mute);
	box-shadow: 0 0 0 3px var(--fm-status-mute-bg);
	flex-shrink: 0;
}
.fm-schedule__status--done   { color: var(--fm-status-done); }
.fm-schedule__status--done   .fm-schedule__dot { background: var(--fm-status-done);   box-shadow: 0 0 0 3px var(--fm-status-done-bg); }
.fm-schedule__status--prog   { color: var(--fm-status-prog); }
.fm-schedule__status--prog   .fm-schedule__dot {
	background: var(--fm-status-prog);
	box-shadow: 0 0 0 3px var(--fm-status-prog-bg);
	position: relative;
	animation: fm-pulse-core 1.4s ease-in-out infinite;
}
.fm-schedule__status--prog   .fm-schedule__dot::before,
.fm-schedule__status--prog   .fm-schedule__dot::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: var(--fm-status-prog);
	pointer-events: none;
	animation: fm-pulse-ring 1.4s cubic-bezier(0, 0, 0.2, 1) infinite;
}
.fm-schedule__status--prog   .fm-schedule__dot::after { animation-delay: 0.7s; }
.fm-schedule__status--wait   { color: var(--fm-status-wait); }
.fm-schedule__status--wait   .fm-schedule__dot { background: var(--fm-status-wait);   box-shadow: 0 0 0 3px var(--fm-status-wait-bg); }
.fm-schedule__status--cancel { color: var(--fm-status-cancel); }
.fm-schedule__status--cancel .fm-schedule__dot { background: var(--fm-status-cancel); box-shadow: 0 0 0 3px var(--fm-status-cancel-bg); }
.fm-schedule__status--delayed { color: var(--fm-status-cancel); }
.fm-schedule__status--delayed .fm-schedule__dot { background: var(--fm-status-cancel); box-shadow: 0 0 0 3px var(--fm-status-cancel-bg); }

@keyframes fm-pulse-ring {
	0%        { transform: scale(1);   opacity: 0.35; }
	80%, 100% { transform: scale(2.6); opacity: 0; }
}
@keyframes fm-pulse-core {
	0%, 100% { transform: scale(1); }
	50%      { transform: scale(1.06); }
}

/* Chevron */
.fm-schedule__chev {
	width: 18px;
	height: 18px;
	justify-self: end;
	color: var(--fm-text-mute);
	transition: transform var(--fm-duration) var(--fm-ease), color var(--fm-duration) var(--fm-ease);
}
.fm-schedule__main:hover .fm-schedule__chev { color: var(--fm-text-soft); }
.fm-schedule__row.is-open .fm-schedule__chev {
	transform: rotate(180deg);
	color: var(--fm-text-soft);
}

/* Drawer */
.fm-schedule__detail {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows var(--fm-duration) var(--fm-ease);
}
.fm-schedule__row.is-open .fm-schedule__detail {
	grid-template-rows: 1fr;
}
.fm-schedule__detail-inner { overflow: hidden; }
.fm-schedule__detail-grid {
	padding: 8px var(--fm-row-pad-x) 28px calc(var(--fm-row-pad-x) + 32px + var(--fm-cell-gap));
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 24px 40px;
	opacity: 0;
	transform: translateY(-4px);
	transition: opacity var(--fm-duration) var(--fm-ease) 40ms, transform var(--fm-duration) var(--fm-ease) 40ms;
}
.fm-schedule__row.is-open .fm-schedule__detail-grid {
	opacity: 1;
	transform: none;
}

.fm-schedule__spec {
	display: flex;
	flex-direction: column;
	gap: 6px;
	min-width: 0;
}
.fm-schedule__spec-label {
	font-size: 10.5px;
	font-weight: 500;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--fm-text-mute);
}
.fm-schedule__spec-value {
	font-size: 14px;
	font-weight: 500;
	color: var(--fm-text);
	overflow: hidden;
	text-overflow: ellipsis;
}
.fm-schedule__spec-value--mono {
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	font-variant-numeric: tabular-nums;
	letter-spacing: -0.01em;
}
.fm-schedule__spec-value--empty {
	color: var(--fm-text-mute);
	font-weight: 400;
}
.fm-schedule__unit {
	color: var(--fm-text-mute);
	font-weight: 400;
	margin-left: 3px;
}
.fm-schedule__spec--notes {
	grid-column: 1 / -1;
	margin-top: 4px;
	padding-top: 20px;
	border-top: 1px dashed var(--fm-line);
}
.fm-schedule__spec--notes .fm-schedule__spec-value {
	font-weight: 400;
	color: var(--fm-text-soft);
	line-height: 1.55;
	white-space: normal;
	max-width: 70ch;
}

.fm-schedule__empty {
	text-align: center;
	font-size: 14px;
	color: var(--fm-text-mute);
	padding: 72px 0;
}

/* Icon toggle buttons in the chrome bar — one rounded square per
   preference, the inner icon swaps to reflect the current state
   (sun ↔ moon, comfortable ↔ compact). */
.fm-schedule__iconbtn {
	appearance: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	padding: 0;
	border: 1px solid var(--fm-line);
	border-radius: 8px;
	background: var(--fm-bg);
	color: var(--fm-text-soft);
	cursor: pointer;
	transition: background-color 120ms var(--fm-ease),
				color            120ms var(--fm-ease),
				border-color     120ms var(--fm-ease);
}
.fm-schedule__iconbtn:hover {
	background: var(--fm-hover);
	color: var(--fm-text);
}
.fm-schedule__iconbtn:focus-visible {
	outline: 2px solid var(--fm-status-prog);
	outline-offset: 2px;
}
.fm-schedule__iconbtn-icon { display: none; }
/* Comfortable is the default state — show its icon unless data-density
   explicitly says compact. Same logic for the theme (light default,
   moon only when dark). */
.fm-schedule:not([data-density="compact"]) .fm-schedule__iconbtn-icon--comfortable,
.fm-schedule[data-density="compact"]      .fm-schedule__iconbtn-icon--compact,
.fm-schedule:not([data-theme="dark"])      .fm-schedule__iconbtn-icon--sun,
.fm-schedule[data-theme="dark"]            .fm-schedule__iconbtn-icon--moon {
	display: block;
}

/* Language switcher (Polylang). Desktop shows a row of EN FR NL DE pills
   (.fm-langs__pills); mobile (≤820px) hides the pills and shows a compact
   "EN ▾" dropdown (.fm-langs__dd) so the bottom bars don't overflow. Both
   are rendered server-side and toggled by CSS — no JS. */
.fm-langs {
	position: relative;
	display: inline-flex;
}
.fm-langs__dd { display: none; } /* desktop: pills only */
.fm-langs__pills {
	display: inline-flex;
	align-items: center;
	gap: 2px;
	padding: 2px;
	border: 1px solid var(--fm-line);
	border-radius: 8px;
	background: var(--fm-bg);
}
.fm-langs__item {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 30px;
	height: 28px;
	padding: 0 8px;
	border-radius: 6px;
	font-size: 12px;
	font-weight: 600;
	line-height: 1;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	text-decoration: none;
	color: var(--fm-text-soft);
	transition: background-color 120ms var(--fm-ease),
				color            120ms var(--fm-ease);
}
.fm-langs__item:hover {
	background: var(--fm-hover);
	color: var(--fm-text);
}
.fm-langs__item.is-current {
	background: var(--fm-text);
	color: var(--fm-bg);
}
.fm-langs__item:focus-visible {
	outline: 2px solid var(--fm-status-prog);
	outline-offset: 2px;
}

@media (max-width: 820px) {
	.fm-langs__pills { display: none; }
	.fm-langs__dd { display: inline-flex; position: relative; }

	.fm-langs__summary {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: 4px;
		height: 40px;
		padding: 0 10px;
		border: 1px solid var(--fm-line);
		border-radius: 8px;
		background: var(--fm-bg);
		color: var(--fm-text-soft);
		font-size: 13px;
		font-weight: 600;
		letter-spacing: 0.02em;
		text-transform: uppercase;
		list-style: none;
		cursor: pointer;
		white-space: nowrap;
	}
	.fm-langs__summary::-webkit-details-marker { display: none; }
	.fm-langs__dd[open] .fm-langs__summary { color: var(--fm-text); }
	.fm-langs__caret { transition: transform 120ms var(--fm-ease); }
	.fm-langs__dd[open] .fm-langs__caret { transform: rotate(180deg); }
	.fm-langs__menu {
		position: absolute;
		right: 0;
		top: calc(100% + 6px);
		display: none;
		flex-direction: column;
		align-items: stretch;
		gap: 2px;
		padding: 2px;
		border: 1px solid var(--fm-line);
		border-radius: 8px;
		background: var(--fm-bg);
		z-index: 60;
		box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
	}
	.fm-langs__dd[open] .fm-langs__menu { display: flex; }
	.fm-langs__menu .fm-langs__item { height: 36px; min-width: 64px; justify-content: flex-start; }

	/* In the bottom-anchored bars the menu must open upward, not down off
	   the bottom edge of the viewport. */
	.fm-public-body .fm-schedule__chrome .fm-langs__menu {
		top: auto;
		bottom: calc(100% + 8px);
	}

	/* Embedded shortcode (no .fm-public-body) keeps the chrome left-
	   aligned at the top of the page. The menu must then open rightward
	   from the button instead of leftward — otherwise it spills off the
	   left edge of the viewport and overlaps the title behind it. */
	body:not(.fm-public-body):not(.fm-pilot-body):not(.fm-control-body) .fm-schedule__chrome .fm-langs__menu {
		left: 0;
		right: auto;
	}
}

/* Login cards center the switcher above the title. */
.fm-pilot__login-langs {
	display: flex;
	justify-content: center;
	margin-bottom: 18px;
}

@media (prefers-reduced-motion: reduce) {
	.fm-schedule__main,
	.fm-schedule__chev,
	.fm-schedule__detail,
	.fm-schedule__detail-grid {
		transition: none;
	}
	.fm-schedule__status--prog .fm-schedule__dot,
	.fm-schedule__status--prog .fm-schedule__dot::before,
	.fm-schedule__status--prog .fm-schedule__dot::after { animation: none; }
	.fm-schedule__status--prog .fm-schedule__dot::before,
	.fm-schedule__status--prog .fm-schedule__dot::after { display: none; }
}

@media (max-width: 820px) {
	.fm-schedule {
		padding: 0;
	}
	.fm-schedule__chrome {
		justify-content: flex-start;
		margin-bottom: 16px;
	}

	/* On the standalone /flights/ page (body carries .fm-public-body)
	   the chrome bar with the theme + density icon toggles sticks to
	   the bottom of the viewport — thumb-reachable, matches /control/.
	   Embedded shortcode usage (no .fm-public-body) keeps the chrome
	   in normal flow at the top. */
	.fm-public-body .fm-schedule {
		padding-bottom: calc(env(safe-area-inset-bottom, 0) + 80px);
	}
	.fm-public-body .fm-schedule__chrome {
		position: fixed;
		bottom: 0;
		left: 0;
		right: 0;
		margin: 0;
		padding: 10px 12px calc(env(safe-area-inset-bottom, 0) + 10px);
		background: var(--fm-bg);
		border-top: 1px solid var(--fm-line);
		z-index: 40;
		gap: 10px;
		justify-content: center;
		box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.06);
	}
	.fm-public-body .fm-schedule[data-theme="dark"] .fm-schedule__chrome {
		box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.45);
	}
	.fm-public-body .fm-schedule__iconbtn { width: 40px; height: 40px; }
	.fm-schedule__head {
		flex-direction: column;
		align-items: flex-start;
		gap: 10px;
		margin-bottom: 20px;
	}
	.fm-schedule__title { font-size: 28px; }
	.fm-schedule__meta { align-items: flex-start; }

	/* Hide the desktop column headers — the two-line row layout below
	   doesn't align with a single header strip. */
	.fm-schedule__headrow { display: none; }

	/* Two-row row layout: pilot + model on line 1, time + status +
	   model-specs on line 2. The model cell becomes display:contents so
	   its model-name and model-specs spans can be placed as grid items
	   on different rows. */
	/* Mobile-tuned density variables so the compact toggle has a
	   visible effect on small screens. Without these the .fm-schedule
	   defaults applied desktop-only padding to a layout that's already
	   tight on a phone. */
	.fm-schedule { --fm-row-pad-y: 14px; --fm-row-pad-x: 16px; }
	.fm-schedule[data-density="compact"] { --fm-row-pad-y: 8px; --fm-row-pad-x: 12px; }

	.fm-schedule__row { padding: 4px 0; }
	/* Mobile rows use a 5-track grid with the time on row 2, column 2.
	   The NOW separator follows the same template so the label drops
	   under the row's HH:MM cell instead of the desktop 90px-wide
	   Time track that doesn't exist here. */
	.fm-schedule__now-line {
		grid-template-columns: 24px auto minmax(0, 1fr) minmax(0, auto) 16px;
		grid-template-rows: auto;
		column-gap: 10px;
		padding-left: var(--fm-row-pad-x);
		padding-right: var(--fm-row-pad-x);
	}
	.fm-schedule__now-label {
		grid-column: 2;
	}
	.fm-schedule__main {
		padding: var(--fm-row-pad-y) var(--fm-row-pad-x);
		display: grid;
		grid-template-columns: 24px auto minmax(0, 1fr) minmax(0, auto) 16px;
		grid-template-rows: auto auto;
		column-gap: 10px;
		row-gap: 4px;
		align-items: center;
	}
	.fm-schedule__cell--num {
		grid-column: 1; grid-row: 1 / span 2;
		align-self: start;
		padding-top: 2px;
	}
	/* Row 1: pilot spans cols 2-3, model name on col 4. */
	.fm-schedule__cell--pilot {
		grid-column: 2 / 4; grid-row: 1;
		min-width: 0;
		font-size: 15px;
		font-weight: 600;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
	.fm-schedule__cell--model { display: contents; }
	.fm-schedule__model-name {
		grid-column: 4; grid-row: 1;
		justify-self: end;
		max-width: 55vw;
		font-size: 13px;
		font-weight: 500;
		color: var(--fm-text);
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
	/* Row 2: time | status | model-specs. */
	.fm-schedule__cell--time {
		grid-column: 2; grid-row: 2;
		font-size: 13px;
		justify-self: start;
	}
	.fm-schedule__cell--status {
		grid-column: 3; grid-row: 2;
		justify-self: start;
	}
	.fm-schedule__model-specs {
		grid-column: 4; grid-row: 2;
		justify-self: end;
		max-width: 55vw;
		font-size: 11.5px;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
		margin: 0;
	}
	.fm-schedule__chev {
		grid-column: 5; grid-row: 1 / span 2;
		align-self: center;
	}

	.fm-schedule__detail-grid {
		grid-template-columns: repeat(2, 1fr);
		padding-left: var(--fm-row-pad-x);
	}
}

/* ---- Cards: pilot + model ---------------------------------------- */

.fm-card {
	display: grid;
	grid-template-columns: 200px 1fr;
	gap: 20px;
	max-width: 800px;
	margin: 24px auto;
	background: #fff;
	border: 1px solid #d8d8d8;
	border-radius: 6px;
	padding: 16px;
}

.fm-card__media img {
	max-width: 100%;
	height: auto;
	border-radius: 4px;
	display: block;
}

.fm-card__title {
	margin: 0 0 12px;
	font-size: 24px;
}

.fm-card__sub {
	margin: 16px 0 8px;
	font-size: 16px;
}

.fm-card__meta {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: 4px 16px;
	margin: 0;
}

.fm-card__meta dt { font-weight: 600; }
.fm-card__meta dd { margin: 0; }

.fm-card__models {
	margin: 0;
	padding: 0 0 0 20px;
}

@media (max-width: 600px) {
	.fm-card { grid-template-columns: 1fr; }
}

/* Apple-style toggle switch ----------------------------------------
   Generic component reused across the operator Settings dialog and the
   pilot notification header. Markup: a <label.fm-toggle> wrapping a
   visually-hidden <input.fm-toggle__input> + a sibling track containing
   the thumb. The active color uses --fm-status-prog (blue) to stay in
   line with the rest of the schedule chrome.

   <label class="fm-toggle">
       <input type="checkbox" class="fm-toggle__input" />
       <span class="fm-toggle__track"><span class="fm-toggle__thumb"></span></span>
       <span class="fm-toggle__label">…</span>
   </label>
*/
.fm-toggle {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	cursor: pointer;
	user-select: none;
}
.fm-toggle__input {
	position: absolute;
	opacity: 0;
	pointer-events: none;
	width: 0;
	height: 0;
}
.fm-toggle__track {
	position: relative;
	display: inline-block;
	width: 44px;
	height: 26px;
	background: #d9d9dc;
	border-radius: 999px;
	transition: background-color 200ms var(--fm-ease, ease);
	flex: 0 0 auto;
}
.fm-toggle__thumb {
	position: absolute;
	top: 2px;
	left: 2px;
	width: 22px;
	height: 22px;
	background: #ffffff;
	border-radius: 50%;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.08);
	transition: transform 200ms var(--fm-ease, ease);
}
.fm-toggle__input:checked ~ .fm-toggle__track {
	background: var(--fm-status-prog, #2563eb);
}
.fm-toggle__input:checked ~ .fm-toggle__track .fm-toggle__thumb {
	transform: translateX(18px);
}
.fm-toggle__input:focus-visible ~ .fm-toggle__track {
	outline: 2px solid var(--fm-text);
	outline-offset: 2px;
}
.fm-toggle__input:disabled ~ .fm-toggle__track {
	opacity: 0.5;
	cursor: not-allowed;
}
.fm-toggle__label {
	font-size: 13px;
	color: var(--fm-text-soft);
	line-height: 1.4;
}
.fm-theme-dark .fm-toggle__track,
.fm-pilot-body.fm-theme-dark .fm-toggle__track,
.fm-control-body.fm-theme-dark .fm-toggle__track {
	background: #3a3a3c;
}
/* The generic .fm-toggle__input:checked rule loses to the dark-mode
   .fm-toggle__track override above (more specific selector), so the
   checked toggle was rendering with the unchecked grey background.
   Re-assert the accent blue in every dark-mode container so On/Off
   reads at a glance in both themes. */
.fm-theme-dark .fm-toggle__input:checked ~ .fm-toggle__track,
.fm-pilot-body.fm-theme-dark .fm-toggle__input:checked ~ .fm-toggle__track,
.fm-control-body.fm-theme-dark .fm-toggle__input:checked ~ .fm-toggle__track {
	background: var(--fm-status-prog, #2563eb);
}
.fm-theme-dark .fm-toggle__thumb,
.fm-pilot-body.fm-theme-dark .fm-toggle__thumb,
.fm-control-body.fm-theme-dark .fm-toggle__thumb {
	background: #fafafa;
}

/* State variants for the pilot notification toggle. The label remains
   muted/disabled visually when permission is denied or the platform
   doesn't support web push at all. */
.fm-toggle[data-notify-state="denied"] {
	cursor: not-allowed;
	color: var(--fm-text-mute);
}
.fm-toggle[data-notify-state="denied"] .fm-toggle__track {
	background: #f0a8a8;
}
.fm-toggle[data-notify-state="unsupported"] {
	cursor: not-allowed;
	color: var(--fm-text-mute);
	opacity: 0.6;
}
.fm-toggle[data-notify-state="unsupported"] .fm-toggle__track {
	background: #d9d9dc;
}

/* NOW separator — inserted by JS between the last completed row and the
   first pending row so readers immediately see where the queue's
   "future" begins. The element reuses .fm-schedule__grid so the label
   drops into the Time column (right under the TIME header). The
   surrounding line is a single absolutely-positioned rule behind the
   label that spans edge to edge; the label has the page background so
   the rule appears "interrupted" where it crosses the label. */
.fm-schedule__now-line {
	position: relative;
	/* Match the row padding so the inner grid columns line up with
	   the row grid above — otherwise the NOW label drifts to the
	   left by --fm-row-pad-x because the line has no padding but the
	   rows do. */
	padding: 6px var(--fm-row-pad-x);
	margin: 4px 0;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 11px;
	color: var(--fm-status-prog);
	letter-spacing: 0.04em;
	text-transform: uppercase;
}
.fm-schedule__now-line::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	height: 1px;
	background: var(--fm-status-prog);
	opacity: 0.45;
}
.fm-schedule__now-label {
	grid-column: 2 / span 1;
	justify-self: start;
	/* Match the slot-lane label pill style: coloured background tint
	   so NOW reads as a coloured chip on top of the horizontal rule. */
	background: var(--fm-status-prog-bg);
	/* Symmetric padding so the rule behind the label "breathes" on
	   both sides instead of touching the N of NOW. */
	padding: 0 8px;
	margin-left: -8px;
	white-space: nowrap;
	position: relative;
	z-index: 1;
}
/* Hide the grey row border on the last completed row just above the
   NOW separator, so the two lines don't double up. */
.fm-schedule__row:has(+ .fm-schedule__now-line) {
	border-bottom: none;
}

/* Simplified tables mode ------------------------------------------
   When the operator flips OPT_SIMPLE_TABLES on, the Model column,
   the per-row model specs cell, the expand chevron and the
   expandable detail drawer all disappear so the schedule reads as
   a clean # / Time / Pilot / Status table. The grid template
   collapses to four tracks at every breakpoint. */
.fm-schedule--simple .fm-schedule__grid {
	grid-template-columns:
		32px           /* # */
		90px           /* time */
		1fr            /* pilot */
		130px;         /* status */
}
.fm-schedule--simple .fm-schedule__col--model,
.fm-schedule--simple .fm-schedule__col--chev,
.fm-schedule--simple .fm-schedule__cell--model,
.fm-schedule--simple .fm-schedule__chev,
.fm-schedule--simple .fm-schedule__detail {
	display: none;
}
.fm-schedule--simple .fm-schedule__main {
	cursor: default;
}
@media (max-width: 820px) {
	.fm-schedule--simple .fm-schedule__main {
		grid-template-columns: 24px auto minmax(0, 1fr) minmax(0, auto);
	}
	/* On mobile pilot used to span cols 2-3 to leave room for the
	   model name on col 4; with model gone, let pilot fill the rest
	   of row 1. */
	.fm-schedule--simple .fm-schedule__cell--pilot {
		grid-column: 2 / -1;
	}
	.fm-schedule--simple .fm-schedule__now-line {
		grid-template-columns: 24px auto minmax(0, 1fr) minmax(0, auto);
	}
}

/* Pilot-requested preferred slot chip ------------------------------
   Rendered next to the row status (and in the pilot portal's My
   flights table) when the operator captured a pilot's preferred
   2-hour window at flight encoding time. Three variants:
     --match  the estimated start falls inside the preferred window
     --miss   the estimated start falls outside it
     --none   no cascade computed yet (no anchor / no event_start)
*/
.fm-schedule__pref-chip {
	display: inline-block;
	margin-left: 8px;
	padding: 2px 8px;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 10.5px;
	letter-spacing: 0.02em;
	border-radius: 4px;
	white-space: nowrap;
	vertical-align: middle;
}
.fm-schedule__pref-chip--match {
	color: var(--fm-status-done);
	background: var(--fm-status-done-bg);
}
.fm-schedule__pref-chip--miss {
	color: var(--fm-status-wait);
	background: var(--fm-status-wait-bg);
}
.fm-schedule__pref-chip--none {
	color: var(--fm-text-mute);
	background: var(--fm-hover);
}

/* Allocated-time hint shown beside the status badge in slot-lanes
   mode. Same mono family as the status text, dimmed so it reads as
   metadata rather than a second status. */
.fm-schedule__slot-mins {
	margin-left: 8px;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 11px;
	color: var(--fm-text-mute);
	vertical-align: middle;
	white-space: nowrap;
}

/* Time-slot lanes ---------------------------------------------------
   When the operator flips OPT_SLOT_LANES on, the schedule renders
   horizontal separators at every 2-hour slot boundary, drawn in the
   same shape as .fm-schedule__now-line but tinted by usage so empty
   / busy zones jump out without scanning the rows.

   Variants:
     --empty grey   (no flights yet in this slot)
     --low   green  (≤ 80% of the 120-min budget used)
     --high  orange (80–100% used)
     --over  red    (slot_minutes sum exceeds 120 — over-booked)
*/
.fm-schedule__slot-line {
	position: relative;
	padding: 6px var(--fm-row-pad-x);
	margin: 4px 0;
	font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 11px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
}
.fm-schedule__slot-line::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	height: 1px;
	background: currentColor;
	opacity: 0.35;
}
.fm-schedule__slot-label {
	grid-column: 2 / span 1;
	justify-self: start;
	background: var(--fm-bg);
	padding: 0 8px;
	margin-left: -8px;
	white-space: nowrap;
	position: relative;
	z-index: 1;
}
.fm-schedule__slot-line--empty      { color: var(--fm-text-mute); }
.fm-schedule__slot-line--low        { color: var(--fm-status-done); }
.fm-schedule__slot-line--high       { color: var(--fm-status-wait); }
.fm-schedule__slot-line--over       { color: var(--fm-status-cancel); }
.fm-schedule__slot-line--unassigned { color: var(--fm-status-wait); }
.fm-schedule__slot-line--empty      .fm-schedule__slot-label { background: var(--fm-hover); }
.fm-schedule__slot-line--low        .fm-schedule__slot-label { background: var(--fm-status-done-bg); }
.fm-schedule__slot-line--high       .fm-schedule__slot-label { background: var(--fm-status-wait-bg); }
.fm-schedule__slot-line--over       .fm-schedule__slot-label { background: var(--fm-status-cancel-bg); }
.fm-schedule__slot-line--unassigned .fm-schedule__slot-label { background: var(--fm-status-wait-bg); }
@media (max-width: 820px) {
	/* Match the row grid override (5 tracks on /flights/ + /pilot/,
	   6 tracks on /operator/) so the slot label drops under the row's
	   Time cell exactly like the NOW separator does. */
	.fm-schedule__slot-line {
		grid-template-columns: 24px auto minmax(0, 1fr) minmax(0, auto) 16px;
		grid-template-rows: auto;
		column-gap: 10px;
	}
	.fm-schedule__slot-label { grid-column: 2; }
	.fm-control .fm-schedule__slot-line {
		grid-template-columns: 22px auto auto minmax(0, 1fr) auto 32px;
	}
}

/* Slot sections (when slot_lanes are on) ---------------------------
   Each 2-hour slot wraps its separator + rows in a <section> so the
   operator can see at a glance which zones are empty (light grey
   background + "No flights planned yet" placeholder), which are
   busy (coloured chip + rows underneath), and so the cross-slot
   drag-and-drop has a real drop target per slot. */
.fm-schedule__slot-section {
	display: block;
	position: relative;
	border-radius: 6px;
	transition: background-color 120ms var(--fm-ease);
}
/* Empty slot tint sits ONLY on the slot-line chip (the
   "15:00 → 17:00 · 0 flight · 0 / 120 min" label) so the operator
   spots empty windows at a glance without the whole section
   becoming a flat grey block. The 'No flights planned yet'
   placeholder underneath stays on the page background. */
.fm-schedule__slot-section--empty {
	background: transparent;
}
.fm-schedule__slot-empty {
	padding: 18px var(--fm-row-pad-x);
	min-height: 60px;
	color: var(--fm-text-mute);
	font-size: 13px;
	font-style: italic;
	text-align: left;
}
/* Drop-target highlight while a row is being dragged across sections.
   The operator-side JS toggles `is-drop-target` on the candidate
   section and `is-drop-blocked` when the target slot is already full. */
.fm-schedule__slot-section.is-drop-target {
	background: var(--fm-status-prog-bg);
	outline: 2px dashed var(--fm-status-prog);
	outline-offset: -2px;
}
.fm-schedule__slot-section.is-drop-blocked {
	background: var(--fm-status-cancel-bg);
	outline: 2px dashed var(--fm-status-cancel);
	outline-offset: -2px;
	cursor: not-allowed;
}
