Skip to content

Commit 95a7e22

Browse files
committed
Add terraform animation
1 parent 37fc5e9 commit 95a7e22

12 files changed

Lines changed: 1031 additions & 128 deletions

File tree

‎src/components/Animations/Terraform/client/index.ts‎

Lines changed: 543 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { isButtonElement } from '@components/scripts/assertions/elements'
2+
3+
const SELECTORS = {
4+
toggleButton: '[data-animation-toggle]',
5+
pauseIcon: '[data-animation-icon="pause"]',
6+
playIcon: '[data-animation-icon="play"]',
7+
} as const
8+
9+
export function queryAnimationToggleButton(scope: ParentNode): HTMLButtonElement | null {
10+
const button = scope.querySelector(SELECTORS.toggleButton)
11+
if (!button) return null
12+
return isButtonElement(button) ? button : null
13+
}
14+
15+
export function queryAnimationTogglePauseIcon(scope: ParentNode): Element | null {
16+
return scope.querySelector(SELECTORS.pauseIcon)
17+
}
18+
19+
export function queryAnimationTogglePlayIcon(scope: ParentNode): Element | null {
20+
return scope.querySelector(SELECTORS.playIcon)
21+
}
22+
23+
/**
24+
* Query a primary edge `<path>` by its ID within the given scope.
25+
* Returns the element cast to `SVGPathElement` so callers can use `getTotalLength()`.
26+
*/
27+
export function queryEdgePath(scope: ParentNode, id: string): SVGPathElement | null {
28+
return scope.querySelector<SVGPathElement>(`#${id}`)
29+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
/**
3+
* Terraform dependency graph animation for home page Hero.
4+
* Depicts a `terraform graph` style two-tier dependency tree inside a
5+
* terminal frame, animated with GSAP to show init → plan → apply workflow.
6+
*/
7+
import Icon from '@components/Icon/index.astro'
8+
---
9+
10+
<terraform-animation class="hidden lg:flex order-1 lg:order-2 justify-center items-start">
11+
<div class="relative w-full max-w-lg mx-auto flex flex-col items-start">
12+
<div class="w-full h-auto rounded-2xl">
13+
<svg
14+
id="terraformAnimation"
15+
aria-hidden="true"
16+
focusable="false"
17+
xmlns="http://www.w3.org/2000/svg"
18+
class="block w-full h-auto max-w-full"
19+
viewBox="0 0 600 600"
20+
preserveAspectRatio="xMidYMid meet"
21+
style="visibility: hidden;"
22+
>
23+
<defs>
24+
<filter id="tf-check-glow" x="-50%" y="-50%" width="200%" height="200%">
25+
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur" />
26+
<feMerge>
27+
<feMergeNode in="blur" />
28+
<feMergeNode in="SourceGraphic" />
29+
</feMerge>
30+
</filter>
31+
</defs>
32+
33+
<!-- ===== TERMINAL FRAME ===== -->
34+
<g id="tf-frame">
35+
<rect id="tf-frame-rect" x="40" y="40" width="520" height="520" rx="10" fill="var(--color-primary)" stroke="var(--color-primary-offset)" stroke-width="2" />
36+
<rect x="40" y="40" width="520" height="32" rx="10" fill="var(--color-primary-offset)" />
37+
<rect x="40" y="60" width="520" height="12" fill="var(--color-primary-offset)" />
38+
<circle cx="62" cy="56" r="5" fill="#dc2626" opacity="0.7" />
39+
<circle cx="78" cy="56" r="5" fill="var(--color-spotlight)" opacity="0.7" />
40+
<circle cx="94" cy="56" r="5" fill="var(--color-success)" opacity="0.7" />
41+
<text x="300" y="61" fill="var(--color-page-base)" font-family="monospace" font-size="11" text-anchor="middle" opacity="0.6">terraform — plan &amp; apply</text>
42+
</g>
43+
44+
<!-- ===== ROOT NODE: main.tf ===== -->
45+
<g id="tf-node-root" transform="translate(300,120)">
46+
<rect x="-52" y="-18" width="104" height="36" rx="6" fill="var(--color-primary-offset)" stroke="var(--color-content-offset)" stroke-width="1.5" />
47+
<rect x="-40" y="-10" width="14" height="16" rx="1" fill="none" stroke="var(--color-page-base)" stroke-width="1.2" />
48+
<polyline points="-32,-10 -32,-6 -26,-6" fill="none" stroke="var(--color-page-base)" stroke-width="1.2" />
49+
<text id="tf-root-text" x="-18" y="5" fill="var(--color-page-base)" font-family="monospace" font-size="13" font-weight="bold">main.tf</text>
50+
<g id="tf-check-root" opacity="0" filter="url(#tf-check-glow)">
51+
<circle cx="60" cy="0" r="8" fill="var(--color-success)" />
52+
<polyline points="55,0 58,4 65,-4" fill="none" stroke="var(--color-page-base)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
53+
</g>
54+
</g>
55+
56+
<!-- ===== PRIMARY EDGES (root → row 1) ===== -->
57+
<g id="tf-edges-primary" fill="none" stroke="var(--color-content-offset)" stroke-width="1.5">
58+
<path id="tf-edge-vpc" d="M270,138 C270,162 150,162 150,186" />
59+
<polygon id="tf-arrow-vpc" points="150,186 146,178 154,178" fill="var(--color-content-offset)" />
60+
<path id="tf-edge-iam" d="M300,138 C300,158 300,166 300,186" />
61+
<polygon id="tf-arrow-iam" points="300,186 296,178 304,178" fill="var(--color-content-offset)" />
62+
<path id="tf-edge-s3" d="M330,138 C330,162 450,162 450,186" />
63+
<polygon id="tf-arrow-s3" points="450,186 446,178 454,178" fill="var(--color-content-offset)" />
64+
</g>
65+
66+
<!-- ===== CROSS-DEPENDENCY EDGES (row 1 → row 2) ===== -->
67+
<g id="tf-edges-cross" fill="none" stroke="var(--color-content-offset)" stroke-width="1" stroke-dasharray="6 3">
68+
<path id="tf-edge-vpc-eks" d="M150,234 C150,260 210,260 210,286" />
69+
<polygon id="tf-arrow-vpc-eks" points="210,286 207,279 213,279" fill="var(--color-content-offset)" />
70+
<path id="tf-edge-iam-eks" d="M300,234 C300,260 210,264 210,286" />
71+
<polygon id="tf-arrow-iam-eks" points="210,286 207,279 213,279" fill="var(--color-content-offset)" />
72+
<path id="tf-edge-vpc-rds" d="M175,234 C175,268 370,260 390,286" />
73+
<polygon id="tf-arrow-vpc-rds" points="390,286 387,279 393,279" fill="var(--color-content-offset)" />
74+
<path id="tf-edge-iam-rds" d="M320,234 C320,260 390,264 390,286" />
75+
<polygon id="tf-arrow-iam-rds" points="390,286 387,279 393,279" fill="var(--color-content-offset)" />
76+
</g>
77+
78+
<!-- ===== ROW 1: VPC ===== -->
79+
<g id="tf-node-vpc" transform="translate(150,210)">
80+
<rect x="-48" y="-24" width="96" height="48" rx="6" fill="var(--color-primary-offset)" stroke="var(--color-content-offset)" stroke-width="1.5" />
81+
<line x1="-28" y1="-8" x2="-6" y2="-8" stroke="var(--color-page-base)" stroke-width="1" opacity="0.5" />
82+
<line x1="-28" y1="0" x2="-6" y2="0" stroke="var(--color-page-base)" stroke-width="1" opacity="0.5" />
83+
<line x1="-28" y1="8" x2="-6" y2="8" stroke="var(--color-page-base)" stroke-width="1" opacity="0.5" />
84+
<line x1="-22" y1="-14" x2="-22" y2="14" stroke="var(--color-page-base)" stroke-width="1" opacity="0.5" />
85+
<line x1="-16" y1="-14" x2="-16" y2="14" stroke="var(--color-page-base)" stroke-width="1" opacity="0.5" />
86+
<line x1="-10" y1="-14" x2="-10" y2="14" stroke="var(--color-page-base)" stroke-width="1" opacity="0.5" />
87+
<text x="16" y="4" fill="var(--color-page-base)" font-family="monospace" font-size="11">VPC</text>
88+
<g id="tf-check-vpc" opacity="0" filter="url(#tf-check-glow)">
89+
<circle cx="54" cy="-14" r="8" fill="var(--color-success)" />
90+
<polyline points="49,-14 52,-10 59,-18" fill="none" stroke="var(--color-page-base)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
91+
</g>
92+
<rect id="tf-flash-vpc" x="-48" y="-24" width="96" height="48" rx="6" fill="none" stroke="var(--color-success)" stroke-width="2.5" opacity="0" />
93+
</g>
94+
95+
<!-- ===== ROW 1: IAM ===== -->
96+
<g id="tf-node-iam" transform="translate(300,210)">
97+
<rect x="-48" y="-24" width="96" height="48" rx="6" fill="var(--color-primary-offset)" stroke="var(--color-content-offset)" stroke-width="1.5" />
98+
<path d="M-18,-14 L-28,-7 L-28,2 C-28,10 -18,16 -18,16 C-18,16 -8,10 -8,2 L-8,-7z" fill="none" stroke="var(--color-page-base)" stroke-width="1.3" stroke-linejoin="round" />
99+
<polyline points="-22,0 -18,4 -12,-4" fill="none" stroke="var(--color-page-base)" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" />
100+
<text x="12" y="4" fill="var(--color-page-base)" font-family="monospace" font-size="11">IAM</text>
101+
<g id="tf-check-iam" opacity="0" filter="url(#tf-check-glow)">
102+
<circle cx="54" cy="-14" r="8" fill="var(--color-success)" />
103+
<polyline points="49,-14 52,-10 59,-18" fill="none" stroke="var(--color-page-base)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
104+
</g>
105+
<rect id="tf-flash-iam" x="-48" y="-24" width="96" height="48" rx="6" fill="none" stroke="var(--color-success)" stroke-width="2.5" opacity="0" />
106+
</g>
107+
108+
<!-- ===== ROW 1: S3 ===== -->
109+
<g id="tf-node-s3" transform="translate(450,210)">
110+
<rect x="-48" y="-24" width="96" height="48" rx="6" fill="var(--color-primary-offset)" stroke="var(--color-content-offset)" stroke-width="1.5" />
111+
<path d="M-28,-10 L-24,10 L-8,10 L-4,-10z" fill="none" stroke="var(--color-page-base)" stroke-width="1.3" stroke-linejoin="round" />
112+
<ellipse cx="-16" cy="-10" rx="12" ry="4" fill="none" stroke="var(--color-page-base)" stroke-width="1.2" />
113+
<path d="M-27,0 Q-16,4 -5,0" fill="none" stroke="var(--color-page-base)" stroke-width="0.8" opacity="0.5" />
114+
<text x="16" y="4" fill="var(--color-page-base)" font-family="monospace" font-size="11">S3</text>
115+
<g id="tf-check-s3" opacity="0" filter="url(#tf-check-glow)">
116+
<circle cx="54" cy="-14" r="8" fill="var(--color-success)" />
117+
<polyline points="49,-14 52,-10 59,-18" fill="none" stroke="var(--color-page-base)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
118+
</g>
119+
<rect id="tf-flash-s3" x="-48" y="-24" width="96" height="48" rx="6" fill="none" stroke="var(--color-success)" stroke-width="2.5" opacity="0" />
120+
</g>
121+
122+
<!-- ===== ROW 2: EKS ===== -->
123+
<g id="tf-node-eks" transform="translate(210,310)">
124+
<rect x="-48" y="-24" width="96" height="48" rx="6" fill="var(--color-primary-offset)" stroke="var(--color-content-offset)" stroke-width="1.5" />
125+
<circle cx="-18" cy="0" r="6" fill="none" stroke="var(--color-page-base)" stroke-width="1.2" />
126+
<circle cx="-18" cy="0" r="2.5" fill="var(--color-page-base)" opacity="0.5" />
127+
<line x1="-18" y1="-6" x2="-18" y2="-12" stroke="var(--color-page-base)" stroke-width="1.2" stroke-linecap="round" />
128+
<line x1="-12.8" y1="-3" x2="-9" y2="-5.2" stroke="var(--color-page-base)" stroke-width="1.2" stroke-linecap="round" />
129+
<line x1="-12.8" y1="3" x2="-9" y2="5.2" stroke="var(--color-page-base)" stroke-width="1.2" stroke-linecap="round" />
130+
<line x1="-18" y1="6" x2="-18" y2="12" stroke="var(--color-page-base)" stroke-width="1.2" stroke-linecap="round" />
131+
<line x1="-23.2" y1="3" x2="-27" y2="5.2" stroke="var(--color-page-base)" stroke-width="1.2" stroke-linecap="round" />
132+
<line x1="-23.2" y1="-3" x2="-27" y2="-5.2" stroke="var(--color-page-base)" stroke-width="1.2" stroke-linecap="round" />
133+
<text x="8" y="4" fill="var(--color-page-base)" font-family="monospace" font-size="11">EKS</text>
134+
<g id="tf-check-eks" opacity="0" filter="url(#tf-check-glow)">
135+
<circle cx="54" cy="-14" r="8" fill="var(--color-success)" />
136+
<polyline points="49,-14 52,-10 59,-18" fill="none" stroke="var(--color-page-base)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
137+
</g>
138+
<rect id="tf-flash-eks" x="-48" y="-24" width="96" height="48" rx="6" fill="none" stroke="var(--color-success)" stroke-width="2.5" opacity="0" />
139+
</g>
140+
141+
<!-- ===== ROW 2: RDS ===== -->
142+
<g id="tf-node-rds" transform="translate(390,310)">
143+
<rect x="-48" y="-24" width="96" height="48" rx="6" fill="var(--color-primary-offset)" stroke="var(--color-content-offset)" stroke-width="1.5" />
144+
<ellipse cx="-18" cy="-8" rx="10" ry="5" fill="none" stroke="var(--color-page-base)" stroke-width="1.2" />
145+
<line x1="-28" y1="-8" x2="-28" y2="8" stroke="var(--color-page-base)" stroke-width="1.2" />
146+
<line x1="-8" y1="-8" x2="-8" y2="8" stroke="var(--color-page-base)" stroke-width="1.2" />
147+
<ellipse cx="-18" cy="8" rx="10" ry="5" fill="none" stroke="var(--color-page-base)" stroke-width="1.2" />
148+
<path d="M-28,0 Q-18,4 -8,0" fill="none" stroke="var(--color-page-base)" stroke-width="0.8" opacity="0.5" />
149+
<text x="10" y="4" fill="var(--color-page-base)" font-family="monospace" font-size="11">RDS</text>
150+
<g id="tf-check-rds" opacity="0" filter="url(#tf-check-glow)">
151+
<circle cx="54" cy="-14" r="8" fill="var(--color-success)" />
152+
<polyline points="49,-14 52,-10 59,-18" fill="none" stroke="var(--color-page-base)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
153+
</g>
154+
<rect id="tf-flash-rds" x="-48" y="-24" width="96" height="48" rx="6" fill="none" stroke="var(--color-success)" stroke-width="2.5" opacity="0" />
155+
</g>
156+
157+
<!-- ===== TERMINAL OUTPUT ===== -->
158+
<g id="tf-terminal" transform="translate(70,370)">
159+
<line id="tf-separator" x1="-30" y1="-8" x2="490" y2="-8" stroke="var(--color-primary-offset)" stroke-width="1" />
160+
<g id="tf-term-line-1" opacity="0">
161+
<text x="0" y="10" fill="var(--color-success)" font-family="monospace" font-size="12">$</text>
162+
<text x="14" y="10" fill="var(--color-page-base)" font-family="monospace" font-size="12">terraform init</text>
163+
</g>
164+
<g id="tf-term-line-2" opacity="0">
165+
<text x="0" y="28" fill="var(--color-content-offset)" font-family="monospace" font-size="11">Initializing provider plugins...</text>
166+
</g>
167+
<g id="tf-term-line-3" opacity="0">
168+
<text x="0" y="48" fill="var(--color-success)" font-family="monospace" font-size="12">$</text>
169+
<text x="14" y="48" fill="var(--color-page-base)" font-family="monospace" font-size="12">terraform plan</text>
170+
</g>
171+
<g id="tf-term-line-4" opacity="0">
172+
<text x="0" y="66" fill="var(--color-content-offset)" font-family="monospace" font-size="11">Plan: 5 to add, 0 to change, 0 to destroy.</text>
173+
</g>
174+
<g id="tf-term-line-5" opacity="0">
175+
<text x="0" y="86" fill="var(--color-success)" font-family="monospace" font-size="12">$</text>
176+
<text x="14" y="86" fill="var(--color-page-base)" font-family="monospace" font-size="12">terraform apply -auto-approve</text>
177+
</g>
178+
<g id="tf-term-line-6" opacity="0">
179+
<text x="0" y="104" fill="var(--color-content-offset)" font-family="monospace" font-size="11">aws_vpc.main: Creating...</text>
180+
</g>
181+
<g id="tf-term-line-7" opacity="0">
182+
<text x="0" y="120" fill="var(--color-content-offset)" font-family="monospace" font-size="11">aws_eks_cluster.platform: Creating...</text>
183+
</g>
184+
<g id="tf-term-line-8" opacity="0">
185+
<text x="0" y="136" fill="var(--color-content-offset)" font-family="monospace" font-size="11">aws_db_instance.primary: Creating...</text>
186+
</g>
187+
<g id="tf-term-complete" opacity="0">
188+
<text x="0" y="162" fill="var(--color-success)" font-family="monospace" font-size="13" font-weight="bold">Apply complete! Resources: 5 added, 0 changed, 0 destroyed.</text>
189+
</g>
190+
<rect id="tf-cursor" x="0" y="170" width="8" height="13" rx="1" fill="var(--color-success)" opacity="0" />
191+
</g>
192+
</svg>
193+
</div>
194+
<button
195+
type="button"
196+
class="z-(--z-raised) flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-inverse transition-colors hover:bg-primary-offset"
197+
data-animation-toggle
198+
aria-label="Pause animation"
199+
aria-pressed="false"
200+
>
201+
<span
202+
data-animation-icon="pause"
203+
class="flex h-12 w-12 items-center justify-center bg-transparent"
204+
>
205+
<Icon name="pause" size={20} variant="custom" class="text-primary-inverse fill-primary-inverse stroke-primary-inverse" />
206+
</span>
207+
<span
208+
data-animation-icon="play"
209+
class="hidden h-12 w-12 items-center justify-center bg-transparent"
210+
>
211+
<Icon name="play" size={20} variant="custom" class="text-primary-inverse fill-primary-inverse stroke-primary-inverse" />
212+
</span>
213+
</button>
214+
</div>
215+
</terraform-animation>
216+
217+
<script>
218+
import { registerTerraformAnimationWebComponent } from '@components/Animations/Terraform/client'
219+
registerTerraformAnimationWebComponent()
220+
</script>

0 commit comments

Comments
 (0)