-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodebase_dump.txt
More file actions
10426 lines (9122 loc) · 400 KB
/
codebase_dump.txt
File metadata and controls
10426 lines (9122 loc) · 400 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
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
=============================
CODEBASE DUMP - Thu Mar 5 16:45:53 PST 2026
=============================
========================================
FILE: index.html
========================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Da Data Dad — Josh Merritt | Analyst, Creator, Engineer</title>
<meta name="description" content="Interactive physics-based portfolio by Josh Merritt. Analyst, creator, and engineer building data-driven solutions with SQL, Python, React, Power BI, and more." />
<meta name="viewport" content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#0a0e12" />
<meta name="author" content="Josh Merritt" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://dadatadad.com/" />
<link rel="shortcut icon" href="/assets/images/favicon.png" />
<link rel="apple-touch-icon" href="/assets/images/favicon.png" />
<!-- Open Graph (Facebook, LinkedIn, iMessage, etc.) -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://dadatadad.com/" />
<meta property="og:title" content="Da Data Dad — Josh Merritt" />
<meta property="og:description" content="Interactive physics-based portfolio. Drag, aim, and shoot to explore projects in data, apps, and technology." />
<meta property="og:image" content="https://dadatadad.com/assets/images/og-preview.jpg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="Da Data Dad" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Da Data Dad — Josh Merritt" />
<meta name="twitter:description" content="Interactive physics-based portfolio. Drag, aim, and shoot to explore projects in data, apps, and technology." />
<meta name="twitter:image" content="https://dadatadad.com/assets/images/og-preview.jpg" />
<!-- Structured Data (JSON-LD) — Person + Portfolio -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Josh Merritt",
"url": "https://dadatadad.com",
"jobTitle": "Analyst, Creator, Engineer",
"description": "Builder with a bias for shipping. Just as comfortable wiring up a data pipeline as designing the frontend that displays it. Turns messy, complex data into clear answers for the people who need them.",
"email": "josh@DaDataDad.com",
"sameAs": [
"https://www.linkedin.com/in/josh-merritt",
"https://github.com/joshmerritt",
"https://www.upwork.com/fl/joshuapmerritt"
],
"knowsAbout": ["SQL", "Python", "React", "Power BI", "DAX", "Data Analytics", "JavaScript", "Arduino", "Google BigQuery", "Microsoft Fabric"]
}
</script>
<!-- Structured Data — WebSite (helps Google understand the site) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Da Data Dad",
"url": "https://dadatadad.com",
"description": "Interactive physics-based portfolio by Josh Merritt — data analytics, BI development, and full-stack engineering.",
"author": {
"@type": "Person",
"name": "Josh Merritt"
}
}
</script>
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-JXCE49FJ7J"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-JXCE49FJ7J');
</script>
<!-- Fonts: Syne (display) + DM Sans (body) + JetBrains Mono (tech) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<!-- Noscript styles for the static fallback -->
<noscript>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #0a0e12;
color: #c7d6d5;
font-family: 'DM Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6;
padding: 0;
overflow-x: hidden;
}
.noscript-portfolio {
max-width: 780px;
margin: 0 auto;
padding: 48px 24px 80px;
}
.noscript-header {
margin-bottom: 48px;
padding-bottom: 32px;
border-bottom: 1px solid rgba(89, 133, 177, 0.25);
}
.noscript-brand {
font-family: 'Syne', sans-serif;
font-size: 2rem;
font-weight: 800;
color: #ffffff;
margin-bottom: 4px;
}
.noscript-tagline {
font-family: 'Syne', sans-serif;
font-size: 1.1rem;
color: #5985b1;
font-weight: 600;
margin-bottom: 16px;
}
.noscript-bio {
color: rgba(199, 214, 213, 0.75);
font-size: 0.95rem;
max-width: 600px;
margin-bottom: 20px;
}
.noscript-links { display: flex; gap: 16px; flex-wrap: wrap; }
.noscript-links a {
color: #5985b1;
text-decoration: none;
font-size: 0.85rem;
font-weight: 500;
padding: 6px 14px;
border: 1px solid rgba(89, 133, 177, 0.3);
border-radius: 6px;
transition: border-color 0.2s;
}
.noscript-links a:hover { border-color: #5985b1; }
.noscript-nav {
margin-bottom: 32px;
padding: 12px 0;
font-size: 0.85rem;
}
.noscript-nav a {
color: #5985b1;
text-decoration: none;
font-weight: 500;
}
.noscript-project {
margin-bottom: 40px;
padding: 24px;
background: rgba(14, 20, 28, 0.6);
border: 1px solid rgba(89, 133, 177, 0.12);
border-radius: 12px;
}
.noscript-project-img {
width: 100%;
max-height: 280px;
object-fit: cover;
border-radius: 8px;
margin-bottom: 16px;
}
.noscript-project h2 {
font-family: 'Syne', sans-serif;
font-size: 1.3rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 4px;
}
.noscript-category {
font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem;
color: #5985b1;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 12px;
}
.noscript-meta { margin-bottom: 12px; }
.noscript-meta dt {
font-weight: 600;
font-size: 0.8rem;
color: rgba(199, 214, 213, 0.5);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 10px;
}
.noscript-meta dd {
font-size: 0.9rem;
color: rgba(199, 214, 213, 0.85);
margin: 2px 0 0 0;
}
.noscript-cta {
display: inline-block;
color: #5985b1;
text-decoration: none;
font-weight: 600;
font-size: 0.85rem;
margin-top: 12px;
padding: 8px 18px;
border: 1px solid rgba(89, 133, 177, 0.35);
border-radius: 8px;
}
.noscript-cta:hover { border-color: #5985b1; background: rgba(89, 133, 177, 0.08); }
.noscript-footer {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid rgba(89, 133, 177, 0.15);
font-size: 0.8rem;
color: rgba(199, 214, 213, 0.35);
text-align: center;
}
</style>
</noscript>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<!--
═══════════════════════════════════════════════════════════════════════
NOSCRIPT FALLBACK — Static semantic HTML for crawlers & accessibility
This content is only shown when JavaScript is disabled or unavailable.
Search engines index this content to understand the portfolio.
═══════════════════════════════════════════════════════════════════════
-->
<noscript>
<main class="noscript-portfolio" role="main">
<header class="noscript-header">
<h1 class="noscript-brand">Da Data Dad</h1>
<p class="noscript-tagline">Honest. Analytical. Data Dreamer.</p>
<p class="noscript-bio">
Builder with a bias for shipping. Just as comfortable wiring up a data pipeline as designing the frontend that displays it. Turns messy, complex data into clear answers for the people who need them — whether that's a VP checking quarterly numbers or a teammate debugging a report.
</p>
<nav class="noscript-links" aria-label="External profiles">
<a href="https://www.linkedin.com/in/josh-merritt">LinkedIn</a>
<a href="https://github.com/joshmerritt">GitHub</a>
<a href="https://www.upwork.com/fl/joshuapmerritt">Upwork</a>
<a href="mailto:josh@DaDataDad.com">Contact</a>
</nav>
</header>
<nav class="noscript-nav" aria-label="Site pages">
<a href="/portfolio.html">View Accessible Portfolio →</a>
</nav>
<article class="noscript-project" itemscope itemtype="https://schema.org/SoftwareApplication">
<img class="noscript-project-img" src="/assets/images/thewineyoudrink.jpg" alt="The Wine You Drink app showing wine cellar management interface" loading="lazy" />
<h2 itemprop="name">The Wine You Drink</h2>
<p class="noscript-category">Apps</p>
<dl class="noscript-meta">
<dt>Goal</dt>
<dd itemprop="description">Replace spreadsheets and sticky notes with a purpose-built app for managing a wine collection — from cellar to glass.</dd>
<dt>Role</dt>
<dd>Product Owner and Creator</dd>
<dt>Technology</dt>
<dd itemprop="keywords">React 18, Firebase, Vite, PWA, Claude AI, CSS Custom Properties</dd>
<dt>Summary</dt>
<dd>A wine cellar app built for collectors who outgrew their spreadsheet. Snap a photo of any label to auto-fill the details with AI, and see at a glance which bottles are in their prime.</dd>
</dl>
<a class="noscript-cta" href="https://thewineyoudrink.web.app" itemprop="url">Open App ↗</a>
</article>
<article class="noscript-project" itemscope itemtype="https://schema.org/SoftwareApplication">
<img class="noscript-project-img" src="/assets/images/dartleague.jpg" alt="Black Sheep Dart League app showing league management dashboard" loading="lazy" />
<h2 itemprop="name">Black Sheep Dart League</h2>
<p class="noscript-category">Apps</p>
<dl class="noscript-meta">
<dt>Goal</dt>
<dd itemprop="description">Eliminate the spreadsheets, group texts, and paper scoresheets that were slowing down league night.</dd>
<dt>Role</dt>
<dd>Product Owner and Creator</dd>
<dt>Technology</dt>
<dd itemprop="keywords">React 19, Firebase, Tailwind CSS, Vite, Claude AI (scoresheet OCR)</dd>
<dt>Summary</dt>
<dd>A league management app used weekly by real players. Handles RSVPs, automatically pairs teams, and tracks stats. The scorekeeper snaps a photo and AI reads the scoresheet — what used to take 30+ minutes now takes seconds.</dd>
</dl>
<a class="noscript-cta" href="https://theblacksheepdartleague.web.app" itemprop="url">Open App ↗</a>
</article>
<article class="noscript-project" itemscope itemtype="https://schema.org/CreativeWork">
<img class="noscript-project-img" src="/assets/images/thisWebsite.jpg" alt="Portfolio website showing physics-based ball launching interaction" loading="lazy" />
<h2 itemprop="name">Portfolio Website</h2>
<p class="noscript-category">Technology</p>
<dl class="noscript-meta">
<dt>Goal</dt>
<dd itemprop="description">Build a portfolio that doubles as a playable physics sandbox — because a list of links doesn't show how you think.</dd>
<dt>Role</dt>
<dd>Creator and Developer</dd>
<dt>Technology</dt>
<dd itemprop="keywords">React 18, Vite, p5.js, Matter.js, ES Modules, GA4</dd>
<dt>Summary</dt>
<dd>Each project is a ball you drag and launch into a goal — part portfolio, part physics playground. Works on phones and desktops, adapts its performance to the device, and tracks every interaction for the analytics dashboard.</dd>
</dl>
<a class="noscript-cta" href="https://github.com/joshmerritt/websiteAnimation" itemprop="url">View on GitHub ↗</a>
</article>
<article class="noscript-project" itemscope itemtype="https://schema.org/CreativeWork">
<img class="noscript-project-img" src="/assets/images/SiteAnalytics.jpg" alt="Site analytics dashboard showing visitor trends and ball engagement metrics" loading="lazy" />
<h2 itemprop="name">Site Analytics</h2>
<p class="noscript-category">Technology</p>
<dl class="noscript-meta">
<dt>Goal</dt>
<dd itemprop="description">Prove the portfolio isn't just pretty — instrument every interaction and surface the data in a live dashboard.</dd>
<dt>Role</dt>
<dd>Creator and Developer</dd>
<dt>Technology</dt>
<dd itemprop="keywords">React, Cloudflare Workers, GA4 Data API, SVG, CSS Grid</dd>
<dt>Summary</dt>
<dd>A live analytics dashboard for this very site. Every ball launch, score, and project open is tracked, piped through a custom backend, and displayed in real time.</dd>
</dl>
<a class="noscript-cta" href="/analytics-dashboard.html" itemprop="url">View Dashboard ↗</a>
</article>
<article class="noscript-project" itemscope itemtype="https://schema.org/CreativeWork">
<img class="noscript-project-img" src="/assets/images/arduinoCoopDoor.jpg" alt="Smart chicken coop door with Arduino controller and motor assembly" loading="lazy" />
<h2 itemprop="name">Smart Chicken Coop</h2>
<p class="noscript-category">Technology</p>
<dl class="noscript-meta">
<dt>Goal</dt>
<dd itemprop="description">Automate a chicken coop door that opens at sunrise and locks at sundown — no manual intervention required.</dd>
<dt>Role</dt>
<dd>Hardware Engineer and Builder</dd>
<dt>Technology</dt>
<dd itemprop="keywords">C++, Arduino, Motor Shield, Photoresistor, Programmable RGB LEDs, Custom Woodworking</dd>
<dt>Summary</dt>
<dd>An automated coop door that senses daylight and drives a repurposed car-window motor to open at sunrise and lock at sundown. Built on an Arduino with a light sensor, color-coded status LEDs, and a custom wooden housing.</dd>
</dl>
<a class="noscript-cta" href="https://github.com/joshmerritt/arduinoChickenCoopDoor" itemprop="url">View on GitHub ↗</a>
</article>
<article class="noscript-project" itemscope itemtype="https://schema.org/CreativeWork">
<img class="noscript-project-img" src="/assets/images/powerBIMetrics.jpg" alt="Microsoft Power BI dashboard showing KPI metrics and data visualizations" loading="lazy" />
<h2 itemprop="name">Microsoft Power BI</h2>
<p class="noscript-category">Business</p>
<dl class="noscript-meta">
<dt>Goal</dt>
<dd itemprop="description">Build a governed data warehouse with automated pipelines that lets business users self-serve answers without filing tickets.</dd>
<dt>Role</dt>
<dd>BI Developer and Data Analyst</dd>
<dt>Technology</dt>
<dd itemprop="keywords">Power BI, DAX, Power Query, REST APIs, Google BigQuery, Azure</dd>
<dt>Summary</dt>
<dd>Built the reporting layer that lets business teams answer their own questions instead of filing a ticket and waiting. Data flows in automatically from multiple sources, lands in a clean, structured model, and powers a set of linked reports users can drill into on their own.</dd>
</dl>
<a class="noscript-cta" href="https://www.upwork.com/fl/joshuapmerritt" itemprop="url">View Upwork Profile ↗</a>
</article>
<article class="noscript-project" itemscope itemtype="https://schema.org/CreativeWork">
<img class="noscript-project-img" src="/assets/images/googleDataStudioServiceTechs.jpg" alt="Google Data Studio streaming dashboard displayed on office TV" loading="lazy" />
<h2 itemprop="name">Google Data Studio Streaming Dashboard</h2>
<p class="noscript-category">Business</p>
<dl class="noscript-meta">
<dt>Goal</dt>
<dd itemprop="description">Give frontline employees real-time visibility into their performance and standing relative to team benchmarks.</dd>
<dt>Role</dt>
<dd>BI Developer</dd>
<dt>Technology</dt>
<dd itemprop="keywords">Google Data Studio, Data Modeling, ScreenCloud, Amazon Fire TV</dd>
<dt>Summary</dt>
<dd>A live KPI dashboard displayed on office TVs so the whole team can see how they're tracking against goals at a glance. Shows individual and team standings updated throughout the day.</dd>
</dl>
<a class="noscript-cta" href="https://www.upwork.com/fl/joshuapmerritt" itemprop="url">View Upwork Profile ↗</a>
</article>
<footer class="noscript-footer">
<p>© 2026 Josh Merritt · Da Data Dad · <a href="mailto:josh@DaDataDad.com" style="color: #5985b1; text-decoration: none;">josh@DaDataDad.com</a></p>
</footer>
</main>
</noscript>
</body>
</html>
========================================
FILE: portfolio.html
========================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Portfolio — Josh Merritt | Da Data Dad</title>
<meta name="description" content="Josh Merritt's data analytics and engineering portfolio. Projects spanning Power BI, React, Firebase, Arduino, and more. Analyst, creator, and engineer." />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Josh Merritt" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://dadatadad.com/portfolio.html" />
<link rel="shortcut icon" href="/assets/images/favicon.png" />
<link rel="apple-touch-icon" href="/assets/images/favicon.png" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://dadatadad.com/portfolio.html" />
<meta property="og:title" content="Portfolio — Josh Merritt | Da Data Dad" />
<meta property="og:description" content="Data analytics and engineering portfolio. Projects in Power BI, React, Firebase, Arduino, and more." />
<meta property="og:image" content="https://dadatadad.com/assets/images/og-preview.jpg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="Da Data Dad" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Portfolio — Josh Merritt | Da Data Dad" />
<meta name="twitter:description" content="Data analytics and engineering portfolio. Projects in Power BI, React, Firebase, Arduino, and more." />
<meta name="twitter:image" content="https://dadatadad.com/assets/images/og-preview.jpg" />
<!-- Structured Data (JSON-LD) — Portfolio page -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"name": "Josh Merritt — Portfolio",
"url": "https://dadatadad.com/portfolio.html",
"description": "Data analytics and engineering portfolio by Josh Merritt, featuring projects in Power BI, React, Firebase, Arduino, and more.",
"author": {
"@type": "Person",
"name": "Josh Merritt",
"url": "https://dadatadad.com",
"jobTitle": "Analyst, Creator, Engineer"
},
"mainEntity": {
"@type": "ItemList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Microsoft Power BI", "url": "https://www.upwork.com/fl/joshuapmerritt" },
{ "@type": "ListItem", "position": 2, "name": "Google Data Studio Streaming Dashboard", "url": "https://www.upwork.com/fl/joshuapmerritt" },
{ "@type": "ListItem", "position": 3, "name": "The Wine You Drink", "url": "https://thewineyoudrink.web.app" },
{ "@type": "ListItem", "position": 4, "name": "Black Sheep Dart League", "url": "https://theblacksheepdartleague.web.app" },
{ "@type": "ListItem", "position": 5, "name": "Portfolio Website", "url": "https://github.com/joshmerritt/websiteAnimation" },
{ "@type": "ListItem", "position": 6, "name": "Site Analytics Dashboard", "url": "https://dadatadad.com/analytics-dashboard.html" },
{ "@type": "ListItem", "position": 7, "name": "Smart Chicken Coop", "url": "https://github.com/joshmerritt/arduinoChickenCoopDoor" }
]
}
}
</script>
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-JXCE49FJ7J"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-JXCE49FJ7J');
</script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/portfolio-main.jsx"></script>
</body>
</html>
========================================
FILE: analytics-dashboard.html
========================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Site Analytics — Da Data Dad</title>
<meta name="description" content="Analytics dashboard for DaDataDad.com" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="/assets/images/favicon.png" />
<!-- Fonts: Syne (display) + DM Sans (body) + JetBrains Mono (data) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-JXCE49FJ7J"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-JXCE49FJ7J');
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/analytics-main.jsx"></script>
</body>
</html>
========================================
FILE: analytics-v2.html
========================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Site Analytics V2 — Da Data Dad</title>
<meta name="description" content="Enhanced analytics dashboard for DaDataDad.com" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="/assets/images/favicon.png" />
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-JXCE49FJ7J"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-JXCE49FJ7J');
</script>
<!-- Fonts: Syne (display) + DM Sans (body) + JetBrains Mono (data) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/analytics-v2-main.jsx"></script>
</body>
</html>
========================================
FILE: analytics-v3.html
========================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Site Analytics V3 — Da Data Dad</title>
<meta name="description" content="Analytics dashboard V3 for DaDataDad.com" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="/assets/images/favicon.png" />
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-JXCE49FJ7J"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-JXCE49FJ7J');
</script>
<!-- Fonts: Syne (display) + DM Sans (body) + JetBrains Mono (data) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/analytics-v3-main.jsx"></script>
</body>
</html>
========================================
FILE: vite.config.js
========================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { readFileSync } from 'fs';
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'));
export default defineConfig({
plugins: [react()],
define: {
__APP_VERSION__: JSON.stringify(pkg.version),
},
server: { open: true },
build: {
outDir: 'dist',
// Disable source maps in production — prevents exposing original source code
sourcemap: false,
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
analytics: resolve(__dirname, 'analytics-dashboard.html'),
analyticsV2: resolve(__dirname, 'analytics-v2.html'),
analyticsV3: resolve(__dirname, 'analytics-v3.html'),
portfolio: resolve(__dirname, 'portfolio.html'),
},
},
},
});
========================================
FILE: package.json
========================================
{
"name": "dadatadad-portfolio",
"private": true,
"version": "3.1.4",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"matter-js": "^0.20.0",
"p5": "^1.11.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.0.0"
}
}
========================================
FILE: .env.production
========================================
# ── GA4 Live Dashboard Connection ────────────────────────────────────────
# Set this to your deployed Cloudflare Worker URL to show live GA4 data.
# Leave blank or omit to show demo data.
#
# Example: VITE_GA4_WORKER_URL=https://ga4-analytics-api.your-subdomain.workers.dev
VITE_GA4_WORKER_URL=https://ga4-analytics-api.dadatadad-analytics.workers.dev
========================================
FILE: src/main.jsx
========================================
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './styles/index.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
);
========================================
FILE: src/App.jsx
========================================
/**
* App.jsx — Root component
*
* Layers:
* 1. LoadingScreen (fades out when p5 finishes preloading)
* 2. GameCanvas (p5.js physics + rendering)
* 3. React overlay (modal + HUD + stats)
* 4. GA4 event tracking (wired to EventBus)
*/
import { useState, useEffect, useCallback } from 'react';
import GameCanvas from './components/GameCanvas.jsx';
import DetailModal from './components/DetailModal.jsx';
import HUD from './components/HUD.jsx';
import LoadingScreen from './components/LoadingScreen.jsx';
import bus from './game/EventBus.js';
import { initGA4Tracking } from './game/ga4.js';
export default function App() {
const [detail, setDetail] = useState(null);
const [missHint, setMissHint] = useState(false);
const handleClose = useCallback(() => {
setDetail(null);
bus.emit('detail:close');
}, []);
useEffect(() => {
const unsub = bus.on('detail:open', (data) => {
setDetail(data);
});
return unsub;
}, []);
// Miss hint popup — shown after 3 consecutive misses
useEffect(() => {
const unsub = bus.on('miss:hint', (show) => {
setMissHint(show);
});
return unsub;
}, []);
// Initialize GA4 tracking on mount
useEffect(() => {
const cleanup = initGA4Tracking();
return cleanup;
}, []);
// ESC to close
useEffect(() => {
const onKey = (e) => {
if (detail && (e.key === 'Escape' || e.key === 'Backspace')) {
handleClose();
}
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [detail, handleClose]);
return (
<>
<LoadingScreen />
<GameCanvas />
<div className="ui-overlay">
<DetailModal detail={detail} onClose={handleClose} />
{!detail && (
<>
<HUD />
{missHint && (
<div className="miss-hint">
<span className="miss-hint-icon">💡</span>
<span>Try <strong>double-clicking</strong> a ball to view project details!</span>
</div>
)}
</>
)}
</div>
</>
);
}
========================================
FILE: src/portfolio-main.jsx
========================================
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import AccessiblePortfolio from './AccessiblePortfolio.jsx';
import './styles/portfolio.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<AccessiblePortfolio />
</StrictMode>,
);
========================================
FILE: src/analytics-main.jsx
========================================
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import AnalyticsDashboard from './analytics/AnalyticsDashboard.jsx';
import './styles/analytics.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<AnalyticsDashboard />
</StrictMode>,
);
========================================
FILE: src/analytics-v2-main.jsx
========================================
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import AnalyticsDashboardV2 from './analytics/AnalyticsDashboardV2.jsx';
import './styles/analytics-v2.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<AnalyticsDashboardV2 />
</StrictMode>,
);
========================================
FILE: src/analytics-v3-main.jsx
========================================
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import AnalyticsDashboardV3 from './analytics/AnalyticsDashboardV3.jsx';
// V3 uses inline styles — no CSS import needed.
// Import base resets only (shared across all dashboard versions).
import './styles/analytics-v2.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<AnalyticsDashboardV3 />
</StrictMode>,
);
========================================
FILE: src/data/projects.js
========================================
/**
* projects.js — Portfolio project data
*
* Replaces the old .txt file loading. Each project's image should be
* placed at public/assets/images/{id}.jpg
*/
const projects = [
{
id: 'aboutMe',
name: 'Josh Merritt',
link: 'https://www.linkedin.com/in/josh-merritt',
category: 'Me',
heroMode: 'full',
goal: 'To leave things better than I found them.',
role: 'Data Engineer \u00B7 BI Developer \u00B7 Full-Stack Builder',
technology:
'SQL, Python, JavaScript, React, Power BI, DAX, Google BigQuery, Git, Jupyter, Excel',
description:
'Builder with a bias for shipping. Just as comfortable wiring up a data pipeline as designing the frontend that displays it. Turns messy, complex data into clear answers for the people who need them \u2014 whether that\u2019s a VP checking quarterly numbers or a teammate debugging a report. Track record of balancing competing priorities while keeping projects on schedule and under budget.',
},
{
id: 'thisWebsite',
name: 'Portfolio Website',
link: 'https://github.com/joshmerritt/websiteAnimation',
category: 'Technology',
goal: 'Build a portfolio that doubles as a playable physics sandbox \u2014 because a list of links doesn\u2019t show how you think.',
role: 'Creator and Developer',
technology: 'React 18, Vite, p5.js (instance mode), Matter.js, ES Modules, GA4',
description:
'The site you\u2019re on right now. Each project is a ball you drag and launch into a goal \u2014 part portfolio, part physics playground. Works on phones and desktops, adapts its performance to the device, and quietly tracks every interaction so the analytics dashboard has real data to show.',
},
{
id: 'SiteAnalytics',
name: 'Site Analytics',
link: '/analytics-v3.html',
category: 'Technology',
goal: 'Prove the portfolio isn\u2019t just pretty \u2014 instrument every interaction and surface the data in a live dashboard.',
role: 'Creator and Developer',
technology: 'React, Cloudflare Workers, GA4 Data API, SVG, CSS Grid',
description:
'A live analytics dashboard for this very site. Every ball launch, score, and project open is tracked, piped through a custom backend, and displayed here in real time. Includes visitor trends, traffic sources, top pages, and a ball engagement funnel that shows how visitors move from launching a ball all the way to clicking a project link.',
},
{
id: 'arduinoCoopDoor',
name: 'Smart Chicken Coop',
link: 'https://github.com/joshmerritt/arduinoChickenCoopDoor',
category: 'Technology',
heroMode: 'full',
goal: 'Automate a chicken coop door that opens at sunrise and locks at sundown \u2014 no manual intervention required.',
role: 'Hardware Engineer and Builder',
technology: 'C++, Arduino, Motor Shield, Photoresistor, Programmable RGB LEDs, Custom Woodworking',
description:
'An automated coop door that senses daylight and drives a repurposed car-window motor to open at sunrise and lock at sundown \u2014 no manual intervention, no missed bedtimes. Built on an Arduino with a light sensor, color-coded status LEDs, and a custom wooden housing. A true end-to-end hardware project: designed the circuit, wrote the firmware, and built the enclosure.',
},
{
id: 'googleDataStudioServiceTechs',
name: 'Google Data Studio Streaming Dashboard',
link: 'https://www.upwork.com/fl/joshuapmerritt',
category: 'Business',
goal: 'Give frontline employees real-time visibility into their performance and standing relative to team benchmarks.',
role: 'BI Developer',
technology: 'Google Data Studio, Data Modeling, ScreenCloud, Amazon Fire TV',
description:
'A live KPI dashboard displayed on office TVs so the whole team can see how they\u2019re tracking against goals at a glance. Shows individual and team standings updated throughout the day, driving friendly competition without anyone needing to open a spreadsheet. Designed to be readable from across the room \u2014 big numbers, color-coded status, and automatic refresh.',
},
{
id: 'powerBIMetrics',
name: 'Microsoft Power BI',
link: 'https://www.upwork.com/fl/joshuapmerritt',
category: 'Business',
goal: 'Build a governed data warehouse with automated pipelines that lets business users self-serve answers without filing tickets.',
role: 'BI Developer and Data Analyst',
technology:
'Power BI, DAX, Power Query, REST APIs, Google BigQuery, Azure',
description:
'Built the reporting layer that lets business teams answer their own questions instead of filing a ticket and waiting. Data flows in automatically from multiple sources, lands in a clean, structured model, and powers a set of linked reports users can drill into on their own. Cut ad-hoc analyst requests significantly and gave leadership real-time visibility into KPIs.',
},
{
id: 'thewineyoudrink',
name: 'The Wine You Drink',
link: 'https://thewineyoudrink.web.app',
category: 'Apps',
goal: 'Replace spreadsheets and sticky notes with a purpose-built app for managing a wine collection \u2014 from cellar to glass.',
role: 'Product Owner and Creator \u2014 built this to solve my own problem after years of tracking bottles in spreadsheets. Developed with Claude AI as a coding partner.',
technology:
'React 18, Firebase (Auth + Firestore), Vite, PWA, Claude AI (label recognition), CSS Custom Properties',
description:
'A wine cellar app built for collectors who outgrew their spreadsheet. Organize multiple cellars, snap a photo of any label to auto-fill the details with AI, and see at a glance which bottles are in their prime. Includes tasting notes, drinking history, and stats \u2014 and it works offline, so you can use it in the cellar where there\u2019s no signal.',
},
{
id: 'dartleague',
name: 'Black Sheep Dart League',
link: 'https://theblacksheepdartleague.web.app',
category: 'Apps',
goal: 'Eliminate the spreadsheets, group texts, and paper scoresheets that were slowing down league night.',
role: 'Product Owner and Creator \u2014 got tired of the paper scoresheets and group-text chaos, so I built something better. Developed with Claude AI as a coding partner.',
technology:
'React 19, Firebase (Auth + Firestore + Cloud Functions), Tailwind CSS, Vite, Claude AI (scoresheet OCR)',
description:
'A league management app used weekly by real players. Handles RSVPs, automatically pairs teams based on past matchups and current standings, and tracks everyone\u2019s stats. The scorekeeper just snaps a photo of the paper scoresheet and AI reads it in \u2014 what used to take 30+ minutes of manual entry now takes seconds.',
},
];
export default projects;
========================================
FILE: src/AccessiblePortfolio.jsx
========================================
/**
* AccessiblePortfolio.jsx — Traditional portfolio layout
*
* A fully accessible, SEO-friendly version of the portfolio that presents
* the same project data as the physics playground but in a traditional
* card-based layout. Semantic HTML, keyboard navigable, screen reader friendly.
*
* Shares the existing design language: dark theme, Syne/DM Sans/JetBrains Mono,
* steel blue accents, glass morphism.
*/
import { useState, useEffect, useRef } from 'react';
import projects from './data/projects.js';
/* ── Category color map (matches the physics game's ball colors) ───────── */
const CATEGORY_COLORS = {
Me: '#5985b1',
Business: '#d4a843',
Technology: '#6b9f6b',
Apps: '#c06060',
};
/* ── Helpers ───────────────────────────────────────────────────────────── */
function ctaLabel(link) {
if (!link) return null;
if (link.includes('linkedin.com')) return 'View LinkedIn';
if (link.includes('github.com')) return 'View on GitHub';
if (link.includes('upwork.com')) return 'View Upwork Profile';
if (link.includes('.html')) return 'View Dashboard';
if (link.includes('.app')) return 'Open App';
return 'View Project';
}
function ctaIcon(link) {
if (link?.includes('.html')) return '📊';
if (link?.includes('github.com')) return '⌨';
if (link?.includes('linkedin.com')) return '💼';
if (link?.includes('.app')) return '🚀';
return '↗';
}
/* ── Scroll-reveal hook ────────────────────────────────────────────────── */
function useReveal() {
const ref = useRef(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) { setVisible(true); obs.disconnect(); } },
{ threshold: 0.12, rootMargin: '0px 0px -40px 0px' },
);
obs.observe(el);
return () => obs.disconnect();
}, []);
return [ref, visible];
}
/* ── Project Card ──────────────────────────────────────────────────────── */
function ProjectCard({ project, index }) {
const [ref, visible] = useReveal();
const categoryColor = CATEGORY_COLORS[project.category] || '#5985b1';
const hasLink = project.link && project.link !== 'null' && project.link.trim() !== '';
// Skip aboutMe from the project grid (it's in the hero)
if (project.id === 'aboutMe') return null;
const rows = [
{ label: 'Goal', value: project.goal },
{ label: 'My Role', value: project.role },
{ label: 'Technology', value: project.technology },
{ label: 'Summary', value: project.description },
].filter((r) => r.value);
return (
<article
ref={ref}
className={`port-card ${visible ? 'port-card--visible' : ''}`}
style={{ animationDelay: `${index * 80}ms` }}
itemScope
itemType="https://schema.org/CreativeWork"
>
<div className="port-card-image">
<img
src={`/assets/images/${project.id}.jpg`}
alt={`${project.name} — ${project.category} project by Josh Merritt`}
loading="lazy"
itemProp="image"
/>
<span className="port-card-badge" style={{ background: categoryColor }}>
{project.category}
</span>
</div>