/*
 * The studio design system, vendored.
 *
 * The colour half, and THREE FACES out of five.
 *
 * `fonts.css` is carried whole so `bin/sync` can check it, but only three
 * faces are here to go with it. The other two declarations --- and SN Pro's
 * italic, which is a fourth file this repo has no use for --- resolve to their
 * fallbacks, and nothing renders them.
 *
 * SN PRO, the face design calls the canvas one, because a calendar you read a
 * year of at a time is a canvas-first product. 122 KB, upright only. It is
 * wired up further down, where --font-ui is pointed at --font-canvas, and the
 * note there is the argument for taking it over the system's default chrome
 * face.
 *
 * SHANTELL SANS, because the design system blesses a hand for "a note, a
 * scrawled event", and a trip name is that. 432 KB, which is most of what this
 * app weighs, and worth it for something the person wrote themselves.
 *
 * MONASPACE NEON at 400, because design describes the tool face as being for
 * "numbers, code, calculator surfaces" and the day counter is all three. 43 KB,
 * for one weight of the three it declares.
 *
 * The wordmark carries a face of its own, which is not one of these and does
 * not come from design at all --- see Ranchers, further down.
 *
 * All three licences travel with their faces, in src/design/fonts/. The OFL
 * requires it and the design repo carries none for any of its five, so these
 * were fetched from upstream rather than copied.
 *
 * Everything here is byte-for-byte what design has, and leveret's `bin/sync`
 * carries a row for each so a drifted copy is reported rather than discovered.
 *
 * Taken rather than referenced because this repo has no build step to copy one
 * in, and because nothing may assume another repo's location on disk.
 */
@import "./design/colors.css";
@import "./design/fonts.css";
@import "./design/semantic.css";

/*
 * Product colours, and the ONE place a schengen concept meets a studio token.
 *
 * The system's rule is that app code references semantic tokens and never a raw
 * step like --sand-9. These aliases exist because "the day you were in the
 * area" is a schengen idea that the shared vocabulary has no word for; each one
 * resolves to a semantic token and none of them reaches past it.
 */
:root {
	/*
	 * The accent, re-pointed to blue. semantic.css says a product may do this
	 * for personality, and names amber as the studio default; the whole family
	 * moves together, because leaving --color-accent-text on amber-11 beside a
	 * blue-9 fill is how an accent ends up meaning two colours.
	 *
	 * BLUE AND AMBER, which is the flag of the union whose border this counts.
	 * It was green and tomato, which said nothing in particular.
	 *
	 * blue-9 is SINGLE-VALUED in Radix --- no light-dark() pair --- so the mark
	 * is the same in both schemes and needs no dark override. That is the same
	 * property that made indigo-9 right for napkin and orange-9 for checkmark,
	 * and white on it measures Lc 64.6, a shade ahead of the green it replaces.
	 */
	--color-accent: var(--blue-9);
	--color-accent-hover: var(--blue-10);
	--color-accent-text: var(--blue-11);
	--color-accent-subtle: var(--blue-3);
	--color-accent-border: var(--blue-7);

	/*
	 * SN PRO, which the design system calls the canvas face and which the
	 * studio's other web app also sets as its UI face. The system's default is
	 * --font-chrome (Instrument Sans); a canvas-first product may take this one
	 * instead, and a calendar you read a year of at a time is that.
	 *
	 * It replaces system-ui, which was never a decision so much as the absence
	 * of one --- and which rendered a different face on every machine, at a
	 * different size, which is why nothing here quite matched the other
	 * products however the numbers were tuned.
	 */
	--font-ui: var(--font-canvas);

	--ink: var(--color-text);
	--ink-quiet: var(--color-text-muted);
	--page: var(--color-bg);
	--sunken: var(--color-surface);
	--line: var(--color-border-subtle);
	/*
	 * Where one month meets the next: the same tomato as today's mark, and two
	 * pixels of it.
	 *
	 * Sharing a colour with today is the point rather than a collision. Amber
	 * is what the CALENDAR says about itself --- this month ends here, you are
	 * standing here --- and blue is what the PERSON has said. Two vocabularies,
	 * and a day the person marked never speaks in the calendar's voice.
	 *
	 * NOT --color-danger, which is tomato in the shared layer. The step wanted
	 * is amber-9 and the semantic name for it would be wrong twice over: this is
	 * neither danger nor the accent.
	 */
	--month-line: var(--amber-9);

	/*
	 * A day spent inside the area, drawn as a bar over the day.
	 *
	 * blue-5 AND blue-12, and both numbers were measured: Lc 80.2 in daylight
	 * and 78.6 at night --- over the body-text bar, and near enough identical in
	 * both schemes to need no override. The green pair it replaces measured 79.4
	 * and 79.5, so this is the same trade in another hue.
	 *
	 * A 9-step with white on it was the first attempt and is Lc 63.7: APCA's
	 * floor for large or bold text and under what a name at this size wants,
	 * which is exactly how it read. Step 3 measures better still, at 90, and is
	 * too pale against the page to read as an object. A bar has to look like a
	 * thing before it has to be legible.
	 *
	 * THIS IS THE ONE PLACE THAT REACHES PAST THE SEMANTIC LAYER. The layer names
	 * step 3 (subtle), 7 (border), 9 (accent) and 11 (accent text), and nothing
	 * at 5 --- there is no name for a surface that is filled but quiet. The
	 * proper fix is a token in design's semantic.css, which would serve every
	 * product; until then this says so out loud rather than pretending.
	 */
	--present: var(--blue-5);
	--present-ink: var(--blue-12);

	/*
	 * The menu pane, which is the one bold surface here.
	 *
	 * blue-10, and NOT VIA THE ACCENT: the accent family stays on blue-9, so
	 * the countdown, the verdict marks and the span being dragged go on speaking
	 * in it. This pane is furniture and points at the step directly.
	 *
	 * WHAT THE NUMBERS ACTUALLY SAY, measured in APCA rather than WCAG 2 --- the
	 * method Radix's own scales are built against, from an implementation checked
	 * against Radix's published Lc 60.8 for white on orange-9:
	 *
	 *   blue-9, one value         white 64.6   dimmed 56.0
	 *   blue-10, light half       white 68.6   dimmed 60.3
	 *   blue-10, dark half        white 58.6   dimmed 51.4
	 *
	 * (Steps by name, not by value. bin/check greps this file for raw colour in
	 * comments as well as in rules, and it is right to: a transcribed hex is a
	 * scale value with no way of knowing when the scale moves under it.)
	 *
	 * So this is BETTER BY DAY AND WORSE BY NIGHT, and the pane no longer looks
	 * the same in both schemes --- blue-10 is a light-dark pair where blue-9 is
	 * a single value. Body text wants Lc 75 and nothing here reaches it either
	 * way, which is why the two quiet things on this pane are named exemptions
	 * rather than an accident. Chosen with the figures in hand.
	 */
	--menu-bg: var(--blue-10);
	--menu-fg: var(--white);
	--menu-plate: var(--black-a6);

	/*
	 * Today, which is a drawn blob rather than a colour on the text.
	 *
	 * amber-9, and it could not be blue: blue already means "you were inside the
	 * area", and a blue ring round today would be saying that. It is a
	 * SINGLE-VALUED 9-step, so the mark is identical in both schemes.
	 *
	 * BLACK ON IT, NOT WHITE, and this is the one thing the swap from tomato
	 * could not carry over. Amber is one of the bright 9-steps: white on it
	 * measures Lc 29.5, under every floor there is, where black measures 77.7.
	 * Tomato took white at 70.5 and amber cannot.
	 */
	--today-mark: var(--amber-9);
	--today-ink: var(--black);

	/*
	 * Out of days, or about to be deleted --- said in words rather than drawn.
	 * tomato-11, because both of those are the ordinary meaning of red rather
	 * than anything about this product: 71.7 against the page in daylight, 60.8
	 * at night. The 9 is not a candidate for text at any size, at 36.2 against
	 * the dialog in the dark.
	 */
	--over-ink: var(--tomato-11);

	/*
	 * A VERDICT IS NOT THE BRAND. Blue and amber say which product this is;
	 * these two say yes and no, and they are the one place where the ordinary
	 * meanings of green and red are worth more than a palette. Somebody
	 * checking whether a trip fits should not have to learn a colour first.
	 *
	 * green-11 measures Lc 71 against the dialog and tomato-11 71.7, near enough
	 * the same weight --- so neither answer shouts louder than the other.
	 */
	--verdict-yes: var(--green-11);
	--verdict-no: var(--tomato-11);

	/*
	 * The ring around a morning that hands days back. sand-12, which is
	 * the system's primary ink --- NOT the calendar's tomato, deliberately.
	 * Tomato is the calendar telling you where you are; this is a fact about
	 * the record, and it belongs in the voice the dates are already written in.
	 */
	--reset-ring: var(--color-text);

	/*
	 * A trip's name where it is being identified rather than drawn --- in the
	 * note pinned to a ringed day. gray-12, which the system has a step for and
	 * no name for: --color-text is sand-12, the page's own ink, and the point
	 * here is to sit a shade apart from the sentence around it. Same
	 * reconciliation --month-label makes, and the alias is where it gets said.
	 */
	--trip-name: var(--gray-12);

	/*
	 * The punctuation holding a line together --- a middot, an arrow --- which is
	 * structure rather than words and should not be read as either. gray-10, a
	 * step the system has and has no name for.
	 *
	 * ONE TOKEN FOR BOTH SURFACES, the reset note and the confirmation. They sit
	 * on the same background, and punctuation stepping back by a different amount
	 * in two places is two decisions where there is one thing.
	 */
	--separator: var(--gray-10);

	/*
	 * The month spine up the left edge of every week's first day. sand-8, which
	 * the system already names --- so this reaches past nothing, and the rule that
	 * app code never touches a raw step holds with no exception.
	 *
	 * The semantic name is about borders and this is a mark, which is exactly
	 * what the alias is for: the step is right and the name is not. The cost of
	 * the indirection is real and worth knowing --- re-point --color-border-hover
	 * for a border reason and this moves with it.
	 *
	 * MEASURED, because "very light" has an edge: sand-8 is Lc 35.6 against the
	 * page in daylight and Lc 20.1 at night, where its endpoint sits close to the
	 * background. Decorative in both and fainter in the dark. That is the
	 * intended register; if the spine ever disappears at night, this is the line
	 * that did it.
	 */
	--month-label: var(--color-border-hover);
}

* {
	box-sizing: border-box;
}

/*
 * THE DOCUMENT IS THE SCROLLER. Everything that used to belong to the calendar
 * div --- snapping, the scroll padding, the hidden scrollbar --- lives up here
 * now, because those properties only do anything on the element that actually
 * scrolls.
 *
 * It reads as a detail and it is the whole reason the calendar reaches the top
 * and bottom of a phone. A mobile browser overlays its toolbars on a page that
 * scrolls and collapses them as you go; a page whose document never moves is
 * held to the small viewport and stops in a white strip at each end. Measured
 * on a 390x844 iPhone: 699 of 844, starting 47 down.
 */
html {
	/*
	 * The chrome floats on top of the grid, so both snapping and any
	 * scroll-into-view have to stop short of it. Without this a snapped month
	 * start lands behind the weekday strip and reads as a calendar that begins
	 * on the 8th. Set from script --- the chrome is as tall as its contents.
	 */
	scroll-padding-top: var(--chrome-height, 0px);

	/*
	 * NO SNAPPING, and it is a decision rather than an oversight.
	 *
	 * The calendar used to come to rest at the top of a month. Two attempts at
	 * it and both were worse than not doing it. The browser's own snapping
	 * needs a target that holds still, and every row here is recycled as you
	 * travel --- so the engine chases a target that keeps becoming a different
	 * week and the page walks off on its own, 24,840px after the hand came off,
	 * measured in WebKit; on a phone it ran to 1995 and redrew the whole way.
	 * Doing it in script instead meant waiting for the scroll to stop and THEN
	 * animating, which is a second movement after the gesture has finished, and
	 * it reads as one.
	 *
	 * So the calendar rests where you leave it. Space and the arrow keys still
	 * step month to month, which is where the argument for resting on a month
	 * really lived.
	 */

	/*
	 * No scrollbar. Across thirty-six years the thumb is a few pixels tall and
	 * a pixel of drag is months, so it was never a handle anybody could use ---
	 * it was a progress indicator for a journey nobody makes. Wheel, trackpad,
	 * touch, Page Up and the arrow keys all still work, and Today is the way
	 * back. If a way to navigate by year is wanted, it wants designing rather
	 * than borrowing this.
	 */
	scrollbar-width: none;

	/*
	 * HERE AND NOT ON THE BODY, and the difference is whether the calendar
	 * snaps to months at all.
	 *
	 * An element with a non-visible overflow in either axis is a scroll
	 * container, and snap targets belong to the nearest one. Set on the body,
	 * the body becomes that container --- it snaps to nothing, because the
	 * snap-type is up here --- and every month boundary silently stops being a
	 * resting place. It fails quietly: nothing errors, scrolling still works,
	 * and a twenty-pixel nudge moves twenty pixels instead of the six hundred
	 * it used to.
	 *
	 * It is needed at all because the trip bar hangs a pixel off the left of
	 * the first column on purpose, to meet the month boundary.
	 */
	overflow-x: hidden;

	/*
	 * Scroll anchoring is for pages that grow above the reader --- a comment
	 * thread, a lazy image --- and it works by quietly moving the scroll
	 * position to keep one element still. Every row here is rewritten as you
	 * travel, so it would be correcting for content that did not really change
	 * and fighting the arithmetic that decides what to draw.
	 */
	overflow-anchor: none;
}

html::-webkit-scrollbar {
	display: none;
}

html,
body {
	/*
	 * NO HEIGHT, and no overflow either. Both were here to make the page itself
	 * immovable so that only the inner div scrolled, and both are exactly what
	 * pinned this to the small viewport. The body is now as tall as the field
	 * inside it, which is what gives the document something to scroll.
	 */
	margin: 0;
}

body {
	display: flex;
	flex-direction: column;
	background: var(--page);
	color: var(--ink);
	font-family: var(--font-ui);
	/*
	 * 450, which is what the studio's other web app sets. SN Pro is variable
	 * from 200 to 900, so this is a real instance rather than a rounding --- a
	 * shade heavier than 400 and the reason the page reads as settled rather
	 * than thin.
	 */
	font-weight: 450;
	font-size: var(--text-sm);
	line-height: 1.4;
}

/*
 * schengen's wordmark face, carried by this repo rather than by design. napkin
 * does the same with Chango and checkmark with Life Savers: a wordmark is one
 * product's mark, not a studio voice, and putting it in the shared system would
 * hand every other surface a font only this one has any business setting.
 *
 * SUBSET TO THE NINE LETTERS AND THE DOT THE NAME USES --- c, e, f, g, h, i,
 * n, s, y and a full stop --- which is what takes it to 3.6 KB. Generated once
 * by Google's CSS API with ?text= and then VENDORED, never fetched at runtime:
 * conventions/boundaries.md rules out CDN requests for fonts, on privacy (a
 * third-party request is a third party watching) and longevity (it still works
 * when the CDN does not).
 *
 * ONE WEIGHT, 400, and it is the only one Ranchers has. Asking for bold
 * anywhere would fetch nothing and let the browser synthesise it, which on a
 * face this heavy turns into a smear. Nothing should ask: this face draws the
 * wordmark and has no other job.
 *
 * Regenerating it, when the name changes and not before:
 *   curl -A '<a browser UA>' \
 *     'https://fonts.googleapis.com/css2?family=Ranchers&text=schengen.fyi'
 * then download the woff2 that CSS names. The user agent matters: Google serves
 * a different format to anything it does not recognise as a browser. The subset
 * covers the name and NOTHING ELSE, so this font will render nothing but the
 * word "schengen.fyi" --- which is the point, and also the trap if anyone
 * reaches for it elsewhere.
 *
 * OFL, like the other two vendored faces. Ranchers is by Impallari Type ---
 * Pablo Impallari and Brenda Gallo --- and the licence travels with the file at
 * src/fonts/Ranchers-OFL.txt. The OFL reserves the font name, which is why the
 * vendored copy keeps it.
 */
@font-face {
	font-display: swap;
	font-family: "Ranchers";
	font-style: normal;
	font-weight: 400;
	src: url("./fonts/Ranchers-schengen.woff2") format("woff2");
	unicode-range: U+2e, U+63, U+65-69, U+6e, U+73, U+79;
}

.visually-hidden {
	block-size: 1px;
	clip-path: inset(50%);
	inline-size: 1px;
	overflow: hidden;
	position: absolute;
	white-space: nowrap;
}

/*
 * The floating chrome --- napkin's and checkmark's arrangement, with the
 * weekday strip brought along.
 *
 * The calendar runs the full height of the viewport and scrolls UNDER this.
 * That is the whole point and it is also the hazard: an overlay hides the top
 * of whatever you scrolled to, so the scroller carries a scroll-padding-top of
 * the same height and the month a snap lands on arrives BELOW the chrome
 * rather than behind it. Set from script, because this is as tall as its own
 * contents and nothing in CSS can hand that number to the scroller.
 */
#chrome {
	position: fixed;
	inset-block-start: 0;
	inset-inline: 0;
	z-index: 20;
	display: flex;
	flex-direction: column;
	align-items: center;
	padding-block-start: calc(0.5rem + env(safe-area-inset-top, 0px));
	/*
	 * It overlaps the grid, so the WRAPPER must not eat clicks meant for the day
	 * under it. The chip and the strip take their own back --- the strip because
	 * it is opaque, and a click passing through it would land on a day nobody
	 * can see.
	 */
	pointer-events: none;
}

#menu {
	/* Containing block for .panel, which hangs off it at 100%. */
	position: relative;
	margin-block-end: 0.5rem;
	pointer-events: auto;
}

#menu > summary {
	display: flex;
	align-items: center;
	/* 0.25rem gap and 0.25rem/0.5rem padding: checkmark's chip, measured. */
	gap: 0.25rem;
	padding: 0.25rem 0.5rem;
	border-radius: 8px;
	list-style: none;
	cursor: pointer;
	/*
	 * It is a button, so it must not select like text --- a double-click on the
	 * chip would otherwise highlight the wordmark, and a long-press on iOS raises
	 * the selection handles.
	 */
	-webkit-user-select: none;
	user-select: none;
}

#menu > summary::-webkit-details-marker {
	display: none;
}

#menu > summary:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: 2px;
}

/*
 * Open state: the chip takes a plate and the chevron points back up at itself.
 * Without this the trigger looks identical open and closed.
 */
#menu[open] > summary {
	background: var(--sunken);
}

#menu[open] > summary .chevron {
	transform: rotate(180deg);
}

/*
 * 2.5rem, which is checkmark's mark measured. The drawing is an 18-unit
 * viewBox, so a 1.5 stroke lands at about 3.3px here --- the same weight the
 * other two marks carry at this size.
 */
#menu > summary .mark {
	display: block;
	block-size: 2.5rem;
	inline-size: 2.5rem;
	color: var(--color-accent);
}

#menu > summary .wordmark {
	color: var(--ink);
	font-family: "Ranchers", Georgia, serif;
	/*
	 * 1.75rem, which is checkmark's wordmark measured rather than guessed at.
	 * The first pass here was 1.25rem and the chip came out 28px tall against
	 * the other two products' 48 and 50 --- half the size, on a page where the
	 * name is the only branding there is.
	 */
	font-size: 1.75rem;
	font-weight: 400;
	line-height: 1;
	/* Flex centres the text BOX, and "schengen" has a descender in the g that
	   drags the visual mass low. Lift by roughly half of it. */
	transform: translateY(-0.06em);
}

#menu > summary .chevron {
	display: block;
}

#menu > summary .chevron path {
	fill: var(--ink);
}

@media (prefers-reduced-motion: no-preference) {
	#menu > summary .chevron {
		transition: transform 120ms ease;
	}
}

/*
 * A BOLD card, not a neutral one, and it sits on the page rather than hovering
 * in a haze: hard shadow, no blur. The colour and what it costs are set out at
 * --menu-bg.
 */
#menu .panel {
	position: absolute;
	top: calc(100% + 0.25rem);
	left: 50%;
	transform: translateX(-50%);
	z-index: 10;
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	/* 16rem, napkin's. A panel narrower than the chip above it reads as a
	   dropdown that has lost its nerve. */
	min-width: 16rem;
	padding: var(--space-4);
	border: none;
	border-radius: var(--radius-md);
	background: var(--menu-bg);
	color: var(--menu-fg);
	/* 16px, and a contrast floor rather than a taste. See --menu-bg. */
	font-size: var(--text-base);
	/* Hard, no blur, so it sits on the page rather than hovering in a haze. */
	box-shadow: 4px 4px 0 var(--black-a3);
	/* Zoom out from the top edge, so the panel reads as emerging from the chip
	   above it rather than fading in place. */
	transform-origin: top center;
}

/*
 * The panel only exists in the DOM while [open], so this plays on every open.
 * Close is left to the native instant-remove: animating it would mean deferring
 * every `menu.open = false` in menu.ts, which is more machinery than a nicety
 * earns --- and the zoom-in is the part the eye registers.
 *
 * The keyframe carries translateX(-50%) forward, or centring is lost the moment
 * the scale takes over.
 */
@keyframes menu-pop {
	from {
		opacity: 0;
		transform: translateX(-50%) translateY(-0.4rem) scale(0.96);
	}
	to {
		opacity: 1;
		transform: translateX(-50%) translateY(0) scale(1);
	}
}

#menu[open] .panel {
	animation: menu-pop var(--duration-fast) var(--ease);
}

@media (prefers-reduced-motion: reduce) {
	#menu[open] .panel {
		animation: none;
	}
}

/*
 * The one action the panel is most often opened for, in black rather than the
 * plate the others wear. Three identical pills make somebody read all three;
 * one darker one is answered at a glance.
 */
#menu .panel #today {
	background: var(--black);
}

#menu .panel #today:hover {
	background: var(--black-a11);
}

/*
 * A flag in the chrome: in the country control, and anywhere else a flag ends
 * up beside a line of type. Sized in ems so it follows what it sits next to
 * rather than being a fixed pebble beside type that changes.
 */
.flag {
	flex: none;
	inline-size: 1.15em;
	block-size: 1.15em;
	vertical-align: -0.2em;
}

/*
 * A flag on a stay is a FIXED 24px, not an em: the bar is one height whatever
 * the name is set in, and a flag that grew with the label would crowd it. The
 * left margin keeps it off the pill's rounded end, where a square sitting flush
 * against the curve reads as a mistake.
 */
.day__flag {
	flex: none;
	inline-size: 24px;
	block-size: 24px;
	margin-inline-start: 0.25rem;
}

/*
 * The attribution the flags are licensed on. At the very bottom, under a rule,
 * smaller than anything else in the panel --- the licence asks for credit, not
 * for advertising, and the other products keep theirs the same way.
 */
.menu__note {
	margin: 0;
	padding-block-start: var(--space-3);
	border-block-start: 1px solid var(--white-a6);
	color: var(--white-a11);
	font-size: 11px;
}

.menu__note a {
	color: inherit;
	text-decoration: none;
}

.menu__note a:hover {
	color: var(--menu-fg);
	text-decoration: underline;
}

/*
 * The flag sits INSIDE the country control, on the left, with the select
 * padded out of its way. A native list cannot carry images --- see the note in
 * confirm.ts --- so this is where one can go without giving up the control the
 * browser and the phone already know how to work.
 */
.country {
	position: relative;
	display: block;
}

.country__flag {
	position: absolute;
	inset-block-start: 50%;
	inset-inline-start: var(--space-3);
	transform: translateY(-50%);
	pointer-events: none;
}

.country__flag[hidden] {
	display: none;
}

/*
 * FLAGS IN THE LIST ITSELF, where the browser can draw them --- and on the
 * closed control too, because <selectedcontent> in the markup clones whatever
 * is chosen. Where that happens the flag beside the control is a second copy of
 * the same thing, so it goes, and the select takes its padding back.
 */
@supports (appearance: base-select) {
	#confirm select,
	#confirm select::picker(select) {
		appearance: base-select;
	}

	#confirm select option {
		display: flex;
		align-items: center;
		gap: var(--space-2);
		padding: var(--space-2) var(--space-3);
	}

	#confirm select option:hover {
		background: var(--color-accent-subtle);
	}

	/*
	 * The control keeps its own box: base-select hands you a bare button, which
	 * shrinks to its text unless told otherwise, and the field beside it is full
	 * width.
	 */
	/*
	 * THE SELECT IS THE ROW, not the button. ::picker-icon is a pseudo-element of
	 * the select and a sibling of the author button, so a block-level select puts
	 * the chevron on a line of its own --- which is what it did.
	 */
	#confirm select {
		display: flex;
		align-items: center;
		gap: var(--space-2);
		inline-size: 100%;
	}

	#confirm select button {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-2);
		flex-wrap: nowrap;
		inline-size: 100%;
		padding: 0;
		border: none;
		background: none;
		color: inherit;
		font: inherit;
	}

	/*
	 * The cloned option is a row of its own: flag, then name. min-inline-size
	 * because a flex item will not shrink below its content by default, which
	 * pushed the chevron onto a second line.
	 */
	#confirm select selectedcontent {
		display: flex;
		align-items: center;
		gap: var(--space-2);
		min-inline-size: 0;
		overflow: hidden;
		white-space: nowrap;
		text-overflow: ellipsis;
	}

	#confirm select::picker-icon {
		flex: none;
	}

	.country__flag {
		display: none;
	}

	#confirm select {
		padding-inline-start: var(--space-3);
	}
}

/* The actions sit closer to each other than to anything else in the panel. */
#menu .panel .actions {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

/*
 * FILLED PILLS, CENTRED, the way the other products do it. These carry the
 * panel's only actions, so they should read as things to press rather than as
 * rows in a list --- which is what a small radius and a left-aligned label make
 * them look like.
 *
 * The plate darkens the pane rather than lightening it, which is what keeps
 * white text on it at Lc 90.0 instead of the pane's own 63.7.
 */
#menu .panel button {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.45rem;
	padding: 0.5rem 0.9rem;
	border: none;
	border-radius: var(--radius-full);
	background: var(--menu-plate);
	color: inherit;
	font: inherit;
	font-size: inherit;
	text-align: center;
	cursor: pointer;
}

#menu .panel button:hover {
	background: var(--black-a7);
}

/* Nothing behind it yet: it says so rather than accepting a click and shrugging. */
#menu .panel button:disabled {
	cursor: not-allowed;
	color: var(--white-a11);
	opacity: 0.55;
}

#menu .panel button:disabled:hover {
	background: var(--menu-plate);
}

/*
 * Strokes are currentColor, so this dims the glyph without touching the label
 * beside it --- a step quieter than the text and still well past the bar for a
 * graphic that carries meaning.
 */
#menu .panel button .icon {
	flex: none;
	color: var(--white-a10);
}

/*
 * NOT A PILL. A setting is not an action, and giving it the same filled shape
 * as the two buttons above made the panel read as four things to press. It is a
 * row with a switch on the end: no fill, no padding to speak of, and a size
 * down, which is the hierarchy the attribution below it already uses.
 */
#menu .panel button[role="switch"] {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: 0;
	border-radius: 0;
	background: none;
	font-size: var(--text-sm);
	text-align: left;
}

#menu .panel button[role="switch"]:hover {
	background: none;
	color: var(--menu-fg);
}

#menu .panel .switch {
	flex: none;
	inline-size: 2.1rem;
	block-size: 1.15rem;
	border-radius: var(--radius-full);
	background: var(--black-a7);
	position: relative;
}

#menu .panel .switch::after {
	content: "";
	position: absolute;
	inset-block-start: 0.15rem;
	inset-inline-start: 0.15rem;
	inline-size: 0.85rem;
	block-size: 0.85rem;
	border-radius: var(--radius-full);
	background: var(--menu-fg);
}

/*
 * Black, not the tomato. Tomato is what the CALENDAR says about itself --- this
 * month ends here, you are standing here --- and a setting is not the calendar
 * speaking. On a green pane it also read as a warning, which an ordinary
 * preference is not.
 */
#menu .panel [aria-checked="true"] .switch {
	background: var(--black);
}

#menu .panel [aria-checked="true"] .switch::after {
	inset-inline-start: auto;
	inset-inline-end: 0.15rem;
}

@media (prefers-reduced-motion: no-preference) {
	#menu .panel .switch,
	#menu .panel .switch::after {
		transition:
			background var(--duration-fast) var(--ease),
			inset-inline-start var(--duration-fast) var(--ease);
	}
}

#menu .panel button:focus-visible {
	outline: 2px solid var(--white);
	outline-offset: 2px;
}

/*
 * minmax(0, 1fr) RATHER THAN 1fr, and on a phone that is the difference between
 * a calendar and a mess. A grid item's min-width is auto, so `1fr` columns
 * refuse to shrink below their own content: seven cells each insisting on room
 * for "SUN 30" came to about 600px, the row overflowed a 390px screen, and two
 * days of every week were simply off the side of the page.
 */
.weekdays,
.week {
	display: grid;
	grid-template-columns: repeat(7, minmax(0, 1fr));
}

.weekdays {
	width: 100%;
	pointer-events: auto;
	padding-right: var(--scrollbar-gutter, 0px);
	border-block: 1px solid var(--line);
	background: var(--sunken);
	color: var(--ink-quiet);
	font-size: 0.7rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.weekdays span {
	padding: 0.4rem 0.5rem;
}

/*
 * PARKED, not removed. The strip works and reads correctly; what is unsettled
 * is whether a fixed row of weekday names is the right thing above a calendar
 * that snaps to months and may grow a count and a rolling window above it.
 *
 * Left in the markup and still built by main.ts, because the question is where
 * this information belongs rather than whether it is wanted, and a deleted
 * strip would have to be rewritten to ask again. One line to bring back.
 *
 * Nothing else needs adjusting when it returns: the chrome measures its own
 * height into --chrome-height, so the calendar's top inset follows.
 */
.weekdays {
	display: none;
}

.calendar {
	/*
	 * svh, NOT vh. On a phone they are different viewports and the difference is
	 * a row: iOS resolves vh against the LARGE viewport --- the one you get when
	 * the toolbar has retracted --- while the small one is what you are looking
	 * at most of the time. Measured on a 390x844 iPhone: 739 against 699.
	 *
	 * It matters more now that the document scrolls, because the viewport
	 * changes height WHILE YOU SCROLL as the toolbar collapses. In vh or dvh
	 * every row on the calendar would resize mid-fling. svh is the one number
	 * of the three that holds still, and Calendar#remeasure leans on that: it
	 * only puts the scroll position back when the row height really moved.
	 */
	--row-height: 16svh;

	position: relative;
	/*
	 * NOT A SCROLLER ANY MORE. Snapping, the scroll padding and the hidden
	 * scrollbar all moved up to html, where the scrolling actually happens ---
	 * on an element that does not scroll they are inert, which is a quiet way
	 * to lose month snapping and not notice for a week.
	 *
	 * overscroll-behavior went with them and did not come back. It was here to
	 * stop a fling at either end bouncing the whole page; the whole page is now
	 * the thing being flung, and the bounce at the end of thirty-six years is
	 * the browser's to do.
	 */
}


/*
 * Measured by the scroller to find out what --row-height came to in pixels.
 * Not display: none --- that box is never laid out and reports zero.
 */
.row-probe {
	position: absolute;
	top: 0;
	left: 0;
	width: 0;
	height: var(--row-height);
	visibility: hidden;
	pointer-events: none;
}

/* Height is set from script: rows x row height, so the scrollbar is honest. */
.field {
	position: relative;
	width: 100%;
}

.week {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	/* Rows are moved rather than laid out. Compositor work, not layout work. */
	will-change: transform;
}

.day {
	/*
	 * A row: the month label on the left, the date on the right. The label is an
	 * empty span on all but one day in a month, and an empty span takes no
	 * width, so `space-between` puts the date hard right either way.
	 */
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 0.4rem;
	/*
	 * The horizontal padding is 0.75rem so today's drawn highlight has somewhere
	 * to be. It overhangs the date on both sides and the cell clips, so the
	 * padding is the blob's whole allowance.
	 */
	padding: 0.35rem 0.75rem;
	/*
	 * A grid item's min-height is auto, so without this a label taller than the
	 * week stretches the cell over the row beneath it.
	 *
	 * NOT overflow: hidden. That was the backstop against the month label
	 * spilling, and it also cut the top off today's mark, which is meant to sit
	 * over the borders. The label carries its own containment instead --- see
	 * .day__month --- so the only thing allowed out of a cell is the one thing
	 * that should be.
	 */
	min-height: 0;
	border: 0;
	/*
	 * TOP AND LEFT, not bottom and right. The month boundary needs to replace a
	 * grid line rather than sit next to one: drawn the other way round, a cell's
	 * month line would land against its neighbour's ordinary line and render as
	 * two hairlines in two colours instead of one division.
	 */
	border-top: 1px solid var(--line);
	border-left: 1px solid var(--line);
	background: transparent;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
	/* Containing block for the staircase's inside corner, and for today's mark. */
	position: relative;
}

/*
 * The 2px square where the month boundary steps down, which is part of neither
 * border. Drawn by the cell below the step, on its own top-left corner: the
 * offsets are negative because an absolutely positioned child is placed against
 * the PADDING box, and this has to sit out on the border.
 */
.day--month-corner::after {
	content: "";
	position: absolute;
	inset-block-start: -1px;
	inset-inline-start: -1px;
	inline-size: 2px;
	block-size: 2px;
	background: var(--month-line);
}

/* The Monday column is the outer edge of the calendar, not a division. */
.day:first-child {
	border-left: 0;
}

/*
 * The 1st takes the dark treatment: this one is a boundary marker rather than
 * the quiet spine, and it says in words what the stepped line says in ink. It
 * also wins on a Monday that happens to be the 1st, by being the later rule.
 */
.day--month-start .day__month {
	color: var(--ink);
}

/*
 * Two pixels, and the padding gives the pixel back.
 *
 * The cells are border-box inside fixed grid columns, so a wider border eats
 * into the content box: without the compensation the date on a month's first
 * day would sit a pixel below its neighbours, and the label a pixel in from the
 * spine above it. Both are asserted, so this would have failed rather than
 * merely looked slightly wrong --- but it would have looked slightly wrong.
 */
.day--month-above {
	border-top-width: 2px;
	border-top-color: var(--month-line);
	padding-block-start: calc(0.35rem - 1px);
}

.day--month-left {
	border-left-width: 2px;
	border-left-color: var(--month-line);
	padding-inline-start: calc(0.75rem - 1px);
}

.day--weekend {
	background: var(--sunken);
}

/*
 * How long a stay beginning on this day could run.
 *
 * UNDER THE DATE, right-aligned to it, at the weekday's size and colour. It
 * began in the middle of the cell, which made every square an announcement:
 * thirteen thousand of them, most saying nothing had happened. Sat beneath its
 * own date it reads as part of the date rather than as a second thing competing
 * with it, and the middle of the cell goes back to belonging to the stay drawn
 * across it and to today's mark.
 *
 * Positioned rather than laid out, because the cell's flex row is the month
 * spine and the date, and a third item in it moves the date --- the same way the
 * trip label did. The offset clears the date's line and the half-em that today's
 * mark overhangs below it.
 */
.day__room {
	position: absolute;
	inset-block-start: 1.9rem;
	inset-inline-end: 0.75rem;
	/*
	 * blue-11 by way of the accent, which is the person's voice --- the same
	 * family the stays are drawn in. What is left of the ninety is a fact about
	 * what they have done, not something the calendar is announcing.
	 */
	color: var(--color-accent-text);
	/*
	 * The tool face, which design describes as being for "numbers, code,
	 * calculator surfaces" --- and a column of counts down a calendar is all
	 * three. Only the 400 weight is carried: design declares three, each with a
	 * true italic, and this renders one number.
	 */
	font-family: var(--font-tool);
	/*
	 * 400 SPELLED OUT, because the body asks for 450 and the tool face has only
	 * its 400 vendored. A browser rounds 450 up to the 500 the stylesheet
	 * declares, tries to fetch a file that is not there, and falls back --- which
	 * Chrome does silently and Firefox logs. Neither is a rendering fault; both
	 * are a 404 on every load.
	 */
	font-weight: 400;
	font-size: 0.68rem;
	font-variant-numeric: tabular-nums;
	text-align: right;
	pointer-events: none;
}

/*
 * A morning worth more than the one before it, ringed by hand.
 *
 * A RING, not a blob, which is the whole difference from today's mark: they are
 * the same drawn oval, but this one carries an inner contour, so the number
 * shows through it rather than sitting on it. That is why it can be sand-12 and
 * the number underneath can stay green --- nothing is being covered.
 *
 * Same technique as today's mark otherwise: a mask, so the shape comes from the
 * file and the colour still comes from a token. `isolation` keeps the negative
 * z-index inside this box rather than dropping it behind the cell.
 */
/*
 * The ring overhangs its number and lands a pixel past the cell edge, where the
 * next cell along paints over it --- pool order is visual order, so a later cell
 * covers an earlier one. Lifting the marked cell is the same fix today wears,
 * for the same reason.
 */
.day--reset {
	z-index: 1;
}

.day--reset .day__room {
	isolation: isolate;
}

.day--reset .day__room::before {
	content: "";
	position: absolute;
	/*
	 * Wider than tall, because the shape is: an oval that merely clears a
	 * two-digit number crops its own ends. The cell's 0.75rem padding is what
	 * this overhang is spending, and at 0.68rem type it does not reach the edge.
	 *
	 * These two numbers are also the click target --- `Calendar#hitRing` reads
	 * this box off the pseudo-element --- so growing the ring grows what you can
	 * hit, which a small oval needs.
	 */
	inset: -0.68em -1.2em;
	z-index: -1;
	background: var(--reset-ring);
	-webkit-mask: url("./reset.svg") center / 100% 100% no-repeat;
	mask: url("./reset.svg") center / 100% 100% no-repeat;
}

/* No room at all: a day to already be somewhere else. */
.day--spent .day__room {
	color: var(--over-ink);
	font-weight: var(--weight-bold);
}

/*
 * The month, running up the left edge of every week's first day, and of the day
 * the month starts on.
 *
 * Set sideways rather than across the top because it is a spine rather than a
 * property of one square, and because that leaves the top of every cell free,
 * which is what lets the dates sit in one consistent place.
 *
 * `vertical-rl` then a half turn, which reads bottom to top. `sideways-lr`
 * says that in one declaration and is too new to rely on.
 *
 * LAID OUT, NOT POSITIONED. The first pass put this in the corner with
 * `inset-inline-start`, which on an element that is itself `vertical-rl`
 * resolves against ITS OWN writing mode rather than the cell's --- so it did not
 * mean "left", and the label came out over the date on the right. As a flex item
 * it simply takes the start of the row, and a transform changes no layout, so
 * the box it occupies is the narrow upright one it appears to be.
 */
.day__month {
	/*
	 * NEVER SHRINK. It is a flex item beside the date, and a flex item's default
	 * is to give way --- so on a narrow cell the browser squeezed this to six
	 * pixels and shaved the glyphs down their sides. It reads as a clipped label
	 * and measures as an element that has not overflowed anything, which is why
	 * it took a rect to find.
	 */
	flex: none;
	writing-mode: vertical-rl;
	transform: rotate(180deg);
	color: var(--month-label);
	/*
	 * Small enough that the spelled-out month fits a 16vh row on an ordinary
	 * laptop. At 0.68rem "September 2026" wanted 130px against the 121px a
	 * 757px viewport gives, so every such screen fell back to the abbreviation.
	 * The thresholds in calendar.ts are derived from THIS number.
	 */
	font-size: 0.6rem;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	white-space: nowrap;
	pointer-events: none;
	/*
	 * Its own backstop, now that the cell no longer clips. The tiers in
	 * calendar.ts are what actually keep the name inside the row; this is what
	 * stops a mistake there landing on the week below.
	 */
	max-block-size: 100%;
	overflow: hidden;
}

/* The date, right-aligned: weekday quiet, number loud, sharing a baseline. */
.day__date {
	display: flex;
	align-items: baseline;
	gap: 0.3em;
}

.day__weekday {
	color: var(--ink-quiet);
	font-size: 0.68rem;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.day__num {
	font-size: var(--text-base);
	/*
	 * 800, not 700. The date is the one thing on a cell that has to be findable
	 * at a glance, and at 700 on the light scheme it sank into the weekday beside
	 * it --- sand-12 on near-white has plenty of contrast and not much weight.
	 */
	font-weight: 800;
	line-height: 1;
	font-variant-numeric: tabular-nums;
}

/*
 * Today wears a drawn highlight rather than a colour.
 *
 * The blob is a MASK, not a background image, so the shape comes from
 * today.svg and the colour still comes from a token --- a data URI would have
 * to carry the colour with it and would sit outside the palette entirely.
 *
 * Drawn by a pseudo-element that OVERHANGS rather than by padding the date.
 * Padding would move the date left, and every date in the grid is meant to sit
 * the same distance from the right edge --- there is a check for that, and
 * padding this one would have broken it. The overhang is held to the cell's own
 * padding so the blob is never clipped.
 */
/*
 * Today's cell, and its row, are lifted so the mark can sit ON the borders
 * rather than under them.
 *
 * The row matters because every .week carries a transform and is therefore a
 * stacking context. Pool order does track visual order --- pool[i] always draws
 * row first+i --- so a row already paints above the one above it, and the mark
 * overhangs upward. This is what keeps that true if it ever overhangs down.
 */
.week--today {
	z-index: 1;
}

.day--today {
	z-index: 1;
}

.day--today .day__date {
	position: relative;
	/* So the blob's z-index stays inside this box rather than falling behind
	   the cell it is drawn on. */
	isolation: isolate;
}

/*
 * Named, not inherited. The weekday sets its own quiet colour, and a colour on
 * this box does not beat that however specific the selector is --- inheritance
 * only reaches an element that has not been told otherwise. It left "WED" in
 * grey on the mark.
 */
.day--today .day__weekday,
.day--today .day__num {
	color: var(--today-ink);
}

.day--today .day__date::before {
	content: "";
	position: absolute;
	/*
	 * WIDER THAN IT LOOKS LIKE IT NEEDS, and centred. A rectangle only sits
	 * inside an ellipse if the ellipse is about 1.4 times its size, so an
	 * overhang that merely clears the text leaves the corners of "WED 26"
	 * outside the ink --- and the ink is white, so what escapes does not go grey,
	 * it disappears.
	 *
	 * The first attempt weighted the overhang left to stay clear of the cell
	 * edge, which pushed the TEXT to the right of the blob, which is exactly
	 * where a drawn oval narrows. The cell's padding carries it instead.
	 */
	inset: -0.5em -0.85em;
	z-index: -1;
	background: var(--today-mark);
	-webkit-mask: url("./today.svg") center / 100% 100% no-repeat;
	mask: url("./today.svg") center / 100% 100% no-repeat;
}

/*
 * The days at either end that fall outside the range, because the opening and
 * closing weeks are whole weeks. Dimmed and inert rather than absent: a gap
 * would read as a rendering fault instead of as the edge of the calendar.
 */
.day--outside {
	color: var(--ink-quiet);
	background: var(--sunken);
	cursor: default;
	opacity: 0.45;
}

/*
 * Between the borders opening and the rule taking its current shape. Still a
 * real day somebody may have travelled on, so it stays markable; the counter
 * cannot speak to it, so it does not look like the days the counter can.
 */
.day--pre-rule .day__num {
	color: var(--ink-quiet);
}

/*
 * A stay is a PILL LAID OVER THE DAYS, not a colour poured into them.
 *
 * The day keeps its own background. A week filled edge to edge in the accent
 * shouts, and the thing being said is quiet --- you were here. It also leaves
 * the weekend shading, the month spine and today's mark legible underneath,
 * all of which a solid fill was quietly flattening.
 *
 * Drawn per cell and joined across them: each segment bleeds a pixel to the
 * left, over the grid line between two days, so a run reads as one bar rather
 * than as seven bars in a row. Only the ends of a run are rounded and inset.
 */
.day__trip {
	/*
	 * DRAWN ONLY ON DAYS INSIDE A STAY. The element is in every cell, because
	 * the cells are recycled and building one on demand would mean touching the
	 * DOM on every scroll --- so the bar is hidden by default and shown by the
	 * class. Styling it unconditionally put a full-width bar across every row
	 * on the calendar, which is what that mistake looks like.
	 */
	display: none;
	position: absolute;
	inset-inline: -1px 0;
	/*
	 * At the FOOT of the cell. It was briefly under the date, which read as
	 * belonging to the day more clearly and crossed straight through the month
	 * label running up the left edge --- and that label is the only thing
	 * telling you which month you are looking at.
	 *
	 * Down here it stays clear of the label instead, and the room it takes is
	 * declared to calendar.ts as TRIP_BAR_ROOM so the label knows how much
	 * height it actually has. The two numbers have to move together.
	 */
	inset-block-end: 0.4rem;
	block-size: 1.6rem;
	align-items: center;
	font-size: var(--text-sm);
	font-weight: var(--weight-medium);
	line-height: 1;
	white-space: nowrap;
}

/*
 * The name is in the person's own hand, which is the design system's first
 * blessed use of --font-hand: "a note, a scrawled event". A trip is that. The
 * same file draws the hard line the other way --- never in chrome, never in
 * dense or critical data --- and this is neither.
 *
 * Medium rather than bold: it was bold to buy contrast the colours were not
 * providing, and now they are. See --present.
 */
.day--present .day__trip {
	display: flex;
	background: var(--present);
	color: var(--present-ink);
	font-family: var(--font-hand);
	font-size: var(--text-base);
	font-weight: var(--weight-medium);
}

/* Two pixels where the month boundary is two, or the bar breaks on it. */
.day--present.day--month-left .day__trip {
	inset-inline-start: -2px;
}

.day--present.day--trip-opens .day__trip {
	inset-inline-start: 0.4rem;
	border-start-start-radius: var(--radius-full);
	border-end-start-radius: var(--radius-full);
}

/*
 * Room for the name, wherever the name is written --- which is the start of the
 * stay AND the top of every week it runs into. Kept apart from the rounding,
 * because a wrapped stay needs the padding without the cap.
 */
/*
 * A NAMED BAR IS A ROW: a flag, then the name. Only the named ones --- the
 * continuation bars carry nothing and must stay bare, or every wrapped week
 * would grow a second flag.
 */
.day--present.day--trip-named .day__trip {
	display: flex;
	align-items: center;
	gap: 0.3rem;
	padding-inline-start: 0.5rem;
}

/* No name, no flag: the element is in every cell whether or not it is used. */
.day__flag[hidden] {
	display: none;
}

.day--present.day--trip-closes .day__trip {
	inset-inline-end: 0.4rem;
	border-start-end-radius: var(--radius-full);
	border-end-end-radius: var(--radius-full);
}

/*
 * The name runs past its own cell, over the segments that follow it. It is
 * written where the stay starts and again at the top of each week it runs into,
 * so a fortnight says what it is on every row rather than only the first.
 *
 * It has to: a cell is about a hundred and seventy pixels and "a fortnight in
 * Amsterdam" is not. The opening cell is lifted so its text paints over the
 * following bars rather than under them --- they are the same colour, so it
 * reads as one pill with a label in it. The scroller clips the right edge,
 * which is what keeps a long name in the last column off the rest of the page.
 */
.day--present.day--trip-named {
	z-index: 1;
}

.day--present.day--trip-named .day__trip {
	max-inline-size: calc(600% + 6rem);
}

/*
 * A stay nobody has said the location of, carried over from the day-keyed store
 * where there was never anywhere to put one. A surface rather than the accent,
 * because it does not count: the accent means days against the ninety, and a
 * stay that is not against them must not look like one.
 */
/*
 * SAID WITH AN ELLIPSIS when it does not fit, rather than sheared off at the
 * edge of the screen, which reads as a rendering fault rather than as a long
 * name. How wide "fits" is comes from calendar.ts, because it is the number of
 * cells left in the row.
 *
 * NO min-inline-size HERE. It is the flex default of auto that lets the name
 * paint out over the segments that follow it --- see the note above --- and
 * setting it to zero collapses the name into the one cell it started in, which
 * is a three-letter title and an ellipsis.
 */
.day__name {
	/*
	 * SIZED TO ITS TEXT, THEN CAPPED. All three declarations are load-bearing
	 * and none of them is obvious.
	 *
	 * overflow: hidden is what an ellipsis needs, and it also drops this flex
	 * item's automatic minimum size to zero --- which is the mechanism that let
	 * the name paint out over the segments that follow it. On its own it
	 * collapsed the name to eight pixels.
	 *
	 * max-content puts the width back, so a short name is short and a long one
	 * asks for everything; flex: none stops the bar shrinking it back to the one
	 * cell it starts in. The cap then comes from calendar.ts, because how far a
	 * name may run is how many cells are left in the row.
	 */
	flex: none;
	inline-size: max-content;
	overflow: hidden;
	text-overflow: ellipsis;
}

.day--unplaced .day__trip {
	background: var(--color-surface-active);
	color: var(--ink);
}

/*
 * The span somebody is making right now, before it is a stay. Subtle rather
 * than solid: it is a question, and it should not look like an answer.
 */
.day--picking {
	background: var(--color-accent-subtle);
	box-shadow: inset 0 0 0 2px var(--color-accent-border);
}

/*
 * A span that runs through a stay, which cannot be made. Said while it is being
 * drawn: a click that silently does nothing is indistinguishable from a broken
 * one, and this way the answer arrives before the question is asked.
 */
.day--blocked {
	/*
	 * The ring alone, with the page behind it. The layer names a subtle step
	 * for the accent and none for danger, and inventing one to tint a state
	 * that lasts as long as a mouse movement is not worth reaching past it.
	 */
	background: none;
	box-shadow: inset 0 0 0 2px var(--today-mark);
	cursor: not-allowed;
}

/*
 * Delete sits apart, and does not look like the way out of the dialog.
 *
 * :not([hidden]) is load-bearing. A `display` on a class selector beats the
 * browser's own `[hidden] { display: none }`, so this button went on being
 * shown for a stay that did not exist yet --- offering to delete something
 * nobody had made. Any author rule that sets display has to opt out of hidden
 * itself.
 */
.dialog__actions .dialog__delete:not([hidden]) {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	margin-inline-end: auto;
	padding-inline: var(--space-2);
	border-color: transparent;
	background: none;
	/* amber-11. The 9 this used measures Lc 36.2 in the dark, which is not a
	   colour a label can be written in. */
	color: var(--over-ink);
}

.dialog__delete svg {
	flex: none;
	inline-size: 1.125rem;
	block-size: 1.125rem;
}

.day:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: -2px;
}

/*
 * UNLESS THE LAST THING THAT HAPPENED WAS A TAP. See main.ts: closing the
 * dialog hands focus back to the day underneath it, and the browser counts
 * that restore as keyboard-driven however the day was chosen --- so saving a
 * stay on a phone left a ring drawn around it with no way to dismiss it.
 *
 * Only the day cells. Everything else here is a control somebody pressed on
 * purpose, and a ring on one of those is an answer rather than a leftover.
 */
:root[data-input="pointer"] .day:focus-visible {
	outline: none;
}

/*
 * A viewport-relative row needs a floor. Sixteen percent of a phone held
 * sideways is not a row anybody can read or hit, and the tap target is the part
 * that actually breaks.
 */
/*
 * The dialog. A native one, so the browser owns focus, Escape and the backdrop.
 */
#confirm,
#arrived {
	max-inline-size: min(28rem, calc(100vw - var(--space-6)));
	padding: var(--space-5);
	border: none;
	border-radius: var(--radius-md);
	background: var(--page);
	color: var(--ink);
	box-shadow: 6px 6px 0 var(--black-a3);
}

#confirm::backdrop,
#arrived::backdrop {
	background: var(--black-a5);
}

/*
 * The arrival dialog has no form to lay it out --- there is nothing to submit,
 * only three ways of answering --- so the dialog is the column itself.
 *
 * [open] IS LOAD-BEARING, for the fourth time in this file. `display` on an id
 * beats the browser's `dialog:not([open]) { display: none }`, and a dialog
 * drawn over the calendar from page load is what that looks like.
 */
#arrived[open] {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
}

/*
 * The note pinned to a ringed morning.
 *
 * A POPOVER, NOT A DIALOG. It answers a question about one square, so it should
 * behave like a note pinned to that square rather than like something that stops
 * the room: no backdrop, no button, gone on the next click. Light dismiss,
 * Escape and the top layer are the browser's, which is the whole reason to use
 * the native thing.
 *
 * :popover-open IS LOAD-BEARING, for the third time in this file. The browser
 * hides a closed popover with `[popover]:not(:popover-open) { display: none }`,
 * which an id selector beats outright --- the dialog version of this shipped
 * visible from page load for exactly that reason. Any author rule that sets
 * display on a popover has to say when.
 */
#why {
	/*
	 * Placed by script, in the top layer, so the corner is where it is put --- and
	 * re-placed on every scroll, because it follows the day it is about. See the
	 * note above the Why class for why this is not anchor positioning.
	 */
	position: fixed;
	inset: auto;
	max-inline-size: min(23rem, calc(100vw - var(--space-6)));
	padding: var(--space-3) var(--space-4);
	border: 1px solid var(--line);
	border-radius: var(--radius-md);
	background: var(--page);
	color: var(--ink);
	box-shadow: 4px 4px 0 var(--black-a3);
}

#why:popover-open {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
}

/*
 * The day being explained, which is the heading this note gets instead of one.
 *
 * In the accent, the family the stays are drawn in, because what is left of the
 * ninety is a fact about what the person has done rather than something the
 * calendar is announcing.
 */
.why__date {
	margin: 0;
	color: var(--color-accent-text);
	font-size: var(--text-lg);
	font-weight: var(--weight-bold);
}

/* Why it changed. */
.why__line {
	margin: 0;
	font-size: var(--text-sm);
	line-height: 1.4;
	text-wrap: pretty;
}

/*
 * What the list is, said once, above it and under the rule.
 *
 * The rule moves here from the list below, because the label is the top of that
 * block rather than a thing floating between two.
 */
.why__label {
	margin: 0;
	margin-block-start: var(--space-2);
	padding-block-start: var(--space-3);
	border-block-start: 1px solid var(--color-border);
	/*
	 * The accent, which is the person's voice --- the same family the stays
	 * below it are drawn in, and what this line is announcing.
	 */
	color: var(--color-accent-text);
	font-size: 0.75rem;
}

/*
 * The stays behind it, for anybody who wants to check --- under a rule, because
 * the sentence above is about one day and this is a list of trips, and running
 * them together made the first stay look like part of the sentence.
 */
.why__trips {
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
	list-style: none;
	color: var(--ink-quiet);
	font-size: 0.75rem;
	line-height: 1.4;
	/*
	 * SHORTHANDS FIRST, then the block-start values, and the order is the whole
	 * point: `margin: 0` written after `margin-block-start` resets it, which is
	 * how the rule ended up with four pixels above it and one below.
	 *
	 * The panel already puts --space-1 between its children, so the margin here
	 * is the difference rather than the whole gap: 4 plus 12 above, 16 below.
	 */
	margin: 0;
	padding: 0;
	margin-block-start: var(--space-2);
}

/*
 * Each row is a way back to the stay it names, so it is a button --- stripped of
 * everything a button brings except the pointer and the focus ring.
 */
.why__go {
	display: block;
	inline-size: 100%;
	margin: 0;
	padding: 0;
	border: none;
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.why__go:hover .why__trip {
	text-decoration: underline;
}

.why__go:focus-visible {
	outline: 2px solid var(--color-accent);
	outline-offset: 2px;
	border-radius: var(--radius-sm);
}

/* The name is the part somebody recognises, so it steps out of the quiet. */
.why__trip {
	color: var(--trip-name);
	font-weight: var(--weight-bold);
}

/* And the punctuation between the pieces steps back behind all of it. */
.why__sep {
	color: var(--separator);
}

#confirm form {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
}

#confirm input,
#confirm select {
	padding: var(--space-2);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm);
	background: var(--color-bg-subtle);
	color: inherit;
	font: inherit;
}

/*
 * Room for the flag, whether or not one is showing --- so the name does not
 * shift sideways when somebody picks "Somewhere else".
 *
 * AFTER the shorthand above, not before. Written first, `padding` resets it and
 * the flag lands on top of the country's name, which is what it did.
 */
#confirm select {
	padding-inline-start: calc(var(--space-2) + 1.15em + var(--space-2));
}

/*
 * SIXTEEN PIXELS, OR SAFARI TAKES THE PAGE OFF YOU. Focus a control whose text
 * is smaller than the body default and iOS zooms the viewport to it, and it
 * does not zoom back out --- you finish naming a trip and the calendar is left
 * at 1.3x with no way back but a pinch. Sixteen is the threshold, and the whole
 * fix is to reach it.
 *
 * The alternative going around is maximum-scale=1 on the viewport tag, which
 * buys the same thing by taking pinch-zoom away from everybody, permanently,
 * including the people who need it to read. Not a trade worth making for two
 * text fields.
 *
 * KEYED ON THE POINTER, not the width. The zoom is a property of the input
 * device rather than the screen, and an iPhone held sideways is 844px across
 * --- well past any narrow breakpoint and still very much a phone.
 */
@media (pointer: coarse) {
	#confirm input,
	#confirm select {
		font-size: var(--text-base);
	}
}

#confirm input:focus-visible,
#confirm select:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: 1px;
}

/* The dates, and nothing else on the line. */
.summary {
	margin: 0;
	font-size: var(--text-lg);
}

/*
 * One line of feedback, bottom centre, for the things that finish without
 * changing the page. Fixed rather than in the flow: it appears and goes, and
 * nothing else should move to make room for it.
 */
.flash {
	position: fixed;
	inset-block-end: var(--space-4);
	inset-inline: 0;
	z-index: 20;
	margin: 0 auto;
	inline-size: fit-content;
	max-inline-size: calc(100vw - var(--space-6));
	padding: var(--space-2) var(--space-4);
	border-radius: var(--radius-full);
	background: var(--color-text);
	color: var(--page);
	font-size: var(--text-sm);
	box-shadow: 4px 4px 0 var(--black-a3);
}

/* [hidden] is a UA rule a class rule beats, and this file has learnt that. */
.flash[hidden] {
	display: none;
}

/* The one action in the arrival dialog that is not a retreat. */
.dialog__actions .dialog__go {
	border-color: transparent;
	background: var(--color-accent);
	color: var(--white);
}

/* Nothing to lose, nothing to ask about losing it. */
.dialog__actions button[hidden] {
	display: none;
}

/* Why a link cannot be made, said where the button would have been pressed. */
.menu__hint {
	margin: 0;
	color: var(--white-a11);
	font-size: 0.8125rem;
}

.menu__hint[hidden] {
	display: none;
}

/* The arrow between them is structure, not a word. */
.summary__sep {
	color: var(--separator);
}

/*
 * The verdict: a mark, three words, and a number. One line rather than a
 * paragraph in a box --- somebody who has just drawn a span on a calendar knows
 * what they drew, and is here for the answer.
 */
/*
 * The verdict's colour is held in a variable rather than only in `color`,
 * because the disc needs it as a BACKGROUND while the mark on the disc needs to
 * be white. Written with `background: currentColor` beside `color: white`,
 * currentColor resolves to the white set on the same element --- a white disc
 * carrying a white check, drawn perfectly and visible to nobody.
 */
.verdict {
	--verdict-ink: var(--verdict-yes);

	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-2);
	margin: 0;
	color: var(--verdict-ink);
	font-size: var(--text-base);
}

/*
 * amber-11 rather than the amber-9 this used, and the difference is not
 * subtle: 9 measures Lc 36.2 against the dialog at night, which is under
 * anything. 11 is 73.5 in daylight and 60.8 in the dark.
 */
.verdict--over {
	--verdict-ink: var(--verdict-no);
}

/*
 * Small and heavy. At 0.75rem APCA wants about Lc 90 and green-11 gives 71 ---
 * carried by the weight and by there being two words of it rather than a
 * paragraph. green-12 would measure 97 if this ever needs to be quieter in
 * colour instead.
 */
#verdict-line {
	font-size: 0.75rem;
	font-weight: 800;
}

/* The mark on its own. No disc: the line and the colour say it. */
.verdict__mark {
	flex: none;
	display: grid;
	place-items: center;
	inline-size: 1rem;
	block-size: 1rem;
	color: var(--verdict-ink);
}

/*
 * How far over, when it is over. Sits beside the verdict rather than at the far
 * end, because it finishes the sentence the verdict starts.
 */
.verdict__count {
	color: var(--ink-quiet);
	font-size: 0.75rem;
	font-variant-numeric: tabular-nums;
}

/* How long the stay is, pushed to the end of the same line. */
.verdict__length {
	margin-inline-start: auto;
	color: var(--ink-quiet);
	font-size: 0.75rem;
	font-variant-numeric: tabular-nums;
}

.verdict__mark svg {
	inline-size: 100%;
	block-size: 100%;
}

.dialog__note {
	margin: 0;
	color: var(--ink-quiet);
	font-size: var(--text-sm);
}

.dialog__actions {
	display: flex;
	justify-content: flex-end;
	gap: var(--space-2);
	margin-block-start: var(--space-4);
}

.dialog__actions button {
	padding: var(--space-2) var(--space-4);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm);
	background: var(--color-surface);
	color: inherit;
	font: inherit;
	cursor: pointer;
}

.dialog__actions button[type="submit"] {
	border-color: transparent;
	background: var(--color-accent);
	color: var(--white);
}

@media (max-height: 40rem) {
	.calendar {
		--row-height: 64px;
	}
}

/*
 * ON A NARROW SCREEN THE WEEKDAY IS ONE LETTER. Seven columns of "SUN 30" do
 * not fit a phone, and the day of the week is the part somebody can infer from
 * the column it is in --- the date cannot be.
 *
 * Done with the letter in an attribute and drawn by ::before rather than by
 * rewriting the text: the cells are recycled and the fill runs for every cell
 * of every row that moves, so the alternative is a media query consulted
 * thirty times a scroll.
 */
@media (max-width: 30rem) {
	.day {
		padding-inline: 0.4rem;
	}

	.day__weekday {
		/* Hides the text without hiding the element, so ::before still draws. */
		font-size: 0;
	}

	.day__weekday::before {
		content: attr(data-letter);
		font-size: 0.68rem;
	}

	/*
	 * The spine sits at the FOOT of the cell rather than the head. It reads
	 * bottom to top, so a bottom edge is where it starts --- and on a phone the
	 * date needs the top corner more than the month does.
	 */
	.day__month {
		align-self: flex-end;
	}

	/*
	 * A stay's bar takes the foot of the cell, so on those days the spine steps
	 * up out of its way. Only there: charging every cell for the few that
	 * collide is the mistake the label tiers already exist to avoid.
	 *
	 * PHYSICAL, and it has to be. This element writes vertical-rl, so its own
	 * block end is to the LEFT --- margin-block-end shoved the label sideways
	 * and left it sitting under the bar exactly as before. The flex container
	 * around it writes normally, which is why align-self above does move it
	 * down; a logical margin resolves against the label instead.
	 *
	 * The bar's own room, less the padding the cell already gives. Both numbers
	 * are set in rules above and have to move with them.
	 */
	.day--present .day__month {
		margin-bottom: calc(2rem - 0.35rem);
	}
}
