/*
 * This is a manifest file that'll be compiled into application.css.
 *
 * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
 * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
 * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
 * depending on specificity.
 *
 * Consider organizing styles into separate files for maintainability.
 */

/* The terminal theme. The layout puts the whole app in Bootstrap's dark mode (`data-bs-theme="dark"` on
   <html>) and this block retints that mode: a near-black background with dull blue text, easy on the eyes
   for long sessions, in the spirit of a Bloomberg terminal. Green and red stay reserved for changes in
   value (`text-success`/`text-danger`), lifted a little so they read on black. Color also says what
   can be acted on: static text is blue (`--term-text`, `--term-text-strong` for headings and table
   heads), while anything clickable or editable — links, inputs and selects, the chartable
   metric labels, the time-slice rows, a chart's drag handle — is white (`--term-interactive`). Buttons go further: a white face with black text (`--term-button`).
   Everything else — tables,
   forms, buttons, charts (chart_controller.js reads the `--term-*` tokens) — takes its colors from here,
   so never hardcode a color elsewhere. */

/* The typeface: IBM Plex Sans, self-hosted (app/assets/fonts, OFL — IBM-Plex-Sans-OFL.txt), one
   variable font for every weight, Latin glyphs only (anything else falls back to the system face). A
   precise, technical sans that reads as a financial terminal rather than Bootstrap's system stack, with
   tabular figures for the columns of prices and weights (body sets tabular-nums). */
@font-face {
  font-family: "IBM Plex Sans";
  src: url("/assets/ibm-plex-sans-latin-578a0041.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}

[data-bs-theme="dark"] {
  /* Sharp corners everywhere — no rounded Bootstrap corners on inputs, buttons, tables or boxes. The
     page frames and the navbars go further and lean forward (see "Slanted frames" below). */
  --bs-border-radius: 0;
  --bs-border-radius-sm: 0;
  --bs-border-radius-lg: 0;
  --bs-border-radius-xl: 0;
  --bs-border-radius-xxl: 0;
  --bs-border-radius-2xl: 0;
  --bs-border-radius-pill: 0;
  --term-font: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --bs-body-font-family: var(--term-font);
  --bs-font-sans-serif: var(--term-font);
  --term-bg: #07090d;
  --term-surface: #0d121b;
  --term-navbar: #11214b;
  --term-surface-raised: #141b28;
  --term-border: #223049;
  --term-text: #7f97bd;
  --term-text-strong: #8fb0e6;
  --term-interactive: #e8edf4;
  --term-interactive-hover: #ffffff;
  --term-button: #e8edf4;
  --term-button-hover: #1b3152;
  --term-button-active: #142540;
  --term-on-button-hover: #ffffff;
  --term-down-button-hover: #4a1416;
  --term-on-button: #000000;
  --term-weight-input: #363c46;
  --term-weight-input-border: #4b5361;
  --term-down-on-button: #c62828;
  --term-muted: #56698c;
  --term-accent: #6f96cf;
  --term-accent-fill: #1b3152;
  --term-accent-border: #2d4d7c;
  --term-up: #3fb950;
  --term-down: #f0524f;
  /* Emphasis, not a change in value: the red "!" on the chart grids' minimize-all / maximize-all. */
  --term-alarm: #f0524f;
  /* Adding something: the small green "+" on "+ Rebalance date". */
  --term-add: #3fb950;
  --term-chart-line: #6f96cf;
  /* A merged chart's further lines, after --term-chart-line, in this fixed order (the dataviz
     reference palette's dark orange, aqua and magenta; its green and red are left out, being the
     up / down colors here, and its yellow, which sits too close to orange). */
  --term-chart-series-2: #d95926;
  --term-chart-series-3: #199e70;
  --term-chart-series-4: #d55181;
  --term-chart-grid: #182233;
  --term-busy-overlay: rgba(7, 9, 13, 0.6);

  --bs-body-bg: var(--term-bg);
  --bs-body-bg-rgb: 7, 9, 13;
  --bs-body-color: var(--term-text);
  --bs-body-color-rgb: 127, 151, 189;
  --bs-emphasis-color: var(--term-text-strong);
  --bs-emphasis-color-rgb: 143, 176, 230;
  --bs-heading-color: var(--term-text-strong);
  --bs-secondary-color: var(--term-muted);
  --bs-secondary-color-rgb: 86, 105, 140;
  --bs-tertiary-color: var(--term-muted);
  --bs-secondary-bg: var(--term-surface-raised);
  --bs-tertiary-bg: var(--term-surface);
  --bs-tertiary-bg-rgb: 13, 18, 27;
  --bs-border-color: var(--term-border);
  --bs-border-color-translucent: rgba(127, 151, 189, 0.15);
  --bs-link-color: var(--term-interactive);
  --bs-link-color-rgb: 232, 237, 244;
  --bs-link-hover-color: var(--term-interactive-hover);
  --bs-link-hover-color-rgb: 255, 255, 255;
  --bs-primary: var(--term-accent);
  --bs-primary-rgb: 111, 150, 207;
  --bs-primary-text-emphasis: var(--term-accent);
  --bs-primary-bg-subtle: #0f1a2b;
  --bs-primary-border-subtle: var(--term-accent-border);
  --bs-success: var(--term-up);
  --bs-success-rgb: 63, 185, 80;
  --bs-danger: var(--term-down);
  --bs-danger-rgb: 240, 82, 79;
  --bs-form-valid-color: var(--term-up);
  --bs-form-valid-border-color: var(--term-up);
  --bs-form-invalid-color: var(--term-down);
  --bs-form-invalid-border-color: var(--term-down);
}

body {
  font-variant-numeric: tabular-nums;
}

/* Bootstrap's × icons (a chart's close, a filter's reset) are interactive, so full white: dark mode
   already filters the icon white, but draws it at 50% opacity. */
.btn-close {
  --bs-btn-close-opacity: 1;
  --bs-btn-close-hover-opacity: 1;
}

/* The select caret is interactive too. Bootstrap's dark caret is a light grey SVG; this is the same SVG
   stroked in the --term-interactive white (a data URI cannot read a CSS variable, so the hex is
   repeated here — keep it in step with the token). */
[data-bs-theme="dark"] .form-select {
  --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23e8edf4' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* An editable date input's calendar button, white (Chrome/Safari/Edge; Firefox has no such
   pseudo-element). A disabled date keeps the dim icon, since it cannot be opened. */
.form-control:not(:disabled)::-webkit-calendar-picker-indicator {
  filter: brightness(0) invert(1);
}

/* Header bar: a raised strip rather than Bootstrap's bright primary navbar. */
.app-navbar {
  --bs-navbar-brand-color: var(--term-interactive);
  --bs-navbar-brand-hover-color: var(--term-interactive-hover);
  --bs-navbar-color: var(--term-text);
  --frame-fill: var(--term-navbar);
  --frame-border: var(--term-border);
}

/* The signed-in user's welcome is white like the interactive elements, even though it is static text:
   the user is the most important thing in the bar. */
.app-navbar .app-navbar-user {
  color: var(--term-interactive);
}

/* The resource links: a submenu hanging from the navbar rather than a second bar. It is inset from the
   navbar on both sides and sits flush under it — no gap, no top border (the navbar's own bottom edge is
   the seam), leaning at the navbar's angle — on the weight inputs' grey, its links white (they are
   interactive), the current page's link underlined in the accent blue. */
.app-subnav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin: 0 1.5rem;
  padding: 0.3rem 0.5rem;
  --frame-fill: var(--term-weight-input);
  --frame-border: var(--term-weight-input-border);
  --frame-inset: 0 1px 1px;
}

/* Slanted navbars: the navbar and the submenu hanging from it are parallelograms leaning up and to the
   right, /‾‾‾/ rather than a box: the top edge is shifted right by --slant and the bottom edge left by
   it. A CSS border cannot follow a slanted edge, so each is clipped to that shape (--frame-shape) and
   draws its own outline underneath its content: ::before fills the shape in --frame-border and ::after
   a 1px-inset copy (--frame-inset; the submenu has no top edge, the navbar is its seam) in
   --frame-fill. Content keeps clear of the slanted edges with half a slant of extra padding. The two
   share one angle (slant ∝ height), so the submenu's sides continue the navbar's. Everything else —
   the page boxes (cards), inputs, buttons, tables — is a plain box with sharp corners. */
.app-navbar,
.app-subnav {
  --slant: 1rem;
  --frame-inset: 1px;
  position: relative;
  isolation: isolate;
  border: 0;
  background: none;
  padding-inline: calc(var(--slant) / 2);
  --frame-shape: polygon(var(--slant) 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%);
  clip-path: var(--frame-shape);
}

.app-subnav {
  --slant: 0.68rem;
  padding-inline: calc(0.5rem + var(--slant) / 2);
}

.app-navbar::before,
.app-subnav::before,
.app-navbar::after,
.app-subnav::after {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
}

.app-navbar::before,
.app-subnav::before {
  inset: 0;
  background: var(--frame-border);
  clip-path: var(--frame-shape);
}

.app-navbar::after,
.app-subnav::after {
  inset: var(--frame-inset);
  background: var(--frame-fill);
  clip-path: var(--frame-shape);
}

/* About and Account, pushed to the submenu's right end. */
.app-subnav-end {
  display: flex;
  gap: 0.25rem;
  margin-left: auto;
}

.app-subnav-link {
  padding: 0.25rem 0.6rem;
  border-radius: var(--bs-border-radius-sm);
  color: var(--term-interactive);
  text-decoration: none;
  font-size: 0.875rem;
}

.app-subnav-link:hover,
.app-subnav-link:focus {
  color: var(--term-interactive-hover);
  background-color: var(--term-accent-fill);
}

.app-subnav-link[aria-current="page"] {
  box-shadow: inset 0 -2px 0 var(--term-accent);
}

/* Beta: the "!" in front of About, in the alarm red (emphasis, not a change in value). */
.beta-alarm {
  margin-right: 0.3rem;
  color: var(--term-alarm);
  font-weight: 700;
}

/* The About page's fake-data warning, in the same alarm red. */
.beta-warning {
  color: var(--term-alarm);
}

/* Buttons are solid white with black text — the strongest "you can act here" signal on the dark page.
   On hover (and while pressed) a button flips to a dark blue face with white text, its white border
   kept so it holds its shape. Bootstrap compiles its button colors in, so every variant the app uses is
   retinted here. A destructive (danger) button keeps the same white face with red text, so it still
   reads as a removal, and flips to a dark red instead. */
.btn-primary,
.btn-outline-primary,
.btn-outline-secondary,
.btn-outline-danger {
  --bs-btn-color: var(--term-on-button);
  --bs-btn-bg: var(--term-button);
  --bs-btn-border-color: var(--term-button);
  --bs-btn-hover-color: var(--term-on-button-hover);
  --bs-btn-hover-bg: var(--term-button-hover);
  --bs-btn-hover-border-color: var(--term-button);
  --bs-btn-active-color: var(--term-on-button-hover);
  --bs-btn-active-bg: var(--term-button-active);
  --bs-btn-active-border-color: var(--term-button);
  --bs-btn-disabled-color: var(--term-muted);
  --bs-btn-disabled-bg: var(--term-surface-raised);
  --bs-btn-disabled-border-color: var(--term-border);
  --bs-btn-focus-shadow-rgb: 111, 150, 207;
}

.btn-outline-danger {
  --bs-btn-color: var(--term-down-on-button);
  --bs-btn-hover-bg: var(--term-down-button-hover);
  --bs-btn-hover-border-color: var(--term-down);
  --bs-btn-active-bg: var(--term-down-button-hover);
  --bs-btn-active-border-color: var(--term-down);
  --bs-btn-focus-shadow-rgb: 240, 82, 79;
}

/* An input-group joins its buttons edge to edge with overlapping borders, and these buttons are white
   face and border alike, so neighbours (Earliest | Latest | Remove beside the beginning date) merged
   into one white bar. A page-coloured seam on the left of every button that follows another keeps
   them reading as separate buttons; it outranks .btn:hover, so it holds in every state. */
.input-group > .btn + .btn {
  border-left: 2px solid var(--term-bg);
}

/* Inputs: a surface a step off the page, and a dull blue focus ring instead of Bootstrap's bright one. */
.form-control,
.form-select,
.input-group-text {
  background-color: var(--term-surface);
  border-color: var(--term-border);
  color: var(--term-interactive);
}

.input-group-text {
  color: var(--term-muted);
}

/* A placeholder ("Filter by ticker…", "Search by ticker…") sits inside an input, so it is white like the
   input's value. */
.form-control::placeholder {
  color: var(--term-interactive);
  opacity: 1;
}

/* Bootstrap's focus rule resets the text to the body color, which would turn a value blue while it is
   being typed; it stays white like any editable value. */
.form-control:focus,
.form-select:focus {
  color: var(--term-interactive);
  background-color: var(--term-surface);
  border-color: var(--term-accent);
  box-shadow: 0 0 0 0.2rem rgba(111, 150, 207, 0.2);
}

.form-control:disabled {
  background-color: var(--term-bg);
  color: var(--term-text);
}

/* Table heads (including the sticky ones over scroll boxes) sit on an opaque raised band. */
.table > thead {
  --bs-table-bg: var(--term-surface-raised);
  --bs-table-color: var(--term-text-strong);
}

.table {
  --bs-table-color: var(--term-text);
}

/* Hover and focus feedback is instant: Bootstrap fades a button's (and an input's, a link's, a close
   button's) colors in over 0.15s, which reads as lag. The busy overlay keeps its own delayed fade on
   purpose (fast round trips never flash it). */
.btn,
.btn-close,
.form-control,
.form-select,
.nav-link,
.navbar-toggler {
  transition: none;
}

body {
  font-family: var(--term-font);
  margin: 2rem auto;
  /* One fixed desktop width (the viewport meta matches it): a narrower window scrolls sideways
     instead of squeezing the layout. */
  min-width: 80rem;
  max-width: 80rem;
  padding: 0 1rem;
}

main {
  margin-bottom: 10rem;
}

.scroll-box {
  max-height: 24rem;
  overflow-y: auto;
}

/* The portfolio history form's columns, locked or not, and the security page's card: every table in them
   — totals, metrics, drift, the rebalance picker, the company lists, search results — centers all of its cells.
   !important to beat Bootstrap's own !important `.text-end` utilities and `.metrics-table td`. */
.centered-tables table :is(th, td) {
  text-align: center !important;
}

/* Fixed table column widths, so a numeric column does not jump as its values change. */
.col-width-sm {
  width: 9rem;
}

.col-width-md {
  width: 12rem;
}

.col-width-lg {
  width: 14rem;
}

/* A company's weight input: narrow enough that it and its `%` addon fit a .col-width-sm column.
   Scoped like Bootstrap's own `.input-group > .form-control` (width: 1%; flex: 1 1 auto) to win. */
.input-group > .weight-input {
  flex: 0 0 auto;
  width: 5.5rem;
}

/* The weight inputs are the page's main thing to type into, so they stand apart from the other inputs:
   a dull grey face (--term-weight-input) with white text — still white, so still reads as interactive —
   focused or not. */
.input-group > .weight-input,
.input-group > .weight-input:focus {
  background-color: var(--term-weight-input);
  border-color: var(--term-weight-input-border);
  color: var(--term-interactive);
}

/* The `%` beside a weight input (Normalize to's total) is part of the weighting the user edits, so it is
   white too, on the same grey face so input and unit read as one control. */
.input-group > .weight-input + .input-group-text {
  background-color: var(--term-weight-input);
  border-color: var(--term-weight-input-border);
  color: var(--term-interactive);
}

/* The Weight by dropdowns change the new weights, so they share the weight inputs' dull grey face and
   white text (scoped .form-select.… to win over the dark theme's own select colors). */
.form-select.weight-metric-select,
.form-select.weight-metric-select:focus {
  background-color: var(--term-weight-input);
  border-color: var(--term-weight-input-border);
  color: var(--term-interactive);
}

/* The portfolio history form's time-slice header: one row per slice, nine columns (label, date,
   unlock, arrow, label, date, unlock, fork, "+ Rebalance date"). Each row is a subgrid of the shared columns so they line up
   across rows; buttons and text share the row's vertical center. */
.time-slices {
  display: grid;
  /* After the nine content columns (… unlock, fork, add), a 1fr filler and a last track of up to 50%: the slice rows stop
     before that last track (so they fill roughly the left half of the header) while the charts under
     them span all of it. The last track can shrink to nothing (`minmax(0, …)`), so when the slice
     content needs more than half the header the rows run past the middle rather than wrapping. */
  grid-template-columns: repeat(9, auto) 1fr minmax(0, 50%);
  justify-content: start;
  column-gap: 0.75rem;
  row-gap: 0.25rem;
}

/* Every row carries the same padding and border width, so the outlined row stays in line. Each row
   reads as a clickable card: a faint border and background at rest, the primary color on hover, and
   the shown slice (aria-current) outlined and tinted so it stays distinct from a hovered one. The
   selected ring is a box-shadow, not a thicker border, so it never shifts the row's columns. */
.time-slice {
  display: grid;
  grid-column: 1 / -2;
  grid-template-columns: subgrid;
  align-items: center;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-body-bg);
  color: var(--term-interactive);
  cursor: pointer;
}

.time-slice:hover {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
}

.time-slice[aria-current="true"] {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
  box-shadow: 0 0 0 1px var(--bs-primary);
}

/* The metrics charted across the shown slice sit under that slice's row, spanning the full header
   width four to a line, in the order they were opened. */
.slice-charts {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75rem;
  /* A dull outline groups the slice's charts with the tools that act on all of them. */
  margin: 0.25rem 0 0.75rem;
  padding: 0.5rem 0.75rem 0.75rem;
  border: 1px solid var(--term-border);
  border-radius: var(--bs-border-radius);
}

.slice-chart {
  min-width: 0;
}

/* The chart a metric label just opened or jumped to, or the company table a list button (Weight,
   Normalize, Remove all, …) just changed, is focused (application.js): outlined in the accent blue
   until focus moves on. */
.focus-target:focus {
  outline: 1px solid var(--term-accent);
  outline-offset: 2px;
}

.slice-chart:focus {
  outline: 1px solid var(--term-accent);
  outline-offset: 3px;
  border-radius: var(--bs-border-radius);
}

/* The slice's own tools (minimize all, maximize all, close all) sit in the top-right corner of its
   outlined chart area, in a small toolbar of the same dull outline — read as one control for the whole
   area, not part of the last chart. Reuses the chart buttons' icon style (.slice-chart-size) and the
   chart ×. */
.slice-charts-tools {
  grid-column: 1 / -1;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--term-border);
  border-radius: var(--bs-border-radius);
}

.slice-charts-tools .btn-close {
  font-size: 0.72rem;
}

/* A grid chart's size (chart_controller.js grow / shrink, data-chart-size): one of the row's four
   columns by default, then two and all four, the plot growing taller with it so a wide series
   is not a thin strip. */
.slice-chart[data-chart-size="2"] {
  grid-column: span 2;
}

.slice-chart[data-chart-size="4"] {
  grid-column: 1 / -1;
}

/* Three quarters: only a merged chart of three series is this wide (chart_controller.js mergeSize). */
.slice-chart[data-chart-size="3"] {
  grid-column: span 3;
}

.slice-chart[data-chart-size="3"] .slice-chart-plot {
  height: 15rem;
}

.slice-chart[data-chart-size="2"] .slice-chart-plot {
  height: 12rem;
}

.slice-chart[data-chart-size="4"] .slice-chart-plot {
  height: 18rem;
}

/* The shrink / grow buttons beside a chart's × (and the grid's minimize / maximize all): bare white
   icons (interactive), faded when there is no further step that way. */
.slice-chart-size {
  display: inline-flex;
  padding: 0;
  border: 0;
  background: none;
  color: var(--term-interactive);
  line-height: 1;
}

.slice-chart-size:hover,
.slice-chart-size:focus-visible {
  color: var(--term-interactive-hover);
}

.slice-chart-size svg {
  width: 0.9rem;
  height: 0.9rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Minimize all / maximize all: the stepping icons plus a red "!" (a wider viewBox), which keeps its
   red on hover and fades with the arrows when disabled. */
.slice-chart-size-all svg {
  width: 1.24rem;
}

.slice-chart-size svg .slice-chart-alarm {
  stroke: var(--term-alarm);
  stroke-width: 2.2;
}

/* An icon-only header action (the Fork button): the dark grey of the weight inputs and the submenu, a
   lighter grey outline so the button's edge shows against the page, and a white icon (interactive) —
   the height of a btn-sm beside it (Edit, Delete). Hover / focus take the other buttons' dark face. */
.icon-button {
  display: inline-flex;
  /* Some are links (the timeline's Fork, which opens a filled New form and so can open in a new tab). */
  text-decoration: none;
  align-items: center;
  justify-content: center;
  padding: 0.375rem 0.55rem;
  border: 1px solid var(--term-weight-input-border);
  border-radius: var(--bs-border-radius-sm);
  background: var(--term-weight-input);
  color: var(--term-interactive);
  line-height: 1;
}

.icon-button:hover,
.icon-button:focus-visible {
  border-color: var(--term-interactive);
  background: var(--term-button-hover);
  color: var(--term-on-button-hover);
}

.icon-button svg {
  width: 1.05rem;
  height: 1.05rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The label of a list control that changes the weights (Weight by:, Normalize to:) — white, like the
   controls it names. */
.control-label {
  color: var(--term-interactive);
}

/* Normalize to's total box: room for "100" or "12.5", narrower than a company's weight input. */
/* Wide enough for a total over 100% to 2 places (105.76). */
.input-group > .weight-input.normalize-to-input {
  width: 4.75rem;
}

/* A re-apply icon (Weight by's, Normalize to's): attached to its select or box in an input-group, so
   it takes the control's small height rather than a btn-sm's. */
.icon-button.reapply-button {
  padding: 0 0.45rem;
}

/* An input group pulls each control 1px left so neighbouring borders overlap, and the select / input
   beside the icon is positioned, so it would paint over the icon's left border (its hover white went
   missing). Stack the icon like Bootstrap stacks a .btn in a group: above its neighbours, higher still
   while hovered or focused. */
.input-group > .icon-button {
  position: relative;
  z-index: 2;
}

.input-group > .icon-button:hover,
.input-group > .icon-button:focus-visible {
  z-index: 5;
}

.icon-button.reapply-button svg {
  width: 0.9rem;
  height: 0.9rem;
}

.slice-chart-size:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Reordering a slice's charts (chart_order_controller.js): the grip and name are the handle (the only
   place with a grab cursor); the dragged chart fades, and a primary bar marks the side of the target
   it will land on. */
.slice-chart-handle {
  color: var(--term-interactive);
  cursor: grab;
  user-select: none;
}

.slice-chart-grip {
  color: var(--term-interactive);
}

.slice-chart.is-dragging {
  opacity: 0.4;
}

.slice-chart.drop-before {
  box-shadow: -3px 0 0 var(--bs-primary);
}

.slice-chart.drop-after {
  box-shadow: 3px 0 0 var(--bs-primary);
}

/* Dropping on a chart's middle merges the dragged chart into it (chart_order_controller.js). */
.slice-chart.drop-merge {
  box-shadow: inset 0 0 0 2px var(--bs-primary);
}

/* A merged chart's legend: each series' color and name, and a × splitting a merged series back out. */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  margin-bottom: 0.25rem;
  font-size: 0.8rem;
}

.chart-legend[hidden] {
  display: none;
}

.chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.chart-legend-swatch {
  width: 0.75rem;
  height: 2px;
  background: var(--term-chart-line);
}

.chart-legend-swatch[data-series="2"] { background: var(--term-chart-series-2); }
.chart-legend-swatch[data-series="3"] { background: var(--term-chart-series-3); }
.chart-legend-swatch[data-series="4"] { background: var(--term-chart-series-4); }

.chart-legend-split {
  width: 0.5rem;
  height: 0.5rem;
  background-size: 0.5rem;
}

/* A slice with no charts open keeps its (hx-preserve) container in the DOM, just hidden. */
.slice-charts:not(:has(.slice-chart)) {
  display: none;
}

/* The timeline's beginning chart area takes no row of its own (and no gutter) until a chart opens. */
.beginning-charts:not(:has(.slice-chart)) {
  display: none;
}

.slice-chart-plot {
  height: 9rem;
}

/* Index pages: each record stands apart. A portfolio is a block (name, description, holdings) with
   space and a dividing line before the next; a timeline row gets roomier padding. */
.index-record {
  padding-bottom: 2rem;
  margin-bottom: 2rem;
  border-bottom: 1px solid var(--term-border);
}

.index-record:last-child {
  border-bottom: 0;
}

.index-table > tbody > tr > td {
  padding-top: 0.9rem;
  padding-bottom: 0.9rem;
}

/* A holdings dashboard's price chart (shared/_holdings_dashboard): its header row and plot together, a
   little narrower than the card and centred, so the chart does not run edge to edge. */
.price-chart {
  width: 90%;
  margin: 0 auto;
}

.price-chart-plot {
  height: 22rem;
}

/* A chart whose click picks its metric (the security page's chart grid, holdings-metric#selectFromChart). */
[data-action~="click->holdings-metric#selectFromChart"] .slice-chart-plot {
  cursor: pointer;
}

/* A slice row's labels, dates and buttons never wrap onto a second line. */
.time-slice > * {
  white-space: nowrap;
}

/* The × that closes one chart, scaled down from Bootstrap's 1em default. */
.slice-chart .btn-close {
  font-size: 0.72rem;
}

/* Dates align right so the day and year columns stack. */
.time-slice-date {
  text-align: right;
}

#security_results {
  margin-top: 0.5rem;
}

table {
  border-collapse: collapse;
  width: 100%;
}

th, td {
  border-bottom: 1px solid var(--bs-border-color);
  padding: 0.35rem 0.75rem;
  text-align: left;
}

th {
  border-bottom-width: 2px;
}

td:last-child {
  text-align: right;
}

form .field {
  margin-bottom: 0.75rem;
}

form label {
  display: block;
  margin-bottom: 0.25rem;
  font-weight: 600;
}

form input[type="text"],
form textarea {
  padding: 0.35rem;
  width: 24rem;
  max-width: 100%;
}

.filter-row {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.filter-row > label {
  flex: 0 0 auto;
  width: 5rem;
  margin-bottom: 0;
}

.filter-row > .form-select {
  flex: 0 0 auto;
  width: 15rem;
}

/* The × beside a set Sector / Industry filter (shared/_filter_clear): a small white close icon (Bootstrap's
   btn-close, full white like every × — see .btn-close above). */
.filter-clear {
  flex: 0 0 auto;
  font-size: 0.65rem;
  margin-left: 0.25rem;
}

.form-errors {
  color: var(--term-down);
  margin-bottom: 1rem;
}

.metrics-table {
  font-size: 0.72rem;
  line-height: 1.2;
}

.metrics-table td {
  padding: 0.12rem 0.5rem;
  vertical-align: baseline;
  text-align: left;
  border-right: 1px solid var(--term-border);
  border-bottom: 1px solid var(--term-border);
  background-color: var(--term-surface);
}

.metrics-table td:last-child {
  border-right: none;
}

.metrics-table .metrics-cell {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  white-space: nowrap;
}

/* In the portfolio history form's columns and on the security page, each metric fills its cell: label at
   the left edge, value at the right. Scoped so the portfolio pages keep their compact pairs. */
.centered-tables .metrics-table .metrics-cell {
  display: flex;
  justify-content: space-between;
}

.metrics-table .metrics-label {
  color: var(--term-muted);
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* A holding's value in an ETF page's metric column: interactive (white), it charts that company's metric. */
.holding-metric-link {
  color: var(--term-interactive);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.holding-metric-link:hover,
.holding-metric-link:focus-visible {
  color: var(--term-interactive-hover);
  text-decoration: underline;
}

/* A sortable table's header label (sortable_table_controller.js): clickable, so white; the arrow says
   which way the column is sorted. The label may wrap (overriding a header's text-nowrap): the arrow
   widens every header, and in a half-width list (a rebalance's companies) unwrappable headers pushed
   the table past its box. A wide table never needs to. */
.sort-button {
  color: var(--term-interactive);
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  text-align: inherit;
  white-space: normal;
  cursor: pointer;
}

.sort-button:hover,
.sort-button:focus-visible {
  color: var(--term-interactive-hover);
  text-decoration: underline;
}

/* The arrow is part of the clickable label, so it takes the button's white (hover included), sorted or
   not. A no-break space ties it to the label's last word, so a wrapped label never leaves it alone on a
   line. */
.sort-indicator::after {
  content: "\00A0\2195";
}

.sort-button[data-sort-direction="ascending"] .sort-indicator::after {
  content: "\00A0\25B2";
}

.sort-button[data-sort-direction="descending"] .sort-indicator::after {
  content: "\00A0\25BC";
}

/* A two-value column's ends (start → end), each sorting the column by its own value. */
.sort-ends {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  font-size: 0.75rem;
  font-weight: normal;
}

/* In a page that centres its tables, the ends sit centred under their heading too. */
.centered-tables .sort-ends {
  justify-content: center;
}

.metrics-table .metrics-link {
  color: var(--term-interactive);
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
}

.metrics-table .metrics-link:hover,
.metrics-table .metrics-link:focus {
  color: var(--term-interactive-hover);
  text-decoration: underline;
}

.metrics-table .metrics-value {
  font-weight: 600;
  font-size: 0.72rem;
}

.chart-loading-overlay {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background-color: rgba(var(--bs-body-bg-rgb), 0.7);
  z-index: 10;
}

.chart-loading-overlay.is-loading {
  display: flex;
}


/* A form waiting on its htmx round trip (application.js marks it aria-busy). The overlay covers the
   whole form and swallows clicks at once, so an impatient second click cannot queue another request;
   it only dims, and the spinner only appears, after 150ms, so fast responses never flash. The spinner
   is pinned to the middle of the viewport rather than the form, which is usually taller than the
   screen. */
.busy-overlay {
  position: relative;
  /* The regions htmx swaps are never the browser's scroll anchor: replacing one (a whole form) whose
     content changes height around the clicked element let Chrome's scroll anchoring jump the view a
     screen or more — picking an older time slice landed near the bottom of the page. */
  overflow-anchor: none;
}

.busy-overlay::before,
.busy-overlay::after {
  content: "";
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}

.busy-overlay::before {
  position: absolute;
  inset: 0;
  z-index: 1050;
  background: var(--term-busy-overlay);
  border-radius: var(--bs-border-radius);
}

.busy-overlay::after {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 1051;
  width: 3rem;
  height: 3rem;
  margin: -1.5rem 0 0 -1.5rem;
  border: 0.3rem solid var(--term-interactive);
  border-right-color: transparent;
  border-radius: 50%;
  animation: spinner-border 0.75s linear infinite;
}

.busy-overlay[aria-busy="true"]::before,
.busy-overlay[aria-busy="true"]::after {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.15s ease-in 0.15s;
}

.busy-overlay[aria-busy="true"]::before {
  pointer-events: auto;
  cursor: progress;
}

/* "+ Rebalance date": the grey icon-button face, a small green plus ahead of the label saying another
   one is being added. A btn-sm's font size, line height and padding, so it stands exactly as tall as
   the Unlock (a btn-sm) beside it — the icon buttons' line-height 1 left it a few pixels short. */
.icon-button.add-date-button {
  gap: 0.3rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.875rem;
  line-height: 1.5;
}

.icon-button .add-icon {
  width: 0.7rem;
  height: 0.7rem;
  fill: none;
  stroke: var(--term-add);
  stroke-width: 2.5;
  stroke-linecap: round;
}

/* News posts (About, Account): each a block, divided from the next. */
.news-post + .news-post {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--term-border);
}

/* A conversation between a user and the admins (Account page, admin Messages): the reader's own
   messages indented right, the other side's left, each on a raised band. */
.message-conversation {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--term-border);
}

.message {
  padding: 0.6rem 0.8rem;
  margin-bottom: 0.6rem;
  margin-right: 3rem;
  background-color: var(--term-surface-raised);
  border-left: 2px solid var(--term-border);
}

.message[data-mine="true"] {
  margin-left: 3rem;
  margin-right: 0;
  border-left-color: var(--term-accent);
}

.message-reply {
  margin-top: 0.6rem;
}

/* The Account page's read news, folded under one clickable line. */
.news-read-list {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--term-border);
}

.news-read-list > summary {
  color: var(--term-interactive);
  cursor: pointer;
}

.news-read-list > summary:hover {
  color: var(--term-interactive-hover);
}

.news-read-list[open] > summary {
  margin-bottom: 1rem;
}

/* A conversation's status on the admin pages: "Replied" muted, "Needs reply" in the alarm red. */
.status-label {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  font-size: 0.8rem;
  color: var(--term-muted);
  border: 1px solid var(--term-border);
}

.status-needs-reply {
  color: var(--term-alarm);
  border-color: var(--term-alarm);
  font-weight: 600;
}

/* "How are we doing?" on the Account page: one block per question; the chosen yes / no takes the
   accent's filled face (aria-pressed), the other stays an outline. */
.survey-step {
  padding: 1rem 0;
  border-top: 1px solid var(--term-border);
}

.survey-prompt {
  margin-bottom: 0.5rem;
  color: var(--term-text-strong);
}

.survey-choice[aria-pressed="true"] {
  background-color: var(--term-accent-fill);
  border-color: var(--term-accent);
  color: var(--term-interactive);
}

.survey-amount {
  max-width: 22rem;
}

.survey-thanks {
  padding-top: 1rem;
  border-top: 1px solid var(--term-border);
  color: var(--term-text-strong);
}

.survey-hint {
  margin-top: -0.35rem;
  margin-bottom: 0.6rem;
}
