Skip to content

Commit d0ceb84

Browse files
andresdjassoclaude
andcommitted
feat(loader): gooey thinking loader gets the Figma gradient + inner glow
Replaces the flat currentColor fill on the chat ThinkingLoader with a radial gradient (center → edge) plus a soft white inner-shadow glow, per the Figma loader spec — theme-aware: - light: #A7A7A7 → #D6D6D6, white inner glow at 0.9 - dark: #4F4F4F → #6F6F6F, white inner glow at 0.6 One set of SVG defs serves both themes: the gradient stops and the feFlood glow read CSS custom properties (--tl-grad-inner/-outer, --tl-glow) set per theme on .frame / :global(.dark) .frame. The goo filter now runs in sRGB so the gradient keeps its midtones through the blur, and the inner glow rides the merged silhouette's edge (the Figma inner-shadow technique, stdDeviation scaled 72→100 viewBox). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c245784 commit d0ceb84

2 files changed

Lines changed: 74 additions & 3 deletions

File tree

apps/sim/components/emcn/components/thinking-loader/thinking-loader.module.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@
1818
.frame {
1919
color: #4f4f4f;
2020
flex: none;
21+
/* Loader fill is a radial gradient (center → edge) plus a soft white inner
22+
glow, per the Figma loader spec. Stops and glow opacity are theme vars,
23+
inherited by the gradient stops and feFlood inside the SVG, so one set of
24+
defs serves both themes. Light: #A7A7A7 → #D6D6D6, glow white 0.9. */
25+
--tl-grad-inner: #a7a7a7;
26+
--tl-grad-outer: #d6d6d6;
27+
--tl-glow: rgba(255, 255, 255, 0.9);
28+
}
29+
30+
/* Gradient stops + glow flood read the theme vars via CSS (a raw `var()` in an
31+
SVG presentation attribute would not resolve — it must come through CSS). */
32+
.gradInner {
33+
stop-color: var(--tl-grad-inner);
34+
}
35+
.gradOuter {
36+
stop-color: var(--tl-grad-outer);
37+
}
38+
.glow {
39+
flood-color: var(--tl-glow);
2140
}
2241

2342
/* Wall-clock phase lock: the component sets --tl-sync to a shared negative
@@ -31,6 +50,10 @@
3150

3251
:global(.dark) .frame {
3352
color: #d6d6d6;
53+
/* Dark: #4F4F4F → #6F6F6F, glow white 0.6. */
54+
--tl-grad-inner: #4f4f4f;
55+
--tl-grad-outer: #6f6f6f;
56+
--tl-glow: rgba(255, 255, 255, 0.6);
3457
}
3558

3659
.labelRow {

apps/sim/components/emcn/components/thinking-loader/thinking-loader.tsx

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export function ThinkingLoader({ variant, size = 20, label, className }: Thinkin
180180
const filterId = `tl-goo-${id}`
181181
const clipId = `tl-clip-${id}`
182182
const windowClipId = `tl-window-${id}`
183+
const gradientId = `tl-grad-${id}`
183184
const [cycleVariant, setCycleVariant] = useState<ThinkingLoaderVariant>('metaballs')
184185
const cycling = variant === undefined
185186

@@ -220,10 +221,57 @@ export function ThinkingLoader({ variant, size = 20, label, className }: Thinkin
220221
style={syncDelay ? ({ '--tl-sync': syncDelay } as CSSProperties) : undefined}
221222
>
222223
<defs>
223-
<filter id={filterId} x='-30%' y='-30%' width='160%' height='160%'>
224+
{/* sRGB so the radial gradient fill keeps its authored midtones
225+
through the blur (linearRGB would wash the gradient out). The goo
226+
crush runs first; the inner glow then rides the merged silhouette's
227+
edge — a soft white inset, per the Figma loader spec. */}
228+
<filter
229+
id={filterId}
230+
x='-30%'
231+
y='-30%'
232+
width='160%'
233+
height='160%'
234+
colorInterpolationFilters='sRGB'
235+
>
224236
<feGaussianBlur in='SourceGraphic' stdDeviation='5' result='blur' />
225-
<feColorMatrix in='blur' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9' />
237+
<feColorMatrix
238+
in='blur'
239+
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9'
240+
result='goo'
241+
/>
242+
{/* Inner shadow from the goo silhouette's alpha (Figma technique:
243+
blur the alpha, subtract it from itself to leave an inner ring,
244+
tint white). stdDeviation 4.86 = the spec's 3.5 scaled 72→100. */}
245+
<feColorMatrix
246+
in='goo'
247+
type='matrix'
248+
values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'
249+
result='gooAlpha'
250+
/>
251+
<feGaussianBlur in='gooAlpha' stdDeviation='4.86' result='innerBlur' />
252+
<feComposite
253+
in='innerBlur'
254+
in2='gooAlpha'
255+
operator='arithmetic'
256+
k2='-1'
257+
k3='1'
258+
result='innerMask'
259+
/>
260+
{/* Glow color + per-theme opacity ride a CSS var (light 0.9 / dark
261+
0.6) so one filter serves both themes. */}
262+
<feFlood className={styles.glow} result='glowColor' />
263+
<feComposite in='glowColor' in2='innerMask' operator='in' result='glow' />
264+
<feMerge>
265+
<feMergeNode in='goo' />
266+
<feMergeNode in='glow' />
267+
</feMerge>
226268
</filter>
269+
{/* Radial gradient (center → edge), theme stops via CSS vars. Matches
270+
the Figma loader: dark #4F4F4F→#6F6F6F, light #A7A7A7→#D6D6D6. */}
271+
<radialGradient id={gradientId} cx='50' cy='50' r='50' gradientUnits='userSpaceOnUse'>
272+
<stop className={styles.gradInner} />
273+
<stop offset='1' className={styles.gradOuter} />
274+
</radialGradient>
227275
{/* Shapes clip BEFORE the goo filter, so anything exiting the frame
228276
melts into the edge instead of getting a hard post-filter cut. */}
229277
<clipPath id={clipId}>
@@ -235,7 +283,7 @@ export function ThinkingLoader({ variant, size = 20, label, className }: Thinkin
235283
<rect x='12.5' y='12.5' width='75' height='75' />
236284
</clipPath>
237285
</defs>
238-
<g filter={`url(#${filterId})`} fill='currentColor'>
286+
<g filter={`url(#${filterId})`} fill={`url(#${gradientId})`}>
239287
{stages.map((v) => (
240288
<g
241289
key={v}

0 commit comments

Comments
 (0)