- Move news_job from hourly to 3x/hour (cron 10,30,50), with a CadencePolicy gate that throttles to active hours (07-21 UTC weekdays at 20 min), off-hours (3 h), weekends (6 h). Keeps the daytime feed fresh without spamming RSS sources overnight. - Tag each headline on ingestion via DeepSeek (BATCH_SIZE=25, max_tokens=4000, json.JSONDecoder().raw_decode + per-row regex recovery for resilient parsing). Vocabulary: 16 tags including new EU / USA / AI / Conflict. NULL tags are picked up automatically on the next news_job run, so back-tagging is implicit rather than a separate migration step. - Tag UI: pill bar above the feed with off → include → exclude cycle on click; shift-click jumps straight to exclude. State persists in localStorage and is injected into /api/news requests via htmx:configRequest. Per-row chips sit to the right of the headline (new 5-column grid: age | source | title | tags | UTC) so vertical density stays high. - Strategic log header bug: model was hallucinating "(Updated 21:30 UTC)" in future tense. Bumped PROMPT_VERSION 6→7, added explicit ban on time-of-day clauses, and supply the actual current UTC time in the user prompt so the model has no need to invent one. Migration 0012 adds headlines.tags (JSON, nullable). Tests cover vocabulary integrity, validation/normalisation, and the JSON-recovery parser (17 tests).
1249 lines
34 KiB
CSS
1249 lines
34 KiB
CSS
/* Cassandra — geopolitical-terminal aesthetic with two themes.
|
||
* Mono for data, headers, terminal feel; sans for prose surfaces (log + chat). */
|
||
|
||
:root {
|
||
/* Dark theme (default) */
|
||
--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);
|
||
}
|
||
|
||
[data-theme="light"] {
|
||
--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);
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
.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: "◐ dark"; }
|
||
[data-theme="light"] .theme-toggle__label::before { content: "◐ light"; }
|
||
|
||
/* 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;
|
||
grid-template-areas:
|
||
"indicators log"
|
||
"portfolio log"
|
||
"news news";
|
||
gap: 14px;
|
||
}
|
||
@media (max-width: 1100px) {
|
||
.app-main {
|
||
grid-template-columns: 1fr;
|
||
grid-template-areas: "indicators" "portfolio" "log" "news";
|
||
}
|
||
}
|
||
|
||
#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); }
|
||
.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; }
|
||
|
||
/* --- 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); }
|
||
|
||
.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"] {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
color: var(--text);
|
||
font-family: var(--font-mono);
|
||
font-size: 13px;
|
||
padding: 8px 10px;
|
||
outline: none;
|
||
}
|
||
.auth-card 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-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;
|
||
}
|
||
|
||
/* User chip in header */
|
||
.user-chip {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--muted);
|
||
margin-left: 8px;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.user-chip a {
|
||
color: var(--muted);
|
||
border-bottom: 1px dotted var(--muted);
|
||
}
|
||
.user-chip a:hover { color: var(--accent); border-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); }
|