-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFrontend-Stacks-Visual-Guide.html
More file actions
678 lines (628 loc) · 55.1 KB
/
Copy pathFrontend-Stacks-Visual-Guide.html
File metadata and controls
678 lines (628 loc) · 55.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Frontend Stacks · Interactive Visual Guide 2026</title>
<meta name="description" content="Interactive visual guide to frontend tech stacks in 2026. Compare frameworks, meta-frameworks, styling, state management, build tools, and more." />
<meta name="keywords" content="frontend, tech stack, 2026, React, Vue, Svelte, Next.js, Tailwind, TypeScript, framework comparison" />
<meta property="og:title" content="Frontend Tech Stacks · Visual Guide 2026" />
<meta property="og:description" content="Mind-map view of the 2026 frontend universe — frameworks, meta-frameworks, styling, state, forms, build tools, UI libraries, and hosting." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://opentechstack.stitchwebsite.com/Frontend-Stacks-Visual-Guide.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Frontend Tech Stacks · Visual Guide 2026" />
<meta name="twitter:description" content="Interactive guide to the 2026 frontend stack landscape. Compare 60+ technologies across 10 categories." />
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large" />
<link rel="canonical" href="https://opentechstack.stitchwebsite.com/Frontend-Stacks-Visual-Guide.html" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"Article","name":"Frontend Stacks Visual Guide 2026","headline":"Frontend Tech Stacks — Interactive Visual Guide 2026","description":"Interactive guide to 60+ frontend technologies: React, Vue, Svelte, Next.js, Astro, Tailwind, shadcn/ui, Zustand, TanStack Query, Vite, Vitest, Playwright, and more.","url":"https://opentechstack.stitchwebsite.com/Frontend-Stacks-Visual-Guide.html","publisher":{"@type":"Organization","name":"Open TechStack","url":"https://opentechstack.stitchwebsite.com"},"datePublished":"2026-04-06","dateModified":"2026-04-06","keywords":"frontend, React, Vue, Svelte, Next.js, Astro, Tailwind, tech stack, 2026","isPartOf":{"@type":"WebSite","name":"Open TechStack","url":"https://opentechstack.stitchwebsite.com"}}
</script>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={darkMode:"class"}</script>
<link rel="stylesheet" href="dark-mode.css">
<script src="dark-mode.js"></script>
<style>
html { scroll-behavior: smooth; }
body { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.cat-fw { --c:#06b6d4; --cs:#ecfeff; } /* UI frameworks */
.cat-meta { --c:#6366f1; --cs:#eef2ff; } /* Meta-frameworks */
.cat-style { --c:#ec4899; --cs:#fdf2f8; } /* Styling */
.cat-state { --c:#10b981; --cs:#ecfdf5; } /* State */
.cat-data { --c:#14b8a6; --cs:#f0fdfa; } /* Data fetching */
.cat-form { --c:#f59e0b; --cs:#fffbeb; } /* Forms/validation */
.cat-build { --c:#8b5cf6; --cs:#f5f3ff; } /* Build/tooling */
.cat-test { --c:#0ea5e9; --cs:#f0f9ff; } /* Testing */
.cat-ui { --c:#f97316; --cs:#fff7ed; } /* UI libs */
.cat-host { --c:#e11d48; --cs:#fff1f2; } /* Hosting */
.chip { background: var(--cs); color: var(--c); border: 1px solid color-mix(in srgb, var(--c) 25%, transparent); }
.node { cursor: pointer; transition: transform .2s ease, opacity .3s ease; transform-origin: center; transform-box: fill-box; }
.node:hover { transform: scale(1.08); }
.node.dim { opacity: .18; }
.link { transition: opacity .3s ease, stroke-width .2s ease; }
.link.dim { opacity: .1; }
.card { transition: opacity .25s ease, transform .25s ease, box-shadow .25s ease; }
.card.hidden-card { opacity: 0; transform: scale(.96); pointer-events: none; position: absolute; }
.card.flash { animation: flash 1.2s ease; }
@keyframes flash {
0%,100% { box-shadow: 0 1px 2px rgba(0,0,0,.05); }
30% { box-shadow: 0 0 0 4px #fde68a, 0 10px 30px rgba(245,158,11,.25); }
}
.stage-btn.active { background: #0f172a; color: white; box-shadow: 0 8px 20px rgba(15,23,42,.25); }
.filter-chip.active { background: #0f172a !important; color: white !important; border-color: #0f172a !important; }
@media print {
.no-print { display: none !important; }
.card.hidden-card { display: none !important; }
}
</style>
</head>
<body class="bg-slate-50 text-slate-800">
<!-- ============ NAV ============ -->
<nav class="bg-slate-900 text-white text-sm sticky top-0 z-50">
<div class="max-w-5xl mx-auto px-6 py-2.5 flex items-center justify-between">
<a href="index.html" class="font-bold tracking-tight hover:text-slate-300 transition">⚡ Open TechStack</a>
<div class="flex items-center gap-4 flex-wrap">
<a href="Frontend-Stacks-Visual-Guide.html" class="hover:text-slate-300 transition">Frontend</a>
<a href="Backend-Stacks-Visual-Guide.html" class="hover:text-slate-300 transition">Backend</a>
<a href="AIML-Stacks-Visual-Guide.html" class="hover:text-slate-300 transition">AI/ML</a>
<a href="AI-Coding-Tools-Visual-Guide.html" class="hover:text-slate-300 transition">AI Coding</a>
<a href="DevOps-Stacks-Visual-Guide.html" class="hover:text-slate-300 transition">DevOps</a>
<a href="Mobile-Stacks-Visual-Guide.html" class="hover:text-slate-300 transition">Mobile</a>
<a href="Stack-Combos.html" class="hover:text-slate-300 transition">Combos</a>
<a href="stack-picker.html" class="hover:text-slate-300 transition">Picker</a>
<a href="stack-detector.html" class="hover:text-slate-300 transition text-emerald-400 font-semibold">Detector</a>
<a href="blog/" class="hover:text-slate-300 transition">Blog</a>
<a href="compare/" class="hover:text-slate-300 transition">Compare</a>
<a href="https://github.com/JagadeepPortfolio/awesome-vibecoding-techstacks" target="_blank" rel="noopener" class="hover:text-slate-300 transition">GitHub</a>
</div>
</div>
</nav>
<header class="relative overflow-hidden border-b border-slate-200 bg-gradient-to-br from-white via-cyan-50 to-pink-50">
<div class="max-w-7xl mx-auto px-6 py-10">
<div class="flex items-start justify-between gap-6 flex-wrap">
<div class="max-w-3xl">
<div class="inline-flex items-center gap-2 text-xs font-medium text-cyan-700 bg-cyan-100 rounded-full px-3 py-1 mb-3">
<span class="w-2 h-2 rounded-full bg-cyan-500"></span>
Interactive guide · 2026 edition
</div>
<h1 class="text-4xl md:text-5xl font-extrabold tracking-tight text-slate-900">Frontend Tech Stacks, Visualised</h1>
<p class="mt-3 text-slate-600 text-lg">
A mind-map view of the 2026 frontend universe — frameworks, meta-frameworks, styling, state, forms, build tools, UI libraries, and hosting.
Pick your stage, filter by what matters, and see the stack that fits.
</p>
</div>
<div class="no-print flex items-center gap-2 text-xs text-slate-500">
<kbd class="px-2 py-1 bg-white border border-slate-300 rounded">/</kbd> search
<kbd class="px-2 py-1 bg-white border border-slate-300 rounded">1-4</kbd> stage
<kbd class="px-2 py-1 bg-white border border-slate-300 rounded">Esc</kbd> reset
</div>
</div>
</div>
</header>
<section class="no-print sticky top-0 z-30 bg-white/90 backdrop-blur border-b border-slate-200 shadow-sm">
<div class="max-w-7xl mx-auto px-6 py-4 flex flex-col gap-3">
<div class="flex items-center gap-3 flex-wrap">
<div class="relative flex-1 min-w-[220px]">
<svg class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<input id="search" type="text" placeholder="Search framework, library, or tool…"
class="w-full pl-9 pr-3 py-2 rounded-lg border border-slate-300 focus:outline-none focus:ring-2 focus:ring-cyan-400 bg-white text-sm" />
</div>
<label class="inline-flex items-center gap-2 text-sm text-slate-600"><input type="checkbox" id="f-free" class="rounded border-slate-300"> Free / OSS</label>
<label class="inline-flex items-center gap-2 text-sm text-slate-600"><input type="checkbox" id="f-ts" class="rounded border-slate-300"> TypeScript-first</label>
<label class="inline-flex items-center gap-2 text-sm text-slate-600"><input type="checkbox" id="f-prod" class="rounded border-slate-300"> Production-proven</label>
<button id="reset" class="ml-auto text-xs text-slate-500 hover:text-slate-900 underline">Reset all</button>
</div>
<div id="catChips" class="flex items-center gap-2 flex-wrap text-xs"></div>
</div>
</section>
<section class="max-w-7xl mx-auto px-6 pt-8">
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-slate-900">The landscape at a glance</h2>
<p class="text-xs text-slate-500"><span id="countLabel">—</span> · click a node to jump to its card</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden">
<svg id="mindmap" viewBox="0 0 1200 760" class="w-full h-[560px]"></svg>
</div>
</section>
<section class="max-w-7xl mx-auto px-6 py-10">
<div class="flex items-center justify-between mb-4 flex-wrap gap-3">
<div>
<h2 class="text-lg font-semibold text-slate-900">Which stage are you at?</h2>
<p class="text-sm text-slate-500">Pick one — the stack below adapts, and cards fade to only what fits.</p>
</div>
<div id="stageTabs" class="flex gap-2 flex-wrap"></div>
</div>
<div id="stagePanel" class="rounded-2xl border border-slate-200 bg-gradient-to-br from-white to-slate-50 shadow-sm p-6"></div>
</section>
<section class="max-w-7xl mx-auto px-6 pb-10">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-slate-900">All technologies</h2>
<p class="text-xs text-slate-500" id="gridCount"></p>
</div>
<div id="grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
</section>
<section class="bg-white border-t border-slate-200">
<div class="max-w-7xl mx-auto px-6 py-12">
<h2 class="text-xl font-bold text-slate-900 mb-6">Decision cheat-sheet</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="rounded-xl border border-slate-200 p-5 bg-slate-50">
<h3 class="font-semibold text-slate-900 mb-3">Pick by team situation</h3>
<ul class="space-y-2 text-sm text-slate-700">
<li><b>Solo founder:</b> Next.js + Tailwind + shadcn/ui on Vercel. Or SvelteKit for lighter footprint.</li>
<li><b>Small React team:</b> Next.js App Router, Zustand, TanStack Query, RHF + Zod, Tailwind + shadcn, Playwright.</li>
<li><b>Small Vue team:</b> Nuxt 3, Pinia, VueUse, Tailwind + Nuxt UI, Vitest, Playwright.</li>
<li><b>Content-heavy site:</b> Astro + MDX + Tailwind. Zero JS by default.</li>
<li><b>Dashboard / admin:</b> React + Mantine or Ant Design + TanStack Table.</li>
<li><b>Enterprise / regulated:</b> Angular or Next with strict conventions + MUI.</li>
<li><b>Server-rendered + sprinkles:</b> Rails/Django/Laravel + HTMX/Hotwire + Alpine.</li>
</ul>
</div>
<div class="rounded-xl border border-slate-200 p-5 bg-slate-50">
<h3 class="font-semibold text-slate-900 mb-3">Pick by constraint</h3>
<ul class="space-y-2 text-sm text-slate-700">
<li><b>Smallest bundle:</b> Svelte or Solid.</li>
<li><b>Best SEO / TTI:</b> Astro, Qwik, or Next with RSC.</li>
<li><b>Hiring pool:</b> React, always.</li>
<li><b>Best DX for juniors:</b> Vue + Nuxt or Svelte + SvelteKit.</li>
<li><b>Cross-framework design system:</b> Lit + Web Components or Radix/Ark UI headless.</li>
<li><b>Zero JS if possible:</b> Astro.</li>
</ul>
</div>
<div class="rounded-xl border border-rose-200 p-5 bg-rose-50">
<h3 class="font-semibold text-rose-900 mb-3">Anti-patterns to avoid</h3>
<ul class="space-y-2 text-sm text-rose-900/90">
<li>Chasing the framework of the quarter. Churn is expensive.</li>
<li>Building your own component library on day one.</li>
<li>Runtime CSS-in-JS with React Server Components.</li>
<li>Mirroring server state into Zustand/Redux.</li>
<li>Skipping TypeScript to "move faster".</li>
<li>Retrofitting accessibility after launch.</li>
</ul>
</div>
</div>
</div>
</section>
<footer class="bg-slate-900 text-slate-400 text-sm">
<div class="max-w-7xl mx-auto px-6 py-8">
<div class="flex items-center justify-between flex-wrap gap-4 mb-4">
<div class="font-semibold text-white">⚡ Open TechStack</div>
<div class="flex items-center gap-4">
<a href="index.html" class="hover:text-white transition">Home</a>
<a href="Frontend-Stacks-Visual-Guide.html" class="text-slate-200">Frontend Guide</a>
<a href="Backend-Stacks-Visual-Guide.html" class="hover:text-white transition">Backend Guide</a>
</div>
</div>
<div class="flex items-center justify-between flex-wrap gap-3 pt-4 border-t border-slate-800">
<div>Open source · <a href="https://github.com/JagadeepPortfolio/awesome-vibecoding-techstacks" class="text-cyan-400 hover:text-cyan-300 transition" target="_blank" rel="noopener">Star on GitHub</a> · Contributions welcome</div>
<div class="text-xs">Built with vanilla HTML, Tailwind CSS, and zero dependencies</div>
</div>
</div>
</footer>
<!-- FLOATING SHARE BAR -->
<div id="shareBar" class="fixed bottom-0 left-0 right-0 z-50 bg-slate-900/95 backdrop-blur border-t border-slate-700 transform translate-y-full transition-transform duration-300" style="transition:transform .3s ease">
<div class="max-w-7xl mx-auto px-6 py-3 flex items-center justify-between gap-4">
<div class="text-white text-sm font-medium hidden sm:block">Found this useful? Share it!</div>
<div class="flex items-center gap-2 flex-wrap">
<a id="shareTwitter" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[#1DA1F2] text-white text-xs font-semibold rounded-lg hover:opacity-90 transition">
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
Tweet
</a>
<a id="shareReddit" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[#FF4500] text-white text-xs font-semibold rounded-lg hover:opacity-90 transition">
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 01-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 01.042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 014.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 01.14-.197.35.35 0 01.238-.042l2.906.617a1.214 1.214 0 011.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 00-.231.094.33.33 0 000 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 000-.463.33.33 0 00-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 00-.232-.095z"/></svg>
Reddit
</a>
<a id="shareLinkedin" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[#0A66C2] text-white text-xs font-semibold rounded-lg hover:opacity-90 transition">
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
LinkedIn
</a>
<a href="https://github.com/JagadeepPortfolio/awesome-vibecoding-techstacks" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-white text-slate-900 text-xs font-semibold rounded-lg hover:bg-slate-100 transition">
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
Star on GitHub
</a>
</div>
</div>
</div>
<script>
// Share bar + share links
(function(){
const bar=document.getElementById("shareBar");
const url=encodeURIComponent(location.href);
const title=encodeURIComponent(document.title);
const text=encodeURIComponent("Check out this interactive guide to modern tech stacks — 200+ technologies compared with mind maps and filters");
document.getElementById("shareTwitter").href=`https://x.com/intent/tweet?text=${text}&url=${url}`;
document.getElementById("shareReddit").href=`https://reddit.com/submit?url=${url}&title=${title}`;
document.getElementById("shareLinkedin").href=`https://www.linkedin.com/sharing/share-offsite/?url=${url}`;
let shown=false;
window.addEventListener("scroll",()=>{
if(window.scrollY>400&&!shown){shown=true;bar.style.transform="translateY(0)";}
if(window.scrollY<200&&shown){shown=false;bar.style.transform="translateY(100%)";}
});
})();
</script>
<script>
/* ================= DATA ================= */
const CATEGORIES = [
{ id:"fw", label:"UI Frameworks", cls:"cat-fw" },
{ id:"meta", label:"Meta-Frameworks", cls:"cat-meta" },
{ id:"style", label:"Styling", cls:"cat-style" },
{ id:"state", label:"State Management", cls:"cat-state" },
{ id:"data", label:"Data Fetching", cls:"cat-data" },
{ id:"form", label:"Forms & Validation", cls:"cat-form" },
{ id:"build", label:"Build & Tooling", cls:"cat-build" },
{ id:"test", label:"Testing", cls:"cat-test" },
{ id:"ui", label:"UI Libraries", cls:"cat-ui" },
{ id:"host", label:"Hosting", cls:"cat-host" },
];
const STAGES = [
{ id:"hobby", label:"Hobby", cost:"$0", tag:"Prototype", desc:"Weekend project, zero cost, free-tier everything." },
{ id:"mvp", label:"MVP", cost:"$0–$30", tag:"First users", desc:"Real users, paid plans where needed, proper design system." },
{ id:"growth", label:"Growth", cost:"$30–$500",tag:"Scaling", desc:"Thousands of users, monorepo, Storybook, CI gates, RUM." },
{ id:"scale", label:"Scale", cost:"$500+", tag:"Enterprise", desc:"Micro-frontends, formal a11y, i18n, WAF, design-system package." },
];
const STAGE_STACKS = {
hobby: { framework:"Next.js / Astro / SvelteKit", styling:"Tailwind + shadcn/ui", state:"Zustand / built-in", data:"TanStack Query / RSC", tooling:"Vite + pnpm + Biome", hosting:"Vercel Hobby / CF Pages" },
mvp: { framework:"Next.js / Nuxt / SvelteKit", styling:"Tailwind + shadcn", state:"Zustand / Pinia", data:"TanStack Query + Zod", tooling:"Vitest + Playwright", hosting:"Vercel Pro / CF Pages" },
growth: { framework:"Next / Nuxt + Storybook", styling:"Tailwind tokens", state:"Zustand / Jotai", data:"TanStack Query + tRPC", tooling:"Turborepo + CI gates", hosting:"Vercel / CF + RUM" },
scale: { framework:"Next / Angular + micro-FE", styling:"Design-system pkg", state:"Signals / XState", data:"RSC + TanStack Query", tooling:"Nx + Playwright + axe", hosting:"CF WAF + multi-region" },
};
const TECH = [
// UI Frameworks
{ id:"react", name:"React", cat:"fw", summary:"Dominant UI library. React 19 with Server Components, Actions, and Suspense. Backed by Meta, universal hiring pool.", pros:["Largest ecosystem","Universal hiring","Works everywhere"], cons:["Sharper learning curve","State mgmt pick-your-own","RSC complexity"], bestFor:"Teams valuing ecosystem breadth", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"vue", name:"Vue 3", cat:"fw", summary:"Composition API + Vapor Mode compiler. Single-File Components still one of the best DX decisions in frontend.", pros:["Gentle learning curve","Great SFCs","Strong in EU/Asia"], cons:["Smaller NA hiring","Narrower ecosystem","Dialect fragmentation"], bestFor:"Cohesive opinionated framework", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"svelte", name:"Svelte 5", cat:"fw", summary:"Runes-based fine-grained reactivity. Smaller and faster than VDOM. Most-loved in survey after survey.", pros:["Least code per feature","Tiny bundles","Brilliant DX"], cons:["Smaller ecosystem","Fewer component libs","Breaking changes"], bestFor:"Max velocity, min bundle", free:true, ts:true, prod:true, stages:["hobby","mvp","growth"] },
{ id:"solid", name:"SolidJS", cat:"fw", summary:"JSX with fine-grained reactivity and no VDOM. Consistently tops performance benchmarks.", pros:["Fastest in the browser","Familiar JSX","Great TS"], cons:["Young ecosystem","Fewer jobs","SSR maturing"], bestFor:"Performance-critical apps", free:true, ts:true, prod:true, stages:["mvp","growth"] },
{ id:"angular", name:"Angular", cat:"fw", summary:"Standalone components, signals, new control-flow syntax, Zoneless mode. Modern Angular is surprisingly light.", pros:["Batteries included","Strong TS","Huge enterprise footprint"], cons:["Steeper onboarding","Heavier","Legacy perception"], bestFor:"Large enterprise teams", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"qwik", name:"Qwik", cat:"fw", summary:"Resumability — streams only the code a user actually interacts with.", pros:["Best TTI","Great SEO","Novel model"], cons:["Niche ecosystem","Mental model shift"], bestFor:"Content-heavy sites needing fast TTI", free:true, ts:true, prod:true, stages:["mvp","growth"] },
{ id:"lit", name:"Lit / Web Components", cat:"fw", summary:"Tiny wrapper around native Web Components. Used at Google, Adobe, SAP, ING.", pros:["Framework-agnostic","Native platform","Small runtime"], cons:["Not a full app framework","SSR is harder"], bestFor:"Design systems across apps", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"htmx", name:"HTMX + Alpine", cat:"fw", summary:"Hypermedia revival. Most SPA interactions via HTML attributes and small server responses.", pros:["No SPA complexity","Works with any backend","Small JS"], cons:["Not for rich client apps","Harder complex state"], bestFor:"Server-rendered apps with sprinkles", free:true, ts:false, prod:true, stages:["hobby","mvp","growth"] },
// Meta-frameworks
{ id:"next", name:"Next.js", cat:"meta", summary:"The React meta-framework. App Router with Server Components, Actions, streaming, ISR. Best-in-class on Vercel.", pros:["Massive ecosystem","First-class DX","Covers everything"], cons:["App Router mental model","Vercel-specific bits","Heavier"], bestFor:"Almost any React project", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"rr7", name:"React Router 7 / Remix", cat:"meta", summary:"Remix merged into React Router. Nested routes, loaders/actions, progressive enhancement, no forced runtime.", pros:["Simpler than Next","Deploy anywhere","Great web fundamentals"], cons:["Smaller ecosystem","Less hype"], bestFor:"React without Next's weight", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
{ id:"nuxt", name:"Nuxt 3", cat:"meta", summary:"Vue's official meta-framework. Auto-imports, file routing, Nitro server engine (deploy anywhere).", pros:["Excellent modules","Deploy anywhere","Tight Vue integration"], cons:["Vue-only","Auto-magic can confuse"], bestFor:"Vue projects", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"svkit", name:"SvelteKit", cat:"meta", summary:"Adapter-based Svelte meta-framework. Deploy to Node, Vercel, CF Workers, static with no config changes.", pros:["Lean and fast","Multi-target adapters","Great routing"], cons:["Smaller community","Svelte-only"], bestFor:"Svelte projects", free:true, ts:true, prod:true, stages:["hobby","mvp","growth"] },
{ id:"astro", name:"Astro", cat:"meta", summary:"Content-first meta-framework. Zero JS by default, opt into islands with React/Vue/Svelte/Solid.", pros:["Zero JS default","Blazing fast","Framework-agnostic islands"], cons:["Not for SPA-heavy apps","Limited dynamic story"], bestFor:"Marketing, blogs, docs", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"tanstack",name:"TanStack Start", cat:"meta", summary:"New React meta-framework on TanStack Router. Type-safe end-to-end, file-based routing, server functions.", pros:["Strict typing","Less magic than Next","Great router"], cons:["Newer","Smaller ecosystem"], bestFor:"Type-obsessed React teams", free:true, ts:true, prod:true, stages:["mvp","growth"] },
// Styling
{ id:"tailwind",name:"Tailwind CSS", cat:"style", summary:"Dominant utility-first CSS. Tailwind 4 brought a Rust engine and CSS-variable theming.", pros:["Very fast to write","Great design tokens","Huge ecosystem"], cons:["Noisy markup","Class-name learning curve"], bestFor:"Almost any project", free:true, ts:false, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"cssmod", name:"CSS Modules / vanilla CSS", cat:"style", summary:"Modern CSS has @layer, nesting, :has(), container queries, color-mix. The platform is enough for many teams.", pros:["No tooling lock-in","Platform-native","Small bundle"], cons:["Less DX sugar","Naming discipline"], bestFor:"Teams wanting platform-native CSS", free:true, ts:false, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"panda", name:"Panda / Vanilla Extract", cat:"style", summary:"Zero-runtime CSS-in-JS with full TypeScript types. Extracts to static CSS at build.", pros:["Typed styles","Zero runtime","Design-system friendly"], cons:["Build complexity","Smaller community"], bestFor:"Design-system-heavy teams", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"unocss", name:"UnoCSS", cat:"style", summary:"Atomic CSS engine inspired by Tailwind, faster and more configurable.", pros:["Fast","Configurable","Popular in Vue"], cons:["Smaller ecosystem","Less standardized"], bestFor:"Vue/Nuxt teams", free:true, ts:true, prod:true, stages:["mvp","growth"] },
// State
{ id:"zustand", name:"Zustand", cat:"state", summary:"Tiny hook-based global state. New React default, used at Notion, Vercel.", pros:["Tiny API","Easy to learn","No boilerplate"], cons:["Manual selectors for perf","No devtools parity with Redux"], bestFor:"Most React global state", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"jotai", name:"Jotai", cat:"state", summary:"Atomic state model. Excellent fine-grained reactivity for React.", pros:["Atom composition","Fine-grained","Good async story"], cons:["Mental model shift","Fewer examples than Zustand"], bestFor:"Complex fine-grained state", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
{ id:"redux", name:"Redux Toolkit", cat:"state", summary:"Modern Redux. Still dominant in large codebases, much nicer than classic Redux.", pros:["Massive ecosystem","Best devtools","Mature patterns"], cons:["Boilerplate","Overkill for small apps"], bestFor:"Large enterprise React", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"xstate", name:"XState", cat:"state", summary:"State machines for complex flows — forms, onboarding, checkout.", pros:["Provably correct flows","Great visualizer","Framework-agnostic"], cons:["Learning curve","Overkill for simple state"], bestFor:"Critical stateful flows", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"pinia", name:"Pinia", cat:"state", summary:"Vue's official store. Typed, clean, replaces Vuex.", pros:["Official Vue store","Great types","Simple API"], cons:["Vue-only"], bestFor:"Vue projects", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"signals", name:"Signals (Solid/Angular/Preact)", cat:"state", summary:"Reactive primitives spreading across frameworks in 2025–2026. Built into Solid, Angular, Preact.", pros:["Fine-grained","Framework-native","Simple mental model"], cons:["Implementation varies","Cross-framework fragmentation"], bestFor:"Modern reactive frameworks", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
// Data fetching
{ id:"tanquery",name:"TanStack Query", cat:"data", summary:"The unambiguous winner for client server state. React, Vue, Svelte, Solid.", pros:["Caching done right","Great DX","Framework-agnostic"], cons:["Client-side only","Learning curve on keys"], bestFor:"Almost any client-side data fetching", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"swr", name:"SWR", cat:"data", summary:"Vercel's lighter alternative. Simpler API, same idea.", pros:["Tiny","Simple","Works with Next"], cons:["Fewer features than TanStack Query"], bestFor:"Simple data fetching needs", free:true, ts:true, prod:true, stages:["hobby","mvp"] },
{ id:"trpc", name:"tRPC", cat:"data", summary:"Typed RPC between client and server. Eliminates the API layer in TS-only stacks.", pros:["End-to-end types","No schema duplication","Fast DX"], cons:["TS-only","Tight coupling"], bestFor:"Full-stack TS apps", free:true, ts:true, prod:true, stages:["mvp","growth"] },
{ id:"apollo", name:"Apollo / urql / Relay", cat:"data", summary:"GraphQL clients. Apollo is incumbent, urql is lighter, Relay is the Facebook original.", pros:["Rich GraphQL tooling","Normalized cache","Mature"], cons:["GraphQL-only","Heavier than TanStack Query"], bestFor:"GraphQL-first stacks", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
// Forms
{ id:"rhf", name:"React Hook Form", cat:"form", summary:"The React standard. Uncontrolled, fast, minimal re-renders.", pros:["Great perf","Simple API","Good with Zod"], cons:["React-only","Uncontrolled quirks"], bestFor:"Most React forms", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"tanform", name:"TanStack Form", cat:"form", summary:"Newer, framework-agnostic, strong TypeScript types.", pros:["Framework-agnostic","Great types","Growing fast"], cons:["Newer","Smaller community"], bestFor:"Type-obsessed teams", free:true, ts:true, prod:true, stages:["mvp","growth"] },
{ id:"zod", name:"Zod / Valibot / Arktype", cat:"form", summary:"Schema validation. Zod is default; Valibot is tiny; Arktype is fastest.", pros:["TS inference","Runtime + compile time","Framework-agnostic"], cons:["Bundle size (Zod)","Arktype syntax quirks"], bestFor:"Any form or API boundary", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
// Build/tooling
{ id:"vite", name:"Vite", cat:"build", summary:"The dominant dev server and bundler. Powers SvelteKit, Nuxt, SolidStart, and most standalone apps.", pros:["Blazing dev server","Framework-agnostic","Great plugin ecosystem"], cons:["Prod build is Rollup-based","Config sprawl at scale"], bestFor:"Almost any non-Next project", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"turbopack",name:"Turbopack", cat:"build", summary:"Next.js's Rust-based bundler. Stable for dev, prod still catching up.", pros:["Very fast dev","Next-native","Rust"], cons:["Next-specific","Prod still maturing"], bestFor:"Next.js projects", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
{ id:"rspack", name:"Rspack / Rsbuild", cat:"build", summary:"Rust port of Webpack. Drop-in faster replacement for legacy Webpack projects.", pros:["Webpack-compatible","Much faster","Drop-in"], cons:["Smaller community","Plugin gaps"], bestFor:"Legacy Webpack migration", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"pnpm", name:"pnpm", cat:"build", summary:"Default 2026 package manager. Disk-efficient, fast, strict.", pros:["Disk-efficient","Strict resolution","Great monorepo support"], cons:["Slight learning curve","Some tools still assume npm"], bestFor:"Any new project", free:true, ts:false, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"turbo", name:"Turborepo", cat:"build", summary:"Remote caching, task pipelines, owned by Vercel. Simple and fast monorepo tooling.", pros:["Fast remote cache","Simple","Vercel backing"], cons:["Fewer features than Nx","Vercel gravity"], bestFor:"JS/TS monorepos", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
{ id:"nx", name:"Nx", cat:"build", summary:"Heavier, more features, generators, plugins for every framework.", pros:["Rich plugins","Great for large orgs","Codegen"], cons:["Heavy","Opinionated","Steeper learning"], bestFor:"Large enterprise monorepos", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"biome", name:"Biome", cat:"build", summary:"Rust-based linter + formatter. Replaces ESLint + Prettier for many teams.", pros:["Very fast","Single config","Zero deps"], cons:["Fewer rules than ESLint","Newer"], bestFor:"Teams wanting speed and simplicity", free:true, ts:true, prod:true, stages:["hobby","mvp","growth"] },
{ id:"eslint", name:"ESLint + Prettier", cat:"build", summary:"Still the default, especially in monorepos with complex rules.", pros:["Huge rule ecosystem","Mature","Plugin support"], cons:["Slower","More config"], bestFor:"Complex enterprise linting", free:true, ts:true, prod:true, stages:["growth","scale"] },
// Testing
{ id:"vitest", name:"Vitest", cat:"test", summary:"New standard unit-test runner. Vite-powered, Jest-compatible, fast.", pros:["Very fast","Jest-compatible","Great DX"], cons:["Newer ecosystem","Some Jest edge cases"], bestFor:"Most unit tests in 2026", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"jest", name:"Jest", cat:"test", summary:"Still huge in legacy codebases.", pros:["Massive ecosystem","Battle-tested","Snapshots"], cons:["Slower than Vitest","Dated DX"], bestFor:"Legacy codebases", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"playwright",name:"Playwright", cat:"test", summary:"E2E winner. Cross-browser, fast, excellent tooling, great DX.", pros:["Cross-browser","Great debugging","Fast"], cons:["Bigger download","CI setup"], bestFor:"Any E2E testing", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
{ id:"cypress", name:"Cypress", cat:"test", summary:"Still widely used; best-in-class debugging UX.", pros:["Amazing debugger","Great docs","Mature"], cons:["Single-browser focus historically","Slower than Playwright"], bestFor:"Teams invested in Cypress", free:true, ts:true, prod:true, stages:["mvp","growth"] },
{ id:"storybook",name:"Storybook", cat:"test", summary:"Component workshop + visual regression. Still indispensable for design systems.", pros:["Great for design systems","Visual regression","Docs"], cons:["Config sprawl","Can slow CI"], bestFor:"Design systems", free:true, ts:true, prod:true, stages:["growth","scale"] },
// UI libraries
{ id:"shadcn", name:"shadcn/ui", cat:"ui", summary:"Unavoidable in 2026. Not a library — copy-paste components on Radix + Tailwind. You own the code.", pros:["You own the code","Beautiful defaults","No lock-in"], cons:["Not versioned","Manual updates"], bestFor:"React + Tailwind teams", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"radix", name:"Radix UI / React Aria", cat:"ui", summary:"Unstyled accessible primitives. What shadcn and many design systems sit on.", pros:["Accessible by default","Headless","Flexible"], cons:["You do the styling","Steeper than a UI kit"], bestFor:"Custom design systems", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
{ id:"mantine", name:"Mantine", cat:"ui", summary:"Full-featured, hook-rich, great for dashboards.", pros:["Huge component set","Great hooks","Good TS"], cons:["Opinionated styling","Bigger bundle"], bestFor:"Admin dashboards", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
{ id:"mui", name:"MUI (Material UI)", cat:"ui", summary:"Still enormous in enterprise. Heavier than alternatives.", pros:["Battle-tested","Huge components","Enterprise features"], cons:["Heavy","Material look","Styling overrides"], bestFor:"Enterprise React", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"antd", name:"Ant Design", cat:"ui", summary:"Enterprise default in Asia. Dense, feature-rich.", pros:["Dense components","Mature","Enterprise features"], cons:["Heavy","Styling override work"], bestFor:"Enterprise dashboards", free:true, ts:true, prod:true, stages:["growth","scale"] },
{ id:"heroui", name:"HeroUI (NextUI)", cat:"ui", summary:"Modern, beautiful defaults.", pros:["Beautiful defaults","Good DX","Tailwind-friendly"], cons:["Smaller ecosystem","Design opinionated"], bestFor:"Modern consumer apps", free:true, ts:true, prod:true, stages:["hobby","mvp","growth"] },
{ id:"nuxtui", name:"Nuxt UI / PrimeVue / Vuetify", cat:"ui", summary:"Mature Vue component libraries. All actively maintained.", pros:["Vue-native","Mature","Great DX"], cons:["Vue-only"], bestFor:"Vue projects", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"lucide", name:"Lucide Icons", cat:"ui", summary:"Default icon set. MIT, huge, tree-shakable.", pros:["Huge set","MIT","Tree-shakable"], cons:["Single visual style"], bestFor:"Any project needing icons", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"framer", name:"Framer Motion / Motion One", cat:"ui", summary:"Animation standards in React and beyond.", pros:["Great API","Powerful","Good docs"], cons:["Bundle size","React-focused"], bestFor:"Rich animations", free:true, ts:true, prod:true, stages:["mvp","growth","scale"] },
// Hosting
{ id:"vercel", name:"Vercel", cat:"host", summary:"First-party Next.js hosting, preview deploys, edge + serverless, image optimization.", pros:["Unmatched DX","Instant previews","First-class Next"], cons:["Pricey at scale","Some lock-in"], bestFor:"Next.js SaaS, marketing sites", free:true, ts:true, prod:true, stages:["hobby","mvp","growth"] },
{ id:"cfpages", name:"Cloudflare Pages", cat:"host", summary:"Static + Workers at the edge, 300+ locations, generous free tier, no egress fees on R2.", pros:["Generous free tier","Edge-native","No egress"], cons:["Workers runtime constraints"], bestFor:"Framework-agnostic, cheap global", free:true, ts:true, prod:true, stages:["hobby","mvp","growth","scale"] },
{ id:"netlify", name:"Netlify", cat:"host", summary:"The original JAMstack host. Functions, edge, forms, identity.", pros:["Solid free tier","Framework-agnostic","Good DX"], cons:["Less trendy","Smaller features than Vercel"], bestFor:"JAMstack / static + functions", free:true, ts:true, prod:true, stages:["hobby","mvp"] },
{ id:"ghpages", name:"GitHub Pages / Static S3", cat:"host", summary:"Free static hosting for docs and portfolios. Rock-solid.", pros:["Free","Reliable","Simple"], cons:["Static only","No server-side"], bestFor:"Docs, portfolios, pure static", free:true, ts:false, prod:true, stages:["hobby"] },
{ id:"selfhost",name:"Self-host (Docker + Caddy)", cat:"host", summary:"Host built Next/Nuxt/SvelteKit apps on a Hetzner box with Coolify/Dokploy.", pros:["Cheapest","Full control","No egress limits"], cons:["You run ops","No autoscale"], bestFor:"Cost-sensitive production", free:false, ts:false, prod:true, stages:["mvp","growth"] },
];
/* ================= STATE ================= */
const state = { q:"", cats:new Set(), free:false, ts:false, prod:false, stage:null };
function catOf(id){return CATEGORIES.find(c=>c.id===id);}
function matches(t){
if (state.q){
const q=state.q.toLowerCase();
const hay=(t.name+" "+t.summary+" "+t.bestFor+" "+t.cat).toLowerCase();
if (!hay.includes(q)) return false;
}
if (state.cats.size && !state.cats.has(t.cat)) return false;
if (state.free && !t.free) return false;
if (state.ts && !t.ts) return false;
if (state.prod && !t.prod) return false;
if (state.stage && !t.stages.includes(state.stage)) return false;
return true;
}
/* ================= CHIPS ================= */
const chipsEl = document.getElementById("catChips");
CATEGORIES.forEach(c=>{
const b=document.createElement("button");
b.className=`filter-chip ${c.cls} chip rounded-full px-3 py-1 font-medium`;
b.textContent=c.label; b.dataset.cat=c.id;
b.addEventListener("click",()=>{
if (state.cats.has(c.id)){state.cats.delete(c.id);b.classList.remove("active");}
else {state.cats.add(c.id);b.classList.add("active");}
render();
});
chipsEl.appendChild(b);
});
/* ================= MINDMAP ================= */
const svg=document.getElementById("mindmap");
const NS="http://www.w3.org/2000/svg";
const CX=600, CY=380;
const PRIMARY=[
{cat:"fw", a:-Math.PI/2, r:220},
{cat:"meta", a:-Math.PI/2+(2*Math.PI/5), r:220},
{cat:"style", a:-Math.PI/2+(4*Math.PI/5), r:220},
{cat:"state", a:-Math.PI/2+(6*Math.PI/5), r:220},
{cat:"ui", a:-Math.PI/2+(8*Math.PI/5), r:220},
];
const SECONDARY=["data","form","build","test","host"];
const COLORS={fw:"#06b6d4",meta:"#6366f1",style:"#ec4899",state:"#10b981",data:"#14b8a6",form:"#f59e0b",build:"#8b5cf6",test:"#0ea5e9",ui:"#f97316",host:"#e11d48"};
function buildMindmap(){
svg.innerHTML="";
const defs=document.createElementNS(NS,"defs");
defs.innerHTML=`<radialGradient id="g1"><stop offset="0%" stop-color="#06b6d4"/><stop offset="100%" stop-color="#06b6d4" stop-opacity="0"/></radialGradient>`;
svg.appendChild(defs);
const bg=document.createElementNS(NS,"circle");
bg.setAttribute("cx",CX);bg.setAttribute("cy",CY);bg.setAttribute("r","260");
bg.setAttribute("fill","url(#g1)");bg.setAttribute("opacity","0.15");
svg.appendChild(bg);
// center
const g=document.createElementNS(NS,"g");
const c=document.createElementNS(NS,"circle");
c.setAttribute("cx",CX);c.setAttribute("cy",CY);c.setAttribute("r","58");c.setAttribute("fill","#0f172a");
const t=document.createElementNS(NS,"text");
t.setAttribute("x",CX);t.setAttribute("y",CY-4);t.setAttribute("text-anchor","middle");t.setAttribute("fill","white");t.setAttribute("font-weight","700");t.setAttribute("font-size","18");t.textContent="Frontend";
const s=document.createElementNS(NS,"text");
s.setAttribute("x",CX);s.setAttribute("y",CY+16);s.setAttribute("text-anchor","middle");s.setAttribute("fill","#94a3b8");s.setAttribute("font-size","11");s.textContent="2026";
g.appendChild(c);g.appendChild(t);g.appendChild(s);svg.appendChild(g);
PRIMARY.forEach(br=>{
const bx=CX+Math.cos(br.a)*br.r, by=CY+Math.sin(br.a)*br.r;
const line=document.createElementNS(NS,"line");
line.setAttribute("x1",CX);line.setAttribute("y1",CY);line.setAttribute("x2",bx);line.setAttribute("y2",by);
line.setAttribute("stroke",COLORS[br.cat]);line.setAttribute("stroke-width","3");line.setAttribute("opacity","0.5");
line.classList.add("link");line.dataset.cat=br.cat;svg.appendChild(line);
const hub=document.createElementNS(NS,"g");
hub.classList.add("node");hub.dataset.cat=br.cat;
const hc=document.createElementNS(NS,"circle");
hc.setAttribute("cx",bx);hc.setAttribute("cy",by);hc.setAttribute("r","38");hc.setAttribute("fill",COLORS[br.cat]);
const ht=document.createElementNS(NS,"text");
ht.setAttribute("x",bx);ht.setAttribute("y",by+4);ht.setAttribute("text-anchor","middle");ht.setAttribute("fill","white");ht.setAttribute("font-weight","700");ht.setAttribute("font-size","11");
ht.textContent=catOf(br.cat).label.split(" ")[0];
hub.appendChild(hc);hub.appendChild(ht);
hub.addEventListener("click",()=>{
if (state.cats.has(br.cat)) state.cats.delete(br.cat); else state.cats.add(br.cat);
document.querySelectorAll(".filter-chip").forEach(el=>{if(el.dataset.cat===br.cat) el.classList.toggle("active",state.cats.has(br.cat));});
render();
});
svg.appendChild(hub);
const items=TECH.filter(x=>x.cat===br.cat);
items.forEach((it,i)=>{
const spread=Math.min(items.length,6);
const off=(i-(spread-1)/2)*0.26;
const la=br.a+off;
const lr=br.r+125;
const lx=CX+Math.cos(la)*lr, ly=CY+Math.sin(la)*lr;
const ll=document.createElementNS(NS,"line");
ll.setAttribute("x1",bx);ll.setAttribute("y1",by);ll.setAttribute("x2",lx);ll.setAttribute("y2",ly);
ll.setAttribute("stroke",COLORS[br.cat]);ll.setAttribute("stroke-width","1.5");ll.setAttribute("opacity","0.4");
ll.classList.add("link");ll.dataset.tech=it.id;ll.dataset.cat=it.cat;svg.appendChild(ll);
const lg=document.createElementNS(NS,"g");
lg.classList.add("node");lg.dataset.tech=it.id;lg.dataset.cat=it.cat;
const w=Math.max(70,Math.min(150,it.name.length*6.3));
const r=document.createElementNS(NS,"rect");
r.setAttribute("x",lx-w/2);r.setAttribute("y",ly-14);r.setAttribute("rx","12");r.setAttribute("ry","12");
r.setAttribute("width",w);r.setAttribute("height","28");
r.setAttribute("fill","white");r.setAttribute("stroke",COLORS[br.cat]);r.setAttribute("stroke-width","2");
const tx=document.createElementNS(NS,"text");
tx.setAttribute("x",lx);tx.setAttribute("y",ly+4);tx.setAttribute("text-anchor","middle");
tx.setAttribute("fill","#0f172a");tx.setAttribute("font-size","10.5");tx.setAttribute("font-weight","600");
tx.textContent=it.name.length>20?it.name.slice(0,19)+"…":it.name;
lg.appendChild(r);lg.appendChild(tx);
lg.addEventListener("click",()=>{
const card=document.getElementById("card-"+it.id);
if (card){card.scrollIntoView({behavior:"smooth",block:"center"});card.classList.remove("flash");void card.offsetWidth;card.classList.add("flash");}
});
svg.appendChild(lg);
});
});
SECONDARY.forEach((cid,i)=>{
const angle=Math.PI*0.58+(i-(SECONDARY.length-1)/2)*0.17;
const r=340;
const x=CX+Math.cos(angle)*r, y=CY+Math.sin(angle)*r;
const line=document.createElementNS(NS,"line");
line.setAttribute("x1",CX);line.setAttribute("y1",CY);line.setAttribute("x2",x);line.setAttribute("y2",y);
line.setAttribute("stroke",COLORS[cid]);line.setAttribute("stroke-width","2");line.setAttribute("opacity","0.35");
line.classList.add("link");line.dataset.cat=cid;svg.appendChild(line);
const g=document.createElementNS(NS,"g");
g.classList.add("node");g.dataset.cat=cid;
const c=document.createElementNS(NS,"circle");
c.setAttribute("cx",x);c.setAttribute("cy",y);c.setAttribute("r","26");c.setAttribute("fill",COLORS[cid]);
const tx=document.createElementNS(NS,"text");
tx.setAttribute("x",x);tx.setAttribute("y",y+4);tx.setAttribute("text-anchor","middle");tx.setAttribute("fill","white");tx.setAttribute("font-weight","700");tx.setAttribute("font-size","10");
tx.textContent=catOf(cid).label.split(" ")[0];
g.appendChild(c);g.appendChild(tx);
g.addEventListener("click",()=>{
if (state.cats.has(cid)) state.cats.delete(cid); else state.cats.add(cid);
document.querySelectorAll(".filter-chip").forEach(el=>{if(el.dataset.cat===cid) el.classList.toggle("active",state.cats.has(cid));});
render();
});
svg.appendChild(g);
});
}
function updateMindmap(){
svg.querySelectorAll(".node").forEach(n=>{
const t=n.dataset.tech?TECH.find(x=>x.id===n.dataset.tech):null;
let visible=true;
if (t) visible=matches(t);
else if (n.dataset.cat) visible=TECH.some(x=>x.cat===n.dataset.cat&&matches(x));
n.classList.toggle("dim",!visible);
});
svg.querySelectorAll(".link").forEach(l=>{
const t=l.dataset.tech?TECH.find(x=>x.id===l.dataset.tech):null;
let visible=true;
if (t) visible=matches(t);
else if (l.dataset.cat) visible=TECH.some(x=>x.cat===l.dataset.cat&&matches(x));
l.classList.toggle("dim",!visible);
});
}
/* ================= STAGES ================= */
const stageTabsEl=document.getElementById("stageTabs");
STAGES.forEach((s,i)=>{
const b=document.createElement("button");
b.className="stage-btn px-4 py-2 rounded-full border border-slate-300 bg-white text-sm font-semibold hover:border-slate-900 transition";
b.innerHTML=`<span class="mr-1">${i+1}</span>${s.label} <span class="text-slate-400 font-normal ml-1">${s.cost}</span>`;
b.dataset.stage=s.id;
b.addEventListener("click",()=>{state.stage=state.stage===s.id?null:s.id;render();});
stageTabsEl.appendChild(b);
});
function renderStagePanel(){
const panel=document.getElementById("stagePanel");
const s=STAGES.find(x=>x.id===state.stage);
if (!s){
panel.innerHTML=`
<div class="text-center py-6">
<div class="text-slate-500 text-sm">Pick a stage above to see the recommended stack and filter cards to what fits.</div>
<div class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3 max-w-3xl mx-auto">
${STAGES.map(st=>`
<div class="rounded-xl border border-slate-200 bg-white p-3 text-left">
<div class="text-xs text-slate-500">${st.tag}</div>
<div class="text-sm font-bold text-slate-900">${st.label}</div>
<div class="text-xs text-slate-500 mt-1">${st.cost}/mo</div>
</div>`).join("")}
</div>
</div>`;
return;
}
const stack=STAGE_STACKS[s.id];
panel.innerHTML=`
<div class="flex items-start justify-between gap-4 flex-wrap">
<div class="max-w-2xl">
<div class="text-xs uppercase tracking-wider text-cyan-600 font-semibold">${s.tag}</div>
<h3 class="text-2xl font-bold text-slate-900 mt-1">${s.label} stage · ${s.cost}/month</h3>
<p class="text-slate-600 mt-2">${s.desc}</p>
</div>
<div class="text-right">
<div class="text-5xl font-extrabold text-slate-900">${s.cost}</div>
<div class="text-xs text-slate-500">typical monthly spend</div>
</div>
</div>
<div class="mt-5 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
${Object.entries(stack).map(([k,v])=>`
<div class="rounded-lg border border-slate-200 bg-white p-3">
<div class="text-[10px] uppercase tracking-wide text-slate-500 font-semibold">${k}</div>
<div class="text-sm font-semibold text-slate-900 mt-1">${v}</div>
</div>`).join("")}
</div>`;
}
function updateStageTabs(){
document.querySelectorAll(".stage-btn").forEach(b=>b.classList.toggle("active",b.dataset.stage===state.stage));
}
/* ================= GRID ================= */
const grid=document.getElementById("grid");
function buildGrid(){
grid.innerHTML="";
TECH.forEach(t=>{
const c=catOf(t.cat);
const el=document.createElement("div");
el.id="card-"+t.id;
el.className=`card ${c.cls} bg-white rounded-xl border border-slate-200 p-5 shadow-sm hover:shadow-md`;
el.innerHTML=`
<div class="flex items-start justify-between gap-2 mb-2">
<h3 class="text-base font-bold text-slate-900">${t.name}</h3>
<span class="chip text-[10px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-full whitespace-nowrap">${c.label}</span>
</div>
<p class="text-sm text-slate-600 leading-relaxed">${t.summary}</p>
<div class="mt-3 grid grid-cols-2 gap-3 text-xs">
<div>
<div class="font-semibold text-emerald-700 mb-1">Pros</div>
<ul class="text-slate-600 space-y-0.5">${t.pros.map(p=>`<li>• ${p}</li>`).join("")}</ul>
</div>
<div>
<div class="font-semibold text-rose-700 mb-1">Cons</div>
<ul class="text-slate-600 space-y-0.5">${t.cons.map(p=>`<li>• ${p}</li>`).join("")}</ul>
</div>
</div>
<div class="mt-3 text-xs text-slate-500"><b class="text-slate-700">Best for:</b> ${t.bestFor}</div>
<div class="mt-3 flex flex-wrap gap-1">
${t.free ? '<span class="text-[10px] bg-emerald-100 text-emerald-700 rounded px-1.5 py-0.5 font-semibold">FREE/OSS</span>' : ''}
${t.ts ? '<span class="text-[10px] bg-sky-100 text-sky-700 rounded px-1.5 py-0.5 font-semibold">TS-FIRST</span>' : ''}
${t.prod ? '<span class="text-[10px] bg-indigo-100 text-indigo-700 rounded px-1.5 py-0.5 font-semibold">PROD-PROVEN</span>' : ''}
</div>
<div class="mt-2 flex gap-1">
${STAGES.map(s=>{
const ok=t.stages.includes(s.id);
return `<span class="text-[10px] px-1.5 py-0.5 rounded ${ok?'bg-slate-900 text-white':'bg-slate-100 text-slate-400'}">${s.label}</span>`;
}).join("")}
</div>`;
grid.appendChild(el);
});
}
function updateGrid(){
let shown=0;
TECH.forEach(t=>{
const el=document.getElementById("card-"+t.id);
if (!el) return;
const ok=matches(t);
el.classList.toggle("hidden-card",!ok);
if (ok) shown++;
});
document.getElementById("gridCount").textContent=`${shown} of ${TECH.length} shown`;
document.getElementById("countLabel").textContent=`${shown} of ${TECH.length} technologies`;
}
/* ================= RENDER ================= */
function render(){
updateStageTabs();
renderStagePanel();
updateMindmap();
updateGrid();
const p=[];
if (state.stage) p.push("stage="+state.stage);
history.replaceState(null,"",p.length?"#"+p.join("&"):"#");
}
/* ================= EVENTS ================= */
document.getElementById("search").addEventListener("input",e=>{state.q=e.target.value;render();});
document.getElementById("f-free").addEventListener("change",e=>{state.free=e.target.checked;render();});
document.getElementById("f-ts").addEventListener("change",e=>{state.ts=e.target.checked;render();});
document.getElementById("f-prod").addEventListener("change",e=>{state.prod=e.target.checked;render();});
document.getElementById("reset").addEventListener("click",()=>{
state.q="";state.cats.clear();state.free=state.ts=state.prod=false;state.stage=null;
document.getElementById("search").value="";
["f-free","f-ts","f-prod"].forEach(id=>document.getElementById(id).checked=false);
document.querySelectorAll(".filter-chip").forEach(el=>el.classList.remove("active"));
render();
});
document.addEventListener("keydown",e=>{
if (e.key==="/" && document.activeElement.tagName!=="INPUT"){e.preventDefault();document.getElementById("search").focus();}
if (e.key==="Escape") document.getElementById("reset").click();
if (["1","2","3","4"].includes(e.key) && document.activeElement.tagName!=="INPUT"){
state.stage=STAGES[parseInt(e.key,10)-1].id;render();
}
});
(function(){const h=location.hash.slice(1);if (h) h.split("&").forEach(p=>{const[k,v]=p.split("=");if(k==="stage"&&STAGES.find(s=>s.id===v)) state.stage=v;});})();
buildMindmap();
buildGrid();
render();
</script>
</body>
</html>