.planner-stack {
	box-shadow: var(--shadow-card);
	position: relative;
	max-width: var(--planner-max-width);
	margin: 0 auto;

	/* The headers and the planner must resolve to identical tracks or the
	   columns drift apart, so both read this one template. */
	--planner-day-width: minmax(64px, 7%);
	/* Sized off the minimum, not the maximum: the tracks stretch past it with
	   1fr on a wider screen, and pinning them to the maximum would push the
	   scroll threshold up with it and make a laptop scroll for a planner that
	   used to fit. */
	--planner-column-min: calc(
		(var(--planner-min-width) - 64px) / var(--planner-columns, 2)
	);
	--planner-template: var(--planner-day-width)
		repeat(var(--planner-columns, 2), minmax(var(--planner-column-min), 1fr));
}

/* Narrow screens: let columns get narrower rather than forcing a full-measure
   scroll for what might only be two columns. */
@media (max-width: 48rem) {
	.planner-stack {
		--planner-day-width: minmax(48px, 7%);
		--planner-column-min: 35vw;
	}
}

@media (max-width: 30rem) {
	.planner-stack {
		--planner-day-width: minmax(48px, 7%);
		--planner-column-min: 55vw;
	}
}

:root {
	--z-5: 5;
}

/* Clips rather than scrolls: the planner below owns the scrollbar, and the
   scroll controller mirrors its position onto this element. */
.planner-headers {
	position: sticky;
	top: 56px;
	z-index: 100;
	background: var(--color-surface);
	margin: 0 auto;
	max-width: var(--planner-max-width);
	overflow: hidden;

	.planner-headers-grid {
		display: grid;
		grid-template-columns: var(--planner-template);
		min-width: fit-content;
	}

	.header {
		color: var(--color-text-muted);
		font-family: "Hanken Grotesk", sans-serif;
		font-size: 0.78rem;
		font-weight: 600;
		letter-spacing: 0.05em;
		text-transform: uppercase;
		justify-content: center;
		padding: 1.1em 0.25em;
		display: flex;
		align-items: center;
		gap: 0.5em;

		/* Pinned like the day column it sits above. */
		&:first-child {
			position: sticky;
			left: 0;
			z-index: 1;
			background: var(--color-surface);
		}

		.header-dot {
			width: 0.65em;
			height: 0.65em;
			border-radius: 50%;
			background: var(--item-accent-color, var(--color-text-subtle));
			flex-shrink: 0;
		}
	}
}

.planner-wrapper {
	background: var(--color-surface);
	padding: 0 0 var(--space-md) 0;
	overflow-x: auto;
}

.planner-wrapper-1 {
	transform: rotate(0deg);
}

.planner {
	display: grid;
	min-width: fit-content;

	grid-template-columns: var(--planner-template);

	grid-auto-rows: minmax(5.5em, auto);

	&:has(.day-row) {
		grid-auto-rows: auto;
	}

	/* Invisible on screen: the week groups exist so print can cut the month up,
	   and display: contents keeps their days as items of this one grid, which is
	   what holds the columns in line. */
	.planner-week {
		display: contents;
	}

	.planner-week-heading,
	.planner-week-headers {
		display: none;
	}

	.day-row {
		/* Grows with its contents rather than clipping them. */
		min-height: 2.75em;
	}

	.day-cell {
		min-height: 5.5em;
	}

	/* Named rather than "> div": the direct children are week groups now, and
	   the days sit one level inside them. */
	.day,
	.day-row,
	.day-cell {
		align-items: center;
		display: flex;
		flex-direction: column;
		justify-content: center;
		padding: 0.4em 0.75em;
	}

	.day {
		align-items: center;
		background: var(--color-surface);
		color: var(--color-text-muted);
		font-family: "Hanken Grotesk", sans-serif;
		font-variant-numeric: lining-nums tabular-nums;
		font-feature-settings:
			"lnum" 1,
			"tnum" 1;
		justify-content: center;
		font-size: 1.875rem;
		font-weight: 500;
		letter-spacing: -0.02em;
		text-align: center;
		padding: 0.4em 0.1em;
		position: sticky;
		left: 0;
		z-index: 50;

		/* Clears both sticky headers when jumped to via #day, so the whole day
		   lands below them. Measured by the planner-scroll controller; the
		   fallback is the month header plus a typical column header. */
		scroll-margin-top: var(--planner-sticky-offset, 110px);

		.day-name {
			font-size: 0.35em;
			font-weight: 500;
			letter-spacing: 0.08em;
			text-transform: uppercase;
		}

		.day--number {
			line-height: 1;
		}

		.day--add {
			align-items: center;
			background-color: var(--color-surface-alt);
			border-radius: 50%;
			color: var(--color-text-muted);
			display: flex;
			height: 1.4rem;
			justify-content: center;
			margin-top: 0.25rem;
			opacity: 0;
			transition:
				opacity 0.15s ease,
				background-color 0.15s ease,
				color 0.15s ease,
				transform 0.15s ease;
			width: 1.4rem;

			svg {
				height: 0.85rem;
				width: 0.85rem;
			}

			&:hover,
			&:focus-visible {
				background-color: var(--color-accent);
				color: #fff;
				opacity: 1;
				transform: scale(1.1);
			}

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

		&:hover .day--add,
		&:focus-within .day--add {
			opacity: 1;
		}
	}

	.day--add-item {
		align-items: center;
		background: transparent;
		border: 1px dashed transparent;
		border-radius: var(--radius-sm);
		color: var(--color-text-subtle);
		cursor: pointer;
		display: flex;
		justify-content: center;
		height: 100%;
		opacity: 0;
		padding: 0.2em 0;
		text-decoration: none;
		transition:
			opacity 0.15s ease,
			background-color 0.15s ease,
			border-color 0.15s ease,
			color 0.15s ease;
		width: 100%;

		svg {
			height: 0.9rem;
			width: 0.9rem;
		}

		&:hover,
		&:focus-visible {
			opacity: 1;
		}
	}

	.day-cell:hover .day--add-item {
		border-color: color-mix(in srgb, var(--color-text-subtle) 45%, transparent);
		opacity: 1;
	}

	.day--weekend,
	.day-cell.day--weekend {
		background-color: var(--color-surface-alt);
	}

	/* The faintest rule, and only between two weekdays — the weekend block is
	   already set apart by its tint, so a line there would say it twice. */
	.day--ruled {
		border-top: 1px solid
			color-mix(in srgb, var(--color-text-subtle) 12%, transparent);
	}

	.day--today {
		font-weight: 800;

		&.day-cell {
			background-color: var(--color-accent-tint);
		}

		.day-name {
			color: white;
			line-height: 0.5;
		}

		/* Today's number gets a hand-drawn splat behind it: an irregular
		   border-radius, tilted a little so it reads as marked by hand rather
		   than as a UI badge. Drawn on a pseudo-element so the digit itself
		   stays upright and legible. */
		.day--number {
			color: #fff;
			position: relative;

			&::before {
				background: var(--gradient-accent);
				border-radius: 68% 32% 55% 45% / 48% 62% 38% 52%;
				content: "";
				inset: -0.63em -0.42em -0.32em -0.45em;
				position: absolute;
				transform: rotate(-5deg);
				z-index: -1;
			}
		}
	}

	.day-row {
		display: flex;
		flex-direction: row;
		justify-content: flex-start;
		/* All-day items run onto as many lines as they need. Each card still
		   keeps its title on one line: the time holds its width and the title
		   ellipsises, so wrapping happens between cards, never inside one. */
		flex-wrap: wrap;
		gap: var(--day-row-gap);
		padding: 0.3em 0.5em;
		position: relative;

		--day-row-gap: 0.4em;
		/* Cap a card at 45% of the row once there are two or more columns, so one
		   long item cannot take the whole width and two sit side by side with
		   room for the gap. A single-column planner keeps the full width: min()
		   does the comparison CSS otherwise cannot — 1 stays 1, anything above
		   clamps to 2, which subtracts 55% exactly once. */
		--day-row-share: min(var(--planner-columns, 2), 2);

		.day--item-container {
			max-width: calc(100% - (var(--day-row-share) - 1) * 55%);
			min-width: 0;
		}
	}

	.day-cell {
		position: relative;
	}

	/* Every item in a day is laid out in flow — the cell grows to fit rather
     than hiding items behind a hover-only popover. */
	.day--item-stack {
		display: flex;
		flex-direction: column;
		gap: 0.35em;
		width: 100%;

		.day--item-container {
			width: 100%;
		}
	}

	/* Beyond the first few, items collapse into a disclosure that expands the
     cell in place. Works on touch and by keyboard, unlike the old hover deck. */
	.day--more {
		width: 100%;

		.day--more-toggle {
			align-items: center;
			background-color: var(--color-surface-alt);
			border-radius: var(--radius-sm);
			color: var(--color-text-muted);
			cursor: pointer;
			display: flex;
			font-size: 0.7rem;
			font-weight: 600;
			gap: 0.45em;
			letter-spacing: 0.01em;
			list-style: none;
			padding: 0.35em 0.6em;
			transition:
				background-color 0.15s ease,
				color 0.15s ease;

			&::-webkit-details-marker {
				display: none;
			}

			&:hover,
			&:focus-visible {
				background-color: color-mix(
					in srgb,
					var(--item-accent-strong, var(--item-accent-color)) 14%,
					var(--color-surface-alt)
				);
				color: var(--color-text);
			}
		}

		.day--more-dots {
			display: flex;
			gap: 2px;
		}

		.day--more-dot {
			/* On the page's surface, not on the card, so a pale fill would be
			   invisible — those borrow the column's own colour instead. */
			background-color: var(
				--item-accent-strong,
				var(--item-accent-color, var(--color-text-subtle))
			);
			border-radius: 50%;
			height: 0.4em;
			width: 0.4em;
		}

		.day--more-items {
			display: flex;
			flex-direction: column;
			gap: 0.35em;
			padding-top: 0.35em;
		}

		&[open] .day--more-label {
			display: none;
		}

		&[open] .day--more-toggle::after {
			content: "Show fewer";
		}
	}

	/* Stickers deliberately hang off the right of their card, into the next
	   column. Every .day--item is position: relative with z-index 1, so without
	   this the next column's cards — being later in the DOM — paint over the
	   overhang. Lift the cards that have something hanging out of them. */
	.day--item-container:has(.day--item-sticky),
	.day--item-container.day--item-sticky-only {
		position: relative;
		z-index: 3;
	}

	.day--item-container {
		background-color: transparent;
		border-radius: var(--radius-md);
		padding: 0;
		transition: transform 0.15s ease;

		&.day--item-past {
			opacity: 0.55;
		}

		/* All-day items sit in a fixed-height row, so they must stay on one
		   line: the time holds its size and the title takes the truncation. */
		&.day--item-all {
			padding: 0;

			.day--item {
				flex-direction: row-reverse;
				flex-wrap: nowrap;
				align-items: baseline;
				gap: 0.5em;
				justify-content: flex-end;
				/* The variable, not the literal, so an item given a colour of its
				   own can override it inline. .option-day-item supplies the
				   all-day default. */
				background-color: var(--item-accent-color);
			}

			.row {
				flex-shrink: 0;
			}
		}
	}

	/* A "maybe" is pencilled in, not filled in: the colour becomes an outline
	   and the text takes the column's colour instead of sitting on it. Placed
	   after .day--item-all so it wins over the all-day fill too. */
	.day--item-container.day--item-maybe .day--item {
		/* Literally not filled in: a blank card with a pencilled edge, against
		   columns of solid ones. Surface rather than transparent so it still
		   lifts off the tinted weekend and today cells. */
		background-color: var(--color-surface);
		border: 1px dashed var(--item-accent-strong, var(--item-accent-color));
		box-shadow: none;
		color: var(--item-accent-strong, var(--item-accent-color));

		/* Italic: the plainest way type says "provisional". */
		.day--item-title {
			font-style: italic;
			font-weight: 900;
		}

		.day--item-time {
			color: color-mix(
				in srgb,
				var(--item-accent-strong, var(--item-accent-color)) 70%,
				var(--color-text)
			);
		}

		.day--item-duration {
			color: color-mix(
				in srgb,
				var(--item-accent-strong, var(--item-accent-color)) 50%,
				var(--color-text)
			);
		}
	}

	.day--item {
		background-color: var(--item-accent-color, var(--color-col-blue));
		border-radius: var(--radius-sm);
		/* Pale shades cannot carry white text, so the fill brings its own ink.
		   Everything unshaded falls through to the white it has always been. */
		color: var(--item-ink, #fff);
		display: flex;
		flex-direction: column;
		font-weight: 600;
		letter-spacing: -0.01em;
		padding: 0.55em 0.85em;
		position: relative;
		text-decoration: none;
		z-index: 1;
		box-shadow: 0 1px 2px rgba(42, 39, 48, 0.08);
		transition:
			box-shadow 0.15s ease,
			transform 0.15s ease;
		cursor: pointer;

		.row {
			align-items: center;
			display: flex;
			justify-content: space-between;
		}

		&:hover {
			box-shadow: 0 3px 8px rgba(42, 39, 48, 0.18);
			transform: translateY(-1px);
		}
	}

	.day--item-sticky {
		position: absolute;
		right: -28px;
		top: 10%;
		transform: translateY(-50%);
		width: 48px;
		height: 48px;
		z-index: 2;
	}

	.day--item-sticky-only {
		background-color: transparent !important;
		padding: 0 !important;

		&:hover {
			transform: none;
		}

		.day--item {
			background-color: transparent;
			border: none;
			box-shadow: none;
			padding: 0;
			display: flex;
			align-items: center;
			justify-content: center;
		}
	}

	.day--item-sticky-standalone {
		width: 64px;
		height: 64px;
		display: block;
		transition: transform 0.2s;
		transform: scale(1.5);

		&:hover {
			transform: scale(1.3) rotate(3deg);
		}
	}

	.day--item-title {
		text-overflow: ellipsis;
		white-space: nowrap;
		overflow: hidden;
		font-size: 0.9rem;
		font-weight: 600;
		letter-spacing: -0.01em;
	}

	/* The title's line when there is a mark to set beside it. The title keeps
	   the clipping to itself; this only holds the two together. */
	.day--item-line {
		align-items: center;
		display: flex;
		gap: 0.25em;
		min-width: 0;
		width: 100%;

		.day--item-title {
			min-width: 0;
		}
	}

	/* The mark a shared item wears in every lane it is drawn in. Set beside the
	   title rather than floated into a corner, so it is read with it — and held
	   at its own size, so the title gives way first on a narrow column. */
	.day--item-shared-mark {
		flex-shrink: 0;
		display: inline-flex;
		vertical-align: -0.12em;
		margin-right: 0.25em;
		opacity: 0.75;

		svg {
			height: 0.85em;
			width: 0.85em;
		}
	}

	.day--item-action {
		display: none;
		font-family: "Hanken Grotesk", sans-serif;
		font-size: 0.8rem;
		font-weight: 500;
		text-decoration: none;
		color: color-mix(in srgb, var(--item-ink, #fff) 70%, transparent);

		&:hover {
			color: var(--item-ink, #fff);
		}
	}

	.day--item-time {
		font-family: "Hanken Grotesk", sans-serif;
		font-size: 0.78rem;
		font-weight: 400;
		color: color-mix(in srgb, var(--item-ink, #fff) 75%, transparent);
		/* "19:30 (1h)" is one thing — never break it across lines. */
		white-space: nowrap;
	}

	.day--item-duration {
		font-size: 0.875em;
		color: color-mix(in srgb, var(--item-ink, #fff) 60%, transparent);
	}
}

.option-day-item {
	--item-accent-color: #603434;
}

.option-f0f8ff,
.option-d4e6ff,
.option-d3e0f0,
.option-7c9cc4,
.option-5b8def {
	--item-accent-color: var(--color-col-blue);
}

.option-f7f5ff,
.option-d8ccf6,
.option-dad3f0,
.option-9d86b8,
.option-9b6dd1 {
	--item-accent-color: var(--color-col-purple);
}

.option-f5fff7,
.option-c8f0d6,
.option-cee8d8,
.option-7eab7e,
.option-4caf6f {
	--item-accent-color: var(--color-col-green);
}

.option-fff5f5,
.option-fbc8c0,
.option-f0d0c8,
.option-c97b6e,
.option-e8705a {
	--item-accent-color: var(--color-col-red);
}

.option-f5f5f5,
.option-f5dca8,
.option-f0deb0,
.option-c6a05a {
	--item-accent-color: var(--color-col-amber);
}

.option-c9679b {
	--item-accent-color: var(--color-col-rose);
}

.option-b8e8e8,
.option-c4e0e0,
.option-6ba6a0,
.option-4db8a8 {
	--item-accent-color: var(--color-col-teal);
}

/* ---------------------------------------------------------------------------
   A run of days

   A holiday, a trip, a week of half term: one item drawn down every day it
   covers. Not as a card repeated on each of them — that reads as the same thing
   arranged over and over — and not as one tall block either, which owns a lane
   for a week and says nothing more than a line would.

   It is a stroke down the margin of the lane, and the name written once beside
   it. The stroke is absolutely positioned against the day cell and reaches its
   full height, so consecutive days meet exactly however tall the row turned out
   to be; the cell reserves a strip on its left for as many strokes as the
   column ever needs at once, so the cards inside it never collide with one.
   --------------------------------------------------------------------------- */
.planner {
	/* The stroke, and the distance from one to the next when two runs overlap. */
	--run-width: 6px;
	--run-pitch: 4px;
	/* A length, not an em. The strokes stand off the edge of whatever they are
	   drawn against, and the date column is set in 30px while a day cell is set
	   in 16 — so as an em the household's stroke stood nearly twice as far off
	   its edge as anybody's column stroke did, and its name with it. */
	--run-inset: 3px;

	.day-cell {
		padding-left: calc(0.75em + var(--run-lanes, 0) * var(--run-pitch));
	}

	/* The household's margin is the date column, and its strokes run down the
	   inside edge of it — on the seam between the dates and the lanes, where a
	   thing the whole house is doing belongs. Out at the far left it reads as
	   part of the page's border rather than as a mark against those days. */
	.day {
		padding-right: calc(0.1em + var(--run-lanes, 0) * var(--run-pitch));

		.day--run {
			left: auto;
			right: calc(var(--run-inset) + var(--run-lane, 0) * var(--run-pitch));
		}
	}

	/* A day that carries the all-day band takes a second grid row for it. Held
	   open by a class rather than an inline style, because a band that exists
	   only to name a run again on a printed sheet has to fold away on screen —
	   and a row reserved for something display: none cannot be left standing, or
	   the day keeps the height of a band nobody can see. */
	.day--banded {
		grid-row: span 2;
	}

	.day--banded-in-print {
		grid-row: auto;
	}

	.day-row--continued {
		display: none;
	}

	/* The band is sized for cards. One holding only a run's name is sized for
	   the name, so naming a run does not cost the day the height of a card. */
	.day-row--names-only {
		min-height: 0;
	}

	/* The stroke in the date column stops --run-inset short of that column's
	   edge, and the band then starts its own padding after that same edge — so
	   without this the name would sit both of those away from its stroke, while
	   a name in a column sits only the one. Pulled back by the standoff, the two
	   land the same distance from the line they belong to. */
	.day-row .day--run-name {
		align-self: center;
		margin-left: calc(-1 * var(--run-inset));
	}

	/* Five pixels is the right width to read as a line and the wrong width to
	   aim at, so the line keeps its size and the target grows either side of it.
	   Behind the cards, which sit clear of the margin anyway. */
	.day--run::before {
		content: "";
		/* Never narrower than the stroke itself: the pitch can be tuned below the
		   width, and a negative spread would shrink the target rather than grow
		   it. */
		inset: 0 calc(-1 * max(0.5 * (var(--run-pitch) - var(--run-width)), 0px));
		position: absolute;
	}

	.day--run {
		background-color: var(--item-accent-color, var(--color-col-blue));
		bottom: -1px;
		left: calc(var(--run-inset) + var(--run-lane, 0) * var(--run-pitch));
		position: absolute;
		/* Over the hairline between weekdays, which would otherwise be drawn
		   across a line that does not stop there. The 1px either side is what
		   carries it over that border and into the next day. */
		top: -1px;
		width: var(--run-width);
		z-index: 2;

		/* A ruled line, square at both ends. The 1px bleed above and below is
		   what carries the stroke over the hairline between two weekdays, so it
		   has to stop at the days the run actually starts and ends on — or the
		   line would reach a day past itself at either end. */
		&.day--run-start {
			top: 0;
		}

		&.day--run-end {
			bottom: 0;
		}
	}

	/* Pencilled in, not settled — the same thing the dashed card says, said as a
	   line: the stroke breaks into dashes for as long as the run lasts. */
	.day--run-maybe {
		background-color: transparent;
		background-image: repeating-linear-gradient(
			to bottom,
			var(--item-accent-strong, var(--item-accent-color, var(--color-col-blue)))
				0 6px,
			transparent 6px 11px
		);
	}

	.day--run-past {
		opacity: 0.55;
	}

	.day--run-names {
		display: flex;
		flex-direction: column;
		gap: 0.15em;
		width: 100%;
	}

	/* The name, set as a line of writing rather than a card. A card would say
	   something happens on this day; what is true is that something is going on
	   across all of them, which is what the stroke beside it says. */
	.day--run-name {
		color: var(
			--item-accent-strong,
			var(--item-accent-color, var(--color-col-blue))
		);
		font-size: 0.78rem;
		font-weight: 700;
		letter-spacing: 0.01em;
		overflow: hidden;
		text-decoration: none;

		&:hover,
		&:focus-visible {
			text-decoration: underline;
		}
	}

	.day--run-name--maybe {
		font-style: italic;
		font-weight: 600;
	}

	/* The name again at the top of a week. On screen the stroke is unbroken and
	   saying it twice would be noise; on paper the week is a sheet of its own and
	   the stroke arrives on it from nowhere. */
	.day--run-name--continued {
		display: none;
	}
}
