| 1 |
/** |
| 2 |
* xSpeed dashboard design tokens + self-hosted fonts (redesign v2). |
| 3 |
* |
| 4 |
* Lives in public/ (copied verbatim to assets/ by Vite on build — NOT bundled) |
| 5 |
* so the @font-face url('./fonts/…') references resolve relative to |
| 6 |
* assets/theme.css → assets/fonts/…, and so the CSS variables are available to |
| 7 |
* BOTH the Tailwind utilities in admin.css and every React component. Scoped to |
| 8 |
* #xspeed-app; dark overrides ride the existing `.dark` class that useTheme |
| 9 |
* (and PHP's server-side pre-paint) toggles. |
| 10 |
* |
| 11 |
* Fonts are Latin-subset woff2 (Inter 400/500/600, Space Grotesk 500/600/700, |
| 12 |
* JetBrains Mono 400/500) — admin-only, ~168 KB total, font-display: swap. |
| 13 |
*/ |
| 14 |
|
| 15 |
/* ---- Self-hosted fonts ------------------------------------------------- */ |
| 16 |
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/inter-400.woff2') format('woff2'); } |
| 17 |
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/inter-500.woff2') format('woff2'); } |
| 18 |
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/inter-600.woff2') format('woff2'); } |
| 19 |
@font-face { font-family: 'Space Grotesk'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/space-grotesk-500.woff2') format('woff2'); } |
| 20 |
@font-face { font-family: 'Space Grotesk'; font-style: normal; font-weight: 600; font-display: swap; src: url('./fonts/space-grotesk-600.woff2') format('woff2'); } |
| 21 |
@font-face { font-family: 'Space Grotesk'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/space-grotesk-700.woff2') format('woff2'); } |
| 22 |
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/jetbrains-mono-400.woff2') format('woff2'); } |
| 23 |
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('./fonts/jetbrains-mono-500.woff2') format('woff2'); } |
| 24 |
|
| 25 |
/* ---- Design tokens (light) --------------------------------------------- */ |
| 26 |
#xspeed-app { |
| 27 |
/* Interactive — muted teal */ |
| 28 |
--primary: #2AA7A0; |
| 29 |
--primary-hover: #1E9089; |
| 30 |
--primary-tint: #E6F5F3; |
| 31 |
--on-primary: #FFFFFF; |
| 32 |
/* Text/heading colour for content sitting ON a primary/info tint. */ |
| 33 |
--on-tint: #1E8079; |
| 34 |
--primary-glow: 0 2px 12px rgba(42, 167, 160, .30); |
| 35 |
|
| 36 |
/* Surfaces */ |
| 37 |
--canvas: #FBFBFC; |
| 38 |
--paper: #FFFFFF; |
| 39 |
--panel: #F5F6F8; |
| 40 |
--line: #E6E8EC; |
| 41 |
--line-strong: #D8DCE3; |
| 42 |
|
| 43 |
/* Text */ |
| 44 |
--text: #0E0F13; |
| 45 |
--text-secondary: #5C6270; |
| 46 |
/* WCAG AA (#84). Was #8A909D, which measured 3.10 on --canvas, 3.20 on |
| 47 |
--paper and 2.96 on --panel — the last being the "Off" status pill, the |
| 48 |
only state indicator on a group-landing card and the smallest type on |
| 49 |
screen. Fixing the token rather than the call sites covers all 102 |
| 50 |
usages at once — including Pro's panels, which render inside |
| 51 |
#xspeed-app and ship no tokens of their own. |
| 52 |
|
| 53 |
The first pass at this (#6B7180) only measured the three neutral |
| 54 |
surfaces and missed a fourth class: the TINTS. text-muted is never |
| 55 |
paired with a *-tint class on the same element — the tint sits on an |
| 56 |
ancestor upsell card — so a source grep finds nothing and only a |
| 57 |
rendered-DOM measurement catches it. #6B7180 failed on all four |
| 58 |
(4.35 / 4.43 / 4.49 / 4.28). |
| 59 |
|
| 60 |
#686D7C is the lightest value on the same hue that clears 4.5:1 on |
| 61 |
every surface a muted string can land on: |
| 62 |
|
| 63 |
canvas 4.99 · paper 5.16 · panel 4.77 |
| 64 |
primary+info-tint 4.60 · success-tint 4.68 |
| 65 |
warning-tint 4.74 · danger-tint 4.52 |
| 66 |
|
| 67 |
Still visibly lighter than --text-secondary (5.91 on canvas), so the |
| 68 |
hint/secondary distinction the palette relies on survives. */ |
| 69 |
--muted: #686D7C; |
| 70 |
--faint: #A2A8B4; |
| 71 |
|
| 72 |
/* Semantic (color + tint) */ |
| 73 |
--success: #10B981; |
| 74 |
--success-tint: #E9F7F1; |
| 75 |
--warning: #F59E0B; |
| 76 |
--warning-tint: #FEF4E6; |
| 77 |
--danger: #EF4444; |
| 78 |
--danger-tint: #FDECEC; |
| 79 |
--info: #2AA7A0; |
| 80 |
--info-tint: #E6F5F3; |
| 81 |
|
| 82 |
/* Deep-sea rail (deliberately not near-black, to separate from WP's menu) */ |
| 83 |
--rail-top: #14211F; |
| 84 |
--rail-bottom: #0D1615; |
| 85 |
--rail-text: #9FADAB; |
| 86 |
--rail-edge: rgba(255, 255, 255, .06); |
| 87 |
|
| 88 |
/* Type families */ |
| 89 |
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
| 90 |
--font-display: 'Space Grotesk', 'Inter', system-ui, sans-serif; |
| 91 |
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; |
| 92 |
|
| 93 |
/* Shape */ |
| 94 |
--radius-card: 12px; |
| 95 |
--radius-control: 8px; |
| 96 |
--radius-pill: 999px; |
| 97 |
|
| 98 |
font-family: var(--font-sans); |
| 99 |
color: var(--text); |
| 100 |
} |
| 101 |
|
| 102 |
/* ---- Design tokens (dark) — rides the existing .dark class -------------- */ |
| 103 |
#xspeed-app.dark { |
| 104 |
--canvas: #0E0F13; |
| 105 |
--paper: #16181D; |
| 106 |
--panel: #1B1E24; |
| 107 |
--line: #282C34; |
| 108 |
--line-strong: #333845; |
| 109 |
--text: #E7E9EE; |
| 110 |
--text-secondary: #9AA1AE; |
| 111 |
/* Same tint gap as light mode (#84). #7E8593 measured 4.29 on |
| 112 |
--primary-tint and 4.32 on --success-tint, and cleared --panel by |
| 113 |
exactly 4.50 — no headroom, so any future surface tweak would have |
| 114 |
broken it silently. #868D9B clears every dark surface: |
| 115 |
canvas 5.74 · paper 5.33 · panel 5.01 · primary+info-tint 4.77 |
| 116 |
success-tint 4.81 · warning-tint 5.03 · danger-tint 5.17 |
| 117 |
Dark was never actually at "0 failures" on tints; this is what makes |
| 118 |
that true rather than assumed. */ |
| 119 |
--muted: #868D9B; |
| 120 |
--faint: #6A707D; |
| 121 |
/* Teal, a touch lighter so it reads on the dark surfaces. */ |
| 122 |
--primary: #34C0B7; |
| 123 |
--primary-hover: #2AA7A0; |
| 124 |
--primary-tint: #0E2622; |
| 125 |
--on-tint: #7FD8CF; |
| 126 |
--info: #34C0B7; |
| 127 |
--info-tint: #0E2622; |
| 128 |
--success-tint: #12251F; |
| 129 |
--warning-tint: #251C0F; |
| 130 |
--danger-tint: #2A1516; |
| 131 |
} |
| 132 |
|
| 133 |
/* ---- Signature motion (used by the shell + later screens) -------------- */ |
| 134 |
@keyframes xspeed-boltpulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.82); } } |
| 135 |
@keyframes xspeed-sweep { 0% { background-position: -220px 0; } 100% { background-position: 320px 0; } } |
| 136 |
@keyframes xspeed-ringfill { from { stroke-dashoffset: 326.7; } to { stroke-dashoffset: 26.1; } } |
| 137 |
|
| 138 |
/* Signature-motion utility classes (reduced-motion aware via the rule below). */ |
| 139 |
.xspeed-anim-boltpulse { animation: xspeed-boltpulse 1.8s ease-in-out infinite; } |
| 140 |
.xspeed-anim-sweep { background-size: 220px 100%; animation: xspeed-sweep 1.5s linear infinite; } |
| 141 |
|
| 142 |
@media (prefers-reduced-motion: reduce) { |
| 143 |
#xspeed-app [class*="xspeed-anim"] { animation: none !important; } |
| 144 |
} |
| 145 |
|
| 146 |
/* ---- MCP discovery card (Overview, disconnected) ----------------------- */ |
| 147 |
/* |
| 148 |
* Three tinted surfaces for the "Control and optimize your site with AI" card. |
| 149 |
* They live here rather than in a component because Tailwind cannot express an |
| 150 |
* ALPHA of a var()-backed colour: with `primary: 'var(--primary)'` in the theme, |
| 151 |
* `border-primary/45` silently emits the colour at full strength. Deriving them |
| 152 |
* from --primary with color-mix() keeps the card on-brand in both themes with |
| 153 |
* no new tokens — change --primary and all three follow. |
| 154 |
* |
| 155 |
* Every rule declares a neutral fallback FIRST. color-mix() is ~2023-era, and |
| 156 |
* where it is unsupported the declaration is dropped: without the fallback the |
| 157 |
* card's border-color would fall back to currentColor, painting a near-black |
| 158 |
* hairline around a white card. The fallback degrades to the plain --line |
| 159 |
* border and no wash, which is simply the standard card treatment. |
| 160 |
*/ |
| 161 |
#xspeed-app .xspeed-mcp-card { |
| 162 |
border-color: var(--line); |
| 163 |
border-color: color-mix(in srgb, var(--primary) 45%, transparent); |
| 164 |
background-image: radial-gradient(120% 150% at 100% 0%, |
| 165 |
color-mix(in srgb, var(--primary) 9%, transparent) 0%, transparent 58%); |
| 166 |
} |
| 167 |
|
| 168 |
/* The miniature session sits in a recessed teal well, not a second white |
| 169 |
plane — white-on-white read as a stray box rather than an inset surface. */ |
| 170 |
#xspeed-app .xspeed-mcp-well { |
| 171 |
border-color: var(--line); |
| 172 |
border-color: color-mix(in srgb, var(--primary) 20%, transparent); |
| 173 |
background-image: linear-gradient(180deg, |
| 174 |
color-mix(in srgb, var(--primary) 3%, transparent) 0%, |
| 175 |
color-mix(in srgb, var(--primary) 7%, transparent) 100%); |
| 176 |
} |
| 177 |
|
| 178 |
#xspeed-app .xspeed-mcp-glow { |
| 179 |
background-image: radial-gradient(58% 62% at 62% 46%, |
| 180 |
color-mix(in srgb, var(--primary) 13%, transparent) 0%, transparent 72%); |
| 181 |
} |
| 182 |
|
| 183 |
/* Dark needs more alpha for the same apparent lift: a 3% teal that reads as a |
| 184 |
tint over #FFFFFF is invisible over #16181D. */ |
| 185 |
#xspeed-app.dark .xspeed-mcp-card { |
| 186 |
border-color: color-mix(in srgb, var(--primary) 38%, transparent); |
| 187 |
} |
| 188 |
#xspeed-app.dark .xspeed-mcp-well { |
| 189 |
border-color: color-mix(in srgb, var(--primary) 26%, transparent); |
| 190 |
background-image: linear-gradient(180deg, |
| 191 |
color-mix(in srgb, var(--primary) 6%, transparent) 0%, |
| 192 |
color-mix(in srgb, var(--primary) 11%, transparent) 100%); |
| 193 |
} |
| 194 |
#xspeed-app.dark .xspeed-mcp-glow { |
| 195 |
background-image: radial-gradient(58% 62% at 62% 46%, |
| 196 |
color-mix(in srgb, var(--primary) 16%, transparent) 0%, transparent 72%); |
| 197 |
} |
| 198 |
|