/* ==========================================================================
   PortfolioTree — the application layer
   ==========================================================================

   This file holds only what is specific to PortfolioTree. Everything generic —
   the palette, the type and spacing scales, the gradients, and the surface,
   form, button, table, prose and utility components — comes from
   design_0001.css, which is GENERATED and must not be hand-edited.

       design.json  --stylate generate-->  static/design_0001.css
       this file, written by hand,         static/app_0001.css

   To change a colour, a radius or a scale, edit design.json and regenerate.
   To change how a workbench page is laid out, edit this.

   What lives here is the test of whether the split is right: a rule belongs in
   this file only if a second application would not want it.
   ========================================================================== */

:root {
	/* A data view is wider than prose. The design system holds the readable
	   measure and the content width; how wide a table of ninety numbers should
	   be is this application's problem. */
	--workbench-width: 92rem;
}

/* ==========================================================================
   Base
   ========================================================================== */

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

html {
	scroll-behavior: smooth;
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	font-family: var(--font-family-base);
	font-size: 1rem;
	line-height: 1.65;
	color: var(--color-text);
	background: var(--color-background);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/* A single soft canopy of light behind the top of the page. */
body::before {
	content: '';
	position: fixed;
	inset: 0 0 auto 0;
	height: 40rem;
	pointer-events: none;
	z-index: 0;
	/* The colours and geometry come from the design system as --gradient-wash;
	   where it is painted is this application's decision. */
	background: var(--gradient-wash);
}

body > * { position: relative; z-index: 1; }

h1, h2, h3, h4, h5, h6 {
	color: var(--color-heading);
	font-weight: 650;
	letter-spacing: -.03em;
	line-height: 1.15;
	margin: 0 0 .5em;
	text-wrap: balance;
}

h1 { font-size: clamp(2.25rem, 1.4rem + 3.6vw, 4rem); }
h2 { font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2.05rem); }
h3 { font-size: 1.2rem; }

p { margin: 0 0 1.15rem; }

a {
	color: var(--color-accent);
	text-decoration-color: hsl(147 38% 62% / .35);
	text-underline-offset: .18em;
	transition: color .15s ease, text-decoration-color .15s ease;
}

a:hover { color: var(--color-accent-bright); text-decoration-color: currentColor; }

:focus-visible {
	outline: 2px solid var(--color-accent);
	outline-offset: 3px;
	border-radius: var(--radius-sm);
}

hr {
	border: 0;
	border-top: 1px solid var(--color-border);
	margin: 3rem 0;
}

/* ==========================================================================
   Site header
   ========================================================================== */

body > header {
	position: sticky;
	top: 0;
	z-index: 100;
	height: var(--layout-header-height);
	border-bottom: 1px solid var(--color-border);
	background: hsl(167 30% 5% / .72);
	backdrop-filter: blur(14px) saturate(140%);
	-webkit-backdrop-filter: blur(14px) saturate(140%);
}

body > header > nav {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1.5rem;
	height: 100%;
	max-width: var(--layout-content-width);
	margin: 0 auto;
	padding: 0 1.5rem;
}

/* The header and footer bracket main but cannot inherit its width, so they have
   to ask what page they are wrapping. Without this the logo sits 10rem inside
   the content edge on every workbench page, because they stop at
   --content-width while the page runs to --workbench-width. Browsers without
   :has() fall back to the narrower cap, which is the old behaviour rather than
   a broken one. */
body:has(main.workbench) > header > nav,
body:has(main.workbench) > footer > article { max-width: var(--workbench-width); }

body > header > nav > ul {
	display: flex;
	align-items: center;
	gap: 1.75rem;
	list-style: none;
	margin: 0;
	padding: 0;
}

#site-name {
	display: inline-flex;
	align-items: center;
	gap: .7rem;
	font-size: 1.15rem;
	font-weight: 600;
	letter-spacing: -.02em;
	color: var(--color-heading);
	text-decoration: none;
	/* Both forms: unprefixed user-select is inert in every Safari, so without
	   the prefixed one a long-press on the logo pops iOS's selection callout
	   instead of following the link. */
	-webkit-user-select: none;
	user-select: none;
}

#site-name img {
	display: block;
	width: 2rem;
	height: 2rem;
}

#site-name:hover { color: var(--color-accent-bright); }

/* The trademark rides as a proper superscript instead of a negative-margin hack. */
[data-trademark]::after {
	content: '™';
	font-size: .55em;
	font-weight: 500;
	vertical-align: super;
	margin-left: .1em;
	color: var(--color-text-muted);
}

/* ==========================================================================
   Layout
   ========================================================================== */

main {
	display: block;
	max-width: var(--layout-content-width);
	margin: 0 auto;
	padding: 0 1.5rem;
}

main > article {
	padding: clamp(3rem, 8vw, 6rem) 0 clamp(3rem, 6vw, 5rem);
}

/* ==========================================================================
   Hero
   ========================================================================== */

.hero { max-width: var(--measure); }

.hero h1 { margin-bottom: 1.25rem; }

.hero .lede { margin-bottom: 2rem; }

.hero .actions {
	display: flex;
	flex-wrap: wrap;
	gap: .75rem;
	margin: 2rem 0 0;
}

/* ==========================================================================
   Footer
   ========================================================================== */

footer {
	margin-top: 4rem;
	border-top: 1px solid var(--color-border);
	background: var(--color-surface);
}

footer > article {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
	gap: 2.5rem;
	max-width: var(--layout-content-width);
	margin: 0 auto;
	padding: 3rem 1.5rem 1.5rem;
}

footer h3 {
	font-family: var(--font-family-mono);
	font-size: .72rem;
	font-weight: 500;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--color-text-muted);
	margin-bottom: 1rem;
}

footer ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: .6rem;
}

footer ul ul { margin-top: .6rem; padding-left: 1rem; }

footer a {
	color: var(--color-text);
	text-decoration: none;
}

footer a:hover { color: var(--color-accent-bright); }

footer p { font-size: .875rem; color: var(--color-text-muted); }

#disclaimers li { font-size: .875rem; color: var(--color-text-muted); line-height: 1.55; }

#copyright-notice {
	grid-column: 1 / -1;
	margin: 0;
	padding-top: 1.5rem;
	border-top: 1px solid var(--color-border);
	font-family: var(--font-family-mono);
	font-size: .75rem;
	letter-spacing: .04em;
	color: var(--color-text-muted);
}

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 40rem) {
	body > header > nav { padding: 0 1rem; }
	main { padding: 0 1rem; }
	#site-name { font-size: 1rem; }
	.signature { flex-direction: column; align-items: flex-start; gap: .5rem; }
}

@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*, *::before, *::after { transition-duration: .01ms !important; }
}

/* ==========================================================================
   Auth pages
   ========================================================================== */

.auth {
	max-width: 26rem;
}

.auth h1 {
	font-size: clamp(1.75rem, 1.3rem + 1.8vw, 2.5rem);
	margin-bottom: 1rem;
}

.auth .lede {
	font-size: 1.05rem;
	margin-bottom: 2rem;
}

.auth-form {
	display: flex;
	flex-direction: column;
	gap: .4rem;
	margin: 0 0 1.75rem;
}

.auth-form label {
	font-family: var(--font-family-mono);
	font-size: .7rem;
	font-weight: 500;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--color-text-muted);
	margin-top: .85rem;
}

.auth-form label:first-child { margin-top: 0; }

.auth-form input {
	width: 100%;
	padding: .7rem .85rem;
	font-family: var(--font-family-base);
	font-size: 1rem;
	color: var(--color-heading);
	background: var(--color-background);
	border: 1px solid var(--color-border-strong);
	border-radius: var(--radius-md);
	transition: border-color .15s ease, box-shadow .15s ease;
}

.auth-form input::placeholder { color: var(--color-text-muted); }

.auth-form input:focus {
	outline: none;
	border-color: var(--color-primary);
	box-shadow: 0 0 0 3px hsl(147 30% 50% / .18);
}

/* :invalid matches before the field has been touched, which would paint an
   empty required field red on first paint. :user-invalid waits for interaction. */
.auth-form input:user-invalid {
	border-color: hsl(0 55% 60%);
}

.auth-form .button {
	margin-top: 1.25rem;
	justify-content: center;
}

.form-error {
	padding: .75rem 1rem;
	margin: 0 0 1.5rem;
	border: 1px solid hsl(0 40% 40%);
	border-left-width: 3px;
	border-radius: var(--radius-md);
	background: hsl(0 40% 40% / .12);
	color: hsl(0 60% 82%);
	font-size: .9rem;
}

/* The signed-out header carries no auth links by design; this is for the
   signed-in one. */
.session-actions {
	display: flex;
	align-items: center;
	gap: 1rem;
}

.session-actions form { margin: 0; }

.session-actions button {
	background: none;
	border: 0;
	padding: 0;
	font-family: var(--font-family-base);
	font-size: .9rem;
	color: var(--color-text-muted);
	cursor: pointer;
}

.session-actions button:hover { color: var(--color-accent-bright); }

/* Account deletion is irreversible, so its control should not look like the
   primary action on the page. */
.button-danger {
	background: transparent;
	border-color: hsl(0 40% 42%);
	color: hsl(0 60% 78%);
}

.button-danger:hover {
	background: hsl(0 45% 40%);
	border-color: hsl(0 45% 52%);
	color: var(--color-heading);
}

.form-notice {
	padding: .75rem 1rem;
	margin: 0 0 1.5rem;
	border: 1px solid var(--color-border-strong);
	border-left-width: 3px;
	border-left-color: var(--color-primary);
	border-radius: var(--radius-md);
	background: hsl(147 30% 50% / .10);
	color: var(--color-text);
	font-size: .9rem;
}

.auth h2 {
	font-size: 1.15rem;
	margin-top: 2.5rem;
}

/* Active sessions on the settings page. */
.session-list {
	list-style: none;
	margin: 0 0 1.5rem;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: .5rem;
}

.session {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: .85rem 1rem;
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background: var(--color-surface);
}

.session-current {
	border-color: var(--color-primary);
}

.session-what {
	display: flex;
	flex-direction: column;
	gap: .15rem;
	min-width: 0;
}

/* A user agent is long and arbitrary; it must not push the button off-screen. */
.session-what strong {
	color: var(--color-heading);
	font-size: .9rem;
	font-weight: 550;
	overflow-wrap: anywhere;
}

.session-what .muted {
	font-family: var(--font-family-mono);
	font-size: .7rem;
	letter-spacing: .04em;
}

.session-badge {
	flex-shrink: 0;
	font-family: var(--font-family-mono);
	font-size: .65rem;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--color-accent);
}

.session form { margin: 0; flex-shrink: 0; }
.session .button { padding: .4rem .8rem; font-size: .8rem; }
/* ==========================================================================
   Workbench — the composition editor and its results.
   Proposed additions to main_0009.css. Tokens come from main_0008.css.
   ========================================================================== */

.sr-only {
	position: absolute; width: 1px; height: 1px;
	padding: 0; margin: -1px; overflow: hidden;
	clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* --- page frame --------------------------------------------------------- */

.workbench {
	/* Wider than --content-width: this is a data view, not prose. */
	max-width: var(--workbench-width);
	margin: 0 auto;
	padding: 1.75rem 1.5rem 4rem;
	/* The page spaces its boxes, not the boxes themselves: the design system
	   puts spacing between children on the container, so a surface looks the
	   same wherever it lands. Without it the set editor's surfaces sat a pixel
	   apart, and each grid below reached for a margin of its own. */
	display: flex; flex-direction: column; gap: var(--layout-gap-lg);
}

.workbench-head {
	display: flex; align-items: flex-end; gap: 2rem; flex-wrap: wrap;
	padding-bottom: 1.25rem;
	border-bottom: 1px solid var(--color-border);
}
.workbench-head h1 { margin: .15rem 0 0; font-size: 1.5rem; color: var(--color-heading); }
.workbench-head .eyebrow { margin: 0; }

/* What this composition is pinned to. Always visible: a number computed
   against a different vintage is a different number. */
.pinned { display: flex; gap: 1.75rem; margin: 0; flex-wrap: wrap; }
.pinned div { display: flex; flex-direction: column; gap: .1rem; }
.pinned dt {
	font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase;
	color: var(--color-text-muted);
}
.pinned dd { margin: 0; font-size: .9375rem; color: var(--color-heading); }

.workbench-actions { margin-left: auto; display: flex; gap: .625rem; }

.workbench-grid {
	display: grid;
	grid-template-columns: minmax(24rem, 30rem) 1fr;
	gap: var(--layout-gap-lg);
	align-items: start;
}
@media (max-width: 68rem) {
	.workbench-grid { grid-template-columns: 1fr; }
}

/* --- panels ------------------------------------------------------------- */

.panel {
	background: var(--color-surface);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-lg);
	overflow: hidden;
}
.panel-head {
	display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
	padding: .875rem 1rem;
	border-bottom: 1px solid var(--color-border);
}
.panel-head h2 { margin: 0; font-size: .9375rem; color: var(--color-heading); font-weight: 600; }
.panel-head .muted { font-size: .75rem; }

/* --- tables ------------------------------------------------------------- */

/* Named .data-grid, not .grid. The design system uses .grid for a CSS-grid
   layout utility, and a <table class="grid"> therefore picked up
   display: grid — which detaches every row from its columns. A consumer
   adapts to the system's namespace rather than the other way round. */
.data-grid { width: 100%; border-collapse: collapse; font-size: .875rem; }
.data-grid th, .data-grid td { padding: .4375rem .75rem; text-align: left; }
.data-grid thead th {
	font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase;
	color: var(--color-text-muted); font-weight: 500;
	border-bottom: 1px solid var(--color-border);
	padding-top: .625rem; padding-bottom: .625rem;
}
.data-grid tbody th { font-weight: 400; color: var(--color-text); }
.data-grid tbody tr + tr th, .data-grid tbody tr + tr td { border-top: 1px solid var(--color-surface-raised); }
.data-grid tbody tr:hover th, .data-grid tbody tr:hover td { background: var(--color-surface-raised); }
/* Tabular figures so digits line up column-wise as values change. */
.data-grid .num { text-align: right; font-variant-numeric: tabular-nums; }
.data-grid .mono { font-family: var(--font-family-mono); font-size: .8125rem; }
.data-grid .dim { color: var(--color-text-muted); }
.data-grid tfoot th, .data-grid tfoot td {
	border-top: 1px solid var(--color-border-strong);
	color: var(--color-heading); font-weight: 600;
	padding-top: .5625rem; padding-bottom: .5625rem;
}

/* A short position reads differently at a glance, not only by its sign. */
.negative { color: var(--color-warning); }

/* --- weight inputs ------------------------------------------------------ */

/* No width: a control inside a data cell is sized by the design system, which
   fills the cell when the control is the only thing in it. This carries the
   typographic treatment a figure wants and nothing about its box. */
.weight {
	padding: .3125rem .5rem;
	text-align: right;
	font-family: var(--font-family-mono); font-size: .8125rem;
	font-variant-numeric: tabular-nums;
	color: var(--color-heading);
	background: var(--color-background);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm);
}
.weight:focus { outline: 2px solid var(--color-primary); outline-offset: 1px; border-color: transparent; }
.weight.negative { color: var(--color-warning); }

.row-remove {
	width: 1.5rem; height: 1.5rem; line-height: 1;
	color: var(--color-text-muted); background: none;
	border: 1px solid transparent; border-radius: var(--radius-sm);
	cursor: pointer;
}
.row-remove:hover { color: var(--color-heading); border-color: var(--color-border-strong); }

.add-asset { padding: .75rem 1rem; border-top: 1px solid var(--color-border); }
.add-asset select {
	width: 100%; padding: .4375rem .625rem;
	color: var(--color-text); background: var(--color-background);
	border: 1px solid var(--color-border); border-radius: var(--radius-sm);
	font-family: var(--font-family-base); font-size: .875rem;
}

/* --- segmented control -------------------------------------------------- */

.segmented {
	display: flex; gap: .25rem; margin: .875rem 1rem 0; padding: .25rem;
	border: 1px solid var(--color-border); border-radius: var(--radius-md);
	background: var(--color-background);
}
.segmented label { flex: 1; }
.segmented input { position: absolute; opacity: 0; pointer-events: none; }
.segmented span {
	display: block; padding: .375rem .5rem; text-align: center;
	font-size: .8125rem; color: var(--color-text-muted);
	border-radius: var(--radius-sm); cursor: pointer;
}
.segmented label:hover span { color: var(--color-text); }
.segmented input:checked + span { color: var(--color-background); background: var(--color-accent); font-weight: 600; }
.segmented input:focus-visible + span { outline: 2px solid var(--color-accent-bright); outline-offset: 2px; }

.method-note { margin: .625rem 1rem 0; font-size: .75rem; line-height: 1.5; }

/* --- results ------------------------------------------------------------ */

.results { display: flex; flex-direction: column; gap: var(--layout-gap-lg); }

.metrics {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
	gap: 1px;
	background: var(--color-border);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-lg);
	overflow: hidden;
}
.metric {
	display: flex; flex-direction: column; gap: .3125rem;
	padding: .875rem 1rem;
	background: var(--color-surface);
}
.metric-label {
	font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase;
	color: var(--color-text-muted);
}
.metric-value {
	font-size: 1.375rem; line-height: 1.1; color: var(--color-heading);
	font-variant-numeric: tabular-nums;
}
.metric-value .unit { font-size: .875rem; color: var(--color-text-muted); margin-left: .0625rem; }
.metric .dim { color: var(--color-text-muted); font-size: .625rem; }

/* A derived figure that has a published counterpart says so under itself. It
   sits inside the flex column .metric already establishes, so it needs no
   layout of its own — only a size that reads as an annotation rather than as a
   second number competing with the one above it. */
.metric-note {
	font-size: .625rem; line-height: 1.35;
	color: var(--color-text-muted);
	text-wrap: pretty;
}
.metric-note .mono { font-variant-numeric: tabular-nums; }

/* Unavailable is not zero: it renders as an em dash and says why on hover. */
.metric-value.unavailable { color: var(--color-text-muted); }

.bar-col { width: 6rem; }
.bar {
	display: block; height: .5rem; border-radius: 999px;
	width: var(--share);
	background: linear-gradient(90deg, var(--brand-from), var(--color-accent));
}

/* The weight bar reads as the quieter of the pair. Risk attribution is what
   the table is for, so capital is drawn as context for it rather than as its
   equal — same geometry, less contrast, and the eye lands on the divergence
   rather than on two competing stripes. */
.bar-weight { background: linear-gradient(90deg, var(--color-border-strong), var(--color-text-muted)); }

/* A short still moves the portfolio, so it draws a bar; the colour says which
   way rather than the length, because a negative length is not a thing. */
.bar-negative { background: linear-gradient(90deg, var(--color-border-strong), var(--color-warning)); }

/* --- the pinned universe, and how much of it is used -------------------- */

.universe .surface-head span { font-size: .75rem; }

.universe-stats {
	display: flex; flex-wrap: wrap; gap: 1.25rem;
	margin: 0; padding: .875rem 1rem 0;
}
.universe-stats div { display: flex; flex-direction: column; gap: .1875rem; }
.universe-stats dt {
	font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase;
	color: var(--color-text-muted);
}
.universe-stats dd {
	margin: 0; font-size: 1.125rem; color: var(--color-heading);
	font-variant-numeric: tabular-nums;
}
.universe-stats .unit { font-size: .75rem; color: var(--color-text-muted); margin-left: .0625rem; }

.universe-note {
	font-size: .75rem; line-height: 1.5; text-wrap: pretty;
	margin: 0; padding: .625rem 1rem 0;
}

/* One cell per asset, wrapping. A vintage of sixty assets is a legible block
   at this size and a composition's share of it is a shape rather than a
   fraction to work out. auto-fill rather than a fixed count so the block
   reflows with the panel instead of scrolling. */
.universe-strip {
	display: grid; grid-template-columns: repeat(auto-fill, minmax(.5rem, 1fr));
	gap: .1875rem; list-style: none;
	margin: 0; padding: .875rem 1rem 1rem;
}
.universe-cell { display: block; }
.universe-cell span[aria-hidden] {
	display: block; aspect-ratio: 1; border-radius: 2px;
	background: var(--color-border);
}
.universe-cell.is-held span[aria-hidden] {
	background: linear-gradient(135deg, var(--brand-from), var(--color-accent));
}

/* The strip is decoration over a list that is already stated in numbers above
   and in the table beside it, so motion here is never load-bearing. */
@media (prefers-reduced-motion: no-preference) {
	.universe-cell span[aria-hidden] { transition: background .15s ease; }
}

.methodology { font-size: .8125rem; line-height: 1.6; margin: 0; }
.methodology a { color: var(--color-accent); }

/* --- additions the ex ante pages need beyond the prototype -------------- */

.workbench-grid-wide { grid-template-columns: 1fr minmax(22rem, 28rem); }
@media (max-width: 68rem) { .workbench-grid-wide { grid-template-columns: 1fr; } }

.button-small { padding: .25rem .5rem; font-size: .75rem; }
.row-actions { text-align: right; }
.row-actions form { display: inline; }

.panel-note, .field-note {
	margin: 0; padding: .75rem 1rem;
	font-size: .75rem; line-height: 1.55;
	border-top: 1px solid var(--color-surface-raised);
}
.field-note { border-top: 0; padding: .375rem 0 0; }

.empty { margin: 0; padding: 1.25rem 1rem; color: var(--color-text-muted); font-size: .875rem; }
.empty a { color: var(--color-accent); }

.stacked-form { display: flex; flex-direction: column; gap: .875rem; padding: 1rem; }
/* min-width: 0 is load-bearing. A flex item defaults to min-width: auto, which
   refuses to shrink below its content's intrinsic width — and a text input's is
   about twenty characters. Chrome shrinks them anyway; Safari does not, so the
   second field in a row overflows its panel and crosses the border. */
.field { display: flex; flex-direction: column; gap: .3125rem; min-width: 0; }
.field-row { display: flex; gap: .875rem; }
.field-row .field { flex: 1; min-width: 0; }
.field label { font-size: .8125rem; color: var(--color-text-muted); }
.field input, .field select, .field textarea {
	padding: .4375rem .625rem;
	color: var(--color-heading); background: var(--color-background);
	border: 1px solid var(--color-border); border-radius: var(--radius-sm);
	font-family: var(--font-family-base); font-size: .875rem;
}
.field textarea { font-family: var(--font-family-mono); font-size: .75rem; line-height: 1.5; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
	outline: 2px solid var(--color-primary); outline-offset: 1px; border-color: transparent;
}
.form-actions { display: flex; justify-content: flex-end; }

/* An action row that is a direct child of .panel rather than of .stacked-form,
   which is where .form-actions gets its padding from. Without this the button
   sits flush against the panel's bottom and right edge. */
.panel-actions {
	display: flex; justify-content: flex-start;
	padding: 0 1rem 1.125rem;
}

/* A weight box that the method computed rather than the user typed. */
.weight[readonly] { color: var(--color-text-muted); background: var(--color-surface); cursor: not-allowed; }

/* --- assumption set editor ---------------------------------------------- */

.workbench-grid-single { grid-template-columns: 1fr; }

/* The editor is a column of panels rather than a grid: the correlation matrix
   wants every pixel of width it can get. The workbench spaces it from the head. */
#assumption-editor { display: flex; flex-direction: column; gap: var(--layout-gap-lg); }
#assumption-editor .panel + .panel { margin-top: 0; }

/* --- what sits directly inside a surface --------------------------------- */

/* A surface's header strip has an inner edge, and what follows the strip needs
   the same one or it runs into the border: the set editor's notes and the
   composition's seed field both did. */
.surface > .field-note { padding: var(--space-sm) var(--space-md) 0; }
.surface > .field-note:last-child { padding-bottom: var(--space-md); }
.surface > .field { padding: var(--space-sm) var(--space-md) 0; }
.surface > .field:last-child { padding-bottom: var(--space-md); }
/* The design system gives `section > table` a top margin, which suits a table
   in a document; but a surface is often a <section>, and a table that is a
   card's content should meet the card's header strip rather than leave a band
   under it. The design system's own table rule says as much — this belongs
   upstream in styleate, as `section > table:not(.surface > *)` or similar. */
.surface > table { margin-top: 0; }

/* --- the set editor's pages ---------------------------------------------- */

/* Tabs across the five pages. Each is a real page reached with hx-boost, so
   these are links, and the current one is marked by the server rather than by
   script state. */
.draft-nav ul {
	display: flex; flex-wrap: wrap; gap: var(--space-xs);
	margin: 0; padding: 0; list-style: none;
	border-bottom: var(--border-sm) solid var(--color-border);
}
.draft-nav a {
	display: block; padding: var(--space-sm) var(--space-md);
	margin-bottom: calc(-1 * var(--border-sm));
	color: var(--color-text-muted); text-decoration: none;
	border-bottom: 2px solid transparent;
}
.draft-nav a:hover { color: var(--color-heading); text-decoration: none; }
.draft-nav a.current { color: var(--color-heading); border-bottom-color: var(--color-primary); }
.draft-nav a:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; border-radius: var(--radius-sm); }

/* A click-to-edit cell is a button until it is clicked, so it reads as a
   number and still says it can be changed. The button fills the cell, which
   makes the whole cell the target. Reset from the browser's own button, which
   is what drew it as a grey box. */
.draft-table td.draft-editable { padding: .1875rem .25rem; }
.cell-open {
	appearance: none; display: block; width: 100%; min-height: 2rem;
	padding: .25rem .5rem;
	font: inherit; font-variant-numeric: tabular-nums; line-height: 1.4; text-align: inherit;
	color: var(--color-heading); background: transparent;
	border: var(--border-sm) solid transparent; border-radius: var(--radius-sm);
	cursor: text;
}
.cell-open:hover { border-color: var(--color-border-strong); background: var(--color-surface-raised); }
.cell-open:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
/* A hole to fill — an unstated correlation or yield. Dashed and muted, so it
   reads as absent rather than as wrong. */
.cell-missing .cell-open { border-style: dashed; border-color: var(--color-border-strong); color: var(--color-text-muted); }
/* A value the cell refused: outlined in the error colour, with the reason
   underneath in .cell-problem, which the one-page editor shares. */
.cell-invalid .cell-open { border-color: var(--color-error); }
.draft-editable .cell-problem { padding: 0 .5rem .25rem; text-align: left; white-space: normal; max-width: 24rem; }
/* The open editor takes the button's place and size. */
.draft-editable.editing input { width: 100%; min-width: 5rem; }
.draft-editable.num.editing input { text-align: right; }

/* Settings: one card per role, as many to a row as fit. */
.role-list {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
	gap: var(--layout-gap) var(--layout-gap-lg);
	margin: 0; padding: var(--space-md);
}
.role { display: flex; flex-direction: column; gap: var(--space-xs); }
.role dt { font-weight: 600; color: var(--color-heading); }
.role dd { margin: 0; font-family: var(--font-family-base); font-size: .8125rem; line-height: 1.5; }
.role form { display: flex; gap: var(--space-sm); align-items: center; margin-top: var(--space-xs); }
.role select { flex: 1; min-width: 0; }

/* The horizon is one number in a one-row table, which would otherwise spread
   its three cells across the whole card. */
.horizon-table { width: auto; }

/* The fitted parameters, laid out as the pinned facts in a page head are. */
.rate-params {
	display: flex; flex-wrap: wrap; gap: var(--space-md) var(--space-xl);
	margin: 0; padding: var(--space-md);
	border-bottom: var(--border-sm) solid var(--color-surface-raised);
}
.rate-params div { display: flex; flex-direction: column; gap: var(--space-xs); }
.rate-params dt {
	font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase;
	color: var(--color-text-muted);
}
.rate-params dd { margin: 0; font-size: 1.0625rem; color: var(--color-heading); font-variant-numeric: tabular-nums; }
.rate-params .unit { font-size: .75rem; color: var(--color-text-muted); }

/* Adding an asset: its four fields in a row, wrapping on a narrow page. */
.new-asset {
	display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-sm) var(--space-md);
	padding: var(--space-md);
	border-top: var(--border-sm) solid var(--color-surface-raised);
}
.new-asset .field { flex: 1 1 7rem; }
.new-asset .field:first-of-type { flex: 2 1 14rem; }

/* Wide content scrolls inside its own box. A ten-asset matrix is eleven
   columns, and the page itself must never scroll sideways. */
.grid-scroll { overflow-x: auto; }

/* Rows are taller than a read-only table because each cell holds a control:
   .4375rem around an input leaves the focus ring nowhere to go. */
.assets-table th, .assets-table td { padding: .5rem .75rem; }
/* Top, via the design system's token rather than a bare vertical-align, so a
   row overrides it by setting --cell-align rather than by out-specifying a
   rule. The name cell's slug sits under its input, which is what makes these
   rows tall; the action column opts back to middle so its button does not
   float at the top of one. */
.assets-table tbody { --cell-align: top; }
.assets-table .row-actions { --cell-align: middle; }
.assets-table input { width: 100%; }
.assets-table td.name-col { min-width: 12rem; }
.assets-table .slug {
	display: block; font-size: .6875rem; margin-top: .3125rem;
}

.corr-table th[scope="row"] { white-space: nowrap; font-weight: 500; }
.corr-head {
	font-size: .6875rem; font-weight: 500; line-height: 1.2;
	max-width: 5rem; white-space: normal;
}
/* Sized on the CELL, not on the input inside it, so the control can fill it.
   The padding applies only to the cells the design system does not take over —
   the diagonal and the blank lower half, which hold a span rather than a
   control. */
.corr-cell { padding: .3125rem; min-width: 5.25rem; }
.corr-input { text-align: right; }

/* The diagonal and the blank lower half are not controls, so they get centred
   in the space an input would occupy rather than sitting on the baseline. */
.corr-cell > span { display: inline-block; padding: .4375rem .25rem; }
.mirror { opacity: .45; }

/* A matrix is read by tracing a row to a column, which is hard when the row
   header has scrolled out of the box. */
.corr-table th[scope="row"] { position: sticky; left: 0; background: var(--color-surface); }
.corr-table tbody tr:hover th[scope="row"] { background: var(--color-surface-raised); }

/* Sized to its contents rather than stretched to the panel. A four-column
   matrix spread across 90rem puts the number you are comparing an inch from the
   one you are comparing it to, and the grid stops reading as a grid. */
.corr-table { width: auto; }
/* Row headings and values stay on one line; column headings wrap, which is
   what .corr-head's max-width is for. This rule used to match every th, which
   out-specified .corr-head's white-space, and a vintage of fifty-nine assets
   printed its column names on top of one another. */
.corr-table tbody th, .corr-table td { white-space: nowrap; }
/* Wrapped at spaces only, and each column as wide as its longest word. Capped
   at .corr-head's 5rem, names broke between any two letters — "INFLATIO / N" —
   and hyphens: auto cannot help: Chrome does not hyphenate a capitalized word,
   and every asset name starts with one. The matrix scrolls in its own box, so
   a wider column costs nothing the page can see. */
.corr-table thead th.corr-head { vertical-align: bottom; max-width: none; overflow-wrap: normal; }

/* The name column absorbs the slack instead, so the numeric columns sit
   together at their natural width, and the headers keep their unit on the same
   line as the word it belongs to. */
.assets-table { table-layout: auto; }
/* By class, not by position: a column was inserted to the left of the name and
   :first-child silently moved with it, squashing the name to a sliver. */
.assets-table .name-col { width: 100%; }
.assets-table thead th { white-space: nowrap; }
/* The column is capped, not the control: a control fills its cell's width even
   when the cell holds other things, so capping the input instead left it
   floating in a cell it did not fill. */
.assets-table td.name-col { max-width: 22rem; }

/* A cell whose value is part of a problem. The outline rather than a fill so
   the number stays readable. */
.cell-invalid .corr-input { outline: 2px solid var(--color-error); outline-offset: 1px; }
.row-problem input { border-color: var(--color-error); }
.cell-problem {
	display: block; margin-top: .25rem;
	font-size: .6875rem; color: color-mix(in srgb, var(--color-error), var(--color-heading) 55%);
}

.problems .empty.ok { color: var(--color-accent); }
.problem-list {
	list-style: none; margin: 0; padding: .75rem 1rem;
	display: flex; flex-direction: column; gap: .5rem;
	font-size: .8125rem; line-height: 1.5;
}
.problem-list li { display: flex; gap: .625rem; align-items: baseline; }
.problem-list .mono { flex: 0 0 auto; font-size: .6875rem; min-width: 9rem; }

/* The editor's action row sits beside the panel note, so it keeps the note's
   left edge rather than the flush-right of a form's submit row. */
#assumption-editor .panel-actions { padding-top: .25rem; }

/* Focus has to be visible on a grid of small boxes, and the ring must not be
   clipped by the scrolling container. */
.corr-input:focus-visible, .assets-table input:focus-visible {
	outline: 2px solid var(--color-primary); outline-offset: 1px; border-color: transparent;
}

.problem-summary {
	margin: 0; padding: .75rem 1rem .25rem;
	font-size: .8125rem; color: color-mix(in srgb, var(--color-error), var(--color-heading) 55%);
}

/* --- reordering the asset rows ------------------------------------------ */

/* The whole row drags, so the grip is an affordance rather than the handle:
   it says "this moves" without being the only place you can grab. */
.assets-table tbody.sortable > tr { cursor: grab; }
.assets-table tbody.sortable > tr:active { cursor: grabbing; }

/* Controls are not draggable surfaces — a drag started inside an input would
   fight text selection — so the pointer reverts over them. */
.assets-table tbody.sortable input,
.assets-table tbody.sortable button { cursor: auto; }
.assets-table tbody.sortable button { cursor: pointer; }

.drag-col { width: 1%; white-space: nowrap; padding-right: 0; }

.drag-icon {
	color: var(--color-text-muted);
	vertical-align: middle;
}

/* Where the row will land. Not a colour swap on the row itself: the row being
   dragged is a clone, and styling the placeholder is what shows the gap. */
.dragging > td,
.dragging > th { background: var(--color-surface-raised); opacity: .55; }

/* The keyboard equivalent of a drag. Small and quiet until the row is hovered
   or one of them is focused, so a ten-asset table is not twenty arrows. */
.move-buttons {
	display: inline-flex;
	flex-direction: column;
	margin-left: .125rem;
	vertical-align: middle;
}

.move {
	border: 0;
	background: none;
	padding: 0 .125rem;
	line-height: .85;
	font-size: .5rem;
	color: var(--color-text-muted);
	cursor: pointer;
	opacity: 0;
	transition: opacity var(--transition-fast) ease, color var(--transition-fast) ease;
}

.assets-table tbody tr:hover .move,
.move:focus-visible { opacity: 1; }

.move:hover { color: var(--color-accent); }

/* Always reachable by keyboard even before it is visible: opacity does not
   remove an element from the tab order, and the focus rule above reveals it
   the moment it is reached. */
.move:focus-visible {
	outline: var(--border-sm) solid var(--color-accent-bright);
	color: var(--color-accent-bright);
}

/* A vintage's read-only tables. No controls, so the rows can be tighter than
   the editor's and the figures sit on their own baseline. */
.assets-view th[scope="row"] { font-weight: 400; color: var(--color-heading); }
.assets-view td { white-space: nowrap; }

/* The name column takes the slack so the figures sit together, the same way
   the editor's does. */
.assets-view th[scope="row"] { width: 100%; }

/* --- a repaired correlation --------------------------------------------- */

/* How far a value moved, as a continuous tint rather than a set of classes
   with thresholds in them: --shade runs −1 (down) to +1 (up) and the sign
   picks the hue.
 
   Both tints are mixed INTO the surface rather than laid over it, so the
   result is a real colour on whichever theme is in force — the same rule
   works on a near-black panel and a near-white one, and neither needs a
   second declaration.
 
   Down is red and up is green, matching the direction convention the rest of
   the application already uses for a negative number. */
.corr-input[data-moved] {
	--shade-magnitude: max(var(--shade), -1 * var(--shade));
	--shade-hue: var(--color-success);
	background: color-mix(
		in srgb,
		var(--shade-hue) calc(var(--shade-magnitude) * 45%),
		var(--color-background)
	);
	/* Dotted, so a moved cell is still identifiable without colour — the
	   distinction has to survive a monochrome display and the commonest forms
	   of colour blindness, and red against green is the worst possible pair
	   for the latter. */
	border-style: dashed;
}

/* The sign of --shade cannot be tested directly, so the negative case is
   selected on the attribute the template writes for it. Set in a second rule
   rather than duplicating the whole thing. */
.corr-input[data-moved][style*="--shade: -"] { --shade-hue: var(--color-error); }

.corr-input[data-moved]:focus-visible { border-style: solid; }

/* --- recalculating ------------------------------------------------------

   Every figure on this page is derived, so a change to the policy or to the
   holdings makes all of them stale at once. htmx puts .htmx-request on
   whatever hx-indicator names for the life of the request, which is enough to
   say so in CSS alone: no script, and nothing left behind if a request fails.

   The stale numbers stay in place under the skeleton rather than being blanked.
   Replacing them with empty cards loses the reader's place in a table they were
   reading, and a figure that is about to be replaced is still better company
   than a hole. */

.recalculating {
	display: none;
	align-items: center; gap: .5rem;
	margin: 0 0 .75rem; padding: .5rem .75rem;
	font-size: .8125rem; color: var(--color-text-muted);
	background: var(--color-surface); border: 1px solid var(--color-border);
	border-radius: var(--radius-sm, 4px);
}
.htmx-request .recalculating,
.htmx-request.recalculating { display: flex; }

.spinner {
	width: .75rem; height: .75rem; flex: none;
	border: 2px solid var(--color-border-strong);
	border-top-color: var(--color-accent);
	border-radius: 50%;
}

/* The figures themselves, and the two tables that move with them. */
.htmx-request .metric-value,
.htmx-request .universe-stats dd,
.htmx-request .contrib-table tbody td,
.htmx-request .contrib-table tbody th {
	color: transparent;
	background-image: linear-gradient(90deg,
		var(--color-surface) 0%, var(--color-surface-raised) 50%, var(--color-surface) 100%);
	background-size: 200% 100%;
	border-radius: 3px;
}
.htmx-request .metric-value .unit,
.htmx-request .contrib-table .bar { visibility: hidden; }

/* Motion is the decoration; the word "Recalculating" is the fact. Anybody who
   has asked for less of it gets a static skeleton and the same text. */
@media (prefers-reduced-motion: no-preference) {
	.spinner { animation: spin .7s linear infinite; }
	.htmx-request .metric-value,
	.htmx-request .universe-stats dd,
	.htmx-request .contrib-table tbody td,
	.htmx-request .contrib-table tbody th { animation: shimmer 1.1s ease-in-out infinite; }
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* The form stays legible and stays usable: a policy change is fast, and
   disabling the controls under the cursor is how a second click lands on
   nothing. Only the derived figures above are uncertain. */
.htmx-request .holdings-table,
.htmx-request .segmented { opacity: .7; }

/* --- cash and financing -------------------------------------------------

   Weights need not sum to 100%, so a composition can be under-invested or
   levered. These controls say what happens to the difference, and the row they
   produce in the attribution table is the difference taking its place among the
   holdings — same weight, same risk, and labelled apart because nobody chose
   it. */

.financing {
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm, 4px);
	margin: 0 1rem 1rem; padding: .75rem 1rem 1rem;
}
.financing legend {
	padding: 0 .375rem;
	font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase;
	color: var(--color-text-muted);
}
.financing .field + .field { margin-top: .75rem; }

/* A percent sign belongs to the box, not to a label somewhere near it. */
.input-suffix { display: flex; align-items: stretch; }
.input-suffix input { flex: 1 1 auto; min-width: 0; }
.input-suffix .suffix {
	display: flex; align-items: center; padding: 0 .5rem;
	font-size: .8125rem; color: var(--color-text-muted);
	border: 1px solid var(--color-border); border-left: 0;
	background: var(--color-surface);
}

.allocation {
	display: flex; gap: 1.5rem;
	margin: .875rem 0 0; padding-top: .75rem;
	border-top: 1px solid var(--color-border);
}
.allocation div { display: flex; flex-direction: column; gap: .1875rem; }
.allocation dt {
	font-size: .6875rem; letter-spacing: .06em; text-transform: uppercase;
	color: var(--color-text-muted);
}
.allocation dd {
	margin: 0; font-size: 1.0625rem; color: var(--color-heading);
	font-variant-numeric: tabular-nums;
}
.allocation .unit { font-size: .75rem; color: var(--color-text-muted); }
.allocation .negative { color: var(--color-warning); }

/* The residual reads as part of the table and not as another holding. */
.financing-row th { font-style: italic; }
.financing-row td, .financing-row th { border-top: 1px dashed var(--color-border-strong); }
