/* Toast Box -- warm letterpress. The whole visual argument is: this looks like
   a hand-set invitation, not a Google Form, so answering it feels personal.
   Palette is defined once on :root and only re-tinted for dark.

   Every colour pair below is measured, not eyeballed. The ratios in the
   comments are against the surface the token is actually used on; if you
   re-tint the palette, re-check them rather than trusting the hue. */

:root {
	color-scheme: light dark;

	--paper: #fbf7f0;
	--paper-raised: #fffdf9;
	--paper-sunk: #f3ece1;
	--ink: #221e1a;
	--ink-soft: #4d443b;
	/* 5.22 on --paper, 5.49 on --paper-raised, 4.75 on --paper-sunk. The old
	   #877a6c sat at 3.9-4.1 and failed AA everywhere it was used at small
	   sizes -- which was the eyebrow, occasion line, hint, footer, pill labels,
	   "optional" and the character counter. */
	--ink-muted: #736657;
	--rule: #e3d9c9;
	--rule-strong: #cdbfa9;
	/* Interactive boundaries are held to the 3:1 of WCAG 1.4.11, unlike the
	   purely decorative --rule above. 3.87 on --paper-raised, 3.69 on --paper. */
	--field-border: #8f7e64;
	--accent: #a8532a;
	--accent-soft: #f0dfd2;
	--accent-ink: #ffffff;
	--gold: #b98b30;
	--danger: #a33a2f;
	--danger-soft: #f7e4e0; /* --danger on this: 5.35 */

	--display: "Fraunces", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
	--sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

	--radius: 14px;
	--shadow: 0 1px 2px rgb(60 40 20 / 6%), 0 8px 24px -12px rgb(60 40 20 / 18%);
	--measure: 34rem;
	/* Comfortable minimum for a thumb. Applied to controls under a coarse
	   pointer rather than everywhere, so the desk-bound console stays dense. */
	--touch: 44px;
}

@media (prefers-color-scheme: dark) {
	:root {
		--paper: #17130f;
		--paper-raised: #211b16;
		--paper-sunk: #120e0b;
		--ink: #f3ebe0;
		--ink-soft: #d3c7b8;
		--ink-muted: #9b8d7c;
		--rule: #342b22;
		--rule-strong: #4a3d31;
		--field-border: #7d6c57; /* 3.37 on --paper-raised, 3.66 on --paper */
		--accent: #e08a5a;
		--accent-soft: #3a2418;
		--accent-ink: #17130f;
		--gold: #d9ab55;
		--danger: #e07a6c;
		--danger-soft: #331915; /* --danger on this: 5.56 */
		--shadow: 0 1px 2px rgb(0 0 0 / 30%), 0 10px 30px -14px rgb(0 0 0 / 70%);
	}
}

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

/* The `hidden` attribute only sets `display: none` in the UA stylesheet, so any
   author rule that sets display -- .facts, .fact, .prompts -- silently beats it
   and renders an empty element. Toggling visibility via el.hidden is the pattern
   used throughout app.js and admin.js, so make it actually stick. */
[hidden] {
	display: none !important;
}

body {
	margin: 0;
	background: var(--paper);
	color: var(--ink);
	font-family: var(--sans);
	font-size: 17px;
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/* A faint paper grain. Pure CSS, so it costs no request and never blocks. */
body::before {
	content: "";
	position: fixed;
	inset: 0;
	pointer-events: none;
	z-index: 0;
	opacity: 0.5;
	background-image: radial-gradient(circle at 12% 18%, var(--paper-sunk) 0, transparent 45%),
		radial-gradient(circle at 88% 8%, var(--accent-soft) 0, transparent 38%);
}

.wrap {
	position: relative;
	z-index: 1;
	max-width: 46rem;
	margin: 0 auto;
	padding: 0 1.5rem 5rem;
}

h1,
h2,
h3 {
	font-family: var(--display);
	font-weight: 600;
	letter-spacing: -0.015em;
	line-height: 1.12;
	margin: 0;
	font-variation-settings: "SOFT" 40, "WONK" 1;
}

a {
	color: var(--accent);
	text-underline-offset: 3px;
}

/* ------------------------------------------------------------------ hero -- */

.hero {
	padding: 3.5rem 0 2.5rem;
	text-align: center;
}

.eyebrow {
	font-size: 0.72rem;
	font-weight: 600;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--ink-muted);
	margin: 0 0 1.5rem;
}

.eyebrow::after {
	content: "";
	display: block;
	width: 2.5rem;
	height: 1px;
	margin: 1.1rem auto 0;
	background: var(--rule-strong);
}

.hero h1 {
	font-size: clamp(2.4rem, 8vw, 4.2rem);
}

.hero h1 em {
	font-style: italic;
	color: var(--accent);
}

.occasion {
	font-family: var(--display);
	font-style: italic;
	font-size: 1.05rem;
	color: var(--ink-muted);
	margin: 1rem 0 0;
}

.lede {
	max-width: var(--measure);
	margin: 1.5rem auto 0;
	font-size: 1.09rem;
	color: var(--ink-soft);
	text-wrap: pretty;
}

/* Countdown + running total. Two small facts that do a lot of persuading.
   Both are server-rendered, so this row never pops in after paint. */
.facts {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.6rem;
	margin-top: 1.75rem;
}

.fact {
	display: inline-flex;
	align-items: baseline;
	gap: 0.45rem;
	padding: 0.4rem 0.9rem;
	border: 1px solid var(--rule);
	border-radius: 999px;
	background: var(--paper-raised);
	font-size: 0.85rem;
	color: var(--ink-muted);
}

.fact b {
	font-family: var(--display);
	font-size: 1.05rem;
	font-weight: 600;
	color: var(--ink);
}

.fact.urgent {
	border-color: var(--accent);
	background: var(--accent-soft);
}

.fact.urgent b {
	color: var(--accent);
}

/* The one thing the page wants you to do, offered before the scroll rather than
   three screens into it.

   .btn-primary is full-width for the submit button at the bottom of the form;
   the hero CTA must not inherit that, or the page carries two identical
   full-bleed primary buttons and the larger one is the one that only scrolls.

   Selector is .btn.hero-cta, not .hero-cta: .btn-primary is declared later in
   this file with equal specificity, so a bare .hero-cta loses `width` to it on
   source order -- the same trap documented in CLAUDE.md. */
.btn.hero-cta {
	display: inline-block;
	width: auto;
	margin-top: 1.75rem;
	padding-inline: 2.25rem;
	text-decoration: none;
}

/* --------------------------------------------------------------- prompts -- */

.section-head {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin: 3rem 0 1.25rem;
}

.section-head h2 {
	font-size: 1.28rem;
	white-space: nowrap;
}

.section-head::after {
	content: "";
	flex: 1;
	height: 1px;
	background: var(--rule);
}

.hint {
	color: var(--ink-muted);
	font-size: 0.92rem;
	/* Same measure as .lede. It ran the full 688px at the page's max width,
	   which the old two-line hint got away with and a longer one does not. */
	max-width: var(--measure);
	margin: 0 0 1.25rem;
}

/* The whole pool lives in the DOM and all but three ship hidden, so this grid
   only ever holds exactly three items -- which is why it steps straight from
   one column to three and never passes through two. auto-fill did pass through
   two, between roughly 640px and the 46rem page width, and three cards in two
   columns is a row plus an orphan sitting next to a hole.

   3 x 1fr from 40rem, which holds only because prompts are capped at ~65
   characters (see PROMPT_POOL in src/worker.ts). Measured at 660px: 196px
   columns, longest prompt four lines, tallest card 130px. Let the copy grow
   back past that and this breakpoint has to move up toward 46rem -- at 102
   characters the same column was six lines and 178px. */
.prompts {
	display: grid;
	grid-template-columns: 1fr;
	gap: 0.7rem;
}

/* After the base rule, not before it: equal specificity, and a media query
   above the rule it means to override loses. */
@media (min-width: 40rem) {
	.prompts {
		grid-template-columns: repeat(3, 1fr);
	}
}

.prompt {
	position: relative;
	/* Grid stretches all three cards to the tallest, and a button centres its
	   own content vertically -- so a two-line prompt floated in the middle of a
	   five-line row and the three cards started their text up to 37px apart.
	   text-align: left already promises text starts top-left; this keeps that
	   promise. display: block does NOT fix it in Chrome. */
	display: flex;
	align-items: flex-start;
	text-align: left;
	font: inherit;
	font-size: 0.95rem;
	color: var(--ink);
	background: var(--paper-raised);
	border: 1px solid var(--field-border);
	border-radius: var(--radius);
	padding: 0.95rem 1.05rem;
	cursor: pointer;
	transition: transform 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease;
}

/* The span carries the text so the entrance animation has something to fade
   that isn't the card itself. Let it take the full width, or prompts wrap
   against their own content box instead of the card's padding. */
.prompt > span {
	flex: 1;
}

.prompt:hover {
	transform: translateY(-2px);
	border-color: var(--accent);
	box-shadow: var(--shadow);
}

.prompt[aria-pressed="true"] {
	border-color: var(--accent);
	background: var(--accent-soft);
}

/* Sized to its content, not to the grid. Full-width made the one optional,
   tertiary control on the page wider than "Send it in" at every breakpoint --
   and a full-width bar directly under a list is the Load More convention, so
   the shape argued with the label. */
.shuffle-prompts {
	display: block;
	margin: 0.85rem auto 0;
	width: auto;
}

/* Swapping three cards with no transition reads as a glitch rather than as an
   answer to the button that was just pressed. Nudged in from below so the eye
   is told where to look; the reduced-motion block at the foot of this file
   zeroes the duration for anyone who has asked for that.

   Animates the text span, never the card: fading the button faded its border
   and background with it, so for 0.22s all three cards went translucent at
   once and the section read as a page flash. Holding the frames still and
   changing only what is inside them also hides most of the row-height change,
   because the grid never visually empties. */
@keyframes prompt-in {
	from {
		opacity: 0;
		transform: translateY(4px);
	}
}

.prompt.is-fresh > span {
	animation: prompt-in 0.22s ease both;
}

/* ------------------------------------------------------------------ form -- */

.card {
	background: var(--paper-raised);
	border: 1px solid var(--rule);
	border-radius: calc(var(--radius) + 4px);
	box-shadow: var(--shadow);
	padding: clamp(1.25rem, 4vw, 2rem);
}

.field {
	margin-bottom: 1.15rem;
}

.field:last-of-type {
	margin-bottom: 0;
}

label {
	display: block;
	font-size: 0.86rem;
	font-weight: 600;
	letter-spacing: 0.01em;
	margin-bottom: 0.4rem;
}

label .optional {
	font-weight: 400;
	color: var(--ink-muted);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
textarea,
select {
	width: 100%;
	font: inherit;
	color: var(--ink);
	background: var(--paper);
	border: 1px solid var(--field-border);
	border-radius: 10px;
	padding: 0.65rem 0.8rem;
	transition: border-color 0.14s ease, box-shadow 0.14s ease;
}

textarea {
	min-height: 11rem;
	resize: vertical;
	line-height: 1.65;
}

/* A 3px tint on paper measures 1.2:1 -- decorative, not an indicator. The
   outline is the actual focus signal: --accent is 5:1 on light, 7:1 on dark. */
:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

input:focus-visible,
textarea:focus-visible,
select:focus-visible {
	border-color: var(--accent);
}

/* Set by app.js when a field fails validation, alongside moving focus to it. */
[aria-invalid="true"] {
	border-color: var(--danger);
	box-shadow: 0 0 0 3px var(--danger-soft);
}

.row {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 1rem;
}

@media (max-width: 34rem) {
	.row {
		grid-template-columns: 1fr;
		gap: 0;
	}
}

/* Echo of the spark card the contributor picked. Without this the chosen
   prompt lives only in a hidden input, and by the time they reach the box the
   question they agreed to answer has scrolled off the top of the screen. */
.prompt-echo {
	display: flex;
	align-items: flex-start;
	gap: 0.6rem;
	margin: 0 0 0.5rem;
	padding: 0.55rem 0.75rem;
	border-left: 3px solid var(--accent);
	border-radius: 0 8px 8px 0;
	background: var(--accent-soft);
	font-size: 0.92rem;
	color: var(--ink);
}

.prompt-echo span {
	flex: 1;
	font-style: italic;
}

.prompt-echo button {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	margin: -0.35rem -0.4rem -0.35rem 0;
	font: inherit;
	font-size: 1.1rem;
	line-height: 1;
	border: 0;
	border-radius: 6px;
	background: transparent;
	color: var(--ink-soft);
	cursor: pointer;
}

.prompt-echo button:hover {
	background: var(--paper-raised);
}

.counter {
	text-align: right;
	font-size: 0.78rem;
	color: var(--ink-muted);
	margin-top: 0.3rem;
}

.counter.over {
	color: var(--danger);
}

.check {
	display: flex;
	gap: 0.65rem;
	align-items: flex-start;
	padding: 0.85rem 1rem;
	border: 1px dashed var(--field-border);
	border-radius: 10px;
	background: var(--paper-sunk);
	cursor: pointer;
}

.check input {
	margin: 0.28rem 0 0;
	accent-color: var(--accent);
	flex-shrink: 0;
	width: 1.05rem;
	height: 1.05rem;
}

/* The surrounding `label` rule is 600; reset here or the description reads as
   semibold and the nested <b> resolves to 900 -- a weight we never load, so the
   browser fakes it. */
.check span {
	font-size: 0.88rem;
	font-weight: 400;
	color: var(--ink-soft);
}

.check b {
	display: block;
	font-weight: 600;
	color: var(--ink);
	font-size: 0.9rem;
}

/* Sits with the submit button, not in the footer: the reassurance has to be
   readable at the moment someone decides whether to type the risky story. */
.privacy {
	display: flex;
	gap: 0.5rem;
	margin: 1.25rem 0 0;
	font-size: 0.84rem;
	line-height: 1.5;
	color: var(--ink-muted);
}

.privacy span[aria-hidden] {
	font-size: 0.8rem;
	line-height: 1.6;
}

/* ---------------------------------------------------------------- buttons -- */

.btn {
	font: inherit;
	font-weight: 600;
	border-radius: 999px;
	border: 1px solid transparent;
	padding: 0.7rem 1.5rem;
	cursor: pointer;
	transition: transform 0.12s ease, opacity 0.12s ease, background 0.12s ease;
}

.btn:active {
	transform: translateY(1px);
}

.btn[disabled] {
	opacity: 0.55;
	cursor: not-allowed;
}

.btn-primary {
	background: var(--accent);
	color: var(--accent-ink);
	width: 100%;
	padding: 0.85rem 1.5rem;
	font-size: 1rem;
}

.btn-ghost {
	background: transparent;
	color: var(--ink);
	border-color: var(--field-border);
}

.btn-ghost:hover {
	background: var(--paper-sunk);
}

.btn-sm {
	font-size: 0.82rem;
	padding: 0.4rem 0.85rem;
}

/* Thumbs need more than the 35-36px the dense console gives a mouse. */
@media (pointer: coarse) {
	.btn-sm,
	.tab,
	.link-btn {
		min-height: var(--touch);
		padding-inline: 1rem;
	}
}

/* ----------------------------------------------------------------- states -- */

.alert {
	margin-top: 1rem;
	padding: 0.7rem 0.95rem;
	border-radius: 10px;
	font-size: 0.9rem;
	background: var(--danger-soft);
	color: var(--danger);
	border: 1px solid currentcolor;
}

.thanks {
	text-align: center;
	padding: 1rem 0 0.5rem;
}

/* Focused programmatically after a successful submit, so the swap is
   announced rather than the form simply vanishing. */
.thanks:focus {
	outline: none;
}

.thanks .mark {
	font-family: var(--display);
	font-size: 3rem;
	color: var(--accent);
	line-height: 1;
}

.thanks h2 {
	font-size: 1.9rem;
	margin: 0.75rem 0 0;
}

.thanks p {
	color: var(--ink-soft);
	max-width: 28rem;
	margin: 0.7rem auto 0;
}

.share {
	display: flex;
	gap: 0.5rem;
	justify-content: center;
	flex-wrap: wrap;
	margin-top: 1.6rem;
}

footer {
	margin-top: 3.5rem;
	padding-top: 1.5rem;
	border-top: 1px solid var(--rule);
	text-align: center;
	font-size: 0.82rem;
	color: var(--ink-muted);
}

.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}

/* Two smooth scrollIntoView calls and a handful of transforms; none of it is
   load-bearing, so honour the OS setting. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}

	.prompt:hover,
	.btn:active {
		transform: none;
	}
}
