:root {
  color-scheme: light dark;
  --bg: #f5f6f2;
  --surface: #ffffff;
  --ink: #161b18;
  --ink-muted: #5c6862;
  --accent: #2e5c4e;
  --accent-ink: #ffffff;
  --pos: #1e7a3c;
  --pos-bg: #e4f2e7;
  --neg: #b23a3a;
  --neg-bg: #f8e7e6;
  --flat-bg: #eceeea;
  --border: #dee3dd;
  --row-hover: #f0f2ed;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #111512;
    --surface: #171d19;
    --ink: #e8ece7;
    --ink-muted: #8b968e;
    --accent: #6fc0a6;
    --accent-ink: #0d211a;
    --pos: #4fae72;
    --pos-bg: #17281d;
    --neg: #e1746f;
    --neg-bg: #2c1a19;
    --flat-bg: #1e2521;
    --border: #293530;
    --row-hover: #1c2320;
  }
}

:root[data-theme="dark"] {
  --bg: #111512;
  --surface: #171d19;
  --ink: #e8ece7;
  --ink-muted: #8b968e;
  --accent: #6fc0a6;
  --accent-ink: #0d211a;
  --pos: #4fae72;
  --pos-bg: #17281d;
  --neg: #e1746f;
  --neg-bg: #2c1a19;
  --flat-bg: #1e2521;
  --border: #293530;
  --row-hover: #1c2320;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  padding: clamp(8px, 1.2vw, 16px);
  display: flex;
  justify-content: center;
}

.page {
  width: 100%;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 10px;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 8px;
}

h1 {
  font-family: "Fraunces", Georgia, serif;
  font-weight: 600;
  font-size: clamp(22px, 2.6vw, 28px);
  letter-spacing: -0.01em;
  text-wrap: balance;
  margin: 0;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.status-bar {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 11.5px;
  color: var(--ink-muted);
  text-align: right;
  /* Always rendered (never [hidden]) so it permanently reserves this one
     line of height -- only its text changes as the load state changes,
     which never shifts anything below it (the table included). */
  min-height: 1.4em;
}

.status-bar .status-live {
  color: var(--accent);
}

.status-bar .status-live.error {
  color: var(--neg);
}

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

.table-scroll {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  min-width: 980px;
}

thead th {
  text-align: left;
  font-family: "IBM Plex Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 500;
  padding: 6px 12px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  position: sticky;
  top: 0;
  background: var(--surface);
}

thead th.num, tbody td.num {
  text-align: right;
}

thead th.sortable {
  cursor: pointer;
  user-select: none;
  transition: color 0.12s ease;
}

thead th.sortable:hover {
  color: var(--ink);
}

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

.th-inner {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.sort-arrow {
  display: inline-flex;
  flex-direction: row;
  gap: 0px;
  font-size: 8px;
  line-height: 1;
  font-family: "IBM Plex Sans", sans-serif;
}

.sort-arrow .arrow-up,
.sort-arrow .arrow-down {
  opacity: 0.3;
  color: var(--ink-muted);
}

thead th.sort-asc .arrow-up {
  opacity: 1;
  color: var(--accent);
}

thead th.sort-desc .arrow-down {
  opacity: 1;
  color: var(--accent);
}

tbody tr {
  border-bottom: 1px solid var(--border);
  transition: background 0.12s ease;
}

tbody tr:last-child { border-bottom: none; }

tbody tr:hover { background: var(--row-hover); }

tbody td {
  padding: 4px 12px;
  vertical-align: middle;
  white-space: nowrap;
}

/* table-cell vertical-align: middle centers based on font baseline
   metrics, not the actual rendered pixel box -- close, but visibly off
   by a px or two once a row's height (set by the tallest cell, usually
   Latest News) is noticeably taller than a cell's own one-line content.
   Every cell except Latest News (which centers itself via .news-clamp)
   wraps its content in this instead for exact centering. */
.cell-center {
  display: flex;
  align-items: center;
  height: 100%;
}

.price .cell-center,
.change-cell .cell-center {
  justify-content: flex-end;
}

.actions-cell .cell-center {
  justify-content: center;
  gap: 10px;
}

.owned-cell .chip {
  font-family: "IBM Plex Sans", sans-serif;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.chip.owned-yes { color: var(--pos); background: var(--pos-bg); }
.chip.owned-no { color: var(--ink-muted); background: var(--flat-bg); }

tbody tr.row-owned td:first-child {
  border-left: 3px solid var(--accent);
}

tbody tr.row-observed td:first-child {
  border-left: 3px solid transparent;
}

/* Applied per-cell rather than on the row itself, and skipping .news --
   .news-clamp's text is already var(--ink-muted), so dimming it further
   here made a not-owned row's news read visibly darker/muted than the
   exact same var(--ink-muted) text in an owned row. */
tbody tr.row-observed td:not(.news) {
  opacity: 0.82;
}

.exchange {
  font-family: "IBM Plex Mono", monospace;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.03em;
  color: var(--ink-muted);
}

/* flag-icons' own .fi class supplies the actual flag image (via its
   fi-<code> pair, e.g. "fi fi-gb") -- this just overrides its sizing to
   match the rest of the UI. Comes from stock.exchange.flagClass, set
   server-side in app.py for every exchange, built-in or custom, so
   there's exactly one flag-rendering path for both. */
.flag {
  width: 16px;
  height: 11px;
  margin-right: 6px;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset;
  background-size: cover;
  background-position: center;
  vertical-align: middle;
}

.ticker {
  font-family: "IBM Plex Mono", monospace;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.02em;
}

.company {
  font-size: 12.5px;
  color: var(--ink-muted);
  white-space: nowrap;
}

.stock-link {
  color: inherit;
  text-decoration: none;
}

.stock-link:hover,
.stock-link:focus-visible {
  text-decoration: underline;
}

.price {
  font-family: "IBM Plex Mono", monospace;
  font-variant-numeric: tabular-nums;
  font-size: 15px;
  font-weight: 500;
  white-space: nowrap;
  /* Reserves the width a real price needs (e.g. "PLN 1,230.00") from the
     very first paint, while this cell still just shows the pending
     spinner -- so the column doesn't visibly widen once /api/prices
     resolves. Price/change are the only columns that go pending -> real
     on every single page load (Earnings/News already arrive final in the
     first phase), which is why only these two get this treatment. */
  min-width: 130px;
}

.change-cell {
  min-width: 115px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: "IBM Plex Mono", monospace;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  font-weight: 500;
  padding: 2px 8px;
  border-radius: 6px;
  white-space: nowrap;
  /* Without this, an inline-flex box's baseline is synthesized from its
     bottom margin edge (there's no in-flow text to give it a real one),
     which sits lower than the td's actual vertical middle -- makes chips
     (Owned, Daily Change) look a little low compared to plain-text cells
     in the same row. */
  vertical-align: middle;
}

.chip.pos { color: var(--pos); background: var(--pos-bg); }
.chip.neg { color: var(--neg); background: var(--neg-bg); }
.chip.flat { color: var(--ink-muted); background: var(--flat-bg); }

.chip.chip-stack {
  flex-direction: column;
  align-items: flex-end;
  gap: 0;
  line-height: 1;
  padding: 0 8px;
}

.chip-pct {
  font-size: 13px;
  font-weight: 600;
}

.chip-amt {
  font-size: 10.5px;
  font-weight: 500;
  opacity: 0.78;
}

.earnings {
  font-family: "IBM Plex Mono", monospace;
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  /* Caps how wide this column can force itself under the table's
     table-layout:auto -- without this, a long "note" (e.g. an
     estimated-earnings caveat) had no limit, growing this column at
     the News column's expense. Truncation happens inside .est alone
     (not here) so the ellipsis renders in .est's own Sans font instead
     of picking up this element's Mono font -- the badge and date never
     truncate, only the note does. Full text is still available via the
     title attribute (see buildRow in script.js). */
  display: flex;
  align-items: baseline;
  max-width: 230px;
}

.earnings .tag {
  flex-shrink: 0;
}

.earnings-date {
  flex-shrink: 0;
  white-space: nowrap;
}

.earnings .est {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 11px;
  color: var(--ink-muted);
  margin-left: 6px;
  /* The only part of .earnings allowed to shrink/truncate -- see the
     min-width:0 note above; a flex item's default min-width:auto would
     otherwise refuse to shrink below its content's width even with
     overflow/ellipsis set. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.earnings .est::before {
  content: "· ";
}

.news {
  width: 30%;
  vertical-align: top;
  padding-top: 6px;
  padding-bottom: 6px;
}

/* The one-line "needs research" placeholder has no date/link to align
   with, so unlike real news content it's centered like the earnings
   column's own needs-research cell (a plain td, middle by default). */
.news.news-empty {
  vertical-align: middle;
}

.news-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: normal;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink-muted);
  min-height: calc(12.5px * 1.4 * 2);
}

.spinner {
  display: inline-block;
  width: 11px;
  height: 11px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  vertical-align: middle;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}

.pending-label {
  margin-left: 6px;
  font-size: 11px;
  font-style: italic;
  color: var(--ink-muted);
  opacity: 0.75;
  vertical-align: middle;
}

/* Shown instead of the earnings/news cell content for a stock that has
   never been researched (e.g. just added) -- there's no automated source
   for either field, so this is a permanent note, not a loading state. */
.needs-research {
  font-size: 12px;
  font-style: italic;
  color: var(--ink-muted);
  opacity: 0.75;
}

/* .news-clamp reserves a fixed 2-line-tall box (min-height) for real
   clamped news text -- this one-line note would otherwise sit at the top
   of that box instead of centered in it. */
.news-clamp.needs-research {
  display: flex;
  align-items: center;
}

.news-date {
  font-family: "IBM Plex Mono", monospace;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  opacity: 0.75;
}

.news-date::after {
  content: "  \00b7  ";
  opacity: 0.6;
}

.news a {
  /* Full-contrast text (reads as white in dark mode) rather than the
     muted color it inherited from .news-clamp -- .news-date keeps that
     muted color, unaffected, since it has its own explicit `color` rule. */
  color: var(--ink);
  text-decoration: none;
}

.news a:hover,
.news a:focus-visible {
  text-decoration: underline;
}

.tag {
  display: inline-block;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  margin-right: 5px;
  /* Same baseline-synthesis issue as .chip -- an inline-block with no
     vertical-align hangs a little low next to the rest of the earnings
     cell's plain text. */
  vertical-align: middle;
}

.tag.confirmed { color: var(--pos); }
.tag.estimated { color: var(--ink-muted); }

.loading-row td,
.error-row td {
  white-space: normal;
  padding: 28px 18px;
  text-align: center;
  font-family: "IBM Plex Mono", monospace;
  font-size: 13px;
  color: var(--ink-muted);
}

.error-row td {
  color: var(--neg);
}

a { color: var(--accent); }

.watch-th {
  width: 1%;
  text-align: center;
}

.watch-cell {
  text-align: center;
  white-space: nowrap;
}

/* Outline-only icon (its <svg> is fill:none/stroke:currentColor -- see
   watchCell() in script.js) -- color here is what "currentColor"
   resolves to, muted by default so it doesn't compete with the row's
   actual data, shifting to the accent color on hover/focus same as
   this app's other icon buttons (.edit-btn/.remove-btn). */
.watch-bulb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  border-radius: 6px;
  transition: color 0.12s ease, background 0.12s ease;
}

.watch-bulb:hover,
.watch-bulb:focus-visible {
  color: var(--accent);
  background: var(--row-hover);
  outline: none;
}

/* Appended directly to <body> (see getWatchTooltipEl() in script.js), not
   nested in the table -- position: fixed and a high z-index so it always
   floats above the table (and the .table-scroll container's own implicit
   overflow-y: auto, which would otherwise clip it) regardless of scroll. */
.watch-tooltip {
  position: fixed;
  z-index: 2000;
  width: 300px;
  max-width: calc(100vw - 24px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
  padding: 12px 14px;
  font-family: "IBM Plex Sans", sans-serif;
  pointer-events: none;
}

.watch-tooltip[hidden] {
  display: none;
}

.watch-tooltip-title {
  font-family: "IBM Plex Mono", monospace;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 8px;
}

.watch-tooltip ol {
  margin: 0;
  padding-left: 18px;
}

.watch-tooltip li {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink);
  margin-bottom: 6px;
  white-space: normal;
}

.watch-tooltip li:last-child {
  margin-bottom: 0;
}

.deepdive-th {
  width: 1%;
  text-align: center;
}

.deepdive-cell {
  text-align: center;
  white-space: nowrap;
}

.deepdive-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: "IBM Plex Mono", monospace;
  font-size: 11.5px;
  font-weight: 500;
  padding: 5px 10px;
  border-radius: 6px;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.deepdive-btn:hover,
.deepdive-btn:focus-visible {
  background: var(--accent);
  color: var(--accent-ink);
  outline: none;
}

/* No report file dropped in claude/deepdive/<TICKER>.html yet -- shown
   pale and inert rather than styled like a real, clickable action. */
.deepdive-btn.deepdive-unavailable {
  font-family: "IBM Plex Sans", sans-serif;
  font-style: italic;
  border-color: var(--border);
  color: var(--ink-muted);
  opacity: 0.55;
  cursor: default;
}

.deepdive-btn.deepdive-unavailable:hover {
  background: transparent;
  color: var(--ink-muted);
}

.actions-th {
  width: 1%;
  text-align: center;
}

.actions-cell {
  text-align: center;
  white-space: nowrap;
}

.owned-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 12px;
  color: var(--ink-muted);
  cursor: pointer;
}

.owned-toggle input {
  accent-color: var(--accent);
  cursor: pointer;
}

.edit-btn,
.remove-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--ink-muted);
  /* Same baseline-synthesis issue as .chip/.tag. */
  vertical-align: middle;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}

.edit-btn {
  font-size: 12px;
}

.edit-btn:hover,
.edit-btn:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--row-hover);
  outline: none;
}

.remove-btn:hover,
.remove-btn:focus-visible {
  color: var(--neg);
  border-color: var(--neg);
  background: var(--neg-bg);
  outline: none;
}

.add-stock {
  margin-top: 14px;
}

.add-stock-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.add-stock-form input[type="text"],
.add-stock-form select {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 10px;
}

.add-stock-form input[type="text"]:focus-visible,
.add-stock-form select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.add-stock-form select#add-exchange {
  width: 230px;
}

.add-stock-form input#add-ticker {
  width: 100px;
  font-family: "IBM Plex Mono", monospace;
  text-transform: uppercase;
}

.add-stock-form input#add-exchange-code {
  width: 90px;
  font-family: "IBM Plex Mono", monospace;
  text-transform: uppercase;
}

.add-stock-form input#add-exchange-country {
  width: 60px;
  font-family: "IBM Plex Mono", monospace;
  text-transform: uppercase;
}

.add-stock-form input#add-exchange-name {
  width: 200px;
}

.add-owned {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  color: var(--ink-muted);
  white-space: nowrap;
}

.add-owned input {
  accent-color: var(--accent);
}

.add-stock-form button {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent-ink);
  background: var(--accent);
  border: none;
  border-radius: 6px;
  padding: 8px 14px;
  cursor: pointer;
}

.add-stock-form button:hover {
  opacity: 0.9;
}

.add-stock-status {
  font-size: 12.5px;
}

.add-stock-status.error { color: var(--neg); }
.add-stock-status.success { color: var(--pos); }

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 15, 12, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px 24px;
  z-index: 1000;
}

.modal-overlay[hidden] {
  display: none;
}

.modal-box {
  width: min(92vw, 1040px);
  height: calc(100vh - 24px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.35);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

.modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--ink-muted);
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}

.modal-close:hover,
.modal-close:focus-visible {
  color: var(--neg);
  border-color: var(--neg);
  background: var(--neg-bg);
  outline: none;
}

.modal-iframe {
  flex: 1;
  width: 100%;
  border: none;
  background: #fff;
}

@media (max-width: 520px) {
  header { flex-direction: column; align-items: flex-start; }
  .status-bar { text-align: left; }
}
