Fed giữ lãi suất, nhưng biểu đồ dot plot nói điều khác
Ba thành viên FOMC hạ dự báo cắt giảm cho 2027.
◆ Foundations
Every value below is lifted from app/assets/theme.css and the revamped components. Switch the theme in the masthead to see both palettes applied live.
Tokens
Two palettes, one set of names. Each swatch shows the light value on the left and the dark value on the right. Greys carry the interface. Green, amber and red are reserved for state.
| Token | Use | Do not use for |
|---|---|---|
| --text-primary | Ink. Body text, ink buttons, ink rules, active underlines, the notice bar default. | Backgrounds larger than a button. |
| --text-secondary | Body copy in secondary position, nav links at rest, tab labels, micro-labels on data. | Headings. |
| --text-tertiary | Eyebrows, dates, table headers, denominators, index numbers, placeholder text. | Anything that must pass AA on its own as body text. |
| --border-color | The default hairline. Frames, dividers, table rows, dashed card rules. | Text. |
| --border-lighter | Inner list rows, meter tracks, citation rows. | Outer frames. |
| --surface-2 | Ledger strips, table header rows, zebra rows, code, hover rows. | Page background. |
| --card-bg | Floating layers (panel, dialog, sheet head) and plan cells. | Decorative cards on the page plane. |
| --accent-color | Live dots, available quota, active tab underline, post-title hover, focus ring, select focus glow. | Large fills. Button backgrounds (the quiet button hover is the one exception). |
| --positive-color / --negative-color | Gains and losses as bare coloured text at weight 600. Error notices use negative. | Success or failure decoration unrelated to money or errors. |
| --warning-color | Low quota, stalled progress, upcoming earnings. | Emphasis. |
| --primary-color | Legacy grey-blue control colour. Kept for older components. | New work. Use ink. |
Plans and tiers get a personal ink via a local --ink and --ink-rgb. It colours a 2px cap rule, the name, the checkmarks and a tag. It never colours the background beyond a 4.5% tint on the featured plan.
| Plan | Light | Dark | RGB |
|---|---|---|---|
| Free | var(--text-tertiary) | var(--text-tertiary) | 107, 114, 128 |
| Plus | #059669 | #34d399 | 16, 185, 129 |
| Pro | #2563eb | #60a5fa | 59, 130, 246 |
| Max | #b45309 | #fbbf24 | 245, 158, 11 |
Type
Three families, one job each. Loaded in a single Google Fonts request from theme.css. Weights are limited to the ones listed; do not request more.
◆ AI research note Beta
| Role | Family | Size | Weight | Tracking | Line height |
|---|---|---|---|---|---|
| Display (share page symbol) | serif | clamp(2.6rem, 7vw, 4rem) | 700 | -0.02em | 1 |
| Sheet title | serif | 1.55rem | 600 | -0.01em | 1.2 |
| Prose h1 / h2 / h3 | serif | 1.5 / 1.35 / 1.15rem | 600 | -0.01em | 1.25 |
| Dialog title | serif | 1.4rem | 600 | -0.01em | 1.3 |
| Notice title | serif | 1.3rem | 600 | -0.01em | 1.3 |
| Plan name | serif | 1.3rem | 700 | -0.01em | 1 |
| Post card title | sans | 1rem | 700 | 0 | 1.35 |
| Nav link | sans | 14px | 500 | 0.01em | |
| Body prose | sans | 1rem (0.97 mobile) | 400 | 0 | 1.75 |
| Note body | sans | 0.92rem | 400 | 0 | 1.6 |
| Table cell | sans | 12px | 400 | 0 | |
| Button | sans | 0.76rem (0.72 sm) | 700 | 0.12em | |
| Table header | sans | 10.5px | 700 | 0.05em | |
| Eyebrow | sans | 0.62rem | 700 | 0.14em | |
| Disclaimer | sans | 0.74rem | 400 | 0 | 1.55 |
font-variant-numeric: tabular-nums. Prices, changes, percentages, counts, dates, list markers, table cells. Without it columns wobble as values update.Structure
A rule is always 1px solid unless it is one of three named exceptions: the 2px ink cap on dropdown panels and active tabs, the 3px ink bar on notices and accent cap on the overview card, and the 3px folio rule that opens the footer.
.rule { border: 0; border-top: 1px solid var(--border-color); }
.rule--faint { border-top-color: var(--border-lighter); }
.rule--dashed { border-top-style: dashed; }
.rule--strong { border-top-color: var(--text-primary); }
/* footer opening: 3px ink then 1px hairline 3px below */
.folio { border-top: 3px solid var(--text-primary); position: relative; }
.folio::before { content: ''; position: absolute; left: 0; right: 0; top: 3px; height: 1px; background: var(--border-color); }
A grid of cells that must share rules is drawn with gap: 1px on a --border-color backdrop. No cell has its own border, so rules never double.
.plan-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1px;
border: 1px solid var(--border-color);
background: var(--border-color);
}
.plan { background: var(--card-bg); }
Shape
See the summary on the overview. In practice:
border-radius: 0 on every button, input, panel, sheet, dialog, table, card and chip you create. Do not rely on omission.Grid
| Container | Max width | Padding | Notes |
|---|---|---|---|
| Masthead, footer | 1600px | 0 28px → 20px @1140 → 16px @960 | Grid auto 1fr auto, gap 24px. Bar height 64px, 56px under 960. |
| Default layout main | 1600px | 20px | Flex column, footer pushed down. |
| Homepage | 1440px | 24px → 16px @768 → 12px @480 | Two columns 1fr 310px, gap 56px; single column under 1024. |
| Reading column (AI share, prose) | 760px | Prose has no max-width of its own. Measure comes from the container. | |
| Note frames (notice, progress, dialog) | 560px / 440px | 1.25rem / 1.75rem | Centered with margin: 0 auto. |
| Right sheet | min(640px, 100vw) | 1.5rem → 1.15rem @768 | Full viewport height, 100dvh. |
No spacing tokens exist. The app uses rem for note surfaces and px for chrome. The values that recur:
| 4 · 6 · 8 | Gaps inside a control: icon to label, chip padding, meter to figure. |
| 10 · 12 · 14 · 16 | Control padding, table cells (9/11 by 12), panel rows (9 by 16), post card gaps. |
| 20 · 24 · 28 | Card padding (28/24/24 plan), section padding, page gutters. |
| 32 · 44 · 48 · 56 · 72 | Between sections, footer bands (44/32), homepage column gap (56), footer top margin (72). |
| 1rem · 1.25 · 1.5 · 2.1 | Note surfaces: body padding 1.5rem, notice 1.25rem, prose heading top margin 2.1rem, rule margins 2rem. |
| 30px | Footer segmented control and back-to-top. 36px on mobile. |
| 34px | Masthead login button, sheet close button. |
| 38px | Small ink button (notice, dialog), billing toggle, select trigger. |
| 40px | Bare icon button in the masthead. |
| 44px | Primary ink button, search box, plan CTA. Also the minimum touch target. |
| 46px | Full-width mobile buttons in sheets. |
Viewports
Desktop-first with max-width queries. 768px is the mobile line and matches useDeviceType. 960px is the masthead line. Post cards use container queries instead.
| Breakpoint | Count | What changes |
|---|---|---|
| 1320px | – | Wordmark shortens, nav gap tightens, search collapses to an icon. |
| 1200px | 7 | Homepage sidebar 310 → 290px. |
| 1100px | – | Plan grid 4 → 2 columns. |
| 1024px | 25 | Homepage single column. Stock tabs vertical → horizontal. Sidebar ad hidden. |
| 960px | – | Masthead switch. Bar 64 → 56px, desktop nav hidden, burger and icon search appear, right drawer sheet. |
| 768px | 182 | Mobile. Bottom tab bar, footer accordion, hero 2 columns, ledger 1 column, tooltips hidden, inputs 16px, sheet full width, sticky plan CTA, comparison table shows one tier via picker, ink buttons full width, tables scroll horizontally with edge fades. |
| 640px | 23 | Toasts edge to edge. Usage ledger 1 column. AI rebalance card wraps. |
| 480px | 89 | Tightest padding. Notice and dialog actions stack, full-width buttons. Section margins 40px. |
| 380px | – | Hero figure 2.125rem. Wordmark shortest. |
display: none !important; dotted underlines go with them.Ba thành viên FOMC hạ dự báo cắt giảm cho 2027.
Cấu trúc options đang nói gì về tuần tới.
Bốn quỹ lớn tăng tỷ trọng XLE.
.post-grid-wrap { container-type: inline-size; } /* query container = ancestor */
.post-grid { display: grid; grid-template-columns: 1fr; }
@container (min-width: 560px) { .post-grid { grid-template-columns: 1fr 1fr; } }
@container (min-width: 820px) { .post-grid { grid-template-columns: 1fr 1fr 1fr; } }
/* cells own their top and left dashed rule; first row and column drop theirs */
Time
| Name | Value | Used for |
|---|---|---|
| House ease | cubic-bezier(0.16, 1, 0.3, 1) | Panels, sheets, ink underlines, chevrons, search modal, dialog panel. |
| Thumb ease | cubic-bezier(0.22, 1, 0.36, 1) | Sliding segmented thumb, usage meter width, login popup, sticky CTA. |
| Image ease | cubic-bezier(0.25, 0.46, 0.45, 0.94) | Card image zoom (0.55s), hero shadow (0.35s). |
| Micro hover | 0.12 to 0.18s ease | Colour, border, background on hover. |
| Standard | 0.2 to 0.25s | Chevron rotate, tab underline, progress fill (linear). |
| Layer entry | 0.22s panel · 0.22s dialog · 0.35s sheet | Panel drops from -6px. Dialog rises from 12px. Sheet slides from 100%. |
| Ambient | 1.8s breathe · 2.2s sheen · 2.4s pulse | Live dot, progress sheen, composing diamond. Ease-in-out, infinite. |
@media (prefers-reduced-motion: reduce) {
.live-dot, .progress-sheen, .mark { animation: none; }
.sheet, .sheet-scrim, .dialog, .progress-fill { transition: none; }
}
Every file that animates ends with this block. The masthead uses a blanket kill on all descendants. A banner may keep a 0.15s opacity fade.
Content and controls: outline: 2px solid var(--accent-color); outline-offset: 2px. Masthead links use an inset ink ring, outline: 2px solid var(--text-primary); outline-offset: -2px. Never remove the outline without replacing it.
Depth
Shadows exist only on floating layers and are long, soft and mostly negative-spread, so they read as a cast shadow at the edge, not a glow.
| Layer | z-index | Shadow | Backdrop |
|---|---|---|---|
| Mobile tab bar | 900 | none | |
| Masthead | 10000 | 0 10px 30px -24px rgba(0,0,0,.35) when scrolled | |
| Dropdown panel | inside masthead | 0 24px 48px -28px rgba(0,0,0,.35) | |
| Toast, banner | 10003 | 0 10px 30px rgba(0,0,0,.12) | |
| Nav mobile sheet | 10010 | -24px 0 48px -32px rgba(0,0,0,.45) | rgba(10,10,10,.45) + blur(2px) |
| AI sheet | 10015 (--z-ai-sheet) | -24px 0 48px -28px rgba(0,0,0,.45) | --overlay-background, no blur |
| AI dialog | 10016 (--z-ai-dialog) | 0 24px 48px -28px rgba(0,0,0,.45) | --overlay-background + blur(2px) |
| Search modal | 10020 | 0 24px 64px rgba(0,0,0,.22), 0 8px 24px rgba(0,0,0,.12) | rgba(0,0,0,.65) + blur(8px) |
Dark theme shadows are deeper: 0 28px 56px -28px rgba(0,0,0,.8) for panels. Nothing on the page plane casts a shadow.
Implementation
Light is the default. The choice lives in a theme cookie (light | dark | system) and is resolved to a data-theme attribute on <html> by an inline script in <head>, before first paint. CSS never reads prefers-color-scheme directly.
<script>
(function () {
try {
var m = document.cookie.match(/(?:^|; )theme=([^;]*)/);
var pref = m ? decodeURIComponent(m[1]) : 'light';
var theme = 'light';
if (pref === 'dark') theme = 'dark';
else if (pref === 'system') theme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', theme);
/* status bar colour must follow the attribute, not the media query */
var meta = document.createElement('meta');
meta.name = 'theme-color';
meta.content = theme === 'dark' ? '#0e0d0c' : '#ffffff';
document.head.insertBefore(meta, document.head.firstChild);
} catch (e) {
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script>
[data-theme="dark"] .plan-plus { --ink: #34d399 }.filter: invert(1).app/stores/theme.js owns the cookie and the meta tag. Do not set data-theme from a component.