feat(macro-vibes): rename BTC Signal → Macro Vibes; add MacroPanel UI
Module rename across page H1, navbar tab, URL (/en/btc → /en/macro), all metadata/JSON-LD, sitemap, llms.txt, opengraph image, and SystemControl copy. Old /btc route fully removed. New components/btc/MacroPanel.tsx polls /api/macro/snapshot and lays out the 8 indicators in four sections (Valuation / Bottom trigger reference / Market structure / Sentiment & flows / Positioning) with tone-coloured values, current-band threshold chips, and a single CoinGlass / source chart link per card. Composite -100..+100 needle pulses on score change. Also fixes the pinned bottom-reversal alert on the homepage, which still linked to the now-404 /en/btc — now routes to /en/macro. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+760
-2
@@ -48,8 +48,8 @@
|
||||
--shadow-2: 0 4px 16px rgba(20, 18, 14, 0.06), 0 1px 2px rgba(20, 18, 14, 0.03);
|
||||
--shadow-3: 0 12px 40px rgba(20, 18, 14, 0.08), 0 2px 4px rgba(20, 18, 14, 0.04);
|
||||
|
||||
--sans: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
|
||||
--mono: var(--font-geist-mono), ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
--sans: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
|
||||
--mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] {
|
||||
@@ -331,6 +331,647 @@ html[data-theme="dark"] .asset-switch button.on { background: var(--surface-2);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* PageHint — the prominent "what is this page" tagline directly under
|
||||
.page-title. Stronger contrast + bigger than .page-sub so it actually
|
||||
gets read (page-sub was so faint it functioned as decoration).
|
||||
Use via <PageHint>One sentence about the page.</PageHint>. */
|
||||
.page-hint {
|
||||
color: var(--ink-2);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
margin: 10px 0 0;
|
||||
max-width: 64ch;
|
||||
}
|
||||
.page-hint-count {
|
||||
color: var(--ink-4);
|
||||
font-size: 13px;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid var(--line);
|
||||
}
|
||||
|
||||
/* SectionHint — always-visible explanation block. Pairs with a section/tab
|
||||
to tell the user "what does this view actually do" in 1–3 sentences.
|
||||
More detailed than PageHint, less detail-y than an InfoTip. Spans the
|
||||
full content width on purpose — narrow boxes inside a wide page looked
|
||||
like accidentally-floating callouts. */
|
||||
.section-hint {
|
||||
background: var(--bg-sunk);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 14px;
|
||||
color: var(--ink-2);
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.section-hint strong {
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* InfoTip — small "?" pill that reveals a one-line tooltip on hover/focus.
|
||||
No JS, pure CSS. Pattern mimics the (i) dots in Linear/Hyperliquid:
|
||||
subtle until needed, never blocks the underlying content. */
|
||||
.infotip {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
cursor: help;
|
||||
outline: none;
|
||||
/* The icon should sit on the text baseline without nudging line-height. */
|
||||
line-height: 1;
|
||||
}
|
||||
.infotip-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-sunk);
|
||||
color: var(--ink-3);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
border: 1px solid var(--line);
|
||||
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
||||
user-select: none;
|
||||
}
|
||||
.infotip:hover .infotip-icon,
|
||||
.infotip:focus-visible .infotip-icon {
|
||||
background: var(--ink);
|
||||
color: var(--bg);
|
||||
border-color: var(--ink);
|
||||
}
|
||||
.infotip-bubble {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
width: var(--tip-w, 240px);
|
||||
padding: 8px 10px;
|
||||
/* Always-dark bubble — common pattern (Linear/Stripe/GitHub) — but needs
|
||||
a visible border in dark mode so it doesn't blend into the page bg. */
|
||||
background: #0f0d0a;
|
||||
color: #f5f2ea;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
text-align: left;
|
||||
box-shadow: 0 6px 24px rgba(0,0,0,0.25), 0 1px 2px rgba(0,0,0,0.2);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transform: translateY(2px);
|
||||
transition: opacity 0.12s, transform 0.12s;
|
||||
white-space: normal;
|
||||
}
|
||||
/* Dark mode: page bg is ~oklch(15%), bubble is #0f0d0a — visually identical.
|
||||
Lighten the bubble slightly and add a 1px ring so the shape stays defined. */
|
||||
html[data-theme="dark"] .infotip-bubble {
|
||||
background: oklch(28% 0.008 85);
|
||||
border-color: oklch(38% 0.01 85);
|
||||
box-shadow: 0 6px 24px rgba(0,0,0,0.6), 0 1px 2px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* MacroPanel cell — subtle hover so the grid feels alive without becoming
|
||||
a giant heat-map of click affordances. Just a tiny background lift. */
|
||||
.macro-cell:hover {
|
||||
background: var(--bg-sunk) !important;
|
||||
}
|
||||
|
||||
.macro-panel {
|
||||
padding: 0;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.macro-panel-head {
|
||||
padding: 20px 22px 18px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.macro-panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.macro-panel-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: var(--up);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.macro-panel-subtitle {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.macro-panel-meta {
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.macro-section {
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.macro-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 22px 12px;
|
||||
/* Was 10px / ink-3 / weight 700 — too quiet, users couldn't see the
|
||||
section structure. Bumped to 12px / ink-2 / weight 800. Still uppercase
|
||||
+ tracked so it reads as a label, not a heading that competes with the
|
||||
metric values. */
|
||||
font-size: 12px;
|
||||
color: var(--ink-2);
|
||||
letter-spacing: 0.10em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 800;
|
||||
background: var(--bg-sunk);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
/* Tiny dot anchor on the left — visual signal that "this is a section
|
||||
header" without using a heavy colored bar. */
|
||||
.macro-section-title::before {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--ink-3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.macro-grid {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.macro-grid.one {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.macro-grid.two {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.macro-metric-card,
|
||||
.macro-guide-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 18px;
|
||||
min-width: 0;
|
||||
min-height: 248px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.macro-metric-card {
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
|
||||
.macro-metric-card.tone-up {
|
||||
border-color: color-mix(in oklab, var(--up) 28%, var(--line));
|
||||
}
|
||||
|
||||
.macro-metric-card.tone-down {
|
||||
border-color: color-mix(in oklab, var(--down) 28%, var(--line));
|
||||
}
|
||||
|
||||
/* Stat card layout — label STACKED ABOVE the big number, not side-by-side.
|
||||
Previous side-by-side layout pushed the title to the far left and the
|
||||
value to the far right with too much air between them on wide screens.
|
||||
The eye now reads top→bottom: label → value → summary → chips → button. */
|
||||
.macro-metric-head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.macro-metric-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
/* Slightly larger + bolder so "AHR999" doesn't disappear next to the
|
||||
big number below. Treated as a label, not a heading. */
|
||||
font-size: 15px;
|
||||
font-weight: 650;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.macro-rank {
|
||||
font-size: 11px;
|
||||
color: var(--ink-4);
|
||||
letter-spacing: 0.08em;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.macro-metric-value {
|
||||
/* Same size as before but sits directly under the label now, so the
|
||||
eye-distance between "AHR999" and "0.4663" is ~6px instead of the
|
||||
full card width. */
|
||||
font-size: 34px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.03em;
|
||||
font-weight: 800;
|
||||
color: var(--ink);
|
||||
text-align: left;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.macro-guide-title {
|
||||
font-size: 20px;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.macro-summary {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-2);
|
||||
min-height: 66px;
|
||||
}
|
||||
|
||||
.macro-thresholds {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
/* THRESHOLD CHIPS — "you are here" treatment
|
||||
─────────────────────────────────────────────────────────
|
||||
Design goal: the chip that the CURRENT value falls into must be the
|
||||
single most-eye-catching element in the cell. Everything else fades back.
|
||||
|
||||
Two states:
|
||||
base (inactive) — flat, outlined, muted gray. Tone is suppressed
|
||||
so a "down (red)" inactive chip doesn't visually
|
||||
outshout an active "neutral" chip. Stripping
|
||||
tone here is intentional.
|
||||
.active — filled pill with the tone colour, larger, raised
|
||||
on shadow, scale 1.05. The eye can't miss it.
|
||||
|
||||
Hover the row → all chips bump to 0.8 opacity so you can read the
|
||||
other bands' rules without losing the active one. */
|
||||
|
||||
.macro-threshold-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 5px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: transparent;
|
||||
font-size: 11.5px;
|
||||
line-height: 1.35;
|
||||
color: var(--ink-4);
|
||||
font-weight: 500;
|
||||
opacity: 0.45;
|
||||
transition: opacity 160ms, transform 180ms cubic-bezier(.2,.8,.2,1),
|
||||
box-shadow 180ms, background 180ms, color 180ms;
|
||||
}
|
||||
.macro-thresholds:hover .macro-threshold-chip { opacity: 0.8; }
|
||||
|
||||
/* When INACTIVE, suppress tone colors. They'd compete with the active chip
|
||||
for attention. Tone returns the moment the chip becomes .active below. */
|
||||
.macro-threshold-chip:not(.active) {
|
||||
color: var(--ink-4) !important;
|
||||
background: transparent !important;
|
||||
border-color: var(--line) !important;
|
||||
}
|
||||
|
||||
/* ACTIVE — the band the current value falls into. */
|
||||
.macro-threshold-chip.active {
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
padding: 6px 14px;
|
||||
transform: scale(1.06);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.macro-threshold-chip.up.active {
|
||||
background: var(--up);
|
||||
color: #fff;
|
||||
border-color: var(--up);
|
||||
box-shadow:
|
||||
0 6px 18px color-mix(in oklab, var(--up) 35%, transparent),
|
||||
0 1px 0 rgba(255,255,255,0.25) inset;
|
||||
}
|
||||
.macro-threshold-chip.down.active {
|
||||
background: var(--down);
|
||||
color: #fff;
|
||||
border-color: var(--down);
|
||||
box-shadow:
|
||||
0 6px 18px color-mix(in oklab, var(--down) 35%, transparent),
|
||||
0 1px 0 rgba(255,255,255,0.25) inset;
|
||||
}
|
||||
.macro-threshold-chip.neutral.active {
|
||||
/* Amber = "caution" signal — between safe and danger. Lets the user
|
||||
know "you are here, no extreme yet" without the alarming red. */
|
||||
background: var(--amber);
|
||||
color: oklch(20% 0.04 75);
|
||||
border-color: var(--amber);
|
||||
box-shadow:
|
||||
0 6px 18px color-mix(in oklab, var(--amber) 35%, transparent),
|
||||
0 1px 0 rgba(255,255,255,0.4) inset;
|
||||
}
|
||||
|
||||
/* Dark mode tweaks — the soft white-shimmer inset doesn't read on dark cards,
|
||||
so swap to a subtle darker outline. */
|
||||
html[data-theme="dark"] .macro-threshold-chip.up.active,
|
||||
html[data-theme="dark"] .macro-threshold-chip.down.active,
|
||||
html[data-theme="dark"] .macro-threshold-chip.neutral.active {
|
||||
box-shadow:
|
||||
0 8px 24px color-mix(in oklab, currentColor 30%, transparent),
|
||||
0 0 0 1px rgba(0,0,0,0.25) inset;
|
||||
}
|
||||
|
||||
/* Section variants — reference cards (rules-only, no live number) get a
|
||||
muted treatment so users instantly see they're explanatory, not live.
|
||||
We REPLACE the dot anchor with a 📐 emoji (overriding the .macro-section-
|
||||
title::before above) and dim the text color. */
|
||||
.macro-section.reference .macro-section-title {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
.macro-section.reference .macro-section-title::before {
|
||||
content: '📐';
|
||||
width: auto;
|
||||
height: auto;
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
filter: grayscale(0.3);
|
||||
}
|
||||
.macro-guide-card {
|
||||
background: color-mix(in oklab, var(--bg-sunk) 60%, var(--surface));
|
||||
border-style: dashed;
|
||||
}
|
||||
.macro-guide-card .macro-guide-title::before {
|
||||
content: 'RULES ';
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background: color-mix(in oklab, var(--ink) 8%, transparent);
|
||||
color: var(--ink-3);
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Action row — single small button, aligned to the bottom-right of the card.
|
||||
Was previously a full-width dual-button group; pinning a compact button to
|
||||
the corner keeps the card's eye-flow on the value + thresholds, with the
|
||||
"go look at history" affordance present but quiet. */
|
||||
.macro-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: auto;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.macro-action-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
/* Compact natural width — fits "Open chart CoinGlass ↗" comfortably */
|
||||
padding: 7px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--line);
|
||||
background: color-mix(in oklab, var(--ink) 4%, var(--surface));
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
transition: background 120ms, border-color 120ms, transform 120ms, box-shadow 120ms;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
.macro-action-btn:hover {
|
||||
background: color-mix(in oklab, var(--ink) 7%, var(--surface));
|
||||
border-color: color-mix(in oklab, var(--ink) 12%, var(--line));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
|
||||
.macro-action-btn.chart {
|
||||
background: color-mix(in oklab, var(--amber) 10%, var(--surface));
|
||||
border-color: color-mix(in oklab, var(--amber) 18%, var(--line));
|
||||
}
|
||||
|
||||
/* The kicker ("OPEN CHART" uppercase eyebrow) used to sit ABOVE the label
|
||||
in a 2-row layout. With the new compact button it lives inline next to
|
||||
the label as a tiny prefix tag — same words, less vertical space. */
|
||||
.macro-action-copy {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.macro-action-kicker {
|
||||
font-size: 9.5px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-4);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.macro-action-label {
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.macro-action-arrow {
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
color: var(--ink-3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.macro-disclosure {
|
||||
margin-top: 14px;
|
||||
border-top: 1px dashed var(--line);
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.macro-disclosure summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.macro-disclosure summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.macro-disclosure-body {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.macro-composite {
|
||||
border-top: 1px solid var(--line);
|
||||
padding: 20px 22px 22px;
|
||||
background: var(--bg-sunk);
|
||||
}
|
||||
|
||||
.macro-composite-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.macro-composite-label {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-3);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.macro-composite-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.macro-composite-value {
|
||||
font-size: 32px;
|
||||
line-height: 0.95;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.macro-regime-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.macro-composite-track {
|
||||
position: relative;
|
||||
height: 12px;
|
||||
border-radius: 999px;
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.macro-composite-needle {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--ink);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow-2);
|
||||
transition: left 0.4s cubic-bezier(.2,.8,.2,1);
|
||||
/* React re-mounts this element via `key={composite_score}` whenever the
|
||||
score changes, which re-triggers the pulse animation below — a brief
|
||||
attention cue for users tracking how the macro tilt shifts day to day. */
|
||||
animation: macro-needle-pulse 0.9s ease-out 1;
|
||||
}
|
||||
@keyframes macro-needle-pulse {
|
||||
0% { transform: scale(0.85); box-shadow: 0 0 0 0 currentColor; }
|
||||
40% { transform: scale(1.18); box-shadow: 0 0 0 8px color-mix(in oklab, currentColor 18%, transparent); }
|
||||
100% { transform: scale(1); box-shadow: var(--shadow-2); }
|
||||
}
|
||||
|
||||
.macro-composite-scale {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--ink-4);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.infotip:hover .infotip-bubble,
|
||||
.infotip:focus-visible .infotip-bubble {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* Placement variants (default = top) */
|
||||
.infotip-top .infotip-bubble {
|
||||
bottom: calc(100% + 8px);
|
||||
left: 50%; transform: translate(-50%, 2px);
|
||||
}
|
||||
.infotip-top:hover .infotip-bubble,
|
||||
.infotip-top:focus-visible .infotip-bubble {
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
.infotip-bottom .infotip-bubble {
|
||||
top: calc(100% + 8px);
|
||||
left: 50%; transform: translate(-50%, -2px);
|
||||
}
|
||||
.infotip-bottom:hover .infotip-bubble,
|
||||
.infotip-bottom:focus-visible .infotip-bubble {
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
.infotip-left .infotip-bubble {
|
||||
right: calc(100% + 8px);
|
||||
top: 50%; transform: translate(2px, -50%);
|
||||
}
|
||||
.infotip-left:hover .infotip-bubble,
|
||||
.infotip-left:focus-visible .infotip-bubble {
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
.infotip-right .infotip-bubble {
|
||||
left: calc(100% + 8px);
|
||||
top: 50%; transform: translate(-2px, -50%);
|
||||
}
|
||||
.infotip-right:hover .infotip-bubble,
|
||||
.infotip-right:focus-visible .infotip-bubble {
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Cards + primitives
|
||||
============================================================ */
|
||||
@@ -1273,6 +1914,116 @@ html[data-theme="dark"] .ai-reasoning-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.macro-panel-head,
|
||||
.macro-composite-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.macro-panel-meta {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.macro-grid.two {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.macro-grid {
|
||||
padding: 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.macro-metric-card,
|
||||
.macro-guide-card {
|
||||
padding: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.macro-metric-head {
|
||||
/* Desktop is now also flex-direction: column. Just override the gap
|
||||
for mobile to keep label-to-value spacing comfortable on small fonts. */
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.macro-metric-value {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.macro-guide-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.macro-summary {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.macro-threshold-chip {
|
||||
font-size: 11.5px;
|
||||
/* Bump tap target on mobile — iOS HIG asks for ≥44pt, this is the
|
||||
inline-flex bubble so we go a bit smaller but still comfortable. */
|
||||
min-height: 36px;
|
||||
padding: 7px 12px;
|
||||
}
|
||||
.macro-threshold-chip.active {
|
||||
font-size: 12px;
|
||||
padding: 8px 14px;
|
||||
/* Stronger scale on mobile — small screens lose subtle differences. */
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.macro-action-btn {
|
||||
/* Stays compact on mobile too — bottom-right corner pill, not full-width. */
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
/* Composite scale labels — "BEAR / NEUTRAL / BULL" letters crowd at narrow
|
||||
widths. Shorten them. */
|
||||
.macro-composite-scale {
|
||||
font-size: 9.5px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* InfoTip — boost tap area and clamp bubble inside viewport */
|
||||
.infotip {
|
||||
/* Pad outside the visible icon to enlarge the hit zone without making
|
||||
the icon itself look bloated. */
|
||||
padding: 8px;
|
||||
margin: -8px -2px -8px 4px;
|
||||
}
|
||||
.infotip-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.infotip-bubble {
|
||||
/* On small viewports a 280px-wide bubble can hang off the edge when
|
||||
the tip sits near the right margin. Clamp to viewport width minus
|
||||
a 16px breathing room each side. */
|
||||
max-width: calc(100vw - 32px) !important;
|
||||
}
|
||||
/* Touch-only browsers fire :active on tap. Combined with :hover, this
|
||||
ensures bubble shows on first tap (not the doubled-tap that some
|
||||
CSS-hover-only tooltips require). */
|
||||
.infotip:active .infotip-bubble {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.macro-action-label {
|
||||
font-size: 12px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.macro-composite {
|
||||
padding: 18px 16px 20px;
|
||||
}
|
||||
|
||||
.macro-composite-right {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.row.between {
|
||||
align-items: flex-start;
|
||||
}
|
||||
@@ -1325,6 +2076,13 @@ html[data-theme="dark"] .ai-reasoning-card {
|
||||
.modal, .dialog { width: calc(100vw - 24px) !important; max-width: calc(100vw - 24px) !important; }
|
||||
}
|
||||
|
||||
/* Ultra-narrow screens — drop the regime descriptive words on the composite
|
||||
scale so it doesn't wrap. Keep just the numeric anchors. The .word spans
|
||||
inside each label are set in MacroPanel.tsx so this hide works cleanly. */
|
||||
@media (max-width: 420px) {
|
||||
.macro-composite-scale .word { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 380px) {
|
||||
.nav { padding: 8px 12px; }
|
||||
.page { padding: 16px 12px 48px; }
|
||||
|
||||
Reference in New Issue
Block a user