Three pieces of phone-side feedback:
1. Indicator group tabs wrap onto multiple rows instead of
horizontal-scrolling — every group is visible at a glance. Each
button keeps its own bottom border so wrapped rows stay
visually delimited; the container's bottom border is removed.
2. Portfolio holdings table hides Qty and Avg columns on mobile via
the mobile-hide class (same mechanism as the indicator table).
Remaining columns are the actionable ones: Ticker, Name, Last,
P/L, %.
3. Markets bar at the bottom compacts to one row per chip —
dot + code + change% only. The state word ("open" / "closed")
is implied by the dot colour; the index label, price, and
until-time are dropped on mobile. Grid columns drop their 220px
floor so the full set fits the viewport without horizontal
scroll (previously the bar scrolled within itself).
275 lines
7.9 KiB
CSS
275 lines
7.9 KiB
CSS
/* Cassandra — dashboard-specific widgets: market chips, aggregate read
|
|
* header, indicator summary, glossary tooltips, group tabs, badges. */
|
|
|
|
/* --- Dashboard top header (markets + aggregate read) ----------------- */
|
|
|
|
.dash-header {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 12px;
|
|
margin-bottom: 0;
|
|
}
|
|
.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;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
/* --- 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; }
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
/* --- Mobile (≤480px) -------------------------------------------------- */
|
|
|
|
@media (max-width: 480px) {
|
|
/* Hide secondary indicator-table columns: Label, Ccy, 1y, anchor,
|
|
as-of. The cells are tagged with .mobile-hide in indicators.html;
|
|
this rule keeps display intent in CSS while letting the template
|
|
handle the conditional anchor column. Symbol / Price / 1d / 1m
|
|
remain — the four numbers a phone user actually wants. */
|
|
.dense .mobile-hide { display: none; }
|
|
|
|
/* Tighter cell padding so the four remaining columns fit
|
|
comfortably on a 360px viewport. */
|
|
.dense th, .dense td {
|
|
padding: 4px 6px;
|
|
font-size: 11px;
|
|
}
|
|
/* Symbol column gets a touch more breathing room — it's the
|
|
identifying anchor. */
|
|
.dense td.label { font-weight: 600; }
|
|
|
|
/* Group tabs: wrap onto multiple rows instead of horizontal
|
|
scrolling so the user can see every group at a glance. The
|
|
border-bottom moves to each row so wrapped rows are still
|
|
visually delimited. */
|
|
.group-tabs {
|
|
flex-wrap: wrap;
|
|
overflow-x: visible;
|
|
border-bottom: 0;
|
|
}
|
|
.group-tabs button {
|
|
padding: 6px 10px;
|
|
font-size: 11px;
|
|
border-bottom: 1px solid var(--border);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Aggregate-read summary header tightens — stack the label above
|
|
the timestamp to avoid wrapping at awkward points. */
|
|
.ind-summary__head {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 2px;
|
|
}
|
|
.ind-summary__body { font-size: 12px; }
|
|
}
|