| @@ -29,9 +29,9 @@ | ||
| 29 | 29 | --primary-hover: #1E9089; |
| 30 | 30 | --primary-tint: #E6F5F3; |
| 31 | 31 | --on-primary: #FFFFFF; |
| 32 | 32 | /* Text/heading colour for content sitting ON a primary/info tint. */ |
| 33 | - --on-tint: #1E8079; | |
| 33 | + --on-tint: #1A7770; /* 4.79:1 on --primary-tint, 5.37:1 on --paper — small Pro badge text sits on the tint */ | |
| 34 | 34 | --primary-glow: 0 2px 12px rgba(42, 167, 160, .30); |
| 35 | 35 | |
| 36 | 36 | /* Surfaces */ |
| 37 | 37 | --canvas: #FBFBFC; |
| @@ -42,9 +42,32 @@ | ||
| 42 | 42 | |
| 43 | 43 | /* Text */ |
| 44 | 44 | --text: #0E0F13; |
| 45 | 45 | --text-secondary: #5C6270; |
| 46 | - --muted: #8A909D; | |
| 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; | |
| 47 | 70 | --faint: #A2A8B4; |
| 48 | 71 | |
| 49 | 72 | /* Semantic (color + tint) */ |
| 50 | 73 | --success: #10B981; |
| @@ -84,9 +107,17 @@ | ||
| 84 | 107 | --line: #282C34; |
| 85 | 108 | --line-strong: #333845; |
| 86 | 109 | --text: #E7E9EE; |
| 87 | 110 | --text-secondary: #9AA1AE; |
| 88 | - --muted: #7E8593; | |
| 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; | |
| 89 | 120 | --faint: #6A707D; |
| 90 | 121 | /* Teal, a touch lighter so it reads on the dark surfaces. */ |
| 91 | 122 | --primary: #34C0B7; |
| 92 | 123 | --primary-hover: #2AA7A0; |
| @@ -102,12 +133,71 @@ | ||
| 102 | 133 | /* ---- Signature motion (used by the shell + later screens) -------------- */ |
| 103 | 134 | @keyframes xspeed-boltpulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.82); } } |
| 104 | 135 | @keyframes xspeed-sweep { 0% { background-position: -220px 0; } 100% { background-position: 320px 0; } } |
| 105 | 136 | @keyframes xspeed-ringfill { from { stroke-dashoffset: 326.7; } to { stroke-dashoffset: 26.1; } } |
| 137 | +@keyframes xspeed-drain { from { transform: scaleX(1); } to { transform: scaleX(0); } } | |
| 138 | +@keyframes xspeed-pop { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: none; } } | |
| 106 | 139 | |
| 107 | 140 | /* Signature-motion utility classes (reduced-motion aware via the rule below). */ |
| 108 | 141 | .xspeed-anim-boltpulse { animation: xspeed-boltpulse 1.8s ease-in-out infinite; } |
| 109 | 142 | .xspeed-anim-sweep { background-size: 220px 100%; animation: xspeed-sweep 1.5s linear infinite; } |
| 143 | +/* Purge result (§24.38). The resting state is the END state, so the reduced-motion | |
| 144 | + rule below leaves the hairline undrawn and the icon simply present. */ | |
| 145 | +.xspeed-anim-drain { transform: scaleX(0); transform-origin: left; animation: xspeed-drain .6s cubic-bezier(.3, .7, .3, 1) both; } | |
| 146 | +.xspeed-anim-pop { animation: xspeed-pop .2s ease-out both; } | |
| 110 | 147 | |
| 111 | 148 | @media (prefers-reduced-motion: reduce) { |
| 112 | 149 | #xspeed-app [class*="xspeed-anim"] { animation: none !important; } |
| 150 | +} | |
| 151 | + | |
| 152 | +/* ---- MCP discovery card (Overview, disconnected) ----------------------- */ | |
| 153 | +/* | |
| 154 | + * Three tinted surfaces for the "Control and optimize your site with AI" card. | |
| 155 | + * They live here rather than in a component because Tailwind cannot express an | |
| 156 | + * ALPHA of a var()-backed colour: with `primary: 'var(--primary)'` in the theme, | |
| 157 | + * `border-primary/45` silently emits the colour at full strength. Deriving them | |
| 158 | + * from --primary with color-mix() keeps the card on-brand in both themes with | |
| 159 | + * no new tokens — change --primary and all three follow. | |
| 160 | + * | |
| 161 | + * Every rule declares a neutral fallback FIRST. color-mix() is ~2023-era, and | |
| 162 | + * where it is unsupported the declaration is dropped: without the fallback the | |
| 163 | + * card's border-color would fall back to currentColor, painting a near-black | |
| 164 | + * hairline around a white card. The fallback degrades to the plain --line | |
| 165 | + * border and no wash, which is simply the standard card treatment. | |
| 166 | + */ | |
| 167 | +#xspeed-app .xspeed-mcp-card { | |
| 168 | + border-color: var(--line); | |
| 169 | + border-color: color-mix(in srgb, var(--primary) 45%, transparent); | |
| 170 | + background-image: radial-gradient(120% 150% at 100% 0%, | |
| 171 | + color-mix(in srgb, var(--primary) 9%, transparent) 0%, transparent 58%); | |
| 172 | +} | |
| 173 | + | |
| 174 | +/* The miniature session sits in a recessed teal well, not a second white | |
| 175 | + plane — white-on-white read as a stray box rather than an inset surface. */ | |
| 176 | +#xspeed-app .xspeed-mcp-well { | |
| 177 | + border-color: var(--line); | |
| 178 | + border-color: color-mix(in srgb, var(--primary) 20%, transparent); | |
| 179 | + background-image: linear-gradient(180deg, | |
| 180 | + color-mix(in srgb, var(--primary) 3%, transparent) 0%, | |
| 181 | + color-mix(in srgb, var(--primary) 7%, transparent) 100%); | |
| 182 | +} | |
| 183 | + | |
| 184 | +#xspeed-app .xspeed-mcp-glow { | |
| 185 | + background-image: radial-gradient(58% 62% at 62% 46%, | |
| 186 | + color-mix(in srgb, var(--primary) 13%, transparent) 0%, transparent 72%); | |
| 187 | +} | |
| 188 | + | |
| 189 | +/* Dark needs more alpha for the same apparent lift: a 3% teal that reads as a | |
| 190 | + tint over #FFFFFF is invisible over #16181D. */ | |
| 191 | +#xspeed-app.dark .xspeed-mcp-card { | |
| 192 | + border-color: color-mix(in srgb, var(--primary) 38%, transparent); | |
| 193 | +} | |
| 194 | +#xspeed-app.dark .xspeed-mcp-well { | |
| 195 | + border-color: color-mix(in srgb, var(--primary) 26%, transparent); | |
| 196 | + background-image: linear-gradient(180deg, | |
| 197 | + color-mix(in srgb, var(--primary) 6%, transparent) 0%, | |
| 198 | + color-mix(in srgb, var(--primary) 11%, transparent) 100%); | |
| 199 | +} | |
| 200 | +#xspeed-app.dark .xspeed-mcp-glow { | |
| 201 | + background-image: radial-gradient(58% 62% at 62% 46%, | |
| 202 | + color-mix(in srgb, var(--primary) 16%, transparent) 0%, transparent 72%); | |
| 113 | 203 | } |