@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/inter-400-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/inter-400-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/inter-400-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/inter-500-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/inter-500-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/inter-500-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/inter-600-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/inter-600-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/inter-600-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/inter-700-cyrillic.woff2) format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/inter-700-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/inter-700-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
:root {
	color-scheme: light dark;

	--page: #ffffff;
	--page-tint: #f6f5f4;       /* warm white — section alternation */
	--surface: #ffffff;          /* cards */
	--text: rgba(0, 0, 0, .95);  /* softened black, not #000 */
	--text-2: #615d59;           /* warm gray 500 */
	/*
	 * A line, not a voice. It reaches 2.66:1 on white, and to clear AA it would
	 * have to be almost as dark as --text-2 — the luminance window between
	 * "readable" and "lighter than the secondary text" is empty, for this hue
	 * and for every other. So text that has to be read uses --text-2, and this
	 * draws borders and list markers.
	 */
	--text-3: #a39e98;
	--line: rgba(0, 0, 0, .1);   /* whisper border */
	--field-line: #dddddd;

	--accent: #0075de;
	--accent-active: #005bab;
	/*
	 * What is written ON the accent. Two tokens, not one, because the accent
	 * flips between themes: white clears 4.58:1 on the light-theme blue and
	 * only 2.40:1 on the dark-theme one. A single "on-accent" colour cannot
	 * serve both grounds — the luminance simply does not allow it.
	 */
	--on-accent: #ffffff;
	--focus: #097fe8;
	--badge-bg: #f2f9ff;
	/* Counts are 11–12px text, so they need 4.5:1 on their badge: #097fe8 was 3.79. */
	--badge-text: #0068c9;
	/*
	 * Danger carries words — the failure banner, the sign-in error, Delete — so
	 * it is a text colour and clears 4.5:1 on every ground, its own wash on the
	 * tint included. #dd5b00 was 3.1–3.8:1.
	 */
	--danger: #a64200;
	/*
	 * Three washes, declared rather than written into the rules that use them.
	 *
	 * --accent-line was rgba(0, 117, 222, .35) written inside .task:hover — the
	 * light theme's accent, spelled out. The accent flips to #62aef0 on dark and
	 * that literal did not, so a hovered task drew the light blue on the dark
	 * page. A colour that has to follow the theme has to be a token.
	 *
	 * --on-accent-wash follows --on-accent for the same reason it exists:
	 * what sits ON the accent cannot be one colour, because the accent is a
	 * dark blue in one theme and a light one in the other.
	 */
	--accent-line: rgba(0, 117, 222, .35);
	--on-accent-wash: rgba(255, 255, 255, .22);
	--danger-wash: rgba(166, 66, 0, .08);
	--done: #1aae39;

	/*
	 * Tinted, not black. Every neutral in this palette carries a yellow-brown
	 * undertone, and a pure black shadow under a warm surface reads grey — the
	 * one cool thing in an otherwise warm interface. Same opacities, same
	 * multi-layer falloff the contract asks for; only the hue changes.
	 */
	--card-shadow:
		rgba(49,42,32,.05) 0 4px 18px,
		rgba(49,42,32,.033) 0 2.025px 7.84688px,
		rgba(49,42,32,.024) 0 .8px 2.925px,
		rgba(49,42,32,.012) 0 .175px 1.04062px;

	--r-control: 4px;
	--r-small: 8px;
	--r-card: 12px;
	--r-pill: 9999px;

	/*
	 * One curve, named.
	 *
	 * Every transition here declared a duration and no timing function, which
	 * means they all ran on the browser's default curve, chosen by nobody.
	 * Decelerating: a control that starts fast and settles reads as answering
	 * the press, where the default reads as a thing that happens to be moving.
	 */
	--ease-out: cubic-bezier(.2, 0, 0, 1);
}

/*
 * Three states, in this order:
 *   1. no choice        -> follow the system  (the media query below)
 *   2. chose light      -> the media query must NOT win  (:not([data-theme="light"]))
 *   3. chose dark       -> win even on a light system    ([data-theme="dark"])
 *
 * Every dark token is therefore declared twice. Light lives on the bare
 * :root above, so a colour is never defined only inside a conditional block.
 */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) { 		--page: #1f1e1c;
		--page-tint: #262523;
		--surface: #31302e;        /* the contract's warm dark */
		--text: rgba(255, 255, 255, .95);
		--text-2: #b3ada6;
		--text-3: #8a847d;
		--line: rgba(255, 255, 255, .12);
		--field-line: rgba(255, 255, 255, .18);

		--accent: #62aef0;         /* its link colour for dark surfaces */
		--accent-active: #97cbf7;
		--on-accent: #11212e;
		--accent-line: rgba(98, 174, 240, .35);
		/* Dark ink, because on this theme the accent is the light colour. */
		--on-accent-wash: rgba(17, 33, 46, .18);
		--badge-bg: rgba(98, 174, 240, .14);
		--badge-text: #97cbf7;
		/* The light theme's danger is 3.5:1 on this card; the dark one has its own. */
		--danger: #ff8a4c;
		--danger-wash: rgba(255, 138, 76, .1);

		--card-shadow: rgba(12,10,8,.4) 0 4px 18px, rgba(12,10,8,.26) 0 2px 8px;
	 }
}
:root[data-theme="dark"] { 		--page: #1f1e1c;
		--page-tint: #262523;
		--surface: #31302e;        /* the contract's warm dark */
		--text: rgba(255, 255, 255, .95);
		--text-2: #b3ada6;
		--text-3: #8a847d;
		--line: rgba(255, 255, 255, .12);
		--field-line: rgba(255, 255, 255, .18);

		--accent: #62aef0;         /* its link colour for dark surfaces */
		--accent-active: #97cbf7;
		--on-accent: #11212e;
		--accent-line: rgba(98, 174, 240, .35);
		/* Dark ink, because on this theme the accent is the light colour. */
		--on-accent-wash: rgba(17, 33, 46, .18);
		--badge-bg: rgba(98, 174, 240, .14);
		--badge-text: #97cbf7;
		/* The light theme's danger is 3.5:1 on this card; the dark one has its own. */
		--danger: #ff8a4c;
		--danger-wash: rgba(255, 138, 76, .1);

		--card-shadow: rgba(12,10,8,.4) 0 4px 18px, rgba(12,10,8,.26) 0 2px 8px;
	 }

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

/*
 * A form carries margin-bottom: 1em from the browser's own stylesheet — a
 * legacy of forms as documents. Every form here is a control inside a layout
 * that manages its own spacing, so the margin only ever misaligns something.
 *
 * It was fixed once for the header nav, where it lifted the theme button 8px
 * above its neighbours. That was too narrow a fix for a general problem: the
 * same margin was lifting Pause and Stop 7px above their own table row, and
 * would have surfaced again in the next place a form became a control.
 */
form { margin: 0; }

body {
	margin: 0;
	/* No 300ms double-tap delay, and no grey flash on tap. */
	touch-action: manipulation;
	-webkit-tap-highlight-color: transparent;
	background: var(--page-tint);
	color: var(--text);
	font-family: Inter, -apple-system, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
	font-size: 1rem;
	font-weight: 400;
	line-height: 1.5;
	font-feature-settings: "lnum", "locl";
	-webkit-font-smoothing: antialiased;
	padding-bottom: env(safe-area-inset-bottom);
}

/*
 * One typeface, which is ADR-0009's original answer and stays that way.
 *
 * A pairing was tried — Instrument Serif on the wordmark, page titles and
 * horizon names — because a single face doing display and body is a known mark
 * of a generated interface. Seen running, it was rejected: the serif read as
 * borrowed from a different product rather than as this one's voice. That is
 * the same judgement that rejected the Swiss attempt, and it is the one that
 * counts. Hierarchy here comes from size and weight, not from a second face.
 */
h1, h2, h3 { margin: 0; font-weight: 700; text-wrap: balance; }
h1 { font-size: clamp(1.75rem, 5vw, 2.5rem); line-height: 1.04; letter-spacing: -.05em; }
h2 { font-size: 1.375rem; line-height: 1.27; letter-spacing: -.0125em; }
h3 { font-size: 1.125rem; line-height: 1.3; }
p { margin: 0 0 1rem; text-wrap: pretty; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-control); }

button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; border-radius: var(--r-control); }

/*
 * A control that cannot be pressed, said once for all of them.
 *
 * The copy-token button disables itself for the length of a fetch and used to
 * look exactly like a working one, so a slow copy read as a dead button. Said
 * with a named ground and the cursor rather than by fading the control:
 * opacity on an already-secondary colour drops it under 3:1, which trades a
 * confusing control for an unreadable one.
 */
button:disabled {
	cursor: not-allowed;
	background: var(--page-tint);
	border-color: var(--line);
	color: var(--text-2);
	box-shadow: none;
}
button:disabled:hover { background: var(--page-tint); color: var(--text-2); }

@media (prefers-reduced-motion: no-preference) {
	button, a, .task { transition: background-color .12s var(--ease-out), border-color .12s var(--ease-out),
	                       color .12s var(--ease-out), box-shadow .12s var(--ease-out); }
	button:active { transform: scale(.97); }
}

/* Visible only once focused — the first stop for a keyboard, invisible to everyone else. */
.skip {
	position: absolute; left: .5rem; top: -4rem; z-index: 3;
	background: var(--accent); color: var(--on-accent);
	padding: .5rem .875rem; font-weight: 600; border-radius: var(--r-control);
}
.skip:focus { top: .5rem; text-decoration: none; }

/* ---------- Shell ---------- */
/*
 * Edge-aligned, not a bar.
 *
 * The full-bleed strip with a hairline border underneath is the most-copied
 * header shape there is, and it was doing nothing here but drawing a line: the
 * app has one ground colour, so there is no edge between two surfaces for the
 * rule to mark. The header now sits in the same column as the content, on the
 * same page, and is separated by space rather than by a line.
 *
 * Still sticky — reaching the controls from halfway down a long Later list is
 * worth more than the tidiness of losing them.
 */
.bar {
	display: flex; align-items: center; gap: .5rem;
	max-width: 1200px; margin: 0 auto;
	/* The insets keep the header off a landscape notch; the gutter is the
	   same 1rem the page uses whenever the notch is narrower. */
	padding: 1rem max(1rem, env(safe-area-inset-right)) 1rem max(1rem, env(safe-area-inset-left));
	background: var(--page-tint);
	position: sticky; top: 0; z-index: 2;
}
@media (min-width: 768px) { .bar { padding: 1.5rem 2rem .75rem; } }
.wordmark { margin: 0; font-weight: 700; font-size: 1.25rem; letter-spacing: -.03em; color: var(--text); text-decoration: none; }
.wordmark.big { font-size: 2.5rem; letter-spacing: -.05em; display: block; margin-bottom: 1.5rem; }
/* Every control in the header is the same 34px box on the same baseline —
   otherwise a link, a button and the theme pill each pick their own height and
   the row visibly shifts between pages. */
.bar .nav { margin-left: auto; display: flex; align-items: center; gap: .25rem; }
.bar .nav form { display: flex; }
.nav-btn {
	width: 34px; height: 34px; padding: 0;
	display: inline-flex; align-items: center; justify-content: center;
	background: none; border: 1px solid transparent; border-radius: var(--r-control);
	color: var(--text-2); text-decoration: none;
}
.nav-btn:hover { background: var(--page-tint); border-color: var(--line); color: var(--text); text-decoration: none; }
/* Where you are, said rather than left to be worked out from the URL. */
.nav-btn.on { background: var(--page-tint); border-color: var(--line); color: var(--text); }
.nav-btn svg { width: 18px; height: 18px; }
.bar .wordmark { line-height: 1; }
.link {
	background: none; border: 0; padding: .5rem .75rem;
	font-size: .9375rem; font-weight: 500; color: var(--text-2);
	border-radius: var(--r-control);
}
.link:hover { color: var(--text); background: var(--page-tint); }

main {
	padding: .5rem max(1rem, env(safe-area-inset-right)) 4rem max(1rem, env(safe-area-inset-left));
	max-width: 1200px; margin: 0 auto;
}
/* The skip link lands here, and the header is sticky — without this the
   first task sits underneath it. */
#board { scroll-margin-top: 5rem; }

/* A request in flight, said rather than left to guesswork. htmx puts
   .htmx-request on the indicator for the life of the call. */
#board.htmx-request { opacity: .6; }
#board.htmx-request button { pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
	#board { transition: opacity .1s var(--ease-out); }
}
@media (min-width: 768px) { main { padding: .75rem 2rem 6rem; } }

/* Said once, on a board that has never held anything. */
.first-run {
	max-width: 60ch; margin: 0 0 2rem;
	color: var(--text-2); font-size: .9375rem; line-height: 1.6;
}
.first-run strong { color: var(--text); font-weight: 600; }

/* ---------- Bands ---------- */
/*
 * A phone shows one horizon at a time and this says which. Sticky, because the
 * answer to "where am I" has to be on screen at the moment the question is
 * asked, which is halfway down a list rather than at the top of one.
 */
.bands {
	/* Below the sticky header rather than under it: that row is 1rem of padding
	   around a 34px control box, and a tab half-hidden by it reads as broken. */
	position: sticky; top: 4.125rem; z-index: 3;
	display: flex; gap: .25rem;
	/*
	 * Edge to edge, against the page's own gutter.
	 *
	 * Four names and four counts do not fit a 320px screen and should not be
	 * shrunk until they do, so the strip scrolls. Inside the 1rem gutter the
	 * fourth tab began 8px before the right edge — present, and invisible.
	 * Pulled out to the edges it clears about 40px, which is the whole
	 * affordance: a tab cut in half says there is more this way.
	 *
	 * The insets are for landscape, where the notch eats the gutter.
	 */
	margin: 0 calc(-1 * max(1rem, env(safe-area-inset-right))) 1rem
	        calc(-1 * max(1rem, env(safe-area-inset-left)));
	padding: .375rem max(1rem, env(safe-area-inset-right)) .375rem
	         max(1rem, env(safe-area-inset-left));
	overflow-x: auto;
	/* Without this, a swipe past the last tab stops being the strip's and
	   becomes the browser's — which iOS reads as "go back". */
	overscroll-behavior-x: contain;
	scrollbar-width: none;
	background: var(--page-tint); border-bottom: 1px solid var(--line);
}
.bands::-webkit-scrollbar { display: none; }
.band-tab {
	display: flex; align-items: center; gap: .375rem; white-space: nowrap;
	padding: .4375rem .75rem; border-radius: var(--r-pill);
	font-size: .875rem; font-weight: 600; color: var(--text-2); text-decoration: none;
}
.band-tab .count {
	font-size: .6875rem; font-weight: 600;
	color: var(--badge-text); background: var(--badge-bg);
	border-radius: var(--r-pill); padding: .0625rem .375rem;
	font-variant-numeric: tabular-nums;
}
.band-tab:hover { color: var(--text); text-decoration: none; }
/* The one lit tab. Said with a filled ground and not with colour alone —
   aria-current says the same thing to a reader that cannot see it. */
.band-tab.on { background: var(--accent); color: var(--on-accent); }
.band-tab.on .count { background: var(--on-accent-wash); color: var(--on-accent); }

/* ---------- Board ---------- */
.horizon { margin-bottom: 2rem; }

/*
 * Now is louder than its neighbours in the type as well as the grid: a wider
 * column alone reads as an accident of layout, and one asymmetry stated twice
 * reads as a decision. Applied at every width, because the hierarchy is the
 * product's claim and not a desktop embellishment.
 */
#now > h2 { color: var(--text); }
#now > h2 .count { color: var(--on-accent); background: var(--accent); }
#now .task .text { font-weight: 500; }
.horizon h2 {
	font-size: .875rem; font-weight: 600; letter-spacing: normal;
	color: var(--text-2); margin-bottom: .75rem;
	display: flex; align-items: center; gap: .5rem;
}
.horizon h2 .count {
	font-size: .75rem; font-weight: 600; letter-spacing: .0125em;
	color: var(--badge-text); background: var(--badge-bg);
	border-radius: var(--r-pill); padding: .125rem .5rem;
	font-variant-numeric: tabular-nums;
}
.truncated {
	margin: -.25rem 0 .75rem; font-size: .8125rem; color: var(--text-2);
}

.horizon ul, .done-block ul { list-style: none; margin: 0 0 .75rem; padding: 0; }

.task {
	/* A long list stops costing layout for rows nobody has scrolled to.
	   The intrinsic size keeps the scrollbar from jumping as they render. */
	content-visibility: auto;
	contain-intrinsic-size: auto 56px;
	/* Wrapping, because a dictated task is a sentence and the controls beside
	   it are not worth a column of shredded text. They take the second line. */
	display: flex; align-items: flex-start; gap: .75rem; flex-wrap: wrap;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--r-card);
	box-shadow: var(--card-shadow);
	padding: .75rem;
	margin-bottom: .5rem;
}
.task:hover { border-color: var(--accent-line); }
/*
 * break-word, not anywhere: the second breaks at any character it likes, which
 * turned a squeezed column into vertical confetti. This one breaks only a word
 * that would otherwise overflow — a pasted URL, never a Ukrainian noun that
 * would have fitted on the next line.
 *
 * The basis is 14rem rather than 0, so the text claims a readable width before
 * the controls get any, and they wrap below it when there is not enough for
 * both.
 */
.task .text {
	flex: 1 1 14rem; min-width: 0; overflow-wrap: break-word;
	line-height: 1.5; padding-top: .0625rem;
}
.task form { display: contents; }

.tick {
	flex: none; width: 20px; height: 20px; margin-top: .1875rem;
	/* The secondary text colour, not the whisper: the tick is a control, and its outline needs 3:1. */
	background: none; border: 1.5px solid var(--text-2); border-radius: var(--r-control);
	display: grid; place-items: center; padding: 0; position: relative;
}
.tick::after {
	content: ""; width: 10px; height: 10px; border-radius: 1px;
	background: var(--accent); opacity: 0; transform: scale(.6);
}
.tick:hover { border-color: var(--accent); }
.tick:hover::after { opacity: .35; transform: scale(1); }
/* A comfortable tap target without a 44px box breaking the row rhythm. */
.tick::before { content: ""; position: absolute; inset: -12px; }
.tick.static { border-color: var(--done); cursor: default; }
.tick.static::after { background: var(--done); opacity: 1; transform: scale(1); }

/* Pushed to the end of whichever line they land on, and allowed to wrap
   themselves rather than force the row wider than its column. */
.moves { display: flex; gap: .25rem; flex: 0 1 auto; flex-wrap: wrap; margin-left: auto; }
.move {
	background: none; border: 1px solid transparent; border-radius: var(--r-control);
	padding: .25rem .5rem; font-size: .75rem; font-weight: 600; letter-spacing: .0125em;
	color: var(--text-2);
}
.move:hover { background: var(--page-tint); border-color: var(--line); color: var(--text); }
.nudge {
	background: none; border: 1px solid transparent; border-radius: var(--r-control);
	padding: .25rem .375rem; font-size: .8125rem; line-height: 1; color: var(--text-2);
	/* WCAG 2.5.8: a target no smaller than 24px, the icon centred in it. */
	display: inline-flex; align-items: center; justify-content: center;
	min-width: 1.5rem; min-height: 1.5rem;
}
.nudge svg { width: 16px; height: 16px; }
.nudge:hover { background: var(--page-tint); border-color: var(--line); color: var(--text); }

/* A row being repaired or questioned is a state, and states are visible. */
.task.editing, .task.confirming { border-color: var(--accent); }
.task.confirming { border-color: var(--danger); }

.edit { display: contents; }
.task.editing input[type=text] {
	/* The same basis as the text it replaces, so opening a long task for
	   editing does not change the shape of its row. */
	flex: 1 1 14rem; min-width: 0; padding: .375rem .5rem;
	background: var(--surface); color: var(--text);
	border: 1px solid var(--field-line); border-radius: var(--r-control);
	font-size: inherit; font-family: inherit;
}
.task.editing input:focus { outline: 2px solid var(--focus); outline-offset: -1px; border-color: transparent; }
.move.save { border-color: var(--field-line); color: var(--text); }
.move.save:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* Said in words beside the button, not left to the colour alone. */
.ask { font-size: .75rem; color: var(--text-2); align-self: center; }
.move.danger { color: var(--danger); border-color: var(--danger); }
.move.danger:hover { background: var(--danger); border-color: var(--danger); color: var(--on-accent); }

.task.done { box-shadow: none; background: transparent; }
.task.done .text { color: var(--text-2); text-decoration: line-through; }
.empty { color: var(--text-2); font-size: .875rem; margin: 0 0 .75rem; }

.add { display: flex; gap: .5rem; }
.add input[type=text] {
	flex: 1; min-width: 0; padding: .5rem .625rem;
	background: var(--surface); color: var(--text);
	border: 1px solid var(--field-line); border-radius: var(--r-control);
}
/* Placeholders are read, not glanced at — the horizon they name is only
   written here and in the aria-label. */
.add input::placeholder { color: var(--text-2); }
.add input:focus { outline: 2px solid var(--focus); outline-offset: -1px; border-color: transparent; }
.add button {
	background: none; border: 1px solid var(--field-line);
	padding: .5rem .875rem; font-size: .875rem; font-weight: 600; color: var(--text-2);
}
.add button:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

.done-block { border-top: 1px solid var(--line); padding-top: 1rem; margin-top: 1rem; }
.done-block summary {
	cursor: pointer; color: var(--text-2); font-size: .875rem; font-weight: 500;
	padding: .375rem 0; list-style-position: inside;
}
.done-block summary:hover { color: var(--text); }
/* A heading for navigation, set as the summary line it sits in. */
.done-heading { display: inline; font: inherit; margin: 0; letter-spacing: inherit; }

/* ---------- A request that failed ---------- */
.board-error {
	margin: 0 auto; max-width: 1200px;
	padding: .75rem 1rem; color: var(--danger); font-size: .9375rem; font-weight: 500;
	background: var(--danger-wash); border: 1px solid var(--danger);
	border-radius: var(--r-small);
}
.board-error[hidden] { display: none; }

/* What the last tap did. Empty most of the time, and then takes no room. */
.board-status {
	margin: 0 auto; max-width: 1200px; padding: .5rem 1rem;
	color: var(--text-2); font-size: .9375rem;
	display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
}
.board-status:empty { display: none; }
@media (min-width: 768px) { .board-status { margin: 0 2rem; } }
@media (min-width: 768px) { .board-error { margin: 0 2rem; } }

/* ---------- Install offer ---------- */
.install {
	position: fixed; z-index: 4;
	left: 1rem; right: 1rem; bottom: calc(1rem + env(safe-area-inset-bottom));
	display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
	background: var(--surface); border: 1px solid var(--line);
	border-radius: var(--r-card); box-shadow: var(--card-shadow);
	padding: .875rem 1rem;
}
.install[hidden] { display: none; }
.install p { margin: 0; display: flex; flex-direction: column; gap: .125rem; flex: 1; min-width: 12rem; }
.install span { color: var(--text-2); font-size: .875rem; }
.install-actions { display: flex; align-items: center; gap: .5rem; margin-left: auto; }
@media (min-width: 560px) { .install { left: auto; max-width: 26rem; } }

/* ---------- Sign in ---------- */
.signin {
	max-width: 24rem; margin: 3rem auto; padding: 2rem;
	background: var(--surface); border: 1px solid var(--line);
	border-radius: 16px; box-shadow: var(--card-shadow);
}
.signin label { display: block; font-size: .875rem; font-weight: 600; margin-bottom: .375rem; }
.signin input {
	width: 100%; padding: .5rem .625rem; margin-bottom: 1rem;
	background: var(--page); color: var(--text);
	border: 1px solid var(--field-line); border-radius: var(--r-control);
}
.signin input:focus { outline: 2px solid var(--focus); outline-offset: -1px; border-color: transparent; }
.signin button {
	width: 100%; background: var(--accent); color: var(--on-accent); border: 0;
	padding: .625rem 1rem; font-size: .9375rem; font-weight: 600;
	border-radius: var(--r-control);
}
.signin button:hover { background: var(--accent-active); }
.hint { color: var(--text-2); font-size: .875rem; margin: 1.25rem 0 0; }
.signin h1 { font-size: 1.375rem; margin: 0 0 .5rem; }

/* Both answers are one box of the same size on the same baseline.
   One shared rule rather than two that happen to agree: an anchor and a
   button disagree about borders, line-height and intrinsic height, and
   matching them by hand is a coincidence waiting to drift. Setting the form to
   display:contents drops it out of the layout so the button is a grid item
   like its neighbour; it still submits, because form association is not a
   display property. */
.confirm { display: grid; grid-template-columns: 1fr 1fr; gap: .625rem; margin-top: 1.5rem; }
.confirm form { display: contents; }
.confirm .stay, .confirm .secondary {
	display: flex; align-items: center; justify-content: center;
	padding: .625rem 1rem; font-size: .9375rem; font-weight: 600; line-height: 1.4;
	border-radius: var(--r-control); border: 1px solid transparent;
	text-decoration: none; min-height: 0; width: auto;
}
.confirm .stay { background: var(--accent); color: var(--on-accent); }
.confirm .stay:hover { background: var(--accent-active); text-decoration: none; }
.confirm .secondary { background: none; color: var(--text-2); border-color: var(--field-line); }
.confirm .secondary:hover { color: var(--danger); border-color: var(--danger); }
.error {
	color: var(--danger); font-size: .875rem; font-weight: 500;
	background: var(--danger-wash); border-radius: var(--r-small);
	padding: .5rem .75rem; margin: 0 0 1rem;
}

/* ---------- Theme control ---------- */
/* One button, the same box as its neighbours. It used to be a three-button
   pill: a different width and shape from everything beside it, which is what
   made the header look assembled from two designs. */
.theme { display: flex; flex: none; }
/*
 * An optical correction, not a size.
 *
 * Every glyph in this row sits in the same 34px box and is drawn on the same
 * 24 grid, and that is not the same as looking the same size. Measured on the
 * live header, the ink of each one covers:
 *
 *   routines 16.1x14.6   connect 13.1x16.1   admin 15.4x15.4
 *   sign out 13.1x13.1   theme   16.1x16.1
 *
 * The theme glyph is the only one that is the largest in BOTH directions, so
 * it carries about 10% more ink than anything beside it and reads as bigger —
 * which is exactly what it looked like. 17px brings its ink to 15.2 and puts
 * it in the middle of the row instead of at the top of it.
 */
.theme svg { width: 17px; height: 17px; }

/*
 * The phone board: one band, and a row that keeps its controls behind a door.
 *
 * Both are the same trade. A 390px row cannot hold six controls at a size a
 * thumb can hit, and a stacked board cannot show three horizons without the
 * boundaries between them going past unread. Neither is a problem on a desk,
 * so neither rule applies there.
 */
@media (max-width: 767px) {
	.band:not(.on) { display: none; }
	/* The switcher above already carries the name and the count, so the heading
	   is redundant to look at — but not to a screen reader, which needs the
	   section to still have one. Taken out of sight, not out of the document. */
	.horizon { margin-bottom: 0; }
	/* Adding is the commonest act on a phone; the field leads the band rather
	   than waiting under the last task. */
	.horizon { display: flex; flex-direction: column; }
	.horizon .add { order: -1; margin-bottom: .75rem; }
	.horizon h2, .done-block summary {
		position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
	}
	.done-block { border-top: 0; margin-top: 0; padding-top: 0; }

	.task .moves { display: none; }
	/* Opened on the row itself: the controls arrive under the text they act
	   on, wrapping rather than shrinking, at a size a thumb can land on. */
	.task:has(.row-menu[open]) { flex-wrap: wrap; }
	.task:has(.row-menu[open]) .moves { display: flex; flex-wrap: wrap; gap: .375rem; width: 100%; order: 2; }
	.task .moves.open { display: flex; flex-wrap: wrap; gap: .375rem; width: 100%; }
	.task:has(.row-menu[open]) .moves .move, .task:has(.row-menu[open]) .moves .nudge,
	.task .moves.open .move, .task .moves.open .nudge { padding: .5rem .75rem; font-size: .8125rem; }
	/* The door stays where it was, beside the text; the controls open below. */
	.row-menu { order: 1; margin-left: auto; }
	.row-menu summary { list-style: none; cursor: pointer; }
	.row-menu summary::-webkit-details-marker { display: none; }
	.row-menu[open] summary { color: var(--text); }
	/* The question takes its own line, so Delete and Cancel stay together on
	   the next one instead of Cancel wrapping alone under the row. */
	.task.confirming .moves.open .ask { flex-basis: 100%; }
	.more { font-size: 1rem; line-height: 1; letter-spacing: .0625em; padding: .375rem .5rem; }
}

@media (min-width: 768px) {
	/* The three columns say which horizon a task is in by where it sits, so
	   the switcher has nothing left to say and the door nothing to hide. */
	.bands { display: none; }
	.more, .row-menu { display: none; }

	/*
	 * Not three equal columns.
	 *
	 * The product's one claim is that Now, Next and Later are different kinds
	 * of thing — Now is what you are actually doing. Splitting the width evenly
	 * says the opposite, and it is the layout contradicting the premise rather
	 * than a matter of taste. Now gets the widest column and the others share
	 * what is left.
	 */
	#board { display: grid; grid-template-columns: 1.35fr 1fr 1fr; gap: 1.5rem; align-items: start; }
	/* Both of these are about the whole board, not one horizon — left as grid
	   items they take a column each and push the third horizon onto a new row. */
	.done-block, .first-run { grid-column: 1 / -1; }
	.horizon { margin-bottom: 0; }
}

/* A second level, below the shared header rather than instead of it. */
.tabs {
	display: flex; gap: 1.25rem; align-items: center;
	padding: 0 1rem .5rem;
	max-width: 1200px; margin: 0 auto;
}
@media (min-width: 768px) { .tabs { padding: 0 2rem .5rem; } }
.tabs a { font-size: .9375rem; font-weight: 500; color: var(--text-2); }
.tabs a.on, .tabs a:hover { color: var(--text); text-decoration: none; }

.admin h1 { margin-bottom: .5rem; }
.admin h2 { font-size: 1.125rem; margin: 2.5rem 0 .75rem; }
.admin .lede { color: var(--text-2); max-width: 60ch; margin: 0 0 1.5rem; }

.scroll-x {
	overflow-x: auto; background: var(--surface);
	border: 1px solid var(--line); border-radius: var(--r-card);
	box-shadow: var(--card-shadow);
}
.grid { width: 100%; border-collapse: collapse; font-size: .875rem; }
.grid th {
	text-align: left; font-size: .75rem; font-weight: 600; letter-spacing: .0125em;
	color: var(--text-2); padding: .75rem 1rem;
	border-bottom: 1px solid var(--line); white-space: nowrap;
}
.grid td { padding: .75rem 1rem; border-bottom: 1px solid var(--line); }
.grid tr:last-child td { border-bottom: 0; }
.grid .muted { color: var(--text-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.grid code {
	font-family: ui-monospace, SFMono-Regular, monospace; font-size: .8125rem;
	background: var(--page-tint); border-radius: var(--r-control); padding: .125rem .375rem;
}

.off { color: var(--danger); font-weight: 500; }
.hidden-note { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

.token-card {
	background: var(--surface); border: 1px solid var(--accent);
	border-radius: var(--r-card); box-shadow: var(--card-shadow);
	padding: 1.25rem; margin-bottom: 1.5rem;
}
.token-card p { margin: 0 0 .75rem; max-width: 60ch; }
.token {
	display: block; overflow-wrap: anywhere;
	font-family: ui-monospace, SFMono-Regular, monospace; font-size: .875rem;
	background: var(--page-tint); border-radius: var(--r-small);
	padding: .75rem; margin-bottom: .75rem;
}
.copy {
	background: var(--accent); color: var(--on-accent); border: 0;
	padding: .5rem 1rem; font-size: .875rem; font-weight: 600;
}
.copy:hover { background: var(--accent-active); }
.warn { color: var(--danger); font-size: .875rem; font-weight: 500; margin: .75rem 0 0; }

.add-user { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.add-user input[type=text] {
	flex: 1; min-width: 12rem; padding: .5rem .625rem;
	background: var(--surface); color: var(--text);
	border: 1px solid var(--field-line); border-radius: var(--r-control);
}
.add-user input[type=text]:focus { outline: 2px solid var(--focus); outline-offset: -1px; border-color: transparent; }
.add-user button {
	background: var(--accent); color: var(--on-accent); border: 0;
	padding: .5rem 1rem; font-size: .875rem; font-weight: 600;
}
.add-user button:hover { background: var(--accent-active); }
.check { display: flex; align-items: center; gap: .375rem; color: var(--text-2); font-size: .875rem; }

/* Prose still gets a reading width, but the column it sits in is the same one
   every other page uses — centring the narrow pages moved the left edge, so
   the whole interface appeared to shift as you walked through it. */
.connect > * { max-width: 52rem; }
.connect h1 { margin-bottom: .75rem; }
.connect .lede { color: var(--text-2); font-size: 1.125rem; max-width: 60ch; margin: 0 0 2.5rem; }
.section-label { font-size: 1.125rem; font-weight: 700; margin: 2.5rem 0 1rem; }
.connect ol { margin: 0; padding-left: 1.25rem; max-width: 60ch; }
.connect li { margin-bottom: .5rem; }
.connect li::marker { color: var(--text-3); font-variant-numeric: tabular-nums; }
.connect .prep { margin-bottom: 1rem; }
.connect .note { color: var(--text-2); max-width: 60ch; margin-top: 1rem; }

/* The one field on this page somebody types a paragraph into. */
.instructions textarea {
	display: block; width: 100%; max-width: 60ch;
	margin: 1rem 0 .75rem;
	padding: .75rem .875rem;
	background: var(--surface); color: var(--text);
	border: 1px solid var(--field-line); border-radius: var(--r-small);
	font-family: ui-monospace, SFMono-Regular, monospace;
	/* 16px: below it iOS zooms the page into the field and leaves it there. */
	font-size: 1rem; line-height: 1.6;
	resize: vertical;
}
.instructions textarea:focus { outline: 2px solid var(--focus); outline-offset: -1px; border-color: transparent; }
.instructions-actions { display: flex; align-items: center; gap: .75rem; margin: 0; flex-wrap: wrap; }
.instructions-actions .note { margin: 0; }

/* CSS-only switcher: it works before any script loads. Positioned, so the
   hidden radios stay inside it — absolute with no positioned ancestor
   anchors them to the page origin and focusing one scrolls the page. */
.switcher { display: flex; flex-wrap: wrap; gap: .375rem; position: relative; }
.tab-input { position: absolute; top: 0; left: 0; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.tab {
	padding: .5rem .875rem; font-size: .875rem; font-weight: 600;
	color: var(--text-2); cursor: pointer;
	background: var(--surface); border: 1px solid var(--line);
	border-radius: var(--r-pill);
}
.tab:hover { color: var(--text); border-color: var(--field-line); }
.tab-input:checked + .tab { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.tab-input:focus-visible + .tab { outline: 2px solid var(--focus); outline-offset: 2px; }
.panels {
	width: 100%; margin-top: 1.25rem; padding: 1.25rem;
	background: var(--surface); border: 1px solid var(--line);
	border-radius: var(--r-card); box-shadow: var(--card-shadow);
}
.guide { display: none; }
#tab-claude:checked ~ .panels #guide-claude { display: block; }
#tab-chatgpt:checked ~ .panels #guide-chatgpt { display: block; }
#tab-antigravity:checked ~ .panels #guide-antigravity { display: block; }
#tab-claude-code:checked ~ .panels #guide-claude-code { display: block; }
#tab-browser:checked ~ .panels #guide-browser { display: block; }
.code {
	font-family: ui-monospace, SFMono-Regular, monospace; font-size: .8125rem; line-height: 1.6;
	background: var(--page-tint); border: 1px solid var(--line); border-radius: var(--r-small);
	padding: .875rem; overflow-x: auto; margin: 1rem 0 0; max-width: 100%;
}

.copy-row { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin: 0 0 1rem; }
.copy-row .note { margin: 0; }
.paused {
	font-size: .75rem; font-weight: 600; letter-spacing: .0125em;
	color: var(--text-2); background: var(--page-tint);
	border-radius: var(--r-pill); padding: .125rem .5rem; margin-left: .5rem;
}
/*
 * Row actions sit at the table's right edge, not adrift in the middle of it.
 *
 * The actions column has no heading and no fixed width, so its buttons landed
 * wherever the three text columns happened to end — a hundred and forty pixels
 * short of the card's edge, which reads as a row that lost its alignment
 * rather than as a deliberate group.
 */
.row-actions { display: flex; gap: .375rem; justify-content: flex-end; }
.grid th:last-child, .grid td:last-child { text-align: right; }
.add-user select {
	padding: .5rem .625rem; background: var(--surface); color: var(--text);
	border: 1px solid var(--field-line); border-radius: var(--r-control);
}

/* The connect diagram, generated with archify and re-tokenised.
   archify paints its own surfaces; on this page the card already is the
   surface, so the lanes are left transparent and only their labels and rules
   remain. The viewBox is cropped to the drawing — the renderer sizes for a
   toolbar and legend room that this page does not use, which read as lopsided
   padding once embedded. */
.figure { width: 100%; height: auto; display: block; font-family: Inter, system-ui, sans-serif; }
.figure-wrap { margin: 0 0 2.5rem; overflow-x: auto; }
.figure { --mask: var(--surface); --bg: transparent; --panel: transparent; --panel-border: var(--line);
	--text: var(--text); --text-muted: var(--text-2); --text-dim: var(--text-3); --text-faint: var(--text-3);
	--grid: transparent; --lane-fill: transparent; --lane-stroke: var(--line);
	--arrow: var(--text-3); --arrow-emphasis: var(--accent);
	--frontend-fill: rgba(0,117,222,.08); --frontend-stroke: var(--accent);
	--backend-fill: rgba(0,117,222,.06); --backend-stroke: var(--accent);
	--database-fill: rgba(26,174,57,.10); --database-stroke: var(--done);
	--messagebus-fill: rgba(42,157,153,.12); --messagebus-stroke: #2a9d99;
	--security-fill: rgba(221,91,0,.10); --security-stroke: var(--danger);
	--cloud-fill: rgba(0,117,222,.06); --cloud-stroke: var(--accent);
	--external-fill: var(--page-tint); --external-stroke: var(--line);
}
.figure .c-grid{stroke: var(--grid); fill: none;}
.figure .c-mask{fill: var(--mask); stroke: none;}
.figure .c-frontend{fill: var(--frontend-fill);   stroke: var(--frontend-stroke);}
.figure .c-backend{fill: var(--backend-fill);    stroke: var(--backend-stroke);}
.figure .c-database{fill: var(--database-fill);   stroke: var(--database-stroke);}
.figure .c-security{fill: var(--security-fill);   stroke: var(--security-stroke);}
.figure .c-messagebus{fill: var(--messagebus-fill); stroke: var(--messagebus-stroke);}
.figure .t-primary{fill: var(--text);}
.figure .t-muted{fill: var(--text-muted);}
.figure .t-dim{fill: var(--text-dim);}
.figure .t-backend{fill: var(--backend-stroke);}
.figure .t-database{fill: var(--database-stroke);}
.figure .t-security{fill: var(--security-stroke);}
.figure .a-default{stroke: var(--arrow); fill: none;}
.figure .a-emphasis{stroke: var(--arrow-emphasis); fill: none;}
.figure .a-dashed{stroke: var(--database-stroke); fill: none; stroke-dasharray: 4,4;}
.figure .m-default{fill: var(--arrow);}
.figure .m-emphasis{fill: var(--arrow-emphasis);}
.figure .m-security{fill: var(--security-stroke);}
.figure .m-dashed{fill: var(--database-stroke);}
.figure .c-lane{fill: var(--lane-fill); stroke: var(--lane-stroke); stroke-dasharray: 6,6;}
/* Dark: the light tints were nearly invisible on a warm dark card — a node
   read as an outline floating on nothing. The fills carry more of the colour
   here and the rules are lifted, which is the same trade the rest of the app
   makes between the two themes. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .figure { 		--lane-stroke: rgba(255,255,255,.16);
		--arrow: rgba(255,255,255,.45);
		--frontend-fill: rgba(98,174,240,.18); --frontend-stroke: #62aef0;
		--backend-fill: rgba(98,174,240,.14); --backend-stroke: #62aef0;
		--database-fill: rgba(60,200,95,.18); --database-stroke: #57cc74;
		--messagebus-fill: rgba(70,200,195,.18); --messagebus-stroke: #4fc9c4;
		--security-fill: rgba(240,140,70,.18); --security-stroke: #f0995e;
		--external-fill: rgba(255,255,255,.06); --external-stroke: rgba(255,255,255,.18);
	 }
}
:root[data-theme="dark"] .figure { 		--lane-stroke: rgba(255,255,255,.16);
		--arrow: rgba(255,255,255,.45);
		--frontend-fill: rgba(98,174,240,.18); --frontend-stroke: #62aef0;
		--backend-fill: rgba(98,174,240,.14); --backend-stroke: #62aef0;
		--database-fill: rgba(60,200,95,.18); --database-stroke: #57cc74;
		--messagebus-fill: rgba(70,200,195,.18); --messagebus-stroke: #4fc9c4;
		--security-fill: rgba(240,140,70,.18); --security-stroke: #f0995e;
		--external-fill: rgba(255,255,255,.06); --external-stroke: rgba(255,255,255,.18);
	 }


/* WCAG 2.4.11: the sticky header and band strip are about 115px on a phone,
   and a control focused by Tab or autofocus could land beneath them. */
html { scroll-padding-top: 8rem; }
@media (min-width: 768px) { html { scroll-padding-top: 6rem; } }

/* Forced colours drop backgrounds, and the band shown was marked by one. */
@media (forced-colors: active) {
	.band-tab.on, .tab-input:checked + .tab, .tabs a.on { border: 2px solid Highlight; outline: 2px solid Highlight; }
	.tick { border-color: CanvasText; }
}

/* A short viewport — a landscape phone, or 400% zoom — cannot spare half its
   height for two sticky bars. */
@media (max-height: 500px) {
	.bar, .bands { position: static; }
}

/* The install card is fixed to the bottom; while it shows, the page keeps
   room for it instead of hiding the add field and the last rows. */
body:has(#install:not([hidden])) { padding-bottom: 10rem; }

@media (max-width: 599px) {
	/* Tables become cards: each row a block, its actions at the end, nothing
	   scrolled off the side of the screen. */
	.grid, .grid tbody, .grid tr, .grid td { display: block; width: 100%; }
	.grid thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
	.grid tr { padding: .75rem 1rem; border-bottom: 1px solid var(--line); }
	.grid tr:last-child { border-bottom: 0; }
	.grid td { padding: .125rem 0; border: 0; text-align: left; }
	.grid td:last-child { text-align: left; }
	.grid td.row-actions { display: flex; justify-content: flex-start; flex-wrap: wrap; margin-top: .375rem; }
	.scroll-x { overflow-x: visible; }

	/* The diagram keeps a readable size and scrolls, rather than shrinking its
	   text to five pixels. */
	.figure-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
	.figure-wrap svg { min-width: 640px; }
}
