/*
 * Tradez — dark theme, "Slate & Emerald".
 *
 * Colours live here as tokens and nowhere else. If a template hardcodes a
 * hex value, that is a bug: it will not follow when this file changes.
 *
 * Two greens, on purpose. --accent is the brand: links, buttons, the
 * wordmark. --profit is money. They share a family so the app feels
 * consistent, but --profit is brighter and lighter so a gain never reads as
 * a link in a table of numbers.
 */

:root {
  /* surfaces */
  --bg:        #1B1E21;   /* page */
  --panel:     #252A2E;   /* cards, nav */
  --panel-2:   #31373C;   /* hover, striped rows */
  --line:      #454C53;   /* borders, rules */

  /* type */
  --text:      #F0F2F4;
  --muted:     #B2B9C0;   /* row labels */
  --dim:       #949CA4;   /* dates, fees, secondary figures */

  /* brand green */
  --accent:       #34D399;  /* links, focus */
  --accent-hover: #6EE7B7;
  --accent-fill:  #0F766E;  /* filled buttons -- light text sits on this */
  --accent-weak:  #34D39922; /* tinted backgrounds */

  /* money. Colour REINFORCES the sign, it never replaces it -- red/green is
     the most common colourblind confusion, so every figure is rendered with
     an explicit + or - and right-aligned in tabular figures. */
  --profit:    #4ADE80;
  --loss:      #FB7185;

  /* Roles. The ledger is mostly numbers, and a wall of grey ones is a wall.
     Colour lets a put, a strike or a premium be found without reading --
     though the word is always still there, so nothing depends on seeing
     the difference. Every one clears 4.5:1 against --panel. */
  --call:      #60A5FA;   /* call contracts */
  --put:       #C084FC;   /* put contracts */
  /* Shares are the one ledger row that is not an option, and they are
     neither a gain nor a loss. Amber sat 13 degrees from --strike, and
     lime or pink would have read as profit or loss on a page full of
     both -- so this stays deliberately neutral and lets the coloured
     roles mean "contract". */
  --shares:    #CBD5E1;   /* share rows */
  --strike:    #FBBF24;   /* strike prices */
  --price:     #5EEAD4;   /* premiums */

  /* status */
  --warn:      #FBBF24;
  --danger:    #FB7185;

  --radius:    10px;
  --radius-sm: 7px;

  /* How wide the app is allowed to get before it stops growing and
     centres instead. Roughly a large laptop; beyond that, extra pixels
     add distance rather than information. */
  --page-max:  1600px;

  /* One card, and the space between two. The row of summary cards and the
     ledger beneath it are both measured from these, so they stay the same
     width as each other by construction. */
  --card-w:    340px;
  --card-gap:  1rem;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
        sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------- nav */

/* The bar spans the window; its contents are capped and centred to line up
   with main. Two elements, because a centred nav with a full-bleed
   background cannot be one. */
.nav-bar {
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  padding: 0.75rem 1.5rem;
}
/* On a phone the links exceed one line; let them wrap under the wordmark
   rather than pushing the last one past the edge and scrolling the page. */
@media (max-width: 560px) {
  .nav { padding: 0.6rem 0.9rem; }
  .nav .spacer { flex-basis: 100%; height: 0; }
  main { padding: 1rem 0.9rem 3rem; }
}
.nav .brand {
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--accent);
}
.nav .brand:hover { text-decoration: none; color: var(--accent-hover); }
.nav .spacer { flex: 1; }

/* ---------------------------------------------------------------- text */

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

/* Full width up to a point, then centred. Without the cap, content on a
   very wide monitor either hugs the left edge or stretches so far that the
   eye has to travel between a row's first column and its last. The nav is
   capped to match, so the wordmark lines up with the content beneath it. */
main {
  width: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 1.5rem 1.5rem 4rem;
}

.nav {
  max-width: var(--page-max);
  margin: 0 auto;
  width: 100%;
}

h1 { font-size: 1.4rem; margin: 0 0 1rem; font-weight: 600; }
h2 { font-size: 1.05rem; margin: 0 0 0.5rem; font-weight: 600; }

.muted { color: var(--muted); }
.dim   { color: var(--dim); font-size: 0.85rem; }
.warn  { color: var(--warn); }

/* ---------------------------------------------------------------- money */

/* Tabular figures so columns of numbers line up on the decimal. */
.num, td.num, .money {
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.profit { color: var(--profit); }
.loss   { color: var(--loss); }

/* ---------------------------------------------------------------- cards */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
}
.card + .card { margin-top: 1rem; }

.row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.3rem 0;
}
.row.total {
  border-top: 1px solid var(--line);
  margin-top: 0.4rem;
  padding-top: 0.6rem;
  font-weight: 600;
}

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

table { width: 100%; border-collapse: collapse; }
th {
  text-align: left;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  padding: 0.5rem 0.6rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
td {
  padding: 0.5rem 0.6rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
tbody tr:hover { background: var(--panel-2); }

/* ---------------------------------------------------------------- forms */

.stack { display: grid; gap: 0.6rem; max-width: 320px; }

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

input, select, textarea {
  font: inherit;
  padding: 0.45rem 0.55rem;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  width: 100%;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}

.helptext, .errorlist { font-size: 0.8rem; }
.errorlist { color: var(--danger); list-style: none; padding: 0; margin: 0.2rem 0; }

/* ---------------------------------------------------------------- buttons */

button, .button {
  font: inherit;
  font-weight: 500;
  color: #fff;
  background: var(--accent-fill);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 0.45rem 0.9rem;
  cursor: pointer;
}
button:hover, .button:hover { background: var(--accent); text-decoration: none; }

button.ghost {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent);
}
button.ghost:hover { background: var(--accent-weak); color: var(--accent-hover); }

/* A button that reads as a link — used for POST-only actions like sign out,
   which must be a form rather than an anchor. */
button.linkish {
  background: none;
  border: 0;
  color: var(--accent);
  padding: 0;
  font-weight: 400;
}
button.linkish:hover { background: none; color: var(--accent-hover);
                       text-decoration: underline; }

/* ---------------------------------------------------------------- misc */

.plain { list-style: none; padding: 0; margin: 0; }

.messages { list-style: none; margin: 0; padding: 0.75rem 1.25rem; }
.messages .success { color: var(--profit); }
.messages .warning { color: var(--warn); }
.messages .error   { color: var(--danger); }

form.inline { display: inline; margin: 0; }

/* ================================================================ desk */

/* Two columns: what has arrived, and where it can go. Wider than the
   article-width `main`, because the whole point is seeing both at once. */
/* A rule between the two columns, so the inbox and the campaigns read as
   two places rather than two piles floating on one page. The gap is split
   either side of it, which is why it is a border on the column rather than
   a separate element -- an element would need its own width and would not
   collapse when the columns stack. */
.desk {
  display: grid;
  grid-template-columns: minmax(300px, 400px) 1fr;
  gap: 2rem;
  align-items: stretch;
}
/* `.column.campaigns` and not `.desk > .campaigns`: the element carries
   both classes, and a later `.desk { align-items: start }` also has to be
   beaten, which is why the stretch is restated here. */
.desk { align-items: stretch; }
.desk > .column.campaigns {
  border-left: 1px solid var(--line);
  padding-left: 2rem;
}
@media (max-width: 860px) {
  /* Stacked, the divider belongs above the second column rather than
     beside it. */
  .desk { grid-template-columns: 1fr; gap: 1.5rem; }
  .desk > .column.campaigns {
    border-left: 0;
    border-top: 1px solid var(--line);
    padding-left: 0;
    padding-top: 1.5rem;
  }
}

/* Each column is titled and underlined, so the heading belongs to what is
   beneath it rather than hovering over the page. */
.column-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.9rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}
.column-head h1 {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.column-head .count {
  background: var(--panel-2);
  color: var(--muted);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  font-size: 0.75rem;
}
.column-head a { margin-left: auto; font-size: 0.8rem; }
.column-head button { margin-left: auto; }

.stack-list { display: grid; gap: 0.5rem; }

/* -- a trade waiting to be placed -- */

.trade {
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.7rem;
  cursor: grab;
}
.trade:active { cursor: grabbing; }
.trade.lifting { opacity: 0.4; }

.trade-head {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.85rem;
}
.trade-head .sym { font-weight: 500; }
.trade-meta {
  display: flex;
  justify-content: space-between;
  font-size: 0.75rem;
  color: var(--muted);
  margin-top: 0.15rem;
}

.chip {
  display: inline-block;
  margin-top: 0.45rem;
  font: inherit;
  font-size: 0.72rem;
  background: var(--accent-weak);
  color: var(--accent);
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  cursor: pointer;
}
.chip:hover { background: var(--accent-fill); color: #fff; }
.chip.ask {
  background: transparent;
  border-color: var(--warn);
  color: var(--warn);
  cursor: help;
}

/* -- campaign cards, which are also drop targets -- */

.card-grid {
  display: grid;
  /* auto-FILL, not auto-fit: empty tracks are kept so cards stay a
     consistent size. Two campaigns stretched to 800px each would be worse
     than two normal cards and some empty desk. */
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 0.75rem;
}

.campaign {
  display: block;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.8rem 0.9rem;
  color: var(--text);
  transition: border-color 0.12s, transform 0.12s;
}
.campaign:hover {
  text-decoration: none;
  border-color: var(--accent);
  transform: translateY(-1px);
}
.campaign.done { opacity: 0.62; }

.campaign-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}
.campaign-head .ticker { font-weight: 600; letter-spacing: 0.01em; }
.campaign-meta {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.4rem;
  font-size: 0.75rem;
  flex-wrap: wrap;
}
.campaign-legs { font-size: 0.72rem; margin-top: 0.3rem; }

.tag {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
  font-size: 0.7rem;
  color: var(--muted);
}

/* The empty slot. Dashed, because it is a place rather than a thing. */
.campaign.new {
  display: grid;
  place-items: center;
  text-align: center;
  min-height: 92px;
  border-style: dashed;
  background: transparent;
  color: var(--dim);
  font-size: 0.78rem;
}

/* Only ever applied while a card is actually over a target. */
.drop.over {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-weak);
  color: var(--text);
}

.empty { color: var(--dim); font-size: 0.85rem; }

/* ======================================================= campaign page */

.back { display: inline-block; font-size: 0.8rem; margin-bottom: 0.75rem; }

.campaign-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}
.campaign-header h1 { margin: 0 0 0.35rem; }
.campaign-header .tag { margin-right: 0.4rem; }

.headline {
  font-size: 1.6rem;
  font-weight: 600;
  text-align: right;
  line-height: 1.2;
}
.headline .label {
  display: block;
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* A 12-column bed rather than auto-fit. auto-fit cannot collapse its empty
   tracks here, because the full-width cards span them -- which left three
   narrow panels stranded beside a stretch of nothing on a wide screen. */
.panels {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1rem;
  align-items: start;
}
.panels .card { grid-column: span 4; }
.panels .card.wide { grid-column: 1 / -1; }
@media (max-width: 1180px) { .panels .card { grid-column: span 6; } }
@media (max-width: 720px)  { .panels .card { grid-column: 1 / -1; } }
/* Measure, not layout: a line of prose stays readable even when its panel
   is 1,600px wide. */
.card p { max-width: 62ch; }
.panels .card + .card { margin-top: 0; }

/* Wide tables scroll inside their own panel rather than the page. */
.scroll-x { overflow-x: auto; }
.scroll-x table { min-width: 520px; }

/* Across a full-width panel an even split throws a date to the far left and
   its amount to the far right, with a hand's width of nothing between. Pin
   every column to its content and let one description column absorb the
   slack instead. */
table.data th, table.data td { white-space: nowrap; width: 1%; }
table.data th.grow, table.data td.grow { width: auto; white-space: normal; }

/* The panel fills the window; the table inside it does not. Stretching a
   row from a date on the far left to its amount 1,600px away makes it hard
   to follow a single line across -- so the table keeps a readable measure
   and the panel simply has room to spare. */
.card.wide table.data { max-width: 1100px; }

/* Striping earns its keep on a long ledger, where a row has to be tracked
   across several columns. */
table.data tbody tr:nth-child(even),
table.data tr:nth-child(even) { background: rgba(255, 255, 255, 0.018); }

/* An open campaign's figure is capital deployed, not a loss. Neutral until
   it is genuinely realised P&L. */
.num.resting { color: var(--text); }

/* Pages with a single small form -- sign-in -- would otherwise sit in the
   top-left corner of a very wide window. */
.centred {
  max-width: 320px;
  margin: 4rem auto 0;
}

/* A card that holds a short list rather than a panel of prose. */
.card.compact {
  max-width: 340px;
  margin-bottom: 1rem;
}
.card.compact h2 { margin-bottom: 0.35rem; }

/* Tight tables drop the header row and the generous padding that goes with
   it -- a four-line position list does not need either. */
table.tight td {
  padding: 0.25rem 0.6rem 0.25rem 0;
  border-bottom: 0;
}
table.tight tr:hover { background: none; }

/* The ledger carries many narrow columns, so it wants a smaller type size
   and tighter gutters than a two-column table. */
/* The ledger fills its card, with the columns sharing the width rather
   than bunching left. Every column is a short number or a date, so none of
   them needs the slack -- spreading it evenly keeps each value under its
   own heading instead of leaving a gap at the end of the row. */
table.ledger { font-size: 0.82rem; width: 100%; }

/* ...and let the card do the same. A card is a block, so it fills its
   parent whether or not it has anything to put there -- which left a
   1,550px panel around a 750px table. inline-block sizes it to the table
   instead. It still scrolls on a narrow screen, because .scroll-x caps it
   at the available width. */
.card.fits {
  display: inline-block;
  max-width: 100%;
}

/* Exactly as wide as the four cards above it: four tracks plus the three
   gaps between them. Computed rather than typed, so changing --card-w
   moves both together. */
.card.matches-cards {
  display: block;
  width: calc(4 * var(--card-w) + 3 * var(--card-gap));
  max-width: 100%;
}
@media (max-width: 1500px) {
  .card.matches-cards { width: calc(2 * var(--card-w) + var(--card-gap)); }
}
@media (max-width: 780px) {
  .card.matches-cards { width: 100%; }
}

/* A long ledger scrolls inside its own card, with the header pinned to the
   top of that box. Sticky needs a scrolling ancestor to stick within, so
   the height cap is what makes it work at all -- without it the header
   would only pin against the window, and a card that ends halfway down the
   page would leave the header floating over nothing.

   The cap is in vh so it adapts to the window: tall screens show more
   rows, short ones fewer, and neither has to scroll the page to reach the
   end of the trades. */
.scroll-y {
  max-height: 60vh;
  overflow-y: auto;
  overflow-x: auto;
}

.scroll-y thead th,
.scroll-y tr:first-child th {
  position: sticky;
  top: 0;
  z-index: 1;
  /* An opaque background is mandatory: rows scroll UNDER a sticky header,
     and a transparent one would show them through the labels. */
  background: var(--panel);
}
/* border-bottom does not travel with a sticky cell -- the border belongs to
   the row, which scrolls away. A box-shadow on the cell does stay. */
.scroll-y tr:first-child th {
  box-shadow: inset 0 -1px 0 var(--line);
  border-bottom: 0;
}
/* The width:1% trick from table.data forces columns narrow when the table
   itself is 100% wide. Here the table sizes to its content instead, and a
   percentage cell width inside an auto-layout table resolves circularly --
   so those cells go back to auto and nowrap does the work. */
table.ledger th, table.ledger td {
  width: auto;
  padding-right: 1rem;
}
/* Breathing room at both ends. Without it the first and last values sit
   flush against the card's edge and the row reads as clipped rather than
   contained. */
table.ledger th:first-child, table.ledger td:first-child {
  padding-left: 0.75rem;
}
table.ledger th:last-child, table.ledger td:last-child {
  padding-right: 0.75rem;
}
/* Fill the card, but never at the cost of a number.
   `width: 100%` with automatic layout spreads the slack across the columns
   when there is room, and stops at content width when there is not --
   min-width then lets the card scroll instead of squeezing.

   table-layout: fixed was the obvious way to get even columns and the
   wrong one: at 1,290px it shrank every column to 59px and clipped 116
   cells, including dates and dollar amounts. A truncated $129.49 is worse
   than an uneven column. */
table.ledger { min-width: max-content; }

/* Two compact cards abreast, stacking when there is no room for both. */
/* Four cards abreast at full size, collapsing as the window narrows. The
   row's width is stated rather than emergent -- the ledger below has to
   line up with it, and two layouts that merely happen to agree will drift
   apart the moment either changes. */
.side-by-side {
  display: grid;
  grid-template-columns: repeat(4, var(--card-w));
  justify-content: start;
  gap: var(--card-gap);
  align-items: stretch;
  margin-bottom: 1rem;
  /* Fit the cards rather than the page. As a plain block this stretched to
     main's full width, leaving empty container to the right of the last
     card -- invisible until something below tried to line up with it. */
  width: max-content;
  max-width: 100%;
}
@media (max-width: 1500px) {
  .side-by-side { grid-template-columns: repeat(2, var(--card-w)); }
}
@media (max-width: 780px) {
  .side-by-side { grid-template-columns: minmax(0, var(--card-w)); }
}
/* `.card + .card` adds a top margin so stacked cards do not touch. Side by
   side that rule pushes the second card 16px below the first, which is
   what knocked these two out of alignment -- the gap does the spacing
   here. */
.side-by-side .card,
.side-by-side .card + .card {
  margin-top: 0;
  margin-bottom: 0;
  max-width: none;
  /* align-items:start would size each card to its own content, so two
     cards with different numbers of rows end up different heights and the
     pair reads as ragged. Stretch makes them share the tallest height. */
  height: 100%;
}
.side-by-side { align-items: stretch; }

/* Unrealized is real money, but not yet earned -- green, held back from
   the full weight of a realised gain. */
.dim-profit { color: var(--accent); }

.tiny { font-size: 0.72rem; margin-top: 0.6rem; }


/* ================================================== scrollbars */

/* Chrome and Safari paint a light scrollbar by default, which reads as a
   bright stripe against a dark panel. Two mechanisms are needed: the
   standard `scrollbar-color` for Firefox, and the WebKit pseudo-elements
   for everything Chromium-based. Neither covers both.

   Applied to the whole document as well as scrolling panels, so the page
   scrollbar matches the ones inside cards. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 999px;
  /* A transparent border with background-clip keeps the thumb clear of the
     track edge, so it reads as a floating bar rather than a filled gutter. */
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--panel-2);
  background-clip: content-box;
}
/* Where the vertical and horizontal bars meet. */
::-webkit-scrollbar-corner {
  background: transparent;
}




/* ============================================================ roles

   Applied to ledger cells so the data itself is legible at a glance.
   Colour is never the only signal -- the row still names the contract
   type in words. */
.r-call   { color: var(--call); }
.r-put    { color: var(--put); }
.r-shares { color: var(--shares); }
.r-strike { color: var(--strike); }
.r-price  { color: var(--price); }

/* ============================================================= rolls

   A roll is two legs of one order: close this contract, open that one.
   The pair is outlined so it reads as one event, with a rule down the left
   edge. Only orders Schwab grouped itself are marked; see derived.rolls.

   Twenty colours, cycled in order, so back-to-back rolls stay apart. The
   first is the accent green, and the sequence walks the wheel in 126-degree
   steps -- coprime with 20, so every slot is used before any repeats and no
   two consecutive rolls land on neighbouring hues. Every one clears 3:1
   against the panel, which is the bar for a 2px rule rather than for text.
   Past twenty the cycle restarts, which is the point at which a campaign
   has more rolls than anyone can hold in their head anyway. */
:root {
  --roll-1: #5EF9B1;
  --roll-2: #B96EE5;
  --roll-3: #E5BF57;
  --roll-4: #77F9E4;
  --roll-5: #DC57E5;
  --roll-6: #E1E56E;
  --roll-7: #5EE5F9;
  --roll-8: #E56EC9;
  --roll-9: #B6E557;
  --roll-10: #77C1F9;
  --roll-11: #E55799;
  --roll-12: #99E56E;
  --roll-13: #5E88F9;
  --roll-14: #E56E82;
  --roll-15: #60E557;
  --roll-16: #7C77F9;
  --roll-17: #E56A57;
  --roll-18: #6EE58A;
  --roll-19: #925EF9;
  --roll-20: #E5A16E;
}

/* Outline the pair: a top edge on the first leg, a bottom edge on the last,
   and a side on every row. Borders sit on the CELLS, not the row -- a
   border-collapse table drops row borders entirely.

   Each edge is declared explicitly rather than relying on `border-style`
   alone, because the generic td rule sets border-bottom later in the file
   and would otherwise repaint the outline grey. */
tr[class*="roll-"] > td {
  border-top: 0 solid transparent;
  border-bottom: 0 solid transparent;
  border-left: 0 solid transparent;
  border-right: 0 solid transparent;
}
tr[class*="roll-"].roll-start > td { border-top-width: 1px; }
tr[class*="roll-"].roll-end > td { border-bottom-width: 1px; }
tr[class*="roll-"] > td:first-child { border-left-width: 1px; }
tr[class*="roll-"] > td:last-child { border-right-width: 1px; }

tr.roll-1 td { border-color: var(--roll-1); }
tr.roll-1 td:first-child { box-shadow: inset 2px 0 0 var(--roll-1); }
tr.roll-1 .roll-tag { color: var(--roll-1); }
tr.roll-2 td { border-color: var(--roll-2); }
tr.roll-2 td:first-child { box-shadow: inset 2px 0 0 var(--roll-2); }
tr.roll-2 .roll-tag { color: var(--roll-2); }
tr.roll-3 td { border-color: var(--roll-3); }
tr.roll-3 td:first-child { box-shadow: inset 2px 0 0 var(--roll-3); }
tr.roll-3 .roll-tag { color: var(--roll-3); }
tr.roll-4 td { border-color: var(--roll-4); }
tr.roll-4 td:first-child { box-shadow: inset 2px 0 0 var(--roll-4); }
tr.roll-4 .roll-tag { color: var(--roll-4); }
tr.roll-5 td { border-color: var(--roll-5); }
tr.roll-5 td:first-child { box-shadow: inset 2px 0 0 var(--roll-5); }
tr.roll-5 .roll-tag { color: var(--roll-5); }
tr.roll-6 td { border-color: var(--roll-6); }
tr.roll-6 td:first-child { box-shadow: inset 2px 0 0 var(--roll-6); }
tr.roll-6 .roll-tag { color: var(--roll-6); }
tr.roll-7 td { border-color: var(--roll-7); }
tr.roll-7 td:first-child { box-shadow: inset 2px 0 0 var(--roll-7); }
tr.roll-7 .roll-tag { color: var(--roll-7); }
tr.roll-8 td { border-color: var(--roll-8); }
tr.roll-8 td:first-child { box-shadow: inset 2px 0 0 var(--roll-8); }
tr.roll-8 .roll-tag { color: var(--roll-8); }
tr.roll-9 td { border-color: var(--roll-9); }
tr.roll-9 td:first-child { box-shadow: inset 2px 0 0 var(--roll-9); }
tr.roll-9 .roll-tag { color: var(--roll-9); }
tr.roll-10 td { border-color: var(--roll-10); }
tr.roll-10 td:first-child { box-shadow: inset 2px 0 0 var(--roll-10); }
tr.roll-10 .roll-tag { color: var(--roll-10); }
tr.roll-11 td { border-color: var(--roll-11); }
tr.roll-11 td:first-child { box-shadow: inset 2px 0 0 var(--roll-11); }
tr.roll-11 .roll-tag { color: var(--roll-11); }
tr.roll-12 td { border-color: var(--roll-12); }
tr.roll-12 td:first-child { box-shadow: inset 2px 0 0 var(--roll-12); }
tr.roll-12 .roll-tag { color: var(--roll-12); }
tr.roll-13 td { border-color: var(--roll-13); }
tr.roll-13 td:first-child { box-shadow: inset 2px 0 0 var(--roll-13); }
tr.roll-13 .roll-tag { color: var(--roll-13); }
tr.roll-14 td { border-color: var(--roll-14); }
tr.roll-14 td:first-child { box-shadow: inset 2px 0 0 var(--roll-14); }
tr.roll-14 .roll-tag { color: var(--roll-14); }
tr.roll-15 td { border-color: var(--roll-15); }
tr.roll-15 td:first-child { box-shadow: inset 2px 0 0 var(--roll-15); }
tr.roll-15 .roll-tag { color: var(--roll-15); }
tr.roll-16 td { border-color: var(--roll-16); }
tr.roll-16 td:first-child { box-shadow: inset 2px 0 0 var(--roll-16); }
tr.roll-16 .roll-tag { color: var(--roll-16); }
tr.roll-17 td { border-color: var(--roll-17); }
tr.roll-17 td:first-child { box-shadow: inset 2px 0 0 var(--roll-17); }
tr.roll-17 .roll-tag { color: var(--roll-17); }
tr.roll-18 td { border-color: var(--roll-18); }
tr.roll-18 td:first-child { box-shadow: inset 2px 0 0 var(--roll-18); }
tr.roll-18 .roll-tag { color: var(--roll-18); }
tr.roll-19 td { border-color: var(--roll-19); }
tr.roll-19 td:first-child { box-shadow: inset 2px 0 0 var(--roll-19); }
tr.roll-19 .roll-tag { color: var(--roll-19); }
tr.roll-20 td { border-color: var(--roll-20); }
tr.roll-20 td:first-child { box-shadow: inset 2px 0 0 var(--roll-20); }
tr.roll-20 .roll-tag { color: var(--roll-20); }

.roll-tag { color: var(--roll-1); }
.roll-2 td { border-color: var(--roll-2); }
.roll-2 td:first-child { box-shadow: inset 2px 0 0 var(--roll-2); }
.roll-2 .roll-tag { color: var(--roll-2); }
.roll-3 td { border-color: var(--roll-3); }
.roll-3 td:first-child { box-shadow: inset 2px 0 0 var(--roll-3); }
.roll-3 .roll-tag { color: var(--roll-3); }
.roll-4 td { border-color: var(--roll-4); }
.roll-4 td:first-child { box-shadow: inset 2px 0 0 var(--roll-4); }
.roll-4 .roll-tag { color: var(--roll-4); }
.roll-5 td { border-color: var(--roll-5); }
.roll-5 td:first-child { box-shadow: inset 2px 0 0 var(--roll-5); }
.roll-5 .roll-tag { color: var(--roll-5); }
.roll-6 td { border-color: var(--roll-6); }
.roll-6 td:first-child { box-shadow: inset 2px 0 0 var(--roll-6); }
.roll-6 .roll-tag { color: var(--roll-6); }
.roll-7 td { border-color: var(--roll-7); }
.roll-7 td:first-child { box-shadow: inset 2px 0 0 var(--roll-7); }
.roll-7 .roll-tag { color: var(--roll-7); }
.roll-8 td { border-color: var(--roll-8); }
.roll-8 td:first-child { box-shadow: inset 2px 0 0 var(--roll-8); }
.roll-8 .roll-tag { color: var(--roll-8); }
.roll-9 td { border-color: var(--roll-9); }
.roll-9 td:first-child { box-shadow: inset 2px 0 0 var(--roll-9); }
.roll-9 .roll-tag { color: var(--roll-9); }
.roll-10 td { border-color: var(--roll-10); }
.roll-10 td:first-child { box-shadow: inset 2px 0 0 var(--roll-10); }
.roll-10 .roll-tag { color: var(--roll-10); }
.roll-11 td { border-color: var(--roll-11); }
.roll-11 td:first-child { box-shadow: inset 2px 0 0 var(--roll-11); }
.roll-11 .roll-tag { color: var(--roll-11); }
.roll-12 td { border-color: var(--roll-12); }
.roll-12 td:first-child { box-shadow: inset 2px 0 0 var(--roll-12); }
.roll-12 .roll-tag { color: var(--roll-12); }
.roll-13 td { border-color: var(--roll-13); }
.roll-13 td:first-child { box-shadow: inset 2px 0 0 var(--roll-13); }
.roll-13 .roll-tag { color: var(--roll-13); }
.roll-14 td { border-color: var(--roll-14); }
.roll-14 td:first-child { box-shadow: inset 2px 0 0 var(--roll-14); }
.roll-14 .roll-tag { color: var(--roll-14); }
.roll-15 td { border-color: var(--roll-15); }
.roll-15 td:first-child { box-shadow: inset 2px 0 0 var(--roll-15); }
.roll-15 .roll-tag { color: var(--roll-15); }
.roll-16 td { border-color: var(--roll-16); }
.roll-16 td:first-child { box-shadow: inset 2px 0 0 var(--roll-16); }
.roll-16 .roll-tag { color: var(--roll-16); }
.roll-17 td { border-color: var(--roll-17); }
.roll-17 td:first-child { box-shadow: inset 2px 0 0 var(--roll-17); }
.roll-17 .roll-tag { color: var(--roll-17); }
.roll-18 td { border-color: var(--roll-18); }
.roll-18 td:first-child { box-shadow: inset 2px 0 0 var(--roll-18); }
.roll-18 .roll-tag { color: var(--roll-18); }
.roll-19 td { border-color: var(--roll-19); }
.roll-19 td:first-child { box-shadow: inset 2px 0 0 var(--roll-19); }
.roll-19 .roll-tag { color: var(--roll-19); }
.roll-20 td { border-color: var(--roll-20); }
.roll-20 td:first-child { box-shadow: inset 2px 0 0 var(--roll-20); }
.roll-20 .roll-tag { color: var(--roll-20); }

.roll-tag {
  display: inline-block;
  margin-right: 0.35rem;
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* =========================================================== identity */

/* The mark scales with the wordmark beside it rather than being pinned to
   a pixel size, so it stays proportionate if the brand type ever changes. */
.logo {
  width: 1.35rem;
  height: 1.35rem;
  min-width: 1.35rem;
  vertical-align: -0.28em;
  margin-right: 0.4rem;
}

/* Icons take their colour from the text beside them -- currentColor means a
   nav icon dims and brightens with its link without extra rules.

   Sized in REM, not em. An SVG establishes its own font-size context, so
   1.05em resolved against the SVG rather than the heading and rendered a
   300px icon. rem is absolute and cannot be knocked off course that way. */
.icon {
  width: 1rem;
  height: 1rem;
  min-width: 1rem;
  vertical-align: -0.16em;
  margin-right: 0.34rem;
  flex: none;
}

.nav a { display: inline-flex; align-items: center; }
.nav .brand { display: inline-flex; align-items: center; }

/* Section headings carry a dimmer icon than their label -- it should mark
   the heading, not compete with it. */
.card h2 .icon,
.column-head h1 .icon {
  color: var(--dim);
  margin-right: 0.4rem;
}
.card h2, .column-head h1 { display: flex; align-items: center; }

/* ============================================================= footer */

/* The footer sits at the bottom of the WINDOW on a short page and below
   the content on a long one. The body is a column at least as tall as the
   viewport, and main takes the slack -- so the footer is pushed down when
   there is spare height and simply follows the content when there is not.

   Not `position: fixed`: that would float the footer over the ledger and
   cover the last rows on every long page. */
.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.page > main { flex: 1 0 auto; }

.footer {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 1.25rem 1.5rem 1.5rem;
  border-top: 1px solid var(--line);
  color: var(--dim);
  font-size: 0.75rem;
}

/* ================================================= ledger action icons */

/* A glyph beside each action, so a column of Sell / Buy / Expired can be
   scanned by shape instead of read word by word. */
.action { display: inline-flex; align-items: center; white-space: nowrap; }
.action .icon {
  width: 0.85rem;
  height: 0.85rem;
  min-width: 0.85rem;
  margin-right: 0.3rem;
}
.action-sell .icon { color: var(--profit); }
.action-buy .icon { color: var(--loss); }
.action-expired .icon { color: var(--dim); }
.action-assigned .icon { color: var(--warn); }

/* ======================================================= empty states */

/* An empty inbox should look finished, not broken. */
.empty-state {
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--dim);
}
.empty-state .icon {
  width: 2.2rem;
  height: 2.2rem;
  min-width: 2.2rem;
  margin: 0;
  color: var(--line);
}
.empty-state p { margin: 0; font-size: 0.82rem; max-width: 26ch; }

/* ============================================================ notices

   A banner above the desk. Used for things that will stop the app working
   if ignored -- chiefly Schwab access lapsing, which otherwise makes a
   dead import look like a quiet week. */
.notice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1.25rem;
  padding: 0.65rem 0.9rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--panel);
  font-size: 0.82rem;
}
.notice .icon { color: var(--accent); }
.notice a:not(.button) { margin-left: auto; }

.notice-warn { border-left-color: var(--warn); }
.notice-warn .icon { color: var(--warn); }
.notice-warn .button { margin-left: auto; }

button.small, .button.small {
  font-size: 0.75rem;
  padding: 0.25rem 0.6rem;
}
button.failed {
  border-color: var(--danger);
  color: var(--danger);
  background: transparent;
}

/* The outcome of an import, beside the button rather than inside it --
   replacing a button's contents takes its icon with it, and a message that
   disappears after two seconds is easy to miss. This one stays until the
   next press. */
.import-result {
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}
.import-result.failed { color: var(--danger); }

/* ======================================================== add trade form */

.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.page-head h1 { margin-bottom: 0; }
.page-head .button { display: inline-flex; align-items: center; }

/* Fields on the left, the live panel riding alongside -- visible while
   any field is being typed in, which is the entire point of it. */
.trade-layout {
  display: grid;
  grid-template-columns: minmax(340px, 560px) 320px;
  gap: 1.5rem;
  align-items: start;
  margin-top: 1rem;
}
@media (max-width: 900px) {
  .trade-layout { grid-template-columns: 1fr; }
}

/* Six columns so rows can hold two fields (half) or three (third).
   Michael's layout: strategy|ticker, entered|expiration,
   strike|contracts|premium, misc|commission, note. */
/* Thirty columns -- divisible by two, three AND five -- so a row of
   halves, a row of thirds and a row of five number fields all run the
   same full width. Ten columns looked fine until the roll form put
   thirds (3x3=9) under halves (2x5=10) and every row ended somewhere
   different. */
.field-grid {
  display: grid;
  grid-template-columns: repeat(30, 1fr);
  gap: 0.9rem 1rem;
  margin-top: 0.9rem;
}
.field-grid .half  { grid-column: span 15; }
.field-grid .third { grid-column: span 10; }
/* A third that shares its row with a half AND a fifth: 15+9+6 = 30.
   The plain third (10) only sums when the whole row is thirds. */
.field-grid .third-tight { grid-column: span 9; }
.field-grid .fifth { grid-column: span 6; }
.field-grid .full  { grid-column: 1 / -1; }
@media (max-width: 640px) {
  /* Five abreast has no room on a phone; pair them up instead. */
  .field-grid .fifth { grid-column: span 15; }
}

.field label {
  display: flex;
  align-items: center;
  margin-bottom: 0.3rem;
}
.field label .icon { color: var(--dim); }
.ticker-input { text-transform: uppercase; }
.field input, .field select {
  width: 100%;
  color-scheme: dark;   /* native date/time pickers match the theme */
}
.field .errorlist { margin-top: 0.25rem; }

.submit-trade {
  margin-top: 1.2rem;
  display: inline-flex;
  align-items: center;
  padding: 0.55rem 1.2rem;
}

.live-panel { position: sticky; top: 1rem; }
.live-panel .row .num { transition: color 0.15s; }


/* A value that just changed announces it, briefly. */
@keyframes live-pulse {
  0%   { color: var(--accent-hover); }
  100% { color: inherit; }
}
.live-panel .num.pulse { animation: live-pulse 0.6s ease-out; }

/* Buttons lift a little on hover -- movement without noise. */
button, .button { transition: transform 0.1s, background 0.15s; }
button:hover:not(:disabled), .button:hover { transform: translateY(-1px); }

/* Provisional campaign chips on the desk, until it is designed. */
.chip-row { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1rem; }
.campaign-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  color: var(--text);
}
.campaign-chip:hover { border-color: var(--accent); text-decoration: none; }
.campaign-chip .icon { color: var(--accent); }

.cover-note { margin: 0.55rem 0 0; }
.page-head .button + .button, .chip-row .button + .button { margin-left: 0.5rem; }

/* ======================================================== lot actions */

.section-label {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0.4rem 0 0;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.35rem;
}

.lot-actions .chip {
  margin: 0 0.25rem 0.25rem 0;
  cursor: pointer;
}

.confirm-dialog {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.2rem 1.4rem;
  min-width: 22rem;
  max-width: 90vw;
}
.confirm-dialog::backdrop { background: rgba(0, 0, 0, 0.6); }
.confirm-dialog h2 { margin-bottom: 0.7rem; }
.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  margin-top: 1rem;
}

/* ------------------------------------------------------------------ desk */

/* The strip: the whole account in five tiles. Tiles fade up one after
   another -- 40ms apart, driven by --i from the template -- which reads
   as "alive" without ever being something to wait for. */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.8rem;
  margin: 1.2rem 0 1.6rem;
}
.stat {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  animation: fade-up 0.35s ease-out backwards;
}
.stat:nth-child(1) { animation-delay: 0ms; }
.stat:nth-child(2) { animation-delay: 40ms; }
.stat:nth-child(3) { animation-delay: 80ms; }
.stat:nth-child(4) { animation-delay: 120ms; }
.stat:nth-child(5) { animation-delay: 160ms; }
.stat-label {
  color: var(--dim);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.stat-label .icon { width: 0.9rem; height: 0.9rem; }
.stat-num {
  font-size: 1.35rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.stat-num.dim { font-size: 1.35rem; }   /* keep size when dimmed */

@keyframes fade-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* The campaigns, as cards -- Michael picked these over a list. The whole
   card is the link; hover lifts it a hair and warms the border, same
   restraint as the buttons. */
.desk-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--card-gap);
}
.campaign-card {
  display: block;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  color: inherit;
  animation: fade-up 0.35s ease-out backwards;
  animation-delay: calc(var(--i, 0) * 50ms);
  transition: transform 0.12s ease, border-color 0.12s ease;
}
.campaign-card:hover {
  text-decoration: none;
  color: inherit;
  transform: translateY(-2px);
  border-color: var(--accent);
}
.campaign-card .card-head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}
.campaign-card .ticker {
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.campaign-card .days { margin-left: auto; }
.campaign-card .row { padding: 0.28rem 0; }
.campaign-card .row.total {
  border-top: 1px solid var(--line);
  margin-top: 0.35rem;
  padding-top: 0.5rem;
}

/* Position chips, shared by both options. */
.position-bits {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.7rem;
}
.bit {
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  background: var(--accent-weak);
}

/* A finished campaign: same card, quieter. The date range replaces the
   position chips -- there is no position, only what it ended as. */
.campaign-card.closed { opacity: 0.85; }
.campaign-card.closed:hover { opacity: 1; }
.campaign-card.closed .ticker { color: var(--muted); }

/* ------------------------------------------------- campaign detail */

.head-actions { display: flex; gap: 0.6rem; }

/* The line under the title: dates and days, then the position as the
   same chips the cards wear. */
.detail-sub {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: -0.4rem 0 0.4rem;
}

/* A tile can carry one quiet second line ("· annualized",
   "broker shows ..."). */
.stat-sub { color: var(--dim); font-size: 0.78rem; }

.tag.done {
  color: var(--warn);
  border-color: var(--warn);
}

/* Lots: the ledger's role colours name the row -- put, call, shares --
   so the eye finds a lot without reading. Settled lots recede.
   Both this card and History run the full content width so their edges
   line up with the tile strip above -- three ragged right edges read
   as three unrelated pages. */
.card.lots-card { margin-bottom: 1rem; }
.card.history-card { max-width: 100%; overflow-x: auto; }
.lot-name { font-weight: 600; }
.lot-name.lot-put { color: var(--put); }
.lot-name.lot-call { color: var(--call); }
.lot-name.lot-shares { color: var(--shares); }
tr.settled-lot .lot-name { opacity: 0.6; }

/* History wants its columns spread across the card, not squeezed to
   content width -- eleven columns of numbers need the room. */
table.data.spread th, table.data.spread td { width: auto; }
table.data.spread td.lot-actions { width: 1%; }

/* The Dividend toggle, dressed as a form field: label above, switch
   where the input would sit. A plain checkbox drawn as a switch -- the
   input stays real (and keyboard-reachable); only its pixels change. */
.switch {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  /* Centre the 20px pill in the height an input occupies beside it. */
  padding: 0.45rem 0;
}
.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.switch .slider {
  width: 34px;
  height: 20px;
  border-radius: 999px;
  background: var(--line);
  position: relative;
  flex: none;
  transition: background 0.15s ease;
}
.switch .slider::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text);
  transition: transform 0.15s ease;
}
.switch input:checked + .slider { background: var(--accent-fill); }
.switch input:checked + .slider::after { transform: translateX(14px); }
.switch input:focus-visible + .slider {
  box-shadow: 0 0 0 3px var(--accent-weak);
}

/* The gear menu on a campaign page: a details/summary dropdown, so it
   opens and closes with zero script. */
.gear-menu {
  position: relative;
  /* Stretch to the row so the gear stands exactly as tall as the
     buttons beside it. */
  display: flex;
  align-items: stretch;
}
.gear-menu summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* The base .icon rule spaces an icon from the label after it; the gear
   stands alone, so that margin just pushed it off-centre. */
.gear-menu summary .icon { margin-right: 0; }
.gear-menu summary::-webkit-details-marker { display: none; }
.gear-pop {
  position: absolute;
  right: 0;
  top: calc(100% + 0.4rem);
  z-index: 10;
  min-width: 220px;
  padding: 0.7rem 0.9rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}
.gear-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}
.gear-row .muted {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.gear-row .switch { padding: 0; }

/* The desk gear's settings form. */
.gear-pop form {
  display: grid;
  gap: 0.6rem;
}
.gear-field {
  display: grid;
  gap: 0.3rem;
}
.gear-field .muted {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.85rem;
}

/* The closed-campaigns trade log. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  margin-bottom: 0.9rem;
}
.filter-bar select, .filter-bar input { width: auto; }
.filter-bar input[name="ticker"] { width: 7.5rem; }
tr.rowlink { cursor: pointer; transition: background 0.1s ease; }
tr.rowlink:hover { background: var(--accent-weak); }
.ticker-link { font-weight: 700; letter-spacing: 0.02em; }
.pager {
  display: flex;
  gap: 1rem;
  align-items: center;
  margin-top: 0.9rem;
}

/* ------------------------------------------------------------ calendar */

.cal-nav { align-items: center; gap: 0.8rem; }
.cal-title {
  font-weight: 600;
  min-width: 10rem;
  text-align: center;
}
.cal-strip { grid-template-columns: repeat(auto-fit, minmax(200px, 240px)); }

.cal-card { padding: 0.6rem 0.8rem; }
.cal-table { table-layout: fixed; }
.cal-table th {
  text-align: right;
  padding-right: 0.5rem;
}
.cal-table th.cal-week-th { border-left: 1px solid var(--line); }
.cal-day {
  height: 4.6rem;
  vertical-align: top;
  position: relative;
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--line);
}
.cal-num {
  color: var(--dim);
  font-size: 0.78rem;
}
.cal-out .cal-num { opacity: 0.35; }
.cal-out .cal-amount { opacity: 0.55; }
.cal-today .cal-num {
  color: var(--accent);
  font-weight: 700;
}
.cal-amount {
  position: absolute;
  right: 0.5rem;
  bottom: 0.4rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.cal-day.cal-has { cursor: pointer; transition: background 0.1s ease; }
.cal-day.cal-has:hover { background: var(--accent-weak); }
.cal-week {
  border-left: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  vertical-align: bottom;
  padding: 0.35rem 0.5rem 0.4rem;
  font-weight: 600;
}
#day-lines .row a { color: var(--muted); }
#day-lines .row a:hover { color: var(--accent); }

/* ---------------------------------------------------------------- plan */

.plan-pop { min-width: 260px; }
.year-ends {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1rem;
}
.year-ends .bit strong { color: var(--text); margin-left: 0.3rem; }
.plan-table td { padding-top: 0.3rem; padding-bottom: 0.3rem; }
tr.plan-past td { opacity: 0.75; }
tr.plan-current td {
  background: var(--accent-weak);
  opacity: 1;
}
tr.plan-current td:first-child { border-left: 3px solid var(--accent); }
tr.plan-future td { color: var(--muted); }
.inline-form { display: inline; }
input.contrib {
  width: 6.5rem;
  padding: 0.2rem 0.4rem;
  text-align: right;
  font-size: 0.9rem;
}
a.stat-link { color: inherit; }
a.stat-link:hover { text-decoration: none; border-color: var(--accent); }

/* Plan arrows: actual against target, at a glance. */
.arrow {
  display: inline-block;
  width: 1.1em;
  text-align: right;
  font-size: 0.75em;
}
.arrow-up::before   { content: "▲"; color: var(--profit); }
.arrow-down::before { content: "▼"; color: var(--loss); }
.arrow-flat::before { content: "="; color: var(--dim); }

/* The error pages: one short sentence and a way back. */
.centred-note {
  max-width: 32rem;
  margin: 4rem auto;
  text-align: center;
}
.centred-note .button { margin-top: 1rem; display: inline-flex; }

/* The "set a rate" hint under Commission: a whisper, not a warning. */
.rate-hint { margin: -0.4rem 0 0; }
