/* ========================================================================
   Portfolio — Design System
   ======================================================================== */

/* ------------------------------------------------------------------------
   1. Design tokens
   ------------------------------------------------------------------------ */

:root {
  /* Surfaces */
  --surface-0: #f9f9f7;
  --surface-1: #fcfcfb;
  --surface-2: #f1f0ec;

  /* Text */
  --text-1: #0b0b0b;
  --text-2: #52514e;
  --text-3: #898781;

  /* Structure */
  --grid: #e1e0d9;
  --axis: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10);

  /* Interaction */
  --accent: #2a78d6;

  /* Status */
  --good: #0ca30c;
  --warning: #fab219;
  --critical: #d03b3b;

  /* Delta colors */
  --up: #006300;
  --down: #d03b3b;

  /* Categorical series (fixed order) */
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
  --series-6: #008300;
  --series-7: #4a3aa7;
  --series-8: #e34948;

  /* Spacing */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  /* Radii */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;

  /* Shadow */
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

/* Dark mode — both @media and explicit toggle */
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    --surface-0: #0d0d0d;
    --surface-1: #1a1a19;
    --surface-2: #232322;
    --text-1: #ffffff;
    --text-2: #c3c2b7;
    --text-3: #898781;
    --grid: #2c2c2a;
    --axis: #383835;
    --border: rgba(255, 255, 255, 0.10);
    --accent: #3987e5;
    --good: #0ca30c;
    --warning: #fab219;
    --critical: #d03b3b;
    --up: #0ca30c;
    --down: #e66767;
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-6: #008300;
    --series-7: #9085e9;
    --series-8: #e66767;
  }
}

:root[data-theme="dark"] {
  --surface-0: #0d0d0d;
  --surface-1: #1a1a19;
  --surface-2: #232322;
  --text-1: #ffffff;
  --text-2: #c3c2b7;
  --text-3: #898781;
  --grid: #2c2c2a;
  --axis: #383835;
  --border: rgba(255, 255, 255, 0.10);
  --accent: #3987e5;
  --good: #0ca30c;
  --warning: #fab219;
  --critical: #d03b3b;
  --up: #0ca30c;
  --down: #e66767;
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-6: #008300;
  --series-7: #9085e9;
  --series-8: #e66767;
}

/* ------------------------------------------------------------------------
   2. Reset & base
   ------------------------------------------------------------------------ */

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

* {
  margin: 0;
  padding: 0;
}

html {
  height: 100%;
}

/* A single overflowing element (a squeezed flex row, a wide table that
   escapes its wrapper) must never drag the whole page sideways — it should
   be that element's own problem to scroll, not the viewport's. */
html, body {
  overflow-x: hidden;
}

body {
  min-height: 100%;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-1);
  background: var(--surface-0);
  -webkit-font-smoothing: antialiased;
}

img, picture, video, canvas, svg {
  display: block;
  max-width: 100%;
}

input, button, textarea, select {
  font: inherit;
}

button {
  border: none;
  background: none;
  cursor: pointer;
}

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

a:hover {
  text-decoration: underline;
}

/* ------------------------------------------------------------------------
   3. Layout shell
   ------------------------------------------------------------------------ */

.app {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 208px;
  background: var(--surface-1);
  border-right: 1px solid var(--border);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  overflow-y: auto;
}

.sidebar__nav {
  padding: var(--sp-4) 0;
  flex: 1;
}

.sidebar__group {
  border-top: 1px solid var(--border);
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
}

.sidebar__group:first-child {
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}

.sidebar__link {
  display: block;
  padding: var(--sp-2) var(--sp-4);
  color: var(--text-2);
  text-decoration: none;
  transition: color 0.15s;
  position: relative;
}

.sidebar__link:hover {
  color: var(--text-1);
  text-decoration: none;
}

.sidebar__link.is-active {
  color: var(--text-1);
  font-weight: 500;
}

.sidebar__link.is-active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--accent);
}

.main {
  flex: 1;
  display: flex;
  flex-direction: column;
  margin-left: 208px;
}

.header {
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  padding: 0 var(--sp-5);
  height: 64px;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-shrink: 0;
}

.header__title {
  font-size: 20px;
  font-weight: 600;
  color: var(--text-1);
}

.header__actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.content {
  flex: 1;
  padding: var(--sp-5);
  max-width: 1600px;
  width: 100%;
  margin: 0 auto;
}

.grid {
  display: grid;
  gap: var(--sp-4);
  /* Cards take their natural height. Stretching them to the tallest sibling
     leaves a chart card with a large dead area under its plot. */
  align-items: start;
}

.grid--2 {
  grid-template-columns: repeat(2, 1fr);
}

.grid--3 {
  grid-template-columns: repeat(3, 1fr);
}

.grid--4 {
  grid-template-columns: repeat(4, 1fr);
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.row {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
}

/* In a filter bar the controls are different heights (a multi-select is several
   rows tall), so they share a bottom edge instead of a centre line: that puts
   every label of a one-row control on the same line. */
.filters .row {
  align-items: flex-end;
  flex-wrap: wrap;
}

.spacer {
  flex: 1;
}

/* ------------------------------------------------------------------------
   4. Typography & utilities
   ------------------------------------------------------------------------ */

.num {
  font-variant-numeric: tabular-nums;
}

.num--up {
  color: var(--up);
}

.num--down {
  color: var(--down);
}

.muted {
  color: var(--text-3);
}

.mono {
  font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace;
  font-size: 13px;
}

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

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

/* ------------------------------------------------------------------------
   5. Cards
   ------------------------------------------------------------------------ */

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}

.card__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-bottom: 1px solid var(--border);
}

.card__title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-1);
}

.card__tools {
  margin-left: auto;
  display: flex;
  gap: var(--sp-2);
}

.card__body {
  padding: var(--sp-4);
}

.card__foot {
  padding: var(--sp-4);
  border-top: 1px solid var(--border);
  background: var(--surface-0);
}

/* ------------------------------------------------------------------------
   6. Stat tiles
   ------------------------------------------------------------------------ */

.stat {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.stat__label {
  font-size: 13px;
  color: var(--text-2);
  font-weight: 500;
}

.stat__value {
  font-size: 28px;
  font-weight: 600;
  color: var(--text-1);
  line-height: 1.2;
}

.stat--hero .stat__value {
  font-size: 40px;
}

.stat__delta {
  font-size: 14px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.stat__delta--up {
  color: var(--up);
}

.stat__delta--down {
  color: var(--down);
}

.stat__note {
  font-size: 12px;
  color: var(--text-3);
  margin-top: var(--sp-1);
}

/* ------------------------------------------------------------------------
   7. Tables
   ------------------------------------------------------------------------ */

.table-wrap {
  overflow-x: auto;
  margin: calc(-1 * var(--sp-4));
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.table thead {
  background: var(--surface-2);
  position: sticky;
  top: 0;
  z-index: 1;
}

.table th {
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  font-weight: 600;
  color: var(--text-2);
  font-size: 13px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.table th a {
  color: inherit;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.table th a:hover {
  color: var(--text-1);
}

.table td {
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}

.table tbody tr:hover {
  background: var(--surface-2);
}

.table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  /* A figure must never wrap: "58.448,91 €" split over two lines reads as two
     numbers. Narrow columns scroll the table instead. */
  white-space: nowrap;
}

/* For short labels whose wrapping costs more than the width it saves. */
.table__nowrap {
  white-space: nowrap;
}

/* `display: flex` directly on a td lays out its row-height contribution
   through a different (flex) code path than plain table cells use, which
   rounds to a different sub-pixel value and throws the shared collapsed
   border under this column off by 1px from the rest of the row. Buttons are
   inline-flex already, so plain text-align + margin gets the same layout
   without putting the td itself in flex formatting. */
.table__actions {
  text-align: right;
  white-space: nowrap;
}

.table__actions .btn + .btn {
  margin-left: var(--sp-2);
}

/* Full-size icon buttons make the actions cell nearly as tall as the row's
   own padding, so the row divider reads flush under the buttons but has a
   visible gap under the text cells — the border looks "off" from column to
   column. Shrinking them here keeps row height close to a text row. */
.table__actions .btn--icon {
  min-width: 28px;
  min-height: 28px;
  padding: var(--sp-1);
}

.table--compact td {
  padding: var(--sp-1) var(--sp-3);
}

.table__total {
  font-weight: 600;
  background: var(--surface-2);
}

.table__bar {
  display: block;
  height: 8px;
  background: var(--accent);
  border-radius: var(--r-sm);
  min-width: 2px;
}

/* The bar is sized in % of its cell, so the cell needs a width of its own or
   every share renders as the same short stub. */
.table td:has(> .table__bar) {
  width: 120px;
  min-width: 120px;
}

/* ------------------------------------------------------------------------
   8. Badges & pills
   ------------------------------------------------------------------------ */

.badge {
  display: inline-block;
  padding: 2px var(--sp-2);
  border-radius: var(--r-sm);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.badge--buy {
  background: var(--good);
  color: white;
}

.badge--sell {
  background: var(--critical);
  color: white;
}

.badge--dividend {
  background: var(--series-1);
  color: white;
}

.badge--transfer {
  background: var(--text-3);
  color: white;
}

.badge--switch {
  background: var(--warning);
  color: var(--text-1);
}

.pill {
  display: inline-block;
  padding: var(--sp-1) var(--sp-2);
  background: var(--surface-2);
  border-radius: var(--r-sm);
  font-size: 13px;
  color: var(--text-2);
}

/* ------------------------------------------------------------------------
   9. Forms & filters
   ------------------------------------------------------------------------ */

.filters {
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  padding: var(--sp-4);
  margin-bottom: var(--sp-4);
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
  align-items: flex-end;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  flex: 1;
  min-width: 160px;
}

.field__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.field__hint {
  font-size: 12px;
  color: var(--text-3);
}

.field__error {
  font-size: 12px;
  color: var(--critical);
  font-weight: 500;
}

.input,
.select {
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text-1);
  transition: border-color 0.15s, background 0.15s;
}

.input:focus,
.select:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface-1);
  box-shadow: 0 0 0 2px var(--accent), 0 0 0 4px var(--surface-1);
}

.input:disabled,
.select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%23898781' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-2) center;
  padding-right: var(--sp-5);
}

.select[multiple] {
  background-image: none;
  padding-right: var(--sp-3);
  /* No vertical padding: a multi-select sizes its box to whole option rows, so
     padding pushes the last row half out of view and it reads as a clipped
     control rather than a scrollable list. `size` (set in select_field) decides
     how many rows show. */
  padding-block: 0;
  min-height: 36px;
}

.select[multiple][data-chips] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.checkbox {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  user-select: none;
}

.checkbox input[type="checkbox"] {
  width: 18px;
  height: 18px;
  cursor: pointer;
  accent-color: var(--accent);
}

.switch {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  user-select: none;
}

.switch input[type="checkbox"] {
  appearance: none;
  width: 40px;
  height: 22px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 11px;
  position: relative;
  cursor: pointer;
  transition: background 0.2s;
}

.switch input[type="checkbox"]::before {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  background: var(--text-1);
  border-radius: 50%;
  top: 2px;
  left: 2px;
  transition: left 0.2s;
}

.switch input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}

.switch input[type="checkbox"]:checked::before {
  left: 20px;
  background: white;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}

.chips__item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-2);
  background: var(--accent);
  color: white;
  border-radius: var(--r-sm);
  font-size: 13px;
}

.chips__remove {
  cursor: pointer;
  padding: var(--sp-1);
  min-width: 24px;
  min-height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: background 0.15s;
  margin: calc(-1 * var(--sp-1));
  margin-left: 0;
}

.chips__remove:hover {
  background: rgba(0, 0, 0, 0.2);
}

.repeater {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.repeater__row {
  display: flex;
  gap: var(--sp-2);
  align-items: flex-end;
}

/* Transaction rows: one line per transaction in the batch entry form. */

.tx-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
}

/* The rows themselves. .tx-rows is the wrapper and also holds the column header,
   the hint and the add button, so its gap does not separate one row from the next. */
.tx-rows__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.tx-row {
  display: grid;
  /* Hard minimums, not bare fr: a derived unit price runs to twelve characters
     ("67343.876691") and a plain 1fr column clips it at this modal's width.
     The minimums add up to 804px; below the breakpoint where the modal can no
     longer give them that, the row stops being one line at all. */
  grid-template-columns:
    140px
    minmax(130px, 1.1fr)
    minmax(130px, 1.1fr)
    minmax(130px, 1.1fr)
    minmax(80px, 0.7fr)
    minmax(110px, 1fr)
    /* 36px matches .btn--icon's min-width; a 32px track leaves the remove button
       hanging 4px outside the row. */
    36px;
  gap: var(--sp-2);
  align-items: center;
}

.tx-field {
  display: block;
  /* Without this a grid item refuses to shrink below its content's width and the
     row overflows the modal instead of dividing it. */
  min-width: 0;
}

/* The column header names the fields while the row is one line. */
.tx-field__label {
  display: none;
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: var(--sp-1);
}

.tx-row--head {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-3);
}

/* Spans the whole row so a long message doesn't squeeze the columns. */
.tx-row__error {
  grid-column: 1 / -1;
}

/* ------------------------------------------------------------------------
   10. Buttons
   ------------------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-sm);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-1);
  white-space: nowrap;
  min-height: 36px;
}

.btn:hover {
  background: var(--surface-1);
  text-decoration: none;
}

.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}

.btn--primary:hover {
  background: var(--series-1);
  border-color: var(--series-1);
}

.btn--ghost {
  background: transparent;
  border-color: transparent;
}

.btn--ghost:hover {
  background: var(--surface-2);
}

.btn--danger {
  background: var(--critical);
  border-color: var(--critical);
  color: white;
}

.btn--danger:hover {
  background: var(--down);
  border-color: var(--down);
}

.btn--icon {
  padding: var(--sp-2);
  min-width: 36px;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-group {
  display: flex;
  gap: var(--sp-2);
}

/* ------------------------------------------------------------------------
   11. Modals
   ------------------------------------------------------------------------ */

.modal {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: var(--sp-4);
}

/* The nested layer stacks above the first, so its own backdrop dims the modal
   underneath and the two never look like one flat sheet. */
#modal-root-2 .modal {
  z-index: 1100;
}

.modal__panel {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  max-width: 600px;
  width: 100%;
  max-height: 90vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}

/* The transaction form lays its rows out in columns; 600px squeezes them into
   unreadable slivers. */
.modal__panel--wide {
  max-width: 900px;
}

.modal__head {
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.modal__head h2 {
  font-size: 20px;
  font-weight: 600;
  color: var(--text-1);
}

/* The <form> sits between .modal__panel (flex column) and .modal__body: without
   this it's a plain block box that grows to fit its content, so .modal__body's
   flex/overflow-y never get a bounded height to scroll within. */
.modal__panel form {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.modal__body {
  padding: var(--sp-5);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}

.modal__foot {
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  display: flex;
  gap: var(--sp-2);
  justify-content: flex-end;
  background: var(--surface-0);
}

/* ------------------------------------------------------------------------
   12. Flash messages
   ------------------------------------------------------------------------ */

.flash {
  position: fixed;
  top: 80px;
  right: var(--sp-4);
  min-width: 280px;
  max-width: 420px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
  z-index: 3000;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  animation: slideIn 0.3s ease-out;
}

.flash span {
  word-wrap: break-word;
  overflow-wrap: break-word;
  flex: 1;
}

@keyframes slideIn {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.flash--ok {
  border-left: 4px solid var(--good);
}

.flash--error {
  border-left: 4px solid var(--critical);
}

.flash button {
  margin-left: auto;
  padding: var(--sp-1);
  opacity: 0.5;
  min-width: 24px;
  min-height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.flash button:hover {
  opacity: 1;
}

/* ------------------------------------------------------------------------
   13. Chart containers & legend
   ------------------------------------------------------------------------ */

.chart {
  width: 100%;
  position: relative;
}

.chart--tall {
  height: 360px;
}

.chart--short {
  height: 220px;
}

/* Taller than the ring needs: the bottom strip is the legend, which names the
   slices so identity is never colour-alone. */
.chart--donut {
  height: 320px;
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
}

.legend__item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 13px;
  color: var(--text-2);
}

/* Both keys are also used standalone as the colour column of a chart's table twin,
   so they must carry their own box -- an inline span ignores width/height. */
.legend__key {
  display: inline-block;
  flex-shrink: 0;
  width: 12px;
  height: 2px;
  border-radius: 1px;
}

.legend__swatch {
  display: inline-block;
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}

/* ------------------------------------------------------------------------
   14. Empty state & pagination
   ------------------------------------------------------------------------ */

.empty {
  text-align: center;
  padding: var(--sp-6);
  color: var(--text-3);
}

.empty p {
  margin-bottom: var(--sp-3);
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) 0;
  font-size: 13px;
}

.pagination__info {
  color: var(--text-2);
}

.pagination__nav {
  display: flex;
  gap: var(--sp-2);
}

/* ------------------------------------------------------------------------
   15. HTMX states
   ------------------------------------------------------------------------ */

.htmx-indicator {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--accent);
  z-index: 9999;
  opacity: 0;
  transition: opacity 0.3s;
}

.htmx-request .htmx-indicator {
  opacity: 1;
}

.htmx-request.htmx-request > * {
  opacity: 0.55;
  pointer-events: none;
}

/* ------------------------------------------------------------------------
   16. Responsive
   ------------------------------------------------------------------------ */

/* The one-line row needs 804px of column minimums. By the time the modal's own
   padding and two scrollbars come off the viewport that is about 910px, so below
   960px each row becomes its own card of labelled fields instead of a line of
   slivers you cannot read what you are typing in. Its own breakpoint, not the
   900px one below: that one is about the sidebar. */
@media (max-width: 960px) {
  .tx-row--head {
    display: none;
  }

  .tx-row {
    position: relative;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-3);
    align-items: start;
    padding: var(--sp-5) var(--sp-4) var(--sp-4);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
  }

  .tx-field__label {
    display: block;
  }

  /* Out of the grid flow, into the card's corner: as a seventh cell it would
     either steal a field's width or drop onto a line of its own. */
  .tx-row__remove {
    position: absolute;
    top: var(--sp-1);
    right: var(--sp-1);
  }
}

@media (max-width: 900px) {
  .app {
    flex-direction: column;
  }

  .sidebar {
    position: static;
    width: 100%;
    border-right: none;
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    overflow-y: visible;
  }

  .sidebar__nav {
    display: flex;
    align-items: stretch;
    padding: 0 var(--sp-3);
    overflow-x: auto;
  }

  .sidebar__group {
    display: flex;
    border-top: none;
    border-left: 1px solid var(--border);
    margin-top: 0;
    margin-left: var(--sp-3);
    padding-top: 0;
    padding-left: var(--sp-3);
  }

  .sidebar__group:first-child {
    border-left: none;
    margin-left: 0;
    padding-left: 0;
  }

  .sidebar__link {
    display: flex;
    align-items: center;
    padding: var(--sp-3) var(--sp-4);
    white-space: nowrap;
  }

  .sidebar__link.is-active::before {
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: auto;
    height: 3px;
  }

  .main {
    margin-left: 0;
  }

  .grid--2,
  .grid--3,
  .grid--4 {
    grid-template-columns: 1fr;
  }

  /* Several pages group their fields in a nested .row inside .filters (so the
     "Filter" / "Clear filters" buttons can sit at the end of the same line on
     desktop). .filters stacking its own direct children isn't enough then —
     the .row itself still has to stack, or its fields just keep shrinking
     toward zero width (flex-basis 0, no wrap trigger) instead of ever
     dropping to their own line. */
  .filters,
  .filters .row {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
  }

  .field {
    min-width: 0;
  }

  .header {
    height: auto;
    min-height: 64px;
    flex-wrap: wrap;
    row-gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
  }

  .header__title {
    flex: 1 1 100%;
  }

  .header__actions {
    flex: 1 1 100%;
    margin-left: 0;
    flex-wrap: wrap;
  }
}

@media (max-width: 600px) {
  .header {
    padding: 0 var(--sp-3);
  }

  .content {
    padding: var(--sp-3);
  }

  .modal {
    padding: 0;
    align-items: flex-end;
  }

  .modal__panel {
    max-height: 95vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
  }
}

/* ------------------------------------------------------------------------
   17. Print & reduced motion
   ------------------------------------------------------------------------ */

@media print {
  .sidebar,
  .header__actions,
  .card__tools,
  .table__actions,
  .filters,
  .flash,
  .modal {
    display: none;
  }

  body {
    background: white;
  }

  .card {
    break-inside: avoid;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------------------------------------------------
   18. Focus visible
   ------------------------------------------------------------------------ */

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Override specific focus states that were already defined */
.input:focus-visible,
.select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: none;
}

.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
