read.markets/app/static/css/cassandra.css
Giorgio Gilestro 477a47be2c dashboard: drop "held locally · prices via /api/universe" meta line
Subtitle was technical noise that didn't earn its space in the header.
Title alone reads cleaner. Kept the scoped panel-header layout override
in cassandra.css since it's harmless and future-proof against re-adding
header children.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 15:19:52 +02:00

2516 lines
66 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Cassandra — geopolitical-terminal aesthetic with two themes.
* Mono for data, headers, terminal feel; sans for prose surfaces (log + chat). */
:root {
/* Light theme (default) */
--bg: #f5f3ec; /* warm off-white, easier on the eyes than pure white */
--surface: #ffffff;
--surface-2: #efece3;
--border: #d6d3cb;
--text: #1c1f25;
--muted: #545b69;
--dim: #8a8f9a;
--accent: #0e7490; /* deep teal — still terminal-feel on light */
--positive: #166534;
--negative: #b91c1c;
--alert: #c2410c;
--warning: #a16207;
--user-bubble-bg: rgba(14, 116, 144, 0.07);
}
[data-theme="dark"] {
--bg: #0a0e14;
--surface: #11151c;
--surface-2: #161b25;
--border: #2a3142;
--text: #d4dae8; /* lifted from #c0caf5 for readability */
--muted: #8189a1; /* lifted from #565f89 — was unreadably dim */
--dim: #565f89;
--accent: #00d9ff;
--positive: #50fa7b;
--negative: #ff5b5b;
--alert: #ff8a4a;
--warning: #f1fa8c;
--user-bubble-bg: rgba(0, 217, 255, 0.08);
}
/* Font stacks. Mono for terminal feel; sans for reading. */
:root {
--font-mono: 'JetBrains Mono', 'IBM Plex Mono', 'Fira Code', ui-monospace, monospace;
--font-sans: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', Roboto,
'Helvetica Neue', system-ui, sans-serif;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.5;
font-variant-numeric: tabular-nums;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* --- Layout ---------------------------------------------------------- */
.app {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border);
padding: 10px 18px;
background: var(--surface);
letter-spacing: 0.08em;
text-transform: uppercase;
position: sticky;
top: 0;
z-index: 50;
}
.app-header .brand {
color: var(--accent);
font-weight: 700;
text-decoration: none;
}
.app-header .brand:hover { color: var(--text); }
.app-header .brand::before { content: "▰ "; opacity: 0.6; }
.app-header nav a {
margin-left: 18px;
color: var(--muted);
}
.app-header nav a.active { color: var(--text); }
.app-header .meta { color: var(--muted); font-size: 11px; }
.app-header .header-right { display: flex; align-items: center; gap: 14px; }
.theme-toggle {
background: transparent;
border: 1px solid var(--border);
color: var(--muted);
padding: 3px 8px;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.08em;
cursor: pointer;
text-transform: lowercase;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }
.theme-toggle__label::before { content: "◐ light"; }
[data-theme="dark"] .theme-toggle__label::before { content: "◐ dark"; }
/* Tone toggle (segmented control: Novice | Intermediate) */
.tone-toggle {
display: inline-flex;
border: 1px solid var(--border);
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.tone-toggle button {
background: transparent;
color: var(--muted);
border: 0;
padding: 4px 10px;
cursor: pointer;
font: inherit;
letter-spacing: inherit;
text-transform: inherit;
}
.tone-toggle button + button { border-left: 1px solid var(--border); }
.tone-toggle button:hover { color: var(--accent); }
.tone-toggle[data-tone="NOVICE"] button[data-value="NOVICE"],
.tone-toggle[data-tone="INTERMEDIATE"] button[data-value="INTERMEDIATE"] {
background: var(--accent);
color: var(--bg);
}
.app-main {
padding: 14px;
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
grid-template-rows: auto auto auto auto;
grid-template-areas:
"header header"
"indicators log"
"portfolio log"
"news news";
gap: 14px;
}
@media (max-width: 1100px) {
.app-main {
grid-template-columns: 1fr;
grid-template-areas: "header" "indicators" "portfolio" "log" "news";
}
}
#dash-header-container { grid-area: header; }
#indicators-panel { grid-area: indicators; }
#portfolio-panel { grid-area: portfolio; }
#log-panel {
grid-area: log;
/* Don't stretch to fill both grid rows; if the log is shorter than
the portfolio next to it, the surplus below would render as a big
empty white box. Aligning to the start makes the panel shrink to
its content and the dashboard background fills any gap. */
align-self: start;
}
#news-panel { grid-area: news; }
/* Legacy footer rules — kept for the /api/health page which still uses
the old class via the standalone HTML template. */
.app-footer {
border-top: 1px solid var(--border);
padding: 8px 18px;
background: var(--surface);
font-size: 11px;
color: var(--muted);
display: flex;
gap: 16px;
flex-wrap: wrap;
}
/* Sticky bottom markets bar — uses the same .mkt chip styling as the
old dashboard header, extended with each market's headline index. */
.markets-bar {
position: sticky;
bottom: 0;
z-index: 50;
background: var(--surface);
border-top: 1px solid var(--border);
}
.markets-bar__inner {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1px;
background: var(--border);
border: 0;
}
.markets-bar .mkt {
border: 0;
border-radius: 0;
}
/* --- Panels ----------------------------------------------------------- */
.panel {
background: var(--surface);
border: 1px solid var(--border);
position: relative;
}
.panel-header {
border-bottom: 1px solid var(--border);
padding: 8px 12px;
display: flex;
align-items: center;
justify-content: space-between;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
font-size: 11px;
background: linear-gradient(180deg, var(--surface-2), var(--surface));
}
.panel-header .title { color: var(--text); font-weight: 700; }
.panel-header .title::before { content: "■ "; color: var(--accent); }
.panel-header .meta { color: var(--dim); }
.panel-body { padding: 6px 0; }
.panel-body--scroll { max-height: 70vh; overflow-y: auto; }
/* --- Tables ----------------------------------------------------------- */
table.dense {
width: 100%;
border-collapse: collapse;
}
table.dense th, table.dense td {
padding: 4px 12px;
font-size: 12px;
border-bottom: 1px solid var(--surface-2);
white-space: nowrap;
}
table.dense th {
text-align: left;
color: var(--muted);
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 10px;
background: var(--surface-2);
}
table.dense th.num,
table.dense td.num { text-align: right; }
table.dense td.label { color: var(--text); }
table.dense td.label.has-tip,
table.dense td[title] {
cursor: help;
border-bottom: 1px dotted color-mix(in srgb, var(--accent) 40%, transparent);
border-bottom-width: 1px;
}
.pf-name.has-tip {
cursor: help;
border-bottom: 1px dotted color-mix(in srgb, var(--accent) 50%, transparent);
}
table.dense tr:hover td { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.pos { color: var(--positive); }
.neg { color: var(--negative); }
.neu { color: var(--muted); }
.note { color: var(--dim); font-size: 11px; }
/* Stale indicator rows — last observation > 90 days old */
table.dense tr.row-stale td { color: var(--dim); }
.stale-tag {
display: inline-block;
font-size: 8.5px;
letter-spacing: 0.08em;
color: var(--alert);
border: 1px solid var(--alert);
padding: 0 4px;
margin-left: 4px;
vertical-align: middle;
text-transform: uppercase;
cursor: help;
}
/* --- Status LEDs ------------------------------------------------------ */
.led { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 4px; vertical-align: middle; }
.led.ok { background: var(--positive); box-shadow: 0 0 6px var(--positive); }
.led.warn { background: var(--warning); box-shadow: 0 0 6px var(--warning); }
.led.err { background: var(--negative); box-shadow: 0 0 6px var(--negative); }
.led.idle { background: var(--dim); }
/* --- Dashboard top header (markets + aggregate read) ----------------- */
.dash-header {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
margin-bottom: 0;
}
.dash-header__markets {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
}
.mkt {
background: var(--surface);
padding: 6px 10px;
font-family: var(--font-mono);
font-size: 11px;
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-rows: auto auto;
align-items: center;
gap: 2px 6px;
}
.mkt__dot {
width: 8px; height: 8px; border-radius: 50%;
grid-row: 1 / span 2; grid-column: 1;
align-self: center;
}
.mkt--open .mkt__dot { background: var(--positive); box-shadow: 0 0 6px var(--positive); }
.mkt--closed .mkt__dot { background: var(--dim); }
.mkt__name {
grid-row: 1; grid-column: 2;
color: var(--text); font-weight: 700;
text-transform: uppercase; letter-spacing: 0.08em;
}
.mkt__state {
grid-row: 1; grid-column: 3;
font-size: 9.5px; letter-spacing: 0.08em;
text-transform: lowercase;
}
.mkt--open .mkt__state { color: var(--positive); }
.mkt--closed .mkt__state { color: var(--dim); }
.mkt__index {
grid-row: 2; grid-column: 2;
font-size: 10.5px;
font-variant-numeric: tabular-nums;
display: inline-flex;
align-items: baseline;
gap: 5px;
white-space: nowrap;
}
.mkt__index-label { color: var(--dim); }
.mkt__index-price { color: var(--text); }
.mkt__index-change.pos { color: var(--positive); }
.mkt__index-change.neg { color: var(--negative); }
.mkt__index-change.neu { color: var(--muted); }
.mkt__index--empty { color: var(--dim); font-size: 10px; }
.mkt__when {
grid-row: 2; grid-column: 3;
color: var(--muted); font-size: 10px;
font-variant-numeric: tabular-nums;
text-align: right;
}
.mkt__when-label { color: var(--dim); }
.dash-header__read {
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
background: color-mix(in srgb, var(--accent) 4%, transparent);
padding: 10px 14px;
}
.dash-header__read-meta {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 4px;
}
.dash-header__read-body {
margin: 0;
font-family: var(--font-sans);
font-size: 14px;
line-height: 1.55;
color: var(--text);
}
.dash-header__read--pending { color: var(--dim); font-style: italic; }
.dash-header__read--pending .dash-header__read-body { color: var(--dim); font-size: 12px; }
/* --- Indicator group summary (above the table) ----------------------- */
.ind-summary {
font-family: var(--font-sans);
padding: 10px 16px;
border-bottom: 1px solid var(--surface-2);
border-left: 3px solid var(--accent);
background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.ind-summary__head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 4px;
}
.ind-summary__label {
font-family: var(--font-mono);
font-size: 10px;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 700;
}
.ind-summary__label::before { content: "▸ "; }
.ind-summary__when {
font-family: var(--font-mono);
font-size: 10px;
color: var(--dim);
font-variant-numeric: tabular-nums;
}
.ind-summary__body {
margin: 0;
font-size: 13.5px;
line-height: 1.55;
color: var(--text);
}
.ind-summary--pending { color: var(--dim); font-style: italic; }
.ind-summary--pending .ind-summary__body { color: var(--dim); font-size: 12px; }
/* --- Glossary tooltips (Novice mode) --------------------------------- */
/* The term gets a dotted underline. The actual tooltip is a single shared
element (#glossary-tooltip) positioned by JS so it can flip on viewport
edges and never clip behind sticky bars (which sit at z-index 50). */
.glossary {
border-bottom: 1px dotted var(--accent);
cursor: help;
/* Same colour as surrounding text — only the underline signals "tooltip
available", keeping the paragraph visually quiet. */
}
.glossary:focus { outline: 1px dotted var(--accent); outline-offset: 2px; }
#glossary-tooltip {
position: fixed;
z-index: 200; /* Above sticky bars (z-index 50). */
max-width: 300px;
padding: 9px 12px;
background: var(--surface);
color: var(--text);
border: 1px solid var(--accent);
font-family: var(--font-sans);
font-size: 12.5px;
line-height: 1.5;
letter-spacing: 0;
text-transform: none;
font-weight: normal;
box-shadow: 0 6px 18px rgba(0,0,0,0.35);
pointer-events: none;
opacity: 0;
transition: opacity 90ms ease;
}
#glossary-tooltip[data-visible="1"] { opacity: 1; }
#glossary-tooltip[hidden] { display: none; }
/* --- Group tabs ------------------------------------------------------- */
.group-tabs {
display: flex;
border-bottom: 1px solid var(--border);
overflow-x: auto;
}
.group-tabs button {
background: transparent;
border: 0;
border-right: 1px solid var(--border);
color: var(--muted);
font-family: inherit;
font-size: 11px;
padding: 6px 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
cursor: pointer;
}
.group-tabs button:hover { color: var(--text); }
.group-tabs button.active {
color: var(--accent);
background: var(--bg);
box-shadow: inset 0 -2px 0 var(--accent);
}
/* --- Portfolio overall ----------------------------------------------- */
.pf-overall {
border-bottom: 1px solid var(--border);
padding: 10px 14px 12px;
background: linear-gradient(180deg, var(--surface-2), var(--surface));
}
.pf-overall__head {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 8px;
}
.pf-name {
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 700;
font-size: 11px;
}
.pf-name::before { content: "◆ "; opacity: 0.6; }
.pf-as-of { color: var(--dim); font-size: 11px; }
.pf-overall__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 6px 24px;
}
@media (max-width: 640px) {
.pf-overall__grid { grid-template-columns: repeat(2, 1fr); }
}
.pf-stat-label {
font-size: 10px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.pf-stat-value {
font-size: 16px;
color: var(--text);
font-variant-numeric: tabular-nums;
margin-top: 2px;
}
.pf-stat-value.pos { color: var(--positive); }
.pf-stat-value.neg { color: var(--negative); }
.pf-stat-value.neu { color: var(--muted); }
.pf-ccy { color: var(--dim); font-size: 11px; margin-left: 2px; }
.pf-pct { color: var(--dim); font-size: 11px; margin-left: 4px; }
.pf-pills { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.pf-pill {
font-size: 10.5px;
font-family: var(--font-mono);
color: var(--muted);
background: var(--surface-2);
border: 1px solid var(--border);
padding: 2px 6px;
letter-spacing: 0.04em;
}
.pf-warn {
border-left: 3px solid var(--alert);
background: color-mix(in srgb, var(--alert) 6%, transparent);
color: var(--alert);
padding: 8px 10px;
font-size: 12px;
margin: 10px 0;
}
.pf-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.pf-actions button {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.06em;
text-transform: uppercase;
background: var(--surface-2);
color: var(--accent);
border: 1px solid var(--border);
padding: 7px 14px;
cursor: pointer;
}
.pf-actions button:hover { border-color: var(--accent); }
.pf-actions button:disabled { opacity: 0.5; cursor: not-allowed; }
.pf-actions .pf-secondary { color: var(--muted); }
.pf-actions .pf-secondary:hover { color: var(--negative); border-color: var(--negative); }
/* Settings-page action button — same visual language as .pf-actions
button so buttons across /settings (Manage subscription, future
actions) read as one family. Standalone class (not nested under a
parent) so it can be dropped onto any button anywhere on the page. */
.settings-btn {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.06em;
text-transform: uppercase;
background: var(--surface-2);
color: var(--accent);
border: 1px solid var(--border);
padding: 7px 14px;
cursor: pointer;
border-radius: 2px;
text-decoration: none;
display: inline-block;
}
.settings-btn:hover { border-color: var(--accent); }
.settings-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Icon-button variant for inline row actions (e.g. Manage subscription
gear in the Tier row). Square hit area, accent on hover, tooltip via
title attribute. */
.settings-icon-btn {
background: transparent;
border: 1px solid transparent;
color: var(--muted);
width: 32px;
height: 32px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 3px;
flex-shrink: 0;
transition: color 80ms linear, border-color 80ms linear, background 80ms linear;
}
.settings-icon-btn:hover {
color: var(--accent);
border-color: var(--border);
background: var(--surface-2);
}
.settings-icon-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.settings-icon-btn svg { display: block; }
.pf-analysis {
margin-top: 14px;
background: var(--surface-2);
border: 1px solid var(--border);
}
.pf-analysis__details { padding: 0; }
.pf-analysis__head {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 11px;
color: var(--muted);
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 10px 16px;
cursor: pointer;
user-select: none;
list-style: none; /* hide native marker in Firefox */
}
.pf-analysis__head::-webkit-details-marker { display: none; }
.pf-analysis__head-left::before {
content: "▸ ";
display: inline-block;
width: 1em;
color: var(--accent);
transition: transform 120ms ease;
}
details[open] .pf-analysis__head-left::before { content: "▾ "; }
.pf-analysis__head:hover { color: var(--accent); }
.pf-analysis__head:hover .pf-analysis__head-left::before { color: var(--accent); }
.pf-analysis__details[open] .pf-analysis__head {
border-bottom: 1px solid var(--border);
}
.pf-analysis__body {
font-family: var(--font-sans);
font-size: 14px;
line-height: 1.65;
color: var(--text);
white-space: pre-wrap;
margin: 0;
padding: 14px 16px 16px;
}
/* --- Log panel -------------------------------------------------------- */
.log-content {
font-family: var(--font-sans);
padding: 28px clamp(20px, 4vw, 56px) 32px;
font-size: 15.5px;
line-height: 1.72;
color: var(--text);
max-width: 76ch;
margin: 0 auto;
max-height: calc(100vh - 240px);
overflow-y: auto;
}
.log-content p { margin: 0 0 1.1em; }
.log-content h1, .log-content h2, .log-content h3, .log-content h4 {
font-family: var(--font-mono);
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 12px;
margin-top: 1.8em;
margin-bottom: 0.5em;
font-weight: 700;
}
.log-content h1:first-child,
.log-content h2:first-child,
.log-content h3:first-child { margin-top: 0; }
/* TL;DR callout — model is instructed to put it first, so style the first
* heading + paragraph block as a callout. */
.log-content h3:first-of-type {
font-size: 11px;
color: var(--accent);
border-left: 3px solid var(--accent);
padding-left: 10px;
margin-bottom: 0;
}
.log-content h3:first-of-type + p {
font-size: 16.5px;
line-height: 1.6;
color: var(--text);
border-left: 3px solid var(--accent);
padding: 4px 14px 12px;
margin: 0 0 1.8em;
background: color-mix(in srgb, var(--accent) 5%, transparent);
font-weight: 500;
}
.log-content strong { color: var(--text); font-weight: 700; }
.log-content em { color: var(--muted); font-style: italic; }
.log-content ul, .log-content ol { padding-left: 1.4em; margin: 0 0 1.1em; }
.log-content li { margin-bottom: 0.4em; }
.log-content hr {
border: 0;
border-top: 1px solid var(--border);
margin: 1.6em 0;
}
/* --- Log page (calendar + log + chat sidebar) ------------------------- */
.log-page__body {
display: grid;
grid-template-columns: 220px 1fr 320px;
gap: 1px;
background: var(--border);
}
@media (max-width: 1100px) {
.log-page__body { grid-template-columns: 1fr; }
}
.log-page__cal, .log-page__content, .log-page__chat { background: var(--surface); }
.log-page__cal { padding: 10px; }
.log-page__content { min-height: 60vh; }
.log-page__chat { padding: 8px; min-height: 60vh; display: flex; flex-direction: column; }
.log-page__chat--locked { opacity: 0.92; }
.chat-locked {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
gap: 16px;
padding: 24px 18px;
color: var(--muted);
font-size: 13px;
line-height: 1.55;
border: 1px dashed var(--border);
border-radius: 4px;
margin: 8px 4px;
}
.chat-locked p { margin: 0; max-width: 280px; }
.chat-locked strong { color: var(--text); display: block; margin-bottom: 6px; }
/* --- Calendar widget --------------------------------------------------- */
.cal__nav {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.cal__title { color: var(--accent); font-weight: 700; }
.cal__btn {
background: transparent;
color: var(--muted);
border: 1px solid var(--border);
padding: 2px 8px;
cursor: pointer;
font-family: inherit;
font-size: 13px;
}
.cal__btn:hover { color: var(--accent); border-color: var(--accent); }
.cal__grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
}
.cal__h {
text-align: center;
font-size: 9px;
color: var(--dim);
background: var(--surface-2);
padding: 3px 0;
text-transform: uppercase;
}
.cal__d {
background: var(--surface);
border: 0;
color: var(--muted);
font-family: inherit;
font-size: 11px;
padding: 6px 0;
text-align: center;
cursor: not-allowed;
}
.cal__d--empty { background: var(--bg); cursor: default; }
.cal__d--has-log {
color: var(--text);
cursor: pointer;
position: relative;
}
.cal__d--has-log::after {
content: "";
position: absolute;
bottom: 3px;
left: 50%;
transform: translateX(-50%);
width: 3px; height: 3px;
border-radius: 50%;
background: var(--accent);
}
.cal__d--has-log:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.cal__d--today { color: var(--warning); }
.cal__d--selected {
background: var(--accent);
color: var(--bg);
font-weight: 700;
}
.cal__d--selected::after { background: var(--bg); }
/* --- Badges (tone / analysis indicators) ------------------------------ */
.badge {
display: inline-block;
font-family: var(--font-mono);
font-size: 9.5px;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 1px 6px;
border: 1px solid currentColor;
margin-right: 4px;
background: transparent;
vertical-align: middle;
}
/* Tone axis — green→accent→amber as audience density rises */
.badge--tone-novice { color: var(--positive); }
.badge--tone-intermediate { color: var(--accent); }
.badge--tone-pro { color: var(--alert); }
/* Analysis axis — dry is muted, speculative is accent */
.badge--analysis-dry { color: var(--muted); }
.badge--analysis-speculative { color: var(--accent); }
.badge--ver { color: var(--dim); }
.badge--ok { color: var(--positive); border-color: var(--positive); }
.meta__hint { color: var(--dim); font-size: 10px; margin-right: 4px; }
/* --- Log metadata footer ---------------------------------------------- */
.log-meta {
padding: 4px clamp(20px, 4vw, 56px) 6px;
max-width: 76ch;
margin: 0 auto;
border-top: 1px dashed var(--border);
color: var(--dim);
font-size: 10.5px;
font-family: var(--font-mono);
letter-spacing: 0.04em;
}
/* --- Auth pages (login / signup, standalone — no app chrome) -------- */
.auth-shell {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg);
padding: 20px;
}
.auth-card {
width: 360px;
max-width: 100%;
background: var(--surface);
border: 1px solid var(--border);
padding: 28px 26px;
}
.auth-card__brand {
font-family: var(--font-mono);
color: var(--accent);
font-size: 18px;
letter-spacing: 0.12em;
text-transform: uppercase;
font-weight: 700;
}
.auth-card__brand::before { content: "▰ "; opacity: 0.6; }
.auth-card__hint {
font-family: var(--font-mono);
color: var(--muted);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.08em;
margin: 2px 0 18px;
}
.auth-card form { display: flex; flex-direction: column; gap: 12px; }
.auth-card label {
display: flex;
flex-direction: column;
font-family: var(--font-mono);
color: var(--muted);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
gap: 4px;
}
.auth-card input[type="email"],
.auth-card input[type="password"],
.auth-card input[type="text"] {
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
font-family: var(--font-mono);
font-size: 16px;
padding: 12px 14px;
outline: none;
border-radius: 3px;
}
/* The 6-digit OTP input wants to be visually loud — it's the only
thing the user is doing on that page. Bigger, more spacing, taller. */
.auth-card input[name="code"] {
font-size: 24px;
padding: 16px 14px;
letter-spacing: 0.5em;
text-align: center;
}
.auth-card input:focus { border-color: var(--accent); }
/* --- Modal text inputs (cloud-sync PIN modal, etc.) ---------------- */
/* Same visual treatment as auth-card so prompts read as a coherent
family. Replaces the inline `style="padding:8px"` that left these
inputs feeling cramped. */
.modal-input {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
font-family: var(--font-mono);
font-size: 16px;
padding: 12px 14px;
margin-bottom: 12px;
outline: none;
border-radius: 3px;
box-sizing: border-box;
}
.modal-input:focus { border-color: var(--accent); }
.auth-card button {
margin-top: 8px;
background: transparent;
border: 1px solid var(--accent);
color: var(--accent);
font-family: var(--font-mono);
font-size: 11px;
padding: 9px 12px;
text-transform: uppercase;
letter-spacing: 0.1em;
cursor: pointer;
}
.auth-card button:hover { background: var(--accent); color: var(--bg); }
.auth-card__alt {
margin-top: 18px;
font-size: 12px;
color: var(--muted);
text-align: center;
}
.auth-error {
border-left: 3px solid var(--negative);
background: color-mix(in srgb, var(--negative) 6%, transparent);
color: var(--negative);
padding: 8px 10px;
font-size: 12px;
margin-bottom: 14px;
font-family: var(--font-mono);
}
.auth-info {
border-left: 3px solid var(--accent);
background: color-mix(in srgb, var(--accent) 6%, transparent);
color: var(--accent);
padding: 8px 10px;
font-size: 12px;
margin-bottom: 14px;
font-family: var(--font-mono);
}
.auth-info--invited {
/* Slightly warmer / friendlier shading for the referral banner. */
border-left-color: var(--positive);
background: color-mix(in srgb, var(--positive) 7%, transparent);
color: var(--text);
font-family: var(--font-sans);
font-size: 13px;
line-height: 1.5;
}
.auth-info--invited strong { color: var(--positive); font-weight: 600; }
/* --- Settings page --------------------------------------------------- */
.settings-row {
display: flex;
align-items: baseline;
gap: 14px;
padding: 8px 0;
border-bottom: 1px solid var(--surface-2);
font-size: 13px;
}
.settings-row__label {
width: 110px;
flex-shrink: 0;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 10.5px;
font-family: var(--font-mono);
}
.settings-row__value { color: var(--text); }
.settings-row__hint {
color: var(--dim);
font-size: 11px;
margin-left: 8px;
}
/* Sections are <details> elements — collapsed by default to keep the
settings page scannable. Click the summary to expand. */
.settings-section {
margin-top: 14px;
border-top: 1px solid var(--surface-2);
padding-top: 14px;
}
.settings-section__head {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 6px;
cursor: pointer;
list-style: none;
user-select: none;
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
/* Suppress the native disclosure marker (Webkit + Firefox). */
.settings-section__head::-webkit-details-marker { display: none; }
.settings-section__head::marker { content: ""; }
.settings-section__head::before {
content: "▸";
color: var(--accent);
display: inline-block;
transition: transform 120ms ease-out;
font-size: 10px;
}
.settings-section[open] > .settings-section__head::before {
transform: rotate(90deg);
}
.settings-section[open] > .settings-section__head { margin-bottom: 10px; }
.settings-section__head:hover { color: var(--text); }
.settings-section__head:hover::before { color: var(--text); }
.settings-section__lede {
color: var(--muted);
font-size: 12.5px;
line-height: 1.55;
margin: 0 0 14px;
}
.settings-section__lede strong { color: var(--positive); font-weight: 600; }
.invite-block {
background: var(--surface-2);
border: 1px solid var(--border);
padding: 14px 16px;
}
.invite-block__label {
display: block;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 4px;
}
.invite-block__label:not(:first-child) { margin-top: 12px; }
.invite-block__code {
font-family: var(--font-mono);
font-size: 22px;
letter-spacing: 0.32em;
color: var(--accent);
background: var(--surface);
padding: 10px 14px;
border: 1px solid var(--accent);
text-align: center;
user-select: all;
}
.invite-block__link {
display: flex;
gap: 6px;
}
.invite-block__link input {
flex: 1;
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
padding: 7px 10px;
font-family: var(--font-mono);
font-size: 12px;
}
.invite-block__link button {
background: var(--accent);
color: var(--bg);
border: 0;
padding: 0 14px;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.06em;
text-transform: uppercase;
cursor: pointer;
}
.invite-block__link button:hover { opacity: 0.85; }
.invite-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
margin-top: 16px;
}
.invite-stats > div {
background: var(--surface);
padding: 10px 14px;
}
.invite-stats__label {
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.invite-stats__value {
font-family: var(--font-mono);
font-size: 18px;
color: var(--text);
font-variant-numeric: tabular-nums;
margin-top: 4px;
}
.auth-card__lede {
font-size: 12.5px;
color: var(--muted);
margin: 0 0 16px;
line-height: 1.5;
}
.auth-card__lede strong { color: var(--text); font-weight: normal; }
.auth-card__resend {
background: transparent !important;
color: var(--muted) !important;
border: 1px dashed var(--border) !important;
font-size: 11px !important;
}
.auth-card__resend:hover {
color: var(--accent) !important;
border-color: var(--accent) !important;
}
/* Import preview action row — two stacked buttons with an explainer. */
.import-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 14px;
}
.import-choice { flex: 1 1 240px; min-width: 220px; }
.import-choice button { width: 100%; }
.import-choice .settings-row__hint {
display: block;
margin-top: 6px;
line-height: 1.5;
}
/* User chip in header — now a button that toggles a dropdown menu. */
.user-menu { position: relative; margin-left: 8px; }
.user-chip {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--muted);
letter-spacing: 0.04em;
background: none;
border: 0;
padding: 0;
cursor: pointer;
}
.user-chip:hover { color: var(--accent); }
.user-menu__caret { margin-left: 4px; opacity: 0.6; }
.user-menu__panel {
position: absolute;
top: calc(100% + 6px);
right: 0;
min-width: 160px;
background: var(--surface-1, var(--surface-2));
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
z-index: 200;
padding: 4px 0;
}
.user-menu__item {
display: block;
padding: 8px 14px;
color: var(--text);
text-decoration: none;
font-size: 12px;
}
.user-menu__item:hover { background: var(--surface-2); color: var(--accent); }
/* --- Upload page (drag-drop CSV) ------------------------------------- */
.dz {
border: 2px dashed var(--border);
background: var(--surface-2);
padding: 36px 20px;
text-align: center;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.dz:hover, .dz--over {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 6%, var(--surface-2));
}
.dz__icon {
font-family: var(--font-mono);
font-size: 28px;
color: var(--accent);
letter-spacing: -2px;
margin-bottom: 6px;
}
.dz__label {
font-family: var(--font-mono);
font-size: 13px;
color: var(--text);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.dz__hint { color: var(--muted); font-size: 11.5px; margin-top: 4px; }
.dz__hint a { color: var(--accent); }
.dz__filename { margin-top: 10px; color: var(--accent); font-size: 12px; font-family: var(--font-mono); min-height: 1em; }
.form-row { display: grid; grid-template-columns: 180px 1fr; align-items: center; gap: 12px; padding: 6px 0; }
.form-row label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; }
.form-row input[type="text"], .form-row select {
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
font-family: var(--font-mono);
font-size: 12px;
padding: 6px 8px;
outline: none;
}
.form-row input[type="text"]:focus, .form-row select:focus { border-color: var(--accent); }
#submit-btn {
margin-top: 14px;
background: transparent;
border: 1px solid var(--accent);
color: var(--accent);
font-family: var(--font-mono);
font-size: 11px;
padding: 8px 18px;
text-transform: uppercase;
letter-spacing: 0.1em;
cursor: pointer;
}
#submit-btn:hover:not(:disabled) { background: var(--accent); color: var(--bg); }
#submit-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.result {
margin-top: 20px;
padding: 14px;
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
background: color-mix(in srgb, var(--accent) 4%, transparent);
font-family: var(--font-sans);
font-size: 13px;
}
.result--err { border-left-color: var(--negative); background: color-mix(in srgb, var(--negative) 5%, transparent); }
.result__head {
font-family: var(--font-mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--accent);
margin-bottom: 10px;
}
.result--err .result__head { color: var(--negative); }
.result__tag {
display: inline-block;
margin-left: 6px;
font-size: 9px;
padding: 1px 5px;
border: 1px solid var(--accent);
color: var(--accent);
}
.result__grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px 18px;
margin-bottom: 10px;
}
.result__grid .k {
font-family: var(--font-mono);
font-size: 9.5px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.result__grid .v { font-size: 17px; color: var(--text); font-variant-numeric: tabular-nums; margin-top: 2px; }
.result__grid .v.pos { color: var(--positive); }
.result__grid .v.neg { color: var(--negative); }
.result__row { color: var(--muted); font-size: 12px; margin-top: 6px; }
.result__warn { color: var(--alert); font-size: 12px; margin-top: 4px; }
.result__warn code { background: rgba(0,0,0,0.15); padding: 1px 4px; font-family: var(--font-mono); }
/* --- Chat sidebar ----------------------------------------------------- */
.chat-header {
border-bottom: 1px solid var(--border);
padding: 6px 4px 8px;
margin-bottom: 6px;
display: flex;
flex-direction: column;
}
.chat-title {
color: var(--accent);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 11px;
}
.chat-title::before { content: "▸ "; }
.chat-hint { color: var(--dim); font-size: 10px; margin-top: 2px; }
.chat-thread {
flex: 1 1 auto;
overflow-y: auto;
padding: 4px 2px;
display: flex;
flex-direction: column;
gap: 8px;
min-height: 0;
}
.chat-msg {
font-family: var(--font-sans);
font-size: 13.5px;
padding: 9px 11px;
border: 1px solid var(--border);
line-height: 1.6;
word-wrap: break-word;
}
.chat-msg--system {
color: var(--muted);
font-size: 12px;
background: transparent;
border-style: dashed;
font-family: var(--font-mono);
}
.chat-msg--user {
background: var(--user-bubble-bg);
border-color: var(--accent);
color: var(--text);
align-self: flex-end;
max-width: 92%;
white-space: pre-wrap;
}
.chat-msg--user::before {
content: "you ";
font-family: var(--font-mono);
color: var(--accent);
opacity: 0.7;
font-size: 10px;
}
.chat-msg--assistant { background: var(--surface-2); color: var(--text); }
.chat-msg--assistant::before {
content: "cassandra ";
font-family: var(--font-mono);
color: var(--accent);
opacity: 0.7;
font-size: 10px;
}
.chat-msg--pending { color: var(--dim); font-style: italic; }
.chat-msg--error { color: var(--negative); border-color: var(--negative); }
.chat-msg p { margin: 0.4em 0; }
.chat-msg p:first-child { margin-top: 0; }
.chat-msg p:last-child { margin-bottom: 0; }
.chat-msg h2, .chat-msg h3, .chat-msg h4 {
font-family: var(--font-mono);
color: var(--accent);
font-size: 11px;
margin: 0.8em 0 0.3em;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.chat-msg strong { color: var(--text); font-weight: 700; }
.chat-msg em { color: var(--muted); font-style: italic; }
.chat-form {
border-top: 1px solid var(--border);
padding-top: 6px;
display: flex;
gap: 6px;
align-items: flex-end;
}
.chat-form textarea {
flex: 1;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
font-family: inherit;
font-size: 12px;
padding: 6px 8px;
resize: vertical;
min-height: 36px;
outline: none;
}
.chat-form textarea:focus { border-color: var(--accent); }
.chat-form button {
background: transparent;
border: 1px solid var(--accent);
color: var(--accent);
font-family: inherit;
font-size: 11px;
padding: 6px 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
cursor: pointer;
}
.chat-form button:hover:not(:disabled) { background: var(--accent); color: var(--bg); }
.chat-form button:disabled { opacity: 0.4; cursor: not-allowed; }
/* --- News ------------------------------------------------------------- */
.news-row {
padding: 4px 12px;
display: grid;
/* age | source | title | tags-on-right | utc-time */
grid-template-columns: 50px 130px minmax(0, 1fr) minmax(0, auto) 110px;
gap: 12px;
font-size: 12px;
border-bottom: 1px solid var(--surface-2);
align-items: center;
}
@media (max-width: 720px) {
.news-row { grid-template-columns: 50px 100px 1fr; }
.news-row .local,
.news-row__tags { display: none; }
}
.news-row:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.news-row .age { color: var(--dim); text-align: right; }
.news-row .source { color: var(--muted); font-size: 11px; }
.news-row .title { color: var(--text); }
.news-row .title:hover { color: var(--accent); }
.news-row .local {
color: var(--muted);
font-size: 11px;
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* News tag chips on each row + the top-bar pill toggles */
.news-row__tags {
display: inline-flex;
flex-wrap: nowrap;
gap: 3px;
justify-content: flex-end;
overflow: hidden;
max-width: 100%;
}
.tag-chip {
font-family: var(--font-mono);
font-size: 9px;
letter-spacing: 0.04em;
color: var(--muted);
background: var(--surface-2);
border: 1px solid var(--border);
padding: 0 4px;
white-space: nowrap;
text-transform: uppercase;
line-height: 1.5;
}
.news-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
background: var(--surface-2);
}
.news-tag {
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--muted);
background: transparent;
border: 1px solid var(--border);
padding: 3px 8px;
cursor: pointer;
}
.news-tag:hover { color: var(--accent); border-color: var(--accent); }
.news-tag[data-state="include"] {
background: var(--accent);
color: var(--bg);
border-color: var(--accent);
}
.news-tag[data-state="exclude"] {
color: var(--negative);
border-color: var(--negative);
text-decoration: line-through;
}
.news-tag--clear { color: var(--dim); border-style: dashed; }
.news-tag--clear:hover { color: var(--negative); border-color: var(--negative); }
/* --- Empty / loading state ------------------------------------------- */
.empty {
padding: 24px;
text-align: center;
color: var(--muted);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.htmx-indicator {
display: inline-block;
color: var(--dim);
opacity: 0;
transition: opacity 0.2s;
}
.htmx-request .htmx-indicator { opacity: 1; }
/* --- Scrollbar -------------------------------------------------------- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--dim); border-radius: 0; }
::-webkit-scrollbar-thumb:hover { background: var(--muted); }
/* ============================================================
* Public pages — landing, pricing, about, terms, privacy, disclaimer.
* Shared by all templates extending public_base.html. Visual language
* matches the app shell (same palette, monospace brand, restrained
* typography) but without dashboard chrome.
* ============================================================ */
.public-page { background: var(--bg); }
.public-shell {
display: flex;
flex-direction: column;
min-height: 100vh;
max-width: 1080px;
margin: 0 auto;
padding: 0 24px;
}
.public-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 22px 0 16px;
border-bottom: 1px solid var(--border);
}
.public-header__brand {
color: var(--accent);
font-weight: 700;
text-decoration: none;
font-family: var(--font-mono);
font-size: 15px;
letter-spacing: 0.01em;
}
.public-header__brand::before { content: "▰ "; opacity: 0.6; }
.public-header__brand:hover { color: var(--text); }
.public-header__nav { display: flex; align-items: center; gap: 22px; }
.public-header__nav a {
color: var(--muted);
font-size: 13px;
text-decoration: none;
}
.public-header__nav a:hover,
.public-header__nav a.active { color: var(--text); }
.public-header__cta {
color: var(--accent) !important;
border: 1px solid var(--accent);
padding: 6px 14px;
border-radius: 3px;
}
.public-header__cta:hover { background: var(--accent); color: var(--bg) !important; }
.public-main {
flex: 1;
padding: 48px 0 64px;
}
.public-footer {
border-top: 1px solid var(--border);
padding: 28px 0 36px;
margin-top: 24px;
font-size: 12px;
color: var(--muted);
}
.public-footer__inner {
display: flex;
flex-direction: column;
gap: 14px;
}
.public-footer__brand strong { color: var(--text); margin-right: 10px; }
.public-footer__tagline { color: var(--muted); }
.public-footer__links { display: flex; flex-wrap: wrap; gap: 16px; }
.public-footer__links a { color: var(--muted); text-decoration: none; }
.public-footer__links a:hover { color: var(--accent); }
.public-footer__meta { color: var(--dim); font-size: 11px; }
/* --- Hero (landing) -------------------------------------------------- */
.hero {
padding: 32px 0 48px;
border-bottom: 1px solid var(--border);
margin-bottom: 48px;
}
.hero__brand {
color: var(--muted);
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero__headline {
font-size: clamp(28px, 5vw, 44px);
font-weight: 700;
line-height: 1.15;
color: var(--text);
margin: 12px 0 14px;
letter-spacing: -0.01em;
}
.hero__subhead {
font-size: 16px;
color: var(--muted);
max-width: 640px;
line-height: 1.55;
margin: 0 0 24px;
}
.hero__ctas { display: flex; flex-wrap: wrap; gap: 12px; }
/* Shared button shape — was previously scoped to .hero__ctas, which made
the pricing-card CTAs render as bare anchors. */
.btn-primary,
.btn-secondary {
display: inline-block;
padding: 10px 22px;
border-radius: 3px;
font-size: 13.5px;
font-weight: 500;
line-height: 1.4;
text-decoration: none;
text-align: center;
cursor: pointer;
}
/* Block variant: full-width within parent, slightly taller — used inside
tier cards so each CTA spans the card and reads as the obvious action. */
.btn-block { display: block; width: 100%; padding: 12px 22px; font-size: 14px; }
/* Qualify with `a` so we beat `a { color: var(--accent) }` and any
:link/:visited UA defaults. Without `a.btn-primary` the cascade can
resolve in favour of the visited-link color on some browsers and the
label disappears against the accent background. */
a.btn-primary,
a.btn-primary:link,
a.btn-primary:visited {
background: var(--accent);
color: var(--bg);
border: 1px solid var(--accent);
}
a.btn-primary:hover { background: transparent; color: var(--accent); }
a.btn-secondary,
a.btn-secondary:link,
a.btn-secondary:visited {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
}
a.btn-secondary:hover { color: var(--accent); border-color: var(--accent); }
/* --- Feature blocks (landing) --------------------------------------- */
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 24px;
margin: 8px 0 56px;
}
.feature-card {
border: 1px solid var(--border);
border-radius: 4px;
padding: 22px 22px 24px;
background: var(--surface);
/* Flex column so the screenshot thumbnail can dock to the bottom via
margin-top:auto — that's what lines the three thumbnails up across
cards regardless of body-text length. */
display: flex;
flex-direction: column;
}
.feature-card__tag {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--accent);
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 10px;
}
.feature-card__title {
font-size: 17px;
font-weight: 600;
color: var(--text);
margin: 0 0 10px;
}
.feature-card__body {
font-size: 13.5px;
line-height: 1.6;
color: var(--muted);
margin: 0;
/* Grow to fill the flex column so the thumbnail below docks to the
bottom of the card. With grid-stretched equal-height cards, this is
what aligns the thumbnails across the three cards. */
flex-grow: 1;
}
/* --- Section primitives reused across pricing/about/legal ---------- */
.public-section {
margin: 0 0 56px;
}
.public-section__head {
font-size: 20px;
font-weight: 600;
color: var(--text);
margin: 0 0 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.public-section h3 {
font-size: 15px;
font-weight: 600;
color: var(--text);
margin: 24px 0 8px;
}
.public-section p,
.public-section li {
font-size: 14px;
line-height: 1.65;
color: var(--text);
}
.public-section p { margin: 0 0 14px; }
.public-section ul {
margin: 0 0 16px;
padding-left: 22px;
}
.public-section li { margin-bottom: 6px; }
.public-section a { color: var(--accent); }
.public-section--callout {
border-left: 3px solid var(--accent);
padding: 16px 22px;
background: var(--surface);
border-radius: 0 4px 4px 0;
margin: 0 0 32px;
}
.public-section--warning {
border-left-color: var(--negative);
background: color-mix(in srgb, var(--negative) 6%, var(--bg));
}
.public-section--warning a { color: var(--text); }
/* --- "What this is not" strip on landing --------------------------- */
.not-strip {
border: 1px dashed var(--border);
padding: 18px 22px;
border-radius: 4px;
margin: 0 0 56px;
background: var(--surface);
}
.not-strip strong { color: var(--text); }
.not-strip ul { display: flex; flex-wrap: wrap; gap: 18px 28px; margin: 8px 0 0; padding: 0; list-style: none; }
.not-strip li { color: var(--muted); font-size: 13px; }
.not-strip li::before { content: "✕ "; color: var(--negative); font-weight: 700; margin-right: 4px; }
/* --- Pricing comparison -------------------------------------------- */
.tier-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin: 8px 0 40px;
}
.tier-card {
position: relative;
border: 1px solid var(--border);
border-radius: 6px;
padding: 28px 26px 28px;
background: var(--surface);
display: flex;
flex-direction: column;
}
.tier-card--featured {
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent) inset,
0 12px 32px rgba(15, 23, 42, 0.10);
}
[data-theme="dark"] .tier-card--featured {
box-shadow: 0 0 0 1px var(--accent) inset,
0 12px 32px rgba(0, 0, 0, 0.45);
}
.tier-card__badge {
position: absolute;
top: -11px;
left: 24px;
background: var(--accent);
color: var(--bg);
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.10em;
text-transform: uppercase;
font-weight: 600;
padding: 4px 10px;
border-radius: 3px;
}
/* Tier name — the actual heading, not the small uppercase chip it used
to be. Pairs with .tier-card__tagline for a one-line value framing. */
.tier-card__name {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--text);
margin: 0 0 4px;
line-height: 1.1;
}
.tier-card__tagline {
font-size: 13px;
color: var(--muted);
line-height: 1.5;
margin-bottom: 22px;
}
.tier-card__price {
font-size: 40px;
font-weight: 700;
color: var(--text);
line-height: 1;
margin-bottom: 8px;
letter-spacing: -0.02em;
}
.tier-card__price-unit {
font-size: 15px;
color: var(--muted);
font-weight: 400;
letter-spacing: 0;
}
.tier-card__price-hint {
font-size: 12px;
color: var(--muted);
line-height: 1.55;
margin-bottom: 20px;
}
.tier-card__divider {
height: 1px;
background: var(--border);
margin: 0 0 18px;
}
.tier-card__list-head {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--muted);
letter-spacing: 0.10em;
text-transform: uppercase;
margin-bottom: 12px;
}
.tier-card ul {
list-style: none;
padding: 0;
margin: 0 0 24px;
flex: 1;
}
.tier-card li {
font-size: 13.5px;
color: var(--text);
line-height: 1.55;
padding: 8px 0 8px 22px;
position: relative;
border-bottom: 1px solid var(--border);
}
.tier-card li:last-child { border-bottom: 0; }
.tier-card li::before {
content: "✓";
position: absolute;
left: 0;
top: 8px;
color: var(--positive);
font-weight: 700;
}
.tier-card__cta { margin-top: 18px; }
/* Consent block above the Subscribe buttons (paid card, logged-in
free user). The Subscribe buttons render disabled; ticking the box
is what enables them. Wording covers ToS agreement (both cadences)
+ the Reg 36 CCR 2013 waiver (monthly only). */
.tier-card__consent {
display: flex;
gap: 10px;
align-items: flex-start;
margin-bottom: 14px;
padding: 12px 14px;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 4px;
font-size: 12px;
line-height: 1.55;
color: var(--muted);
cursor: pointer;
}
.tier-card__consent input[type="checkbox"] {
flex-shrink: 0;
margin-top: 2px;
cursor: pointer;
}
.tier-card__consent a {
color: var(--accent);
text-decoration: underline;
}
.tier-card__consent strong { color: var(--text); }
.tier-card__more {
margin-top: 14px;
padding-top: 14px;
border-top: 1px dashed var(--border);
font-size: 12px;
color: var(--muted);
line-height: 1.55;
}
/* Side-by-side feature comparison table. Lives below the cards and
makes the deltas readable at a glance — the cards sell, the table
confirms. */
.compare-table {
width: 100%;
border-collapse: collapse;
margin: 0 0 16px;
font-size: 13.5px;
}
.compare-table th,
.compare-table td {
text-align: left;
padding: 12px 14px;
border-bottom: 1px solid var(--border);
vertical-align: top;
line-height: 1.5;
}
.compare-table thead th {
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: 0.10em;
text-transform: uppercase;
color: var(--muted);
font-weight: 600;
border-bottom: 1px solid var(--border);
}
.compare-table th[scope="row"] {
font-weight: 500;
color: var(--text);
width: 38%;
}
.compare-table td.compare-table__free { color: var(--muted); }
.compare-table td.compare-table__paid { color: var(--text); font-weight: 500; }
.compare-table td.compare-table__paid strong { color: var(--accent); font-weight: 600; }
.compare-table td.compare-table__none { color: var(--dim); }
@media (max-width: 520px) {
.compare-table th[scope="row"] { width: 50%; }
.compare-table th, .compare-table td { padding: 10px 8px; font-size: 13px; }
}
/* BETA indicator pill in the app header — see app/templates/base.html. */
.beta-chip {
display: inline-block;
margin-left: 8px;
padding: 2px 7px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.14em;
font-family: var(--font-mono);
color: var(--bg);
background: var(--accent);
border-radius: 2px;
vertical-align: middle;
user-select: none;
}
/* -----------------------------------------------------------------------------
Landing-page screenshots: hero shot, thumbnails inside feature cards, gallery
strip, and a <dialog>-based lightbox. See app/templates/landing.html. */
/* All clickable screenshots are <button>s — reset the default chrome so they
read as image cards, not form controls. The shadow is the main "this is a
screenshot, not part of the page" signal; the border alone blends in. */
.shot {
appearance: none;
-webkit-appearance: none;
background: var(--surface);
border: 1px solid var(--border);
padding: 0;
margin: 0;
display: block;
width: 100%;
cursor: zoom-in;
border-radius: 4px;
overflow: hidden;
transition: border-color 120ms ease, transform 120ms ease,
box-shadow 160ms ease;
position: relative;
box-shadow: 0 6px 22px rgba(15, 23, 42, 0.18),
0 2px 6px rgba(15, 23, 42, 0.10);
}
.shot:hover {
border-color: var(--accent);
transform: translateY(-1px);
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.22),
0 4px 10px rgba(15, 23, 42, 0.14);
}
.shot:focus-visible {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent),
0 6px 22px rgba(15, 23, 42, 0.18);
}
/* Dark mode: the soft slate shadow disappears against the near-black bg.
Use a deeper, slightly accent-tinted glow so the cards still lift. */
[data-theme="dark"] .shot {
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.55),
0 2px 8px rgba(0, 0, 0, 0.35);
}
[data-theme="dark"] .shot:hover {
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.65),
0 0 0 1px rgba(0, 217, 255, 0.20);
}
@media (prefers-color-scheme: dark) {
.shot {
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.55),
0 2px 8px rgba(0, 0, 0, 0.35);
}
}
.shot img {
display: block;
width: 100%;
height: auto;
}
/* Hero screenshot — sits just below the headline CTAs, full landing width. */
.shot-hero {
max-width: 960px;
margin: 0 auto 56px;
padding: 0 24px;
}
.shot--hero .shot__zoom {
position: absolute;
bottom: 10px;
right: 12px;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--bg);
background: var(--accent);
padding: 4px 9px;
border-radius: 3px;
opacity: 0.85;
pointer-events: none;
}
/* Thumbnail at the bottom of each feature card. Vertical alignment across
the three cards is achieved by `.feature-card__body { flex-grow: 1 }`
above, which lets the body fill all available space inside the
equal-height grid cell — the thumbnail then sits at the same y across
cards. The fixed margin-top keeps a predictable gap above. */
.feature-card__shot {
margin-top: 18px;
}
.feature-card__shot img {
max-height: 200px;
object-fit: cover;
object-position: top left;
}
/* "More views" strip — flex so we can drop in 2-3 extra shots later. */
.shots-section {
margin-top: 8px;
}
.shots-grid {
display: grid;
gap: 18px;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
margin-top: 12px;
}
.shot__caption {
padding: 10px 12px;
font-size: 12.5px;
line-height: 1.5;
color: var(--muted);
background: var(--surface);
border-top: 1px solid var(--border);
text-align: left;
}
.shot__caption strong {
display: block;
font-size: 13px;
color: var(--text);
margin-bottom: 2px;
font-family: var(--font-mono);
letter-spacing: 0.04em;
}
/* Lightbox. <dialog> handles the modal mechanics (focus trap, ESC-to-close,
inert background); we just style the surface. */
.shot-modal {
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
max-width: min(96vw, 1400px);
max-height: 94vh;
padding: 0;
border-radius: 6px;
overflow: hidden;
}
.shot-modal::backdrop {
background: rgba(0, 0, 0, 0.78);
}
.shot-modal img {
display: block;
max-width: 100%;
max-height: 80vh;
width: auto;
height: auto;
margin: 0 auto;
}
.shot-modal p {
margin: 0;
padding: 14px 22px 18px;
font-size: 13.5px;
line-height: 1.6;
color: var(--muted);
border-top: 1px solid var(--border);
background: var(--surface-2);
}
.shot-modal__close {
position: absolute;
top: 6px;
right: 8px;
background: transparent;
border: 0;
color: var(--text);
font-size: 28px;
line-height: 1;
padding: 4px 10px;
cursor: pointer;
font-family: var(--font-mono);
}
.shot-modal__close:hover,
.shot-modal__close:focus-visible {
color: var(--accent);
outline: none;
}
/* --- Invite-a-friend callout (pricing) ----------------------------- */
/* A single-row visual banner that names the offer at a glance. The
detail text lives in a <dialog> behind the "How it works" button to
keep the pricing page scannable. */
.invite-callout {
display: flex;
align-items: center;
gap: 20px;
padding: 20px 24px;
margin: 0 0 40px;
background: linear-gradient(135deg,
color-mix(in srgb, var(--accent) 12%, var(--surface)),
var(--surface));
border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
border-left: 4px solid var(--accent);
border-radius: 6px;
}
.invite-callout__icon {
font-size: 32px;
line-height: 1;
flex-shrink: 0;
filter: saturate(1.1);
}
.invite-callout__body { flex: 1; min-width: 0; }
.invite-callout__eyebrow {
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: 0.10em;
text-transform: uppercase;
color: var(--accent);
font-weight: 600;
margin-bottom: 4px;
}
.invite-callout__headline {
font-size: 19px;
font-weight: 600;
color: var(--text);
line-height: 1.3;
margin-bottom: 4px;
}
.invite-callout__headline strong { color: var(--accent); font-weight: 700; }
.invite-callout__sub {
font-size: 13px;
color: var(--muted);
line-height: 1.5;
}
.invite-callout .btn-secondary { flex-shrink: 0; }
@media (max-width: 560px) {
.invite-callout { flex-direction: column; align-items: flex-start; gap: 14px; }
.invite-callout .btn-secondary { width: 100%; }
}
/* Generic text-only modal — reuse for any "click for the details"
pattern. Same <dialog> mechanics as .shot-modal. */
.text-modal {
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
max-width: min(92vw, 560px);
max-height: 88vh;
padding: 28px 28px 24px;
border-radius: 6px;
overflow-y: auto;
position: relative;
line-height: 1.6;
}
.text-modal::backdrop { background: rgba(0, 0, 0, 0.65); }
.text-modal__title {
font-size: 20px;
font-weight: 700;
margin: 0 0 14px;
padding-right: 36px;
color: var(--text);
}
.text-modal__head {
font-family: var(--font-mono);
font-size: 10.5px;
letter-spacing: 0.10em;
text-transform: uppercase;
color: var(--muted);
font-weight: 600;
margin: 20px 0 8px;
}
.text-modal p {
font-size: 13.5px;
color: var(--text);
margin: 0 0 12px;
}
.text-modal__list {
margin: 0 0 8px;
padding-left: 22px;
}
.text-modal__list li {
font-size: 13.5px;
color: var(--text);
margin-bottom: 6px;
line-height: 1.55;
}
.text-modal code {
font-family: var(--font-mono);
font-size: 12px;
background: var(--surface-2);
padding: 1px 5px;
border-radius: 2px;
}
.text-modal__close {
position: absolute;
top: 8px;
right: 10px;
background: transparent;
border: 0;
color: var(--muted);
font-size: 26px;
line-height: 1;
padding: 4px 10px;
cursor: pointer;
font-family: var(--font-mono);
}
.text-modal__close:hover,
.text-modal__close:focus-visible {
color: var(--accent);
outline: none;
}
/* ---------- Dashboard portfolio edit mode -----------------------------
*
* Inline composer that sits above the portfolio table. Aesthetic:
* terminal-style command line, no boxed-form chrome, ghost controls,
* tinted-neutral palette pulled from --border / --dim / --muted, accent
* is theme-aware (deep teal in light, electric cyan in dark).
*/
/* The portfolio panel header gains two extra children (the EDIT / Done
* pills). The global `.panel-header` uses `space-between`, which works
* for headers with only title+meta but collapses meta into title once
* any later child has `margin-left: auto`. Switch this header to a
* gap-based flow; meta now sits 12px from the title, edit pill at the
* far right via its own auto-margin. */
#portfolio-panel .panel-header {
justify-content: flex-start;
gap: 12px;
}
/* EDIT / Done toggle buttons in the panel header. */
.pf-edit-btn,
.pf-done-btn {
display: inline-flex;
align-items: center;
gap: 4px;
background: transparent;
border: 1px solid var(--border);
color: var(--dim);
padding: 2px 8px;
border-radius: 2px;
cursor: pointer;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.04em;
text-transform: lowercase;
margin-left: auto;
transition: color 120ms ease-out, border-color 120ms ease-out;
}
.pf-edit-btn:hover, .pf-done-btn:hover {
color: var(--accent);
border-color: var(--accent);
}
/* The JS toggles these via the `hidden` attribute. `display: inline-flex`
* above otherwise wins over the UA's `[hidden] { display: none }`. */
.pf-edit-btn[hidden], .pf-done-btn[hidden] { display: none; }
/* × button per row — hidden by default, visible only in edit mode. */
.pf-row-del-cell { width: 20px; text-align: center; }
.pf-row-del {
display: none;
background: transparent;
border: none;
color: var(--dim);
cursor: pointer;
font-size: 14px;
padding: 0 4px;
font-family: inherit;
transition: color 120ms ease-out;
}
#portfolio-panel.pf-editing .pf-row-del { display: inline; }
#portfolio-panel.pf-editing .pf-row-del:hover { color: var(--negative); }
/* ---------- Inline add-position composer ----------------------------- */
.pf-add {
padding: 6px 0 8px;
border-bottom: 1px dashed var(--border);
margin-bottom: 6px;
font-family: var(--font-mono);
font-size: 12px;
}
.pf-add__line {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.pf-add__prompt {
color: var(--accent);
font-weight: 600;
user-select: none;
margin-right: 2px;
}
.pf-add__div {
width: 1px;
height: 14px;
background: var(--border);
margin: 0 2px;
}
.pf-add__at {
color: var(--dim);
font-size: 11px;
user-select: none;
}
.pf-add__line input[type="text"],
.pf-add__line input[type="number"],
.pf-add__line input[type="date"] {
background: transparent;
border: none;
border-bottom: 1px solid var(--border);
color: var(--text);
padding: 2px 4px;
font-family: inherit;
font-size: 12px;
line-height: 1.4;
font-variant-numeric: tabular-nums;
border-radius: 0;
}
.pf-add__line input:focus {
outline: none;
border-bottom-color: var(--accent);
background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.pf-add__line input::placeholder {
color: var(--dim);
font-style: italic;
}
.pf-add__ticker {
width: 80px;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
}
.pf-add__num--qty { width: 56px; text-align: right; }
.pf-add__num--cost { width: 76px; text-align: right; }
.pf-add__date { width: 128px; margin-left: 4px; }
/* Tiny pill that shows after a successful validate: "172.40 USD". */
.pf-add-currency {
color: var(--muted);
font-size: 11px;
min-width: 24px;
letter-spacing: 0.02em;
}
.pf-add-currency:empty { display: none; }
/* Calendar-icon button — ghost, square, terminal-feel. */
.pf-add__icon {
background: transparent;
border: 1px solid var(--border);
color: var(--dim);
width: 22px;
height: 22px;
padding: 0;
border-radius: 2px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: color 120ms ease-out, border-color 120ms ease-out;
}
.pf-add__icon:hover {
color: var(--accent);
border-color: var(--accent);
}
.pf-add__icon:focus-visible {
outline: none;
color: var(--accent);
border-color: var(--accent);
}
/* Submit button — ghost styled to match the EDIT pill. Lights up to
* solid accent only when an add is actually possible. */
.pf-add__submit {
margin-left: auto;
background: transparent;
border: 1px solid var(--accent);
color: var(--accent);
padding: 3px 14px;
border-radius: 2px;
cursor: pointer;
font-family: inherit;
font-size: 12px;
letter-spacing: 0.06em;
text-transform: lowercase;
transition: background 120ms ease-out, color 120ms ease-out,
border-color 120ms ease-out;
}
.pf-add__submit:hover:not(:disabled),
.pf-add__submit:focus-visible:not(:disabled) {
background: var(--accent);
color: var(--bg);
outline: none;
}
.pf-add__submit:disabled {
border-color: var(--border);
color: var(--dim);
cursor: not-allowed;
}
/* Status indicators next to the ticker + below the row. */
.pf-add-status {
font-size: 11px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.pf-add-status:empty { display: none; }
.pf-add-status--pending { color: var(--dim); font-style: italic; }
.pf-add-status--ok { color: var(--positive); }
.pf-add-status--err { color: var(--negative); }
/* Secondary line below the main row — only takes space when a child has
* content. Holds the date-lookup status and the duplicate warning. */
.pf-add__notes {
display: flex;
gap: 14px;
margin-top: 4px;
font-size: 11px;
}
.pf-add__notes:has(:empty:only-child) { display: none; }
.pf-add-warning {
color: var(--warning);
}
.pf-add-warning:empty { display: none; }