GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — improvement(landing): build-card workflow showcase, real Logs UI moni… · simstudioai/sim@d1d0c2c

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • apps/sim
    • app/(landing)/components
      • features
        • components
          • build-callout
            • build-callout.tsx
            • components
              • build-chat-animation
                • build-chat-animation.tsx
                • index.ts
              • workflow-showcase
                • index.ts
                • showcase-data.ts
                • workflow-showcase.tsx
            • index.ts
          • feature-card
            • feature-card.tsx
          • formation-graph
            • formation-graph.tsx
          • integrations-callout
            • integrations-callout.tsx
          • knowledge-callout
            • knowledge-callout.tsx
          • logs-callout
            • index.ts
            • logs-callout.tsx
          • logs-table-preview
            • index.ts
            • logs-table-preview.tsx
        • features.tsx
      • hero/components
        • hero-platform-loop
          • hero-workflow-stage.tsx
          • stage-block-card.tsx
        • hero-visual
          • workflow-data.ts
    • public/landing
      • feature-monitor-backdrop.jpg
      • feature-monitor-ui.png
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎apps/sim/app/(landing)/components/features/components/build-callout/build-callout.tsx‎

Copy file name to clipboard
+20Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { WorkflowShowcase } from '@/app/(landing)/components/features/components/build-callout/components/workflow-showcase'
2+
import { ThinkingLoader } from '@/app/(landing)/components/thinking-loader'
3+
4+
/**
5+
* The Build beat's callout - the finished workflow canvas centered on the
6+
* card's solid grey stage, with the goo cycle loader phasing through its
7+
* world-state phrases in the bottom-left corner. The Mothership chat loop
8+
* (`components/build-chat-animation`) is parked here unwired, kept for reuse
9+
* on another surface.
10+
*/
11+
export function BuildCallout() {
12+
return (
13+
<div aria-hidden='true' className='pointer-events-none absolute inset-0'>
14+
<WorkflowShowcase />
15+
<div className='absolute bottom-8 left-8 max-sm:bottom-4 max-sm:left-4'>
16+
<ThinkingLoader size={22} phase className='text-[var(--text-body)]' />
17+
</div>
18+
</div>
19+
)
20+
}
Collapse file

‎apps/sim/app/(landing)/components/features/components/build-callout/components/build-chat-animation/build-chat-animation.tsx‎

Copy file name to clipboard
+361Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
'use client'
2+
3+
import { useEffect, useRef, useState } from 'react'
4+
import { cn } from '@sim/emcn'
5+
import { Blimp } from '@sim/emcn/icons'
6+
import { ArrowUp, Mic, Paperclip, Plus, Slash } from 'lucide-react'
7+
import { ThinkingLoader } from '@/app/(landing)/components/thinking-loader'
8+
9+
const PROMPT = 'Build a workflow to schedule and publish posts to my X account.'
10+
const REPLY =
11+
'On it — building a workflow with a schedule trigger, a drafting agent, and an X publish step.'
12+
const REPLY_WORDS = REPLY.split(' ')
13+
14+
const TYPE_START_MS = 900
15+
const TYPE_CHAR_MS = 40
16+
const SEND_MS = 3800
17+
const THINKING_DONE_MS = 6000
18+
const REPLY_START_DELAY_MS = 420
19+
const REPLY_WORD_MS = 95
20+
const MERGE_MS = 9000
21+
const LOOP_MS = 10400
22+
23+
/** Resting gap between the chat surface and the composer once the split settles. */
24+
const SPLIT_GAP_PX = 10
25+
/** Goo blur decay window — the liquid neck between the boxes thins and snaps over this span. */
26+
const GOO_DECAY_MS = 850
27+
/** Peak feGaussianBlur stdDeviation at the send beat. */
28+
const GOO_MAX_BLUR = 16
29+
/** Blur decay tick; stepped so the blur eases out on a (1-t)^2 curve. */
30+
const GOO_STEP_MS = 40
31+
32+
/**
33+
* Chat-surface transition set while opening: geometry travels with the goo
34+
* split (500ms), the surface itself fades in fast (250ms), and the hairline
35+
* ring + elevation crisp in only after the liquid split settles (300ms fade,
36+
* 550ms delay).
37+
*/
38+
const CHAT_SURFACE_TRANSITION_OPEN =
39+
'[transition:height_500ms_ease-out,transform_500ms_ease-out,margin-bottom_500ms_ease-out,opacity_250ms_ease-out,box-shadow_300ms_ease-out_550ms]'
40+
41+
/**
42+
* Chat-surface transition set while merging back: same geometry travel, but
43+
* the ring + elevation drop immediately and the surface fades out fast so the
44+
* goo ghost carries the collapse into the composer.
45+
*/
46+
const CHAT_SURFACE_TRANSITION_MERGE =
47+
'[transition:height_500ms_ease-out,transform_500ms_ease-out,margin-bottom_500ms_ease-out,opacity_250ms_ease-out,box-shadow_200ms_ease-out]'
48+
49+
type BuildChatPhase =
50+
| 'idle'
51+
| 'typing'
52+
| 'thinking'
53+
| 'replyPreparing'
54+
| 'replying'
55+
| 'complete'
56+
| 'merging'
57+
58+
/**
59+
* Decorative Build-card Mothership loop. It mirrors the studio choreography
60+
* with local timers and CSS transitions so the landing page avoids motion
61+
* dependencies while preserving the product-window feel.
62+
*
63+
* The composer is a permanent fixture; the send beat gooey-morphs the chat
64+
* surface out of it, and the loop's final beat gooey-merges it back down in.
65+
* An SVG goo filter (state-driven blur → alpha-contrast matrix → atop
66+
* composite) is applied to an aria-hidden ghost layer of two solid white
67+
* rects mirroring the real boxes (both measured with a ResizeObserver) —
68+
* never to the real UI, which stays crisp above it. The filtered silhouette
69+
* renders a liquid meniscus that necks and snaps as the boxes separate (and
70+
* re-welds as they merge); each morph's blur decays on a (1-t)^2 curve over
71+
* 850ms. The chat surface's ring + shadow crisp in only after the split and
72+
* drop instantly at the merge, so the ghost carries the collapse.
73+
*/
74+
export function BuildChatAnimation() {
75+
const [phase, setPhase] = useState<BuildChatPhase>('idle')
76+
const [typedChars, setTypedChars] = useState(0)
77+
const [revealedWords, setRevealedWords] = useState(0)
78+
const [reducedMotion, setReducedMotion] = useState(false)
79+
const [morphing, setMorphing] = useState(false)
80+
const [gooBlur, setGooBlur] = useState(0)
81+
const [chatContentH, setChatContentH] = useState(0)
82+
const [composerH, setComposerH] = useState(94)
83+
const chatContentRef = useRef<HTMLDivElement>(null)
84+
const composerRef = useRef<HTMLDivElement>(null)
85+
86+
useEffect(() => {
87+
const content = chatContentRef.current
88+
const composer = composerRef.current
89+
if (!content || !composer) return
90+
const measure = () => {
91+
setChatContentH(content.offsetHeight)
92+
setComposerH(composer.offsetHeight)
93+
}
94+
measure()
95+
const observer = new ResizeObserver(measure)
96+
observer.observe(content)
97+
observer.observe(composer)
98+
return () => observer.disconnect()
99+
}, [])
100+
101+
useEffect(() => {
102+
if (!morphing) return
103+
let progress = 0
104+
const interval = setInterval(() => {
105+
progress += GOO_STEP_MS / GOO_DECAY_MS
106+
if (progress >= 1) {
107+
setGooBlur(0)
108+
setMorphing(false)
109+
clearInterval(interval)
110+
return
111+
}
112+
setGooBlur(GOO_MAX_BLUR * (1 - progress) ** 2)
113+
}, GOO_STEP_MS)
114+
return () => clearInterval(interval)
115+
}, [morphing])
116+
117+
useEffect(() => {
118+
const media = window.matchMedia('(prefers-reduced-motion: reduce)')
119+
const timeouts: ReturnType<typeof setTimeout>[] = []
120+
const intervals: ReturnType<typeof setInterval>[] = []
121+
122+
const clearScheduled = () => {
123+
for (const timeout of timeouts) clearTimeout(timeout)
124+
for (const interval of intervals) clearInterval(interval)
125+
timeouts.length = 0
126+
intervals.length = 0
127+
}
128+
129+
const showFinished = () => {
130+
setReducedMotion(true)
131+
setPhase('complete')
132+
setTypedChars(0)
133+
setRevealedWords(REPLY_WORDS.length)
134+
setMorphing(false)
135+
setGooBlur(0)
136+
}
137+
138+
const startTyping = () => {
139+
setPhase('typing')
140+
const startedAt = performance.now()
141+
const interval = setInterval(() => {
142+
const elapsed = performance.now() - startedAt
143+
const nextChars = Math.min(Math.floor(elapsed / TYPE_CHAR_MS) + 1, PROMPT.length)
144+
setTypedChars(nextChars)
145+
if (nextChars >= PROMPT.length) clearInterval(interval)
146+
}, TYPE_CHAR_MS)
147+
intervals.push(interval)
148+
}
149+
150+
const startReply = () => {
151+
setPhase('replying')
152+
const startedAt = performance.now()
153+
const interval = setInterval(() => {
154+
const elapsed = performance.now() - startedAt
155+
const nextWords = Math.min(Math.floor(elapsed / REPLY_WORD_MS) + 1, REPLY_WORDS.length)
156+
setRevealedWords(nextWords)
157+
if (nextWords >= REPLY_WORDS.length) {
158+
clearInterval(interval)
159+
setPhase('complete')
160+
}
161+
}, REPLY_WORD_MS)
162+
intervals.push(interval)
163+
}
164+
165+
const runLoop = () => {
166+
clearScheduled()
167+
setReducedMotion(false)
168+
setPhase('idle')
169+
setTypedChars(0)
170+
setRevealedWords(0)
171+
setMorphing(false)
172+
setGooBlur(0)
173+
174+
timeouts.push(setTimeout(startTyping, TYPE_START_MS))
175+
timeouts.push(
176+
setTimeout(() => {
177+
setPhase('thinking')
178+
setTypedChars(0)
179+
setMorphing(true)
180+
setGooBlur(GOO_MAX_BLUR)
181+
}, SEND_MS)
182+
)
183+
timeouts.push(setTimeout(() => setPhase('replyPreparing'), THINKING_DONE_MS))
184+
timeouts.push(setTimeout(startReply, THINKING_DONE_MS + REPLY_START_DELAY_MS))
185+
timeouts.push(
186+
setTimeout(() => {
187+
setPhase('merging')
188+
setMorphing(true)
189+
setGooBlur(GOO_MAX_BLUR)
190+
}, MERGE_MS)
191+
)
192+
timeouts.push(setTimeout(runLoop, LOOP_MS))
193+
}
194+
195+
const syncMotionPreference = () => {
196+
clearScheduled()
197+
if (media.matches) {
198+
showFinished()
199+
return
200+
}
201+
runLoop()
202+
}
203+
204+
syncMotionPreference()
205+
media.addEventListener('change', syncMotionPreference)
206+
return () => {
207+
media.removeEventListener('change', syncMotionPreference)
208+
clearScheduled()
209+
}
210+
}, [])
211+
212+
const composerText = phase === 'typing' ? PROMPT.slice(0, typedChars) : ''
213+
const chatOpen = reducedMotion || !['idle', 'typing', 'merging'].includes(phase)
214+
const thinkingPhraseVisible = phase === 'thinking'
215+
const replyVisible =
216+
reducedMotion || phase === 'replyPreparing' || phase === 'replying' || phase === 'complete'
217+
const replyComplete = reducedMotion || phase === 'complete'
218+
const replyText = REPLY_WORDS.slice(0, reducedMotion ? REPLY_WORDS.length : revealedWords).join(
219+
' '
220+
)
221+
222+
return (
223+
<div className='absolute inset-0 flex items-end justify-start p-10 max-sm:px-3 max-sm:py-0 max-lg:items-center max-lg:justify-center max-lg:px-8 max-lg:py-8'>
224+
<svg width='0' height='0' aria-hidden='true' className='absolute'>
225+
<defs>
226+
<filter id='build-callout-goo'>
227+
<feGaussianBlur in='SourceGraphic' stdDeviation={gooBlur} result='blur' />
228+
<feColorMatrix
229+
in='blur'
230+
type='matrix'
231+
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -8'
232+
result='goo'
233+
/>
234+
<feComposite in='SourceGraphic' in2='goo' operator='atop' />
235+
</filter>
236+
</defs>
237+
</svg>
238+
239+
<div className='relative h-[310px] w-full max-w-[560px] max-sm:h-[270px] max-sm:max-w-[430px]'>
240+
{!reducedMotion && (
241+
<div
242+
aria-hidden='true'
243+
className='pointer-events-none absolute inset-0 z-0 flex flex-col justify-end [filter:url(#build-callout-goo)_drop-shadow(0_1px_3px_rgba(28,40,64,0.10))]'
244+
>
245+
<div
246+
className={cn(
247+
'w-full rounded-2xl bg-[var(--white)] transition-[height,transform,margin-bottom] duration-500 ease-out motion-reduce:transition-none',
248+
chatOpen ? 'translate-y-0' : 'translate-y-5'
249+
)}
250+
style={{
251+
height: chatOpen ? chatContentH : 0,
252+
marginBottom: chatOpen ? SPLIT_GAP_PX : 0,
253+
}}
254+
/>
255+
<div className='w-full rounded-2xl bg-[var(--white)]' style={{ height: composerH }} />
256+
</div>
257+
)}
258+
259+
<div className='relative z-10 flex h-full flex-col justify-end'>
260+
<section
261+
className={cn(
262+
'w-full overflow-hidden rounded-2xl bg-[var(--white)]',
263+
!reducedMotion &&
264+
(chatOpen ? CHAT_SURFACE_TRANSITION_OPEN : CHAT_SURFACE_TRANSITION_MERGE),
265+
chatOpen
266+
? 'translate-y-0 opacity-100 shadow-[0_24px_80px_color-mix(in_srgb,var(--text-primary)_14%,transparent),0_0_0_1px_var(--border-1)]'
267+
: 'translate-y-5 opacity-0 shadow-[0_24px_80px_transparent,0_0_0_1px_transparent]'
268+
)}
269+
style={{
270+
height: chatOpen ? chatContentH : 0,
271+
marginBottom: chatOpen ? SPLIT_GAP_PX : 0,
272+
}}
273+
>
274+
<div ref={chatContentRef} className='flex flex-col gap-4 p-4 max-sm:gap-2.5 max-sm:p-3'>
275+
<div className='ml-auto max-w-[78%] rounded-lg bg-[var(--surface-5)] px-3 py-2 text-[15px] text-[var(--text-primary)] leading-[1.45] max-sm:max-w-[86%] max-sm:text-[12px] max-sm:leading-[1.35]'>
276+
{PROMPT}
277+
</div>
278+
279+
<div className='relative min-h-[34px]'>
280+
<div
281+
className={cn(
282+
'absolute inset-0 flex items-center transition-opacity duration-300 ease-out motion-reduce:transition-none',
283+
thinkingPhraseVisible ? 'opacity-100' : 'opacity-0'
284+
)}
285+
>
286+
<ThinkingLoader
287+
size={20}
288+
startVariant='corners'
289+
phase
290+
labelRatio={0.66}
291+
className='text-[var(--text-body)]'
292+
/>
293+
</div>
294+
295+
<div
296+
className={cn(
297+
'flex items-start gap-2.5 text-[15px] text-[var(--text-primary)] leading-[1.5] transition-opacity duration-300 ease-out motion-reduce:transition-none max-sm:text-[12px] max-sm:leading-[1.4]',
298+
replyVisible ? 'opacity-100' : 'opacity-0'
299+
)}
300+
>
301+
<span className='relative mt-0.5 size-[20px] shrink-0'>
302+
<span
303+
className={cn(
304+
'absolute inset-0 flex items-center justify-center transition-opacity duration-200 ease-out motion-reduce:transition-none',
305+
replyComplete ? 'opacity-0' : 'opacity-100'
306+
)}
307+
>
308+
<ThinkingLoader size={20} startVariant='corners' />
309+
</span>
310+
<span
311+
className={cn(
312+
'absolute inset-0 flex items-center justify-center transition-opacity duration-200 ease-out motion-reduce:transition-none',
313+
replyComplete ? 'opacity-100' : 'opacity-0'
314+
)}
315+
>
316+
<Blimp className='size-[18px] text-[var(--text-icon)]' />
317+
</span>
318+
</span>
319+
<p className='flex-1'>{replyText}</p>
320+
</div>
321+
</div>
322+
</div>
323+
</section>
324+
325+
<div
326+
ref={composerRef}
327+
className='h-[94px] w-full rounded-2xl border border-[var(--border-1)] bg-[var(--white)] px-2.5 py-2 shadow-[0_18px_60px_color-mix(in_srgb,var(--text-primary)_12%,transparent)]'
328+
>
329+
<p
330+
className={cn(
331+
'min-h-[42px] px-1.5 pt-1 text-[15px] leading-[1.35] max-sm:text-[13px]',
332+
composerText ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)]'
333+
)}
334+
>
335+
{composerText || 'Send message to Sim'}
336+
</p>
337+
<div className='flex items-center gap-1.5'>
338+
<span className='flex size-[28px] items-center justify-center rounded-full'>
339+
<Plus className='size-[16px] text-[var(--text-icon)]' />
340+
</span>
341+
<span className='flex size-[28px] items-center justify-center rounded-full'>
342+
<Paperclip className='size-[16px] text-[var(--text-icon)]' />
343+
</span>
344+
<span className='flex size-[28px] items-center justify-center rounded-full'>
345+
<Slash className='size-[16px] text-[var(--text-icon)]' />
346+
</span>
347+
<span className='ml-auto flex items-center gap-1.5'>
348+
<span className='flex size-[28px] items-center justify-center rounded-full'>
349+
<Mic className='size-[16px] text-[var(--text-icon)]' />
350+
</span>
351+
<span className='flex size-[28px] items-center justify-center rounded-full bg-[#808080]'>
352+
<ArrowUp className='size-[16px] text-[var(--surface-1)]' />
353+
</span>
354+
</span>
355+
</div>
356+
</div>
357+
</div>
358+
</div>
359+
</div>
360+
)
361+
}
Collapse file

‎apps/sim/app/(landing)/components/features/components/build-callout/components/build-chat-animation/index.ts‎

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { BuildChatAnimation } from './build-chat-animation'
Collapse file

‎apps/sim/app/(landing)/components/features/components/build-callout/components/workflow-showcase/index.ts‎

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { WorkflowShowcase } from './workflow-showcase'

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.