/* Brand tokens, and the three places µCSS's indigo theme is not used as shipped.

   --nf-brand is the indigo theme's light-mode primary, held fixed. Primary
   itself lightens in dark mode (#655cd6 -> #8577dd) so it can be read as text
   on a dark page, and that is right for text and borders -- but a fill that
   carries white type needs the darker value in both modes, which is why the
   closing band and the buttons below use this and not --mu-primary.

   1. --mu-primary-background. Every µCSS theme sets it to a pale tint in light
      mode (indigo: #d8d0f1) for its alert, badge and hero components, and the
      plain <button> reads the same variable -- so a default button is white
      type on lavender at about 1.4:1, and only becomes legible on hover. This
      site uses none of those tinted components, so the variable is repointed
      at the solid brand fill. Dark mode ships #1f1e5e, which reads, but sits on
      the dark page as a button you have to look for.
   2. --mu-primary-hover-background, repointed with it so a hover still moves,
      and always darker. The theme's dark hover is #8577dd, which is white type
      at 3.7:1 -- and because its button:focus reads the same variable, a
      clicked button would hold that for as long as it kept focus. Dark mode
      uses #5e56c7 instead: white type at 5.8:1, and still 3.1:1 against the
      page, which a darker value would not be (#3838ab is 2.0:1 there, so the
      button would sink into the background it sits on). Light mode has room
      for the deeper #3838ab (9:1).
   3. --nf-contrast. Pico's --pico-contrast was the near-black (light) or
      near-white (dark) a label needs on a coloured fill. In µCSS "contrast" is
      a colour role, and in indigo it is pink; the old meaning gets its own
      name rather than inheriting a hue.

   :not([data-theme]) mirrors the selector the theme's own dark block uses, to
   tie its specificity -- this file loads after it, so the tie goes to us. The
   site never sets data-theme; if it ever does, these need the same pair of
   selectors the theme has. */
:root {
	--nf-brand: #655cd6;
}

:root:not([data-theme]) {
	--mu-primary-background: var(--nf-brand);
	--mu-primary-hover-background: #3838ab;
	--nf-contrast: #181c25;
}

@media only screen and (prefers-color-scheme: dark) {
	:root:not([data-theme]) {
		--mu-primary-hover-background: #5e56c7;
		--nf-contrast: #dfe3eb;
	}
}

/* Light mode's page background. The theme ships pure white, and indigo reads
   better against a faint cool grey than against white: #f3f4f9 is about two
   steps off white with a little of the brand's blue in it. Text still clears
   AA on it: body 10.1:1, muted 4.9:1, brand-coloured text 4.7:1.

   Two things hang off --mu-background-color that must not move with it.
   --mu-card-background-color is *defined as* var(--mu-background-color), so
   darkening the page alone would darken every card with it and leave them
   dissolving into the page -- they are pinned back to white so they lift off
   it instead. And this is `not (prefers-color-scheme: dark)` rather than an
   unconditional rule because the dark block above and the theme's own dark
   block tie this selector's specificity and this file loads last: an
   unconditional override would win in dark mode too and paint it light. */
@media not (prefers-color-scheme: dark) {
	:root:not([data-theme]) {
		--mu-background-color: #f3f4f9;
		--mu-card-background-color: #fff;
	}
}

header {
	padding-bottom: var(--mu-spacing);
	border-bottom: 1px solid var(--mu-muted-border-color);
}

/* Hides Alpine-controlled elements (x-cloak) until Alpine has booted and
   evaluated their x-show, so an initially-false x-show never flashes
   visible for a frame first -- e.g. profile.templ's savedBadge. */
[x-cloak] {
	display: none !important;
}

body header nav li {
    margin: 0;
    padding: 0;
}
body header nav li h2{
    margin: 0;
}
/* Space the tabs with a gap, not per-button margins: the active tab
   carries no margin of its own, so margins shifted the whole row
   sideways each time the active tab changed. */
body header nav ul {
    gap: 0.5rem;
}
/* µCSS gives every <a> in a nav li a negative vertical margin, then only
   resets margin-bottom for [role=button] -- leaving link-buttons sitting
   ~4px higher than sibling <button> tabs. */
body header nav li a[role="button"] {
    margin-top: 0;
    margin-bottom: 0;
}

/* App nav (the signed-in tab row) */

/* Anchors the dropdown panel, which is positioned against the nav below the
   breakpoint. */
#app-nav {
    position: relative;
}

/* The hamburger and the logout label only exist inside the dropdown; wide
   screens show the tabs inline, where there is nothing to toggle and the
   logout icon is unambiguous on its own. */
#app-nav .app-nav-burger,
#app-nav .app-nav-logout-label {
    display: none;
}

/* Four tabs plus the logout button and the wordmark do not fit across a
   phone -- they wrap into a second row that collides with the header rule.
   Below the breakpoint the tab row becomes a panel the hamburger opens.

   Collapsed is the default state in CSS rather than an Alpine x-show, so a
   full page load renders it closed in the frames before Alpine boots; the
   .app-nav-open class Alpine adds is the only thing that reveals it.

   767.98 rather than 767 so nothing falls between this and the inline row
   at a fractional width (zoom, some device pixel ratios), which is where
   the cramped five-across tabs this replaces would reappear. */
@media (max-width: 767.98px) {
    #app-nav .app-nav-tabs {
        display: none;
        position: absolute;
        top: 100%;
        right: 0;
        left: 0;
        z-index: 10;
        flex-direction: column;
        align-items: stretch;
        margin-top: var(--mu-spacing);
        padding: 0.5rem;
        background-color: var(--mu-card-background-color);
        border: 1px solid var(--mu-muted-border-color);
        border-radius: var(--mu-border-radius);
        box-shadow: var(--mu-card-box-shadow);
    }

    #app-nav .app-nav-tabs.app-nav-open {
        display: flex;
    }

    /* µCSS sizes nav buttons to their label; as stacked rows they want the
       whole panel width, both to line up and to give a thumb a real target. */
    #app-nav .app-nav-tabs button {
        width: 100%;
        margin: 0;
    }

    #app-nav .app-nav-burger {
        display: flex;
    }

    #app-nav .app-nav-logout-label {
        display: inline;
        margin-left: 0.5rem;
    }
}

footer {
	padding-top: var(--mu-spacing);
	border-top: 1px solid var(--mu-muted-border-color);
}
html, body {
	height: 100%;
}

body {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
}

main {
	flex: 1;
}

/* Taste compass */

.taste-compass-card-header {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--mu-spacing);
	margin-bottom: var(--mu-spacing);
}

.taste-compass-card-header h3 {
	margin-bottom: 0;
}

.taste-compass-count {
	color: var(--mu-muted-color);
	font-size: 0.85rem;
	white-space: nowrap;
}

.taste-compass-caption {
	margin: var(--mu-spacing) 0 0;
	color: var(--mu-muted-color);
	font-size: 0.75rem;
}

.taste-compass-plot {
	display: grid;
	grid-template-columns: auto 1fr;
	grid-template-rows: 1fr 1fr auto;
	column-gap: var(--mu-spacing);
	row-gap: calc(var(--mu-spacing) / 2);
}

.taste-compass-label {
	color: var(--mu-muted-color);
	font-size: 0.75rem;
	font-weight: bold;
	letter-spacing: 0.05em;
	text-transform: uppercase;
}

.taste-compass-label-top {
	grid-column: 1;
	grid-row: 1;
	align-self: start;
}

.taste-compass-label-bottom {
	grid-column: 1;
	grid-row: 2;
	align-self: end;
}

/* No overflow: hidden, deliberately. This used to clip, as a last line of
   defense against a dot landing outside the plot -- but the dots carry µCSS
   tooltips drawn as pseudo-elements *inside* this box, so clipping cropped the
   title card of every dot near an edge instead of letting it hang over the
   frame. The defense is not needed: both coordinates are clamped to
   +/-tastespace.CoordScale when they are computed and again to 0-100% when they
   are rendered, so the worst case is a dot centered exactly on the border
   overhanging it by half its width. Tooltips near an edge are flipped to the
   opposite side instead -- see tasteCompassTooltipPlacement. */
.taste-compass-grid {
	position: relative;
	grid-column: 2;
	grid-row: 1 / span 2;
	aspect-ratio: 1 / 1;
	border: 1px solid var(--mu-muted-border-color);
	border-radius: var(--mu-border-radius);
}

.taste-compass-hline,
.taste-compass-vline {
	position: absolute;
	background: var(--mu-muted-border-color);
}

.taste-compass-hline {
	top: 50%;
	left: 0;
	width: 100%;
	height: 1px;
}

.taste-compass-vline {
	top: 0;
	left: 50%;
	width: 1px;
	height: 100%;
}

.taste-compass-axis-x {
	grid-column: 2;
	grid-row: 3;
	display: flex;
	justify-content: space-between;
}

/* A single film's position (movie detail page), plotted directly on the
   grid rather than inside a --tc-x/--tc-y cluster like the dots above. */
.taste-compass-point {
	position: absolute;
	top: var(--tc-y);
	left: var(--tc-x);
	width: 0.65rem;
	height: 0.65rem;
	border-radius: 50%;
	background: var(--mu-primary);
	border: 2px solid var(--mu-card-background-color);
	box-shadow: 0 0 0 1px var(--mu-primary);
	transform: translate(-50%, -50%);
}

/* A user's actual rated films (profile page), plotted directly on the grid
   like .taste-compass-point above but colored per-dot by tier via the
   shared .grade-badge-* rules instead of one fixed color. */
.taste-compass-scatter-dot {
	position: absolute;
	top: var(--tc-y);
	left: var(--tc-x);
	width: 0.55rem;
	height: 0.55rem;
	border-radius: 50%;
	background: var(--gb-color);
	border: 1px solid var(--mu-card-background-color);
	transform: translate(-50%, -50%);
	text-decoration: none;
	cursor: pointer;
	transition: width 0.1s ease, height 0.1s ease;
}

/* Grows the dot under the cursor (rather than just changing color, which
   would fight the tier color it's showing) and lifts it above its
   neighbors so an overlapping dot doesn't win the hover/click.

   Grown by its width and height, NOT by transform: scale(). µCSS draws the
   tooltip as ::before/::after on this same element, and a transform on an
   element scales its pseudo-elements too -- so a scale(1.7) here rendered the
   title card at 1.7x, which read as an enormous tooltip on hover and a
   normal-sized one whenever the tooltip appeared without hover (see the
   :focus rule below). Width and height leave the pseudo-elements alone, and
   because the dot is absolutely positioned and centered with a percentage
   translate, growing the box keeps its center on the same coordinate. It also
   grows the hit area, which a box-shadow ring would not. */
.taste-compass-scatter-dot:hover,
.taste-compass-scatter-dot:focus-visible {
	width: 0.95rem;
	height: 0.95rem;
	/* Above the cluster markers, not just above the other dots. A z-index of
	   its own makes this dot a stacking context, so the tooltip's z-index of 99
	   is resolved *inside* it -- at 1 the whole dot, tooltip included, painted
	   under any marker it overlapped. Costs a marker being covered by one dot
	   while the cursor is on it, which is cheaper than an unreadable title. */
	z-index: 3;
}

/* µCSS shows a tooltip on :focus as well as :hover, and a dot is a link that
   opens in a new tab -- so clicking one left it focused and its tooltip
   pinned open over the plot after the new tab was closed, with nothing
   hovering it. Keyboard focus still shows it; a mouse click no longer does.
   The animation has to be cancelled as well as the opacity, since µCSS's
   keyframes set opacity in their own forwards fill. */
.taste-compass-scatter-dot:focus:not(:focus-visible)::before,
.taste-compass-scatter-dot:focus:not(:focus-visible)::after {
	opacity: 0;
	animation: none;
}

/* A cluster centroid on the profile compass. Sits above the film dots (which
   are drawn first) so a marker is always readable, and is kept small because
   there are real dots underneath it to avoid burying. */
.taste-compass-cluster-marker {
	position: absolute;
	top: var(--tc-y);
	left: var(--tc-x);
	transform: translate(-50%, -50%);
	width: 1.35rem;
	height: 1.35rem;
	border-radius: 50%;
	display: grid;
	place-items: center;
	background: var(--tc-color);
	color: #fff;
	font-size: 0.7rem;
	font-weight: bold;
	line-height: 1;
	z-index: 2;
	/* Separates the marker from any dot it happens to overlap. */
	border: 2px solid var(--mu-card-background-color);
}

.taste-cluster-legend {
	margin: var(--mu-spacing) 0 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: calc(var(--mu-spacing) / 3);
}

.taste-cluster-legend li {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin: 0;
	color: var(--mu-muted-color);
	font-size: 0.8rem;
}

.taste-cluster-legend-badge {
	flex: 0 0 auto;
	width: 1.25rem;
	height: 1.25rem;
	border-radius: 50%;
	display: grid;
	place-items: center;
	background: var(--tc-color);
	color: #fff;
	font-size: 0.7rem;
	font-weight: bold;
	line-height: 1;
}

.taste-compass-empty {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	margin: 0;
	padding: 1rem;
	color: var(--mu-muted-color);
	font-size: 0.85rem;
	text-align: center;
}

/* Content meters */

.content-meters {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--mu-spacing);
}

.content-meter progress {
	margin-bottom: 0;
}

.content-meter-row {
	display: flex;
	justify-content: space-between;
	font-size: 0.85rem;
	margin-bottom: 0.25rem;
}

.content-meter-score {
	color: var(--mu-muted-color);
}

/* Grade badges */

.grade-badges {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
	gap: var(--mu-spacing);
}

.grade-badge {
	border-radius: var(--mu-border-radius);
	overflow: hidden;
	text-align: center;
}

.grade-badge-letter {
	background: var(--gb-color);
	color: #fff;
	font-size: 1.75rem;
	font-weight: bold;
	padding: 1rem 0;
}

.grade-badge-label {
	background: var(--mu-card-background-color);
	border: 1px solid var(--mu-muted-border-color);
	border-top: none;
	color: var(--mu-muted-color);
	font-size: 0.8rem;
	padding: 0.4rem 0;
}

.grade-badge-s { --gb-color: var(--pico-color-fuchsia-500); }
.grade-badge-a { --gb-color: var(--pico-color-green-500); }
.grade-badge-b { --gb-color: var(--pico-color-lime-500); }
.grade-badge-c { --gb-color: var(--pico-color-amber-500); }
.grade-badge-d { --gb-color: var(--pico-color-orange-500); }
.grade-badge-f { --gb-color: var(--pico-color-red-500); }
.grade-badge-u { --gb-color: var(--pico-color-grey-500); }

/* Review form: the three-step wizard (tier, content, short review).
   review.templ renders all three steps and switches them with x-show, so
   everything below styles chrome that is always in the DOM. */

/* Progress header. Three equal columns, each a button with µCSS's chrome
   stripped back to a label over the rule that carries the progress. */
.review-wizard-steps {
	display: flex;
	gap: 0.5rem;
	list-style: none;
	margin: 0 0 var(--mu-spacing);
	padding: 0;
}

.review-wizard-steps li {
	flex: 1;
	margin: 0;
	padding: 0;
}

.review-wizard-step {
	align-items: center;
	background: none;
	border: none;
	border-bottom: 3px solid var(--mu-muted-border-color);
	border-radius: 0;
	color: var(--mu-muted-color);
	display: flex;
	font-size: 0.85rem;
	gap: 0.5rem;
	justify-content: center;
	margin: 0;
	padding: 0.5rem 0.25rem;
	width: 100%;
}

/* µCSS fades a disabled button to half opacity. Here "not reachable yet" is
   already said by the muted colour, and fading it further makes the two steps
   ahead of a new user look broken rather than pending. */
.review-wizard-step:disabled {
	background: none;
	cursor: not-allowed;
	opacity: 1;
}

.review-wizard-step-num {
	align-items: center;
	background: var(--mu-muted-border-color);
	border-radius: 50%;
	color: var(--mu-color);
	display: inline-flex;
	flex-shrink: 0;
	font-size: 0.75rem;
	font-weight: bold;
	height: 1.5rem;
	justify-content: center;
	width: 1.5rem;
}

.review-wizard-step.is-done {
	color: var(--mu-color);
	border-bottom-color: var(--nf-brand);
}

.review-wizard-step.is-done .review-wizard-step-num {
	background: var(--nf-brand);
	color: var(--mu-primary-inverse);
}

.review-wizard-step.is-current {
	color: var(--mu-primary-hover);
	border-bottom-color: var(--mu-primary);
}

.review-wizard-step.is-current .review-wizard-step-num {
	background: var(--nf-brand);
	color: var(--mu-primary-inverse);
}

/* Below µCSS's .grid breakpoint the labels are what goes first: three circled
   numbers under a highlighted rule still say which step you are on. */
@media (max-width: 420px) {
	.review-wizard-step-label {
		display: none;
	}
}

/* Back on the left, forward on the right, in each step's card footer. */
.review-wizard-actions {
	display: flex;
	gap: 0.75rem;
	justify-content: space-between;
}

.review-wizard-actions button {
	margin: 0;
	width: auto;
}

/* The line under each step's heading saying what that step is for -- the
   labels the scrolling form carried inline ("Optional", "0 none - 5 ...")
   have room to be a sentence now that one step fills the card. */
.review-step-hint {
	color: var(--mu-muted-color);
	display: block;
	font-weight: normal;
	margin-top: 0.25rem;
}

/* Review form: tier and content-category option grids */

.review-option {
	align-items: center;
	border: 1px solid var(--mu-muted-border-color);
	border-radius: var(--mu-border-radius);
	cursor: pointer;
	display: flex;
	flex-direction: column;
	justify-content: center;
	padding: 0.75rem 0.5rem;
	transition: background-color .15s ease, color .15s ease;
}

.review-option h2 {
	margin: 0 0 0.25rem;
}

.review-option p {
	margin: 0;
}

.content-category-option {
	font-size: 1.5rem;
	font-weight: bold;
	height: 4rem;
	padding: 0.5rem;
}

/* Browse */

/* Sidebar (filters) + results grid. A single column until µCSS's own .grid
   breakpoint (768px, see mu.indigo.css) kicks in -- overriding its
   default equal-width columns with a ~20/80 split instead. */
.browse-layout {
	align-items: start;
}

@media (min-width: 768px) {
	.browse-layout {
		grid-template-columns: minmax(260px, 1fr) 3fr;
	}
}

.browse-filters {
	display: flex;
	flex-direction: column;
	gap: 1.1rem;
	padding: var(--mu-spacing);
	border: 1px solid var(--mu-muted-border-color);
	border-radius: var(--mu-border-radius);
	margin-bottom: var(--mu-spacing);
}

/* The filter controls themselves, split off from .browse-filters so the
   whole set can be collapsed on a phone without taking the search row --
   the title search and the toggle that opens this -- with it. Below the
   768px two-column breakpoint the panel is a disclosure: collapsed is the
   bare CSS default (no :class applied), so a page load
   renders it shut in the frames before Alpine boots and there's no flash of
   the full-height sidebar. From 768px up the toggle is gone and these are
   shown unconditionally, whatever `filtersOpen` happens to be -- so the
   desktop sidebar behaves exactly as it did before. */
.browse-filter-fields {
	display: flex;
	flex-direction: column;
	gap: 1.1rem;
}

/* Title search + the funnel that opens everything else. The search box is
   deliberately outside .browse-filter-fields (see browseFilters in
   browse.templ): it's the filter people reach for most, and a phone had to
   open the panel and find the field before a single character could be
   typed. align-items: stretch rather than center so the button takes the
   search box's height instead of needing its padding tuned to match it --
   the two are different form elements with different µCSS paddings, and any
   figure written here would be right only until one of them changed. */
.browse-search-row {
	display: flex;
	align-items: stretch;
	gap: 0.5rem;
}

.browse-search-row input[type="search"] {
	flex: 1 1 auto;
	/* Without this the input's intrinsic size floors the flex item and the
	   button is pushed out of the row on a narrow phone rather than the
	   input giving up the width. */
	min-width: 0;
}

/* The funnel button, phones only (see the 767.98px block below; from 768px
   up the panel is always open and the search box has the row to itself,
   which is how the sidebar looked when search was the panel's first field).
   width and margin-bottom are the same two µCSS overrides .browse-pager
   button makes -- a bare <button> is width: 100% in µCSS (a Pico v2 fork), which here
   would push the search box out of the row entirely. position is for the
   active dot below. */
.browse-filters-toggle {
	display: none;
	position: relative;
	flex: 0 0 auto;
	width: auto;
	margin-bottom: 0;
	padding: 0 0.9rem;
}

/* "Some filter is narrowing these results" -- the panel is shut by default,
   so without this there's nothing on screen saying the grid isn't the full
   catalogue. A dot rather than the "Active" text pill this was while the
   toggle was a full-width labelled button: an icon-only button has no text
   to sit beside. Same condition as the empty-state message
   (HasActiveFilters), so the two always agree about what counts as active,
   and browseFiltersToggleLabel puts the same fact on the button's
   aria-label, which is the only place a screen reader can read it. */
.browse-filters-active {
	position: absolute;
	top: 0.3rem;
	right: 0.3rem;
	width: 0.45rem;
	height: 0.45rem;
	border-radius: 50%;
	background: var(--mu-primary);
}

/* The panel's two Apply buttons (browseApplyButton in browse.templ). Phones
   only, for the same reason the toggle is: above the breakpoint the panel is
   always open beside the grid and there is nothing to dismiss. Full width is
   µCSS's own default for a <button>, and wanted here. */
.browse-apply-filters {
	display: none;
	margin-bottom: 0;
}

/* 767.98 rather than 767 to butt right up against the 768px layout query
   above, leaving no fractional width (zoom, some device pixel ratios) where
   neither matches and the fields would be collapsed with no toggle to open
   them. */
@media (max-width: 767.98px) {
	.browse-filters-toggle {
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}

	.browse-apply-filters {
		display: block;
	}

	/* Sets the expanded panel off from the search row above it, which is a
	   different kind of thing -- always visible, always editable -- rather
	   than the first of the fields the funnel just revealed. Carried as a
	   margin on the panel rather than under the search row so it exists only
	   while the panel does: collapsed, .browse-filter-fields is display: none
	   and so not a flex item at all, and its margin goes with it instead of
	   leaving dead space at the bottom of a shut filter card. */
	.browse-search-row + .browse-filter-fields {
		margin-top: 0.5rem;
	}

	.browse-filter-fields {
		display: none;
	}

	.browse-filter-fields-open {
		display: flex;
	}
}

.browse-filters input[type="search"] {
	margin-bottom: 0;
}

.browse-filters select {
	margin-bottom: 0;
}

.browse-filters fieldset {
	margin: 0;
}

.browse-filters legend {
	font-weight: bold;
	font-size: 0.8rem;
	color: var(--mu-muted-color);
	text-transform: uppercase;
	letter-spacing: 0.02em;
	margin-bottom: 0.5rem;
	padding: 0;
}

.browse-filters hr {
	margin: 0;
}

/* Release-year range: two selects joined into one µCSS [role=group]
   segmented control (see browseYearRangeFilter in browse.templ for why it's
   two selects and not a slider). µCSS gives [role=group] a bottom margin of
   its own, which double-spaces it against .browse-filter-fields' flex gap. */
.browse-year-range {
	margin-bottom: 0;
}

/* The dash between the two selects. µCSS flexes every direct child of a
   group to 1 1 auto, which would stretch this to a third of the control's
   width, so it opts out and takes only the space the character needs. The
   borders and background are the form element's own: a group's children keep
   their borders and butt up against each other, so without them this would
   read as a notch cut out of the middle of the control rather than as a seam
   between its two halves. Top/bottom only -- the neighbouring selects supply
   the verticals, same as they do for each other. */
.browse-year-range .browse-year-sep {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	padding: 0 0.4rem;
	border-top: var(--mu-border-width) solid var(--mu-form-element-border-color);
	border-bottom: var(--mu-border-width) solid var(--mu-form-element-border-color);
	background-color: var(--mu-form-element-background-color);
	color: var(--mu-muted-color);
}

.browse-filters .provider-list {
	max-height: 340px;
}

/* A labeled role="switch" checkbox (show-unrated, use-defaults): switch on
   the right, label text on the left, same row. */
.switch-option {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	margin-bottom: 0;
}

.switch-option input[type="checkbox"] {
	margin-bottom: 0;
	flex-shrink: 0;
}

/* The caps sliders/provider checkboxes while "use my profile defaults" is
   on -- dimmed and inert, but every field stays in the form's serialized
   values (see browse.templ's browseFilters comment on why nothing here
   uses the native disabled attribute). */
.filter-locked {
	opacity: 0.55;
	pointer-events: none;
}

.browse-toolbar {
	display: flex;
	justify-content: flex-end;
	align-items: center;
	gap: 1rem;
	margin-bottom: var(--mu-spacing);
}

.browse-count {
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

.browse-pager {
	display: flex;
	align-items: center;
	gap: 0.5rem;
}

.browse-pager button {
	width: auto;
	margin-bottom: 0;
	padding: 0.25rem 0.6rem;
}

/* Stands in for .movie-cards while a pager request is in flight (browse.templ's
   browseLoading). The spinner is µCSS's own, drawn off aria-busy as a ::before
   and sized in em, so font-size is what scales it up from the 1em it would be
   inside a line of text. min-height is about a screen's worth of grid: the
   cards are hidden the instant an arrow is clicked, and without a floor here
   the page would collapse to just the toolbar and take the scroll position
   with it, then jump back when the new page landed. */
.browse-loading {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 60vh;
	font-size: clamp(4rem, 14vw, 8rem);
}

.browse-page-indicator {
	color: var(--mu-muted-color);
	font-size: 0.85rem;
	white-space: nowrap;
}

/* Bottom copy of .browse-toolbar (see BrowseResults in browse.templ) -- phones
   only. Below 768px a page of cards can run well past one screen, so without
   this the only way back to the pager is scrolling to the top of the grid;
   above 768px the grid rarely runs that long and a second toolbar under
   buttons already in view would just be noise. Only display and margin are
   overridden here -- flex/gap/justify-content stay on .browse-toolbar so both
   copies share one definition of "what a toolbar looks like." margin flips
   from margin-bottom (spacing the top toolbar off the cards below it) to
   margin-top (spacing this one off the cards above it -- nothing follows it
   but the grid's own end, hence margin-bottom: 0). Same 767.98px cutoff as
   .browse-filter-fields above; kept as its own media query rather than folded
   into that block since nothing else there depends on it. */
.browse-toolbar-bottom {
	display: none;
	margin-top: var(--mu-spacing);
	margin-bottom: 0;
}

@media (max-width: 767.98px) {
	.browse-toolbar-bottom {
		display: flex;
	}
}

.my-list-tabs {
	margin-bottom: var(--mu-spacing);
}

.movie-cards {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
	gap: var(--mu-spacing);
}

.movie-card {
	display: flex;
	flex-direction: column;
	padding: 0;
	overflow: hidden;
	/* The whole <article> is the link to the film (see movieCard's hx-get), but
	   nothing in it is an <a>, so the pointer is the only thing telling a
	   visitor the card is clickable at all. */
	cursor: pointer;
	/* position for the hover's z-index below; the card has no offset children
	   of its own to disturb (the chips are positioned against
	   .movie-card-poster). */
	position: relative;
	transition: transform 0.15s ease, box-shadow 0.15s ease;
}

/* Hover: the theme's primary colour as a ring, plus a scale shift small enough to read
   as the card lifting rather than as the grid moving.

   A ring drawn with box-shadow rather than a real border, because a border
   would have to exist at rest (transparent) to avoid reflowing the card on
   hover, and would then sit inside overflow: hidden and inset the poster by a
   pixel on every card whether hovered or not. µCSS's own card shadow is
   restated first so adding the ring doesn't drop it.

   z-index because grid items paint in DOM order: without it the scaled-up card
   slides under whichever cards come after it in the row. */
.movie-card:hover {
	transform: scale(1.015);
	box-shadow: var(--mu-card-box-shadow), 0 0 0 1px var(--mu-primary-border);
	z-index: 1;
}

.movie-card-poster {
	position: relative;
}

.movie-card-poster img,
.movie-card-noposter {
	display: block;
	width: 100%;
	aspect-ratio: 2 / 3;
}

.movie-card-poster img {
	object-fit: cover;
}

/* Corner review-score chip, overlaid on a card's poster, top-right. Color
   comes from the shared .grade-badge-* rules above (grey for "U"). */
.review-chip {
	position: absolute;
	top: 0.4rem;
	right: 0.4rem;
	display: grid;
	place-items: center;
	width: 1.6rem;
	height: 1.6rem;
	border-radius: var(--mu-border-radius);
	background: var(--gb-color);
	color: #fff;
	font-size: 0.85rem;
	font-weight: bold;
	line-height: 1;
}

/* Corner taste-match chip, overlaid on a card's poster, top-left -- opposite
   the community grade's top-right. Colour is not set here: the chip carries a
   .pico-background-* utility from TasteMatchColorClass, running red at 0%
   through green at 100%. Auto width with padding because "100%" needs more
   room than a letter, but the same height as .review-chip so the two corners
   sit level. */
.taste-match-chip {
	position: absolute;
	top: 0.4rem;
	left: 0.4rem;
	display: grid;
	place-items: center;
	height: 1.6rem;
	padding: 0 0.4rem;
	border-radius: var(--mu-border-radius);
	font-size: 0.8rem;
	font-weight: bold;
	line-height: 1;
}

.movie-card-noposter {
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--mu-card-sectioning-background-color);
	color: var(--mu-muted-color);
	font-size: 2rem;
}

/* Overlaid on the poster's bottom edge rather than sat below it, so the
   title is always readable against the art without shrinking the poster. */
.movie-card-title {
	position: absolute;
	right: 0;
	bottom: 0;
	left: 0;
	margin: 0;
	padding: 1.5rem 0.75rem 0.6rem;
	background: linear-gradient(to top, rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0));
	color: #fff;
	font-size: 1rem;
	line-height: 1.25;
}

/* Three stacked lines -- "2024 · 118 min", the genre list, then Browse's
   inline Review/Not Interested pair -- rather than the year-left/genres-right
   row this was. A column because the actions are a full-width block of their
   own: squeezed into the old space-between row they'd have shared a baseline
   with text, on a card only 8rem wide. */
.movie-card-footer {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	padding: 0.6rem var(--mu-block-spacing-horizontal);
	color: var(--mu-muted-color);
	font-size: 0.8rem;
}

/* The genres now own a full line, so they read left-to-right like the line
   above rather than being pushed right against the year. Still one line and
   still ellipsised: cards sit in grid rows, so letting a six-genre film wrap
   would make its whole row as tall as the tallest card in it. */
.movie-card-genres {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* Browse's inline card actions (movieCardActions in browse.templ). Wraps
   rather than stacking outright: the grid's cards are minmax(8rem, 1fr), so
   the pair fits on one line on a roomy card and falls to two on a narrow one,
   which keeps the grid's density out of it either way. */
.movie-card-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
}

/* µCSS sizes buttons to the full container width with a bottom margin -- the
   same three overrides as .browse-pager button, one grid over. flex: 1 1 auto
   is what lets the pair share a line evenly and then wrap; nowrap keeps "Not
   Interested" from breaking across two lines inside its own button. */
.movie-card-actions button {
	flex: 1 1 auto;
	width: auto;
	margin-bottom: 0;
	padding: 0.25rem 0.5rem;
	font-size: 0.75rem;
	white-space: nowrap;
}

/* "Not Interested" becomes a spinner while its POST is in flight (the
   :aria-busy binding in movieCardActions). µCSS draws that spinner as a
   ::before and, on a button that isn't :empty, gives it a trailing margin and
   lets it share the line with the label -- which would widen the button
   mid-request and shove "Review" along the row it shares.

   So: hide the label with visibility rather than display, which keeps the
   button exactly the width it already was, and lift µCSS's ::before out of the
   flow to sit centred over the gap. inset + margin: auto is what centres it,
   and also overrides the margin-inline-end µCSS gave it -- the two selectors
   tie on specificity, and this file loads after µCSS, so the ancestor class is
   here to win it outright rather than by load order. */
.movie-card-actions button[aria-busy="true"] {
	position: relative;
}

.movie-card-actions button[aria-busy="true"] .movie-card-action-label {
	visibility: hidden;
}

.movie-card .movie-card-actions button[aria-busy="true"]::before {
	position: absolute;
	inset: 0;
	margin: auto;
}

/* Movie detail */

/* Back button + MovieListButtons (Review/Watch List/Not Interested), above
   the poster on the Movie and Browse-detail pages. Unwrapped, `space-between` +
   `align-items: center` vertically centers the buttons' whole block against
   Back's single line, so on a narrow screen -- once the buttons themselves
   wrap to two lines -- their first line rides up and overlaps Back. A
   phone-width stack sidesteps that: each side gets the full row to itself, so
   there's nothing left to center against.

   A class rather than the row's id, because the Tonight result cards render
   the same row three times on one page, each under its own id
   (MovieListButtonsIn). */
.movie-detail-actions {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 0.5rem;
	margin-bottom: 1rem;
}

.movie-list-buttons {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: 0.5rem;
}

@media (max-width: 576px) {
	.movie-detail-actions {
		flex-direction: column;
		align-items: stretch;
	}

	.movie-list-buttons {
		justify-content: flex-start;
	}
}

.movie-detail {
	display: grid;
	grid-template-columns: minmax(0, 16rem) 2fr 1fr;
	gap: calc(var(--mu-spacing) * 1.5);
	margin-bottom: var(--mu-spacing);
}

/* Below the nav's own collapse point (see #app-nav's 767.98px breakpoint),
   the info and side columns have no room left: their intrinsic content
   (the compass grid's "Dark"/"Light" axis labels, the title) is wider than
   an even split of what's left after the poster, so the row overflows the
   viewport sideways instead of wrapping. Stacking to one column removes the
   side-by-side squeeze entirely -- poster, then title/badges/overview, then
   the compass, each given the full width, in the same order they already
   sit in the markup. */
@media (max-width: 767.98px) {
	.movie-detail {
		grid-template-columns: 1fr;
	}
}

.movie-detail-poster img,
.movie-detail-poster .movie-card-noposter {
	width: 100%;
	border-radius: var(--mu-border-radius);
	aspect-ratio: 2 / 3;
}

.movie-detail-poster img {
	display: block;
	object-fit: cover;
}

.movie-detail-meta,
.movie-detail-genres {
	color: var(--mu-muted-color);
}

.movie-detail-side {
	display: flex;
	flex-direction: column;
	gap: var(--mu-spacing);
}

/* "View on IMDb", under the overview. µCSS gives <small> no top margin, so the
   link would otherwise sit tight against the overview paragraph. */
.imdb-link {
	display: block;
	margin-top: var(--mu-spacing);
}

/* The favicon comes straight off imdb.com at whatever size they ship it, so
   it's sized against the link's own font rather than left at natural size --
   the JustWatch mark in the streaming card is unsized and renders at 16px
   regardless of the text around it. */
.imdb-link img {
	height: 1em;
	width: auto;
	vertical-align: -0.15em;
}

/* Sizes the shared .grade-badge down from the full-width home-page key grid
   to a small card that fits the detail page's side column. */
.movie-detail-badge {
	width: 6rem;
}

/* The community grade and the viewer's taste match, side by side. align-items
   start keeps them top-aligned when the grade's caption wraps to two lines and
   the match's doesn't. */
.movie-detail-badges {
	display: flex;
	align-items: start;
	gap: 0.5rem;
	margin-bottom: var(--mu-spacing);
}

/* Mirrors .grade-badge's proportions so the two sit as a matched pair. Colour
   is not set here: the value block carries a .pico-background-* utility from
   TasteMatchColorClass, which sets its own background and a readable
   foreground, so the badge runs red at 0% through green at 100%. */
.taste-match-badge {
	width: 6rem;
	border-radius: var(--mu-border-radius);
	overflow: hidden;
	text-align: center;
}

.taste-match-badge-value {
	font-size: 1.75rem;
	font-weight: bold;
	padding: 1rem 0;
}

.taste-match-badge-label {
	background: var(--mu-card-background-color);
	border: 1px solid var(--mu-muted-border-color);
	border-top: none;
	color: var(--mu-muted-color);
	font-size: 0.8rem;
	padding: 0.4rem 0;
}

/* Content dimensions card */

.content-dimensions-header {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--mu-spacing);
	margin-bottom: var(--mu-spacing);
}

.content-dimensions-header h4 {
	margin-bottom: 0;
}

.content-dimensions-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
	gap: calc(var(--mu-spacing) * 1.5);
}

.content-meter-cap-note {
	display: block;
	margin-top: 0.25rem;
	color: var(--mu-muted-color);
}

/* The per-category rating count sits on its own line under the label rather
   than beside it: the grid's columns go down to 9rem, which "Violence & gore"
   already fills, so an inline count would wrap at a different point in each
   column and knock the three bars out of line with one another. */
.content-meter-count {
	display: block;
	color: var(--mu-muted-color);
}

.content-meter-cap-note-over {
	color: var(--pico-color-red-500);
}

.streaming-card h4 {
	margin-bottom: var(--mu-spacing);
}

/* Taste profile card (tasteProfileCard in movie.templ). A two-column <dl>
   so the labels line up down one edge and the values wrap in their own
   column -- µCSS styles neither dt nor dd, so without the grid a value's
   second line would wrap back under its label. On a phone the label sits
   above its value instead, since a 7rem column would leave the values a
   sliver. */
.taste-profile-appeal {
	font-size: 1.05rem;
}

.taste-profile-grid {
	display: grid;
	grid-template-columns: 7rem 1fr;
	gap: 0.5rem 1rem;
	margin-bottom: var(--mu-spacing);
}

.taste-profile-grid dt {
	color: var(--mu-muted-color);
	font-size: 0.8rem;
	text-transform: uppercase;
	letter-spacing: 0.03em;
	padding-top: 0.15rem;
}

.taste-profile-grid dd {
	margin: 0;
}

@media (max-width: 576px) {
	.taste-profile-grid {
		grid-template-columns: 1fr;
		gap: 0.15rem;
	}

	.taste-profile-grid dd {
		margin-bottom: 0.5rem;
	}
}

/* The logo in the public header (publicHeader in public_header.templ) is a
   link home, but it should still read as the logo rather than as link text. */
.public-header-logo {
	text-decoration: none;
}

.streaming-groups {
	align-items: start;
}

.streaming-group h6 {
	color: var(--mu-muted-color);
	font-size: 0.8rem;
	text-transform: uppercase;
	letter-spacing: 0.03em;
	margin-bottom: 0.5rem;
}

.streaming-provider-list {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

.streaming-provider {
	display: flex;
	align-items: center;
	gap: 0.75rem;
}

.streaming-provider-logo {
	width: 1.75rem;
	height: 1.75rem;
	border-radius: 6px;
	object-fit: cover;
	flex: none;
}

.streaming-provider-logo-fallback {
	display: grid;
	place-items: center;
	background: var(--mu-card-sectioning-background-color);
	color: var(--mu-muted-color);
}

/* Landing page (home.templ, static/js/landing.js)

   One argument top to bottom -- you are losing time to scrolling, this is what
   fixes it, here is proof, here is the button -- with a clock running through
   it. The clock itself is script-driven (the hero eyebrow, the fixed pill and
   the closing band all read the same value); everything here only styles it.

   Bands are stacked inside µCSS's container rather than bled to the viewport
   edge, so alternating bands carry the same inline padding as plain ones and
   the text of every band lines up. Every run of prose is held to a measure. */

.landing-eyebrow {
	margin: 0 0 0.6rem;
	color: var(--mu-primary);
	font-size: 0.78rem;
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
}

/* tabular-nums so "8:19 PM" -> "8:20 PM" does not shift the text beside it. */
.landing-eyebrow-clock {
	color: var(--mu-muted-color);
	font-variant-numeric: tabular-nums;
}

.landing-hero {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	margin-bottom: calc(var(--mu-spacing) * 0.75);
	padding: calc(var(--mu-spacing) * 3) var(--mu-spacing) calc(var(--mu-spacing) * 3.5);
	border-radius: var(--mu-border-radius);
}

/* The browse grid, faded into the page. z-index -1 inside the hero's own
   stacking context (isolation: isolate) keeps it behind the text without
   leaving the hero. The mask fades it out toward the bottom so it never meets
   the next band as a hard edge, and the blur keeps it from competing with the
   headline -- it is there for texture, not to be read. */
.landing-hero-bg {
	position: absolute;
	inset: 0;
	z-index: -1;
	background-position: top center;
	background-size: cover;
	opacity: 0.3;
	filter: blur(3px);
	-webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.55) 60%, transparent 100%);
	mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.55) 60%, transparent 100%);
}

.landing-hero-inner {
	max-width: 46rem;
}

.landing-hero h1 {
	margin: 0 0 var(--mu-spacing);
	font-size: clamp(2.3rem, 7vw, 4.25rem);
	letter-spacing: -0.02em;
	line-height: 1.05;
}

.landing-accent {
	display: block;
	color: var(--mu-primary);
}

.landing-lede {
	max-width: 36rem;
	margin: 0 0 calc(var(--mu-spacing) * 1.5);
	font-size: 1.1rem;
	color: var(--mu-muted-color);
}

/* The page's only button, rendered once in the hero and once at the close. */
.landing-cta {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.6rem;
}

/* Start Now, and in the hero the Browse films link beside it. Wraps rather
   than shrinking, so on a phone the second button drops under the first. */
.landing-cta-buttons {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.75rem;
}

/* µCSS sizes a <button> to its container, which here is the whole page. The
   link-as-button is sized to match so the pair reads as one row. */
.landing-cta button,
.landing-cta a[role="button"] {
	width: auto;
	margin-bottom: 0;
	padding-inline: 2.25rem;
	font-size: 1.05rem;
}

.landing-cta small {
	max-width: 30rem;
	color: var(--mu-muted-color);
}

/* The hero and closing band left-align their button, where /films centres its
   own. */
.landing-hero .landing-cta,
.landing-final .landing-cta {
	align-items: flex-start;
}

.landing-hero .landing-cta-buttons,
.landing-final .landing-cta-buttons {
	justify-content: flex-start;
}

.landing-band {
	margin-bottom: calc(var(--mu-spacing) * 0.75);
	padding: calc(var(--mu-spacing) * 3) var(--mu-spacing);
	border-radius: var(--mu-border-radius);
}

.landing-band-alt {
	background: var(--mu-card-background-color);
}

.landing-band h2,
.landing-final h2 {
	max-width: 20ch;
	margin: 0 0 0.6rem;
	font-size: clamp(1.8rem, 4.5vw, 2.75rem);
	letter-spacing: -0.01em;
	line-height: 1.1;
}

.landing-section-lede {
	max-width: 42rem;
	margin-bottom: calc(var(--mu-spacing) * 1.5);
	color: var(--mu-muted-color);
}

/* The problem and the deal share a layout: the claim on the left, the detail
   on the right, stacked on a phone. */
.landing-problem {
	display: grid;
	grid-template-columns: 1fr;
	gap: calc(var(--mu-spacing) * 1.5);
}

@media (min-width: 768px) {
	.landing-problem {
		grid-template-columns: 1fr 1fr;
		align-items: start;
	}
}

.landing-rows {
	margin: 0;
}

.landing-rows > div {
	display: grid;
	grid-template-columns: 9rem 1fr;
	gap: 1rem;
	padding-block: 1.1rem;
	border-bottom: 1px solid var(--mu-muted-border-color);
}

.landing-rows > div:first-child {
	padding-top: 0;
}

.landing-rows > div:last-child {
	border-bottom: 0;
}

.landing-rows dt {
	font-weight: 700;
}

.landing-rows dd {
	margin: 0;
	color: var(--mu-muted-color);
}

.landing-price p {
	margin: 0;
	color: var(--mu-muted-color);
}

.landing-price + .landing-price {
	margin-top: calc(var(--mu-spacing) * 1.5);
}

.landing-price .landing-price-line {
	margin-bottom: 0.4rem;
	color: var(--mu-muted-color);
}

.landing-price-line strong {
	margin-right: 0.35rem;
	color: var(--mu-color);
	font-size: 2.25rem;
	line-height: 1.1;
}

/* A product screenshot or the browse recording, framed. */
.landing-shot {
	margin: calc(var(--mu-spacing) * 1.5) 0 0;
	overflow: hidden;
	border: 1px solid var(--mu-muted-border-color);
	border-radius: 0.9rem;
	box-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.85);
}

.landing-shot img,
.landing-shot video {
	display: block;
	width: 100%;
	height: auto;
}

/* The toy. Nothing here is real data -- see landing.js. */

.landing-try {
	min-height: 21rem;
}

.landing-try-note {
	color: var(--mu-muted-color);
}

.try-card {
	display: grid;
	grid-template-columns: 11rem 1fr;
	gap: calc(var(--mu-spacing) * 1.5);
	align-items: center;
	max-width: 42rem;
}

@media (max-width: 575px) {
	.try-card {
		grid-template-columns: 1fr;
		justify-items: start;
	}

	.try-card .try-poster {
		width: 9rem;
	}
}

.try-poster {
	display: block;
	width: 100%;
	aspect-ratio: 2 / 3;
	border-radius: 0.6rem;
	background: var(--mu-card-sectioning-background-color);
	box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.9);
	object-fit: cover;
}

/* Stands in for a poster that failed to load, so a bad path costs a plainer
   card and not a hole in the layout. */
.try-poster-fallback {
	display: grid;
	place-items: center;
	padding: 0.75rem;
	background: linear-gradient(160deg, #2a2f45, #14161f);
	color: var(--mu-color);
	font-weight: 700;
	text-align: center;
}

.try-count {
	margin: 0;
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

.try-title {
	margin: 0.2rem 0 0.15rem;
	font-size: 1.7rem;
}

.try-tags {
	margin: 0 0 1rem;
	color: var(--mu-muted-color);
	font-size: 0.9rem;
}

.try-tiers {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
}

/* --gb-color comes from the shared .grade-badge-* rules, so the toy's letters
   are the app's letters. */
.try-tier {
	width: 3.2rem;
	height: 3.2rem;
	margin: 0;
	padding: 0;
	border: 0;
	background: var(--gb-color);
	color: #fff;
	font-size: 1.2rem;
	font-weight: 800;
	transition: transform 0.12s ease;
}

.try-tier:hover,
.try-tier:focus-visible {
	background: var(--gb-color);
	transform: translateY(-2px) scale(1.06);
}

.try-scale {
	display: flex;
	justify-content: space-between;
	max-width: 20.8rem;
	margin-top: 0.5rem;
	color: var(--mu-muted-color);
	font-size: 0.75rem;
}

.try-result h3 {
	margin: 0 0 0.3rem;
	font-size: 1.5rem;
}

.try-result > p {
	max-width: 36rem;
	color: var(--mu-muted-color);
}

.try-picks {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 0.75rem;
	max-width: 32rem;
	margin: calc(var(--mu-spacing) * 1.25) 0;
}

.try-pick {
	position: relative;
}

.try-pick .try-poster {
	box-shadow: none;
}

.try-match {
	position: absolute;
	top: 0.4rem;
	left: 0.4rem;
	padding: 0.1rem 0.45rem;
	border-radius: 0.3rem;
	background: var(--pico-color-green-600);
	color: #fff;
	font-size: 0.8rem;
	font-weight: 700;
}

.try-pick-title {
	margin: 0.4rem 0 0;
	font-size: 0.85rem;
	line-height: 1.25;
}

.try-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
}

.try-actions a[role="button"],
.try-actions button {
	width: auto;
	margin: 0;
}

/* The closing band: the brand-coloured block the page has been walking toward.
   It inverts the page (white type on the brand fill), so every colour inside
   it is set here rather than inherited -- the muted greys and primary-coloured
   links are unreadable on the brand colour. The fill is --nf-brand, not
   --mu-primary: primary lightens in dark mode, where white type on it falls
   to 3.7:1, while the fixed fill holds 5.2:1 in both. */
.landing-final {
	margin-bottom: calc(var(--mu-spacing) * 2);
	padding: calc(var(--mu-spacing) * 3) var(--mu-spacing);
	border-radius: var(--mu-border-radius);
	background: var(--nf-brand);
	color: #fff;
}

.landing-final .landing-eyebrow {
	color: rgba(255, 255, 255, 0.92);
	font-variant-numeric: tabular-nums;
}

.landing-final h2 {
	color: #fff;
}

.landing-final .landing-cta small {
	color: rgba(255, 255, 255, 0.92);
}

.landing-final .landing-cta button {
	border-color: #fff;
	background: #fff;
	color: var(--nf-brand);
}

.landing-final h2 {
	margin-bottom: 1.25rem;
}

/* .landing-colophon centres itself for /films; this band is left-aligned. */
.landing-final .landing-colophon {
	max-width: none;
	margin: calc(var(--mu-spacing) * 1.5) 0 0;
	color: rgba(255, 255, 255, 0.92);
}

.landing-final a {
	color: inherit;
}

/* The fixed clock. Hidden until the hero has scrolled away and again once the
   closing band is on screen (each shows the time itself); landing.js toggles
   .is-visible. Bottom-right rather than top so it never sits over the header's
   links. pointer-events stays off while hidden so an invisible pill cannot
   swallow a tap. */
.landing-clock {
	position: fixed;
	right: 1rem;
	bottom: 1rem;
	z-index: 20;
	display: flex;
	align-items: center;
	gap: 0.6rem;
	padding: 0.55rem 1rem;
	border: 1px solid var(--mu-primary);
	border-radius: 999px;
	background: color-mix(in srgb, var(--mu-background-color) 92%, transparent);
	-webkit-backdrop-filter: blur(8px);
	backdrop-filter: blur(8px);
	box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.8);
	color: var(--mu-primary-hover);
	font-variant-numeric: tabular-nums;
	opacity: 0;
	pointer-events: none;
	transform: translateY(0.75rem);
	transition: opacity 0.25s ease, transform 0.25s ease;
}

.landing-clock.is-visible {
	opacity: 1;
	transform: none;
}

.landing-clock-time {
	font-size: 1.05rem;
	font-weight: 800;
}

.landing-clock-elapsed {
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

/* Closing band: the second copy of the button, plus who built the thing. */

.landing-close {
	padding-block: calc(var(--mu-spacing) * 2.5);
	border-top: 1px solid var(--mu-muted-border-color);
	text-align: center;
}

.landing-close h2 {
	max-width: 22ch;
	margin: 0 auto var(--mu-spacing);
	font-size: 1.5rem;
}

.landing-colophon {
	max-width: 36rem;
	margin: calc(var(--mu-spacing) * 1.5) auto 0;
	color: var(--mu-muted-color);
}

/* Footer */

.footer-row {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: var(--mu-spacing);
	flex-wrap: wrap;
}

.footer-row h2 {
	font-size: 1.1rem;
	margin-bottom: 0.25rem;
}

.footer-row p {
	margin-bottom: 0;
}

.footer-row a[role="button"] {
	width: auto;
	white-space: nowrap;
}

/* The copyright line, under the footer's link row. Quieter than the links
   above it -- it's a notice, not navigation -- and it has to put back a top
   margin of its own, since .footer-row p zeroes the paragraph spacing that
   would otherwise separate the two lines. */
.footer-copyright {
	margin-top: 0.5rem;
	color: var(--mu-muted-color);
}

/* Profile */

.profile-header {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: var(--mu-spacing);
}

.profile-identity {
	display: flex;
	align-items: center;
	gap: var(--mu-spacing);
}

.profile-avatar {
	display: grid;
	place-items: center;
	flex: none;
	width: 3.25rem;
	height: 3.25rem;
	border-radius: 50%;
	background: var(--nf-brand);
	color: var(--mu-primary-inverse);
	font-weight: bold;
	font-size: 1.1rem;
}

.profile-name {
	margin-bottom: 0.15rem;
}

.profile-meta {
	margin-bottom: 0;
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

.profile-stats {
	display: flex;
	gap: calc(var(--mu-spacing) * 1.5);
}

.profile-stat {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
}

.profile-stat-value {
	font-size: 1.75rem;
	font-weight: bold;
	line-height: 1.2;
}

.profile-stat-label {
	color: var(--mu-muted-color);
	font-size: 0.75rem;
	white-space: nowrap;
}

.profile-taste-grid {
	align-items: stretch;
}

.rated-films-column {
	display: flex;
	flex-direction: column;
	gap: var(--mu-spacing);
}

.rated-films-card {
	margin-bottom: 0;
}

.rated-films-header {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	margin-bottom: 0.6rem;
}

.rated-films-header h4 {
	margin-bottom: 0;
}

.rated-films-sub {
	color: var(--mu-muted-color);
	font-size: 0.8rem;
	white-space: nowrap;
}

.rated-films-list {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	margin: 0;
	padding: 0;
	list-style: none;
}

.rated-films-list li {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	font-size: 0.9rem;
}

.grade-chip {
	display: grid;
	place-items: center;
	flex: none;
	width: 1.25rem;
	height: 1.25rem;
	border-radius: 4px;
	background: var(--gb-color);
	color: #fff;
	font-size: 0.7rem;
	font-weight: bold;
}

.profile-settings {
	margin-top: calc(var(--mu-spacing) * 1.5);
}

.profile-settings-grid {
	align-items: start;
}

.profile-settings-hint {
	margin-bottom: var(--mu-spacing);
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

.content-limit-row {
	margin-bottom: var(--mu-spacing);
}

/* µCSS only colors the range thumb with the brand while it's being dragged
   (--mu-range-thumb-active-color); at rest it falls back to
   --mu-range-thumb-color, which defaults to a gray secondary color and
   reads as disabled. Force the brand at rest too, and repoint the active color
   to a distinct shade so dragging still gives visible feedback. */
.content-limit-row input[type="range"] {
	margin-bottom: 0;
	--mu-range-thumb-color: var(--mu-primary-background);
	--mu-range-thumb-active-color: var(--mu-primary-hover-background);
}

.profile-settings-card-header {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	margin-bottom: 0.6rem;
}

.profile-settings-card-header h4 {
	margin-bottom: 0;
}

.settings-saved {
	color: var(--pico-color-green-500);
	white-space: nowrap;
}

.settings-saved-error {
	color: var(--pico-color-red-500);
}

.provider-list {
	max-height: 500px;
	overflow-y: auto;
}

.provider-option {
	display: flex;
	align-items: center;
	gap: 0.4rem;
}

.provider-logo {
	width: 1.25rem;
	height: 1.25rem;
	border-radius: 4px;
	object-fit: cover;
}
/* Legal pages (Terms of Service, Privacy Policy) */

/* A narrower measure than the container gives: these pages are read line by
   line rather than scanned, and full-width lines are hard to track back to. */
.legal {
	max-width: 46rem;
}

.legal section {
	margin-bottom: calc(var(--mu-spacing) * 1.5);
}

.legal h2 {
	font-size: 1.3rem;
	margin-bottom: var(--mu-spacing);
	padding-bottom: 0.4rem;
	border-bottom: 1px solid var(--mu-muted-border-color);
}

.legal p {
	margin-bottom: 0.75rem;
}

.legal address {
	font-style: normal;
	margin: 0.75rem 0;
	padding-left: var(--mu-spacing);
	border-left: 2px solid var(--mu-muted-border-color);
	line-height: 1.6;
}

.legal table {
	margin-bottom: 0.75rem;
}

/* Two-column key/value tables (contact points, providers, retention): the
   label column is the narrower half and reads as a label, not as data. */
.legal-kv td:first-child {
	width: 34%;
	color: var(--mu-muted-color);
}

/* Placeholders still to be filled in before launch. Deliberately quiet --
   there are a lot of them, and highlighter yellow down the whole page would
   be worse than the text it marks. No horizontal padding: most placeholders
   are followed immediately by a comma or full stop, and padding reads as a
   space before the punctuation. */
.legal mark {
	padding: 0;
	background: none;
	color: var(--mu-muted-color);
	border-bottom: 1px dashed var(--mu-muted-color);
	font-size: 0.95em;
}

/* Contents */

/* µCSS lays a <nav> out as a flex row and pads each item for a nav bar; this
   one is a contents list, so it opts back out of both. */
.legal-toc ol {
	display: block;
	list-style: none;
	padding-left: 0;
	margin-bottom: 0;
	columns: 2;
	column-gap: calc(var(--mu-spacing) * 2);
}

.legal-toc li {
	display: list-item;
	padding: 0;
	margin: 0 0 0.4rem 0;
	break-inside: avoid;
}

.legal-toc a {
	text-decoration: none;
}

.legal-toc a:hover {
	text-decoration: underline;
}

.legal-notice {
	border-left: 4px solid var(--mu-primary);
}

.legal-notice p:last-child {
	margin-bottom: 0;
}

/* Clauses */

/* The clause number hangs in its own column so every clause body shares one
   left edge down the page. */
.clause {
	display: grid;
	grid-template-columns: 3.5rem 1fr;
	margin-bottom: var(--mu-spacing);
}

.clause-num {
	color: var(--mu-muted-color);
	font-variant-numeric: tabular-nums;
}

.clause > div > :last-child {
	margin-bottom: 0;
}

/* (a), (b), (c) sub-clauses. */
.sub-clauses {
	list-style-type: lower-alpha;
	margin-bottom: 0;
}

.sub-clauses li {
	margin-bottom: 0.3rem;
}

@media (max-width: 576px) {
	/* On a phone the hanging number costs too much of the line, so it sits
	   above its clause instead of beside it. */
	.clause {
		grid-template-columns: 1fr;
		gap: 0.15rem;
	}

	.legal-toc ol {
		columns: 1;
	}
}

/* About page */

/* Slightly wider than the legal pages, for the stat strip and the stack
   table, but the prose still wants a bounded measure. */
.about {
	max-width: 52rem;
}

.about section {
	margin-bottom: calc(var(--mu-spacing) * 2);
}

.about h2 {
	font-size: 1.4rem;
	margin-bottom: var(--mu-spacing);
	padding-bottom: 0.4rem;
	border-bottom: 1px solid var(--mu-muted-border-color);
}

.about p {
	margin-bottom: 0.9rem;
}

.about-lede {
	font-size: 1.05rem;
	color: var(--mu-color);
}

.about table {
	margin-bottom: 0.9rem;
	font-size: 0.92rem;
}

/* Headline numbers */

.about-stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
	gap: 0.75rem;
	margin-bottom: calc(var(--mu-spacing) * 1.25);
}

.about-stat {
	padding: 0.75rem 0.9rem;
	border: 1px solid var(--mu-muted-border-color);
	border-radius: var(--mu-border-radius);
	background: var(--mu-card-background-color);
}

.about-stat-value {
	display: block;
	font-size: 1.45rem;
	font-weight: 700;
	line-height: 1.2;
	font-variant-numeric: tabular-nums;
	color: var(--mu-primary);
}

.about-stat-label {
	display: block;
	margin-top: 0.15rem;
	font-size: 0.8rem;
	color: var(--mu-muted-color);
}

/* The provenance line under the figure strip. Deliberately quiet -- it is
   there so a reader who doubts the numbers can see where they came from, not
   to be read on the way past. Sized below the stat labels for that reason. */

.about-stats-note {
	margin-top: calc(var(--mu-spacing) * -0.75);
	margin-bottom: calc(var(--mu-spacing) * 1.25);
	font-size: 0.75rem;
	line-height: 1.5;
	color: var(--mu-muted-color);
}

/* "How it works": three short labelled points. */

.about-points dt {
	margin-top: var(--mu-spacing);
	font-weight: 600;
	color: var(--mu-primary-hover);
}

.about-points dd {
	margin-left: 0;
	padding-left: var(--mu-spacing);
	border-left: 2px solid var(--mu-muted-border-color);
}

/* Founding-member/premium CTA banner (components.UpgradeBanner), shown on
   private pages -- Profile, Tonight -- to a viewer who is neither yet. */

.founding-cta-banner {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: var(--mu-spacing);
	margin: var(--mu-spacing) 0;
	border-left: 3px solid var(--mu-primary);
}

.founding-cta-banner p {
	margin: 0.15rem 0 0;
	color: var(--mu-muted-color);
	font-size: 0.9rem;
}

.founding-cta-banner > a[role="button"] {
	width: auto;
	white-space: nowrap;
}

/* Founding-member offer page */

.founding-offer-card {
	max-width: 34rem;
	margin: 0 auto;
}

.founding-offer-price {
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
	flex-wrap: wrap;
}

.founding-offer-price-amount {
	font-size: 2.5rem;
	font-weight: 700;
	line-height: 1;
	color: var(--mu-primary);
}

.founding-offer-price-meta {
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

.founding-offer-slots-row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	margin-bottom: 0.4rem;
}

.founding-offer-slots progress {
	margin-bottom: 0.6rem;
}

.founding-offer-slots-left {
	color: var(--mu-muted-color);
	font-size: 0.9rem;
}

.founding-offer-slots-note {
	margin-bottom: 0;
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

.founding-offer-perks {
	display: flex;
	flex-direction: column;
	gap: var(--mu-spacing);
	margin: 0;
	padding: 0;
	list-style: none;
}

.founding-offer-perk {
	display: flex;
	gap: 0.6rem;
	align-items: flex-start;
}

.founding-offer-perk-icon {
	flex: none;
	margin-top: 0.2rem;
	color: var(--mu-primary);
}

.founding-offer-perk > div {
	min-width: 0;
}

.founding-offer-perk-desc {
	margin: 0.1rem 0 0;
	color: var(--mu-muted-color);
	font-size: 0.85rem;
}

.founding-offer-roadmap {
	display: flex;
	flex-direction: column;
	gap: 0.6rem;
	margin: 0;
	padding: 0;
	list-style: none;
}

.founding-offer-roadmap-item {
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
}

.founding-offer-roadmap-tag {
	flex: none;
	padding: 0.1rem 0.5rem;
	border-radius: var(--mu-border-radius);
	background: var(--mu-card-sectioning-background-color);
	color: var(--mu-muted-color);
	font-size: 0.75rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.03em;
}

.founding-offer-trust {
	margin: 0.6rem 0 0;
	color: var(--mu-muted-color);
	text-align: center;
}

/* The offer page's checkout button is wrapped in a <form> so it can POST to
   Stripe (founding_member_offer.templ). µCSS styles the button full-width, so
   the wrapper has to not introduce a margin of its own. */
.founding-offer-card form {
	margin: 0;
}

/* Post-checkout thank-you page (founding_member_thanks.templ). Reuses
   .founding-offer-card and the offer's perk/roadmap lists -- only the bits
   with no counterpart on the offer page are below. */

.founding-thanks-head {
	text-align: center;
}

.founding-thanks-renewal,
.membership-cancelled-access {
	margin: 0;
	padding: 0.7rem 0.9rem;
	border-radius: var(--mu-border-radius);
	background: var(--mu-card-sectioning-background-color);
	font-size: 0.9rem;
}

.founding-thanks-renewal > i,
.membership-cancelled-access > i {
	margin-right: 0.35rem;
	color: var(--mu-primary);
}

.founding-thanks-roadmap-intro {
	color: var(--mu-muted-color);
	font-size: 0.9rem;
}

.founding-thanks-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.6rem;
}

.founding-thanks-actions > a[role="button"] {
	flex: 1 1 12rem;
	text-align: center;
}

/* Cancellation page (membership_cancelled.templ). The brand colour is this
   app's "you're a member" colour, so the cancelled tag deliberately doesn't use
   it -- muted reads as a state that has ended. */
.membership-cancelled-tag {
	color: var(--mu-muted-color);
}

/* Membership card on the profile page, in the upgrade banner's place for a
   viewer who already pays (profile.templ). Same shape as
   .founding-cta-banner so the two never jump the layout around. */

.membership-card {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: var(--mu-spacing);
	margin: var(--mu-spacing) 0;
	border-left: 3px solid var(--mu-primary);
}

.membership-card p {
	margin: 0.15rem 0 0;
	color: var(--mu-muted-color);
	font-size: 0.9rem;
}

.membership-card form {
	margin: 0;
}

.membership-card button {
	width: auto;
	white-space: nowrap;
	margin: 0;
}

.membership-card-renewal > i {
	margin-right: 0.3rem;
}

/* A membership that's been cancelled but hasn't lapsed yet: still a working
   membership, so the card stays as it is and only this line changes tone. */
.membership-card-ending {
	color: var(--mu-primary);
}

/* Centered icon/heading/message/button card: "you're already a member"
   (founding_member_offer.templ) and "you've hit the free-tier cap"
   (review.templ) both use this -- same shape, different icon and copy. */
.upgrade-notice {
	text-align: center;
	padding: var(--mu-spacing) 0;
}

.upgrade-notice > i {
	font-size: 2rem;
	color: var(--mu-primary);
}

.upgrade-notice p {
	max-width: 28rem;
	margin: 0.5rem auto calc(var(--mu-spacing) * 1.25);
	color: var(--mu-muted-color);
}

/* Review form: inline error from a rejected submission (e.g. the free-tier
   review cap, raced past the page's own AtReviewLimit check). */
.review-form-error {
	display: block;
	margin-top: 0.5rem;
	color: var(--pico-color-red-500);
}

/* Character count under the short-review textarea. Muted until the textarea
   is actually full, where it turns the same red as the form's error line --
   at that point it is explaining why typing stopped, not offering trivia. */
.review-text-count {
	display: block;
	margin-top: 0.25rem;
	text-align: right;
	color: var(--mu-muted-color);
}

.review-text-count.is-at-limit {
	color: var(--pico-color-red-500);
}

/* Pick-username screen (pick_username.templ). The same narrow centred column
   as the founding-member offer, for the same reason -- both are one-decision
   pages shown outside the app nav. */

.pick-username-card {
	max-width: 28rem;
	margin: 0 auto;
}

.pick-username-card form {
	margin: 0;
}

/* µCSS puts the field's own bottom margin between the input and this line,
   which reads as detached from the field it describes -- pull it back up
   against the input and carry the gap below instead. */
.pick-username-hint,
.pick-username-error {
	display: block;
	margin-top: calc(var(--mu-spacing) * -0.5);
	margin-bottom: var(--mu-spacing);
}

.pick-username-error {
	color: var(--pico-color-red-500);
}

/* Onboarding (onboarding.templ, onboarding_nav.templ). The signup flow's own
   chrome: shown in place of the app nav, and only until the tenth rating.
   Reuses .movie-cards / .movie-card wholesale for the results grid, so only
   what differs lives here. */

/* Progress counter in the header, where the app nav's tabs would be. The nav
   is a flex row of <ul>s (see the App nav section), so this sits inside the
   second one and stacks the label over the bar. */
.onboarding-progress {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 0.25rem;
	min-width: 9rem;
}

.onboarding-progress-count {
	white-space: nowrap;
}

/* µCSS's default <progress> carries the bottom margin of a form field, which
   inside a nav row pushes the bar off-centre against the logo. */
.onboarding-progress progress {
	margin: 0;
	height: 0.4rem;
}

/* The same narrow centred column as .pick-username-card, and for the same
   reason: both are signup steps shown outside the app, and a full-width
   measure would make the explanation on each of them hard to read. The grid
   below deliberately escapes it. */
.onboarding-intro {
	max-width: 40rem;
	margin: 0 auto var(--mu-spacing) auto;
}

.onboarding-intro hgroup {
	margin-bottom: var(--mu-spacing);
}

.onboarding-remaining {
	font-size: 1.25rem;
	color: var(--mu-primary);
}

.onboarding-search {
	max-width: 40rem;
	margin: 0 auto var(--mu-spacing) auto;
}

/* µCSS gives a form's last child no bottom margin, but this form's only child
   is the input, whose margin is what separates it from the results grid. */
.onboarding-search input {
	margin-bottom: 0;
}

.onboarding-empty {
	max-width: 40rem;
	margin: calc(var(--mu-spacing) * 2) auto;
	text-align: center;
	color: var(--mu-muted-color);
}

/* Public catalog (films.templ). The grid and cards are Browse's and the
   rater's; only the link around each card is new. It wraps the <article> so
   a crawler has an href to follow, and must not look like one: no underline
   under the title, no link colour on the year. */
.films-card-link {
	display: block;
	color: inherit;
	text-decoration: none;
}

.films-join {
	margin-top: calc(var(--mu-spacing) * 2);
}

/* Completion screen -- the one page between the last rating and the app. */
.onboarding-complete {
	max-width: 34rem;
	margin: calc(var(--mu-spacing) * 2) auto;
	text-align: center;
}

.onboarding-complete-icon {
	font-size: 3rem;
	color: var(--mu-primary);
}

.onboarding-complete-hint {
	color: var(--mu-muted-color);
}

/* The three preference steps between the taste fit and the app
   (onboarding_prefs.templ): region, content limits, streaming services.

   Same narrow centred measure as .onboarding-intro and .pick-username-card --
   every one-decision signup screen shares it. The sliders and the provider
   checkboxes are the profile page's own controls, so their styling comes from
   the Profile section above and nothing is repeated here. */
.onboarding-step {
	max-width: 40rem;
	margin: 0 auto;
}

.onboarding-step hgroup {
	margin-bottom: var(--mu-spacing);
}

/* Sits directly under the heading block as a thin rule, so it reads as
   progress through the flow rather than as a form field. */
.onboarding-step-progress {
	height: 0.3rem;
	margin-bottom: calc(var(--mu-spacing) * 1.5);
}

.onboarding-step-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
	align-items: center;
	margin-top: var(--mu-spacing);
}

/* Both children are actions of equal weight in layout terms even though Skip
   is visually secondary -- µCSS would otherwise stretch the <a role="button">
   to full width while the <button> sized to its text. */
.onboarding-step-actions > button,
.onboarding-step-actions > a[role="button"] {
	width: auto;
	margin: 0;
}

/* Region tiles. A real radio group -- one <input> per tile, inside its label --
   so it works, and shows which tile is picked, with no JS at all. The input is
   hidden from sight but not from assistive tech or the keyboard, which is why
   this is clip-based rather than display:none. */
.onboarding-region-tiles {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
	gap: var(--mu-spacing);
	border: none;
	padding: 0;
	margin-bottom: 0;
}

.onboarding-region-tile {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.25rem;
	padding: var(--mu-spacing);
	border: 2px solid var(--mu-muted-border-color);
	border-radius: var(--mu-border-radius);
	background-color: var(--mu-card-background-color);
	cursor: pointer;
	text-align: center;
	transition: border-color 0.15s ease, background-color 0.15s ease;
}

.onboarding-region-tile input[type="radio"] {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.onboarding-region-tile:hover {
	border-color: var(--mu-primary-border);
}

/* :has() is what lets the checked state style the tile rather than the input.
   A browser without it still gets a working radio group -- just one that
   doesn't highlight -- which is why the focus ring below is separate. */
.onboarding-region-tiles:has(input:checked) .onboarding-region-tile:has(input:checked) {
	border-color: var(--mu-primary);
	background-color: var(--mu-primary-background);
	color: var(--mu-primary-inverse);
}

/* The code is muted grey at rest, which on the solid brand fill is 1:1 -- the
   selected tile would lose its own label. It follows the tile's text instead. */
.onboarding-region-tiles:has(input:checked) .onboarding-region-tile:has(input:checked) .onboarding-region-code {
	color: inherit;
}

.onboarding-region-tile:has(input:focus-visible) {
	outline: var(--mu-outline-width) solid var(--mu-primary-focus);
	outline-offset: 2px;
}

.onboarding-region-flag {
	font-size: 2.5rem;
	line-height: 1.1;
}

.onboarding-region-name {
	font-weight: 600;
}

.onboarding-region-code {
	font-size: 0.75rem;
	color: var(--mu-muted-color);
	letter-spacing: 0.05em;
}

/* A <legend> that exists for screen readers only -- the <h1> above already
   names the question, so showing both would read as a duplicate heading. */
.onboarding-visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* driver.js popovers (the review form's guided tour, static/js/review-tour.js).
   Its stylesheet ships a light-theme popover with a blue action button, which
   against the page's dark theme reads as a browser dialog rather than part of the
   page. Only the colours are overridden -- the positioning and the cutout are
   what the library is for. */
.driver-popover {
	background-color: var(--mu-card-background-color);
	color: var(--mu-color);
	border: 1px solid var(--mu-muted-border-color);
	border-radius: var(--mu-border-radius);
}

.driver-popover-title {
	color: var(--mu-primary-hover);
}

/* The arrow is a CSS triangle built from border colours, so each direction
   names the one edge that faces the popover. */
.driver-popover-arrow-side-top.driver-popover-arrow {
	border-top-color: var(--mu-card-background-color);
}

.driver-popover-arrow-side-bottom.driver-popover-arrow {
	border-bottom-color: var(--mu-card-background-color);
}

.driver-popover-arrow-side-left.driver-popover-arrow {
	border-left-color: var(--mu-card-background-color);
}

.driver-popover-arrow-side-right.driver-popover-arrow {
	border-right-color: var(--mu-card-background-color);
}

.driver-popover-navigation-btns button {
	background-color: var(--mu-primary-background);
	color: var(--mu-primary-inverse);
	border: none;
	border-radius: var(--mu-border-radius);
	text-shadow: none;
}

.driver-popover-close-btn {
	color: var(--mu-muted-color);
}

.driver-popover-progress-text {
	color: var(--mu-muted-color);
}

/* Navigation loading spinner (base.templ's navLoading) */

/* Shown while an htmx swap of <main> is in flight -- see navLoading for what
   raises it and why it lives outside <main>.

   Fixed and centred in the viewport rather than laid over <main>: the header
   height is not a variable anything here can read, so a scrim positioned
   against the content area would have to guess at it and would guess wrong
   the moment the nav wraps to two rows on a narrow screen. A floating card
   needs no such measurement and reads against whatever is behind it.

   pointer-events: none because nothing here is a button. The previous page is
   still on screen and still interactive, which is the honest state -- a
   visitor who picked the wrong tab can pick another one without waiting for
   the first to arrive.

   font-size is the spinner's size control, not text sizing: µCSS draws the
   aria-busy spinner as a 1em ::before, the same lever .browse-loading pulls.
   line-height: 1 stops the line box adding invisible padding on top of the
   real padding below it.

   z-index 100 clears everything this stylesheet draws -- the highest is the
   mobile nav dropdown at 10 -- while staying well under driver.js's tour
   overlay, which sits in the thousands. That ordering is the one we want: a
   navigation started from inside the review tour should not paint over the
   step the visitor is reading. */
.nav-loading {
	position: fixed;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 100;
	visibility: hidden;
	opacity: 0;
	pointer-events: none;
	font-size: 2.5rem;
	line-height: 1;
	padding: 1.5rem;
	border-radius: var(--mu-border-radius);
	background-color: var(--mu-card-background-color);
	box-shadow: var(--mu-card-box-shadow);
}

/* The 250ms answer to "is this slow enough to be worth saying so".

   Below about 100ms a swap reads as instantaneous and any indicator is pure
   flicker; past about a second an unannounced wait reads as a broken click.
   250ms sits in the gap: fast connections never see this at all, and the
   cases that actually hurt -- Browse's first match-sorted load of the day,
   which rebuilds the whole score set -- are covered well before the visitor
   starts wondering whether the tab registered.

   Expressed as an animation-delay rather than a JS timer on purpose. A timer
   has to be cancelled on every path that ends a request -- success, HTTP
   error, network failure, abort, HX-Redirect -- and the one that gets missed
   leaves a spinner stranded over a page that finished loading. A delay can't
   outlive the class: Alpine removes nav-loading-active, the animation is
   cancelled mid-flight, and the base rule above is what the element falls
   back to.

   visibility, not just opacity, is what keeps role="status" out of a screen
   reader's way for the 250ms before there is anything to report -- an
   opacity-0 element is still in the accessibility tree. It is animated from
   the first keyframe rather than transitioned because visibility does not
   interpolate; forwards is what holds it visible once the fade finishes.

   aria-busy on the element is doing two jobs, and the second one is easy to
   delete by accident. Besides drawing the spinner, it is what exempts this
   rule from µCSS's prefers-reduced-motion block, which slams
   `animation-delay: -1ms !important` onto every selector matching
   :not([aria-busy=true]). Drop the attribute and a reduced-motion visitor
   stops getting the 250ms debounce and starts getting a spinner flashed at
   them on every navigation -- the exact thing the setting asks for less of.
   (µCSS's own spin is animated inside the icon's SVG data URI, so it is a
   separate document and that block never reached it either way.) */
.nav-loading-active {
	animation: nav-loading-in 120ms linear 250ms forwards;
}

@keyframes nav-loading-in {
	from {
		visibility: visible;
		opacity: 0;
	}
	to {
		visibility: visible;
		opacity: 1;
	}
}

/* Account management: the rename card and the danger zone (profile.templ's
   profileAccount / usernameCard), plus the close-account screen
   (account_close.templ).

   Red here is deliberately the *only* red in a structural role. Everywhere
   else in this stylesheet --pico-color-red-500 is text (a failed save, an F
   badge, a review over its cap), because nothing else in the app is
   destructive. The left border and the filled button below are reserved for
   the one flow that is, so that seeing red as a shape rather than as a word
   means something irreversible is on screen. */
.username-current {
	margin: 0;
	font-size: 1.05rem;
}

.username-error {
	display: block;
	margin-top: 0.4rem;
	color: var(--pico-color-red-500);
}

.profile-account {
	margin-top: calc(var(--mu-spacing) * 1.5);
}

/* Same geometry as .membership-card and .founding-cta-banner -- a full-width
   flex row with a 3px left border -- so the danger zone reads as the third
   member of that family rather than as a different kind of object. Only the
   colour distinguishes it, which is the point. */
.danger-zone {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: var(--mu-spacing);
	margin: var(--mu-spacing) 0 0;
	border-left: 3px solid var(--pico-color-red-500);
}

.danger-zone-title {
	color: var(--pico-color-red-500);
}

.danger-zone p {
	margin: 0.15rem 0 0;
	max-width: 46rem;
	color: var(--mu-muted-color);
	font-size: 0.9rem;
}

/* Outline rather than filled, unlike the button on the confirm screen itself.
   This one only navigates -- nothing is destroyed by pressing it -- and a
   solid red button here would spend the strongest affordance in the app on a
   link, leaving nothing louder for the button that actually deletes. */
.danger-zone-action {
	width: auto;
	flex: 0 0 auto;
	--mu-background-color: transparent;
	--mu-border-color: var(--pico-color-red-500);
	--mu-color: var(--pico-color-red-500);
	--mu-primary-hover-background: var(--pico-color-red-500);
	--mu-primary-hover-border: var(--pico-color-red-500);
}

.danger-zone-action:hover,
.danger-zone-action:focus {
	--mu-color: var(--mu-primary-inverse);
	background: var(--pico-color-red-500);
	border-color: var(--pico-color-red-500);
}

/* The confirm screen. Narrow measure on purpose: this is the one page in the
   app meant to be read rather than scanned, and a 60-character line is what
   makes somebody actually read the list of what they are about to lose. */
.account-close-card {
	max-width: 44rem;
	margin: var(--mu-spacing) auto;
	border-left: 3px solid var(--pico-color-red-500);
}

.account-close-eyebrow {
	display: block;
	margin-bottom: 0.3rem;
	color: var(--pico-color-red-500);
	letter-spacing: 0.06em;
}

.account-close-whoami,
.account-close-nocopy {
	padding: 0.7rem 0.9rem;
	border-radius: var(--mu-border-radius);
	background: var(--mu-card-sectioning-background-color);
	font-size: 0.9rem;
}

.account-close-list {
	margin: var(--mu-spacing) 0;
}

.account-close-list ul {
	margin: 0.5rem 0 0;
}

.account-close-list li {
	margin-bottom: 0.35rem;
	font-size: 0.93rem;
}

/* The two conditional warnings (membership, founding slot). Brand-coloured, not
   red: red is the deletion itself, and these are consequences of it that a reader
   has to be able to tell apart from the headline at a glance. */
.account-close-warning {
	display: flex;
	gap: 0.6rem;
	margin: 0 0 var(--mu-spacing);
	padding: 0.7rem 0.9rem;
	border-radius: var(--mu-border-radius);
	border-left: 3px solid var(--mu-primary);
	background: var(--mu-card-sectioning-background-color);
	font-size: 0.9rem;
}

.account-close-warning > i {
	color: var(--mu-primary);
	padding-top: 0.15rem;
}

.account-close-error {
	display: block;
	margin-top: 0.4rem;
	color: var(--pico-color-red-500);
}

/* Keep-my-account first in source order, so it is first for the keyboard and
   for a screen reader too, not just visually. row-reverse is deliberately NOT
   used to achieve that -- the reading order is the safe order. */
.account-close-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.6rem;
	margin-top: var(--mu-spacing);
}

.account-close-keep {
	flex: 1 1 12rem;
	text-align: center;
}

.account-close-delete {
	flex: 1 1 16rem;
	--mu-background-color: var(--pico-color-red-500);
	--mu-border-color: var(--pico-color-red-500);
}

/* µCSS fades a disabled button to half opacity, which here is exactly right:
   until the username is typed, deleting is not an action that is available,
   and it should not look like one. */
.account-close-delete:disabled {
	cursor: not-allowed;
}

/* Tonight (tonight.templ, tonight_find.templ, tonight_room.templ,
   tonight_form.templ): the hub, "Find a Movie", and Movie Nights.

   Every choice on the questionnaire is a real radio or checkbox inside its
   label, hidden from sight but not from assistive tech or the keyboard -- the
   region tiles' technique -- so the form works, and shows what is picked, with
   no JS. :has() styles the chosen label; a browser without it still gets a
   working form. */

.tonight-hub > hgroup,
.tonight-find > hgroup,
.tonight-room hgroup,
.tonight-join > hgroup {
	margin-bottom: calc(var(--mu-spacing) * 1.25);
}

/* The small brand-coloured line over a Tonight page's heading -- which flow this is. */
.tonight-kicker {
	margin-bottom: 0.25rem;
	color: var(--mu-primary);
	font-size: 0.8rem;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.tonight-hub-cards {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
	gap: var(--mu-spacing);
}

.tonight-card {
	display: flex;
	flex-direction: column;
	margin: 0;
}

.tonight-card > header {
	display: flex;
	align-items: center;
	gap: 0.5rem;
}

.tonight-card > header .bi {
	color: var(--mu-primary);
	font-size: 1.25rem;
}

/* The card's action sits at its foot whatever the length of the copy above
   it, so the three buttons line up across the row. */
.tonight-card > form,
.tonight-card > a[role="button"] {
	margin-top: auto;
	margin-bottom: 0;
}

.tonight-card > form button,
.tonight-card > a[role="button"] {
	width: 100%;
}

.tonight-card form fieldset[role="group"] {
	margin-bottom: 0;
}

/* Locked rather than hidden: the card is how a free member learns Movie
   Nights exist, so it stays in the row, quieter. */
.tonight-card-locked > header,
.tonight-card-locked > p:not(.tonight-locked-note) {
	opacity: 0.6;
}

.tonight-locked-note {
	color: var(--mu-primary);
	font-size: 0.9rem;
}

/* "Your Movie Night is on" and the founding-member banner share one
   geometry: a full-width strip with its one action at the end. */
.tonight-return {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: var(--mu-spacing);
	border-left: 3px solid var(--mu-primary);
}

.tonight-return p {
	margin: 0.15rem 0 0;
	color: var(--mu-muted-color);
}

.tonight-return > a[role="button"] {
	width: auto;
	white-space: nowrap;
}

.tonight-code,
.tonight-code-inline {
	font-family: var(--mu-font-family-monospace);
	letter-spacing: 0.08em;
}

.tonight-code {
	color: var(--mu-primary);
}

/* Tonight's picks on the hub: three posters in a row, the whole strip one
   link to the results. */
.tonight-strip > header {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 0.5rem;
}

/* Capped at a thumbnail's width: at full width three posters would push the
   hub's three ways in below the fold. */
.tonight-thumbs {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 9rem));
	gap: var(--mu-spacing);
	color: inherit;
	text-decoration: none;
}

.tonight-thumb {
	margin: 0;
}

.tonight-thumb img,
.tonight-thumb .movie-card-noposter {
	width: 100%;
	aspect-ratio: 2 / 3;
	object-fit: cover;
	border-radius: var(--mu-border-radius);
}

.tonight-thumb figcaption {
	display: flex;
	flex-direction: column;
	margin-top: 0.35rem;
	font-size: 0.85rem;
	line-height: 1.3;
}

.tonight-thumb figcaption small {
	color: var(--mu-muted-color);
}

/* The questionnaire. One section at a time once Alpine is up; stacked, each
   its own block, without it. */
.tonight-form {
	max-width: 44rem;
}

.tonight-section {
	padding: 0;
	margin-bottom: calc(var(--mu-spacing) * 2);
	border: none;
}

.tonight-section-head {
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
	margin-bottom: 0.5rem;
}

.tonight-section-title {
	font-size: 1.4rem;
	font-weight: 700;
	line-height: 1.25;
}

.tonight-section-blurb {
	color: var(--mu-muted-color);
}

.tonight-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
}

.tonight-chip,
.tonight-option {
	position: relative;
	display: inline-flex;
	align-items: center;
	margin: 0;
	padding: 0.4rem 0.9rem;
	border: 1px solid var(--mu-muted-border-color);
	border-radius: 999px;
	background-color: var(--mu-card-background-color);
	cursor: pointer;
	transition: border-color 0.15s ease, background-color 0.15s ease;
}

.tonight-chip input,
.tonight-option input {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.tonight-chip:hover,
.tonight-option:hover {
	border-color: var(--mu-primary-border);
}

.tonight-chip:has(input:checked),
.tonight-option:has(input:checked) {
	border-color: var(--mu-primary);
	background-color: var(--mu-primary-background);
	color: var(--mu-primary-inverse);
}

.tonight-chip:has(input:focus-visible),
.tonight-option:has(input:focus-visible) {
	outline: var(--mu-outline-width) solid var(--mu-primary-focus);
	outline-offset: 2px;
}

/* At the tone or payoff limit, the boxes not yet ticked are disabled (the
   form's Alpine counts), and look it. */
.tonight-chip:has(input:disabled) {
	cursor: not-allowed;
	opacity: 0.4;
}

.tonight-chip-any {
	border-style: dashed;
}

/* Intensity's four words carry the gloss the prompt explains them by, so
   each is a block rather than a pill. */
.tonight-chips-glossed .tonight-chip,
.tonight-option {
	border-radius: var(--mu-border-radius);
}

.tonight-chip small,
.tonight-option small {
	display: block;
	color: var(--mu-muted-color);
	font-size: 0.8rem;
}

.tonight-chip:has(input:checked) small,
.tonight-option:has(input:checked) small {
	color: inherit;
	opacity: 0.85;
}

.tonight-services,
.tonight-runtime,
.tonight-era {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: 0.5rem;
}

/* µCSS sizes any label holding a radio to fit-content, which leaves a column
   of options ragged at the right; these are a list of choices, one width. */
.tonight-services .tonight-option,
.tonight-runtime .tonight-option,
.tonight-era .tonight-option {
	width: 100%;
}

.tonight-provider-list {
	margin: 0.25rem 0 0.25rem 1rem;
}

.tonight-wish-count {
	display: block;
	margin-top: -0.5rem;
	color: var(--mu-muted-color);
	text-align: right;
}

.tonight-over {
	color: var(--pico-color-red-500);
}

.tonight-seen {
	display: flex;
	flex-direction: column;
	gap: var(--mu-spacing);
}

.tonight-seen-film {
	display: grid;
	grid-template-columns: 4rem minmax(0, 1fr);
	gap: var(--mu-spacing);
	align-items: center;
}

.tonight-seen-film img,
.tonight-seen-film .movie-card-noposter {
	width: 4rem;
	aspect-ratio: 2 / 3;
	object-fit: cover;
	border-radius: var(--mu-border-radius);
}

.tonight-seen-film small {
	margin-left: 0.4rem;
	color: var(--mu-muted-color);
}

.tonight-seen-reactions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	margin-top: 0.4rem;
}

.tonight-comfort {
	display: flex;
	align-items: center;
	gap: var(--mu-spacing);
}

.tonight-comfort input[type="range"] {
	flex: 1;
	margin: 0;
}

.tonight-comfort span {
	color: var(--mu-muted-color);
	font-size: 0.9rem;
	white-space: nowrap;
}

.tonight-problem {
	padding: 0.6rem 0.9rem;
	border-left: 3px solid var(--pico-color-red-500);
	background-color: var(--mu-card-background-color);
}

.tonight-notice {
	border-left: 3px solid var(--mu-primary);
}

.tonight-notice h3 {
	margin-bottom: 0.4rem;
	font-size: 1.1rem;
}

.tonight-notice .onboarding-step-actions form {
	margin: 0;
}

.tonight-back {
	margin-top: calc(var(--mu-spacing) * 1.5);
}

/* Result cards: poster on the left, everything about the pick beside it,
   stacking on a phone. */
.tonight-picks {
	display: flex;
	flex-direction: column;
	gap: var(--mu-spacing);
	margin-bottom: var(--mu-spacing);
}

.tonight-pick {
	display: grid;
	grid-template-columns: minmax(0, 10rem) minmax(0, 1fr);
	gap: calc(var(--mu-spacing) * 1.25);
	margin: 0;
}

@media (max-width: 576px) {
	.tonight-pick {
		grid-template-columns: minmax(0, 1fr);
	}

	.tonight-pick-poster {
		max-width: 12rem;
	}
}

.tonight-pick-poster {
	position: relative;
	display: block;
	align-self: start;
}

.tonight-pick-poster img,
.tonight-pick-poster .movie-card-noposter {
	width: 100%;
	aspect-ratio: 2 / 3;
	object-fit: cover;
	border-radius: var(--mu-border-radius);
}

.tonight-pick-slot {
	margin-bottom: 0.15rem;
	color: var(--mu-primary);
	font-size: 0.8rem;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.tonight-pick-body h3 {
	margin-bottom: 0.25rem;
}

.tonight-fit span {
	color: var(--mu-muted-color);
}

.tonight-traits {
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
	padding: 0;
	margin-bottom: var(--mu-spacing);
	list-style: none;
}

.tonight-traits li {
	padding: 0.1rem 0.6rem;
	margin: 0;
	border: 1px solid var(--mu-muted-border-color);
	border-radius: 999px;
	color: var(--mu-muted-color);
	font-size: 0.8rem;
	list-style: none;
}

/* The pick's streaming card sits inside the result card, so it drops its
   own card chrome and reads as one more section of the pick. */
.tonight-pick .streaming-card {
	padding: 0;
	margin-bottom: var(--mu-spacing);
	background: none;
	box-shadow: none;
}

.tonight-pick .movie-list-buttons {
	justify-content: flex-start;
}

.tonight-pick .movie-list-buttons button {
	width: auto;
	margin-bottom: 0;
}

/* Movie Night */
.tonight-room-header {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: flex-end;
	gap: var(--mu-spacing);
	margin-bottom: var(--mu-spacing);
}

.tonight-room-header hgroup {
	margin-bottom: 0;
}

.tonight-share {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem;
}

.tonight-share button {
	width: auto;
	margin: 0;
}

.tonight-lobby-bar {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--mu-spacing) * 1.25);
	margin-bottom: var(--mu-spacing);
	color: var(--mu-muted-color);
}

.tonight-members {
	padding: 0;
	margin-bottom: var(--mu-spacing);
	list-style: none;
}

.tonight-members li {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 1rem;
	padding: 0.5rem 0;
	margin: 0;
	border-bottom: 1px solid var(--mu-muted-border-color);
	list-style: none;
}

.tonight-member-name {
	flex: 1 1 12rem;
	font-weight: 600;
}

.tonight-member-state {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.4rem;
	color: var(--mu-muted-color);
}

.tonight-badge {
	padding: 0 0.45rem;
	margin-left: 0.35rem;
	border: 1px solid var(--mu-muted-border-color);
	border-radius: 999px;
	color: var(--mu-muted-color);
	font-size: 0.7rem;
	font-weight: 400;
	text-transform: uppercase;
}

.tonight-kick {
	width: auto;
	padding: 0.15rem 0.5rem;
	margin: 0;
}

.tonight-host-controls {
	margin-bottom: var(--mu-spacing);
}

.tonight-room-settings details,
.tonight-room-questions details {
	margin-bottom: 0;
}

.tonight-room-questions > header {
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
}

.tonight-room-questions > header small {
	color: var(--mu-muted-color);
}

.tonight-leave button {
	width: auto;
}
