Quantbot Design System
Dark-first, blue-on-near-black system for an algorithmic-trading product family: a Tailwind v4 marketing site with an 8-bit pixel wordmark, and — the focus of this spec — a companion analytics dashboard portal built as a widget grid (stat tiles, equity/returns charts, progress trackers) on a shadcn-style component vocabulary. This page is the visual regression test for the JSON spec: every component and every meaningful state below is rendered as a live specimen on the actual design-system surfaces.
At a glance
Signature moves
The six moves that make this system itself. Violating any one of these breaks the system's identity even if individual screens "look fine".
1 · Rim, not shadow
Cards/widgets/inputs/modals are bounded by a 1px white-at-6% inset border on a surface exactly one step darker than its background — no drop shadow. What breaks if you skip it: add box-shadows to cards and the dashboard reads like a default Material/Bootstrap admin template — the #1 tell of generic AI UI here.
2 · Top-right period-selector pill
A small dark pill (border-rim bg-white/3 px-3 py-1.5 text-[11px] + a trailing ChevronDown) pinned top-right of every chart/metric/tracker widget header. Skip it and widgets stop rhyming — the grid looks assembled from a parts bin.
3 · Two cool near-blacks
The dashboard page is slate-950 #020617 (faint blue cast); cards are zinc-900 #18181b (neutral). Two distinct near-blacks, not one — and neither is pure #000. Skip it and the rim disappears into the background; use #000 and it looks like an OLED test card.
4 · Skeleton, never spinner
Loading states keep the widget's header and rim intact and replace the body with pulsing zinc-800 placeholder blocks. No centered spinners, no "Loading…" text. Skip it and a centered spinner inside a dark card screams "unfinished admin tool" and causes layout jump.
5 · Flat — no scale-on-hover
Interactive surfaces respond to hover with a background-color lift only (surface → surface-hover, 120ms). No scale(), no translateY on widgets/rows/buttons. (Marketing service cards are the lone, subtle exception: a 2px lift.) Skip it and bouncy hover transforms read as "demo", not "platform".
6 · One saturated royal-blue
#2563eb (blue-600) is the entire accent system — buttons, links-as-action, active nav/tab, focus rings, progress fill, primary chart line, eyebrow accents. Multi-series charts rotate through 6 hues but those are data, never reused as UI chrome. Skip it — introduce a second accent and the hierarchy collapses.
Tokens
Two cool near-blacks are deliberate: the dashboard page is slate-950 (#020617, faint blue cast) and cards are zinc-900 (#18181b, neutral). The marketing site uses warmer zinc-950 (#09090b) as page. surface-hover is always one zinc step up from the surface it lifts from. The rim is the elevation primitive — a 1px white-at-6% inset border — used instead of drop shadows. Every value below is a resolved Tailwind v4 canonical token; oklch is the framework's canonical form, hex/rgb are the resolved equivalents.
Color roles — page & surfaces
Rims & dividers
Ink scale (text)
Brand family
Status
Chart-series rotation
Ordered rotation for multi-series bar/line charts (e.g. monthly-returns bars). First series is brand/link blue; the rest are evenly-spaced hues. These are data colors, never brand — never restyle a button or nav with a chart-series color. Single series → #3b82f6 line/bars; area charts use the brand@18% → transparent gradient under a 2px #3b82f6 line.
#3b82f6
#22c55e
#f97316
#a855f7
#ec4899
#facc15
brand@18%→0
Radii
Three structural stops + a pill. The source dashboard portal uses 10px for cards (shadcn's --radius default territory); the recommended canonical value for new builds is 12px (lg / widget). Don't "split the difference" to arbitrary values — pick from this set.
Spacing scale
Tailwind 4px base unit. Internal widget padding lives at 16–20px (px-5 / py-4..py-5). The dashboard grid gap is 16px; container padding 32px. Marketing section padding ramps 48 → 80 → 96px (mobile → tablet → desktop). widgetHeader: title→subtitle 2px, header→body 16px (gap-4). nav-link gap 28px; icon→text 8px.
4
6
8
12
16
20
24
32
48
64
80
96
Border widths · z-index · breakpoints
Border widths
| Token | px |
|---|---|
| hairline | 1 |
Everything is 1px. The focus "ring" is a 2px box-shadow ring (ring-2), not a border-width change.
Breakpoints (Tailwind defaults)
| Name | px | Effect |
|---|---|---|
sm | 640 | below: single-col dashboard grid |
md | 768 | dashboard becomes a 6-col layout; sidebar → off-canvas drawer |
lg | 1024 | full 12-col dashboard grid; spans as in layout.dashboard.grid.spans.desktop |
xl | 1280 | marketing content max-width |
Z-index layers
| Layer | z |
|---|---|
| base content | 0 |
| sticky table header / sidebar | 10 |
| dropdown / popover / tooltip | 30 |
| lead-capture modal (marketing) / app modal | 40 |
| sticky top nav | 50 |
| toast region | 60 |
| design-system page toolbar (this doc only) | 1000 |
Type ramp
~26 type roles. Marketing uses Inter; the dashboard portal uses the ui-sans-serif system stack. Press Start 2P (alt VT323) is used for the 8-bit brand wordmark only — never for UI text. Specimens below are rendered at the exact size/weight/leading/tracking for each role.
| Specimen | Role | px / weight / line / tracking | Color role | Context | Use |
|---|---|---|---|---|---|
| Gestión de capital | hero-display | 48 / 700 / 1.1 / -0.02em | ink | website | Marketing hero H1. |
| QUANTBOT | display-pixel-wordmark | 20 / 400 / 1.4 / 0.4em · uppercase · 'Press Start 2P' | ink | both | The 8-bit brand wordmark ONLY. Never UI text. |
| Account overview | section-h1 | 36 / 700 / 1.15 / -0.02em | ink | both | Marketing section H1; dashboard page title (portal renders ~30px). |
| Account overview | page-title-app | 30 / 700 / 1.2 · ui-sans-serif | ink | app | Dashboard page heading ("Account overview", "Settings"). |
| Gestión de cuenta real | section-h2 | 30 / 700 / 1.2 / -0.01em | ink | website | Service-card titles, large sub-headings. |
| Card sub-heading | section-h3 | 24 / 600 / 1.3 | ink | both | Card sub-headings, sticky CTA headline, modal title (large). |
| Equity curve | widget-title | 17 / 600 / 1.35 · ui-sans-serif | ink | app | Title in a widget/card header (16–18px OK; 17 recommended). |
| Payment status overview for last 6 months | widget-subtitle | 12 / 400 / 1.4 · ui-sans-serif | ink-muted | app | Optional one-line caption under a widget title. |
| +$2,156.92 | metric-value | 26 / 700 / 1.1 · tabular figures | ink | app | Big number in a stat tile (22–28px; 26 recommended). |
| +12% from last month | metric-delta-chip | 11 / 500 / 1.3 | ok | danger | ink-muted | app | "+12% from last month" under the value. Green/red/neutral by sign. |
| Symbol | table-header | 12 / 600 / 1.3 / 0.05em · uppercase | ink-muted | app | Column headers in a data table / list card. |
| EUR/USD · long · 0.50 lot | table-cell | 14 / 400 / 1.5 · tabular for numeric | ink-2 | app | Body cells in a table / list row. Numeric cells right-aligned + tabular. |
| Jan · Feb · Mar | axis-tick | 11 / 400 / 1 | ink-muted | app | Chart axis labels (month names, y-axis values). |
| Dashboard | nav-label-app | 14 / 500 / 1.4 | ink-2 (ink/brand when active) | app | Sidebar nav item label. |
| Overview | sidebar-section-label | 10 / 600 / 1.3 / 0.08em · uppercase | ink-faint | app | Group heading inside the sidebar ("OVERVIEW", "ACCOUNTS"). |
| Servicios | nav-link-marketing | 14 / 500 / 1.4 | ink-2 (ink on hover) | website | Top-nav links on the marketing site. |
| Iniciar sesión | button-md | 14 / 500 / 1 | white on brand / ink on ghost | both | Default button label (10/20px padding, 40px min-height). |
| Contratar ahora | button-lg | 16 / 600 / 1 | white on brand | both | Large CTA (14/28px padding, 48px min-height). |
| tucorreo@ejemplo.com | input-text | 14 / 400 / 1.4 | ink (placeholder: ink-disabled) | both | Text typed into form fields (12/16px padding, 44px min-height). |
| label | 14 / 500 / 1.4 | ink-2 | both | Form field label ("Email", "Contraseña"). | |
| Riesgo alto | badge | 12 / 500 / 1 | matches badge tone | both | Pill badge text (risk tags, status pills). |
| Hero subtitle paragraph | body-lg | 18 / 400 / 1.55 | ink-2 | website | Hero subtitle, primary supporting paragraph. |
| Default body copy on dark surfaces | body | 16 / 400 / 1.5 | ink-2 | both | Default body copy. |
| Footer copy, fine print | small | 14 / 400 / 1.5 | ink-muted | both | Footer copy, fine print, secondary table copy. |
| Beneficios destacados | caption / eyebrow | 12 / 500 / 1.4 / 0.025em | ink-muted (or brand for accent eyebrows) | both | Eyebrow labels above section titles; meta captions. |
| Last 6 months | period-pill-text | 11 / 500 / 1 | ink-muted | app | Text inside the period-selector pill. |
Motion
standard easing (cubic-bezier(0.4,0,0.2,1), Tailwind's default ease) for ~everything; emphasized (cubic-bezier(0.2,0,0,1)) only for entrances like the lead-capture modal.
| Interaction class | Duration | Easing | Properties | Notes |
|---|---|---|---|---|
| hover / press feedback | 120ms | standard | background-color, border-color, color, opacity | Widgets, list rows, nav items, buttons. NO transform — see signatures. |
| color / background transition | 200ms | standard | background-color, color, border-color, box-shadow | Tailwind transition-colors default. The general-purpose transition. |
| panel / drawer / accordion | 320ms | standard | max-height, transform, opacity | Sidebar drawer (mobile); FAQ accordion expand (chevron rotates 180°); off-canvas panels. |
| modal entrance | 320ms | emphasized | opacity, transform | Lead-capture modal fades in + slides down ~10px. Exit is faster (~200ms, standard). |
| button press | 120ms | standard | background-color, box-shadow | Brand buttons darken to brand-pressed + gain an inset top highlight inset 0 1px 0 rgba(255,255,255,0.08). |
Hover the chips — each runs its transition tier on a background-color lift.
prefers-reduced-motion: reduce: drop all transform/translate motion (modal slide, card lift), keep color/opacity transitions but cap them at ≤120ms, replace accordion max-height animation with an instant toggle, and never auto-animate charts on mount.Elevation — rims, not shadows
This system uses rims, not drop shadows. A card is defined by a 1px white-at-6% inset border on a one-step-darker-than-its-neighbour surface — not by a shadow. Adding box-shadows to cards is the single fastest way to make this system look wrong. (Modals and dropdown panels are the only things that get a real shadow.)
Exception shadows (the only real shadows in the system)
0 0 0 1px rgba(255,255,255,.06)
rgba(37,99,235,.45) 0%, rgba(37,99,235,0) 70%)
card-rim-gradient (optional top-lit refinement on hero cards): linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0)).
Iconography
Set: Lucide (outline) — alt Heroicons (outline), equivalent. Stroke width 1.5. Sizes 16 & 20px (16 = inline-with-text, table cells, chips, small buttons, period-pill chevron; 20 = nav items, larger buttons, card-header actions, status dots). Color inherits currentColor — defaults to ink-muted; goes ink or brand when active/hover; semantic colors (ok/warn/danger) when the icon carries status. Glyphs below are inline-SVG stand-ins; the canonical Lucide name is in the table.
| Glyph | Meaning | Lucide name + color |
|---|---|---|
| done / complete | Check or CheckCircle, colored ok #22c55e | |
| failed / not-included | X or XCircle, colored danger #ef4444 | |
| warning / risk | AlertTriangle, colored warn #facc15 | |
| info | Info, colored info #3b82f6 | |
| external link | ArrowUpRight (inline) or ExternalLink | |
| expand / collapse | ChevronDown — rotates 180° on open (200ms standard) | |
| period / date range | ChevronDown trailing the label inside the period-selector pill (Calendar optional leading) | |
| trend up | TrendingUp, colored ok | |
| trend down | TrendingDown, colored danger | |
| more actions | MoreHorizontal (kebab is MoreVertical) — top-right of a widget when there's no period selector | |
| close | X, colored ink-faint, 16px, top-right | |
| search | Search, leading inside a search input | |
| settings | Settings (gear) | |
| user / account | User or CircleUser | |
| → | navigate / link-arrow | literal '→' character used in marketing "Saber más →" affordances (not an icon font) |
Layout & grid
The dashboard shell + the 12-col / 16px-gap widget grid math, and the marketing layout rhythm.
Dashboard shell
Grid math
| Property | Value |
|---|---|
| columns | 12 |
| gap | 16px |
| container padding | 32px |
| row height | auto / content-driven (CSS grid auto rows). Chart cards enforce their own min-height (220–260px body); everything else is content height. |
| Widget type | desktop (lg+) | tablet (md) | mobile (sm-) | Note |
|---|---|---|---|---|
stat-tile | 4 | 3 | 12 | 3-up at desktop; 2-up at tablet; stacked on mobile. Content height. |
area-chart-card | 8 (alt 6) | 6 | 12 | Hero chart often span-8 next to a span-4 status card; or span-6 in a 2-up chart row. Fixed min-height. |
bar-chart-card | 6 | 6 | 12 | Fixed min-height (body 220–260px). |
list-table-card | 6 (alt 12) | 6 | 12 | span-6 alongside another card, or span-12 for a primary table. Content height. |
progress-tracker-card | 12 | 6 | 12 | Almost always full-width — it's a list of labelled progress rows. Content height. |
status-card | 4 | 3 | 12 | Content height. |
Collapse rules: ≥1024px (lg) → 12-col grid, spans as above. 768–1023px (md) → effectively a 6-col grid: stat-tiles 3-up become 2-up, chart cards go full-width-of-row, trackers/tables full width. <768px (sm and below) → single column, every widget full width in source order, charts keep their min-height; tables may switch to a stacked-row layout.
12-column grid diagram
Row 1 — three stat-tiles @ span-4
Row 2 — primary chart @ span-8 + status @ span-4
Row 3 — two cards @ span-6
Row 4 — full-width tracker @ span-12
Marketing layout
Components
Every component from the JSON, rendered as a live specimen with its parts, dimensions, a demo of every state in its matrix (each state shown as its own labeled instance — you can't hover a static screenshot), and its codeAnchor (React + Tailwind v4). The dashboard widgets come first (the focus of this spec), then the shared controls, then the website components.
widget-container app
The universal dashboard primitive. Every chart/metric/tracker/list widget is this shell + a body. Cohesion across the grid comes from this being identical everywhere.
| Slot | Token |
|---|---|
| root | bg surface #18181b · rim 1px rgba(255,255,255,.06) · radius widget 12px (10px in source) · padding 16–20px · CSS grid item, gets grid-column: span N |
| header | flex row, items-start justify-between, gap 12px → titleBlock + controlSlot |
| header.title | typeRamp widget-title (17/600), color ink |
| header.subtitle (optional) | typeRamp widget-subtitle (12/400), color ink-muted, margin-top 2–4px |
| header.controlSlot | right-aligned; holds the period-selector-pill (default), a dropdown filter (list cards), or a kebab — pick exactly one. Prefer a context-label pill over an empty slot — the slot is load-bearing. |
| body | margin-top 16px (gap-4 from header); the chart / metric / table / progress rows / empty-state / skeleton goes here |
| footer (optional) | right-aligned link-arrow ("View all →") in brand; margin-top 16px; only on list/summary widgets |
Dimensions: border-radius 12px (10px source) · border 1px rgba(255,255,255,.06) · bg #18181b · minHeight content-driven; chart variants 220–260px body min-height. Default grid span 6. Carries control: period-selector-pill.
function Widget({ title, subtitle, control, span = 6, footer, children }) { return ( <section style={{ gridColumn: `span ${span} / span ${span}` }} className="rounded-[12px] border border-white/[0.06] bg-[#18181b] p-5 transition-colors duration-[120ms] ease-[cubic-bezier(.4,0,.2,1)] focus-within:border-[#2563eb] focus-within:ring-2 focus-within:ring-[#2563eb]/20"> <header className="flex items-start justify-between gap-3"> <div> <h3 className="text-[17px] font-semibold leading-tight text-[#f9fafb]">{title}</h3> {subtitle && <p className="mt-0.5 text-[12px] text-[#a1a1aa]">{subtitle}</p>} </div> {control} </header> <div className="mt-4">{children}</div> {footer && <div className="mt-4 text-right">{footer}</div>} </section> ); }
period-selector-pill app
The signature affordance — a small dark dropdown trigger pinned top-right of data widgets. Often the only thing that makes the grid feel unified.
| Slot | Token |
|---|---|
| trigger | inline-flex, items-center, gap 6px · border 1px rim · bg white/3 (rgba(255,255,255,.03)) · radius md 8px · padding 6px 12px (px-3 py-1.5) · text period-pill-text 11/500 ink-muted |
| label | the current range ("Last 6 months", "Last 30 days", "YTD") |
| chevron | ChevronDown, 14px, ink-muted — rotates 180° when the menu is open (200ms) |
| menu | → dropdown-panel: opens below, right-aligned, ~160–200px wide |
Dimensions: height ≈28px · min-width fit-content.
<button type="button" className="inline-flex items-center gap-1.5 rounded-md border border-white/[0.06] bg-white/[0.03] px-3 py-1.5 text-[11px] font-medium text-[#a1a1aa] transition-colors duration-[120ms] hover:border-white/15 hover:text-[#e4e4e7] focus-visible:ring-2 focus-visible:ring-[#2563eb]/20"> Last 6 months <ChevronDown className="size-3.5" /> </button>
stat-tile app
Compact KPI widget — a label, a big number, a signed delta chip. The atom of the metrics row. Uses widget-container chrome; usually no header title (the label IS the content); often carries no control or a tiny context label.
| Slot | Token |
|---|---|
| icon (optional) | Lucide 16–20px in a 28–32px brand-soft rounded square (radius sm 6px), top-left or inline before the label |
| label | 12/500, ink-muted ("Total profit/loss", "Equity", "Monthly performance") |
| value | metric-value 26/700 ink, tabular figures, margin-top 4px ("+$2,156.92", "$12,701.19", "+6.4%") |
| delta | metric-delta-chip 11/500, color by sign — ok if ≥0, danger if <0, ink-muted if flat; margin-top 4px; "+12% from last month" |
| sparkline (optional) | tiny inline area/line, ~32px tall, 1.5px link line, no axes — bottom of the tile |
Dimensions: padding 16px 20px · grid span 4.
<Widget title="" span={4} control={<PeriodPill label="This month" />}> <div className="text-[12px] font-medium text-[#a1a1aa]">Total profit / loss</div> <div className="mt-1 text-[26px] font-bold tabular-nums text-[#f9fafb]">+$2,156.92</div> <div className={`mt-1 text-[11px] font-medium ${delta >= 0 ? 'text-[#22c55e]' : 'text-[#ef4444]'}`}> {delta >= 0 ? '+' : ''}{delta}% from last month </div> </Widget>
area-chart-card app
Single-series trend widget (equity curve, balance over time). The signature data viz: blue gradient fill under a 2px blue line.
| Slot | Token |
|---|---|
| root | → widget-container; header carries the period-selector pill; padding 20/20 |
| body | Recharts/visx AreaChart, min-height 220–260px, fills width |
| area | fill: url(#brandGradient) — linear-gradient(180deg, rgba(37,99,235,0.18) → rgba(37,99,235,0)); stroke: #3b82f6; strokeWidth: 2; type: monotone |
| dots | none by default; active dot on hover only — r:4, fill #3b82f6, stroke #18181b strokeWidth 2 |
| grid | none, or horizontal-only at rgba(255,255,255,0.04), no vertical lines |
| axes | x: month/period labels, axis-tick 11/ink-muted, no axis line, no tick line. y: optional, same treatment. Hide one axis if the chart is small. |
| tooltip | → dataViz.tooltip |
Dimensions: grid span 8 (alt 6) · body min-height 240px.
<Widget title="Equity curve" span={8} control={<PeriodPill label="Last 6 months" />}> <div className="h-[240px]"> <ResponsiveContainer> <AreaChart data={data} margin={{ top: 8, right: 8, bottom: 0, left: 0 }}> <defs> <linearGradient id="brandGradient" x1="0" y1="0" x2="0" y2="1"> <stop offset="0%" stopColor="#2563eb" stopOpacity={0.18} /> <stop offset="100%" stopColor="#2563eb" stopOpacity={0} /> </linearGradient> </defs> <XAxis dataKey="month" tick={{ fill: '#a1a1aa', fontSize: 11 }} axisLine={false} tickLine={false} /> <YAxis hide /> <Tooltip cursor={{ stroke: 'rgba(255,255,255,0.1)' }} content={<DarkTooltip />} /> <Area type="monotone" dataKey="equity" stroke="#3b82f6" strokeWidth={2} fill="url(#brandGradient)" dot={false} activeDot={{ r: 4, fill: '#3b82f6', stroke: '#18181b', strokeWidth: 2 }} /> </AreaChart> </ResponsiveContainer> </div> </Widget>
bar-chart-card app
Categorical/periodic widget — e.g. monthly returns. Multi-color bars rotating through the chart-series palette (blue → green → orange → purple → pink → yellow, then wrap). Bars radius:[4,4,0,0] (rounded top only), category gap ~24–32%.
| Slot | Token |
|---|---|
| root | → widget-container; header carries title + (often) a subtitle + period pill |
| body | Recharts BarChart, min-height 220–260px |
| bars | vertical bars; each takes the next color in chartSeries.order; radius [4,4,0,0]; category gap ~24–32% |
| axes | x: category labels (Jan, Feb…) axis-tick 11/ink-muted, no line. y: value labels with %/units, same treatment, no line; faint horizontal gridlines rgba(255,255,255,0.04) optional |
| tooltip | → dataViz.tooltip |
| legend (optional) | only if categories aren't self-explanatory; top-right or below, 11px, colored dot + label |
Dimensions: grid span 6 · body min-height 240px.
<Widget title="Monthly returns" subtitle="Net return per month" span={6} control={<PeriodPill label="Last 6 months" />}> <div className="h-[240px]"> <ResponsiveContainer> <BarChart data={data}> <CartesianGrid stroke="rgba(255,255,255,0.04)" vertical={false} /> <XAxis dataKey="month" tick={{ fill: '#a1a1aa', fontSize: 11 }} axisLine={false} tickLine={false} /> <YAxis tick={{ fill: '#a1a1aa', fontSize: 11 }} axisLine={false} tickLine={false} unit="%" /> <Tooltip cursor={{ fill: 'rgba(255,255,255,0.04)' }} content={<DarkTooltip />} /> <Bar dataKey="return" radius={[4, 4, 0, 0]}> {data.map((_, i) => <Cell key={i} fill={['#3b82f6','#22c55e','#f97316','#a855f7','#ec4899','#facc15'][i % 6]} />)} </Bar> </BarChart> </ResponsiveContainer> </div> </Widget>
progress-tracker-card app
Full-width list of labelled progress rows (e.g. "Certificate progress tracker": Phase 1 target 100%, Phase 2 target 75%). Each row: label, bar, %, status glyph. span-12 typically.
| Slot | Token |
|---|---|
| root | → widget-container; header has title + subtitle + period pill |
| row | grid/flex row, items-center, gap 16px, padding-y 12px, optional divider-soft hairline between rows |
| row.label | 14/500 ink-2 ("Phase 1 target (10%)") |
| row.bar | progress bar — track surface-hover #27272a, height 6px, radius pill; fill brand #2563eb (or link #3b82f6); becomes ok #22c55e at 100%; the track flexes to fill available width |
| row.value | right-aligned, 12/500 ink ("100%", "75%"), tabular figures |
| row.status | trailing glyph — ok CheckCircle when complete; a brand/link badge with a small check when in progress; ink-faint Circle when not started |
Dimensions: grid span 12 · row height ≈40px · bar height 6px.
<Widget title="Certificate progress tracker" subtitle="Payment status overview for the last 6 months" span={12} control={<PeriodPill label="Last 6 months" />}> <ul className="divide-y divide-white/5"> {rows.map(r => ( <li key={r.id} className="flex items-center gap-4 py-3"> <span className="w-48 shrink-0 text-[14px] font-medium text-[#e4e4e7]">{r.label}</span> <div className="h-1.5 flex-1 rounded-full bg-[#27272a]"> <div className="h-full rounded-full" style={{ width: `${r.pct}%`, background: r.pct >= 100 ? '#22c55e' : '#2563eb' }} /> </div> <span className="w-12 shrink-0 text-right text-[12px] font-medium tabular-nums text-[#f9fafb]">{r.pct}%</span> {r.pct >= 100 ? <CheckCircle className="size-4 text-[#22c55e]" /> : <Circle className="size-4 text-[#71717a]" />} </li> ))} </ul> </Widget>
list-table-card app
Tabular widget — recent trades, open positions, transactions. Header row + data rows with subtle dividers; hover lift on rows; status pills inline. Control slot often a filter dropdown instead of a period pill; optional footer link "View all →".
| Slot | Token |
|---|---|
| header-row | table-header 12/600 uppercase ink-muted, padding-y 8px, bottom border divider #1f2937 |
| data-row | table-cell 14/400 ink-2, padding-y 12px, bottom border divider-soft; numeric columns right-aligned + tabular figures; status column uses badge-pill |
| row-hover | bg → surface-hover #27272a, 120ms |
| empty-row | single full-width cell → empty-state inline |
Dimensions: grid span 6 (alt 12) · row height ≈44px.
<Widget title="Recent trades" span={6} control={<FilterDropdown />} footer={<a className="text-[14px] font-medium text-[#2563eb] hover:text-[#3b82f6]">View all →</a>}> <table className="w-full text-left"> <thead> <tr className="border-b border-[#1f2937]"> {['Symbol','Type','Size','P/L','Status'].map(h => <th key={h} className="py-2 text-[12px] font-semibold uppercase tracking-wider text-[#a1a1aa]">{h}</th>)} </tr> </thead> <tbody> {rows.map(r => ( <tr key={r.id} className="border-b border-white/5 transition-colors duration-[120ms] hover:bg-[#27272a]"> <td className="py-3 text-[14px] text-[#e4e4e7]">{r.symbol}</td> <td className="py-3 text-[14px] text-[#e4e4e7]">{r.side}</td> <td className="py-3 text-right text-[14px] tabular-nums text-[#e4e4e7]">{r.size}</td> <td className={`py-3 text-right text-[14px] tabular-nums ${r.pnl >= 0 ? 'text-[#22c55e]' : 'text-[#ef4444]'}`}>{r.pnl}</td> <td className="py-3"><Badge tone={r.status === 'open' ? 'info' : 'ok'}>{r.status}</Badge></td> </tr> ))} </tbody> </table> </Widget>
status-card app
Single-fact widget — the state of one thing (bot running / paused / error; broker connected). A big colored dot + a label + a supporting line. span-4; usually no control, maybe a kebab.
| Slot | Token |
|---|---|
| indicator | 8–10px dot, ok (running/online) / warn (paused/degraded) / danger (error/offline) / ink-faint (idle); optionally with a soft halo <tone>-soft |
| status-text | widget-title 17/600 ink ("Running", "Paused") |
| supporting | small 14/400 ink-muted ("3 bots active · last trade 4m ago") |
| action (optional) | a small ghost button or link-arrow ("Pause", "Reconnect") |
Dimensions: grid span 4 · padding 16px 20px.
<Widget title="" span={4}> <div className="flex items-center gap-2"> <span className="size-2.5 rounded-full bg-[#22c55e] shadow-[0_0_0_4px_rgba(34,197,94,0.12)]" /> <span className="text-[17px] font-semibold text-[#f9fafb]">Running</span> </div> <p className="mt-1 text-[14px] text-[#a1a1aa]">3 bots active · last trade 4m ago</p> </Widget>
sidebar app
Fixed left navigation column for the dashboard. Wordmark + grouped nav items under uppercase section labels.
| Slot | Token |
|---|---|
| root | fixed, width 240px, height 100vh, bg page (#020617) or #0c0c0e, right border 1px rim, padding 28px 0, overflow-y auto |
| logo | padding 0 22px 22px, bottom hairline rim; the pixel wordmark in display-pixel-wordmark type (uppercase, 0.4em tracking), optionally a tiny ink-muted subtitle below |
| group | vertical stack; preceded by a sidebar-section-label (10/600 uppercase ink-faint, padding 16px 22px 6px) |
| nav-item | → sidebar-nav-item |
States: default; collapsed (responsive < md) → translateX(-100%) off-canvas, opens as a drawer over a scrim, 320ms standard, hamburger toggle; scrolled → internal scroll only, logo block can stay pinned.
sidebar — default + an active item + a hover item + an item with a count badge
<aside className="fixed inset-y-0 left-0 w-60 overflow-y-auto border-r border-white/[0.06] bg-[#020617] py-7"> <div className="border-b border-white/[0.06] px-[22px] pb-[22px]"> <span className="font-[var(--font-display)] text-base uppercase tracking-[0.4em] text-[#f9fafb]">QUANTBOT</span> </div> <nav className="mt-4"> <div className="px-[22px] pb-1.5 pt-4 text-[10px] font-semibold uppercase tracking-[0.08em] text-[#71717a]">Overview</div> <SidebarItem icon={LayoutDashboard} active>Dashboard</SidebarItem> <SidebarItem icon={Wallet}>Accounts</SidebarItem> </nav> </aside>
top-bar app
sticky top-0 z-50, height 64px, bg page (optionally page/85 + backdrop-blur-md), bottom border 1px divider-soft, padding 0 32px, flex items-center justify-between. Left: hamburger (< md only) + page title / breadcrumb. Right: a ghost pill button ("Contact support"), an icon button (Bell), an avatar/account menu trigger.
Dimensions: height 64 · padding-x 32.
<header className="sticky top-0 z-50 flex h-16 items-center justify-between border-b border-white/5 bg-[#020617] px-8"> <h1 className="text-[18px] font-semibold text-[#f9fafb]">Account overview</h1> <div className="flex items-center gap-3"> <button className="rounded-md border border-white/10 px-3 py-1.5 text-[13px] font-medium text-[#e4e4e7] hover:bg-white/5">Contact support</button> <button className="rounded-md p-2 text-[#a1a1aa] hover:bg-white/5"><Bell className="size-5" /></button> <button className="size-8 rounded-full bg-[#27272a]" /> </div> </header>
tab-pill-switcher app
Segmented control — a track holding 2+ tabs; the active tab is a filled brand pill inside the track. Used for the login/register switcher and for in-page view toggles. Track: bg divider #1f2937 · radius md 8px · padding 4px. Tab: padding 8px 16px · radius sm 6px · 14px. Active: bg brand #2563eb · text white · weight 600. Inactive: transparent · text ink-muted (→ ink on hover) · weight 500.
<div className="grid grid-cols-2 rounded-md bg-[#1f2937] p-1"> <button className="rounded-sm bg-[#2563eb] px-4 py-2 text-[14px] font-semibold text-white">Iniciar sesión</button> <button className="rounded-sm px-4 py-2 text-[14px] font-medium text-[#a1a1aa] hover:text-[#f9fafb]">Crear cuenta</button> </div>
dropdown-panel app website
The floating menu surface used by the period-selector pill, filter dropdowns, account menu, kebab menus. Panel: bg surface #18181b · border 1px rim · radius md 8px · padding 4px · shadow 0 24px 64px rgba(0,0,0,.6) (the modal-grade shadow — popovers get it too) · min-width 160px. Item: flex items-center gap 8px · padding 8px 12px · radius sm 6px · 14px ink-2 · optional leading icon 16px ink-muted. Active-item: text → brand (or a brand/10 fill row); optional trailing Check brand. Separator: 1px divider-soft, margin 4px 0. Section-label: 10/600 uppercase ink-faint.
<div className="min-w-40 rounded-md border border-white/[0.06] bg-[#18181b] p-1 shadow-[0_24px_64px_rgba(0,0,0,0.6)]"> {options.map(o => ( <button key={o.id} className={`flex w-full items-center justify-between rounded-sm px-3 py-2 text-[14px] hover:bg-[#27272a] ${o.id === value ? 'text-[#2563eb]' : 'text-[#e4e4e7]'}`}> {o.label}{o.id === value && <Check className="size-4 text-[#2563eb]" />} </button> ))} </div>
modal app website
Centered dialog over a scrim. Scrim: fixed inset-0, bg rgba(0,0,0,.7), z-40. Dialog: centered, bg surface-2/surface (#101012 / #18181b) · border 1px rim · radius lg 12px · padding 24px · shadow 0 24px 64px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.06) · width ~320–480px. Close: top-right X, 16px, ink-faint, hover ink. Title: 16/500 (or section-h3 24/600 for larger). Actions: right-aligned row — a ghost (cancel) + a primary/primary-lg (confirm); destructive confirm uses destructive. Entering: scrim fades 0→0.7; dialog opacity 0→1 + translateY +10px→0, 320ms emphasized. Exiting: reverse, faster ~200ms standard. Focus trapped; Esc closes; body scroll locked.
<div className="fixed inset-0 z-40 grid place-items-center bg-black/70"> <div className="relative w-80 rounded-[12px] border border-white/8 bg-[#101012] p-6 text-center shadow-[0_24px_64px_rgba(0,0,0,0.6)]"> <button className="absolute right-4 top-4 text-[#71717a] hover:text-[#f9fafb]"><X className="size-4" /></button> <h2 className="text-[16px] font-medium text-[#f9fafb]">Solicítalos ya</h2> <button className="mt-4 w-full rounded-md bg-[#2563eb] py-3 text-[16px] font-semibold text-white hover:bg-[#1d4ed8]">Solicitar</button> </div> </div>
toast app website
Transient notification — bottom-right (or top-right), auto-dismiss, dark with a status-colored accent. Container: fixed, z-60, stacking from bottom-right (gap 12px). Toast: bg surface #18181b · border 1px rim · radius md 8px · padding 12px 16px · shadow 0 24px 64px rgba(0,0,0,.6) · max-width ~360px · flex gap 12px. Icon: 16–20px status icon. Text: title body 14/600 ink + optional description 13/400 ink-muted. Accent: a 3px left border in the status color, OR an accent on the icon only. Entering: slide in from the right + fade, 200ms standard. Shown 4–6s; pauses on hover. Stacked: older toasts shift up; max ~3 visible.
<div className="flex max-w-sm items-start gap-3 rounded-md border border-white/[0.06] bg-[#18181b] p-3 pr-4 shadow-[0_24px_64px_rgba(0,0,0,0.6)]"> <CheckCircle className="mt-0.5 size-5 text-[#22c55e]" /> <div> <p className="text-[14px] font-semibold text-[#f9fafb]">Settings saved</p> <p className="text-[13px] text-[#a1a1aa]">Your bot configuration was updated.</p> </div> </div>
button app website
One filled brand variant (two sizes), one ghost variant, one link-arrow, one icon button, one destructive. Radius 8px (md) on all. Min touch target 44px on mobile, 36–40px desktop. The full variant × state matrix below.
| Variant | Spec |
|---|---|
| primary | bg brand #2563eb · text white · padding 10px 20px · radius md 8px · type button-md 14/500 · min-height 40px (44 mobile). hover → brand-hover #1d4ed8 (200ms); active → brand-pressed #1e40af + inset top highlight; focus-visible → outline 2px brand offset 2px (or ring-2 brand/40); disabled → brand@40% (or #27272a), text ink-disabled; loading → 16px spinner (the ONE place a small inline spinner is allowed), width preserved, disabled |
| primary-lg | as primary but padding 14px 28px · type button-lg 16/600 · min-height 48px. Use: modal CTA, VPS "Contratar ahora", hero CTA |
| ghost | bg transparent · text ink · border 1px input-border (rgba(255,255,255,.15)) · padding 10px 20px · radius md 8px. hover → border rim-strong 20–30% + bg white/4; active → bg white/8; focus-visible → ring-2 brand/40; disabled → opacity 0.4. (App variant uses a 10% border + ink-2 text.) |
| link-arrow | no bg/border · text brand #2563eb (→ link #3b82f6 on hover) · 14/500 · inline-flex gap 4px · trailing literal "→". hover → text link/blue-400; the "→" nudges 2px right (translateX, 120ms) — the one tiny transform allowed |
| icon | square, 36–40px · radius md 8px · transparent bg · ink-muted icon at 18–20px. hover → bg white/5 + icon ink; active → bg white/8; focus-visible → ring-2 brand/40; disabled → opacity 0.4; active-toggle → bg brand/15 + icon brand |
| destructive | bg danger #ef4444 · text white · otherwise identical to primary; OR ghost-style with text/border danger for low-emphasis. hover → ~red-600 #dc2626; active → red-700 #b91c1c; focus-visible → ring-2 danger/40; disabled → opacity 0.4 |
// primary <button className="inline-flex h-10 items-center justify-center rounded-md bg-[#2563eb] px-5 text-[14px] font-medium text-white transition-colors duration-200 hover:bg-[#1d4ed8] active:bg-[#1e40af] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#2563eb] disabled:opacity-40">Iniciar sesión</button> // ghost <button className="inline-flex h-10 items-center justify-center rounded-md border border-white/15 px-5 text-[14px] font-medium text-[#f9fafb] transition-colors hover:border-white/30 hover:bg-white/5">Cancel</button> // link-arrow <a className="group inline-flex items-center gap-1 text-[14px] font-medium text-[#2563eb] hover:text-[#3b82f6]">Saber más <span className="transition-transform duration-[120ms] group-hover:translate-x-0.5">→</span></a>
input app website
Text field. Marketing variant: surface-2 bg, 15%-white border. Dashboard variant: surface-overlay (5% white) bg, 8%-white border. Both: 8px radius, 12/16 padding, 44px min-height, brand focus border + soft ring. Label 14/500 ink-2 + 8px margin-bottom; placeholder ink-disabled; optional leading icon (Lucide 16px ink-muted, search inputs); optional trailing affordance (password eye / clear X); hint/error 12px (hint ink-muted, error danger) + 6px margin-top.
| State | Delta |
|---|---|
| default | border 15%/8% white, bg as above |
| hover | border → +5% (e.g. 15% → 20% white) |
| focus | border-color → brand #2563eb + box-shadow 0 0 0 3px rgba(37,99,235,.18) (soft brand ring); 200ms |
| filled | text ink; placeholder gone |
| error | border-color → danger #ef4444; ring rgba(239,68,68,.18); hint slot shows danger message + optional AlertTriangle |
| disabled | opacity 0.5, bg slightly flatter, no caret, cursor not-allowed |
| readonly | border softened to divider, no focus ring, text ink-2 |
// dashboard input <label className="block text-[14px] font-medium text-[#e4e4e7]">Email</label> <input type="email" placeholder="tucorreo@ejemplo.com" className="mt-2 h-11 w-full rounded-md border border-white/8 bg-white/5 px-4 text-[14px] text-[#f9fafb] placeholder:text-[#52525b] outline-none transition-colors focus:border-[#2563eb] focus:ring-[3px] focus:ring-[#2563eb]/20" />
badge-pill app website
Small status/category pill. Soft-tinted background + matching text color, pill radius. inline-flex items-center gap 4px · padding 4px 10px · radius pill 9999px · type badge 12/500. States: static (default — badges are usually non-interactive); removable (+ trailing X 12px; X hover → text darkens); as-filter selected (solid brand bg + white text when used as a toggle chip).
<span className="inline-flex items-center gap-1 rounded-full bg-[#ef4444]/15 px-2.5 py-1 text-[12px] font-medium text-[#ef4444]">Riesgo alto</span>
skeleton app website
Loading placeholder. A pulsing zinc-800 block shaped like the eventual content. NEVER a spinner (except the tiny inline one inside a loading button). Block: bg surface-hover #27272a · radius matching the real element (text → sm or full pill, chart → md) · animate-pulse (opacity 1↔0.5, ~1.5s) · explicit width/height matching the target. Resolved: skeleton unmounts, real content fades in over ≤120ms; layout must not shift. Reduced-motion → static 0.6 opacity, no pulse.
<div className="space-y-2" aria-hidden> <div className="h-3 w-24 animate-pulse rounded bg-[#27272a]" /> <div className="h-7 w-32 animate-pulse rounded bg-[#27272a]" /> <div className="h-3 w-36 animate-pulse rounded bg-[#27272a]" /> </div>
empty-state app website
What a widget/page shows when there's genuinely no data. Always designed, never blank, never raw "null". Wrap: centered column, padding ~32px, max-width ~280px, text-center. Icon: a relevant Lucide glyph at ~32px, ink-faint, often inside a surface-hover circle. Title: body 14–16/600 ink-2 — what's missing. Supporting: small 13–14/400 ink-muted — ONE line on what happens next. CTA (optional): a ghost small button or a link-arrow.
<div className="mx-auto flex max-w-[280px] flex-col items-center gap-2 py-8 text-center"> <div className="grid size-12 place-items-center rounded-full bg-[#27272a]"><Inbox className="size-5 text-[#71717a]" /></div> <p className="text-[14px] font-semibold text-[#e4e4e7]">No trades yet</p> <p className="text-[13px] text-[#a1a1aa]">They'll appear here once your bots execute their first orders.</p> <a className="mt-2 text-[14px] font-medium text-[#2563eb] hover:text-[#3b82f6]">Connect a broker →</a> </div>
service-card website
bg surface-2 #101012 · border 1px rim · radius lg 12px · padding 32px. icon-tile: 48px square · bg brand-soft rgba(37,99,235,.18) · radius md 8px · centered Lucide icon brand/ink. title: section-h2/section-h3 24–30/600–700 ink, margin-top 20px. body: body 16/400 ink-2, margin-top 12px. link: link-arrow "Saber más →" in brand, margin-top 20px. States: default (rim 6%, no shadow); hover (border → rim-strong 20% white; translateY -2px — the rare allowed lift; 200ms — restrained, not bouncy); focus-within (ring-2 brand/20).
Gestión de cuenta real
Operamos tu cuenta con estrategias algorítmicas verificadas y control de riesgo continuo.
Saber más →Bots automatizados
Despliega estrategias preconfiguradas en minutos, con backtests y métricas transparentes.
Saber más →Infraestructura VPS
Servidores de baja latencia para que tus algoritmos operen sin interrupciones.
Saber más →<article className="rounded-[12px] border border-white/[0.06] bg-[#101012] p-8 transition-colors hover:border-white/20"> <div className="grid size-12 place-items-center rounded-md bg-[#2563eb]/[0.18]"><LineChart className="size-6 text-[#3b82f6]" /></div> <h3 className="mt-5 text-[24px] font-semibold text-[#fafafa]">Gestión de cuenta real</h3> <p className="mt-3 text-[16px] text-[#e4e4e7]">…</p> <a className="mt-5 inline-flex items-center gap-1 text-[14px] font-medium text-[#2563eb]">Saber más →</a> </article>
comparison-table website
Two-product comparison (this product vs a generic provider). 3-col grid: feature label | highlighted column (brand-tinted, green checks) | neutral column (muted, red crosses). Root: bg surface-3 #131313 · radius lg 12px · overflow hidden. Row: grid grid-cols-3 · padding 16px 24px · bottom border 1px divider-soft. label-cell: left column, weight 600 ink. highlighted-cell: bg brand/8 rgba(37,99,235,.08) · a ok Check (or value text). neutral-cell: text ink-muted · a danger X (or "—"). header-row: the two product names as column headers; the highlighted product gets the brand tint + maybe a "Recomendado" badge. Optional row:hover → entire row bg lifts to white/2.
<div className="overflow-hidden rounded-[12px] bg-[#131313]"> {rows.map(r => ( <div key={r.id} className="grid grid-cols-3 border-b border-white/5 px-6 py-4 text-[14px]"> <span className="font-semibold text-[#fafafa]">{r.label}</span> <span className="bg-[#2563eb]/[0.08] -mx-6 px-6 flex items-center text-[#22c55e]"><Check className="size-4" /></span> <span className="text-[#a1a1aa] flex items-center"><X className="size-4 text-[#ef4444]" /></span> </div> ))} </div>
faq-accordion website
Row: full-width · padding 20px 24px · bottom border 1px rim. Question: flex justify-between · body 16/500 ink · trailing ChevronDown ink-muted. Answer: small 14/400 ink-muted line-height 1.6 · padding-top 12px · revealed on open. States: collapsed (answer hidden, max-height 0; chevron points down); hover (row bg → white/3); expanded (answer expands via max-height transition, 200ms standard; chevron rotates 180°); focus-visible (ring-2 brand/20 on the question button).
<div className="border-b border-white/[0.06]"> <button className="flex w-full items-center justify-between px-6 py-5 text-left text-[16px] font-medium text-[#fafafa] hover:bg-white/[0.03]"> ¿Qué necesito para empezar? <ChevronDown className="size-5 text-[#a1a1aa] transition-transform data-[open]:rotate-180" /> </button> <div className="px-6 pb-5 text-[14px] leading-relaxed text-[#a1a1aa]">…</div> </div>
testimonial-card website
bg surface-2 #101012 · border 1px rim · radius lg 12px · padding 32px. stars: 5× star, warn #facc15, ~16px. quote: section-h3-ish 20/500 ink, margin-top 16px. author: small 14/400 ink-muted, margin-top 16px. States: default (static; optional very subtle hover border brighten in a carousel).
Los informes mensuales son claros y los resultados, consistentes. Es lo más cerca que he estado de "configúralo y olvídate".
La latencia del VPS marca la diferencia. Mis estrategias entran cuando deben.
<figure className="rounded-[12px] border border-white/[0.06] bg-[#101012] p-8"> <div className="flex gap-0.5 text-[#facc15]">{Array.from({length:5}).map((_,i)=><Star key={i} className="size-4 fill-current"/>)}</div> <blockquote className="mt-4 text-[20px] font-medium text-[#fafafa]">…</blockquote> <figcaption className="mt-4 text-[14px] text-[#a1a1aa]">— Nombre Apellido</figcaption> </figure>
sticky-cta-banner website
bg surface-2 #101012 · border 1px rim · radius xl 16px · padding 24px 32px · flex (column on mobile, row on desktop) justify-between items-center gap 16px. text: title section-h3 24/600 ink + description small ink-muted. actions: a ghost + a primary/primary-lg blue button. States: default (static block above the footer).
<div className="flex flex-col items-start justify-between gap-4 rounded-[16px] border border-white/[0.06] bg-[#101012] p-6 lg:flex-row lg:items-center lg:p-8"> <div><h3 className="text-[24px] font-semibold text-[#fafafa]">¿Listo para empezar?</h3><p className="mt-1 text-[14px] text-[#a1a1aa]">…</p></div> <div className="flex gap-3"> <button className="rounded-md border border-white/15 px-5 py-2.5 text-[14px] font-medium text-[#fafafa] hover:bg-white/5">Saber más</button> <button className="rounded-md bg-[#2563eb] px-7 py-3 text-[16px] font-semibold text-white hover:bg-[#1d4ed8]">Reservar llamada</button> </div> </div>
lead-capture-modal website
Persistent (non-blocking) dark popup absolutely positioned over the hero on every marketing page — captures a results-data email lead. Not a true modal (no scrim, page is usable). Card: width 320px · bg surface-2 #101012 · border 1px rim (8% white) · radius lg 12px · padding 24px · shadow 0 24px 64px rgba(0,0,0,.6) · text-center. close: top-right X 16px ink-faint. title: 16/500 centered ink. preview: an inline mini dashboard-chart card (dark) as a teaser. field+cta: email input + full-width primary blue "Solicitar" / "Solicítalos ya" button (8px radius). States: shown (fades in + slides down ~10px, 320ms emphasized, ~1–2s after load); dismissed (fades out; remembers dismissal so it doesn't re-nag).
Gestión algorítmica de capital, sin complicaciones.
Resultados verificados, control de riesgo y un panel claro. Tú decides cuánto destinar.
// see `modal` codeAnchor — same card, but rendered absolutely over the hero with no scrim and a dismissable state <div className="absolute right-8 bottom-8 z-40 w-80 rounded-[12px] border border-white/8 bg-[#101012] p-6 text-center shadow-[0_24px_64px_rgba(0,0,0,0.6)]">…</div>
Composition recipes
Five full-page layouts as data. The "Account overview" recipe is also rendered as a miniature-but-real assembled dashboard — the centerpiece showing what the system looks like put together.
1 · Account overview (dashboard home) app
Shell: sidebar (240px) + top-bar ("Account overview" + "Contact support" ghost pill + bell + avatar) + content (padding 32px) holding a 12-col / 16px-gap grid. Row 1 — three stat-tiles @ span-4. Row 2 — area-chart-card @ span-8 + status-card @ span-4. Row 3 — bar-chart-card @ span-6 + list-table-card @ span-6 (filter dropdown, footer "View all →"). Row 4 — progress-tracker-card @ span-12. Responsive: md → stat-tiles 2-up then 1, charts full-width-of-row, status-card full width, tracker full width. sm → single column, source order, charts keep min-height.
2 · Settings page app
Shell: sidebar + top-bar ("Settings") + content with a max-width ~720px reading column (NOT the full-bleed grid). Section "Profile" — a card (widget chrome) with label+input pairs (Name, Email), a Save primary button bottom-right. Section "Notifications" — a card with rows of label + description + toggle (use a real switch in brand when on, not the tab-pill styling). Section "Danger zone" — a card with a 1px danger/30 border, a "Delete account" destructive (ghost-style) button, and a confirmation modal. Responsive: always single column; cards stack with 24px gap.
3 · Login / register (the source's hero) app
Shell: no sidebar — full-page grid grid-cols-1 lg:grid-cols-2 bg-page text-ink min-h-screen. Left half: a tilted MacBook product shot (frame #3f3f46, screen-shadow 0 32px 64px rgba(0,0,0,.6)) showing the dashboard overview — i.e. the dashboard IS the marketing visual. Right half: a centered ~440px form column — pixel wordmark → 30px "Inicia sesión en tu cuenta" → supporting line → tab-pill-switcher (Iniciar sesión / Crear cuenta) → label+input (Email) → label+input (Contraseña, with eye toggle) → "Mantener sesión iniciada" checkbox + "¿Olvidaste tu contraseña?" link → full-width primary "Iniciar sesión" → "OR" divider → full-width ghost "Iniciar con Google".
4 · Marketing home website
top-nav-marketing (sticky, blurred) → Hero (eyebrow chip "Desde 2023" → 48px headline → 18px subtitle → primary-lg blue CTA → glowing 3D globe behind → lead-capture-modal floating over it) → Trust strip (broker partner logos on dark, low contrast) → Services (3× service-card @ ~1/3 width) → Globe / global-reach section (full-bleed dark, glowing globe, "Alcance global" headline, a country count) → Platform compatibility (MT4 / MT5 / cTrader logo grid + a dashboard preview on a blurred dark surface) → Testimonials (a row of testimonial-cards) → sticky-cta-banner ("Reservar llamada" prompt) → footer (4 columns of links + a newsletter input+button group in the last column). Responsive: card grids 3→2→1; footer columns stack; nav links → hamburger drawer.
5 · VPS landing (single-offer page) website
Hero (centered "40 USD/mes" price headline + a primary-lg "Contratar ahora" CTA + supporting copy) → "Beneficios destacados" (3-col grid of vps-feature tiles, bg surface-3 #131313, blue Lucide icons) → "Características técnicas" (2-col bulleted spec lists with blue check glyphs) → comparison-table ("this VPS" vs "generic provider" — highlighted column brand-tinted with green checks, neutral column muted with red crosses) → sticky-cta-banner + footer. Responsive: grids collapse 3→1; comparison table stays 3-col but tightens padding.
Data viz
Library: Recharts (SVG, themeable) — or visx for more control. Whatever it is, it must render flat (no default drop-shadows, no default gridline soup) and theme to the tokens. Charts live inside a widget-container — the card chrome (rim, header, period pill) is not the chart library's job. One series → brand blue; multiple → the chartSeries.order rotation (data colors, never reused as UI chrome). Minimal axes: no axis lines, no tick lines, ticks in axis-tick type (11px ink-muted). Hide an axis entirely on small charts. Gridlines: none, or horizontal-only at rgba(255,255,255,0.04). Never vertical gridlines. No data dots at rest; active dot on hover only.
Specs
Tooltip + chart states
Voice & copy
Plain, confident, low-jargon. The source is Spanish; copy is short, direct, slightly formal-friendly ("Inicia sesión", "Reservar llamada"). No exclamation marks, no hype words. Numbers and outcomes do the talking.
| Aspect | Rule |
|---|---|
| headings | Sentence case ("Inicia sesión en tu cuenta", "Gestión de cuenta real"). |
| buttons | Sentence case ("Iniciar sesión", "Reservar llamada", "Solicitar", "Suscribirse"). Never ALL CAPS, never Title Case. |
| labels / form fields | Sentence case ("Email", "Contraseña" — single-word labels capitalized). |
| nav items | Sentence case. |
| section eyebrows / sidebar section labels | Short; the sidebar section labels render UPPERCASE via CSS but are authored sentence case. Eyebrows ≤3 words ("Beneficios destacados"). |
| table headers | Render UPPERCASE via CSS; authored normally. |
| currency | $12,701.19 — symbol prefix, comma thousands, 2 decimals. (Pricing also uses "USD" suffix: "40 USD/mes".) |
| percent | Performance figures show 1 decimal ("+6.4%"); round percentages show 0 ("75%", "100%"). Deltas ALWAYS carry an explicit sign: "+12%", "-2%". |
| deltaChip | Pattern: "<sign><value>% from last month". NB: the source literally renders "form last month" — that is a typo on the source site; use the correct "from last month" in new builds. |
| dates / ranges | Prefer relative ranges in selectors and captions: "Last 6 months", "Last 30 days", "YTD". Absolute dates short: "Apr 12", "Apr 12, 2026". |
| largeNumbers | Comma-grouped; no "k"/"M" abbreviation inside stat tiles (full number reads as more credible). Abbreviation OK in dense table cells if space-constrained. |
| tabularFigures | Use tabular/monospaced figures (tabular-nums) anywhere numbers stack or align — metric values, table numeric columns, axis ticks, progress percentages. |
Never show "undefined", "null", "NaN", "0" where there should be a dash — use "—" in ink-muted. Loading states use skeletons, not the word "Loading…". Buttons say what they do ("Iniciar sesión", not "Submit"/"OK"). Links-as-action use a trailing "→". Destructive confirmations name the consequence: "Delete account" / "This permanently removes your data and bots."
Anti-patterns
The do-not list. Violating any of these breaks the system's identity even if individual screens "look fine".
Don't add drop shadows to cards/widgets
Depth = a 1px white-at-6% rim on a one-step-darker surface. (Modals and dropdown panels are the only things that get a real shadow.)
Don't scale() / translateY on hover (dashboard)
Hover = a background-color lift only (surface → surface-hover, 120ms). The marketing service-card's subtle -2px lift is the lone exception — don't generalize it.
Don't use a centered spinner inside a card/page region
Use skeletons. The only allowed spinner is a 16px inline one inside a loading button.
Don't use pure black #000 for any surface
The dashboard page is #020617; cards are #18181b; the marketing page is #09090b.
Don't introduce a second accent hue
#2563eb (blue-600) is the entire accent system. A "success-green button", a "purple secondary CTA", a "teal highlight" all break it. Chart series colors are data only — never restyle a button/nav/badge with them.
Don't invent radii / split the difference to 10px arbitrarily
Use 6 / 8 / 12 / 16 / pill. (10px is allowed only when matching the source portal exactly — then be consistent about it.) Don't create hierarchy by bolding body text — use the type ramp + the ink/ink-2/ink-muted color steps. Don't put a border AND a shadow on the same surface (other than the modal, which is intentionally shadow + rim). Don't use Tailwind's default blue-500 for primary actions — primary is blue-600 #2563eb (blue-500 is reserved for inline text links and "info"). Don't use Inter for the dashboard portal body text — that surface uses the ui-sans-serif system stack.
Re-skin contract
This system re-skins by swapping exactly two things: the brand accent and the display (wordmark) font. Everything structural — the cool-near-black surface ramp, radii, motion, rims, the chart-series rotation — stays put. After swapping, re-check the contrast pairs below.
Swappable
Do NOT swap
- The surface ramp (page #020617 / #09090b, surface #18181b, surface-hover #27272a, the white-alpha rims) — tuned for dark-first rim contrast; changing it breaks the no-shadow depth model.
- Radii set (6 / 8 / 12 / 16 / pill).
- Motion (easing + the 120/200/320ms tiers).
- Chart-series rotation order (blue → green → orange → purple → pink → yellow).
- Status colors (ok #22c55e / warn #facc15 / danger #ef4444) — they carry universal meaning.
Swap instructions: (1) Replace the five brand-family tokens per the rule. (2) Optionally replace font-display. (3) Re-run the white-on-brand and new-brand-as-text (link) checks. (4) Leave surfaces, radii, motion, rims, status colors, chart series untouched. (5) If the new brand is light/low-contrast, darken brand-pressed more aggressively and keep button text white.
Known-AA contrast table
| Pair | Ratio | Verdict | Live sample |
|---|---|---|---|
ink #f9fafb on page #020617 | ≈ 19:1 | AAA — any size | The quick brown fox |
ink #f9fafb on surface #18181b | ≈ 16:1 | AAA — any size | The quick brown fox |
ink-2 #e4e4e7 on surface #18181b | ≈ 13:1 | AAA — use for dense body copy | The quick brown fox |
ink-muted #a1a1aa on surface #18181b | ≈ 6.4:1 | AA for normal text, AAA for large — fine for labels/captions/ticks; for paragraphs below ~16px prefer ink-2 | The quick brown fox |
ink-faint #71717a on surface #18181b | ≈ 3.4:1 | Large text / non-text only (10px uppercase section labels borderline — acceptable as decoration-grade labels, not body) | SECTION LABEL |
ink-disabled #52525b on surface-overlay | < 3:1 | Placeholder/disabled only — never load-bearing text | placeholder text |
white #ffffff on brand #2563eb | ≈ 5.2:1 | AA — prefer ≥14px medium for button labels; AAA for ≥18px bold | Iniciar sesión |
ok #22c55e text on surface #18181b | ≈ 7.6:1 | AA at normal size — still saturated; reserve for short labels/icons, not paragraphs | +12% from last month |
danger #ef4444 text on surface #18181b | ≈ 4.4:1 | Just under AA for normal text — for body errors pair with an icon or bump to ≥18px / use a lighter red; fine for icons + short labels | -2% from last month |
link #3b82f6 text on surface #18181b | ≈ 5.0:1 | AA — good for inline links | Saber más → |
Stack mapping
Translations for the three target stacks so you skip a translation pass.
Tailwind v4 — @theme { … } block
App-scoped values shown; for the marketing surface swap --color-page to #09090b and use Inter. Tailwind v4's default palette already covers the zinc/blue scales — these are the brand-aware aliases.
@theme {
--color-page: #020617; /* slate-950 */
--color-page-marketing: #09090b; /* zinc-950 */
--color-surface: #18181b; /* zinc-900 */
--color-surface-2: #101012;
--color-surface-3: #131313;
--color-surface-hover: #27272a; /* zinc-800 */
--color-surface-overlay: rgba(255,255,255,0.05);
--color-rim: rgba(255,255,255,0.06);
--color-rim-hover: rgba(255,255,255,0.10);
--color-rim-strong: rgba(255,255,255,0.20);
--color-divider: #1f2937;
--color-divider-soft: rgba(255,255,255,0.05);
--color-ink: #f9fafb; /* gray-50 */
--color-ink-2: #e4e4e7;
--color-ink-muted: #a1a1aa;
--color-ink-faint: #71717a;
--color-ink-disabled: #52525b;
--color-brand: #2563eb; /* blue-600 */
--color-brand-hover: #1d4ed8;
--color-brand-pressed: #1e40af;
--color-brand-soft: rgba(37,99,235,0.18);
--color-link: #3b82f6;
--color-ok: #22c55e;
--color-warn: #facc15;
--color-danger: #ef4444;
--color-info: #3b82f6;
--radius-sm: 6px;
--radius-md: 8px;
--radius-widget: 12px;
--radius-lg: 12px;
--radius-xl: 16px;
--font-sans: ui-sans-serif, system-ui, sans-serif;
--font-sans-marketing: 'Inter', system-ui, -apple-system, sans-serif;
--font-display: 'Press Start 2P', 'VT323', ui-monospace, monospace;
--ease-standard: cubic-bezier(0.4,0,0.2,1);
--ease-emphasized: cubic-bezier(0.2,0,0,1);
--duration-quick: 120ms;
--duration-base: 200ms;
--duration-slow: 320ms;
--shadow-modal: 0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.06);
--shadow-rim: inset 0 0 0 1px rgba(255,255,255,0.06);
--shadow-blue-glow: 0 0 32px rgba(37,99,235,0.35);
}
Framework-neutral CSS custom properties
Same names as the Tailwind block above — put them under :root (and the marketing overrides under a [data-surface="marketing"] scope if you host both in one app). Reference as var(--color-surface) etc. Identical key/value pairs to the @theme block.
SwiftUI — for a native dashboard
Rims become .overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(Color.white.opacity(0.06))); never use .shadow() on cards (only on sheets/popovers). Hover-equivalents (selection/press) use .animation(.easeInOut(duration: 0.12), value:). Honor @Environment(\.accessibilityReduceMotion).
static let page = Color(red: 0.008, green: 0.024, blue: 0.090) // #020617 static let pageMarketing = Color(red: 0.035, green: 0.035, blue: 0.043) // #09090b static let surface = Color(red: 0.094, green: 0.094, blue: 0.106) // #18181b static let surfaceHover = Color(red: 0.153, green: 0.153, blue: 0.165) // #27272a static let divider = Color(red: 0.122, green: 0.161, blue: 0.216) // #1f2937 static let ink = Color(red: 0.976, green: 0.980, blue: 0.984) // #f9fafb static let ink2 = Color(red: 0.894, green: 0.894, blue: 0.906) // #e4e4e7 static let inkMuted = Color(red: 0.631, green: 0.631, blue: 0.667) // #a1a1aa static let inkFaint = Color(red: 0.443, green: 0.443, blue: 0.478) // #71717a static let brand = Color(red: 0.145, green: 0.388, blue: 0.922) // #2563eb static let brandHover = Color(red: 0.114, green: 0.306, blue: 0.847) // #1d4ed8 static let link = Color(red: 0.231, green: 0.510, blue: 0.965) // #3b82f6 static let ok = Color(red: 0.133, green: 0.773, blue: 0.369) // #22c55e static let warn = Color(red: 0.980, green: 0.800, blue: 0.082) // #facc15 static let danger = Color(red: 0.937, green: 0.267, blue: 0.267) // #ef4444 // Body / UI: Font.system(.body). widget-title → .system(size: 17, weight: .semibold). // metric-value → .system(size: 26, weight: .bold).monospacedDigit(). table-cell → .system(size: 14). axis-tick / caption → .system(size: 11). // Wordmark: a bundled pixel font (Press Start 2P / VT323) at ~20pt with wide tracking — wordmark only. extension View { func rim(_ r: CGFloat = 12) -> some View { self.overlay(RoundedRectangle(cornerRadius: r).strokeBorder(Color.white.opacity(0.06))) } } // card helper: surface bg + .cornerRadius(12) + .rim(12) — no .shadow().
react-grid-layout — for the editable dashboard
cols={{ lg: 12, md: 12, sm: 1 }}, rowHeight ~ 8px with margins, margin={[16,16]}, containerPadding={[32,32]}. Each item's child is a <Widget> (the component above) — react-grid-layout owns x/y/w/h only and must never inject styles onto the card. Default w per widget type: stat-tile w=4, area-chart w=8, bar-chart w=6, list-table w=6, progress-tracker w=12, status-card w=4. h: charts ~32 rows-of-8px (~256px), tiles ~14, tracker by content.
Tailwind reference
How to consume the exported tailwind block from the JSON. There are two scoped configs — app (dashboard portal, the top-level tailwind block) and website (marketing). Both share the brand + status + rim tokens; they differ on the page color (slate-950 vs zinc-950) and the body font (ui-sans-serif vs Inter). Drop the theme.extend values into your Tailwind config (or the @theme vars from Stack mapping above for v4), then build components from the componentRecipes map rather than reinventing class strings.
App config — theme.extend colors
| Token | Value | Maps to |
|---|---|---|
brand / brand-hover / brand-pressed | #2563eb / #1d4ed8 / #1e40af | Primary actions, hover, pressed |
brand-soft | rgba(37,99,235,0.18) | Icon tiles, active-nav tint, badge bg |
link / info | #3b82f6 | Inline text links, info dots |
page | #020617 | Dashboard page background (slate-950) |
surface / surface-2 / surface-hover | #18181b / #101012 / #27272a | Cards · modal body · hover lift |
surface-overlay | rgba(255,255,255,0.05) | Inset form fields / wells |
rim / rim-hover | rgba(255,255,255,0.06) / 0.10 | Card edges (the elevation primitive) |
divider / divider-soft | #1f2937 / rgba(255,255,255,0.05) | Table rows, tab track · faintest hairline |
ink / ink-2 / ink-muted / ink-faint / ink-disabled | #f9fafb / #e4e4e7 / #a1a1aa / #71717a / #52525b | Text scale (high → placeholder) |
ok / warn / danger | #22c55e / #facc15 / #ef4444 | Positive / caution / negative & destructive |
chart-1…chart-6 | #3b82f6 / #22c55e / #f97316 / #a855f7 / #ec4899 / #facc15 | Multi-series chart rotation (DATA only) |
Other theme.extend keys: fontFamily.sans = ui-sans-serif/system-ui/sans-serif · fontFamily.display = 'Press Start 2P'/VT323/ui-monospace · fontSize page-title 30/700/1.2 · widget-title 17/600/1.35 · metric 26/700/1.1 · table 14/1.5 · label 14/500/1.4 · caption 12/1.4 · tick 11/1 · borderRadius sm 6 / md 8 / lg 10 / widget 12 / xl 16 · boxShadow rim · popover (0 24 64 /.6) · modal (popover + rim) · blue-glow (0 0 32 /.35) · status-halo (0 0 0 4px rgba(34,197,94,.12)) · transitionDuration quick 120 / base 200 / slow 320 · transitionTimingFunction standard (cubic-bezier(.4,0,.2,1)) / emphasized (cubic-bezier(.2,0,0,1)) · screens sm 640 / md 768 / lg 1024 / xl 1280.
App config — componentRecipes
| Recipe | Classes |
|---|---|
appShell | min-h-screen bg-page text-ink-2 |
sidebar | fixed inset-y-0 left-0 w-60 overflow-y-auto border-r border-rim bg-page py-7 |
sidebarItem / sidebarItemActive | flex items-center gap-2 border-l-2 border-transparent px-[22px] py-[7px] text-[14px] font-medium text-white/70 transition-colors duration-quick hover:bg-white/[0.04] hover:text-ink · …border-l-2 border-brand bg-brand/[0.08] …text-ink |
topBar | sticky top-0 z-50 flex h-16 items-center justify-between border-b border-white/5 bg-page px-8 |
contentGrid | grid grid-cols-1 gap-4 p-8 md:grid-cols-2 lg:grid-cols-12 |
widget | rounded-[12px] border border-rim bg-surface p-5 transition-colors duration-quick focus-within:border-brand focus-within:ring-2 focus-within:ring-brand/20 |
widgetHeader / widgetTitle / widgetSubtitle / widgetBody | flex items-start justify-between gap-3 · text-[17px] font-semibold leading-tight text-ink · mt-0.5 text-[12px] text-ink-muted · mt-4 |
periodPill | inline-flex items-center gap-1.5 rounded-md border border-rim bg-white/[0.03] px-3 py-1.5 text-[11px] font-medium text-ink-muted transition-colors duration-quick hover:border-white/15 hover:text-ink-2 focus-visible:ring-2 focus-visible:ring-brand/20 |
statValue / statDeltaPositive / statDeltaNegative / statDeltaFlat | mt-1 text-[26px] font-bold tabular-nums text-ink · mt-1 text-[11px] font-medium text-ok / text-danger / text-ink-muted |
tableHeader / tableRow / tableCell / tableCellNum | py-2 text-left text-[12px] font-semibold uppercase tracking-wider text-ink-muted · border-b border-white/5 transition-colors duration-quick hover:bg-surface-hover · py-3 text-[14px] text-ink-2 · py-3 text-right text-[14px] tabular-nums text-ink-2 |
progressTrack / progressFill / progressFillDone | h-1.5 flex-1 rounded-full bg-surface-hover · h-full rounded-full bg-brand · h-full rounded-full bg-ok |
statusDot | size-2.5 rounded-full bg-ok shadow-status-halo |
tabTrack / tabActive / tabInactive | grid grid-cols-2 rounded-md bg-divider p-1 · rounded-sm bg-brand px-4 py-2 text-[14px] font-semibold text-white · rounded-sm px-4 py-2 text-[14px] font-medium text-ink-muted hover:text-ink |
inputApp | h-11 w-full rounded-md border border-white/8 bg-white/5 px-4 text-[14px] text-ink placeholder:text-ink-disabled outline-none transition-colors focus:border-brand focus:ring-[3px] focus:ring-brand/20 |
btnPrimary / btnPrimaryFull / btnGhost / googleBtn | inline-flex h-10 items-center justify-center rounded-md bg-brand px-5 text-[14px] font-medium text-white transition-colors duration-base hover:bg-brand-hover active:bg-brand-pressed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand disabled:opacity-40 · w-full rounded-md bg-brand py-3 text-[14px] font-semibold text-white hover:bg-brand-hover · …border border-white/10 …text-ink-2 hover:bg-white/5 · inline-flex w-full items-center justify-center gap-2 rounded-md border border-white/10 bg-transparent py-3 text-[14px] font-medium text-ink-2 hover:bg-white/5 |
dropdownPanel / dropdownItem / dropdownItemActive | min-w-40 rounded-md border border-rim bg-surface p-1 shadow-popover · flex w-full items-center justify-between rounded-sm px-3 py-2 text-[14px] text-ink-2 transition-colors duration-quick hover:bg-surface-hover · …text-brand |
badge / skeletonBlock / emptyState / toast | inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[12px] font-medium · animate-pulse rounded bg-surface-hover · mx-auto flex max-w-[280px] flex-col items-center gap-2 py-8 text-center · flex max-w-sm items-start gap-3 rounded-md border border-rim bg-surface p-3 pr-4 shadow-popover |
Website config — theme.extend + componentRecipes
Same brand/status/rim tokens; differs on page (#09090b zinc-950), surface (#101012 / #131313), font (Inter sans), and adds rim-strong (rgba(255,255,255,0.20)) plus marketing-only type sizes (display 48 / h1 36 / h2 30 / h3 24 / body-lg 18 / body 16 / small 14 / caption 12) and spacing (section-y 96 / -tablet 80 / -mobile 48 / nav 64).
| Recipe | Classes |
|---|---|
navTop | sticky top-0 z-50 flex h-16 items-center gap-7 border-b border-white/5 bg-page/85 px-8 backdrop-blur-md saturate-150 text-ink |
logoPixel | font-display text-base uppercase tracking-[0.4em] text-ink |
navLink / navCTA | text-[14px] font-medium text-ink-2 transition-colors hover:text-ink · rounded-md bg-brand px-4 py-2 text-[13px] font-medium text-white transition-colors hover:bg-brand-hover |
btnPrimary / btnPrimaryLg / btnGhost / linkArrow | inline-flex h-10 items-center justify-center rounded-md bg-brand px-5 text-[14px] font-medium text-white transition-colors duration-base hover:bg-brand-hover active:bg-brand-pressed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand disabled:opacity-40 · …h-12 …px-7 text-[16px] font-semibold… · …border border-white/15 …text-ink hover:border-white/30 hover:bg-white/5 · group inline-flex items-center gap-1 text-[14px] font-medium text-brand hover:text-link |
cardService / iconTile | rounded-lg border border-rim bg-surface p-8 transition-colors hover:border-rim-strong · grid size-12 place-items-center rounded-md bg-brand-soft |
input / modal | h-11 w-full rounded-md border border-white/15 bg-surface px-4 text-[14px] text-ink placeholder:text-ink-disabled outline-none transition-colors focus:border-brand focus:ring-[3px] focus:ring-brand/20 · fixed left-1/2 top-1/2 z-40 w-80 -translate-x-1/2 -translate-y-1/2 rounded-lg border border-white/8 bg-surface p-6 text-center shadow-modal |
faqRow / faqQuestion | border-b border-rim · flex w-full items-center justify-between px-6 py-5 text-left text-[16px] font-medium text-ink hover:bg-white/3 |
comparisonRow / comparisonHighlightCell / comparisonNeutralCell | grid grid-cols-3 border-b border-white/5 px-6 py-4 text-[14px] · bg-brand/[0.08] flex items-center text-ok · flex items-center text-ink-muted |
badge / stickyBanner / footer | inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[12px] font-medium · flex flex-col items-start justify-between gap-4 rounded-xl border border-rim bg-surface p-6 lg:flex-row lg:items-center lg:p-8 · border-t border-white/5 bg-page py-16 text-[14px] text-ink-muted |