Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ The visual regression suite (`e2e/design-system-visual.spec.ts`) captures galler

`packages/ui/src/styles/globals.css` carries a namespaced `--color-gmx-*` palette, landing font stacks (`--font-landing-sans` = Archivo, the OFL stand-in for GMX's paid TTHoves; `--font-landing-code` = Space Mono), landing typography/button utilities (`text-heading-1…4`, `text-subheadline`, `text-description`, `btn-landing`), and helpers (`border-hairline`, `scrollbar-hide`, `animate-pause`, `--animate-scroll`). These are **source material for the GrantFox 3 theme revamp**, not tokens for general app use — app components keep using the semantic tokens above; only the dark theme (GF3-001) and the landing route consume the `gmx-*` set. The full GMX token reference, licensing notes, and the GMX→SO4 mapping table live in [`docs/gf_3/001_theme_update.md`](./docs/gf_3/001_theme_update.md); the landing spec in [`docs/gf_3/002_landing_page.md`](./docs/gf_3/002_landing_page.md).

### Dark theme (GF3-001)

`.dark` in `globals.css` had been removed at some point, so toggling to dark mode silently fell back to the light values above. GF3-001 (re)introduces it, this time derived from the GMX reference palette instead of an arbitrary dark scale — surfaces, text roles, `border`/`input`, `primary`/`ring`, and `accent` all resolve to a `--color-gmx-*` value per the mapping table in [`001_theme_update.md` §8.2](./docs/gf_3/001_theme_update.md#82-layer-2--semantic-dark-theme-what-gf3-001-applies). Trading-state colors (`long`/`short`/`liquidation`, `success`/`warning`/`danger`, `neutral`) are not GMX concepts, so their foreground values are untouched — only their `subtle`/`border` fill pairs are re-lightened in `.dark`, because the `:root` pairs were tuned to sit on a white page and read as almost-black on the new slate-800/900 surfaces. `--info` is the one status color that does move, to `--color-gmx-blue-400`, since "informational blue" is the same concept as GMX's brand blue. The light theme (`:root`) is unchanged.

The landing route (`apps/web/src/routes/index.tsx`) forces this `.dark` scope on its own root element regardless of the user's theme setting — GMX's landing has no light variant — by adding the `dark` class directly rather than touching `<html>`, so the override never leaks into `/trade`, `/pools`, `/earn`, `/referrals`, or `/faucet`.

## Audit history

- **DS-050** (this pass): found and fixed 268 arbitrary-value violations (229 arbitrary font sizes, 38 raw hex colors, 1 arbitrary radius) across ~40 files. Of these:
Expand Down
42 changes: 42 additions & 0 deletions apps/web/src/features/gallery/components/gallery-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,48 @@ export function GalleryPage() {
</div>
</div>
</Section>

<Section title="Landing Typography (GMX)">
{/* GMX's landing is dark-only (docs/gf_3/001_theme_update.md §7), so
this swatch forces the `.dark` token scope the same way the
landing route itself does, independent of the gallery's own
theme toggle. */}
<div className="dark space-y-6 bg-background p-6">
<div>
<p className="mb-2 text-13 text-muted-foreground">text-heading-1</p>
<h1 className="text-heading-1 text-foreground">Perpetual markets.</h1>
</div>
<div>
<p className="mb-2 text-13 text-muted-foreground">text-heading-2</p>
<h2 className="text-heading-2 text-foreground">Built for traders.</h2>
</div>
<div>
<p className="mb-2 text-13 text-muted-foreground">text-heading-3</p>
<h3 className="text-heading-3 text-foreground">Card titles</h3>
</div>
<div>
<p className="mb-2 text-13 text-muted-foreground">text-heading-4</p>
<h4 className="text-heading-4 text-foreground">Small card titles</h4>
</div>
<div>
<p className="mb-2 text-13 text-muted-foreground">text-subheadline</p>
<p className="text-subheadline">Supporting copy under a call to action.</p>
</div>
<div>
<p className="mb-2 text-13 text-muted-foreground">text-description</p>
<p className="text-description max-w-md">
Body copy on a dark card surface, set in Archivo at 16px with GMX's
tracking and line height.
</p>
</div>
<div>
<p className="mb-2 text-13 text-muted-foreground">btn-landing</p>
<Button variant="default" className="btn-landing h-10 px-5 text-sm">
Launch trading app
</Button>
</div>
</div>
</Section>
</main>
)
}
5 changes: 4 additions & 1 deletion apps/web/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export const Route = createFileRoute("/")({ component: LandingPage })

function LandingPage() {
return (
<div className="font-trading min-h-svh bg-background text-foreground antialiased">
// GMX's landing is dark-only — force the `.dark` token scope on this
// subtree regardless of the user's theme setting (docs/gf_3/001_theme_update.md §7),
// without touching <html> so /trade, /pools, etc. keep honoring it.
<div className="dark font-landing-sans min-h-svh bg-background text-foreground antialiased">
<Navbar variant="landing" />
<Hero />
<Stats />
Expand Down
5 changes: 0 additions & 5 deletions apps/web/src/styles/landing.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,3 @@
font-family: "Geist Mono", ui-monospace, monospace;
font-feature-settings: "tnum" 1, "zero" 1;
}

/* Trading feature settings for body copy */
.font-trading {
font-feature-settings: "ss01", "cv11";
}
4 changes: 2 additions & 2 deletions apps/web/src/ui/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function Navbar({ variant }: Props) {
{!isApp && (
<Button
variant="default"
className="hidden h-9.5 gap-2 px-4 text-13-5 sm:inline-flex"
className="btn-landing hidden h-9.5 gap-2 px-4 text-13-5 sm:inline-flex"
>
Launch app
<span className="transition-transform group-hover:translate-x-0.5">→</span>
Expand Down Expand Up @@ -135,7 +135,7 @@ export function Navbar({ variant }: Props) {
))}
</ul>
{!isApp && (
<Button variant="default" className="mt-3 w-full gap-2">
<Button variant="default" className="btn-landing mt-3 w-full gap-2">
Launch app →
</Button>
)}
Expand Down
13 changes: 4 additions & 9 deletions apps/web/src/ui/landing/features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,12 @@ export function Features() {
<span className="inline-block h-px w-4 bg-primary" />
Engine
</div>
<h2
className="font-trading font-medium leading-[1.04] tracking-[-0.025em] text-foreground"
style={{ fontSize: "clamp(34px, 3.4vw, 46px)" }}
>
<h2 className="text-heading-2 text-foreground">
Built for traders{" "}
<span className="italic font-normal text-primary">who care</span>{" "}
where their fills come from.
</h2>
<p className="mt-4 max-w-[560px] text-base leading-[1.55] text-muted-foreground">
<p className="mt-4 max-w-[560px] text-description">
so4 runs a unified liquidity layer — one pool backs every market,
with deterministic settlement on every fill. No hidden routes, no
opaque MM rebates.
Expand All @@ -106,10 +103,8 @@ export function Features() {
<div className="mb-5 inline-flex h-9 w-9 items-center justify-center border border-primary/20 bg-primary/8 text-primary">
{icon}
</div>
<h3 className="mb-2.5 text-lg font-medium tracking-[-0.015em] text-foreground">
{title}
</h3>
<p className="text-sm leading-[1.55] text-muted-foreground">{body}</p>
<h3 className="text-heading-4 mb-2.5 text-foreground">{title}</h3>
<p className="text-description">{body}</p>
<div className="mt-5 flex items-baseline justify-between border-t border-border pt-4 font-mono-num">
<span className="text-10-5 uppercase tracking-[0.12em] text-muted-foreground/60">
{statKey}
Expand Down
9 changes: 3 additions & 6 deletions apps/web/src/ui/landing/final-cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,20 @@ export function FinalCTA() {
return (
<section className="final-glow relative overflow-hidden px-4 py-28 text-center sm:px-6 lg:px-8 sm:py-36">
<div className="relative mx-auto max-w-[1320px]">
<h2
className="font-trading mx-auto max-w-[900px] font-medium leading-none tracking-[-0.035em] text-foreground"
style={{ fontSize: "clamp(44px, 5.5vw, 76px)" }}
>
<h2 className="text-heading-2 mx-auto max-w-[900px] text-foreground">
The book is{" "}
<span className="italic font-normal text-primary">open.</span>
<br />
You're a wallet away.
</h2>

<p className="mx-auto mt-5 max-w-[540px] text-17 leading-[1.55] text-muted-foreground">
<p className="mx-auto mt-5 max-w-[540px] text-subheadline">
No signup. No email. No deposit minimum. Connect and trade — or fork the
contracts and run your own venue.
</p>

<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
<Button variant="default" className="h-12 gap-2 px-6 text-sm font-medium">
<Button variant="default" className="btn-landing h-12 gap-2 px-6 text-sm">
Launch trading app →
</Button>
<Button variant="outline" className="h-12 px-6 text-sm font-medium">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/ui/landing/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export function Footer() {
{/* ds-allow: text-[16.4vw] is fluid viewport-relative display type
(scales the whole wordmark with page width) — not a fixed size
a px-based token scale can represent. */}
<div className="font-trading relative select-none overflow-hidden border-t border-border py-7 text-[16.4vw] font-semibold leading-[0.82] tracking-[-0.06em] text-foreground">
<div className="relative select-none overflow-hidden border-t border-border py-7 text-[16.4vw] font-semibold leading-[0.82] tracking-[-0.06em] text-foreground">
<div className="flex items-start gap-[0.06em] whitespace-nowrap">
<span>so4.market</span>
{/* ds-allow: text-[0.18em] is intentionally relative to the
Expand Down
9 changes: 3 additions & 6 deletions apps/web/src/ui/landing/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,7 @@ export function Hero() {
Mainnet · v1.4 · Lagos UTC+1
</span>

<h1
className="font-trading mt-[22px] font-medium leading-none tracking-[-0.035em] text-foreground"
style={{ fontSize: "clamp(48px, 5.6vw, 78px)" }}
>
<h1 className="text-heading-1 mt-[22px] text-foreground">
<span className="block">perpetual</span>
<span className="block">
markets,{" "}
Expand All @@ -265,14 +262,14 @@ export function Hero() {
<span className="block">on-chain.</span>
</h1>

<p className="mt-[22px] max-w-[520px] text-17 leading-[1.55] text-muted-foreground">
<p className="mt-[22px] max-w-[520px] text-subheadline">
A unified-liquidity perp DEX. Deep books, sub-second matching, and
self-custodied risk — built for traders who care where their fills
come from.
</p>

<div className="mt-8 flex flex-wrap items-center gap-3">
<Button variant="default" className="h-12 gap-2 px-5 text-sm font-medium">
<Button variant="default" className="btn-landing h-12 gap-2 px-5 text-sm">
Start trading <span>→</span>
</Button>
<Button variant="outline" className="h-12 px-5 text-sm font-medium">
Expand Down
13 changes: 4 additions & 9 deletions apps/web/src/ui/landing/how-it-works.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,11 @@ export function HowItWorks() {
<span className="inline-block h-px w-4 bg-primary" />
Flow
</div>
<h2
className="font-trading font-medium leading-[1.04] tracking-[-0.025em] text-foreground"
style={{ fontSize: "clamp(34px, 3.4vw, 46px)" }}
>
<h2 className="text-heading-2 text-foreground">
From wallet to fill in{" "}
<span className="italic font-normal text-primary">three steps.</span>
</h2>
<p className="mt-4 max-w-[560px] text-base leading-[1.55] text-muted-foreground">
<p className="mt-4 max-w-[560px] text-description">
No KYC, no email, no signup form. Connect a wallet and the orderbook is
open — your collateral never leaves your control.
</p>
Expand All @@ -84,10 +81,8 @@ export function HowItWorks() {
<div className="font-mono-num text-11 uppercase tracking-[0.18em] text-primary">
{num}
</div>
<h3 className="mt-7 text-22 font-medium tracking-[-0.02em] text-foreground">
{title}
</h3>
<p className="mt-3 text-sm leading-[1.55] text-muted-foreground">{body}</p>
<h3 className="text-heading-4 mt-7 text-foreground">{title}</h3>
<p className="mt-3 text-description">{body}</p>
<Terminal lines={lines} />
</div>
))}
Expand Down
7 changes: 2 additions & 5 deletions apps/web/src/ui/landing/infrastructure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,12 @@ export function Infrastructure() {
<span className="inline-block h-px w-4 bg-primary" />
Infrastructure
</div>
<h2
className="font-trading font-medium leading-[1.04] tracking-[-0.025em] text-foreground"
style={{ fontSize: "clamp(34px, 3.4vw, 46px)" }}
>
<h2 className="text-heading-2 text-foreground">
An{" "}
<span className="italic font-normal text-primary">app-specific chain</span>{" "}
with one job: settle perps.
</h2>
<p className="mt-4 max-w-[560px] text-base leading-[1.55] text-muted-foreground">
<p className="mt-4 max-w-[560px] text-description">
so4 runs on a custom L1 tuned for orderbook throughput. We don't share
blockspace with NFT mints or memecoin launches — your fill is the only
thing in the queue.
Expand Down
7 changes: 2 additions & 5 deletions apps/web/src/ui/landing/markets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,11 @@ export function Markets() {
<span className="inline-block h-px w-4 bg-primary" />
Markets
</div>
<h2
className="font-trading font-medium leading-[1.04] tracking-[-0.025em] text-foreground"
style={{ fontSize: "clamp(34px, 3.4vw, 46px)" }}
>
<h2 className="text-heading-2 text-foreground">
184 perpetuals.{" "}
<span className="italic font-normal text-primary">One book.</span>
</h2>
<p className="mt-4 max-w-[520px] text-base leading-[1.55] text-muted-foreground">
<p className="mt-4 max-w-[520px] text-description">
Crypto, FX, rates, and commodity perps — all settled in USDC, all
backed by the same unified liquidity layer.
</p>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions packages/ui/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,88 @@
--sidebar-ring: oklch(0.556 0 0);
}

/*
* Dark theme (GF3-001) — GMX-derived.
*
* Re-introduces `.dark` (previously absent, so dark mode fell back to the
* light values above). Surfaces, text roles, border, primary/ring, and accent
* are sourced straight from the GMX reference palette per the mapping table
* in docs/gf_3/001_theme_update.md §8.2 — trading-state foreground colors
* (long/short/liquidation/success/warning/danger/neutral) are intentionally
* NOT GMX concepts and are left untouched, inherited from :root. Only their
* `subtle`/`border` fill pairs are re-lightened here so a badge still reads
* as a distinct tint against the new slate-800/900 surfaces instead of
* blending into them (the :root pairs were tuned for a white canvas).
*/
.dark {
--background: var(--color-gmx-slate-900);
--foreground: oklch(1 0 0);
--card: var(--color-gmx-slate-800);
--card-foreground: oklch(1 0 0);
--popover: var(--color-gmx-slate-800);
--popover-foreground: oklch(1 0 0);
--primary: var(--color-gmx-blue-400);
--primary-foreground: oklch(1 0 0);
--secondary: var(--color-gmx-slate-700);
--secondary-foreground: oklch(1 0 0);
--muted: var(--color-gmx-slate-700);
--muted-foreground: var(--color-gmx-slate-400);
--accent: var(--color-gmx-blue-300);
--accent-foreground: oklch(1 0 0);
--destructive-foreground: oklch(1 0 0);
--border: var(--color-gmx-stroke-primary);
--input: var(--color-gmx-stroke-primary);
--ring: var(--color-gmx-blue-400);

--surface-canvas: var(--color-gmx-slate-900);
--surface-sunken: var(--color-gmx-slate-950);
--surface-raised: var(--color-gmx-slate-800);
--surface-overlay: var(--color-gmx-slate-800);
--surface-interactive: var(--color-gmx-slate-650);

--text-primary: oklch(1 0 0);
--text-secondary: var(--color-gmx-slate-400);
--text-tertiary: var(--color-gmx-slate-500);
--text-inverse: oklch(1 0 0);
--text-link: var(--color-gmx-blue-400);

--info: var(--color-gmx-blue-400);
--sidebar-primary: var(--color-gmx-blue-400);

--sidebar: var(--color-gmx-slate-800);
--sidebar-foreground: oklch(1 0 0);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: var(--color-gmx-slate-650);
--sidebar-accent-foreground: oklch(1 0 0);
--sidebar-border: var(--color-gmx-stroke-primary);
--sidebar-ring: var(--color-gmx-blue-400);

--chart-surface: var(--color-gmx-slate-800);
--chart-text: var(--color-gmx-slate-400);
--chart-grid: var(--color-gmx-stroke-primary);
--chart-crosshair: var(--color-gmx-slate-500);
--chart-crosshair-label: var(--color-gmx-slate-700);
--chart-border: var(--color-gmx-stroke-primary);

/* Trading-state subtle/border — rebalanced, see file comment above */
--long-subtle: oklch(0.26 0.07 163.225);
--long-border: oklch(0.42 0.12 163.225);
--short-subtle: oklch(0.26 0.07 27.325);
--short-border: oklch(0.42 0.12 27.325);
--liquidation-subtle: oklch(0.26 0.07 75.834);
--liquidation-border: oklch(0.42 0.12 75.834);
--success-subtle: oklch(0.26 0.07 163.225);
--success-border: oklch(0.42 0.12 163.225);
--warning-subtle: oklch(0.26 0.07 75.834);
--warning-border: oklch(0.42 0.12 75.834);
--info-subtle: oklch(0.26 0.09 268.08);
--info-border: oklch(0.42 0.16 268.08);
--danger-subtle: oklch(0.26 0.07 27.325);
--danger-border: oklch(0.42 0.12 27.325);
--neutral-subtle: oklch(0.28 0 0);
--neutral-border: oklch(0.40 0 0);
}

@layer base {
* {
@apply border-border outline-ring/50;
Expand Down
Loading