/* cortex — a precision instrument with a safelight.
   Cool graphite ground, hairline edges, one amber accent that always means "live,
   marked, act". Geist carries the interface; Plex Mono carries every measured fact;
   Fraunces italic speaks only as the brand and the rare voice line. The full system,
   with mockups, lives in the "cortex" claude.ai/design project. */

@font-face {
	font-family: 'Geist';
	src: url('/static/fonts/geist-latin.woff2') format('woff2');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Fraunces';
	src: url('/static/fonts/fraunces-latin.woff2') format('woff2');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Fraunces';
	src: url('/static/fonts/fraunces-italic-latin.woff2') format('woff2');
	font-weight: 100 900;
	font-style: italic;
	font-display: swap;
}
@font-face {
	font-family: 'IBM Plex Mono';
	src: url('/static/fonts/plex-mono-latin.woff2') format('woff2');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'IBM Plex Mono';
	src: url('/static/fonts/plex-mono-500-latin.woff2') format('woff2');
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

:root {
	--void:   #0a0c10;   /* canvas wells, image mats */
	--ground: #0f1216;   /* the page */
	--panel:  #151a21;   /* cards, rails, docks */
	--raised: #1c232d;   /* controls at rest */
	--lift:   #232b36;   /* controls under the pointer */
	--line:   rgba(255, 255, 255, 0.06);
	--line-2: rgba(255, 255, 255, 0.12);
	--text:  #eaedf1;
	--dim:   #96a0ad;
	--faint: #5a6370;
	/* The safelight. canvas.js reads --accent with getComputedStyle for the engine's
	   mask wash, so it must stay a literal hex, not a var() reference. */
	--accent:    #f4a63b;
	--safe:      #f4a63b;
	--safe-hi:   #ffbd62;
	--safe-ink:  #1a1206;
	--safe-tint: rgba(244, 166, 59, 0.13);
	--safe-line: rgba(244, 166, 59, 0.40);
	/* The region colour, and cool on purpose: amber already promises "a model will
	   regenerate this", while a region only says "an adjustment stops here". canvas.js
	   reads --pick the same way it reads --accent, so this stays a literal hex too. */
	--pick:      #6fa8ff;
	--pick-hi:   #9cc6ff;
	--pick-tint: rgba(111, 168, 255, 0.13);
	--pick-line: rgba(111, 168, 255, 0.40);
	--ok:       #54c588;
	--ok-tint:  rgba(84, 197, 136, 0.12);
	--warn:     #f4a63b;
	--err:      #e4674f;
	--err-tint: rgba(228, 103, 79, 0.12);
	--font-ui:    'Geist', system-ui, -apple-system, "Segoe UI", sans-serif;
	--font-voice: 'Fraunces', Georgia, 'Times New Roman', serif;
	--font-data:  'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
	--r-1: 7px;
	--r-2: 11px;
	--r-3: 16px;
	--shadow-dock: 0 12px 32px rgba(0, 0, 0, 0.50), 0 2px 8px rgba(0, 0, 0, 0.40),
	               inset 0 1px 0 rgba(255, 255, 255, 0.05);
	--shadow-pop:  0 16px 48px rgba(0, 0, 0, 0.55), 0 4px 12px rgba(0, 0, 0, 0.40);
}

* { box-sizing: border-box; }

/* Several components set their own display, which would beat the browser's [hidden]
   rule and has bitten three times now (context menu, editor tools, picked bar). Settle
   it once for everything. */
[hidden] { display: none !important; }

body {
	margin: 0;
	background: var(--ground);
	color: var(--text);
	font: 400 13.5px/1.55 var(--font-ui);
	-webkit-font-smoothing: antialiased;
}

a { color: var(--text); text-decoration: none; }
a:hover { color: var(--safe-hi); }
:focus-visible { outline: 2px solid var(--safe); outline-offset: 2px; border-radius: 3px; }
::selection { background: var(--safe-tint); }
::placeholder { color: var(--faint); }

h1 { font-size: 19px; font-weight: 600; letter-spacing: -0.01em; margin: 0; }
h2 { font-size: 14px; font-weight: 600; margin: 0 0 0.6rem; }
/* Hierarchy is size and space; nothing on these pages goes bolder than 600. */
strong, b { font-weight: 600; }

.muted { color: var(--dim); }
code, .mono { font-family: var(--font-data); font-size: 0.93em; }
/* The voice: empty states and the one human sentence on a screen. Never on a control. */
.flavour { font-family: var(--font-voice); font-style: italic; font-weight: 420; font-size: 1.1em; }

.eyebrow {
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: 0.11em;
	text-transform: uppercase;
	color: var(--faint);
	margin: 0;
}

.tag {
	display: inline-block;
	padding: 0 0.45rem;
	border: 1px solid var(--line-2);
	border-radius: 999px;
	font-family: var(--font-data);
	font-size: 10px;
	font-style: normal;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--dim);
	line-height: 1.7;
	vertical-align: 1px;
}
.tag.err { color: var(--err); border-color: rgba(228, 103, 79, 0.4); }
.tag.warn { color: var(--warn); border-color: var(--safe-line); }

.kbd {
	display: inline-block;
	padding: 1px 5px;
	border: 1px solid var(--line-2);
	border-radius: 4px;
	font-family: var(--font-data);
	font-size: 10px;
	color: var(--faint);
	line-height: 1.5;
}

/* --- shell ---------------------------------------------------------------- */

.shell { display: grid; grid-template-columns: 228px minmax(0, 1fr); min-height: 100vh; }
.shell.bare { grid-template-columns: 1fr; }

.rail {
	display: flex;
	flex-direction: column;
	gap: 1.4rem;
	padding: 1.15rem 0.8rem 0.9rem;
	background: var(--panel);
	border-right: 1px solid var(--line);
	position: sticky;
	top: 0;
	height: 100vh;
	overflow-y: auto;
}

.brand {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	padding: 0.1rem 0.65rem;
	font-family: var(--font-voice);
	font-style: italic;
	font-size: 1.35rem;
	font-weight: 500;
	letter-spacing: 0.01em;
	color: var(--text);
}
.brand:hover { color: var(--text); }

.brand-mark {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--safe);
	box-shadow: 0 0 10px 2px var(--safe-tint), 0 0 2px var(--safe);
	flex: none;
}

.rail nav { display: flex; flex-direction: column; gap: 1.2rem; flex: 1; }
.nav-group { display: flex; flex-direction: column; gap: 1px; }
.nav-eyebrow {
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--faint);
	padding: 0 0.65rem 0.4rem;
}

.nav-row { display: flex; align-items: center; }
.nav-row .nav-item { flex: 1; min-width: 0; }

/* Words, not boxes: hover only brightens, and the section you are in carries the
   safelight at its side. */
.nav-item {
	position: relative;
	display: flex;
	align-items: center;
	gap: 0.65rem;
	padding: 0.32rem 0.65rem;
	color: var(--dim);
	font-size: 13px;
	font-weight: 500;
}
.nav-item::before {
	content: '';
	position: absolute;
	left: 0;
	top: 5px;
	bottom: 5px;
	width: 2px;
	border-radius: 999px;
	background: transparent;
}
.nav-item:hover { color: var(--text); }
.nav-item.on { color: var(--text); }
.nav-item.on::before { background: var(--safe); box-shadow: 0 0 8px 1px var(--safe-tint); }
.nav-badge {
	margin-left: auto;
	font-family: var(--font-data);
	font-style: normal;
	font-size: 10px;
	color: var(--faint);
	border: 1px solid var(--line-2);
	border-radius: 999px;
	padding: 0 0.38rem;
	line-height: 1.6;
}

/* An act where a badge would sit: out of the way until the row is under the pointer or
   it is tabbed to, amber the moment it is either. */
.nav-new {
	flex: none;
	padding: 0 0.5rem;
	font-size: 15px;
	line-height: 1.2;
	color: var(--faint);
	opacity: 0;
}
.nav-row:hover .nav-new, .nav-new:focus-visible { opacity: 1; }
.nav-new:hover, .nav-new:focus-visible { color: var(--safe); }

.space-switch { margin: 0 0 -0.7rem; }
.space-switch select { padding: 0.3rem 0.45rem; font-size: 12px; }

.rail-foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
	padding: 0.65rem 0.65rem 0;
	border-top: 1px solid var(--line);
}
.rail-user { font-size: 12px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; }
.logout { margin: 0; }
.logout button {
	background: none;
	border: none;
	color: var(--faint);
	font: inherit;
	font-size: 12px;
	cursor: pointer;
	padding: 0;
}
.logout button:hover { color: var(--text); }

main { padding: 1.8rem 2.2rem 4rem; max-width: 1280px; width: 100%; margin: 0 auto; }
/* The two editors are instruments, not documents: they take the whole main column,
   with the rail beside them and none of the reading measure a page wants. */
main:has(.editor-shell), main:has(.studio-shell) { padding: 0; max-width: none; }

/* --- page furniture ------------------------------------------------------- */

.page-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem 0.8rem; margin-bottom: 1.4rem; }
.page-sub { margin: 0; color: var(--dim); font-size: 13px; max-width: 68ch; }
.page-sub .tag { margin-left: 0.2rem; }

.crumbs { flex-basis: 100%; font-size: 12px; color: var(--dim); margin-bottom: 0.2rem; }
.crumbs a { color: var(--dim); }
.crumbs a:hover { color: var(--text); }
.crumbs span { margin: 0 0.15rem; color: var(--faint); }

.section-head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 0.75rem;
	margin: 1.9rem 0 0.85rem;
}
.section-head:first-child { margin-top: 0; }
.section-head .count { font-family: var(--font-data); font-size: 11px; color: var(--faint); }
.section-head .rule { flex: 1; border-top: 1px solid var(--line); align-self: center; }
.section-head .gallery-filter { margin-left: 0; }

.notice {
	padding: 0.8rem 1rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	color: var(--dim);
}
.notice a { color: var(--safe); }

.error {
	padding: 0.6rem 0.75rem;
	background: var(--err-tint);
	border: 1px solid rgba(228, 103, 79, 0.35);
	border-radius: var(--r-1);
	color: var(--err);
	font-size: 12.5px;
}

.aside-note {
	margin-top: 2.2rem;
	padding: 1rem 1.2rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-left: 2px solid var(--safe);
	border-radius: var(--r-2);
	max-width: 72ch;
}
.aside-note p { margin: 0.5rem 0 0; color: var(--dim); font-size: 12.5px; }

.card-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	gap: 0.8rem;
}
.card {
	padding: 1rem 1.2rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.card.wide { max-width: 72ch; }
.card h2 { font-size: 14.5px; font-weight: 600; }
.card-body { margin: 0.55rem 0 0; color: var(--dim); font-size: 13px; }
.card-body .mono { color: var(--text); }

/* --- forms and buttons ---------------------------------------------------- */

label {
	display: block;
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 0.03em;
	color: var(--dim);
	margin: 0 0 0.3rem;
}

input, select, textarea {
	width: 100%;
	padding: 0.5rem 0.7rem;
	background: var(--void);
	color: var(--text);
	border: 1px solid var(--line-2);
	border-radius: var(--r-1);
	font: inherit;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--safe-line); }
input[type="number"] { font-family: var(--font-data); font-size: 12.5px; }

/* The travelled part of the track wears the safelight. --fill is set from the value by
   canvas.js; a slider nobody drives keeps the plain track. */
input[type="range"] {
	-webkit-appearance: none;
	appearance: none;
	height: 4px;
	padding: 0;
	border: none;
	border-radius: 999px;
	background: linear-gradient(var(--safe), var(--safe)) var(--fill-at, 0%) 0 / var(--fill, 0%) 100% no-repeat,
	            var(--raised);
	cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
	-webkit-appearance: none;
	width: 13px;
	height: 13px;
	border-radius: 50%;
	background: var(--text);
	border: none;
	box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
input[type="range"]::-moz-range-thumb {
	width: 13px;
	height: 13px;
	border-radius: 50%;
	background: var(--text);
	border: none;
	box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
input[type="range"]:focus-visible { outline: 2px solid var(--safe); outline-offset: 4px; }

button { font-family: inherit; }
/* The one lit control on a screen: the action that spends GPU or commits work. */
button.primary, a.button.primary {
	display: inline-flex;
	align-items: center;
	gap: 0.45rem;
	padding: 0.5rem 1rem;
	background: var(--safe);
	color: var(--safe-ink);
	border: none;
	border-radius: var(--r-1);
	font: inherit;
	font-size: 13px;
	font-weight: 600;
	cursor: pointer;
	white-space: nowrap;
}
button.primary:hover, a.button.primary:hover { background: var(--safe-hi); color: var(--safe-ink); }
button.primary:disabled { background: var(--raised); color: var(--faint); cursor: not-allowed; }
button.primary .kbd { background: rgba(26, 18, 6, 0.14); color: rgba(26, 18, 6, 0.75); border: none; }

button.secondary, a.button.secondary {
	display: inline-flex;
	align-items: center;
	padding: 0.46rem 0.85rem;
	background: transparent;
	color: var(--text);
	border: 1px solid var(--line-2);
	border-radius: var(--r-1);
	font: inherit;
	font-size: 13px;
	font-weight: 500;
	cursor: pointer;
	white-space: nowrap;
}
button.secondary:hover, a.button.secondary:hover { border-color: var(--safe-line); color: var(--text); }
/* Hold, while a Generate or a Hold is still being sent. */
button.secondary:disabled { opacity: 0.4; cursor: not-allowed; }
button.secondary:disabled:hover { border-color: var(--line-2); }

button.danger {
	padding: 0.42rem 0.85rem;
	font-size: 12.5px;
	font-weight: 500;
	background: transparent;
	color: var(--err);
	border: 1px solid rgba(228, 103, 79, 0.35);
	border-radius: var(--r-1);
	cursor: pointer;
}
button.danger:hover { background: var(--err-tint); }

button.link {
	background: none;
	border: none;
	color: var(--dim);
	font: inherit;
	cursor: pointer;
	padding: 0;
}

/* --- login ---------------------------------------------------------------- */

.login {
	max-width: 340px;
	margin: 16vh auto 0;
	padding: 1.8rem 1.7rem 1.5rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-3);
}
.login h1 {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	font-family: var(--font-voice);
	font-style: italic;
	font-size: 1.5rem;
	font-weight: 500;
}
.login .sub {
	margin: 0.25rem 0 1.5rem;
	font-family: var(--font-voice);
	font-style: italic;
	color: var(--dim);
	font-size: 13px;
}
.login label { margin-top: 0.9rem; }
.login button.primary { width: 100%; margin-top: 1.25rem; justify-content: center; }
.login .error { margin: 0 0 1rem; }
.login .notice { margin: 0 0 1rem; font-size: 12.5px; }
.login-links {
	display: flex;
	justify-content: space-between;
	gap: 0.75rem;
	margin: 1.1rem 0 0;
	font-size: 12px;
}
.login-links a { color: var(--dim); }
.login-links a:hover { color: var(--text); }

/* Inputs are 100% wide, and a password box the width of a page reads as a mistake. */
.account-form { max-width: 320px; }
.account-form label { margin-top: 0.9rem; }
.account-form button { margin-top: 1.1rem; }

/* --- a file shared in from another app ----------------------------------- */

.share-shot {
	display: block;
	max-width: 100%;
	max-height: 60vh;
	background: var(--void);
	border-radius: var(--r-1);
}
.share progress { width: 100%; max-width: 320px; }
.share-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 1rem; }
.share-acts form { margin: 0; }

/* --- the second factor ---------------------------------------------------- */

.enrol {
	display: flex;
	flex-wrap: wrap;
	gap: 1.4rem;
	margin-top: 1rem;
	align-items: flex-start;
}
/* The QR carries its own quiet zone and light ground: a scanner wants dark on light,
   whatever the page behind it is doing. */
.enrol .qr {
	width: 190px;
	flex: none;
	border-radius: var(--r-2);
	overflow: hidden;
	line-height: 0;
}
.enrol .qr svg { width: 100%; height: auto; }
.enrol-side { flex: 1; min-width: 260px; }
.enrol-side .footnote { margin: 0; }
.secret {
	max-width: 320px;
	margin: 0.35rem 0 0;
	padding: 0.5rem 0.65rem;
	background: var(--raised);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	color: var(--text);
	font-size: 12.5px;
	word-spacing: 0.15em;
	/* An API token is one unbroken word, wider than the box. */
	overflow-wrap: anywhere;
	user-select: all;
}

/* Shown once, and read off the screen onto paper, so they are set wide apart. */
.codes {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
	gap: 0.4rem 1rem;
	margin: 0.9rem 0 0;
	padding: 0.9rem 1rem;
	list-style: none;
	background: var(--raised);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	font-size: 13px;
	letter-spacing: 0.04em;
	user-select: all;
}

/* One <form> per action in a table cell, kept on a single line at the table's scale. */
.row-acts { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.row-acts form { margin: 0; }
.row-acts button { padding: 0.25rem 0.6rem; font-size: 12px; }

/* --- compose: the composer card and the model tray ------------------------- */

/* The prompt is the instrument's mouthpiece: one panel, the textarea borderless
   inside it, facts as chips on the foot, the safelight on Generate. */
.composer {
	flex: 1 1 auto;
	min-width: 0;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-3);
}
.composer:focus-within { border-color: var(--line-2); }
.composer-row { display: flex; gap: 0.75rem; align-items: stretch; margin-bottom: 1.4rem; }
.composer-body { padding: 0.6rem 0.9rem 0; min-width: 0; }
#compose-msg .error { margin: 0.8rem 0.9rem 0; }

/* The picture the prompt is about, square against the composer's own height: the
   chosen workflow's cover, a print picked to reprompt, or the one developing now. */
.composer-holder {
	position: relative;
	flex: none;
	align-self: stretch;
	width: 260px;
	aspect-ratio: 1;
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	background: var(--void);
	overflow: hidden;
}
.composer-holder img { width: 100%; height: 100%; object-fit: cover; display: block; }
.composer-holder > a:not(.holder-x), .holder-frame { display: block; width: 100%; height: 100%; }
.holder-frame { position: relative; }
.holder-frame .dev-blank { position: absolute; inset: 0; }
.composer-holder .holder-void { display: none; }
.composer-holder.empty {
	border-style: dashed;
	display: grid;
	place-items: center;
	text-align: center;
}
.composer-holder.empty .holder-void { display: block; padding: 0 1rem; }
.composer-holder.empty .holder-void .ic {
	width: 22px;
	height: 22px;
	color: var(--faint);
	display: block;
	margin: 0 auto 0.5rem;
}
.composer-holder.empty span {
	font-family: var(--font-data);
	font-size: 10px;
	line-height: 1.5;
	color: var(--faint);
}
/* A workflow's cover is an illustration of what the card does, not the picture being
   worked on: it sits back so a real print in the same well reads as the real one. */
.composer-holder.cover img { opacity: 0.75; }
/* While the print develops the safelight is on the holder itself. */
.composer-holder.developing {
	border-color: var(--safe-line);
	box-shadow: 0 0 24px -6px var(--safe-tint), 0 0 0 1px var(--safe-tint);
}
.holder-tag {
	position: absolute;
	inset: auto 0 0 0;
	padding: 1.1rem 0.6rem 0.4rem;
	font-family: var(--font-data);
	font-size: 10px;
	color: #f2f4f7;
	background: linear-gradient(transparent, rgba(6, 8, 11, 0.88));
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.holder-tag .dev-clock { margin-left: 0.3rem; }
.holder-x {
	position: absolute;
	top: 5px;
	right: 5px;
	width: 20px;
	height: 20px;
	display: grid;
	place-items: center;
	border: none;
	border-radius: 6px;
	background: rgba(10, 12, 16, 0.6);
	color: rgba(242, 244, 247, 0.8);
	font-size: 13px;
	line-height: 1;
	cursor: pointer;
	backdrop-filter: blur(4px);
}
.holder-x:hover { color: var(--err); }

/* Taller by default and draggable further: a prompt for the 20B models runs to 150
   words. The dragged height is remembered per field in app.js. */
#compose-fields textarea, #edit-fields textarea {
	resize: vertical;
	min-height: 6.4rem;
	max-height: 60vh;
	font-size: 14.5px;
	line-height: 1.6;
}
/* Inside the composer the panel is the box, so the textarea wears no second one. */
.composer .prompt-field textarea {
	background: transparent;
	border: none;
	padding: 0.6rem 0.4rem 0.2rem;
}
/* The prompt is this page's main box, so it opens taller than the edit page's. */
#compose-fields .prompt-field textarea { min-height: 9.5rem; }
.composer .prompt-field textarea:focus { border: none; }
/* Only the lead prompt goes label-less; a second prompt field (a negative, a
   suffix) keeps its name, or the two are indistinguishable boxes. */
.composer-body > .prompt-field:first-child > label {
	position: absolute;
	left: -9999px;
}
#compose-fields .req, #edit-fields .req { color: var(--safe); }

/* Enhance and Describe: actions done to the draft, quiet chips under the box. */
.prompt-field { position: relative; }
.prompt-row { display: flex; flex-direction: column; gap: 0.3rem; }
.prompt-row textarea { flex: 1; min-width: 0; }
.prompt-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
.tool-button {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	margin: 0;
	padding: 0.4rem 0.7rem;
	background: var(--raised);
	color: var(--dim);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	font: inherit;
	font-size: 12px;
	font-weight: 500;
	text-align: left;
	cursor: pointer;
}
.tool-button:hover { border-color: var(--line-2); color: var(--text); }
.tool-button.on { border-color: var(--safe-line); background: var(--safe-tint); color: var(--safe); }
.tool-button:disabled { opacity: 0.4; cursor: default; }
.tool-button:disabled:hover { border-color: var(--line); color: var(--dim); }
.tool-button .ic { width: 13px; height: 13px; flex: none; color: var(--safe); opacity: 0.8; }
.composer .tool-button { background: transparent; border-color: transparent; }
.composer .tool-button:hover { background: var(--raised); color: var(--text); }
.tool-button.htmx-request {
	color: var(--safe);
	border-color: var(--safe-line);
}
.tool-button.htmx-request::after {
	content: '…';
	animation: enhance-wait 1.2s steps(3, end) infinite;
}
.prompt-field.htmx-request textarea { opacity: 0.5; }
@keyframes enhance-wait { 0% { opacity: 0.3; } 50% { opacity: 1; } }

.enhance-note {
	margin: 0.35rem 0 0;
	font-size: 11.5px;
	color: var(--faint);
	font-style: italic;
}
/* Appears only after a rewrite actually changed the box; one step back per press. */
.prompt-undo {
	margin-top: 0.3rem;
	padding: 0;
	font-size: 11.5px;
	color: var(--dim);
}
.prompt-undo:hover { color: var(--safe); }
.file-tool input[type="file"] { display: none; }
/* Off screen rather than display:none: a label still has to be able to open it, and
   it has to keep posting the file the person chose. */
.reference-file {
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}
/* The attached reference, beside the tool that took it rather than in the well the
   print develops in. */
.ref-chip {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	max-width: 24rem;
	padding: 0.2rem 0.25rem;
	border: 1px solid var(--safe-line);
	border-radius: var(--r-1);
	background: var(--safe-tint);
	font-size: 11.5px;
	color: var(--text);
}
.ref-chip img {
	width: 22px;
	height: 22px;
	border-radius: 4px;
	object-fit: cover;
	display: block;
}
.ref-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A card that takes no picture: the chip stays, and says so, rather than the picture
   vanishing without a word. */
.ref-chip.unused { border-color: var(--line-2); background: var(--raised); }
.ref-chip.unused img { opacity: 0.5; }
.ref-note { color: var(--warn); }
.ref-x {
	padding: 0 0.2rem;
	background: none;
	border: none;
	color: var(--dim);
	font-size: 13px;
	line-height: 1;
	cursor: pointer;
}
.ref-x:hover { color: var(--err); }
.read-from { display: flex; align-items: center; gap: 0.5rem; }
.read-thumb {
	width: 34px;
	height: 34px;
	border-radius: 4px;
	object-fit: cover;
	border: 1px solid var(--line-2);
}

.fields-rack {
	display: flex;
	flex-wrap: wrap;
	gap: 0.8rem 1rem;
	margin-top: 0.85rem;
	align-items: flex-start;
}
.fields-rack .field.num { width: 96px; position: relative; }
.fields-rack .field.num label {
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.fields-rack input[type="number"] { padding: 0.34rem 0.5rem; }
.fields-rack .hint { display: none; }
.fields-rack .field.num:focus-within .hint {
	display: block;
	position: absolute;
	margin-top: 0.2rem;
	font-size: 11px;
	color: var(--faint);
	white-space: nowrap;
}

/* The composer's foot: every setting the run carries as a fact chip, then the two
   actions. Each chip opens the real inputs behind it, so nothing here is a display of
   a value held somewhere else. */
.composer-foot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem;
	margin-top: 0.6rem;
	padding: 0.6rem 0.7rem;
	border-top: 1px solid var(--line);
	/* Its own corners, because the composer cannot clip: the popovers open upwards
	   out of this row and a clipping panel would cut them off. */
	border-radius: 0 0 var(--r-3) var(--r-3);
	background: rgba(255, 255, 255, 0.015);
}
.composer-foot .end { margin-left: auto; display: flex; gap: 0.5rem; align-items: center; }

.chip {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.32rem 0.7rem;
	background: var(--raised);
	border: 1px solid var(--line);
	border-radius: 999px;
	font: inherit;
	font-size: 12px;
	font-weight: 500;
	color: var(--dim);
	cursor: pointer;
	white-space: nowrap;
}
.chip:hover { border-color: var(--line-2); color: var(--text); }
.chip.on { background: var(--safe-tint); border-color: var(--safe-line); color: var(--safe); }
.chip .pace { font-family: var(--font-data); font-size: 10.5px; font-weight: 400; color: var(--faint); }
.chip.on .pace { color: var(--safe); opacity: 0.75; }
.chip .x { color: var(--faint); font-size: 13px; line-height: 1; }
.chip:hover .x { color: var(--err); }
/* The tune chip is nothing but its numbers, so it says them and gets out of the way. */
.chip.quiet { background: transparent; border-color: var(--line); }

.chip-slot { position: relative; display: inline-flex; }
/* The panel a chip opens: floating, so it wears a real shadow. */
.composer-foot .popover {
	position: absolute;
	left: 0;
	bottom: calc(100% + 8px);
	z-index: 30;
	min-width: 232px;
	padding: 0.75rem;
	background: var(--panel);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-pop);
}
.popover-rack { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
.popover .field label {
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.popover .field input { padding: 0.34rem 0.5rem; }
.popover .field .hint { display: block; margin-top: 0.25rem; font-size: 10.5px; color: var(--faint); }

/* The fan-out, in the foot with the rest of the run's facts. */
.composer-foot .also-run {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	margin: 0;
	padding: 0;
	border: none;
}
.composer-foot .also-choice { color: var(--text); }
.composer-foot .also-choice:hover { border-color: rgba(228, 103, 79, 0.5); }
.composer-foot .also-pick {
	right: 0;
	left: auto;
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
	max-width: 380px;
}

/* The tray: a shelf of cards, each wearing prints it made itself. The workflow strip
   rests open at the top of the page; the model tray opens from the composer's chip. */
.tray {
	margin-bottom: 1.4rem;
	padding: 0.9rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-3);
}
.shelf + .shelf { margin-top: 1.1rem; }
.shelf-head {
	display: flex;
	align-items: baseline;
	gap: 0.5rem;
	margin: 0 0 0.5rem;
}
.shelf-label {
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: 0.11em;
	text-transform: uppercase;
	color: var(--faint);
}
.shelf-blurb { font-size: 11.5px; color: var(--faint); }
.shelf-count {
	margin-left: auto;
	font-family: var(--font-data);
	font-size: 10.5px;
	color: var(--faint);
}
.shelf-cards {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
	gap: 0.55rem;
}
.model-card {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 2px;
	margin: 0;
	padding: 5px 5px 7px;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	font-size: inherit;
	color: var(--dim);
	cursor: pointer;
	transition: border-color 0.12s ease;
}
.model-card:hover { border-color: var(--line-2); }
.model-card input { position: absolute; opacity: 0; pointer-events: none; }
.model-card:has(input:checked) {
	border-color: var(--safe-line);
	box-shadow: 0 0 0 1px var(--safe-tint), 0 0 24px -8px var(--safe-tint);
}
.model-card:has(input:focus-visible) { outline: 2px solid var(--safe); outline-offset: 2px; }
.model-card.off { opacity: 0.45; cursor: not-allowed; }

.model-shots {
	position: relative;
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 2px;
	border-radius: calc(var(--r-2) - 4px);
	overflow: hidden;
}
/* Landscape crops rather than squares: four prints still read as "what does this model
   look like" at two thirds the height. */
.model-shots img {
	width: 100%;
	aspect-ratio: 3 / 2;
	object-fit: cover;
	display: block;
	background: var(--panel);
}
.model-shots.bare { display: block; }
.model-shots-empty {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 1.5;
	font-size: 11.5px;
	color: var(--faint);
	border: 1px dashed var(--line-2);
	border-radius: calc(var(--r-2) - 4px);
	background: repeating-linear-gradient(
		135deg, transparent 0 7px, rgba(244, 166, 59, 0.05) 7px 14px
	);
}

.model-name {
	padding: 3px 4px 0;
	font-size: 12.5px;
	font-weight: 600;
	line-height: 1.3;
	color: var(--text);
}
/* One line, and the whole of it in the card's hover. */
.model-blurb {
	padding: 0 4px;
	font-size: 11px;
	line-height: 1.4;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.model-card:has(input:checked) .model-blurb { color: var(--text); }
/* A badge on the prints rather than a line of its own. */
.model-pace:not(:empty) {
	position: absolute;
	right: 4px;
	top: 4px;
	padding: 1px 6px;
	border-radius: 4px;
	background: rgba(10, 12, 16, 0.78);
	font-family: var(--font-data);
	font-size: 10px;
	color: var(--dim);
}
.model-card:has(input:checked) .model-pace { color: var(--safe); }

/* --- create: the cards light from what is attached -------------------------- */
/*
 * The tool comes first and the workflows light up from it. With nothing attached the
 * create page is what it has always been, words in; attach a picture and the cards
 * that work from one come to the front of the strip while the words-only ones step
 * back. Nothing is disabled: a dim card can still be chosen, and it says why it is dim.
 */
.card-why {
	display: none;
	padding: 2px 4px 0;
	font-family: var(--font-data);
	font-size: 10px;
	line-height: 1.4;
	color: var(--warn);
}
.composing .flow[data-takes-picture], .composing .flow[data-needs-mask] { display: none; }
.composing.has-reference .flow[data-takes-picture]:not([data-needs-mask]) {
	display: flex;
	order: -1;
}
.composing.has-reference .flow:not([data-takes-picture]) { opacity: 0.5; }
.composing.has-reference .flow:not([data-takes-picture]) .card-why { display: block; }
/* The shelf keeps every card whatever is attached: what exists is not a function of
   what you have picked up, and a card that has gone says nothing about why. */
.composing .model-card[data-needs-mask],
.composing:not(.has-reference) .model-card[data-takes-picture],
.composing.has-reference .model-card:not([data-takes-picture]) { opacity: 0.5; }
.composing .model-card[data-needs-mask] .card-why,
.composing:not(.has-reference) .model-card[data-takes-picture] .card-why,
.composing.has-reference .model-card:not([data-takes-picture]) .card-why { display: block; }
.model-card .card-why { padding: 0 2px; }

/* The workflow strip: model and verb as one card, most used first, the usage bar
   saying how much of that "most" this one is. */
.flow-strip { display: flex; gap: 0.5rem; overflow-x: auto; padding-bottom: 0.25rem; }
.flow {
	flex: none;
	width: 124px;
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 5px 5px 7px;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	color: var(--dim);
	cursor: pointer;
	text-align: left;
}
.flow:hover { border-color: var(--line-2); }
.flow.on { border-color: var(--safe-line); box-shadow: 0 0 0 1px var(--safe-tint); }
.flow.off { opacity: 0.45; cursor: not-allowed; }
.flow-shot { position: relative; border-radius: calc(var(--r-2) - 4px); overflow: hidden; }
.flow-shot img, .flow-plate {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	background: var(--panel);
}
.flow-rank {
	position: absolute;
	left: 4px;
	top: 4px;
	padding: 0 5px;
	border-radius: 4px;
	background: rgba(10, 12, 16, 0.78);
	font-family: var(--font-data);
	font-size: 10px;
	color: var(--dim);
}
.flow.on .flow-rank { color: var(--safe); }
/* Two lines at most: a workflow whose whole title is its name would otherwise make
   its card twice the height of the ones beside it. */
.flow-verb {
	padding: 3px 4px 0;
	font-size: 12px;
	font-weight: 600;
	line-height: 1.25;
	color: var(--text);
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.flow-model {
	padding: 0 4px;
	font-size: 10.5px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* Pushed to the foot so the bars line up across the strip whatever the titles do. */
.flow-use { display: flex; align-items: center; gap: 0.4rem; margin-top: auto; padding: 6px 4px 0; }
.flow-bar { flex: 1; height: 3px; border-radius: 999px; background: var(--raised); overflow: hidden; }
.flow-bar span { display: block; height: 100%; border-radius: 999px; background: var(--faint); }
.flow.on .flow-bar span { background: var(--safe); }
.flow-runs { font-family: var(--font-data); font-size: 10px; color: var(--faint); font-variant-numeric: tabular-nums; }

/* The fan-out row. Deliberately quiet and last: the common case is one workflow. */
.also-run {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 1rem;
	margin-top: 1rem;
	padding-top: 0.85rem;
	border-top: 1px solid var(--line);
}
.also-label {
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
}
/* A picked card is a chip; clicking it is clicking its own checkbox, which unpicks it. */
.also-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.also-choice {
	display: none;
	align-items: center;
	gap: 0.45rem;
	padding: 0.32rem 0.7rem;
	font-size: 12px;
	font-weight: 500;
	color: var(--text);
	background: var(--raised);
	border: 1px solid var(--line);
	border-radius: 999px;
	cursor: pointer;
}
.also-choice:has(input:checked) { display: inline-flex; }
.also-choice { position: relative; }
.also-choice input { position: absolute; opacity: 0; pointer-events: none; }
.also-choice:hover { border-color: rgba(228, 103, 79, 0.5); }
.also-choice .chip-x { color: var(--faint); }
.also-choice:hover .chip-x { color: var(--err); }
.also-add { font-size: 12px; }
.also-pick {
	flex-basis: 100%;
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
	padding: 0.6rem;
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	background: var(--void);
}
.also-option {
	display: inline-flex;
	align-items: center;
	gap: 0.45rem;
	padding: 0.32rem 0.7rem;
	font-size: 12px;
	color: var(--dim);
	background: transparent;
	border: 1px solid var(--line);
	border-radius: 999px;
	cursor: pointer;
}
.also-option:hover { border-color: var(--safe-line); color: var(--text); }
.also-pace { font-family: var(--font-data); font-size: 10.5px; color: var(--faint); }
.also-hint { flex-basis: 100%; margin: 0.2rem 0 0; font-size: 11px; color: var(--faint); }

/* The attached drawing. Sized so a sketch reads at a glance beside the prompt. */
.upload {
	display: flex;
	gap: 0.85rem;
	align-items: center;
	padding: 0.6rem;
	background: var(--void);
	border: 1px solid var(--line-2);
	border-radius: var(--r-1);
}
.upload-thumb, .upload-empty {
	width: 76px;
	height: 76px;
	flex: none;
	border-radius: 5px;
	background: var(--panel);
	object-fit: contain;
}
.upload-empty {
	border: 1px dashed var(--line-2);
	background: repeating-linear-gradient(
		135deg, transparent 0 7px, rgba(244, 166, 59, 0.05) 7px 14px
	);
}
.upload-body { min-width: 0; flex: 1; }
.upload input[type="file"] {
	padding: 0.35rem;
	font-size: 12.5px;
	color: var(--dim);
	border-color: var(--line);
}
.upload input[type="file"]::file-selector-button {
	margin-right: 0.6rem;
	padding: 0.25rem 0.7rem;
	background: var(--raised);
	color: var(--text);
	border: 1px solid var(--line-2);
	border-radius: 5px;
	font: inherit;
	font-size: 12px;
	cursor: pointer;
}
.upload input[type="file"]::file-selector-button:hover { border-color: var(--safe-line); }
.upload .hint { display: block; margin-top: 0.35rem; font-size: 11.5px; color: var(--faint); }
.upload .hint .mono { color: var(--dim); }
.upload.compact { padding: 0.4rem; max-width: 520px; }
.upload.compact .upload-thumb { width: 48px; height: 48px; }

/* The attachments row: what a run has attached to it, on the pages where a picture is
   an input to a sentence rather than the subject. A required one is here from the
   start; an optional one arrives only when the picker asks for it. */
.attachments {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem;
	margin: 0.15rem 0 0.6rem;
}
.attach {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	flex: 0 1 460px;
	padding: 0.4rem;
	background: var(--void);
	border: 1px solid var(--line-2);
	border-radius: var(--r-1);
}
.attach-thumb {
	width: 48px;
	height: 48px;
	flex: none;
	border-radius: 5px;
	background: var(--panel);
	object-fit: contain;
}
.attach-thumb.empty {
	border: 1px dashed var(--line-2);
	background: repeating-linear-gradient(
		135deg, transparent 0 7px, rgba(244, 166, 59, 0.05) 7px 14px
	);
}
.attach-body { min-width: 0; flex: 1; }
.attach-label {
	display: block;
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
}
.attach input[type="file"] {
	margin-top: 0.2rem;
	padding: 0.25rem;
	font-size: 12px;
	color: var(--dim);
	border-color: var(--line);
}
.attach input[type="file"]::file-selector-button {
	margin-right: 0.5rem;
	padding: 0.2rem 0.6rem;
	background: var(--raised);
	color: var(--text);
	border: 1px solid var(--line-2);
	border-radius: 5px;
	font: inherit;
	font-size: 11.5px;
	cursor: pointer;
}
.attach input[type="file"]::file-selector-button:hover { border-color: var(--safe-line); }
.attach .hint { display: block; margin-top: 0.25rem; font-size: 10.5px; color: var(--faint); }
.attach .hint .mono { color: var(--dim); }
.attach-x {
	flex: none;
	align-self: flex-start;
	padding: 0 0.25rem;
	background: none;
	border: none;
	color: var(--faint);
	font-size: 15px;
	line-height: 1;
	cursor: pointer;
}
.attach-x:hover { color: var(--err); }

.attach-picker { position: relative; display: inline-flex; }
.attach-pick {
	position: absolute;
	left: 0;
	top: calc(100% + 6px);
	z-index: 30;
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	min-width: 240px;
	padding: 0.5rem;
	background: var(--panel);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-pop);
}
.attach-option {
	display: flex;
	flex-direction: column;
	gap: 0.1rem;
	padding: 0.4rem 0.55rem;
	background: none;
	border: 1px solid transparent;
	border-radius: 5px;
	color: var(--dim);
	text-align: left;
	cursor: pointer;
}
.attach-option:hover { border-color: var(--safe-line); color: var(--text); }
.attach-hint { font-size: 10.5px; color: var(--faint); }

/* --- picking a picture out of the gallery --------------------------------- */

/* The second door beside the file control, and quieter than it: the file dialog is
   still the ordinary way in, this is the shortcut for work already here. */
.pick-from {
	display: inline-block;
	margin-top: 0.3rem;
	padding: 0;
	background: none;
	border: none;
	color: var(--dim);
	font: inherit;
	font-size: 11.5px;
	text-decoration: underline;
	text-underline-offset: 2px;
	cursor: pointer;
}
.pick-from:hover { color: var(--safe-hi); }

.pick-gallery {
	position: fixed;
	inset: 0;
	z-index: 60;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 2rem 1rem;
	background: rgba(6, 8, 11, 0.72);
}
.pick-panel {
	display: flex;
	flex-direction: column;
	gap: 0.7rem;
	width: min(860px, 100%);
	max-height: 100%;
	padding: 0.9rem;
	background: var(--panel);
	border: 1px solid var(--line-2);
	border-radius: var(--r-3);
	box-shadow: var(--shadow-pop);
}
.pick-bar { display: flex; align-items: center; gap: 0.7rem; }
.pick-search {
	width: 220px;
	margin-left: auto;
	padding: 0.4rem 0.7rem;
	font-size: 12.5px;
}
.pick-close {
	padding: 0 0.35rem;
	background: none;
	border: none;
	color: var(--faint);
	font-size: 18px;
	line-height: 1;
	cursor: pointer;
}
.pick-close:hover { color: var(--text); }
.pick-count { margin: 0; font-size: 11.5px; color: var(--faint); }
.pick-error { margin: 0; }

/* The panel stops at the viewport and the sheet scrolls inside it, so Show older
   extends the grid rather than walking the page behind it down. */
.pick-results { min-height: 0; overflow-y: auto; }
.pick-sheet {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	overflow: hidden;
	background: var(--void);
}
.pick-tile {
	aspect-ratio: 1;
	padding: 0;
	background: none;
	border: none;
	cursor: pointer;
}
.pick-tile img { display: block; width: 100%; height: 100%; object-fit: cover; }
.pick-tile:hover { box-shadow: inset 0 0 0 2px var(--safe); }
.pick-tile:disabled { opacity: 0.5; cursor: progress; }
.pick-more {
	grid-column: 1 / -1;
	display: flex;
	justify-content: center;
	padding: 0.7rem;
}

/* --- the darkroom strip --------------------------------------------------- */

#darkroom:not(:empty) { display: block; }

/* How long the next generation will make you wait. One line, always present. */
.warmth {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 0.5rem;
	margin: 0.1rem 0 1.5rem;
	font-size: 12.5px;
	color: var(--dim);
}
.warmth .warmth-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--faint);
	align-self: center;
}
.warmth.warm .warmth-dot, .warmth.working .warmth-dot {
	background: var(--safe);
	box-shadow: 0 0 8px 1px var(--safe-tint);
}
.warmth.warm .warmth-text, .warmth.working .warmth-text { color: var(--text); }
.warmth.working .warmth-dot { animation: warm-pulse 1.6s ease-in-out infinite; }
.warmth .footnote { margin-left: auto; font-size: 10.5px; }
@keyframes warm-pulse { 50% { opacity: 0.35; } }

/* Held prints ride the same strip, deliberately flatter: they are the queue behind
   what is developing, numbered in the order they will run. */
.dev-tile.held .dev-frame { opacity: 0.55; }
.held-index {
	position: absolute;
	left: 6px;
	top: 6px;
	padding: 0 0.4rem;
	background: rgba(10, 12, 16, 0.78);
	border-radius: 4px;
	font-family: var(--font-data);
	font-size: 10.5px;
	color: var(--dim);
}
.section-head .release { margin-left: 0.8rem; }
.section-head .release button.primary { padding: 0.3rem 0.8rem; font-size: 12.5px; }

.dev-strip {
	display: flex;
	flex-wrap: wrap;
	gap: 0.8rem;
	margin-bottom: 0.4rem;
}

.dev-tile { width: 148px; }

.dev-frame {
	position: relative;
	display: block;
	aspect-ratio: 1;
	border-radius: var(--r-2);
	overflow: hidden;
	background: var(--void);
	border: 1px solid var(--line);
}
.dev-tile.running .dev-frame {
	border-color: var(--safe-line);
	box-shadow: 0 0 24px -6px var(--safe-tint), 0 0 0 1px var(--safe-tint);
}
.dev-frame img { width: 100%; height: 100%; object-fit: cover; display: block; }

.dev-blank { display: block; width: 100%; height: 100%; position: relative; }
.dev-tile.running .dev-blank {
	background: radial-gradient(circle at 50% 58%, var(--safe-tint), transparent 62%);
	animation: develop 2.4s ease-in-out infinite;
}
@keyframes develop {
	0%, 100% { opacity: 0.45; }
	50% { opacity: 1; }
}

.dev-bar {
	position: absolute;
	left: 0; right: 0; bottom: 0;
	height: 3px;
	background: rgba(0, 0, 0, 0.55);
}
.dev-bar > span {
	display: block;
	height: 100%;
	background: var(--safe);
	transition: width 0.4s ease;
}
.dev-bar.soon > span {
	width: 30%;
	animation: seek 1.6s ease-in-out infinite alternate;
}
@keyframes seek {
	from { margin-left: 0; }
	to { margin-left: 70%; }
}

.dev-meta {
	display: flex;
	align-items: center;
	gap: 0.4rem;
	margin-top: 0.4rem;
	font-size: 11px;
	color: var(--dim);
}
.dev-caption { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dev-clock { margin-left: auto; color: var(--safe); font-variant-numeric: tabular-nums; }
.dev-cancel { margin: 0; }
.dev-cancel button {
	background: none;
	border: none;
	color: var(--faint);
	font-size: 13px;
	line-height: 1;
	padding: 0 0.1rem;
	cursor: pointer;
}
.dev-cancel button:hover { color: var(--err); }

/* --- gallery -------------------------------------------------------------- */

.gallery-filter { display: flex; gap: 0.5rem; align-items: center; margin-left: auto; }
.run-filter select { padding: 0.3rem 0.45rem; font-size: 12px; }
#agent-runs .tile-more { gap: 0.5rem; }
.gallery-filter input[type="search"] {
	width: 220px;
	padding: 0.4rem 0.7rem;
	font-size: 12.5px;
	background: var(--panel);
}
.star-toggle { margin: 0; cursor: pointer; user-select: none; }
.star-toggle input { display: none; }
.star-toggle span {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	border: 1px solid var(--line-2);
	border-radius: var(--r-1);
	color: var(--faint);
	font-size: 13px;
	line-height: 1;
}
.star-toggle input:checked + span { color: var(--safe); border-color: var(--safe-line); background: var(--safe-tint); }

.picked {
	display: flex;
	align-items: center;
	gap: 0.85rem;
	margin-bottom: 0.75rem;
	font-size: 12.5px;
	color: var(--dim);
}
.picked a { color: var(--safe); }
.picked .danger { color: var(--err); }

/* The contact sheet: prints edge to edge, nothing between them but the pictures, one
   hairline round the lot. */
.sheet {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
	gap: 0;
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	overflow: hidden;
	background: var(--void);
}
.sheet .tile { border: none; border-radius: 0; }
.sheet .tile:hover { border: none; box-shadow: inset 0 0 0 1px var(--line-2); }
.sheet .tile-wrap:has(.tile-pick:checked)::after { border-radius: 0; }
.sheet .tile-caption { padding-bottom: 0.4rem; }

/* A clip and a splat both wear a poster still, so each needs something that says what
   it really is: one moves, the other turns. */
.tile-play, .tile-3d {
	position: absolute;
	left: 0.5rem;
	top: 0.45rem;
	padding: 0.05rem 0.35rem;
	border-radius: 4px;
	background: rgba(12, 12, 14, 0.62);
	color: var(--text);
	font-size: 10.5px;
	line-height: 1.5;
}

.tile-wrap { position: relative; }

.tile-more {
	grid-column: 1 / -1;
	display: flex;
	justify-content: center;
	padding: 0.6rem 0;
}

.tile {
	position: relative;
	display: block;
	aspect-ratio: 1;
	border-radius: var(--r-2);
	overflow: hidden;
	background: var(--panel);
	border: 1px solid var(--line);
	transition: border-color 0.12s ease;
}
.tile:hover { border-color: var(--line-2); }
.tile img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The hover preview: the clip itself, over its own poster. Held at zero opacity until
   the video says it is playing, so the still carries the tile while the mp4 loads. */
.tile-preview {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	opacity: 0;
	transition: opacity 0.16s ease;
}
.tile-preview.playing { opacity: 1; }
/* The badge only ever meant "this one moves", which the motion now says for itself. */
.tile:has(.tile-preview.playing) .tile-play { opacity: 0; }

.tile-file {
	display: flex;
	align-items: center;
	justify-content: center;
	height: 100%;
	padding: 0.75rem;
	font-size: 11.5px;
	color: var(--dim);
	text-align: center;
	overflow-wrap: anywhere;
}
.tile-caption {
	position: absolute;
	left: 0; right: 0; bottom: 0;
	padding: 1.8rem 0.65rem 0.55rem;
	font-size: 11.5px;
	line-height: 1.45;
	color: #f2f4f7;
	background: linear-gradient(transparent, rgba(6, 8, 11, 0.88));
	opacity: 0;
	transition: opacity 0.12s ease;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.tile:hover .tile-caption, .tile:focus-visible .tile-caption { opacity: 1; }

/* Drawn rather than native: a browser checkbox on top of a photograph is a light
   square on whatever the picture happens to be, and disappears against half of them.
   This one carries its own dark plate and is on screen at rest. */
.tile-pick {
	position: absolute;
	top: 0.45rem;
	left: 0.45rem;
	z-index: 2;
	width: 18px;
	height: 18px;
	margin: 0;
	appearance: none;
	-webkit-appearance: none;
	border: 1px solid rgba(242, 244, 247, 0.5);
	border-radius: 5px;
	background: rgba(10, 12, 16, 0.62);
	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
	cursor: pointer;
	opacity: 0.62;
	transition: opacity 0.12s ease, background-color 0.12s ease;
}
.tile-wrap:hover .tile-pick, .tile-pick:focus-visible { opacity: 1; }
.tile-pick:checked {
	opacity: 1;
	border-color: var(--safe-hi);
	background-color: var(--safe);
	/* The tick as an image rather than a pseudo-element: an input is a replaced
	   element and only some browsers give it children to draw with. */
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.4 8.4l3 3 6.2-6.6' fill='none' stroke='%231a1206' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: center;
	background-size: 13px;
}
/* Over the picture, not under it: the cover image fills the tile edge to edge, so a
   ring drawn on .tile is painted over by its own photograph. */
.tile-wrap:has(.tile-pick:checked)::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	border-radius: var(--r-2);
	background: var(--safe-tint);
	box-shadow: inset 0 0 0 2px var(--safe);
}

/* The star gets a real hit target: the misses were landing on the image link. */
.tile-star { position: absolute; top: 0; right: 0; z-index: 2; margin: 0; }
.tile-star button {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	background: none;
	border: none;
	padding: 0;
	font-size: 13.5px;
	line-height: 1;
	color: rgba(242, 244, 247, 0.8);
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
	cursor: pointer;
	opacity: 0;
	transition: opacity 0.12s ease;
}
.tile-wrap:hover .tile-star button, .tile-star button:focus-visible { opacity: 0.85; }
.tile-star button:hover { opacity: 1; color: var(--safe-hi); }
.tile-star button.on { opacity: 0.95; color: var(--safe); }
@media (hover: none) {
	.tile-star button, .tile-pick { opacity: 0.7; }
}

/* --- right-click menu ----------------------------------------------------- */

.context-menu {
	position: fixed;
	z-index: 50;
	margin: 0;
	padding: 0.3rem;
	list-style: none;
	min-width: 180px;
	max-height: calc(100vh - 16px);
	background: var(--panel);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-pop);
}
.context-menu li { list-style: none; }
.context-menu button {
	display: block;
	width: 100%;
	padding: 0.42rem 0.7rem;
	background: none;
	border: none;
	border-radius: 6px;
	color: var(--text);
	font: inherit;
	font-size: 12.5px;
	text-align: left;
	cursor: pointer;
}
.context-menu button:hover { background: var(--lift); color: var(--text); }
.context-menu .menu-sep {
	margin-top: 0.3rem;
	padding-top: 0.3rem;
	border-top: 1px solid var(--line);
}
.context-menu button.danger { color: var(--err); border: none; }
.context-menu button.danger:hover { background: var(--err-tint); }

/* The credit meter: same weight as the warmth line, never louder than the work. */
.credit {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.55rem;
	margin: -1rem 0 1.5rem;
	font-size: 12.5px;
	color: var(--dim);
}
.credit .credit-bar {
	width: 104px;
	height: 4px;
	border-radius: 999px;
	background: var(--raised);
	overflow: hidden;
}
.credit .credit-bar > span {
	display: block;
	height: 100%;
	background: var(--safe);
	border-radius: 999px;
}
.credit .credit-text { color: var(--text); }
/* A stale figure is dimmed rather than hidden: it is still the last thing we know. */
.credit.stale { opacity: 0.7; }
.credit.stale .credit-bar > span { background: var(--faint); }
.credit.stale .credit-text { color: var(--dim); }

/* --- compare -------------------------------------------------------------- */

.compare-images { display: flex; flex-wrap: wrap; gap: 1rem; }
.compare-images figure { margin: 0; flex: 1 1 240px; max-width: 360px; }
.compare-images img {
	width: 100%;
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	display: block;
}
.compare-images figcaption { margin-top: 0.4rem; font-size: 11.5px; }
/* A 3D file has no thumbnail, so its place on the sheet is held by its name. */
.compare-file {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 1;
	padding: 1rem;
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	background: var(--panel);
	color: var(--dim);
	font-size: 11.5px;
	text-align: center;
	overflow-wrap: anywhere;
}
.compare-n {
	display: inline-block;
	min-width: 1.3em;
	text-align: center;
	border: 1px solid var(--line-2);
	border-radius: 4px;
	margin-right: 0.3rem;
	color: var(--text);
}

.compare-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.compare-table th, .compare-table td {
	text-align: left;
	padding: 0.42rem 0.75rem 0.42rem 0;
	border-bottom: 1px solid var(--line);
	vertical-align: top;
	overflow-wrap: anywhere;
}
.compare-table td { font-family: var(--font-data); font-size: 12px; }
.compare-table thead th {
	color: var(--faint);
	font-size: 10.5px;
	text-transform: uppercase;
	letter-spacing: 0.09em;
}
.compare-table tbody th { font-weight: 500; color: var(--dim); white-space: nowrap; font-family: inherit; }
.compare-table tr.differs td { color: var(--safe); }
.compare-table tr.differs th { color: var(--text); }

/* --- image viewer --------------------------------------------------------- */

/* One column. The picture takes the page, the strip of neighbours sits under it and
   what is known about the print reads below that. --stage-drop is the room the rest of
   the first screen needs: main's top padding, the strip, and enough of the data block
   showing to say there is more under it. */
.viewer { --stage-drop: 13rem; }

.viewer-image img,
.viewer-image video {
	display: block;
	max-width: 100%;
	max-height: calc(100vh - var(--stage-drop));
	margin: 0 auto;
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	background: var(--void);
}
/* What the viewer shows when there is nothing it can draw: the file itself. */
.file-card {
	max-width: 34rem;
	margin: 0 auto;
	padding: 2rem 1.5rem;
	text-align: center;
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	background: var(--panel);
}
.file-card p { margin: 0 0 0.7rem; }
.file-card p:last-child { margin-bottom: 0; }
.file-name { font-size: 13px; color: var(--text); overflow-wrap: anywhere; }
.file-what { font-size: 12px; color: var(--dim); }
.file-note { font-size: 11.5px; color: var(--faint); }
.file-do { display: flex; justify-content: center; gap: 0.6rem; }

/* The 3D viewer takes the card over once it is asked for, rather than replacing it:
   the file line and its Download stay under the canvas, so the offer never goes away.
   model.js adds .viewing. */
.file-card.viewing { max-width: none; padding: 0.75rem; text-align: left; }
.file-card.viewing .file-do { justify-content: flex-start; }
.model-stage {
	aspect-ratio: 4 / 3;
	max-height: 72vh;
	margin-bottom: 0.9rem;
	border-radius: var(--r-1);
	background: var(--void);
	overflow: hidden;
}
/* touch-action, or a drag to orbit scrolls the page out from under the subject instead. */
.model-stage canvas { display: block; width: 100%; height: 100%; touch-action: none; }

/* A step each way with the tiles either side of this one between them, and the way out
   on the right. The thumbnails are small on purpose: this is where you are in the
   gallery, not a second gallery. */
.viewer-strip {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
	align-items: center;
	gap: 0.9rem;
	margin-top: 0.9rem;
	font-size: 12.5px;
}
.viewer-strip a { color: var(--dim); }
.viewer-strip a:hover { color: var(--text); }
.strip-step { white-space: nowrap; }
/* The end of the gallery: the step stays in place so the strip does not shuffle, and
   goes quiet so it does not read as somewhere to click. */
.strip-step.off { color: var(--faint); }
.strip-end { display: flex; align-items: center; justify-content: flex-end; gap: 1rem; }
.strip-run { display: flex; justify-content: center; gap: 0.4rem; min-width: 0; overflow-x: auto; }
.strip-tile { flex: none; }
.strip-thumb {
	display: block;
	width: 64px;
	height: 64px;
	border-radius: var(--r-1);
	object-fit: cover;
	border: 1px solid var(--line-2);
	background: var(--void);
	opacity: 0.55;
}
.strip-tile:hover .strip-thumb { opacity: 1; }
.strip-tile.on .strip-thumb {
	opacity: 1;
	border-color: var(--safe);
	box-shadow: 0 0 10px -2px var(--safe-tint);
}
/* A 3D file has no thumbnail, so it wears the mark the chain gives it. */
.strip-thumb.strip-file {
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--faint);
	font-size: 22px;
}

/* Under the strip: the words and the dials together, the acts and the chain beside
   them. Full width, because there is no side column left to hold them. */
.viewer-data {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
	gap: 1.4rem 2.4rem;
	margin-top: 1.6rem;
	padding-top: 1.3rem;
	border-top: 1px solid var(--line);
}
/* Too narrow for the steps beside the tiles: the tiles take a row of their own and
   scroll sideways, with a step under each end of it. */
@media (max-width: 860px) {
	.viewer-data { grid-template-columns: 1fr; }
	.viewer-strip { display: flex; flex-wrap: wrap; justify-content: space-between; }
	.strip-run { order: -1; flex-basis: 100%; justify-content: flex-start; }
}

.viewer-facts .prompt { margin: 0 0 1rem; font-size: 13.5px; line-height: 1.55; max-width: 74ch; }

.viewer-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-top: 1.25rem; }
.viewer-actions form { margin: 0; }
.viewer-actions button.primary { padding: 0.4rem 0.9rem; font-size: 12.5px; }
.viewer-actions a.button, .viewer-actions button.secondary { font-size: 12.5px; padding: 0.36rem 0.8rem; }
.viewer-actions .job-link { font-size: 11.5px; color: var(--faint); flex-basis: 100%; }

/* The chain wears the same shape here as in the editor; on this page it only needs
   parting from the table above it. */
.viewer-side .lineage { margin-top: 1.5rem; border-top: 1px solid var(--line); padding-top: 0.9rem; }

.fav-action { margin: 0 0 0.9rem; }
button.star {
	padding: 0.38rem 0.8rem;
	font-size: 12.5px;
	background: transparent;
	color: var(--dim);
	border: 1px solid var(--line-2);
	border-radius: var(--r-1);
	cursor: pointer;
}
button.star:hover { border-color: var(--safe-line); color: var(--text); }
button.star.on { color: var(--safe); border-color: var(--safe-line); background: var(--safe-tint); }

/* --- authoring ------------------------------------------------------------ */

.wf-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
	gap: 0.8rem;
}
.wf-card {
	display: block;
	padding: 1.05rem 1.2rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	transition: border-color 0.12s ease;
}
.wf-card:hover { border-color: var(--safe-line); color: inherit; }
.wf-card.off { opacity: 0.55; }
.wf-card h2 { font-size: 14.5px; font-weight: 600; margin: 0; }
.wf-name { display: block; margin-top: 0.15rem; font-family: var(--font-data); font-size: 11px; color: var(--faint); }

.wf-facts { display: flex; flex-wrap: wrap; gap: 0.3rem 1.6rem; margin: 0.85rem 0 0; }
.wf-facts div { display: flex; flex-direction: column; }
.wf-facts dt {
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--faint);
}
.wf-facts dd { margin: 0; font-size: 12.5px; }

.wf-models { margin: 0.8rem 0 0; padding: 0.6rem 0 0; list-style: none; border-top: 1px solid var(--line); }
.wf-models li { font-family: var(--font-data); font-size: 11px; color: var(--dim); overflow-wrap: anywhere; }

.graph-panel { margin-bottom: 0.6rem; }
.graph-scroll {
	overflow-x: auto;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	padding: 0.6rem;
}
.graph { display: block; }
.graph .g-node rect { fill: var(--raised); stroke: var(--line-2); }
.graph .g-title { fill: var(--text); font: 500 11.5px var(--font-ui); }
.graph .g-id { fill: var(--faint); font: 400 10px var(--font-data); }
.graph .g-chip { fill: var(--safe); font: 500 11px var(--font-data); }
.graph .g-field { fill: var(--dim); }
.graph .g-edge { fill: none; stroke: var(--line-2); stroke-width: 1.5; }
.graph .g-edge:hover { stroke: var(--safe); }

.param-table { width: 100%; border-collapse: collapse; }
.param-table thead th {
	text-align: left;
	font-size: 10.5px;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--faint);
	font-weight: 600;
	padding: 0 1rem 0.5rem 0;
	border-bottom: 1px solid var(--line-2);
}
.param-table tbody th, .param-table tbody td {
	text-align: left;
	padding: 0.65rem 1rem 0.65rem 0;
	border-bottom: 1px solid var(--line);
	vertical-align: top;
	font-weight: normal;
}
.param-table tbody th { width: 15ch; }
.param-table tbody th label { margin: 0; font-size: 13px; color: var(--text); }
.param-key { display: block; font-family: var(--font-data); font-size: 10.5px; color: var(--faint); margin-top: 0.1rem; }
.param-value input, .param-value textarea { max-width: 520px; }
.param-value input[type="number"] { max-width: 150px; }
.param-value textarea { resize: vertical; }
.col-bounds { font-size: 11.5px; white-space: nowrap; }
.col-target { font-size: 11.5px; white-space: nowrap; }

.authoring-submit { display: flex; align-items: center; gap: 1rem; margin-top: 1.1rem; }
.authoring-submit .muted { font-size: 12.5px; }

/* The authoring head carries the card's own actions and provenance under its summary. */
.page-head .row-acts, .page-head > .footnote { flex-basis: 100%; margin: 0.2rem 0 0; }

/* Declaring a card: the graph's every literal, and the spec each ticked one gets.
   The spec is hidden until the tick, so the page reads as the graph until it is
   being authored. */
.declare-facts {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: 0.9rem 1.2rem;
	margin-bottom: 1.6rem;
}
.declare-facts .wide { grid-column: 1 / -1; }
.declare-facts .hint { display: block; margin-top: 0.3rem; font-size: 11px; color: var(--faint); }

.declare-node {
	margin: 0.6rem 0;
	padding: 0.8rem 1rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.declare-node h3 {
	margin: 0 0 0.5rem;
	font-size: 13px;
	font-weight: 600;
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
}
.declare-node h3 .mono { font-size: 11px; font-weight: 400; }

.declare-row { padding: 0.4rem 0; border-top: 1px solid var(--line); }
.declare-row:first-of-type { border-top: none; }
.declare-row.inert { opacity: 0.5; }
.declare-head { display: flex; align-items: center; gap: 0.8rem; font-size: 12.5px; }
.declare-now { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.tick { display: inline-flex; align-items: center; gap: 0.35rem; margin: 0; font-size: 12px; }
.tick input { width: auto; }

.declare-spec {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 0.5rem 0.7rem;
	margin: 0.55rem 0 0.3rem 1.5rem;
	padding-left: 0.9rem;
	border-left: 2px solid var(--safe-line);
}
.declare-spec .field { width: 150px; }
.declare-spec .field.narrow { width: 78px; }
.declare-spec input, .declare-spec select { padding: 0.3rem 0.45rem; font-size: 12px; }
.declare-flags { display: flex; flex-wrap: wrap; gap: 0.2rem 0.8rem; }
/* Nothing to set until it is an input, and forty node fields of empty boxes would
   bury the one decision this page is asking for. */
.declare-row:not(:has(.declare-on:checked)) .declare-spec { display: none; }

.raw-json summary { cursor: pointer; padding: 0.5rem 0; color: var(--dim); }
.raw-json summary:hover { color: var(--text); }
.raw-json pre {
	margin: 0.6rem 0 0;
	padding: 1rem 1.2rem;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	overflow-x: auto;
	font-size: 11.5px;
	line-height: 1.5;
	color: var(--dim);
	max-height: 480px;
	overflow-y: auto;
}

/* --- studio: a thin bar, then the editor taking the rest ------------------- */

.page-head .head-act { margin-left: auto; }

/* The same shape the edit page's shell has, and for the same reason: a graph editor
   under a page header is a graph editor in a letterbox. The rail is beside it. */
body:has(.studio-shell) { overflow: hidden; }

.studio-shell {
	display: grid;
	grid-template-rows: 50px minmax(0, 1fr);
	height: 100vh;
}
.studio-warn { color: var(--warn); }
.studio-ok { color: var(--safe); }
.studio-refresh { margin-left: auto; }

/* Pushed right, and ahead of Refresh when both are on the bar. */
.studio-runs-on {
	display: flex;
	align-items: center;
	gap: 6px;
	margin-left: auto;
}
.studio-runs-on + .studio-save, .studio-save + .studio-refresh { margin-left: 0; }
.studio-runs-on label, .studio-open label { color: var(--muted); font-size: 12px; }
.studio-runs-on select, .studio-open select { padding: 2px 6px; font-size: 12px; }

/* Beside the node count rather than out on the right: opening a card is where a
   session starts, and the controls on the right are about the run. */
.studio-open {
	display: flex;
	align-items: center;
	gap: 6px;
}
.studio-open select { max-width: 220px; }

.studio-frame {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
	background: var(--void);
}

/* --- status --------------------------------------------------------------- */

.panels {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	gap: 0.8rem;
	align-items: start;
}
.panel {
	padding: 1rem 1.2rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.panel.wide { grid-column: 1 / -1; }
.panel .eyebrow { margin-bottom: 0.8rem; }
.panel .footnote { margin: 0.9rem 0 0; font-size: 11.5px; line-height: 1.55; }
.panel .error { margin: 0 0 0.9rem; }
.panel a { color: var(--safe); }
/* The ops report is the model's plain text under its three headings; its lines are its own. */
.ops-summary { margin: 0 0 0.6rem; color: var(--text); }
.ops-report {
	margin: 0;
	color: var(--dim);
	font-size: 13px;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

.ok-text { color: var(--ok); }
.warn-text { color: var(--warn); }

.meter {
	display: block;
	height: 4px;
	margin-top: 0.4rem;
	border-radius: 999px;
	overflow: hidden;
	background: var(--raised);
}
.meter > span {
	display: block;
	height: 100%;
	background: var(--ok);
	border-radius: 999px;
	transition: width 0.4s ease;
}
.meter.mid > span { background: var(--warn); }
.meter.high > span { background: var(--err); }

.panel .spinner { display: inline-block; vertical-align: -1px; margin-right: 0.3rem; }

.cost-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
	gap: 1.1rem;
}
.cost { display: flex; flex-direction: column; gap: 0.18rem; }
.cost-label {
	font-size: 10px;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--faint);
	font-weight: 600;
}
.cost-value { font-family: var(--font-data); font-size: 1.5rem; font-weight: 500; letter-spacing: -0.01em; }
.cost .muted { font-size: 11.5px; }

/* --- job list ------------------------------------------------------------- */

table.jobs {
	width: 100%;
	border-collapse: collapse;
	font-size: 13px;
}
table.jobs th {
	text-align: left;
	font-size: 10.5px;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--faint);
	font-weight: 600;
	padding: 0 0.75rem 0.6rem;
	border-bottom: 1px solid var(--line-2);
}
table.jobs td {
	padding: 0.7rem 0.75rem;
	border-bottom: 1px solid var(--line);
	vertical-align: top;
}
table.jobs tbody tr { cursor: pointer; }
table.jobs tbody tr:hover { background: var(--panel); }
table.jobs .num { text-align: right; white-space: nowrap; }
table.jobs .label {
	margin-top: 0.2rem;
	color: var(--dim);
	font-size: 12.5px;
	max-width: 52ch;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
table.jobs .empty { color: var(--dim); padding: 2rem 0.75rem; text-align: center; }

.pill {
	display: inline-flex;
	align-items: center;
	gap: 0.42rem;
	padding: 0.12rem 0.6rem;
	border-radius: 999px;
	font-family: var(--font-data);
	font-size: 10.5px;
	font-weight: 500;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	background: var(--raised);
	color: var(--dim);
}
.pill::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: none; }
.pill.running { background: var(--safe-tint); color: var(--safe); }
.pill.running::before { animation: warm-pulse 1.6s ease-in-out infinite; }
.pill.done    { background: var(--ok-tint); color: var(--ok); }
.pill.failed  { background: var(--err-tint); color: var(--err); }
.pill.cancelled { background: var(--raised); color: var(--faint); }

/* --- job detail ----------------------------------------------------------- */

.job-head { display: flex; align-items: baseline; gap: 0.75rem; }
.job-head h1 { font-family: var(--font-data); font-size: 1.15rem; font-weight: 500; }
.job-head .back { margin-left: auto; font-size: 12.5px; color: var(--dim); }
.job-head .back:hover { color: var(--text); }

.meta {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem 2rem;
	padding: 0.9rem 1.1rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
	font-size: 13px;
}
.meta > div { display: flex; align-items: center; gap: 0.5rem; }
.meta .k {
	font-size: 10px;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--faint);
	font-weight: 600;
}

.stage {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.6rem;
	margin-top: 0.8rem;
	padding: 0.8rem 1.1rem;
	background: var(--safe-tint);
	border: 1px solid var(--safe-line);
	border-radius: var(--r-2);
	font-size: 13px;
}
.stage-clock { color: var(--safe); font-variant-numeric: tabular-nums; }
.stage-note { font-size: 12px; margin-left: auto; }

.bar {
	height: 3px;
	margin-top: 0.5rem;
	background: var(--raised);
	border-radius: 999px;
	overflow: hidden;
}
.bar span {
	display: block;
	height: 100%;
	background: var(--safe);
	border-radius: 999px;
	transition: width 0.3s ease;
}

.preview { margin-top: 1rem; }
.preview img {
	display: block;
	max-width: 512px;
	width: 100%;
	border-radius: var(--r-2);
	border: 1px solid var(--safe-line);
	box-shadow: 0 0 28px -8px var(--safe-tint);
}

.spinner {
	width: 13px;
	height: 13px;
	border: 2px solid var(--safe-tint);
	border-top-color: var(--safe);
	border-radius: 50%;
	animation: spin 0.9s linear infinite;
	flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

.cancel { margin-top: 1rem; }

table.kv { border-collapse: collapse; font-size: 13px; width: 100%; }
table.kv th {
	text-align: left;
	font-weight: 500;
	color: var(--dim);
	padding: 0.35rem 1.5rem 0.35rem 0;
	vertical-align: top;
	white-space: nowrap;
	width: 1%;
}
table.kv td { padding: 0.35rem 0; max-width: 0; overflow-wrap: anywhere; }

/* An image input, drawn. Sized so a row of them reads as a strip of what was sent
   without the table turning into a gallery. */
.kv-shot { display: flex; align-items: center; gap: 0.5rem; text-decoration: none; }
.kv-shot img,
.kv-shot video {
	width: 64px;
	height: 48px;
	object-fit: cover;
	flex: none;
	border-radius: var(--r-1);
	border: 1px solid var(--line);
	background: var(--void);
}
.kv-shot span { overflow-wrap: anywhere; }
.kv-shot:hover img,
.kv-shot:hover video { border-color: var(--line-2); }

.artifacts { display: flex; flex-wrap: wrap; gap: 1rem; }
.artifacts figure { margin: 0; max-width: 320px; }
.artifacts img {
	display: block;
	max-width: 100%;
	border-radius: var(--r-2);
	border: 1px solid var(--line);
	background: var(--void);
}
.artifacts figcaption { margin-top: 0.35rem; font-size: 11.5px; color: var(--dim); }
.artifacts figure.file {
	padding: 0.9rem 1.1rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
}

.timeline { list-style: none; margin: 0; padding: 0; font-size: 12px; }
.timeline li {
	display: flex;
	gap: 0.9rem;
	padding: 0.35rem 0;
	border-bottom: 1px solid var(--line);
}
.timeline .at { color: var(--faint); white-space: nowrap; }
.timeline .kind { min-width: 11ch; color: var(--text); font-weight: 500; }
.timeline .detail { color: var(--dim); overflow-wrap: anywhere; }

/* --- the editor --------------------------------------------------------------
   A full-height instrument in the main column, beside the rail: no page scroll. A topbar
   over the canvas and one panel, each scrolling on its own; the tools float over the
   canvas in a dock, the lit tool opening its option bar above. canvas.js drives all of it
   through data attributes and the `on` / `disabled` / `hidden` states — the classes here
   are only the look. */

body:has(.editor-shell) { overflow: hidden; }

.editor-shell {
	position: relative;
	display: grid;
	/* The panel's width is the person's: the grip writes --side-w on the root, and the
	   page carries it in before first paint. */
	grid-template-columns: minmax(0, 1fr) var(--side-w, 324px);
	/* Topbar, then the canvas taking the whole of what is left: the cards live at the
	   head of the panel now, not on a strip eating into the picture. */
	grid-template-rows: 50px minmax(0, 1fr);
	height: 100vh;
}

.topbar {
	grid-column: 1 / -1;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0 1rem;
	background: var(--panel);
	border-bottom: 1px solid var(--line);
}
.topbar h1 { font-size: 14px; }
.topbar .btn-quiet {
	display: inline-flex;
	align-items: center;
	padding: 0.35rem 0.55rem;
	background: var(--raised);
	border-radius: var(--r-1);
	color: var(--dim);
}
.topbar .btn-quiet:hover { background: var(--lift); color: var(--text); }
.topbar .btn-quiet .ic { width: 14px; height: 14px; display: block; }
.topbar .file { font-family: var(--font-data); font-size: 11.5px; color: var(--faint); }

/* The warmth line's whisper, down to its dot and one word. */
.mini-warmth {
	margin-left: auto;
	display: flex;
	align-items: center;
	gap: 0.45rem;
	font-size: 12px;
	color: var(--dim);
}
.mini-warmth .warmth-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
.mini-warmth.warm .warmth-dot, .mini-warmth.working .warmth-dot {
	background: var(--safe);
	box-shadow: 0 0 8px 1px var(--safe-tint);
}
.mini-warmth.working .warmth-dot { animation: warm-pulse 1.6s ease-in-out infinite; }

.flow.slim {
	position: relative;
	width: auto;
	min-width: 128px;
	max-width: 200px;
	margin: 0;
	padding: 7px 11px 8px;
	gap: 2px;
}
.flow.slim input { position: absolute; opacity: 0; pointer-events: none; }
.flow.slim .flow-verb {
	display: flex;
	align-items: baseline;
	gap: 0.4rem;
	padding: 0;
}
.flow.slim .flow-rank { position: static; padding: 0; background: none; }
.flow.slim .flow-model { padding: 0; }
.flow.slim:has(input:checked) { border-color: var(--safe-line); box-shadow: 0 0 0 1px var(--safe-tint); }
.flow.slim:has(input:checked) .flow-rank { color: var(--safe); }
/* Waiting for something rather than broken: the card says what in its title. */
.flow.slim:has(input:disabled) { opacity: 0.45; cursor: default; }
.flow.slim:has(input:disabled):hover { border-color: var(--line); }
.flow.slim:has(input:focus-visible) { outline: 2px solid var(--safe); outline-offset: 2px; }

/* Not `.stage`: the job page already owns that word for its heartbeat row. */
.edit-stage {
	grid-column: 1;
	display: grid;
	place-items: center;
	overflow: auto;
	background: var(--void);
}
/* One cell for all of them, so the empty face, the develop frame and the print arriving
   are centred on the canvas whatever size it is. */
.edit-stage > canvas, .edit-stage > .edit-empty,
.edit-stage > .stage-dev, .edit-stage > .stage-fade { grid-area: 1 / 1; }
.edit-stage canvas { display: block; touch-action: none; cursor: crosshair; }
.edit-stage.panning canvas { cursor: grabbing; }

/* The child developing from this canvas, on the canvas: the safelight on the picture
   itself, the frames the strip is showing, and the bar along its foot. Sized to the
   canvas by canvas.js, since the zoom is the engine's and this is only glass over it. */
.stage-dev {
	position: relative;
	pointer-events: none;
	border: 1px solid var(--safe-line);
	box-shadow: 0 0 40px -6px var(--safe-tint), 0 0 0 1px var(--safe-tint);
}
/* Until the first frame arrives the glass is empty and the picture is simply lit. Once
   one does, the picture goes down behind it: what is developing is the subject now. */
.stage-dev.framed { background: rgba(6, 8, 11, 0.74); }
.dev-shot {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: contain;
	opacity: 0;
	transition: opacity 0.35s ease;
}
.stage-dev.framed .dev-shot { opacity: 1; }
.stage-fade {
	display: block;
	opacity: 0;
	transition: opacity 0.4s ease;
	pointer-events: none;
}
.stage-fade.in { opacity: 1; }
.edit-empty { text-align: center; color: var(--dim); font-size: 13px; }
.edit-empty .flavour { color: var(--dim); }
.edit-empty input[type="file"] { margin-top: 0.5rem; width: auto; }

/* The brush at the pointer, for the four tools that lay paint. Fixed rather than placed
   in the stage, so a scrolled or zoomed canvas needs no arithmetic to stay under it; it
   is hidden the moment the pointer is off the picture. Light ring inside a dark one, so
   it reads on a white sky and on a black coat alike. */
.brush-ring {
	position: fixed;
	z-index: 4;
	transform: translate(-50%, -50%);
	border-radius: 50%;
	pointer-events: none;
	border: 1px solid rgba(255, 255, 255, 0.85);
	box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(0, 0, 0, 0.6);
}
/* The core the hardness leaves at full strength: between the two rings the brush fades. */
.brush-ring > span {
	position: absolute;
	top: 50%;
	left: 50%;
	width: var(--core, 0);
	height: var(--core, 0);
	transform: translate(-50%, -50%);
	border-radius: 50%;
	border: 1px dashed rgba(255, 255, 255, 0.5);
}

/* Over the stage column rather than inside it, so the stage scrolls under them when
   the canvas is bigger than the window. The offset is half the side panel. */
.floaters {
	position: absolute;
	left: calc(50% - var(--side-w, 324px) / 2);
	bottom: 22px;
	transform: translateX(-50%);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.55rem;
	max-width: calc(100% - var(--side-w, 324px) - 24px);
}
/* Nothing is there by default: no picture, no tools. */
.edit-stage:has(.edit-empty:not([hidden])) ~ .floaters { display: none; }

/* Wraps rather than running off the side: on a phone the one row was 475px in a 390px
   window, the first tools off screen and the whole page laid out wider than it. */
.dock {
	display: inline-flex;
	flex-wrap: wrap;
	justify-content: center;
	max-width: 100%;
	align-items: center;
	gap: 2px;
	padding: 5px;
	background: rgba(21, 26, 33, 0.92);
	border: 1px solid var(--line-2);
	border-radius: var(--r-3);
	box-shadow: var(--shadow-dock);
	backdrop-filter: blur(12px);
}
.dock-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	background: transparent;
	border: none;
	border-radius: 9px;
	color: var(--dim);
	cursor: pointer;
	flex: none;
}
.dock-btn .ic { width: 16px; height: 16px; }
.dock-btn:hover { background: var(--lift); color: var(--text); }
.dock-btn.on { background: var(--safe-tint); color: var(--safe); }
.dock-btn:disabled { opacity: 0.35; cursor: default; background: transparent; }
.dock-sep { width: 1px; height: 20px; background: var(--line-2); margin: 0 4px; flex: none; }
.dock-btn.zoom-label {
	width: auto;
	padding: 0 7px;
	font-family: var(--font-data);
	font-size: 11px;
	color: var(--dim);
}

/* The option bar the lit tool opens: sliders with mono readouts, then the tool's
   own verbs. canvas.js shows and hides each control per tool. */
.optbar {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 0.5rem 0.9rem;
	padding: 0.5rem 0.9rem;
	background: rgba(21, 26, 33, 0.92);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-dock);
	backdrop-filter: blur(12px);
}
.optbar .tool-button { padding: 0.32rem 0.65rem; background: transparent; }
.optbar .tool-button:hover { background: var(--lift); }

.editor-slider {
	display: flex;
	align-items: center;
	gap: 0.45rem;
	margin: 0;
	font-size: 12px;
	color: var(--dim);
	white-space: nowrap;
}
.editor-slider input[type="range"] { width: 96px; }
/* A bare colour input renders as a dot; it has to read as the swatch it is. */
.editor-slider input[type="color"] {
	width: 28px;
	height: 20px;
	padding: 0;
	background: none;
	border: 1px solid var(--line-2);
	border-radius: 5px;
	cursor: pointer;
}
.editor-readout {
	min-width: 4ch;
	font-family: var(--font-data);
	font-size: 11.5px;
	text-align: right;
	text-transform: none;
	color: var(--text);
	font-variant-numeric: tabular-nums;
}

/* The tool's own instruction, and where the editor says a picture would not load. It
   floats over the picture, so it carries its own ground or a pale print swallows it. */
.editor-note {
	margin: 0;
	max-width: 52ch;
	padding: 0.2rem 0.7rem;
	border-radius: 999px;
	background: rgba(21, 26, 33, 0.86);
	backdrop-filter: blur(8px);
	text-align: center;
	font-size: 11px;
	color: var(--dim);
}
.editor-note:empty { display: none; }
.editor-note.err { color: var(--err); }

/* --- the picker's palette --------------------------------------------------- */

/* The widget's body, floating over the picture where it has room to be read. Placed by
   picker.js in the shell's own coordinates and dragged by its head. Held back on an
   empty canvas along with the dock, which is where the button that reopens it lives. */
.edit-stage:has(.edit-empty:not([hidden])) ~ .palette { display: none; }
.palette {
	position: absolute;
	z-index: 5;
	width: 320px;
	background: rgba(21, 26, 33, 0.94);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-dock);
	backdrop-filter: blur(12px);
}
.palette-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
	padding: 0.4rem 0.4rem 0.4rem 0.7rem;
	border-bottom: 1px solid var(--line);
	cursor: grab;
	touch-action: none;
	user-select: none;
}
.palette.moving .palette-head { cursor: grabbing; }
.palette-name { font-size: 12px; color: var(--dim); }
.palette-close {
	padding: 0 0.35rem;
	background: transparent;
	border: none;
	color: var(--faint);
	font-size: 15px;
	line-height: 1;
	cursor: pointer;
}
.palette-close:hover { color: var(--text); }
.palette-body { padding: 0.7rem; }
/* Inside the palette the widget is the whole room: no frame of its own, and the orbit
   takes the full width with the dials under it. */
.palette .picker-body { margin: 0; padding: 0; background: none; border: none; }
.palette .picker-stage { flex: 1 1 100%; max-width: none; }

/* --- the editor's side panel ------------------------------------------------ */

/* Beside the canvas: the panel is one column of its own, top to foot. */
.side {
	grid-column: 2;
	grid-row: 2;
	display: flex;
	flex-direction: column;
	gap: 0.9rem;
	padding: 0.9rem;
	background: var(--panel);
	border-left: 1px solid var(--line);
	overflow-y: auto;
}
.side > * { flex: none; }
.side .editing { display: flex; flex-direction: column; gap: 0.9rem; min-width: 0; }

/* The seam itself: a hair of border with a grabbable width over it, so the panel can be
   pulled wider without the picture losing a pixel to a visible bar. */
.side-grip {
	position: absolute;
	top: 50px;
	bottom: 0;
	right: calc(var(--side-w, 324px) - 3px);
	width: 7px;
	z-index: 6;
	cursor: col-resize;
	touch-action: none;
}
.side-grip:hover, .side-grip:focus-visible, .editor-shell.resizing .side-grip {
	background: var(--safe-tint);
}
.side-grip:focus-visible { outline: none; }

/* The head: the card about to run, and the list of the others behind one word. */
.card-head { position: relative; display: flex; flex-direction: column; gap: 0.4rem; }
.made-with { margin: 0; font-size: 11.5px; color: var(--faint); }
/* Only while a Mask tool is up and the chosen card would ignore the paint. */
.mask-note { margin: 0; font-size: 11.5px; color: var(--dim); }
.mask-note .link { color: var(--safe); }
.card-chip {
	display: grid;
	grid-template-columns: 1fr auto;
	align-items: baseline;
	gap: 0.2rem 0.6rem;
	padding: 0.5rem 0.6rem;
	background: var(--raised);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
}
.card-verb { font-size: 13.5px; font-weight: 600; }
.card-under {
	grid-column: 1;
	font-family: var(--font-data);
	font-size: 11px;
	color: var(--faint);
}
.card-change { grid-column: 2; grid-row: 1 / 3; align-self: center; }
/* Floating, so the panel under it keeps its place while the list is open, and scrolling
   rather than growing past the panel: there are twelve cards. */
.card-pick {
	position: absolute;
	top: calc(100% + 6px);
	left: 0;
	right: 0;
	z-index: 30;
	max-height: 58vh;
	overflow-y: auto;
	padding: 0.5rem;
	background: var(--panel);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-pop);
}
.card-pick .flow-strip { flex-direction: column; overflow: visible; }
.card-pick .flow.slim { max-width: none; width: 100%; }

/* Where this print came from, and the way back to it with these settings on. */
.card-source {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	padding: 0.4rem 0.5rem;
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	font-size: 11.5px;
	color: var(--dim);
}
.card-source:hover { border-color: var(--line-2); color: var(--text); }
.card-source img { width: 34px; height: 34px; object-fit: cover; border-radius: 3px; }

/* Free, client-side and always in view: no tab, so nothing here is hiding. */
.adjust-zone { display: flex; flex-direction: column; gap: 0.9rem; min-width: 0; }

/* The card's own fields, in the order they are reached for. Anything a card does not
   take leaves no gap behind it: a workflow with no prompt is a foot with two buttons. */
.side #edit-fields { display: flex; flex-direction: column; gap: 0.7rem; }
.side .composer-body:not(:has(*)), .rack:not(:has(*)), #compose-msg:empty { display: none; }
.side #compose-msg .error, .side #compose-msg .notice { margin: 0; font-size: 12px; }

/* The prompt as this panel's mouthpiece, lifted off the panel's own ground. */
.side .composer { background: var(--lift); border-color: var(--line-2); border-radius: var(--r-2); }
.side .composer:focus-within { border-color: var(--safe-line); }
.side .composer-body { padding: 0.25rem 0.5rem 0; }
.side .prompt-field textarea {
	background: transparent;
	border: none;
	padding: 0.55rem 0.4rem 0.2rem;
	min-height: 5.4rem;
	font-size: 13.5px;
	line-height: 1.55;
}
.side .prompt-field textarea:focus { border: none; }
.side .composer-body > .prompt-field:first-child > label { position: absolute; left: -9999px; }
.side .prompt-tools { padding: 0 0.2rem 0.2rem; }
.side .composer-foot { margin-top: 0; padding: 0.5rem 0.6rem; border-radius: 0 0 var(--r-2) var(--r-2); }
/* A line of its own: at this width a sentence beside the buttons squeezes both. */
.side .composer-foot .hint { flex: 1 0 100%; font-size: 11px; line-height: 1.35; }
.side .composer-foot .hint:empty { display: none; }
.side .composer-foot button.primary { padding: 0.42rem 0.75rem; font-size: 12.5px; }
.side .composer-foot button.secondary { padding: 0.4rem 0.7rem; font-size: 12.5px; }

/* The rest of what the run carries: small, mono, in reach without being in the way. */
.rack { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.rack .field.num { width: 90px; }
.field-label {
	display: block;
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	margin: 0 0 0.25rem;
}
.input.data { font-family: var(--font-data); font-size: 12.5px; }
.rack .input { padding: 0.34rem 0.5rem; }

/* Which shape a repaint is scoped to. In the region's own colour, because that is what
   the wash on the canvas is wearing and the two are the same shape. */
.mask-source { display: flex; align-items: baseline; gap: 0.6rem; }
.mask-source .field-label { margin: 0; }
.mask-source select {
	flex: 1;
	min-width: 0;
	padding: 0.28rem 0.45rem;
	font-size: 12.5px;
	color: var(--pick);
}

/* The one picture the canvas cannot feed. */
.second-picture { margin: 0; }
.side .upload.compact { padding: 0.35rem; gap: 0.5rem; }
.side .upload.compact .upload-thumb, .side .upload.compact .upload-empty { width: 40px; height: 40px; }
.side .upload input[type="file"] { padding: 0.25rem; font-size: 11px; }
.side .upload .hint { font-size: 10.5px; }
.side .also-run { margin-top: 0; padding-top: 0.7rem; }

/* The child fired from this canvas, laid low and full width. The rest of the darkroom
   — the queue, the held basket, the warmth — belongs on /app/, not over the picture. */
.side-dev:not(:has(.dev-tile:not(.held))) { display: none; }
.side-dev .section-head { margin: 0 0 0.5rem; }
.side-dev .section-head .count, .side-dev .section-head .rule, .side-dev .release,
.side-dev .warmth, .side-dev .dev-tile.held { display: none; }
.side-dev .dev-strip { flex-direction: column; gap: 0.5rem; margin: 0; }
.side-dev .dev-tile { width: 100%; }
.side-dev .dev-frame { aspect-ratio: auto; height: 58px; }

.adjust-group { display: flex; flex-direction: column; gap: 0.4rem; }
.adjust-group > .tool-button { align-self: flex-start; margin-top: 0.3rem; }
.adjust-slider {
	display: grid;
	grid-template-columns: 72px 1fr 42px;
	align-items: center;
	gap: 0.5rem;
	margin: 0;
	font-size: 12px;
	color: var(--dim);
}
.adjust-slider input[type="range"] { width: 100%; }
.adjust-slider .editor-readout { text-align: right; }

.curve-tabs { display: flex; gap: 0.3rem; margin-bottom: 0.4rem; }
.curve-box {
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	overflow: hidden;
}

/* The journal of what happened to the picture, newest first and always in view: one
   hairline runs behind the stack, so the steps read as the chain they are. */
.layers { display: flex; flex-direction: column; gap: 0.35rem; }
.layers-head { display: flex; align-items: center; gap: 0.5rem; }
.layer-acts { margin-left: auto; display: flex; gap: 2px; }
.layer-act {
	padding: 2px;
	line-height: 1;
	font-size: 12.5px;
	color: var(--dim);
	background: none;
	border: none;
	cursor: pointer;
}
.layer-act:hover { color: var(--safe); }
.layer-act:disabled { opacity: 0.35; cursor: default; }
.layer-act:disabled:hover { color: var(--dim); }
.layer-act .ic { width: 13px; height: 13px; display: block; }
.layer-stack {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	margin: 0;
	padding: 0 0 0 0.85rem;
	list-style: none;
}
.layer-stack::before {
	content: '';
	position: absolute;
	left: 3px;
	top: 12px;
	bottom: 12px;
	width: 1px;
	background: var(--line-2);
}
.layer-empty { font-size: 12px; color: var(--faint); }
.layer-row {
	position: relative;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.4rem 0.55rem;
	font-size: 12.5px;
	background: var(--raised);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
}
.layer-row::before {
	content: '';
	position: absolute;
	left: -0.85rem;
	top: 50%;
	width: 5px;
	height: 5px;
	margin: -3px 0 0 -2px;
	border-radius: 50%;
	background: var(--line-2);
}
.layer-row.on { border-color: var(--line-2); background: var(--lift); }
.layer-row.on::before { background: var(--safe); }
.layer-row.off { opacity: 0.5; }
.layer-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.layer-step { font-family: var(--font-data); font-size: 10px; color: var(--faint); }
.layer-row.on .layer-step { color: var(--safe); }

/* The stack: the base picture at the foot, one mask above it per region, and the selected
   row is what every control on the panel is pointing at. A mask row is cool where the base
   row is warm, because the wash on the canvas is cool too and the two have to agree about
   which thing is being worked on. */
.stack .layer-row { flex-wrap: wrap; padding: 0.35rem 0.45rem; }
.stack .layer-row.on { border-color: var(--pick-line); background: var(--pick-tint); }
.stack .layer-row.on::before { background: var(--pick); }
.stack .layer-row.on .layer-step { color: var(--pick); }
.stack .base-row.on { border-color: var(--safe-line); background: var(--safe-tint); }
.stack .base-row.on::before { background: var(--safe); }
.stack .base-row.on .layer-step { color: var(--safe); }
.stack-pick {
	flex: 1;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	min-width: 0;
	appearance: none;
	background: none;
	border: 0;
	padding: 0;
	font: inherit;
	color: inherit;
	text-align: left;
	cursor: pointer;
}
.stack-pick:hover { color: var(--text); }
/* The shape itself, so a row is recognised by what it covers rather than only by its name.
   On the void, because coverage is white and needs something to be white against. */
.stack-thumb {
	flex: none;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: 3px;
	overflow: hidden;
}
.stack-thumb canvas { max-width: 100%; max-height: 100%; display: block; }
.stack-said { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.stack-said .layer-name { font-size: 12.5px; }

/* The mask's own settings, under the row they belong to and nowhere else: they change the
   coverage everything above them reads. */
.row-shape {
	flex: 1 0 100%;
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	margin-top: 0.4rem;
	padding-top: 0.4rem;
	border-top: 1px solid var(--line);
}
.row-shape .adjust-slider { grid-template-columns: 54px 1fr 36px; font-size: 11.5px; }
.row-shape .tool-button { align-self: flex-start; padding: 0.2rem 0.5rem; font-size: 11.5px; }

/* What was done to the picture, as against what is being worked on. Shut by default: it
   answers a question nobody has while they are painting. */
.history { margin-top: 0.2rem; }
.history > summary {
	font-size: 11px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--faint);
	cursor: pointer;
	list-style: none;
}
.history > summary::before { content: '▸ '; }
.history[open] > summary::before { content: '▾ '; }
.history > summary:hover { color: var(--dim); }
.history .layer-stack { margin-top: 0.4rem; }

/* Mask mode. With a mask row selected every control on this panel is scoped to one area,
   which is too large a difference to leave unsaid: the safelight tokens are redefined here
   and everything inside reads them, so the sliders, the prompt box and the buttons all go
   cool together without being recoloured rule by rule. The picture keeps its own amber. */
.side.mask-mode {
	--safe: var(--pick);
	--safe-hi: var(--pick-hi);
	--safe-tint: var(--pick-tint);
	--safe-line: var(--pick-line);
}
.side.mask-mode .composer { border-color: var(--pick-line); }
/* Lit rather than dimmed: with a mask selected these are the cards it can be run through,
   and the rest of the list has already gone quiet. */
.flow.slim.lit { border-color: var(--pick-line); }
.flow.slim.lit .flow-rank { color: var(--pick); }
/* The one combination this page can see coming: a mask selected and a card that takes
   none. The button says which to choose rather than composing a run that gets refused. */
button.primary.waiting {
	background: transparent;
	color: var(--pick);
	border: 1px solid var(--pick-line);
}
button.primary.waiting:hover { background: var(--pick-tint); color: var(--pick); }

/* Naming a region instead of painting it. Cool like the rest of the block, because what
   comes back is a region and not a print, however much of a generation it costs. */
.region-find { display: flex; align-items: stretch; gap: 0.3rem; }
.region-find input {
	flex: 1;
	min-width: 0;
	padding: 0.35rem 0.5rem;
	font: inherit;
	font-size: 12.5px;
	color: var(--text);
	background: var(--raised);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
}
.region-find input:focus-visible { border-color: var(--pick-line); outline: none; }
.region-find .layer-act { padding: 0.35rem 0.5rem; border: 1px solid transparent; }
.region-find .layer-act.on {
	color: var(--pick);
	border-color: var(--pick-line);
	background: var(--pick-tint);
	border-radius: var(--r-1);
}
.region-find .tool-button { padding: 0.35rem 0.6rem; }
.find-note { margin: 0; font-size: 11.5px; color: var(--faint); }
.find-note.err { color: var(--err); }

/* Where an op applies. Quiet until it says something other than "whole picture", so a
   stack of global adjustments does not read as a row of controls demanding attention. */
.layer-bind {
	appearance: none;
	/* Narrow on purpose: the row's first duty is naming the step, and the caption already
	   ends in "in hair" when it is scoped. This is the control, not the readout. */
	max-width: 4.6rem;
	padding: 0.1rem 0.3rem;
	font-family: var(--font-data);
	font-size: 10px;
	color: var(--faint);
	background: transparent;
	border: 1px solid transparent;
	border-radius: var(--r-0);
	cursor: pointer;
}
.layer-bind:hover { border-color: var(--line-2); color: var(--text); }
.layer-bind:focus-visible { border-color: var(--pick-line); color: var(--text); outline: none; }
.layer-row .layer-bind:not([value=""]) { color: var(--pick); }

.edit-open { margin: 0 0 0.9rem; }
.edit-chain { border-top: 1px solid var(--line); padding-top: 0.7rem; }

/* --- the lineage: the chain of prints this one descends from ----------------- */

/* Not the layers: those are what the editor did to this picture, these are the prints
   it came from. One hairline behind the stack, newest first, the current one lit. */
.lineage { display: flex; flex-direction: column; gap: 0.35rem; }
.lineage .eyebrow { margin-bottom: 0.15rem; }
.chain { position: relative; display: flex; flex-direction: column; gap: 0.35rem; }
.chain::before {
	content: '';
	position: absolute;
	left: 21px;
	top: 14px;
	bottom: 14px;
	width: 1px;
	background: var(--line-2);
}
.chain-node {
	position: relative;
	display: flex;
	align-items: center;
	gap: 0.55rem;
	padding: 0.25rem 0.35rem;
	border-radius: var(--r-1);
}
.chain-node:hover { background: var(--raised); }
/* Made from the same source as the print on the canvas: beside the line, not on it,
   so a fork reads as one from either branch. */
.chain-node.fork { margin-left: 1.1rem; }
.chain-thumb {
	width: 30px;
	height: 30px;
	flex: none;
	border-radius: 5px;
	object-fit: cover;
	border: 1px solid var(--line-2);
	background: var(--void);
}
.chain-thumb.chain-file {
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--faint);
	font-size: 13px;
}
.chain-node.on .chain-thumb { border-color: var(--safe-line); box-shadow: 0 0 10px -2px var(--safe-tint); }
/* A print that landed while the picture was being worked on: it waits in the chain
   rather than taking the canvas from under the pointer. */
.chain-node.fresh .chain-thumb {
	border-color: var(--safe-line);
	box-shadow: 0 0 0 2px var(--safe-tint), 0 0 14px -2px var(--safe-tint);
}
.chain-name {
	flex: 1;
	min-width: 0;
	font-size: 12.5px;
	color: var(--text);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.chain-node.on .chain-name { color: var(--safe); }
/* The whole row is the way onto the canvas; the arrow beside it keeps its own click. */
a.chain-name::after { content: ''; position: absolute; inset: 0; }
.chain-meta { font-family: var(--font-data); font-size: 10px; color: var(--faint); }
.chain-open { position: relative; padding: 0 0.2rem; font-size: 12px; color: var(--faint); }
.chain-open:hover { color: var(--safe); }
.chain-node.developing .chain-thumb {
	background: radial-gradient(circle at 50% 58%, var(--safe-tint), transparent 62%);
	animation: develop 2.4s ease-in-out infinite;
}

/* Hovering a node tells the story of that step, out of what that job actually stored. */
.chain-card {
	display: none;
	position: absolute;
	right: 0;
	top: calc(100% + 4px);
	z-index: 30;
	width: 232px;
	padding: 0.6rem 0.7rem;
	background: var(--panel);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-pop);
	font-size: 12px;
}
.chain-node:hover .chain-card, .chain-node:focus-within .chain-card { display: block; }
.chain-said { margin: 0.35rem 0 0; color: var(--dim); font-size: 11.5px; }
.chain-card .ops {
	margin: 0.35rem 0 0;
	padding: 0;
	list-style: none;
	font-family: var(--font-data);
	font-size: 10.5px;
	color: var(--dim);
}
.chain-card .ops li { padding: 2px 0; border-top: 1px solid var(--line); }

/* --- the orbit picker ------------------------------------------------------- */

/* Collapsed until it is wanted: on the plain edit card the light picker is one way of
   working among several, and an open panel of sliders would read as required. */
.picker { margin-top: 0.9rem; min-width: 0; }
.picker-toggle {
	display: flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.32rem 0.65rem 0.32rem 0.5rem;
	background: var(--raised);
	color: var(--dim);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	font: inherit;
	font-size: 12px;
	cursor: pointer;
}
.picker-toggle::before { content: '\25B8'; color: var(--safe); font-size: 10px; }
.picker-toggle[aria-expanded="true"] { color: var(--text); border-color: var(--safe-line); }
.picker-toggle[aria-expanded="true"]::before { content: '\25BE'; }

/* Wrapping rather than two fixed columns: the widget lives in a panel that is dragged
   wider and narrower and in a palette of its own, so the dials sit beside the orbit
   while there is room for them and drop under it when there is not. */
.picker-body {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: 0.9rem 1.1rem;
	margin-top: 0.6rem;
	padding: 0.85rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.picker-stage { flex: 0 1 208px; min-width: 0; max-width: 260px; }
/* touch-action, or dragging the preview scrolls the page instead of orbiting. */
.picker-view {
	display: block;
	width: 100%;
	height: auto;
	border-radius: var(--r-1);
	background: var(--void);
	touch-action: none;
	cursor: ew-resize;
}

.picker-dials {
	flex: 1 1 220px;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
}
.picker-dial { display: flex; align-items: center; gap: 0.6rem; margin: 0; min-width: 0; }
.picker-dial-name {
	flex: 0 1 5.5rem;
	min-width: 3.4rem;
	font-size: 11.5px;
	color: var(--faint);
}
.picker-dial input[type="range"] { flex: 1; min-width: 0; width: auto; }
.picker-dial-value {
	width: 3.6rem;
	flex: none;
	font-family: var(--font-data);
	font-size: 11.5px;
	text-align: right;
	color: var(--text);
}
/* The colour dial shows its own colour, because a hue in degrees means nothing. */
.picker-swatch {
	height: 0.9rem;
	border-radius: 4px;
	border: 1px solid var(--line-2);
	color: transparent;
}

/* What will be written, before it is written. A line of its own whatever the width: a
   phrase in a column a few characters wide wraps one letter to a line. */
.picker-out {
	flex: 1 1 100%;
	min-width: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
}
.picker-out code {
	padding: 0.4rem 0.55rem;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	font-size: 12px;
	color: var(--text);
	word-break: break-word;
}
.picker-caption { font-size: 11.5px; color: var(--dim); }
/* The phrase as the prompt actually carries it, readable with the panel shut: it is
   part of what will be sent, and it is no longer sitting in the box to be read there. */
.picker-said {
	margin: 0.35rem 0 0;
	font-family: var(--font-data);
	font-size: 11.5px;
	line-height: 1.45;
	color: var(--dim);
	overflow-wrap: anywhere;
}
.picker-said:empty { display: none; }
.picker-note { flex: 1 1 100%; min-width: 0; margin: 0; font-size: 11.5px; color: var(--faint); }

/* --- datasets: runs, a run's stages, and the form that starts one ---------- */

.factory-runs { display: flex; flex-direction: column; gap: 0.6rem; }
.factory-run {
	padding: 0.85rem 1.1rem 0.95rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.factory-run.live { border-color: var(--line-2); }
.factory-run-head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.7rem; }
.factory-run-head .mono { font-size: 12.5px; }
.factory-recipe { font-weight: 600; }
.factory-when { margin-left: auto; font-size: 12px; }
.factory-pause { margin: 0.45rem 0 0; font-size: 12.5px; }
.factory-note { margin: 0.6rem 0 0; font-size: 12px; }
.factory-run .error { margin: 0.6rem 0 0; }

.factory-facts { display: flex; flex-wrap: wrap; gap: 0.5rem 1.8rem; margin: 0.75rem 0 0; }
.factory-facts div { display: flex; flex-direction: column; min-width: 0; }
.factory-facts .wide { flex: 1 1 260px; }
.factory-facts dt {
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--faint);
}
.factory-facts dd { margin: 0; font-size: 12.5px; }
.factory-facts .bar { display: block; max-width: 360px; }

.factory-acts {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem;
	margin-top: 0.85rem;
	padding-top: 0.75rem;
	border-top: 1px solid var(--line);
}
.factory-acts form { margin: 0; }
/* Cancelling is the one that cannot be taken back, so it stands apart from the rest. */
.factory-acts .factory-cancel { margin-left: auto; }
.factory-resume { display: flex; align-items: center; gap: 0.45rem; }
.factory-resume label { margin: 0; white-space: nowrap; }
.factory-resume input { width: 7.5em; padding: 0.4rem 0.55rem; font-family: var(--font-data); font-size: 12.5px; }

/* A run's stages: a contact sheet with a line of state under each picture. */
.stage-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
	gap: 0.7rem;
}
.stage-tile { margin: 0; min-width: 0; }
.stage-shot {
	display: block;
	aspect-ratio: 1;
	border-radius: var(--r-1);
	overflow: hidden;
	background: var(--void);
	border: 1px solid var(--line);
}
.stage-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.stage-shot .dev-blank { height: 100%; }
a.stage-shot:hover { border-color: var(--line-2); }
.stage-tile.queued .stage-shot { border-color: var(--safe-line); }
.stage-tile.skipped .stage-shot img, .stage-tile.failed .stage-shot img { opacity: 0.4; }
.stage-empty {
	display: block;
	height: 100%;
	background: repeating-linear-gradient(135deg, transparent 0 7px, rgba(255, 255, 255, 0.025) 7px 14px);
}
.stage-tile figcaption { display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem 0.4rem; margin-top: 0.4rem; }
.stage-tile .pill { padding: 0 0.45rem; font-size: 9.5px; }
.stage-label {
	flex: 1 1 100%;
	min-width: 0;
	font-size: 12px;
	font-weight: 500;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.stage-why, .stage-caption {
	flex: 1 1 100%;
	font-size: 11px;
	line-height: 1.4;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.stage-why { color: var(--dim); }
.stage-tile.failed .stage-why { color: var(--err); }
.stage-caption { font-family: var(--font-data); font-size: 10.5px; color: var(--faint); }

.factory-new {
	max-width: 880px;
	padding: 1rem 1.2rem 1.1rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.factory-step + .factory-step { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.factory-step > .eyebrow, .factory-step-head { margin-bottom: 0.55rem; }
.factory-step-head { display: flex; align-items: baseline; }
.factory-close { margin-left: auto; font-size: 12px; }
.factory-close:hover { color: var(--text); }
.factory-choices { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.factory-choices .flow.slim { max-width: 320px; }
.factory-choices .flow-verb { font-weight: 600; }
.factory-description {
	margin: 0.7rem 0 0;
	max-width: 72ch;
	color: var(--dim);
	font-size: 12.5px;
	overflow-wrap: anywhere;
}
.factory-step .field + .field, .factory-picture + .field, .factory-carried { margin-top: 0.8rem; }
.factory-step .field .hint { display: block; margin-top: 0.3rem; font-size: 11.5px; color: var(--faint); }
.factory-step .attach { max-width: 520px; }
.factory-step select { max-width: 520px; }
.factory-carried { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.factory-missing { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; margin: 0.7rem 0 0; font-size: 12px; }
.factory-run-fields { display: flex; flex-wrap: wrap; gap: 0.8rem; }
.factory-step .factory-run-fields .field { flex: 0 1 220px; margin-top: 0; }
.factory-run-fields #factory-cap { font-family: var(--font-data); }
.factory-estimate { margin: 0.8rem 0 0; font-size: 13px; }
.factory-new .error { margin: 0.7rem 0 0; }
.factory-go { display: flex; flex-wrap: wrap; align-items: center; gap: 0.8rem; margin-top: 0.9rem; font-size: 12.5px; }

/* --- chat: the thread, the box under it, and what is remembered ------------ */

.chat { display: flex; flex-direction: column; min-height: calc(100vh - 5.8rem); }
/* A reply can hold a line nobody can break, and a grid track grows to fit one: without
   this the whole page scrolls sideways on a phone instead of the code block doing it. */
.shell:has(.chat) main { min-width: 0; }

.chat-bar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.4rem 0.9rem;
	padding-bottom: 0.7rem;
	border-bottom: 1px solid var(--line);
	font-size: 12.5px;
}
.chat-bar form { margin: 0; }
.chat-rename { flex: 1 1 14rem; min-width: 0; display: flex; align-items: center; gap: 0.5rem; margin: 0; }
.chat-rename input {
	padding: 0.25rem 0.45rem;
	background: transparent;
	border-color: transparent;
	font-size: 14px;
	font-weight: 600;
}
.chat-rename input:hover { border-color: var(--line-2); }
.chat-rename button { opacity: 0; }
.chat-rename:hover button, .chat-rename:focus-within button { opacity: 1; }
.chat-act { color: var(--dim); }
.chat-act:hover { color: var(--text); }

.chat-threads { position: relative; }
.chat-threads summary { color: var(--dim); cursor: pointer; list-style: none; }
.chat-threads summary::-webkit-details-marker { display: none; }
.chat-threads summary:hover { color: var(--text); }
.chat-threads ul {
	position: absolute;
	z-index: 3;
	margin: 0.4rem 0 0;
	padding: 0.4rem;
	right: 0;
	max-height: 60vh;
	width: min(340px, calc(100vw - 2rem));
	overflow-y: auto;
	list-style: none;
	background: var(--panel);
	border: 1px solid var(--line-2);
	border-radius: var(--r-2);
	box-shadow: var(--shadow-pop);
}
.chat-threads li { display: flex; align-items: baseline; gap: 0.6rem; padding: 0.3rem 0.5rem; border-radius: var(--r-1); }
.chat-threads li:hover { background: var(--raised); }
.chat-threads li.on { background: var(--raised); }
.chat-threads li a { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-threads li span { font-size: 11px; white-space: nowrap; }

.chat-log {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 1.1rem;
	max-width: 76ch;
	width: 100%;
	padding: 1.4rem 0 1.6rem;
}
/* Flex items are as wide as their content unless told otherwise, and one long line in a
   code block would push the whole page sideways on a phone. */
.chat-log > *, .chat-live > * { min-width: 0; max-width: 100%; }
.chat-empty { margin: 2.5rem 0 0.5rem; color: var(--dim); text-align: center; font-size: 1.05rem; }

.chat-said { display: flex; justify-content: flex-end; }
.chat-bubble {
	max-width: 85%;
	padding: 0.55rem 0.85rem;
	background: var(--raised);
	border-radius: var(--r-2);
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

.chat-reply { max-width: 100%; }
.chat-md { overflow-wrap: anywhere; }
.chat-md > *:first-child { margin-top: 0; }
.chat-md > *:last-child { margin-bottom: 0; }
.chat-md p { margin: 0 0 0.7rem; }
.chat-md h1, .chat-md h2, .chat-md h3 { font-size: 14px; font-weight: 600; margin: 1.1rem 0 0.5rem; }
.chat-md ul, .chat-md ol { margin: 0 0 0.7rem; padding-left: 1.3rem; }
.chat-md li { margin: 0.2rem 0; }
.chat-md a { color: var(--safe); }
.chat-md blockquote {
	margin: 0 0 0.7rem;
	padding-left: 0.8rem;
	border-left: 2px solid var(--line-2);
	color: var(--dim);
}
.chat-md code { background: var(--void); border-radius: 4px; padding: 0 0.25rem; }
.chat-md pre {
	margin: 0 0 0.7rem;
	padding: 0.7rem 0.85rem;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	overflow-x: auto;
}
.chat-md pre code { background: none; padding: 0; }
.chat-md table { border-collapse: collapse; margin: 0 0 0.7rem; }
.chat-md th, .chat-md td { padding: 0.3rem 0.6rem; border: 1px solid var(--line); text-align: left; }
/* The reply before chat.js has drawn it, and all of it when there is no JavaScript. */
.chat-raw { margin: 0; font: inherit; white-space: pre-wrap; overflow-wrap: anywhere; }

/* What a tool did, as one line. The result is machine text and stays shut until asked
   for, because a job listing in the middle of a conversation buries the sentences. */
.chat-tool { font-size: 12px; color: var(--faint); }
.chat-tool summary { cursor: pointer; list-style: none; }
.chat-tool summary::before { content: '·'; margin-right: 0.45rem; color: var(--line-2); }
.chat-tool.bare::before { content: '·'; margin-right: 0.45rem; color: var(--line-2); }
.chat-tool summary::-webkit-details-marker { display: none; }
.chat-tool summary:hover { color: var(--dim); }
.chat-tool.bare { margin: 0; }
.chat-tool-raw {
	margin: 0.5rem 0 0 1rem;
	padding: 0.6rem 0.75rem;
	max-height: 18rem;
	overflow: auto;
	background: var(--void);
	border: 1px solid var(--line);
	border-radius: var(--r-1);
	font-size: 11.5px;
	color: var(--dim);
	white-space: pre-wrap;
}

/* A proposed memory, marked rather than lit: the pending reply is the live thing on
   this screen and nothing else competes with it. */
.chat-chip {
	padding: 0.7rem 0.9rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-left: 2px solid var(--safe-line);
	border-radius: var(--r-2);
}
.chat-chip.done { border-left-color: var(--line-2); }
.chat-chip-head { margin: 0 0 0.35rem; }
.chat-chip-was { margin: 0 0 0.25rem; font-size: 12px; color: var(--faint); text-decoration: line-through; }
.chat-chip-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-chip-done { margin: 0.5rem 0 0; font-size: 12.5px; }
.chat-chip .error { margin: 0.6rem 0 0; }
.chat-chip-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin-top: 0.7rem; }
.chat-chip-acts form { margin: 0; }
.chat-chip-acts button.secondary { padding: 0.28rem 0.7rem; font-size: 12.5px; }
.chat-chip-edit summary { font-size: 12.5px; color: var(--dim); cursor: pointer; list-style: none; }
.chat-chip-edit summary::-webkit-details-marker { display: none; }
.chat-chip-edit summary:hover { color: var(--text); }
.chat-chip-edit form { margin-top: 0.5rem; width: 100%; }
.chat-chip-edit textarea { margin-bottom: 0.5rem; font-size: 13px; }

.chat-live { display: flex; flex-direction: column; gap: 1.1rem; }
/* The one glow on this screen: something is happening, and it is here. */
.chat-waiting {
	display: inline-flex;
	align-items: center;
	gap: 0.55rem;
	padding: 0.55rem 0.9rem;
	border: 1px solid var(--safe-line);
	border-radius: var(--r-2);
	background: var(--safe-tint);
	box-shadow: 0 0 22px rgba(244, 166, 59, 0.10);
}
.chat-broke { padding: 0.7rem 0.9rem; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-2); }
.chat-broke p { margin: 0 0 0.5rem; }
.chat-broke-why { font-size: 11.5px; color: var(--faint); overflow-wrap: anywhere; }

/* The box stays where the thumb is, whatever the thread has grown to. */
.chat-composer {
	position: sticky;
	bottom: 0;
	max-width: 76ch;
	width: 100%;
	margin: 0;
	padding: 0.85rem 0 1rem;
	background: var(--ground);
	border-top: 1px solid var(--line);
}
.chat-composer .error { margin: 0 0 0.6rem; }
.chat-box { display: flex; align-items: flex-end; gap: 0.6rem; }
.chat-box textarea {
	resize: none;
	overflow-y: auto;
	min-height: 2.5rem;
	max-height: 320px;
	padding: 0.65rem 0.8rem;
	background: var(--panel);
	border-radius: var(--r-2);
	line-height: 1.5;
}
.chat-box textarea:disabled { color: var(--faint); }
.chat-box button.primary { padding: 0.62rem 1.1rem; }
.chat-hint { margin: 0.45rem 0 0; font-size: 11px; color: var(--faint); }

/* A thread with nothing in it is not a transcript with a bar under it: the box is the
   page, centred and deep enough to write in. The log carries `empty`; it and the pseudo
   element share the space over and under the box, the lower share a little bigger so the
   box sits above the middle rather than on it. */
.chat:has(.chat-log.empty)::after { content: ''; flex: 1.25 1 0; }
.chat-log.empty {
	justify-content: flex-end;
	max-width: 44rem;
	margin-inline: auto;
	padding-bottom: 0.3rem;
}
.chat-log.empty ~ .chat-composer {
	position: static;
	max-width: 44rem;
	margin-inline: auto;
	padding: 0;
	border-top: none;
}
/* Send sits in the box rather than beside it: at this size the two read as one thing,
   and the box keeps the whole width on a phone. */
.chat-log.empty ~ .chat-composer .chat-box { position: relative; }
.chat-log.empty ~ .chat-composer .chat-box textarea {
	min-height: 9rem;
	font-size: 15px;
	padding: 0.85rem 1rem 3.4rem;
}
.chat-log.empty ~ .chat-composer .chat-box button.primary {
	position: absolute;
	right: 0.7rem;
	bottom: 0.7rem;
}

/* --- memory: what is carried into every thread ----------------------------- */

.memory { max-width: 76ch; }
.memory-pending { display: flex; flex-direction: column; gap: 0.6rem; }
.memory-store { display: flex; flex-direction: column; gap: 0.7rem; }
.memory-what { margin: 0; color: var(--dim); font-size: 12.5px; }
.memory-none { margin: 0; color: var(--dim); }
.memory-store form { margin: 0; }
.memory-store textarea { resize: vertical; }
.memory-acts { display: flex; align-items: center; gap: 0.8rem; margin-top: 0.4rem; }
.memory-acts .mono { font-size: 11px; }

.memory-entries { display: flex; flex-direction: column; gap: 0.6rem; margin: 0; padding: 0; list-style: none; }
.memory-entry {
	display: flex;
	align-items: flex-start;
	gap: 0.8rem;
	padding: 0.7rem 0.9rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.memory-entry > form:first-child { flex: 1; min-width: 0; }
.memory-entry textarea { background: var(--void); font-size: 13px; }
.memory-delete button:hover { color: var(--err); }
.memory-entry .error { flex-basis: 100%; }

/* --- the questions that draft a voice -------------------------------------- */

.quiz { max-width: 76ch; }
.quiz-items { display: flex; flex-direction: column; gap: 0.5rem; }
.quiz-item {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	padding: 0.7rem 0.9rem;
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-2);
}
.quiz-ask { margin: 0; font-size: 13px; }
/* Five columns rather than a flex row, so the anchors line up down the page and the
   scale reads as one scale rather than five words per question. */
.quiz-scale { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0.4rem; }
.quiz-choice { display: flex; flex-direction: column; gap: 0.45rem; }
.quiz-item label {
	display: flex;
	align-items: center;
	gap: 0.4rem;
	margin: 0;
	font-size: 12.5px;
	letter-spacing: 0;
	color: var(--dim);
	white-space: nowrap;
	cursor: pointer;
}
/* Every other input on the platform fills its row; a radio has to stay a radio. */
.quiz-item input[type="radio"] { width: auto; margin: 0; flex: none; }
.quiz-choice label { white-space: normal; }
.quiz-item input:checked + span { color: var(--text); }
.quiz-tail { display: flex; flex-direction: column; gap: 0.5rem; }
.quiz-tail textarea { resize: vertical; }
.quiz-back { color: var(--dim); font-size: 12.5px; }
.quiz-door { color: var(--safe); font-size: 12.5px; }
.quiz-said { display: flex; flex-direction: column; gap: 0.25rem; }
.quiz-said p { margin: 0; color: var(--dim); font-size: 12.5px; }

/* --- the page with nothing behind it: sealed, wrong key, no agent ----------- */

.chat-shut { max-width: 56ch; margin: 12vh auto 0; }
.chat-shut h1 { margin-bottom: 0.8rem; }
.chat-shut p { color: var(--dim); }

/* --- small screens -------------------------------------------------------- */

@media (max-width: 900px) {
	/* minmax(0, 1fr), as the wide shell has it: a bare 1fr's floor is the widest child's
	   content, so the model strip or the jobs table made the whole page scroll sideways. */
	.shell { grid-template-columns: minmax(0, 1fr); }
	/* A table cannot shrink below its columns, so it scrolls in its own box instead. */
	table.jobs { display: block; overflow-x: auto; }
	.rail {
		position: static;
		height: auto;
		flex-direction: row;
		align-items: center;
		gap: 1rem;
		padding: 0.6rem 1rem;
		border-right: none;
		border-bottom: 1px solid var(--line);
		overflow-x: auto;
	}
	.rail nav { flex-direction: row; gap: 0.4rem; flex: 1; min-width: 0; overflow-x: auto; }
	.nav-group { flex-direction: row; gap: 0.1rem; }
	.nav-eyebrow { display: none; }
	.nav-item { padding: 0.45rem; }
	.nav-item::before { inset: auto 0 0 0; width: auto; height: 2px; top: auto; }
	.nav-badge { display: none; }
	/* There is no hover on a touch screen and no room in a bar across the top: the
	   page's own New thread is how a thread is started here. */
	.nav-new { display: none; }
	.rail-foot { border-top: none; padding: 0; margin-left: auto; flex: none; }
	.rail-user { display: none; }
	.logout button { white-space: nowrap; }
	main { padding: 1.3rem 1rem 3rem; }
	/* The rail is a bar across the top here, so the picture has that much less room. */
	.viewer { --stage-drop: 16rem; }
	.shelf-cards { grid-template-columns: repeat(auto-fill, minmax(142px, 1fr)); gap: 0.5rem; }
	.composer-row { flex-direction: column; }
	.composer-holder { width: 100%; }
	.composer-foot .popover { left: 0; right: auto; }
	.stage-grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 0.5rem; }
	/* The rail is a bar across the top here, so the thread has the whole window and
	   the box sits on the bottom edge of it. */
	.chat { min-height: calc(100vh - 8.5rem); }
	.chat-composer { padding-bottom: 0.7rem; }
	/* The window is short here, so the empty thread's box is deep without filling it. */
	.chat-log.empty ~ .chat-composer .chat-box textarea { min-height: 6.5rem; }
	.chat-bubble { max-width: 92%; }
	.chat-threads ul { left: 1rem; right: 1rem; width: auto; }
	/* The name gets its own row rather than being cut to a few characters. */
	.chat-rename { flex-basis: 100%; }
	.chat-rename button { opacity: 1; }
}

/* Too narrow for canvas and panel side by side: the panel goes under the stage and the
   page scrolls again. The floaters join the grid in the stage's own cell rather than
   floating over the window, or they land on top of the panel. */
@media (max-width: 1000px) {
	body:has(.editor-shell) { overflow: auto; }
	.editor-shell {
		grid-template-columns: minmax(0, 1fr);
		grid-template-rows: 50px minmax(320px, 62vh) auto;
		height: auto;
	}
	/* Placed by hand: a grid puts definite items down first, so the floaters claiming
	   the stage's cell would otherwise push the stage into a row of its own. */
	.edit-stage { grid-area: 2 / 1; }
	/* One column: there is no seam to drag. */
	.side-grip { display: none; }
	.side {
		grid-area: 3 / 1;
		border-left: none;
		border-top: 1px solid var(--line);
		overflow: visible;
	}
	.floaters {
		position: static;
		grid-area: 2 / 1;
		align-self: end;
		justify-self: center;
		z-index: 1;
		margin-bottom: 12px;
		max-width: calc(100% - 24px);
		transform: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.spinner, .dev-tile.running .dev-blank, .dev-bar.soon > span,
	.warmth.working .warmth-dot, .pill.running::before { animation: none; }
	.tile, .wf-card, .meter > span, .bar span, .dev-bar > span, .model-card,
	.stage-fade, .dev-shot { transition: none; }
}
