/*
One variable file per script. The browser fetches a subset only when the page shows a
character from its range, e.g a Cyrillic collection is fetched when someone types it.
*/
@font-face {
	font-family: "Inter";
	src: url("/fonts/Inter-latin.woff2") format("woff2");
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
		U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Inter";
	src: url("/fonts/Inter-latin-ext.woff2") format("woff2");
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
	unicode-range:
		U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
		U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
		U+A720-A7FF;
}

@font-face {
	font-family: "Inter";
	src: url("/fonts/Inter-cyrillic.woff2") format("woff2");
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
	font-family: "Inter";
	src: url("/fonts/Inter-cyrillic-ext.woff2") format("woff2");
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

@font-face {
	font-family: "JetBrains Mono";
	src: url("/fonts/JetBrainsMono-Regular.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: block;
}

:root {
	/* with no data-theme the browser decides, and light-dark() follows color-scheme */
	color-scheme: light dark;
	/*
	The four neutrals are neutral: hue 0, saturation 0. No warm colors.
	*/
	--fg: light-dark(#1a1a1a, #e7e7e7);
	--muted: light-dark(#646464, #929292);
	--line: light-dark(#d0d0d0, #3b3b3b);
	--error: light-dark(#9b2f1c, #ee967d);
	/* Warning color: same warm family as the error color, but shifted toward orange. */
	--warn: light-dark(#a8560f, #e8a05e);
	/* the fill of a submit button: a step down from --fg, so it doesn't glare */
	--primary: light-dark(#464646, #c3c3c3);
	/* the page fill */
	--bg: Canvas;
	/* one height for every control that sits in a row, whatever its content */
	--control-h: 1.7rem;
	/* page text, fallbacks apply when it is still loading */
	--sans: "Inter", system-ui, sans-serif;
	/* page hexes */
	--mono: "JetBrains Mono", ui-monospace, monospace;

	/*
	Ten collection accents. 
	'-dark' value is canonical: what picker sends and what collections.icon_accent
	stores. internal/clt.DefIconAccent is the gray one. 
	The light twin is shown and is not stored, light-dark() re-resolves every tint on a theme flip.
	*/
	--accent-gray-dark: #dbd7d0;
	--accent-gray: light-dark(#63605b, var(--accent-gray-dark));
	--accent-red-dark: #f8a49d;
	--accent-red: light-dark(#a3544f, var(--accent-red-dark));
	--accent-orange-dark: #ebb077;
	--accent-orange: light-dark(#986021, var(--accent-orange-dark));
	--accent-yellow-dark: #c7c274;
	--accent-yellow: light-dark(#787119, var(--accent-yellow-dark));
	--accent-green-dark: #95cf96;
	--accent-green: light-dark(#447e47, var(--accent-green-dark));
	--accent-teal-dark: #69d3c6;
	--accent-teal: light-dark(#008076, var(--accent-teal-dark));
	--accent-cyan-dark: #70ccee;
	--accent-cyan: light-dark(#007a9b, var(--accent-cyan-dark));
	--accent-blue-dark: #9ebdff;
	--accent-blue: light-dark(#506cac, var(--accent-blue-dark));
	--accent-violet-dark: #cdaef2;
	--accent-violet: light-dark(#7d5e9f, var(--accent-violet-dark));
	--accent-pink-dark: #eda4cc;
	--accent-pink: light-dark(#99547d, var(--accent-pink-dark));
}

/* changing color-scheme re-evaluates all light-dark() values. */
:root[data-theme="light"] {
	color-scheme: light;
}

:root[data-theme="dark"] {
	color-scheme: dark;
}

/*
EXPERIMENTAL, see web-wavelen-modes.md. The two shades are a step of 10 L* each, measured
*/
:root[data-bg="soft"] {
	--bg: light-dark(#e3e3e3, #262626);
	--line: light-dark(#b8b8b8, #494949);
}

/*
--line steps with the fill. It is a faint line by design, 1.55 against the default page, and a
ground that walks onto its own lightness swallows every border and section rule on the page.
Each pair keeps that 1.55.
*/
:root[data-bg="deep"] {
	--bg: light-dark(#c7c7c7, #3c3c3c);
	--line: light-dark(#a0a0a0, #5d5d5d);
}

/*
Zen mode hides text and chrome while preserving layout.
Use visibility instead of display so swatches, bands, and grid rows keep their size.
*/
:root.zen .swatch-text,
:root.zen .detail-hex,
:root.zen .detail-label,
:root.zen .detail-info,
:root.zen .band-hex,
:root.zen #scratch-hex {
	visibility: hidden;
}

/* visibility here too: the header keeps its box, so nothing below it moves */
:root.zen header {
	visibility: hidden;
}

:root.zen .collection-icon {
	color: var(--muted);
}

html {
	/* reserve the scrollbar track, so a short list does not shift the centered body */
	scrollbar-gutter: stable;
}

/* the reserved track survives into full screen as an empty strip at the right edge */
html:has(:fullscreen) {
	scrollbar-gutter: auto;
}

body {
	font-family: var(--sans);
	max-width: 66rem;
	margin: 2rem auto;
	padding: 0 1rem;
	/* on body rather than :root: it propagates to the canvas, so the fill covers the whole
	   viewport and not the 66rem column */
	background: var(--bg);
	color: var(--fg);
}

header {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	margin-bottom: 1.5rem;
}

h1 {
	font-size: 1.25rem;
	margin: 0;
	cursor: pointer;
	/* prevent the second click of a double-click from selecting the title text. */
	user-select: none;
}

/*
The title cycles through two fixed tints.
Use literal values because these colors are used only here and work on both themes.
*/
h1[data-tint="1"] {
	color: #bf15a3;
}

h1[data-tint="2"] {
	color: #3daa6d;
}

/* Describes the page without competing with the title.
   The auto margin keeps the title and beta label together on the left. */
.beta {
	margin-right: auto;
	font-size: 0.8rem;
	color: var(--muted);
}

/* a word beside the title, not a button among the controls */
.header-word {
	border-color: transparent;
	color: var(--muted);
}

.header-word:hover {
	color: var(--fg);
}

/* the anchor,  account-drop is positioned against it*/
.account-menu {
	position: relative;
	display: flex;
	min-width: 0;
}

/*
The account chip: the nickname in a box.
*/
.who-chip,
.login-chip {
	display: flex;
	align-items: center;
	padding: 0.25rem 0.6rem;
	border-color: transparent;
	border-radius: 8px;
	background: light-dark(rgba(0, 0, 0, 0.09), rgba(255, 255, 255, 0.11));
	color: var(--muted);
}

.who-chip {
	max-width: 11rem;
}

.who-chip .name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* hover and open share a look, so the header says the menu is down while it is out of view */
.who-chip:hover,
.who-chip[aria-expanded="true"],
.login-chip:hover {
	background: light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.18));
	color: var(--fg);
}

/*
Right anchored: about sits to the right of the chip, and the listing row ends on its trigger.
*/
.menu-drop {
	position: absolute;
	top: calc(100% + 0.3rem);
	right: 0;
	z-index: 1;
	display: flex;
	flex-direction: column;
	min-width: 10rem;
	padding: 0.25rem;
	border: 1px solid var(--line);
	border-radius: 3px;
	background: Canvas;
}

/* word and its glyph, muted until reached: what these do is not undone */
.menu-item {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.3rem 0.45rem;
	border-color: transparent;
	color: var(--muted);
	white-space: nowrap;
}

.menu-item .icon {
	font-size: 1.1rem;
}

.menu-item:hover {
	border-color: var(--line);
	color: var(--fg);
}

.menu-item:disabled {
	opacity: 0.4;
	cursor: default;
}

.menu-item:disabled:hover {
	border-color: transparent;
	color: var(--muted);
}

/* not .danger, which is the filled submit in the delete dialog */
.menu-item-danger:enabled:hover {
	border-color: var(--error);
	color: var(--error);
}

dialog {
	min-width: 16rem;
	padding: 1.25rem 1.5rem;
	border: 1px solid var(--line);
	border-radius: 3px;
	/* Canvas for the same reason a select does: the page fill is transparent */
	background: Canvas;
	color: CanvasText;
}

dialog::backdrop {
	background: rgba(0, 0, 0, 0.4);
}

dialog h2 {
	margin: 0 0 0.75rem;
	line-height: 0;
}

dialog p {
	margin: 0 0 0.5rem;
	font-size: 0.85rem;
}

dialog hr {
	margin: 0.75rem 0;
	border: 0;
	border-top: 1px solid var(--line);
}

dialog a {
	color: inherit;
}

.about-note {
	color: var(--muted);
}

.about-warn {
	color: var(--warn);
}

/* the way out of a dialog: a click beside one does nothing */
.dialog-close {
	margin-top: 1rem;
	text-align: right;
}

/* make the account dialog wider because its fields are longer */
#account {
	min-width: 20rem;
}

/* the account forms: fields stacked and full width, the dialog is narrow */
.stack {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

.stack input {
	box-sizing: border-box;
	width: 100%;
}

/* only there while it has text, so a clean form doesn't hold an empty line */
.dialog-error {
	margin: 0.75rem 0 0;
	color: var(--error);
	font-size: 0.85rem;
}

.dialog-error:empty {
	display: none;
}

/* cancel first, so the destructive one is not the default target */
.dialog-actions {
	display: flex;
	justify-content: flex-end;
	gap: 0.5rem;
	margin-top: 0.25rem;
}

/* the one filled --error control: a submit weight, since that is what it is */
.danger {
	border-color: var(--error);
	background: var(--error);
	color: Canvas;
	font-weight: 600;
}

.danger:disabled {
	border-color: var(--line);
	background: transparent;
	color: var(--muted);
	font-weight: 400;
	cursor: default;
}

#account-delete label {
	font-size: 0.85rem;
	color: var(--muted);
}

#account-delete h3 {
	margin: 0 0 0.75rem;
	font-size: 1rem;
}

summary,
.panel-title {
	font-size: 0.8rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--muted);
	margin: 0 0 0.75rem;
	padding-bottom: 0.35rem;
	border-bottom: 1px solid var(--line);
}

summary {
	cursor: pointer;
	user-select: none;
}

.summary-help {
	float: right;
	/* the heading is uppercase 0.8rem, the icon is 1rem: pull it back onto the baseline */
	margin-top: -0.15rem;
}

details:not([open]) summary {
	margin-bottom: 0;
}

details {
	margin-bottom: 2rem;
}

.split {
	display: flex;
	align-items: flex-start;
	gap: 1.5rem;
}

/* min-width, or a grid row sized by its content would push the aside past the edge */
main {
	flex: 1;
	min-width: 0;
}

/* sticky, so the color stays in view while a long list scrolls beside it */
aside {
	flex: 0 0 15rem;
	position: sticky;
	top: 2rem;
}

/* stack the columns once there is not enough room to use them side by side */
@media (max-width: 38rem) {
	.split {
		flex-direction: column;
	}

	/* stacked, the cross axis is horizontal, so the flex-start above shrinks a column to
   its content: main came out 147px wide inside a 388px page */
	main,
	aside {
		align-self: stretch;
	}

	aside {
		flex: none;
		position: static;
	}
}

/*
	Keep the native [hidden] behavior even for elements with their own display rules.
	Several elements are toggled with the hidden property.
*/
[hidden] {
	display: none !important;
}

.row {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	flex-wrap: wrap;
	margin-bottom: 0.5rem;
}
/* two rows on one line while there is room, the listing controls against the right edge */
.toolbar {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.5rem 1rem;
	margin-bottom: 0.75rem;
}

.toolbar .row {
	margin-bottom: 0;
}

.toolbar .row:last-child {
	margin-left: auto;
}

label {
	color: var(--muted);
	font-size: 0.85rem;
}

input,
select,
button {
	font: inherit;
	font-size: 0.85rem;
	padding: 0.25rem 0.5rem;
	border: 1px solid var(--line);
	border-radius: 3px;
	background: transparent;
	color: inherit;
}

/*
The transparent background above is wrong for a select: the browser paints the popup
and cannot paint it transparent, so the popup comes out light while the inherited color
stays light. Canvas and CanvasText resolve against color-scheme, which the theme button
flips.
*/
select,
option {
	/* page fill rather than Canvas */
	background: var(--bg);
	color: CanvasText;
}

button {
	cursor: pointer;
}

input,
select,
.icon-button,
.icon-trigger,
.accent-trigger,
#collection-form button,
#add-form button,
#sort,
#order,
#palette-sort,
#palette-order,
#bg,
#space,
.stack > button {
	box-sizing: border-box;
	height: var(--control-h);
}

/*
Give each control type a distinct visual treatment:
filled submits, outlined helpers, underlined settings, and tab-like collections.
*/
#collection-form button:not([type="button"]),
#add-form button:not([type="button"]),
.stack > button {
	background: var(--primary);
	border-color: var(--primary);
	color: Canvas;
	font-weight: 600;
}

#sort,
#order,
#limit,
#palette-sort,
#palette-order,
#bg {
	padding-left: 0.2rem;
	padding-right: 0.2rem;
	border-color: transparent;
	text-decoration: underline dotted;
	text-underline-offset: 0.25em;
}

#sort:hover,
#order:hover,
#limit:hover,
#palette-sort:hover,
#palette-order:hover,
#bg:hover {
	border-color: var(--line);
}

#pins {
	margin-bottom: 0.75rem;
}

/* the wheel is pushed to the row's end, so the pins and the two words that act on them stay
   one group at the left */
.setting-help {
	display: flex;
	align-items: center;
	gap: 0.1rem;
	margin-left: auto;
}

/* borderless and muted, like a strip block's glyphs: a bordered word in this row would be an
   eighth pin, and a filled one would read as a pin that is on */
.bulk-pin {
	padding: 0.25rem 0.4rem;
	border-color: transparent;
	color: var(--muted);
}

.bulk-pin:hover {
	color: var(--fg);
}

#space {
	color: var(--muted);
}

#space:hover {
	color: var(--fg);
}

/* a glyph alone, a step under the control it annotates */
.help {
	display: flex;
	align-items: center;
	padding: 0.1rem 0.25rem;
	border-color: transparent;
	font-size: 0.95rem;
	line-height: 1;
	color: var(--muted);
}

.help-word {
	padding: 0.25rem 0.4rem;
	font-size: 0.85rem;
}

.help:hover {
	color: var(--fg);
}

.setting-help:has(.help-panel:popover-open) .help,
.info-head:has(.help-panel:popover-open) .help {
	color: var(--fg);
}

.help-panel {
	width: 15rem;
	max-width: calc(100vw - 2rem);
	padding: 0.6rem 0.75rem;
	border: 1px solid var(--line);
	border-radius: 3px;
	background: Canvas;
	color: var(--fg);
	font-size: 0.85rem;
}

/* a cycler's help is one line per value: the term is the value the control spells, the
   description is what picking it means */
.help-values {
	display: grid;
	grid-template-columns: auto 1fr;
	/* baseline, not the default stretch */
	align-items: baseline;
	gap: 0.15rem 0.5rem;
	margin: 0;
}

.help-values dt {
	font-family: var(--mono);
}

.help-values dd {
	margin: 0;
	color: var(--muted);
}

.help-line {
	margin: 0;
}

.help-panel a {
	color: inherit;
}

.help-hint {
	margin: 0.5rem 0 0;
	padding-top: 0.5rem;
	border-top: 1px solid var(--line);
	color: var(--muted);
}

[popover].help-panel {
	inset: auto;
	margin: 0;
}

/* Anchor positioning is younger than the popover. Without it the @supports drops and
   the panel keeps the UA's centred placement: readable, just not beside its button. */
@supports (anchor-name: --a) {
	#space-help-open {
		anchor-name: --space-help;
	}

	/* span-inline-start: panel hangs from the button's right edge and grows left, so it
	   stays inside the column whatever the row is. No position-try-fallbacks - Chromium's
	   flip-block fires here with 800px of room below and lands the panel above the row. */
	#space-help {
		position-anchor: --space-help;
		position-area: block-end span-inline-start;
		margin-top: 0.25rem;
	}

	#contrast-help-open {
		anchor-name: --contrast-help;
	}

	#contrast-help {
		position-anchor: --contrast-help;
		position-area: block-end span-inline-start;
		box-sizing: border-box;
		width: 15rem;
		margin-top: 0.25rem;
	}

	#palette-source-open {
		anchor-name: --palette-source;
	}

	#palette-source {
		position-anchor: --palette-source;
		position-area: block-end span-inline-start;
		margin-top: 0.25rem;
	}

	#shades-source-open {
		anchor-name: --shades-source;
	}

	#shades-source {
		position-anchor: --shades-source;
		position-area: block-end span-inline-start;
		margin-top: 0.25rem;
	}

	#hotkeys-open {
		anchor-name: --hotkeys-help;
	}

	#hotkeys-help {
		position-anchor: --hotkeys-help;
		position-area: block-end span-inline-start;
		box-sizing: border-box;
		width: 21rem;
		margin-top: 0.25rem;
	}
}

.tabs {
	display: flex;
	gap: 0.25rem;
	margin-bottom: 0.75rem;
	border-bottom: 1px solid var(--line);
}

/*
The 2px selected border replaces the 1px strip border.
Pull the tab down by 1px so the two borders overlap.
*/
.tab {
	padding: 0.25rem 0.6rem;
	margin-bottom: -1px;
	border: none;
	border-bottom: 2px solid transparent;
	border-radius: 0;
	color: var(--muted);
}

/* aria-selected is the only place the choice is recorded, so the style reads it */
.tab[aria-selected="true"] {
	border-bottom-color: var(--fg);
	color: var(--fg);
	font-weight: 600;
}

/*
Reserve enough width for the widest cycler label so adjacent controls do not move.
Center shorter labels within the reserved width.
*/
#sort,
#palette-sort {
	min-width: 3rem;
}

#order,
#palette-order {
	min-width: 3.5rem;
}

#bg {
	min-width: 2.6rem;
}

#load-more {
	margin-top: 0.75rem;
}

/*
Seven monospace characters. The caret at the end still sits inside the box.
*/
#hex {
	width: calc(7ch + 1rem + 2px);
	font-family: var(--mono);
}

/*
A tab strip, the same shape as the account tabs: the collections choose what the grid
below shows. The summary already draws a line, so the strip is pulled up toward it.
*/
.collection-bar {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	margin: -0.5rem 0 0.9rem;
	border-bottom: 1px solid var(--line);
}

.collections {
	display: flex;
	flex-wrap: wrap;
	gap: 0.25rem;
}

/* the tab carries the mark, the two buttons inside it are bare */
.collection {
	display: flex;
	align-items: center;
	margin-bottom: -1px;
	border-bottom: 2px solid transparent;
	color: var(--muted);
}

.collection.active {
	border-bottom-color: var(--fg);
	color: var(--fg);
}

.collection-name {
	border: none;
	border-radius: 0;
	background: none;
}

/* the glyph and the name in one button, a click anywhere on the tab selects */
.collection-name {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	max-width: 12rem;
	padding: 0.3rem 0.5rem;
}

/* collection names can be long, truncate them inside the tab */
.collection-name .name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* the trigger for the create form, a glyph at the end of the strip */
#collection-new {
	margin-left: 0.25rem;
	border-color: transparent;
}

#collection-new:hover,
#collection-new[aria-expanded="true"] {
	color: var(--fg);
}

.collection-menu {
	position: relative;
	display: flex;
}

/* full ink, like the buttons they share the row with. The open state is the tint the dense
   toggles use, since a color step has nowhere to go from --fg. */
#collection-edit[aria-expanded="true"],
#collection-more[aria-expanded="true"] {
	background: light-dark(rgba(0, 0, 0, 0.14), rgba(255, 255, 255, 0.18));
}

/* account with no collection has nothing to edit */
#collection-edit:disabled {
	opacity: 0.4;
	cursor: default;
}

/*
Shared stroke settings for all sprite icons.
currentColor lets the parent choose the icon color.
*/
.icon {
	width: 1em;
	height: 1em;
	flex: none;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.collection-icon,
.icon-trigger,
.icon-picker {
	color: var(--accent-color, inherit);
}

.collection-icon {
	font-size: 1.1rem;
}

/* grids are positioned against this anchor */
.picker-menu {
	position: relative;
	display: flex;
}

.icon-trigger,
.accent-trigger {
	display: flex;
	align-items: center;
	gap: 0.25rem;
	padding: 0.15rem 0.35rem;
	font-size: 1.1rem;
	line-height: 1;
}

/* not part of the preview, so muted rather than the accent */
.icon-trigger .caret,
.accent-trigger .caret {
	font-size: 0.7rem;
	color: var(--muted);
}

/*
Absolutely positioned, so opening one doesn't push the toolbar and the grid down the page.
*/
.icon-picker,
.accent-picker {
	position: absolute;
	top: calc(100% + 0.25rem);
	left: 0;
	z-index: 1;
	display: grid;
	grid-template-columns: repeat(5, auto);
	gap: 0.15rem;
	padding: 0.3rem;
	border: 1px solid var(--line);
	border-radius: 3px;
	/* opaque, or the toolbar behind shows through. Canvas for the same reason a select does */
	background: Canvas;
}

.icon-option,
.accent-option {
	padding: 0.25rem;
	border: 1px solid transparent;
	border-radius: 3px;
	background: none;
	font-size: 1.1rem;
	line-height: 0;
}

.icon-option:hover,
.accent-option:hover {
	border-color: var(--line);
}

.icon-option[aria-checked="true"],
.accent-option[aria-checked="true"] {
	border-color: var(--fg);
}

/*
Grids are one object at two fills.
*/
.accent-dot {
	display: block;
	width: 1.1rem;
	height: 1.1rem;
	border-radius: 3px;
	background: var(--accent-color);
}

/* trigger's own dot is the preview: what a create at this moment would use */
.accent-trigger .accent-dot {
	width: 1rem;
	height: 1rem;
}

/* the browser draws the swatch edge to edge, so the padding is what keeps the border visible */
input[type="color"] {
	padding: 0.15rem;
	cursor: pointer;
}

.panel {
	margin-bottom: 2rem;
}

.panel:last-child {
	margin-bottom: 0;
}

#pick,
#scratch {
	width: 3.5rem;
}

/* narrower than the two in the aside: it sits in a row of controls, not beside one */
#add-pick {
	width: 2.5rem;
}

.detail-adjust {
	margin: 0.5rem 0 0;
}

/*
The adjust pair keeps the left and the two triggers take the far end. The margin goes on the
first of them only: on both, the row would share the slack between the two and set them apart.
*/
.detail-adjust .icon-button:first-of-type {
	margin-left: auto;
}

/*
One grid per group.
*/
.detail-info {
	/* One start for every value on the block. The two grids are separate, so a term column
	   sized by its own content puts the models and the contrast rows at two different x.
	   Wider than the widest term, which is the sample chip. */
	--info-term: 5rem;
	border-top: 1px solid var(--line);
	padding-top: 0.55rem;
	margin: 0.7rem 0 0;
	font-size: 0.8rem;
}

/* the same pair shape as .help-values, with the faces swapped: here the value is the figure to
   read off and the term only names it */
.detail-info dl {
	display: grid;
	/* baseline, not the default stretch */
	align-items: baseline;
	gap: 0.15rem 0.5rem;
	margin: 0;
}

.detail-info dt {
	color: var(--muted);
}

.detail-info dd {
	margin: 0;
	font-family: var(--mono);
	/* halfway from --fg to --muted: a figure read off on demand, not a line the panel leads with */
	color: color-mix(in oklab, var(--fg) 50%, var(--muted));
}

.info-models {
	grid-template-columns: var(--info-term) 1fr;
}

/* term, ratio, level: the third column is what puts every verdict at one x.
   Centered rather than baseline: the term is a bordered box taller than the figures
   beside it, and a shared baseline leaves that box sitting low against them. */
.info-contrast {
	grid-template-columns: var(--info-term) auto 1fr;
	align-items: center;
}

/* right aligned, so the decimal point and the :1 line up down the column */
.info-contrast .ratio {
	text-align: right;
}

/* the four models are the color written out, the two below are a measurement of it */
.detail-info .ruled {
	border-top: 1px solid var(--line);
	padding-top: 0.45rem;
	margin-top: 0.45rem;
}

.info-head {
	display: flex;
	align-items: flex-start;
	gap: 0.4rem;
}

.info-group {
	flex: 1;
	min-width: 0;
	/* details carries 2rem under it, which is a section's gap and not a group's */
	margin: 0;
}

/* The marker is the caret, so the summary keeps its list-item display. Everything the
   panel heading rule put on it comes back off: a second heading in a 15rem column reads
   as flat against SELECTED above it. */
.info-caption {
	font-size: inherit;
	font-weight: 400;
	text-transform: none;
	letter-spacing: normal;
	color: var(--muted);
	margin: 0;
	padding-bottom: 0;
	border-bottom: none;
}

/* the same gap the caption takes from the rule above it, so it does not sit closer to the
   line than to the rows it names */
.info-group[open] .info-caption {
	margin-bottom: 0.45rem;
}

/* the same glyph at the same size as the harmony wheel's, so the two read as one control.
   The negative margin centers it on the caption line, which is shorter than the button. */
.info-help {
	flex: none;
	margin-top: -0.15rem;
}

/* The sample is the term: 'on white' never said which of the two carries the color.
   Set in the page text face rather than the mono one: the ratio measures text, and the
   mono face is self hosted at one weight, so a 600 there is the browser's fake bold. */
.sample-chip {
	display: inline-grid;
	place-items: center;
	width: 2.6rem;
	height: 1.4rem;
	border: 1px solid var(--line);
	border-radius: 2px;
	font-size: 0.85rem;
	font-weight: 600;
	line-height: 1;
}

#scratch-hex {
	font-family: var(--mono);
}

/* names the strip under it, at the weight a label carries */
.strip-name {
	display: inline-flex;
	align-items: center;
	gap: 0.3rem;
	color: var(--muted);
	font-size: 0.85rem;
}

/* a step over the type it sits in: the marks on the rim are the part that has to read */
.strip-glyph {
	font-size: 1rem;
}

/*
Rotations use 2-4 bands and can share a row.
Scales use 7 bands and take the full column.
*/
#strips {
	display: flex;
	flex-wrap: wrap;
	gap: 0.6rem 1rem;
}

.strip-block {
	flex: 1 1 100%;
}

.strip-block.rotation {
	flex-basis: 16rem;
}

/* a block is a label and a strip, so its toolbar needs less room than a section's */
.strip-block .toolbar {
	margin-bottom: 0.35rem;
}

.strip-block .icon-button,
#collection-new {
	padding: 0.1rem 0.3rem;
	font-size: 0.95rem;
	/* keep the controls muted - the colors remain the visual focus */
	color: var(--muted);
}

.strip-block .icon-button:hover {
	color: var(--fg);
}

/* a glyph alone, sized like the icon trigger */
.icon-button {
	display: flex;
	align-items: center;
	padding: 0.2rem 0.4rem;
	font-size: 1.1rem;
	line-height: 1;
}

/* muted like the two bulk words beside it, or the row gains a full-ink glyph among the pins.
   The margin is the row's own 0.5rem gap over the group's 0.1rem: the help hugs the wheel it
   annotates, and the dense toggle is a control of its own beside them */
#strips-dense {
	margin-right: 0.4rem;
	color: var(--muted);
}

#strips-dense:hover {
	color: var(--fg);
}

#saved-dense[aria-pressed="true"],
#palette-dense[aria-pressed="true"],
#strips-dense[aria-pressed="true"] {
	background: light-dark(rgba(0, 0, 0, 0.14), rgba(255, 255, 255, 0.18));
}

/*
Pinned strips use pills.
Off: outlined and muted.
On: filled and emphasized.
*/
.pill {
	display: inline-flex;
	align-items: center;
	/* the glyph is taller than the 0.85rem word, so a scale's line box takes the same height
	   and the row of pins stays even */
	--pin-height: 1.3rem;
	line-height: var(--pin-height);
	padding: 0.2rem 0.7rem;
	border-radius: 999px;
	background: light-dark(rgba(0, 0, 0, 0.02), rgba(255, 255, 255, 0.03));
	color: var(--muted);
}

.pill .icon {
	font-size: var(--pin-height);
}

/* a rotation is its glyph alone: an even pad over a square glyph, so the pill is a circle */
.pill:has(.icon) {
	padding: 0.2rem;
}

.pill:hover {
	color: var(--fg);
}

.pill[aria-pressed="true"] {
	border-color: transparent;
	background: light-dark(rgba(0, 0, 0, 0.16), rgba(255, 255, 255, 0.2));
	color: var(--fg);
}

#log p {
	margin: 0 0 0.4rem;
	font-size: 0.8rem;
	color: var(--muted);
	/* a hex or a proxy error has no spaces to break at, and the panel is 15rem */
	overflow-wrap: anywhere;
}

#log p.failed {
	color: var(--error);
}

/*
The available width fits five 9rem columns but not six.
auto-fill therefore settles on five columns at the full layout width.
*/
.grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
	gap: 0.4rem;
}

.swatch {
	min-height: 4rem;
	border: none;
}

/* one area, the color first so the text paints over it */
.cell > .swatch,
.cell > .swatch-text {
	grid-area: 1 / 1;
}

/* it covers the color, so the click falls through to it. The hex takes its own back. */
.swatch-text {
	/*
	Positioned so it paints over the color rather than beside it in DOM order.
	*/
	position: relative;
	display: flex;
	flex-direction: column;
	/* flex-end rather than an auto margin on the hex: the saved dense cell drops its label, and
	   the one child left still has to sit on the bottom edge */
	justify-content: flex-end;
	gap: 0.1rem;
	padding: 0.6rem 0.5rem 0.35rem;
	font-family: var(--mono);
	font-size: 0.75rem;
	pointer-events: none;
}

.swatch-hex {
	/* box is the hex and not the width of the cell: the color beside it still selects */
	align-self: flex-start;
	padding: 0;
	border: none;
	background: transparent;
	font: inherit;
	/* after the shorthand, which would otherwise put the cell's own size back */
	font-size: 0.85rem;
	pointer-events: auto;
}

/* the only affordance, same as .detail-hex */
.swatch-hex:hover {
	text-decoration: underline;
}

/*
Dense mode uses ten columns with no gap.
Long names are truncated; the Selected panel shows the full label.

.grid and not a bare .dense: #strips takes the same class, and it is a flex row with a gap of
its own.
*/
.grid.dense,
.shades.dense > .grid {
	grid-template-columns: repeat(10, minmax(0, 1fr));
	gap: 0;
}

/*
Open Color: one .grid per family, so a family is a row of ten under .dense and its own block
of five when the columns stack. 
*/
.shades {
	display: grid;
	gap: 0.4rem;
}

.shades.dense {
	gap: 0;
}

.dense .swatch {
	/* border-box for the same reason .detail-color needs it */
	box-sizing: border-box;
	/* a fifth shorter than wide: two lines of text want less room than a square gives */
	aspect-ratio: 5 / 4;
	min-height: 0;
	/* the shared control radius goes: four rounded corners meet as a notch of page */
	border-radius: 0;
}

.dense .swatch-text {
	padding: 0.35rem 0.4rem 0.2rem;
}

#saved.dense .label {
	display: none;
}

/* the cell is positioned already, the lift is what paints the ring over the neighbours */
.dense .cell:has(.swatch.selected) {
	z-index: 1;
}

/*
Ten to a row is a 32px square once the columns stack. Here and not in the layout query
at the top of the file: same specificity, so the rule that comes later wins.
*/
@media (max-width: 38rem) {
	.grid.dense,
	.shades.dense > .grid {
		grid-template-columns: repeat(5, minmax(0, 1fr));
	}
}

/*
A grid full screen: the palette copy, see showPaletteFullscreen. The rows share the
height rather than the cells keeping their ratio, so nothing scrolls.
*/
.grid:fullscreen {
	height: 100%;
	grid-auto-rows: 1fr;
}

.grid:fullscreen .swatch {
	aspect-ratio: auto;
	min-height: 0;
}

.grid:fullscreen .swatch-text {
	font-size: 1rem;
}

/*
Nothing acts while the grid fills the screen, the same as a band's hex. Not a target.
*/
.grid:fullscreen .swatch-hex {
	pointer-events: none;
}

/*
box-shadow, not outline: the swatch keeps its focus ring. The inner Canvas ring
separates the marker from the swatch color, so it reads on any of them.
*/
.swatch.selected {
	box-shadow:
		0 0 0 2px Canvas,
		0 0 0 4px var(--fg);
}

.swatch-text .label {
	font-family: var(--sans);
	opacity: 0.75;
	/* names and timestamps are longer than the swatch, and wrapping breaks the grid */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
Swatch, text over it and delete button are all siblings inside the cell.
Use minmax(0, 1fr) so a long label cannot expand the grid track.
*/
.cell {
	position: relative;
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

/*
Hidden until the pointer or the keyboard reaches the cell. visibility, not display, for
the same reason as the labels above: nothing reflows when it appears.
*/
.cell .remove {
	position: absolute;
	top: 0.15rem;
	right: 0.15rem;
	/* a box for the bare glyph to center in: the x on its own is a target half this wide */
	display: flex;
	align-items: center;
	justify-content: center;
	width: 1.1rem;
	height: 1.1rem;
	padding: 0;
	border: none;
	background: none;
	font-size: 0.9rem;
	line-height: 1;
	visibility: hidden;
}

.cell:hover .remove,
.cell:focus-within .remove {
	visibility: visible;
}

/* a touch screen doesn't hover, so the control has nothing to come out of */
@media (hover: none) {
	.cell .remove {
		visibility: visible;
	}
}

/*
The add control, on a palette cell and on a band.

The button is the glyph

No (hover: none) twin, unlike the delete above: focus-within is the touch path. A first tap
selects the color and leaves the button focused, which brings the plus out, and a second
adds.
*/
.cell .add,
.band .add,
.detail-stack .add {
	position: absolute;
	display: flex;
	padding: 0;
	border: none;
	background: none;
	line-height: 1;
	visibility: hidden;
}

/* a band and the Selected color have no label to stand in for, and their hex holds the bottom
   edge, so the middle is the free ground */
.band .add,
.detail-stack .add {
	top: 50%;
	left: 50%;
	translate: -50% -50%;
	font-size: 1.4rem;
}

/* panel's color is the largest surface on the page, so the glyph takes a step up with it */
.detail-stack .add {
	font-size: 1.8rem;
}

.cell .add {
	top: 0.3rem;
	left: 0.3rem;
	font-size: 1.1rem;
}

/* .cell, or this reaches every band's plus once #strips carries the class too */
.dense .cell .add {
	top: 0.2rem;
	left: 0.2rem;
}

.cell:hover .add,
.cell:focus-within .add,
.band:hover .add,
.band:focus-within .add,
.detail-stack:hover .add,
.detail-stack:focus-within .add {
	visibility: visible;
}

/* opacity and not color: labelColor writes the ink inline, per color, and an inline style
   outranks this rule */
.cell .add,
.cell .remove,
.band .add,
.detail-stack .add {
	opacity: 0.6;
}

.cell .add:hover,
.cell .add:focus-visible,
.cell .remove:hover,
.cell .remove:focus-visible,
.band .add:hover,
.band .add:focus-visible,
.detail-stack .add:hover,
.detail-stack .add:focus-visible {
	opacity: 1;
}

/* nothing acts while a strip or a grid fills the screen, the same as a band's hex */
.harmony:fullscreen .add,
.grid:fullscreen .add {
	display: none;
}

/* the color and the hex stacked as siblings, same as .cell */
.detail-stack {
	position: relative;
}

.detail-color {
	/* aspect-ratio squares the content box, so a border would make it taller than wide */
	box-sizing: border-box;
	display: block;
	width: 100%;
	aspect-ratio: 1;
	padding: 0;
	border: 1px solid var(--line);
}

/*
The UA stylesheet sizes the fullscreen element, but the square, the border and the
radius survive it: the color comes out letterboxed, hairlined and round-cornered.
*/
.detail-color:fullscreen {
	aspect-ratio: auto;
	width: 100%;
	height: 100%;
	border: none;
	border-radius: 0;
}

.detail-hex {
	position: absolute;
	left: 0.5rem;
	bottom: 0.6rem;
	padding: 0.1rem 0.25rem;
	border: none;
	background: transparent;
	font-family: var(--mono);
}

/* the only affordance: it has no chrome, and an underline reads on any background */
.detail-hex:hover {
	text-decoration: underline;
}

.detail-label {
	margin: 0.4rem 0 0;
	color: var(--muted);
	font-size: 0.8rem;
	/* a name or a timestamp is wider than the panel, and here there is room to wrap it */
	overflow-wrap: break-word;
}

/* requestFullscreen is called on the strip, so it goes at once however many bands it has */
.harmony {
	display: flex;
	gap: 0.25rem;
	width: 100%;
}

/* the same shape as .detail-stack, per band */
.harmony .band {
	position: relative;
	flex: 1;
}

.harmony .band-color {
	display: block;
	width: 100%;
	/* one height for every strip: a band's width is the column over its count, so a
   square would make a two-band strip two blocks the size of the Selected panel */
	height: 6rem;
	padding: 0;
	/* the shared control border reads as a hairline around the color, the way it did on a
	   swatch; the gap between bands is the edge */
	border: none;
}

/* full width, so the whole bottom of a band is the copy target */
.harmony .band-hex {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0.3rem;
	padding: 0;
	border: none;
	background: transparent;
	font-family: var(--mono);
	font-size: 0.85rem;
	text-align: center;
}

/* same affordance as .detail-hex */
.harmony .band-hex:hover {
	text-decoration: underline;
}

/*
Dense strips, from the one toggle in the pins row: every pinned block takes the shape at once.
The narrower rotation basis is the whole of it - three blocks to a row rather than two - plus the
gaps and the radius, the same pair a dense cell drops.

Written without the #strips id and placed above the :fullscreen rules on purpose. These match
the same elements at the same specificity, so source order is what leaves a strip its full
screen height.
*/
#strips.dense {
	gap: 0.35rem 0.6rem;
}

/* 13.16rem is the widest block toolbar, split-complement's, and a narrower basis wraps its two
   glyphs onto a second line and lifts that block's strip out of the row */
.dense .strip-block.rotation {
	flex-basis: 13.5rem;
}

.dense .strip-block .toolbar {
	margin-bottom: 0.15rem;
}

.dense .harmony {
	gap: 0;
}

/* the height is the one thing dense leaves alone: a band is already short beside its own width,
   and half of it reads as a rule rather than as a color */
.dense .harmony .band-color {
	/* the shared control radius goes, the same as a dense cell's */
	border-radius: 0;
}

/* the gap goes too: the UA paints on black, which shows through it as lines */
.harmony:fullscreen {
	height: 100%;
	gap: 0;
}

/* the radius goes with the gap, or the corners meet as notches between bands */
.harmony:fullscreen .band-color {
	height: 100%;
	border-radius: 0;
}

/*
Takes no room in <body>, see showOffstage. The UA's :fullscreen rules size the element
themselves, with !important, so this only applies while it is not up.
*/
.offstage {
	position: fixed;
	width: 0;
	height: 0;
	overflow: hidden;
}

/*
Every strip on the page is the same builder at a different width: two bands in the
main column, seven beside them, forty full screen. So the band measures itself
and drops its label when a hex would be clipped by its neighbours, rather than the
builder guessing from a count or from screen.width.
*/
.harmony .band {
	container-type: inline-size;
}

@container (max-width: 4.5rem) {
	.band-hex {
		display: none;
	}
}

/* the fullscreen label is 1.25rem, so it takes more of the band */
@container (max-width: 6rem) {
	.harmony:fullscreen .band-hex {
		display: none;
	}
}

.harmony:fullscreen .band-hex {
	bottom: 1.5rem;
	font-size: 1.25rem;
	/*
	Nothing acts while a strip fills the screen. Not a target, so a click on the hex
	reaches the color under it and leaves, and the copy tooltip has nothing to hover.
	*/
	pointer-events: none;
}

/* the answer to a press, on the button itself: a dip and back */
.pressed {
	animation: press 0.5s ease-out;
}

@keyframes press {
	from {
		opacity: 0.25;
	}
	to {
		opacity: 1;
	}
}

/*
Buttons that are a color with their hex laid over them as a sibling, see
"Buttons inside buttons".
*/
.swatch.pressed,
.harmony .band-color.pressed {
	animation-name: press-fill;
}

@keyframes press-fill {
	from {
		filter: brightness(0.7);
	}
	to {
		filter: brightness(1);
	}
}

.empty {
	/* a grid parent would otherwise squeeze the line into the first column */
	grid-column: 1 / -1;
	color: var(--muted);
	font-size: 0.85rem;
	margin: 0;
}
