Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
1308 lines (1231 loc) · 92.3 KB
/
Copy pathindex.html
File metadata and controls
1308 lines (1231 loc) · 92.3 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
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PinPoint Studio — measure the golfer, not the ball</title>
<meta name="description" content="PinPoint Studio measures the golfer, not the club or the ball. An open-source desktop app for golf studios that turns the cameras, phone and PC you already own into real body-movement measurement — kinematic sequence, wrist angles, HackMotion support and a cited diagnostic model. Free and open source (GPL v2), local-first and private: no sign-up, no account, no mailing list, no third-party processors — you install it, you use it, your data stays on your machine. In beta and under validation; 1.0 targeted late 2026.">
<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=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;1,9..144,400;1,9..144,500&family=Hanken+Grotesk:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{
--ink:#05080A;
--ink-2:#0A1115;
--ink-3:#111B20;
--paper:#F2EDE2;
--text-2:#CFD3CC;
--paper-dim:#94A09E;
--line:rgba(242,237,226,.12);
--line-strong:rgba(242,237,226,.22);
--pelvis:#E6AC54;
--thorax:#E07E64;
--arm:#8AB389;
--club:#7BC0DB;
--accent:#E6AC54;
--cool:#7BC0DB;
--font-display:'Fraunces',Georgia,serif;
--font-body:'Hanken Grotesk',system-ui,-apple-system,sans-serif;
--font-mono:'DM Mono',ui-monospace,monospace;
--maxw:1180px;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;background:var(--ink)}
body{
background:var(--ink);
color:var(--paper);
font-family:var(--font-body);
font-size:17px;
line-height:1.6;
-webkit-font-smoothing:antialiased;
text-rendering:optimizeLegibility;
overflow-x:clip;
}
.page{
position:relative;min-height:100vh;
background:
radial-gradient(1100px 720px at 8% -10%, rgba(230,172,84,.11), transparent 58%),
radial-gradient(1000px 820px at 104% 4%, rgba(123,192,219,.10), transparent 54%),
radial-gradient(900px 900px at 50% 120%, rgba(224,126,100,.06), transparent 60%),
var(--ink);
}
/* faint motion-capture dot field */
.page::before{
content:'';position:fixed;inset:0;z-index:0;pointer-events:none;
background-image:radial-gradient(rgba(242,237,226,.05) 1px, transparent 1.4px);
background-size:34px 34px;
-webkit-mask-image:radial-gradient(120% 90% at 50% 0%, #000 35%, transparent 78%);
mask-image:radial-gradient(120% 90% at 50% 0%, #000 35%, transparent 78%);
opacity:.6;
}
.page>*{position:relative;z-index:1}
a{color:inherit;text-decoration:none}
::selection{background:var(--accent);color:var(--ink)}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:3px}
.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:30px}
.eyebrow{
font-family:var(--font-mono);
font-size:12px;letter-spacing:.2em;text-transform:uppercase;
color:var(--paper-dim);
}
/* ---- reveal-on-scroll ---- */
.reveal{opacity:0;transform:translateY(22px);
transition:opacity .8s cubic-bezier(.22,.7,.2,1),transform .8s cubic-bezier(.22,.7,.2,1);
transition-delay:calc(var(--d,0) * 70ms);will-change:opacity,transform}
.reveal.in{opacity:1;transform:none}
/* ---- status badges ---- */
.badge{
font-family:var(--font-mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;
padding:3px 10px 3px 9px;border-radius:999px;border:1px solid var(--line-strong);
color:var(--paper-dim);display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
}
.badge i{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none;box-shadow:0 0 8px currentColor}
.badge.now{color:var(--arm);border-color:rgba(138,179,137,.42)}
.badge.dev{color:var(--accent);border-color:rgba(230,172,84,.42)}
.badge.soon{color:var(--club);border-color:rgba(123,192,219,.40)}
.badge.dev i{animation:pulse 2.6s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
/* ---- top bar ---- */
.page>header{
position:sticky;top:0;z-index:60;
background:rgba(5,8,10,.7);
backdrop-filter:blur(14px) saturate(1.2);
border-bottom:1px solid var(--line);
}
.bar{display:flex;align-items:center;justify-content:space-between;height:64px}
.mark{font-family:var(--font-mono);font-size:14px;letter-spacing:.04em;display:flex;align-items:center;gap:10px}
.mark .dot{width:9px;height:9px;border-radius:50%;background:var(--accent);
box-shadow:0 0 0 4px rgba(230,172,84,.16),0 0 14px rgba(230,172,84,.7)}
.mark b{font-weight:500}
.navlinks{display:flex;align-items:center;gap:28px;font-size:14px}
.navlinks a.muted{color:var(--paper-dim);position:relative}
.navlinks a.muted::after{content:'';position:absolute;left:0;right:100%;bottom:-5px;height:1px;background:var(--accent);transition:right .28s ease}
.navlinks a.muted:hover{color:var(--paper)}
.navlinks a.muted:hover::after{right:0}
.navlinks a.muted[aria-current="page"]{color:var(--paper)}
.navlinks a.muted[aria-current="page"]::after{right:0}
.btn{
font-family:var(--font-mono);font-size:13px;letter-spacing:.02em;
padding:9px 16px;border-radius:7px;border:1px solid var(--line-strong);
transition:border-color .2s,background .2s,color .2s,transform .2s,box-shadow .2s;white-space:nowrap;
}
.btn:hover{border-color:var(--paper);transform:translateY(-1px)}
.btn.solid{background:var(--accent);color:#1a1206;border-color:var(--accent);font-weight:500;
box-shadow:0 6px 22px -10px rgba(230,172,84,.9)}
.btn.solid:hover{background:#f0c279;border-color:#f0c279;box-shadow:0 10px 30px -10px rgba(230,172,84,1)}
@media(max-width:1180px){.navlinks{gap:16px;font-size:13px}}
@media(max-width:1040px){.navlinks a.muted{display:none}}
/* ---- hero ---- */
.hero{padding-top:72px;padding-bottom:30px;position:relative}
.hero-grid{display:grid;grid-template-columns:1.04fr .96fr;gap:54px;align-items:center}
h1{
font-family:var(--font-display);
font-weight:480;
font-optical-sizing:auto;
font-size:clamp(42px,6vw,76px);
line-height:.99;
letter-spacing:-.02em;
margin:22px 0 24px;
}
h1 .small{display:block;font-size:.46em;font-weight:500;letter-spacing:-.01em;color:var(--text-2);line-height:1.1;margin-bottom:.18em}
h1 em{font-style:italic;font-weight:500;
background:linear-gradient(96deg,var(--accent),#f3c987 55%,var(--cool));
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.lede{font-size:clamp(16px,1.45vw,19px);color:var(--text-2);max-width:48ch}
.lede strong{color:var(--paper);font-weight:600}
.status-line{
margin-top:26px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;
font-family:var(--font-mono);font-size:12.5px;color:var(--paper-dim);letter-spacing:.02em;
}
.cta-row{display:flex;gap:12px;margin-top:28px;flex-wrap:wrap}
.cta-row .btn{padding:13px 22px;font-size:14px}
/* instrument readout */
.readout{display:flex;gap:0;margin-top:32px;border:1px solid var(--line);border-radius:11px;overflow:hidden;
background:linear-gradient(180deg,rgba(255,255,255,.018),transparent)}
.readout .rd{flex:1;padding:13px 16px;border-right:1px solid var(--line)}
.readout .rd:last-child{border-right:0}
.readout .rk{display:block;font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--paper-dim);margin-bottom:5px}
.readout .rv{font-family:var(--font-mono);font-size:13.5px;color:var(--paper)}
.credit{margin-top:24px;font-family:var(--font-mono);font-size:12.5px;color:var(--paper-dim);letter-spacing:.02em}
.credit b{color:var(--paper);font-weight:400}
@media(max-width:900px){.hero-grid{grid-template-columns:1fr;gap:40px}.lede{max-width:54ch}}
/* ---- live capture panel ---- */
.capture{
background:linear-gradient(180deg,var(--ink-2),#060B0E);
border:1px solid var(--line-strong);
border-radius:16px;
padding:16px 16px 14px;
position:relative;
box-shadow:0 40px 80px -50px rgba(0,0,0,.9),0 0 0 1px rgba(123,192,219,.04) inset;
}
.cap-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;padding:0 4px}
.rec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--paper-dim);display:flex;align-items:center;gap:8px}
.rec i{width:7px;height:7px;border-radius:50%;background:var(--thorax);box-shadow:0 0 10px var(--thorax);animation:pulse 1.8s ease-in-out infinite}
.cap-meta{font-family:var(--font-mono);font-size:11px;color:var(--paper-dim);letter-spacing:.05em}
.cap-meta b{color:var(--cool);font-weight:500}
.scope{position:relative;width:100%;aspect-ratio:16/9.4;border-radius:11px;overflow:hidden;
background:radial-gradient(120% 95% at 50% 0%,rgba(123,192,219,.06),transparent 62%)}
.scope canvas{display:block;width:100%;height:100%}
.peaks{display:flex;flex-direction:column;gap:9px;margin:16px 4px 2px}
.peak{display:grid;grid-template-columns:9px 62px 1fr 58px;align-items:center;gap:11px;opacity:.38;transition:opacity .25s}
.peak.lit{opacity:1}
.pdot{width:9px;height:9px;border-radius:50%;box-shadow:0 0 9px currentColor}
.pname{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.03em;color:var(--text-2)}
.pbar{position:relative;height:4px;border-radius:3px;background:rgba(242,237,226,.07);overflow:hidden}
.pbar i{position:absolute;left:0;top:0;bottom:0;width:0;border-radius:3px;box-shadow:0 0 10px currentColor}
.pval{font-family:var(--font-mono);font-size:12.5px;color:var(--paper);text-align:right;font-variant-numeric:tabular-nums}
.pval .u{color:var(--paper-dim);font-size:10px;margin-left:2px}
.scope-note{margin-top:12px;padding:0 4px;font-family:var(--font-mono);font-size:10px;letter-spacing:.04em;color:var(--paper-dim);opacity:.75}
/* ---- inside the studio: the live app tour ---- */
.studio{
--c:var(--accent);
background:linear-gradient(180deg,var(--ink-2),#060B0E);
border:1px solid var(--line-strong);border-radius:16px;padding:16px 16px 14px;position:relative;
box-shadow:0 40px 80px -50px rgba(0,0,0,.9),0 0 0 1px rgba(123,192,219,.04) inset;
}
.studio-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;padding:0 4px}
.studio-mode{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--paper-dim);display:flex;align-items:center;gap:8px}
.studio-mode i{width:7px;height:7px;border-radius:50%;background:var(--c);box-shadow:0 0 10px var(--c);transition:background .55s,box-shadow .55s}
.studio-mode b{color:var(--paper);font-weight:500;letter-spacing:.05em;transition:color .3s}
.studio-count{font-family:var(--font-mono);font-size:11px;color:var(--paper-dim);letter-spacing:.08em;font-variant-numeric:tabular-nums}
.studio-count b{color:var(--c);font-weight:500;transition:color .55s}
.studio-screen{position:relative;width:100%;display:grid;border-radius:11px;overflow:hidden;background:var(--ink-3);box-shadow:0 0 0 1px var(--line) inset}
.studio-screen::before{content:'';position:absolute;left:0;right:0;top:0;height:2px;z-index:3;background:var(--c);box-shadow:0 0 12px var(--c);opacity:.85;transition:background .55s,box-shadow .55s}
.studio-screen::after{content:'';position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:11px;box-shadow:0 0 80px -12px rgba(0,0,0,.7) inset;background:radial-gradient(120% 90% at 50% 0%,rgba(123,192,219,.05),transparent 60%)}
.studio-screen .shot{grid-area:1/1;display:block;width:100%;height:auto;object-fit:cover;object-position:50% 0;opacity:0;transform:scale(1.05);transition:opacity .9s ease;will-change:opacity,transform;pointer-events:none}
.studio-screen .shot.active{opacity:1;transform:scale(1);transition:opacity .9s ease,transform 6.4s linear}
.studio-screen{cursor:zoom-in}
.studio-screen:focus-visible{outline:2px solid var(--c);outline-offset:3px}
/* ---- studio lightbox ---- */
.lightbox{position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;
padding:56px 72px 28px;background:rgba(5,8,10,.95);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
opacity:0;visibility:hidden;transition:opacity .25s,visibility 0s .25s}
.lightbox.open{opacity:1;visibility:visible;transition:opacity .25s}
.lightbox img{display:block;max-width:100%;max-height:calc(100vh - 120px);width:auto;height:auto;border-radius:8px;box-shadow:0 0 0 1px var(--line),0 30px 80px -30px rgba(0,0,0,.9);cursor:zoom-out}
.lb-cap{display:flex;gap:14px;align-items:baseline;font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--paper-dim)}
.lb-cap b{color:var(--paper);font-weight:500;letter-spacing:.05em}
.lb-cap span{color:var(--c,var(--accent));font-variant-numeric:tabular-nums}
.lb-btn{position:absolute;display:grid;place-items:center;width:44px;height:44px;border-radius:50%;border:1px solid var(--line-strong);
background:rgba(17,27,32,.8);color:var(--paper);font:400 22px/1 var(--font-body);cursor:pointer;transition:border-color .2s,background .2s}
.lb-btn:hover{border-color:var(--paper-dim);background:rgba(17,27,32,1)}
.lb-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.lb-prev{left:16px;top:50%;transform:translateY(-50%)}
.lb-next{right:16px;top:50%;transform:translateY(-50%)}
.lb-close{right:16px;top:12px;font-size:20px}
@media(max-width:620px){.lightbox{padding:56px 8px 72px}.lb-prev,.lb-next{top:auto;bottom:14px;transform:none}.lb-prev{left:calc(50% - 56px)}.lb-next{right:calc(50% - 56px)}}
.studio-note{margin-top:12px;padding:0 4px;font-family:var(--font-mono);font-size:10px;letter-spacing:.04em;color:var(--paper-dim);opacity:.75}
.tourbar{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px}
.tstep{--c:var(--accent);text-align:left;color:inherit;font:inherit;cursor:pointer;position:relative;overflow:hidden;
background:linear-gradient(180deg,rgba(255,255,255,.014),transparent);border:1px solid var(--line);border-radius:12px;
padding:15px 16px 17px;transition:border-color .25s,background .25s,transform .2s}
.tstep:hover{border-color:var(--line-strong);transform:translateY(-2px)}
.tstep .tnum{font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;color:var(--paper-dim);transition:color .3s}
.tstep h4{font-family:var(--font-display);font-weight:500;font-size:18px;letter-spacing:-.01em;margin:5px 0 6px}
.tstep p{font-size:13px;color:var(--text-2);line-height:1.5}
.tstep .tprog{position:absolute;left:0;bottom:0;height:2px;width:100%;background:rgba(242,237,226,.06)}
.tstep .tprog i{display:block;height:100%;width:0;background:var(--c);box-shadow:0 0 8px var(--c)}
.tstep[aria-current="true"]{border-color:var(--line-strong);background:linear-gradient(180deg,rgba(255,255,255,.035),transparent)}
.tstep[aria-current="true"] .tnum{color:var(--c)}
@media(max-width:620px){.tourbar{grid-template-columns:repeat(2,1fr)}}
@media(max-width:430px){.tourbar{grid-template-columns:1fr}}
/* ---- section scaffold ---- */
section{padding-block:96px;position:relative}
.kicker{display:flex;align-items:center;gap:10px;font-family:var(--font-mono);font-size:11.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--paper-dim);margin-bottom:18px}
.kicker .sig{width:8px;height:8px;border-radius:50%;flex:none;box-shadow:0 0 10px currentColor}
.sig.amber{color:var(--pelvis);background:var(--pelvis)}
.sig.coral{color:var(--thorax);background:var(--thorax)}
.sig.green{color:var(--arm);background:var(--arm)}
.sig.cyan{color:var(--club);background:var(--club)}
.sig.paper{color:var(--paper);background:var(--paper)}
h2{font-family:var(--font-display);font-weight:480;font-optical-sizing:auto;font-size:clamp(28px,3.6vw,46px);letter-spacing:-.018em;line-height:1.04}
h2 em{font-style:italic;color:var(--accent)}
.sec-lede{color:var(--text-2);max-width:62ch;margin-top:16px;margin-bottom:38px;font-size:16.5px}
.ulink{color:var(--paper);border-bottom:1px solid var(--line-strong);transition:color .18s,border-color .18s}
.ulink:hover{color:var(--accent);border-color:var(--accent)}
/* ---- guiding principle ---- */
.creed{padding-block:90px;border-top:1px solid var(--line)}
.creed-statement{font-family:var(--font-display);font-weight:480;font-optical-sizing:auto;font-size:clamp(32px,5vw,60px);line-height:1.03;letter-spacing:-.022em;margin:18px 0 18px;max-width:17ch}
.creed-statement em{font-style:italic;
background:linear-gradient(96deg,var(--accent),var(--cool));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.creed-sub{font-size:clamp(16px,1.5vw,19px);color:var(--text-2);max-width:64ch}
.notice{margin-top:38px;border:1px solid var(--line-strong);border-radius:14px;background:linear-gradient(180deg,var(--ink-2),transparent);padding:30px 32px;position:relative;overflow:hidden}
.notice::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(180deg,var(--accent),var(--cool))}
.notice .badge{margin-bottom:14px}
.notice h3{font-family:var(--font-display);font-weight:500;font-size:clamp(21px,2.4vw,28px);letter-spacing:-.01em;line-height:1.13;margin-bottom:12px}
.notice p{color:var(--text-2);max-width:72ch;font-size:15.5px;line-height:1.62}
/* ---- no strings: free, open, local ---- */
.freedom{padding-block:0 96px}
.fpanel{border:1px solid var(--line-strong);border-radius:16px;position:relative;overflow:hidden;
background:linear-gradient(180deg,rgba(138,179,137,.055),var(--ink-2) 42%,transparent);padding:36px 36px 32px}
.fpanel::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(180deg,var(--arm),var(--cool))}
.fpanel .eyebrow{display:block;margin-bottom:14px}
.fpanel h2{font-family:var(--font-display);font-weight:500;font-size:clamp(26px,3.4vw,42px);letter-spacing:-.018em;line-height:1.06;margin-bottom:16px;max-width:22ch}
.fpanel h2 em{font-style:italic;
background:linear-gradient(96deg,var(--arm),var(--cool));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.fpanel .fsub{color:var(--text-2);max-width:74ch;font-size:16.5px;line-height:1.62}
.fpanel .fsub strong{color:var(--paper);font-weight:600}
.fgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-top:28px}
.fitem{padding:19px 20px;border:1px solid var(--line);border-radius:12px;background:rgba(255,255,255,.014)}
.fitem .fk{font-family:var(--font-mono);font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--arm);
display:flex;align-items:center;gap:9px;margin-bottom:10px}
.fitem .fk i{width:7px;height:7px;border-radius:50%;flex:none;background:currentColor;box-shadow:0 0 9px currentColor}
.fitem p{font-size:14px;color:var(--text-2);line-height:1.52}
.fnote{margin-top:24px;padding-top:20px;border-top:1px solid var(--line);
font-family:var(--font-mono);font-size:12.5px;line-height:1.7;letter-spacing:.02em;color:var(--paper-dim);max-width:86ch}
.fnote b{color:var(--paper);font-weight:400}
@media(max-width:980px){.fgrid{grid-template-columns:1fr 1fr}}
@media(max-width:560px){.fgrid{grid-template-columns:1fr}.fpanel{padding:28px 22px 26px}}
@media(max-width:560px){.readout{flex-wrap:wrap}.readout .rd{flex:1 1 44%;border-bottom:1px solid var(--line)}}
/* ---- science in practice: links to the deep-dive sub-pages ---- */
.sci-links{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-top:24px}
.sci-card{display:flex;flex-direction:column;padding:26px 26px;border:1px solid var(--line);border-radius:13px;
background:linear-gradient(180deg,rgba(255,255,255,.014),transparent);
transition:border-color .25s,transform .25s,box-shadow .25s;position:relative;overflow:hidden}
.sci-card:hover{border-color:var(--line-strong);transform:translateY(-3px);box-shadow:0 26px 50px -40px rgba(0,0,0,.9)}
.sci-card .sci-k{font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--paper-dim);display:flex;align-items:center;gap:9px;margin-bottom:13px}
.sci-card .sci-k i{width:8px;height:8px;border-radius:50%;flex:none;background:currentColor;box-shadow:0 0 9px currentColor}
.sci-card h3{font-family:var(--font-display);font-weight:500;font-size:21px;letter-spacing:-.008em;margin-bottom:8px}
.sci-card p{font-size:14.5px;color:var(--text-2);line-height:1.55}
.sci-card .sci-go{display:inline-flex;align-items:center;gap:7px;margin-top:16px;
font-family:var(--font-mono);font-size:13px;letter-spacing:.02em;color:var(--accent);transition:color .2s,gap .2s}
.sci-card:hover .sci-go{gap:11px;color:#f0c279}
@media(max-width:1040px){.sci-links{grid-template-columns:1fr 1fr}}
@media(max-width:820px){.sci-links{grid-template-columns:1fr}}
/* ---- signals: three channels ---- */
.channels{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
.channels.c4{grid-template-columns:repeat(2,1fr)}
.channel{padding:28px 26px;border:1px solid var(--line);border-radius:13px;position:relative;overflow:hidden;
background:linear-gradient(180deg,rgba(255,255,255,.014),transparent);transition:border-color .25s,transform .25s,box-shadow .25s}
.channel::after{content:'';position:absolute;inset:0;border-radius:13px;opacity:0;transition:opacity .3s;
background:radial-gradient(80% 60% at 50% 0%,var(--gw,rgba(255,255,255,.06)),transparent 70%);pointer-events:none}
.channel:hover{transform:translateY(-3px);border-color:var(--line-strong)}
.channel:hover::after{opacity:1}
.ch-top{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
.channel .ch-label{font-family:var(--font-mono);font-size:12px;letter-spacing:.14em;text-transform:uppercase;display:flex;align-items:center;gap:9px}
.channel .ch-label i{width:9px;height:9px;border-radius:50%;box-shadow:0 0 9px currentColor}
.channel p{font-size:15px;color:var(--text-2);line-height:1.58}
@media(max-width:820px){.channels{grid-template-columns:1fr}}
/* ---- validation flow ---- */
.vflow{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
.vstep{padding:28px 26px;border:1px solid var(--line);border-radius:13px;position:relative;overflow:hidden;
background:linear-gradient(180deg,rgba(255,255,255,.014),transparent);transition:border-color .25s,transform .25s,box-shadow .25s}
.vstep:hover{transform:translateY(-3px);border-color:var(--line-strong);box-shadow:0 26px 50px -40px rgba(0,0,0,.9)}
.vstep-top{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
.vstep .vnum{font-family:var(--font-display);font-style:italic;font-weight:500;font-size:32px;line-height:1;color:var(--accent);opacity:.9}
.vflow .vstep:nth-child(2) .vnum{color:var(--cool)}
.vflow .vstep:nth-child(3) .vnum{color:var(--arm)}
.vstep h3{font-family:var(--font-display);font-weight:500;font-size:21px;margin-bottom:9px;letter-spacing:-.008em}
.vstep p{font-size:15px;color:var(--text-2);line-height:1.58}
.vlink{display:inline-flex;align-items:center;gap:7px;margin-top:22px;
font-family:var(--font-mono);font-size:13px;letter-spacing:.02em;color:var(--accent);transition:color .2s,gap .2s}
.vlink:hover{color:#f0c279;gap:11px}
@media(max-width:820px){.vflow{grid-template-columns:1fr}}
.plat{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:14px}
.plat .vstep h3{margin-bottom:0}
@media(max-width:820px){.plat{grid-template-columns:1fr}}
/* ---- four modes ---- */
.screens{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
.screen{
background:linear-gradient(180deg,var(--ink-2),transparent);border:1px solid var(--line);border-radius:14px;
padding:28px 28px 30px;transition:border-color .25s,transform .25s,box-shadow .25s;position:relative;overflow:hidden;
}
.screen:hover{border-color:var(--line-strong);transform:translateY(-3px);box-shadow:0 26px 50px -40px rgba(0,0,0,.9)}
.screen-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.screen .glyph{width:40px;height:40px;margin-bottom:18px;color:var(--accent);filter:drop-shadow(0 0 10px rgba(230,172,84,.35))}
.screen h3{font-family:var(--font-display);font-weight:500;font-size:22px;margin-bottom:9px;letter-spacing:-.008em}
.screen p{font-size:15px;color:var(--text-2);line-height:1.58}
@media(max-width:680px){.screens{grid-template-columns:1fr}}
/* ---- roadmap horizon ---- */
.horizon{display:grid;grid-template-columns:repeat(2,1fr);gap:14px}
.hzcol.c-now{grid-column:1/-1}
.hzcol.c-now ul{display:block;column-count:3;column-gap:34px}
.hzcol.c-now li{break-inside:avoid;margin-bottom:13px}
@media(max-width:1100px){.hzcol.c-now ul{column-count:2}}
.hzcol{padding:26px 26px;border:1px solid var(--line);border-radius:13px;background:linear-gradient(180deg,rgba(255,255,255,.012),transparent)}
.hzcol .hz-head{display:flex;align-items:center;gap:10px;margin-bottom:20px}
.hzcol .hz-head h3{font-family:var(--font-mono);font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--paper);font-weight:500}
.hzcol ul{list-style:none;display:flex;flex-direction:column;gap:13px}
.hzcol li{font-size:14.5px;color:var(--text-2);line-height:1.5;padding-left:20px;position:relative}
.hzcol li::before{content:'';position:absolute;left:0;top:8px;width:7px;height:7px;border-radius:50%;background:var(--line-strong)}
.hzcol.c-now li::before{background:var(--arm);box-shadow:0 0 8px rgba(138,179,137,.6)}
.hzcol.c-dev li::before{background:var(--accent);box-shadow:0 0 8px rgba(230,172,84,.6)}
.hzcol.c-soon li::before{background:var(--club);box-shadow:0 0 8px rgba(123,192,219,.6)}
@media(max-width:820px){.horizon{grid-template-columns:1fr}.hzcol.c-now ul{column-count:1}}
/* ---- spec strip ---- */
.specwrap{border:1px solid var(--line);border-radius:14px;overflow:hidden;background:linear-gradient(180deg,var(--ink-2),transparent)}
.specrow{display:flex;justify-content:space-between;gap:20px;padding:16px 26px;border-bottom:1px solid var(--line);font-family:var(--font-mono);font-size:13.5px;transition:background .2s}
.specrow:last-child{border-bottom:0}
.specrow:hover{background:rgba(255,255,255,.02)}
.specrow .k{color:var(--paper-dim);letter-spacing:.08em;text-transform:uppercase;font-size:11.5px}
.specrow .v{color:var(--paper);text-align:right}
.specrow .v .soon{color:var(--club)}
.specrow.hl{position:relative;background:linear-gradient(90deg,rgba(230,172,84,.10),rgba(123,192,219,.05))}
.specrow.hl::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(180deg,var(--accent),var(--cool))}
.specrow.hl .k{color:var(--accent)}
.specrow.hl:hover{background:linear-gradient(90deg,rgba(230,172,84,.15),rgba(123,192,219,.08))}
@media(max-width:560px){.specrow{flex-direction:column;gap:4px}.specrow .v{text-align:left}}
/* ---- personas ---- */
.personas{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.persona{border-top:2px solid;border-image:linear-gradient(90deg,var(--accent),transparent) 1;padding-top:20px}
.persona h4{font-family:var(--font-mono);font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--paper);margin-bottom:11px}
.persona p{font-size:15px;color:var(--text-2)}
@media(max-width:820px){.personas{grid-template-columns:1fr;gap:28px}}
/* ---- closing ---- */
.closing{text-align:center;padding-block:110px;border-top:1px solid var(--line);position:relative}
.closing h2{font-size:clamp(32px,4.8vw,56px);margin-bottom:20px}
.closing p{color:var(--text-2);max-width:54ch;margin:0 auto 32px}
.closing .cta-row{justify-content:center}
footer{border-top:1px solid var(--line);padding-block:38px;color:var(--paper-dim)}
.foot{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;font-family:var(--font-mono);font-size:12.5px;letter-spacing:.02em}
.foot a:hover{color:var(--paper)}
@media(prefers-reduced-motion:reduce){
.reveal{opacity:1;transform:none;transition:none}
.badge.dev i,.rec i{animation:none}
html{scroll-behavior:auto}
}
</style>
</head>
<body>
<div class="page">
<header>
<div class="wrap bar">
<a class="mark" href="index.html"><span class="dot"></span><b>PinPoint Studio</b></a>
<nav class="navlinks">
<a class="muted" href="index.html" aria-current="page">Home</a>
<a class="muted" href="science/how-it-works.html">How it works</a>
<a class="muted" href="science/swing-scoring.html">Swing scoring</a>
<a class="muted" href="science/session-diagnostics.html">Diagnostics</a>
<a class="muted" href="science/shaft-detection.html">Shaft detection</a>
<a class="muted" href="science/studio-setup.html">Studio setup</a>
<a class="muted" href="science/validation.html">Validation</a>
<a class="btn" href="https://github.com/PinPoint-Golf/PinPointStudio" target="_blank" rel="noopener">GitHub ↗</a>
<a class="btn solid" href="#get">Download</a>
</nav>
</div>
</header>
<main>
<section class="hero wrap">
<div class="hero-grid">
<div>
<span class="eyebrow reveal">Built for the golf studio · Free & open · No sign-up</span>
<h1 class="reveal" style="--d:1"><span class="small">Your launch monitor measures the ball.</span><em>We measure you.</em></h1>
<p class="lede reveal" style="--d:2">If you run a studio, you've already got the cameras, the PC and the launch monitor — so the outcome of every shot is no mystery. What's missing is what your <strong>body</strong> actually did to produce it, and whether you're really making the moves your coach asked for. That's the gap PinPoint Studio fills — and why it's named for the room it's built for.</p>
<p class="status-line reveal" style="--d:3">
<span class="badge dev"><i></i>Beta 4 — October 2026</span>
<span>Fourth beta out now · the first meant for other golfers · still validating · 1.0 targeted late 2026</span>
</p>
<div class="cta-row reveal" style="--d:4">
<a class="btn solid" href="https://github.com/PinPoint-Golf/PinPointStudio" target="_blank" rel="noopener">View on GitHub</a>
<a class="btn" href="#get">Get the beta — no sign-up</a>
</div>
<div class="readout reveal" style="--d:5">
<div class="rd"><span class="rk">Sampling</span><span class="rv">up to 200 Hz</span></div>
<div class="rd"><span class="rk">Pose</span><span class="rv">on-device</span></div>
<div class="rd"><span class="rk">Source</span><span class="rv">open · GPL v2</span></div>
<div class="rd"><span class="rk">Cost</span><span class="rv">free, always</span></div>
</div>
<p class="credit reveal" style="--d:6">From the author of <a class="ulink" href="https://www.goldencheetah.org" target="_blank" rel="noopener">GoldenCheetah</a>.</p>
</div>
<div class="capture reveal" style="--d:2" aria-label="Concept visualisation of the kinematic sequence — angular velocity of the pelvis, thorax, lead arm and club, negative through the backswing, then peaking in turn through the downswing: pelvis about 500, thorax 720, lead arm 1000 and club 2200 degrees per second at impact.">
<div class="cap-head">
<span class="rec"><i></i>Kinematic sequence</span>
<span class="cap-meta"><b id="pframe">P1</b> · deg/s · typical swing</span>
</div>
<div class="scope"><canvas id="kseq"></canvas></div>
<div class="peaks">
<div class="peak" data-seg="pelvis"><span class="pdot" style="background:var(--pelvis);color:var(--pelvis)"></span><span class="pname">Pelvis</span><span class="pbar"><i style="background:var(--pelvis);color:var(--pelvis)"></i></span><span class="pval" data-max="500"><span class="n">0</span><span class="u">°/s</span></span></div>
<div class="peak" data-seg="thorax"><span class="pdot" style="background:var(--thorax);color:var(--thorax)"></span><span class="pname">Thorax</span><span class="pbar"><i style="background:var(--thorax);color:var(--thorax)"></i></span><span class="pval" data-max="720"><span class="n">0</span><span class="u">°/s</span></span></div>
<div class="peak" data-seg="arm"><span class="pdot" style="background:var(--arm);color:var(--arm)"></span><span class="pname">Lead arm</span><span class="pbar"><i style="background:var(--arm);color:var(--arm)"></i></span><span class="pval" data-max="1000"><span class="n">0</span><span class="u">°/s</span></span></div>
<div class="peak" data-seg="club"><span class="pdot" style="background:var(--club);color:var(--club)"></span><span class="pname">Club</span><span class="pbar"><i style="background:var(--club);color:var(--club)"></i></span><span class="pval" data-max="2200"><span class="n">0</span><span class="u">°/s</span></span></div>
</div>
<p class="scope-note">A typical sequence, drawn to scale: each segment peaks later and faster than the one below it, the club at impact. Time is stretched through the downswing, as these charts always are. The live chart plots your own swing.</p>
</div>
</div>
</section>
<!-- inside the studio: live app tour -->
<section id="tour" class="wrap">
<div class="kicker reveal"><span class="sig paper"></span>The app</div>
<h2 class="reveal" style="--d:1">Inside <em>the studio.</em></h2>
<p class="sec-lede reveal" style="--d:2">A first look at the desktop app as it stands today — from setting up your kit, through replaying a swing beside its numbers and turning it round in 3-D, reading those numbers against a codified diagnostic model and seeing what a whole session adds up to, to marking swings up by hand for validation.</p>
<div id="studio" class="studio reveal" style="--d:3">
<div class="studio-head">
<span class="studio-mode"><i></i>Now viewing <b>Home</b></span>
<span class="studio-count"><b>1</b> / 8</span>
</div>
<div class="studio-screen" role="button" tabindex="0" aria-label="View screenshots full screen">
<img class="shot active" src="assets/home.webp" width="1800" height="1230" alt="PinPoint Studio home screen — choosing a player, club and analysis mode to start a session.">
<img class="shot" src="assets/settings.webp" width="1800" height="1230" loading="lazy" alt="PinPoint Studio settings — assigning and testing an IMU sensor before capture.">
<img class="shot" src="assets/replay.webp" width="1800" height="1230" loading="lazy" alt="PinPoint Studio replay — a captured swing played back beside its wrist metrics.">
<img class="shot" src="assets/swing3d.webp" width="1800" height="1230" loading="lazy" alt="PinPoint Studio 3-D swing — a 7-iron swing rebuilt as a figure on a floor grid, seen face-on at the finish, with the club's path traced as a fan of shaft positions through the swing and the target line in orange; preset views for down the line, top, target side, behind and each camera sit above it.">
<img class="shot" src="assets/diagnostics.webp" width="1800" height="1230" loading="lazy" alt="PinPoint Studio diagnostic model — the fault 'early extension' shown as a causal graph of what causes it and what it leads to, beside the measurement that detects it and the citation behind it.">
<img class="shot" src="assets/session.webp" width="1800" height="1230" loading="lazy" alt="PinPoint Studio session diagnostics — a finished fifteen-shot session read as ranked patterns: over the top on 15 of 15 measurable shots, flying trail elbow on 10 of 10, each card carrying the reading that fired it and how far outside its range it was, with limited thoracic rotation named as the likely driver of four of them.">
<img class="shot" src="assets/markup.webp" width="1800" height="1227" loading="lazy" alt="PinPoint Studio markup — annotating swing positions by hand for validation.">
<img class="shot" src="assets/system.webp" width="1800" height="1230" loading="lazy" alt="PinPoint Studio system view — built-in performance profiler showing CPU, memory, threads and per-model GPU use.">
</div>
<div class="tourbar">
<button type="button" class="tstep" data-mode="Home" style="--c:#E6AC54" aria-current="true">
<span class="tnum">01</span><h4>Home</h4>
<p>The starting point — pick a player, a club and an analysis mode to begin a session, and see what that player is working on.</p>
<span class="tprog"><i></i></span>
</button>
<button type="button" class="tstep" data-mode="Settings" style="--c:#7BC0DB" aria-current="false">
<span class="tnum">02</span><h4>Settings</h4>
<p>A rich configuration screen — assign and test every camera and IMU before you record.</p>
<span class="tprog"><i></i></span>
</button>
<button type="button" class="tstep" data-mode="Replay" style="--c:#E07E64" aria-current="false">
<span class="tnum">03</span><h4>Replay</h4>
<p>Play a captured swing back frame-by-frame, beside the key metrics it produced.</p>
<span class="tprog"><i></i></span>
</button>
<button type="button" class="tstep" data-mode="3-D swing" style="--c:#7BC0DB" aria-current="false">
<span class="tnum">04</span><h4>3-D swing</h4>
<p>The swing rebuilt in three dimensions — rotate and zoom it freely, let the view track the body through the motion, and trace the club's path from address to finish. Snap to face-on, down the line, top, target side, behind or either camera's own view.</p>
<span class="tprog"><i></i></span>
</button>
<button type="button" class="tstep" data-mode="Diagnostic model" style="--c:#f0c279" aria-current="false">
<span class="tnum">05</span><h4>Diagnostic model</h4>
<p>Swing faults codified from the literature and leading coaching texts — a causal network linking each fault to its causes, the measurements that detect it and the drills that correct it.</p>
<span class="tprog"><i></i></span>
</button>
<button type="button" class="tstep" data-mode="Session diagnostics" style="--c:#E07E64" aria-current="false">
<span class="tnum">06</span><h4>Session diagnostics</h4>
<p>What a whole session says, not one swing — patterns ranked by how often they recur and how far out they were, each with the reading behind it, and the one cause that would explain the most of them.</p>
<span class="tprog"><i></i></span>
</button>
<button type="button" class="tstep" data-mode="Markup" style="--c:#8AB389" aria-current="false">
<span class="tnum">07</span><h4>Markup</h4>
<p>Annotate swings by hand — the ground-truth labelling that underpins validation.</p>
<span class="tprog"><i></i></span>
</button>
<button type="button" class="tstep" data-mode="System" style="--c:#F2EDE2" aria-current="false">
<span class="tnum">08</span><h4>System</h4>
<p>Built-in profiling of CPU, memory and threads — performance engineered in from the ground up, across Windows, macOS and Linux.</p>
<span class="tprog"><i></i></span>
</button>
</div>
<p class="studio-note">Screens from the current pre-release build — the interface is still evolving.</p>
</div>
</section>
<!-- guiding principle -->
<section class="creed wrap">
<span class="eyebrow reveal">What drives it</span>
<p class="creed-statement reveal" style="--d:1">Peer-reviewed science, <em>in your hands.</em></p>
<p class="creed-sub reveal" style="--d:2">PinPoint Studio exists to put the measurements the research actually supports into the hands of coaches, players and enthusiasts — methods drawn from the published literature, kept open for anyone to check, and led by the science rather than private interests.</p>
<div class="notice reveal" style="--d:3">
<span class="badge dev"><i></i>Prototyping & validating</span>
<h3>We won't publish until the numbers can be trusted.</h3>
<p>PinPoint Studio is an early prototype under active validation. Every measurement is being checked against a hand-annotated corpus of real swings before it goes anywhere near a release — because results you can't rely on are no use to anyone. There's a beta to try if you want to follow along, but nothing to depend on yet — and there won't be until we're confident the figures hold up. That bar is deliberate, and getting the science right is the whole point.</p>
</div>
<p class="sec-lede reveal" style="--d:3;margin-top:42px;margin-bottom:0">Four deep dives into that science in practice — written in plain English, not academic jargon:</p>
<div class="sci-links">
<a class="sci-card reveal" style="--d:4" href="science/how-it-works.html">
<div class="sci-k"><i style="color:var(--pelvis)"></i>The whole stack</div>
<h3>How it works (for the nerds)</h3>
<p>The full tour of the machine — devices, calibration, capture, inference, fusion, segmentation, metrics and diagnosis, stage by stage.</p>
<span class="sci-go">Read the whole pipeline ↗</span>
</a>
<a class="sci-card reveal" style="--d:5" href="science/swing-scoring.html">
<div class="sci-k"><i style="color:var(--thorax)"></i>Scoring & metrics</div>
<h3>The science behind the score</h3>
<p>How a captured swing becomes an honest number — the movements we measure, and the maths that stops one number hiding a real fault.</p>
<span class="sci-go">Read how scoring works ↗</span>
</a>
<a class="sci-card reveal" style="--d:6" href="science/session-diagnostics.html">
<div class="sci-k"><i style="color:var(--arm)"></i>Diagnosis & coaching</div>
<h3>From a session to what to work on</h3>
<p>The diagnostic model in full, how evidence builds swing by swing into patterns and causes, how the work-on list is ordered — and what the app is entitled to call true.</p>
<span class="sci-go">Read how diagnosis works ↗</span>
</a>
<a class="sci-card reveal" style="--d:7" href="science/shaft-detection.html">
<div class="sci-k"><i style="color:var(--club)"></i>Club tracking</div>
<h3>Finding the shaft in the blur</h3>
<p>How we track a thin shaft moving past 100 mph — leaning on the golfer's anatomy and swing physics rather than fighting the blur, and how a bare club becomes its own ruler.</p>
<span class="sci-go">Read how tracking works ↗</span>
</a>
</div>
</section>
<!-- validation methodology -->
<section id="validation" class="wrap">
<div class="kicker reveal"><span class="sig amber"></span>Validation</div>
<h2 class="reveal" style="--d:1">How we earn the <em>numbers.</em></h2>
<p class="sec-lede reveal" style="--d:2">Trustworthy measurement isn't a claim, it's a process. We're building a hand-annotated corpus of real swings and testing every algorithm against it — so a figure only ships once we can show it agrees with the ground truth.</p>
<div class="vflow">
<div class="vstep reveal" style="--d:1">
<div class="vstep-top"><span class="vnum">01</span><span class="badge now"><i></i>Growing</span></div>
<h3>A corpus of swings</h3>
<p>A growing library of captured swings — multiple cameras, IMUs and players, recorded under real studio conditions. The dataset every measurement is held against, kept varied enough to show where a method breaks.</p>
</div>
<div class="vstep reveal" style="--d:2">
<div class="vstep-top"><span class="vnum">02</span><span class="badge now"><i></i>Built</span></div>
<h3>A markup tool to annotate</h3>
<p>A purpose-built tool to mark up each swing by hand — swing events, key positions and joint motion — turning raw captures into labelled ground truth a person has actually checked.</p>
</div>
<div class="vstep reveal" style="--d:3">
<div class="vstep-top"><span class="vnum">03</span><span class="badge dev"><i></i>Ongoing</span></div>
<h3>SwingLab holds them to it</h3>
<p>SwingLab, our validation harness, replays the exact production pipeline over the corpus and scores it against physics invariants and the hand-labelled truth — with byte-identical regression gates, so a change that shifts a single number can't slip through unnoticed. It holds the error bars to account as well: a ± only counts once real errors land inside it as often as it promises.</p>
</div>
</div>
<a class="vlink reveal" style="--d:4" href="science/validation.html">Read the full method — validation & tuning →</a>
</section>
<!-- how it works / signals -->
<section id="signals" class="wrap">
<div class="kicker reveal"><span class="sig cyan"></span>Capture</div>
<h2 class="reveal" style="--d:1">Ordinary kit, measured carefully.</h2>
<p class="sec-lede reveal" style="--d:2">PinPoint Studio reads the swing from hardware you can buy off the shelf — there's no sealed rig and no per-shot cloud round-trip. Five signals capture today — cameras, the phone in your pocket, wrist sensors of two kinds and, if you have one, your launch monitor; force estimation is on the way.</p>
<div class="channels">
<div class="channel reveal" style="--d:1;--gw:rgba(123,192,219,.10)">
<div class="ch-top">
<div class="ch-label" style="color:var(--club)"><i style="background:var(--club)"></i>Cameras</div>
<span class="badge now"><i></i>New in beta 4</span>
</div>
<p>USB webcams or GenICam / FLIR industrial cameras — face-on and down-the-line. Every live feed carries an on-device pose skeleton. The <strong>down-the-line</strong> camera now pulls its weight: it tracks its own skeleton, shaft and ball, sees the posture face-on can't — forward bend, pelvis thrust, trail knee flex — and with face-on it puts the shaft in 3D, with no calibration. And one industrial camera can now take the <strong>impact</strong> view: cropped to a strip at the ball, a Chameleon3 runs at 591 fps on a 70 µs exposure, keeps just the frames around the strike, and loops them picture-in-picture beside the swing. With two cameras the body is now fitted in <strong>3-D</strong>, standing on the floor with the club at its recorded length, and hip and chest turn, X-factor and stretch are read from it with no sensors worn. The down-the-line view also reads shaft lie and over the top from the hand path. The cameras you choose, not a locked box.</p>
</div>
<div class="channel reveal" style="--d:2;--gw:rgba(242,237,226,.08)">
<div class="ch-top">
<div class="ch-label" style="color:var(--paper)"><i style="background:var(--paper)"></i>Phone camera</div>
<span class="badge now"><i></i>Built</span>
</div>
<p>An iPhone running <strong>PinPoint Capture</strong> is a camera in the session, not a file you import afterwards — paired by scanning a QR code off the Studio screen, armed by the session's own Capture button, recording at 120–240 fps and delivering the clip to the swing library over WiFi or a cable. High-speed capture without high-speed cameras.</p>
</div>
<div class="channel reveal" style="--d:3;--gw:rgba(230,172,84,.10)">
<div class="ch-top">
<div class="ch-label" style="color:var(--pelvis)"><i style="background:var(--pelvis)"></i>Motion</div>
<span class="badge now"><i></i>Built</span>
</div>
<p>Off-the-shelf BLE IMUs (Witmotion WT901BLE67) at up to 200 Hz capture the wrist flexion, deviation and rotation a camera can't see cleanly — fused into joint angles on-device.</p>
</div>
<div class="channel reveal" style="--d:4;--gw:rgba(138,179,137,.10)">
<div class="ch-top">
<div class="ch-label" style="color:var(--arm)"><i style="background:var(--arm)"></i>Wrist sensor</div>
<span class="badge now"><i></i>Built</span>
</div>
<p>The <strong>HackMotion wG3</strong> is supported as the criterion instrument for the wrist — its own calibration ceremony, a check-sensors page that speaks the device's reference frame, and forearm rotation reported under the vendor's own name. Angles are published in ISB convention, and its readings sit <em>beside</em> our strap-on IMU estimates rather than in place of them.</p>
</div>
<div class="channel reveal" style="--d:5;--gw:rgba(240,194,121,.10)">
<div class="ch-top">
<div class="ch-label" style="color:var(--thorax)"><i style="background:var(--thorax)"></i>Launch monitor</div>
<span class="badge now"><i></i>Built</span>
</div>
<p>Studio plays the <strong>GSPro Connect</strong> server, so any monitor with an Open Connect bridge — a Garmin R10, Rapsodo MLM2PRO, SkyTrak+, PiTrac — connects straight to it; a Foresight GCQuad is read the other way, out of FSX2020. Either route, the readings are treated as a reference instrument: ball speed, spin, face and path recorded beside our own estimates, never replacing them. The Open Connect link is new, and so far proven against a simulator built from sixteen real clients rather than a device on a mat.</p>
</div>
<div class="channel reveal" style="--d:6;--gw:rgba(148,160,158,.08)">
<div class="ch-top">
<div class="ch-label" style="color:var(--paper-dim)"><i style="background:var(--paper-dim)"></i>Force</div>
<span class="badge soon"><i></i>Planned</span>
</div>
<p>Ground reaction force, estimated from motion, so you can see how the swing loads, shifts and unloads through the strike. The science here is still young, so it's one we'll follow the research on rather than rush.</p>
</div>
</div>
<a class="vlink reveal" style="--d:4" href="science/studio-setup.html">Set the bay up for the cleanest capture — the studio setup guide →</a>
</section>
<!-- companion app -->
<section id="phone" class="wrap">
<div class="kicker reveal"><span class="sig paper"></span>Companion app</div>
<h2 class="reveal" style="--d:1">Your phone is a <em>camera.</em></h2>
<p class="sec-lede reveal" style="--d:2">High-speed cameras are the biggest cost barrier to swing analysis — and most golfers already carry a device that shoots 1080p at 240 fps. <strong>PinPoint Capture</strong>, our iPhone companion app, makes that phone a first-class capture source: not a clip you film and import afterwards, but a device the session drives.</p>
<div class="vflow">
<div class="vstep reveal" style="--d:1">
<div class="vstep-top"><span class="vnum">01</span><span class="badge now"><i></i>Built</span></div>
<h3>Pair it once</h3>
<p>Press <em>Pair to my phone</em> and Studio puts a QR code on screen; scan it with the phone and you're paired. Scan again to add another — a down-the-line and a face-on — and a paired phone is remembered, on Windows and Linux as well as macOS.</p>
</div>
<div class="vstep reveal" style="--d:2">
<div class="vstep-top"><span class="vnum">02</span><span class="badge now"><i></i>Built</span></div>
<h3>It behaves like a camera</h3>
<p>A paired phone appears in the device list beside every other camera, with its own live preview. The session's Capture button arms it along with the rest, and each phone reports its battery, its thermal state, whether it's on WiFi or a cable, and how well its clock agrees with Studio's — agreement now converges in 1–8 seconds.</p>
</div>
<div class="vstep reveal" style="--d:3">
<div class="vstep-top"><span class="vnum">03</span><span class="badge now"><i></i>Built</span></div>
<h3>The swing comes back</h3>
<p>The clip the phone recorded is asked for, transferred over WiFi or a USB cable, and filed against the swing it belongs to in the library — replaying at the file's own frame rate, for as long as it actually is.</p>
</div>
</div>
<div class="plat">
<div class="vstep reveal" style="--d:1">
<div class="vstep-top"><h3>iPhone, while we build it</h3><span class="badge now"><i></i>Now</span></div>
<p>During initial development PinPoint Capture targets the iPhone — iOS 18 or later, on a phone that records 1080p at 120 fps or better. Its first screen checks the camera and tells you plainly whether it's good for capture. Development and hardware testing run on an iPhone 16. It goes to the App Store with the first release, at the end of 2026; until then it builds from source with Xcode.</p>
</div>
<div class="vstep reveal" style="--d:2">
<div class="vstep-top"><h3>Android, in time — not soon</h3><span class="badge soon"><i></i>Later</span></div>
<p>Android is coming, but it won't be quick. Its high-speed camera hands frames over in batches rather than one at a time, its camera and microphone don't always share a clock, and rolling-shutter timing has to be profiled model by model — each of which matters when the point is a millisecond you can trust. So the app is built for the port from day one: the core logic holds no iPhone code, device timing is data rather than code, and the protocol is the same on both. That makes Android a port rather than a rewrite — but it is still a port.</p>
</div>
</div>
<p class="sec-lede reveal" style="--d:4;margin-top:34px">It talks to Studio over <strong>PPCP</strong>, the PinPoint Capture Protocol — an open specification with an MIT-licensed reference implementation, so any device can implement it and any host can read it. The spec and the library are public alongside the app, and the app's own source is MIT-licensed too.</p>
<div class="cta-row reveal" style="--d:5;margin-top:22px">
<a class="btn" href="https://github.com/PinPoint-Golf/PinPointCapture" target="_blank" rel="noopener">PinPoint Capture ↗</a>
<a class="btn" href="https://github.com/PinPoint-Golf/libppcp" target="_blank" rel="noopener">The PPCP protocol ↗</a>
</div>
</section>
<!-- four modes -->
<section id="modes" class="wrap">
<div class="kicker reveal"><span class="sig coral"></span>Analysis</div>
<h2 class="reveal" style="--d:1">Four ways to see a swing.</h2>
<p class="sec-lede reveal" style="--d:2">PinPoint Studio is built around four analysis modes. Wrist Motion and full Swing Analysis both produce real output today — under validation, not yet released; Ground Force and the AI Coach follow on the same capture pipeline.</p>
<div class="screens">
<div class="screen reveal" style="--d:1">
<div class="screen-top">
<svg class="glyph" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 28c6 0 7-16 12-16s6 10 10 10 5-12 12-12"/><circle cx="15" cy="12" r="1.6" fill="currentColor" stroke="none"/></svg>
<span class="badge now"><i></i>Built</span>
</div>
<h3>Swing Analysis</h3>
<p>Full-body kinematics from the captured frames — a 133-keypoint whole-body pose, shaft, club-head and ball tracking, and 88 swing metrics extracted today from a 126-metric catalogue: X-factor, club-head speed, lag, hip and shoulder rotation, tempo and more. Swing timing now speaks in P-positions: P1–P8 and P10 are resolved from the club track itself, arbitrated on what a reading <em>is</em> rather than which producer got there first. The whole-swing score is the piece still being built.</p>
</div>
<div class="screen reveal" style="--d:2">
<div class="screen-top">
<svg class="glyph" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 34V18"/><path d="M20 18a10 10 0 0 1 10-10"/><path d="M20 18a10 10 0 0 0-9 6"/><circle cx="20" cy="18" r="2.2" fill="currentColor" stroke="none"/></svg>
<span class="badge now"><i></i>Built</span>
</div>
<h3>Wrist Motion</h3>
<p>Lead-arm flexion/extension, radial/ulnar deviation and rotation read straight from the sensors, segmented across the swing and scored against coaching bands — the cupping, cocking and flipping a coach watches for. Works from our strap-on IMUs or from a HackMotion wG3, whose readings are kept beside ours rather than replacing them. The first full analyzer; its results are in validation now, not yet released.</p>
</div>
<div class="screen reveal" style="--d:1">
<div class="screen-top">
<svg class="glyph" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 30h28"/><path d="M12 30V16"/><path d="M20 30V8"/><path d="M28 30V22"/></svg>
<span class="badge soon"><i></i>Planned</span>
</div>
<h3>Ground Force</h3>
<p>Loading, weight shift and ground reaction force through the downswing — where the power comes from, not just where it ends up. The approach is scoped and the research reviewed, but it's gated on force-plate ground truth we don't yet have — so we'll let the science lead rather than rush. Realistically, something meaningful here is a late-2027 job.</p>
</div>
<div class="screen reveal" style="--d:2">
<div class="screen-top">
<svg class="glyph" viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 9h26a2 2 0 0 1 2 2v15a2 2 0 0 1-2 2H17l-8 6V11a2 2 0 0 1 2-2Z"/><path d="M14 17h2M20 17h2M26 17h2"/></svg>
<span class="badge dev"><i></i>In dev</span>
</div>
<h3>AI Coach</h3>
<p>Ask about your swing out loud and get answers spoken back — all on-device. The local language model (Phi-4-mini), speech in and out, and the conversation loop are already built and running; grounding it in your own session findings is the work in progress.</p>
</div>
</div>
</section>
<!-- diagnosis -->
<section id="diagnosis" class="wrap">
<div class="kicker reveal"><span class="sig amber"></span>Diagnosis</div>
<h2 class="reveal" style="--d:1">From numbers to a <em>diagnosis.</em></h2>
<p class="sec-lede reveal" style="--d:2">Measurements alone don't coach. PinPoint Studio codifies the swing-fault literature into a causal model, then reads every session against it — so a number out of range becomes a named fault, a likely cause and a drill to work on.</p>
<div class="channels">
<div class="channel reveal" style="--d:1;--gw:rgba(240,194,121,.10)">
<div class="ch-top">
<div class="ch-label" style="color:var(--pelvis)"><i style="background:var(--pelvis)"></i>Diagnostic model</div>
<span class="badge now"><i></i>Built</span>
</div>
<p>160 swing conditions linked by 391 causal edges, 140 measures, 133 detection signals and 183 reference corridors, plus 17 physical screens and a drill library — drawn from a 40-source bibliography, every claim carrying its provenance. Browsable and editable inside the app, causal graph and all.</p>
</div>
<div class="channel reveal" style="--d:2;--gw:rgba(123,192,219,.10)">
<div class="ch-top">
<div class="ch-label" style="color:var(--club)"><i style="background:var(--club)"></i>Session diagnostics</div>
<span class="badge now"><i></i>Built</span>
</div>
<p>A shot fires findings; only a session diagnoses. Evidence accumulates shot by shot into a session ledger, patterns that recur are held to honest statistics — within a club, and counting only the shots that could actually be assessed — and causes are ranked along the model's authored chains. Because it is cumulative by construction, the ledger <em>is</em> the session summary at close: a finished session can be reviewed shot by shot in its own right without rewinding the live one, and the live panel casts to a second screen for the athlete. The panel reads as three tabs, <em>This shot</em>, <em>Session</em> and <em>Watching</em>; <em>This shot</em> follows the swing you pick. Each session's top faults are carried to the home screen as the athlete's <strong>work-ons</strong>, and a fault clears only when a later session measured it and found it quiet.</p>
</div>
<div class="channel reveal" style="--d:3;--gw:rgba(138,179,137,.10)">
<div class="ch-top">
<div class="ch-label" style="color:var(--arm)"><i style="background:var(--arm)"></i>Honest by construction</div>
<span class="badge now"><i></i>Built</span>
</div>
<p>Every verdict carries its receipt — the measurement behind it and the corridor it broke — and a finding with no number behind it says so rather than passing as fact. A reading outside its corridor says so, and never claims it is fine; where something can't be read, the panel says exactly why. The error bars on the readings reach the verdicts too: each finding carries the probability that it really fired, and the likely driver says how firm it is and whether it was measured or only inferred. Today's corridors are literature-seeded; re-seating them from measured swings is what the validation corpus is for.</p>
</div>
</div>
</section>
<!-- roadmap -->
<section id="roadmap" class="wrap">
<div class="kicker reveal"><span class="sig green"></span>Timeline</div>
<h2 class="reveal" style="--d:1">Where it is, and where it's going.</h2>
<p class="sec-lede reveal" style="--d:2">PinPoint Studio reached its fourth beta in October 2026, the first one meant for golfers other than its author: it now tells you what to work on, and says how sure it is. Capture — cameras, phones, wrist sensors and a launch monitor — full-swing metric extraction, the kinematic sequence, the diagnostic model and session diagnostics are all working, and the down-the-line camera now counts; the whole-swing score, ball flight and club data from the impact camera come next. It is still a beta, and we're still proving the numbers.</p>
<div class="horizon">
<div class="hzcol c-now reveal" style="--d:1">
<div class="hz-head"><span class="badge now"><i></i>Now</span><h3>Working</h3></div>
<ul>
<li>Multi-camera capture with live on-device pose overlay</li>
<li>Down-the-line camera with its own tracking — posture face-on can't see, and the shaft in 3D where the two views' shaft planes cross</li>
<li>Impact camera — an industrial camera cropped to a strip at the ball at 591 fps, the frames around the strike looped picture-in-picture</li>
<li>iPhone as a capture camera via the PinPoint Capture companion app, over WiFi or USB</li>
<li>Post-shot analysis — 133-keypoint whole-body pose, shaft, club-head & ball tracking</li>
<li>Markerless club tracking — a bare steel shaft read as its own ruler, so irons need no tape (graphite woods & hybrids still do)</li>
<li>Error bars on the club numbers — every shaft reading and metric carries a ± calibrated against 996 hand-marked frames, says whether it was checked or only propagated, and flags a reading that may be on the wrong thing; a club track the evidence contradicts is refused, with its reason</li>
<li>88 swing metrics extracted today — X-factor, club-head speed, lag, rotation, tempo, the kinematic sequence link by link, and from down the line shaft lie, over the top and posture off the outline of the body</li>
<li>Kinematic sequence chart — pelvis, chest, lead arm and club speeds from IMUs, one camera or two, read through to P8, with a tile setting the order they peaked in beside the order they should, each peak as a percentage of a professional's</li>
<li>Calibrated two-camera 3D body — a jointed skeleton standing on the floor, with hip and chest turn and X-factor read from it without IMUs</li>
<li>3-D swing view — rotate, zoom, track and trace the swing from any angle, or snap to either camera's view; now a panel on the Wrist screen too</li>
<li>Coach lines placed automatically — head circle, hip lines, the rear line and the address plane — and a synthetic club on replay</li>
<li>P-position timeline — P1–P8 and P10 resolved from the club track</li>
<li>Live lead-arm wrist analysis, scored against coaching bands</li>
<li>HackMotion wG3 wrist sensor as the criterion instrument, published in ISB convention</li>
<li>Diagnostic model — 182 conditions, 622 causal edges, 259 norms — editable in-app</li>
<li>Session diagnostics — a cumulative evidence ledger, ranked causes, cast to a second display; This shot, Session and Watching tabs, each finding carrying the probability it really fired</li>
<li>Work-ons on the home screen — each session's top faults, causes before symptoms, added up across sessions; a fault clears only when a later session measured it quiet</li>
<li>Session review — a finished session read back shot by shot, without rewinding the live one</li>
<li>Launch monitors — GSPro Open Connect (R10, MLM2PRO, SkyTrak+, PiTrac…) and the Foresight GCQuad — measured values beside our estimates</li>
<li>Shot detection — manual, IMU-impact and acoustic — with per-shot capture, replay & export</li>
<li>Swing files as .ppsw — a documented binary format, lossless and about a tenth the size, with MIT C++ and Python readers</li>
<li>Archiving — finished sessions out of the library and back, verified byte for byte, with the hours of capture you have left</li>
<li>Swing corpus, in-app markup tool & the SwingLab validation harness</li>
<li>Local speech in & out (Whisper + Kokoro) and a local coaching LLM (Phi-4-mini)</li>
</ul>
</div>
<div class="hzcol c-dev reveal" style="--d:2">
<div class="hz-head"><span class="badge dev"><i></i>Next</span><h3>In development</h3></div>
<ul>
<li>Whole-swing 0–100 score — the scorer & uncertainty maths are built; full-swing bands being seated</li>
<li>P9 — the one rung of the P1–P10 ladder still deferred</li>
<li>Club data from the impact camera — club speed, ball speed, angle of attack & low point, read at the ball, once the calibration card arrives</li>
<li>Ball-flight tracking — ball speed, launch angle & direction</li>
<li>Corpus v1 — re-seating the literature-seeded norms from measured swings</li>
<li>AI coach grounded in your session findings (chat, voice & local LLM already built)</li>
<li>Offline catch-up for the capture companion when the phone loses the host</li>
</ul>
</div>
<div class="hzcol c-soon reveal" style="--d:3">
<div class="hz-head"><span class="badge soon"><i></i>Later</span><h3>On the roadmap</h3></div>
<ul>
<li>Ground reaction force — gated on force-plate ground truth (likely late 2027)</li>
<li>Launch monitors that don't speak Open Connect (Uneekor, Bushnell) and more wrist sensors, on the same beside-not-instead-of contract</li>
<li>PinPoint Capture on Android — designed for from the start, but a real port, and not a quick one</li>
</ul>
</div>
</div>
</section>
<!-- spec -->
<section id="spec" class="wrap">
<div class="kicker reveal"><span class="sig amber"></span>Instrument</div>
<h2 class="reveal" style="--d:1">Built like an instrument.</h2>
<div class="specwrap reveal" style="--d:2">
<div class="specrow"><span class="k">Cameras</span><span class="v">USB webcam → GenICam / FLIR industrial · impact camera at 591 fps · iPhone via PinPoint Capture</span></div>
<div class="specrow"><span class="k">IMUs</span><span class="v">Witmotion WT901BLE67 · BLE · up to 200 Hz</span></div>
<div class="specrow"><span class="k">Wrist sensor</span><span class="v">HackMotion wG3 · BLE · ISB convention · via <a class="ulink" href="https://github.com/PinPoint-Golf/libwrist" target="_blank" rel="noopener">libwrist</a> (MIT)</span></div>
<div class="specrow"><span class="k">Pose</span><span class="v">MoveNet (live) · ViTPose 133-keypoint whole-body (analysis)</span></div>
<div class="specrow"><span class="k">Wrist kinematics</span><span class="v">IMU-quaternion joint angles, per P-position</span></div>
<div class="specrow"><span class="k">Swing timing</span><span class="v">P-position ladder, P1–P8 and P10 (P9 deferred)</span></div>
<div class="specrow"><span class="k">Metrics</span><span class="v">126-metric catalogue · 88 produced today</span></div>
<div class="specrow"><span class="k">Diagnostics</span><span class="v">182 conditions · 622 causal edges · 46 cited sources</span></div>
<div class="specrow"><span class="k">Swing file</span><span class="v">.ppsw — chunked binary, lossless, ~3 MB a swing · open spec · C++ & Python readers via <a class="ulink" href="https://github.com/PinPoint-Golf/libppswing" target="_blank" rel="noopener">libppswing</a> (MIT)</span></div>
<div class="specrow"><span class="k">Launch monitor</span><span class="v">GSPro Open Connect — R10, MLM2PRO, SkyTrak+, PiTrac — via <a class="ulink" href="https://github.com/PinPoint-Golf/libgspro" target="_blank" rel="noopener">libgspro</a> (MIT) · Foresight GCQuad via FSX2020</span></div>
<div class="specrow"><span class="k">Companion app</span><span class="v">PinPoint Capture · iPhone, iOS 18+ · App Store with 1.0, late 2026 · MIT <span class="soon">· Android planned, not soon</span></span></div>
<div class="specrow"><span class="k">Phone capture</span><span class="v">1080p at 120–240 fps · HEVC · 10 s rolling buffer · 30 fps live preview</span></div>
<div class="specrow"><span class="k">Phone link</span><span class="v">PPCP over WiFi or USB · clock σ 1.2 ms WiFi, 0.3 ms cable · open spec, MIT reference library</span></div>
<div class="specrow"><span class="k">Speech</span><span class="v">Whisper STT · Kokoro TTS · on-device</span></div>
<div class="specrow"><span class="k">Privacy</span><span class="v">Local by default — cloud only if you configure it</span></div>
<div class="specrow"><span class="k">Built with</span><span class="v">Qt 6.11 · C++20</span></div>
<div class="specrow hl"><span class="k">GPU acceleration</span><span class="v">NVIDIA GPUs supported <span class="soon">· AMD planned, not yet available</span></span></div>
<div class="specrow"><span class="k">Hardware</span><span class="v">Off-the-shelf — no proprietary kit</span></div>
<div class="specrow"><span class="k">Platforms</span><span class="v">Windows · macOS · Linux</span></div>
<div class="specrow"><span class="k">Licence</span><span class="v">GPL v2 · source in the open</span></div>
</div>
</section>
<!-- personas -->
<section class="wrap">
<div class="kicker reveal"><span class="sig paper"></span>Who it's for</div>
<h2 class="reveal" style="--d:1">Made for the people in the bay.</h2>
<div class="personas" style="margin-top:34px">
<div class="persona reveal" style="--d:1"><h4>Teaching pros</h4><p>Objective numbers to set beside what you already see — an evidence layer for a lesson, on hardware that fits a studio bay or a garage.</p></div>
<div class="persona reveal" style="--d:2"><h4>Serious amateurs</h4><p>The wrist and sequencing measurements you've read about for tour players, applied to your own swing and the kit you choose.</p></div>
<div class="persona reveal" style="--d:3"><h4>Coaches & researchers</h4><p>An open, inspectable platform for understanding golf movement — every metric traceable to the source, the source in the open.</p></div>
</div>
</section>
<!-- no strings: free, open, local -->
<section class="freedom wrap">
<div class="fpanel reveal">
<span class="eyebrow">No sign-up · No mailing list · No third parties</span>
<h2>You install it, you use it. <em>That's the whole deal.</em></h2>
<p class="fsub">There is no registration, no account, no licence key and no trial. We don't ask for your name, your email or your club. There's no mailing list to join and no third-party processor in the loop — <strong>nothing to unsubscribe from, because there's nothing to subscribe to</strong>. It's free as in <strong>freedom</strong> and free as in <strong>beer</strong>, it is open source, and it will stay that way. Download the build, run it, and your swings stay on your own machine.</p>
<div class="fgrid">
<div class="fitem">
<span class="fk"><i></i>No sign-up</span>
<p>No account, no key, no activation, no “request access”. Download it and it runs.</p>
</div>
<div class="fitem">
<span class="fk"><i></i>Free, both ways</span>
<p>Free as in freedom and free as in beer. GPL v2, source in the open — no paid tier, no pro edition, no upsell.</p>
</div>
<div class="fitem">
<span class="fk"><i></i>Nobody's list</span>
<p>No mailing list, no analytics, no tracking, no third-party processors. We hold no details about you because we never collect any.</p>
</div>
<div class="fitem">
<span class="fk"><i></i>Your data, your disk</span>
<p>Swings, video and sessions are files on your own machine, in a documented format any tool can open — archived to a folder you choose, or moved off entirely, whenever you like. Local by default — nothing goes to a cloud unless you set one up yourself.</p>
</div>
</div>
<p class="fnote"><b>Open in every way.</b> PinPoint Studio itself is GPL v2. And when we write something another project could use, we don't leave it buried inside the app — we package it up as a standalone, MIT-licensed library, specifically so others can benefit from it. Talking to a HackMotion wrist sensor became <a class="ulink" href="https://github.com/PinPoint-Golf/libwrist" target="_blank" rel="noopener">libwrist</a>; speaking GSPro Open Connect to a launch monitor became <a class="ulink" href="https://github.com/PinPoint-Golf/libgspro" target="_blank" rel="noopener">libgspro</a>; reading and writing a recorded swing became <a class="ulink" href="https://github.com/PinPoint-Golf/libppswing" target="_blank" rel="noopener">libppswing</a>; the phone protocol is a published spec. We will always be open source, and we will always back the organisations and standards that are open too.</p>
</div>
</section>
<!-- closing -->
<section id="get" class="closing wrap">
<span id="register" aria-hidden="true"></span>
<span class="eyebrow reveal">Pre-release · nothing to sign</span>
<h2 class="reveal" style="--d:1">It's in beta — and it's yours.</h2>
<p class="reveal" style="--d:2">PinPoint Studio reached its fourth beta in October 2026, with a 1.0 targeted for late in the year — once we're confident the measurements can be relied upon. There's no form to fill in and no list to join: take the build, run it, keep it. It's GPL v2 and built in the open — for golfers, coaches and researchers alike. Watch the repo if you want to hear when something lands, and bring questions to the issue tracker.</p>
<div class="cta-row reveal" style="--d:3">
<a class="btn solid" href="https://github.com/PinPoint-Golf/PinPointStudio/releases/latest" target="_blank" rel="noopener">Download the latest beta ↗</a>
<a class="btn" href="https://github.com/PinPoint-Golf/PinPointStudio" target="_blank" rel="noopener">View on GitHub</a>
<a class="btn" href="https://github.com/PinPoint-Golf/PinPointStudio/issues" target="_blank" rel="noopener">Ask a question ↗</a>
</div>
</section>
</main>
<footer>
<div class="wrap foot">
<span>PinPoint Studio · open-source golf swing analysis · no sign-up, no tracking</span>
<span><a href="https://github.com/PinPoint-Golf/PinPointStudio" target="_blank" rel="noopener">GitHub</a> · GPL v2 · Windows / macOS / Linux</span>
</div>
</footer>
</div><!-- /.page -->
<script>
/* ---------- reveal on scroll ---------- */
(function(){
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var nodes = document.querySelectorAll('.reveal');
if(reduce || !('IntersectionObserver' in window)){
nodes.forEach(function(n){ n.classList.add('in'); });
return;
}
var io = new IntersectionObserver(function(entries){
entries.forEach(function(e){
if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target); }
});
}, {rootMargin:'0px 0px -8% 0px', threshold:0.12});
nodes.forEach(function(n){ io.observe(n); });
})();
/* ---------- hero: the kinematic sequence as a live instrument ---------- */
(function(){
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var cv = document.getElementById('kseq');
if(!cv) return;
var ctx = cv.getContext('2d');
var frameLabel = document.getElementById('pframe');
var COL = { paper:'#F2EDE2', dim:'#94A09E', line:'rgba(242,237,226,.11)', strong:'rgba(242,237,226,.22)' };
/* four channels, as a real kinematic sequence reads:
negative through the backswing, then proximal -> distal peaks into impact.
Control points are (P-position index 0..9, deg/s); the curve is a monotone
cubic through them, so it never overshoots and each peak sits on the line. */
var TR = [
{key:'pelvis', col:'#E6AC54', max:500, pk:4.35, pts:[
[0,0],[0.5,-35],[1,-65],[2,-90],[2.6,-75],[3,30],[3.4,150],[3.8,330],
[4,430],[4.35,500],[4.8,485],[5.2,455],[5.6,390],[6,300],[6.6,220],[7,185],[7.6,140],[8,110],[8.5,60],[9,0]]},
{key:'thorax', col:'#E07E64', max:720, pk:4.8, pts:[
[0,0],[0.5,-60],[1,-110],[2,-155],[2.6,-135],[3,-20],[3.4,120],[3.8,380],
[4,600],[4.4,690],[4.8,720],[5.2,700],[5.6,600],[6,450],[6.4,360],[7,290],[7.6,200],[8,150],[8.5,80],[9,0]]},
{key:'arm', col:'#8AB389', max:1000, pk:5.2, pts:[
[0,0],[0.5,-95],[1,-180],[2,-265],[2.6,-230],[3,-30],[3.4,200],[3.8,520],
[4,700],[4.4,850],[4.8,960],[5.2,1000],[5.6,960],[6,800],[6.3,620],[6.7,420],[7,320],[7.6,230],[8,180],[8.5,90],[9,0]]},
{key:'club', col:'#7BC0DB', max:2200, pk:6.0, pts:[
[0,0],[0.5,-200],[1,-370],[2,-480],[2.6,-380],[3,-60],[3.3,-10],[3.6,20],
[4,150],[4.4,450],[4.8,950],[5.2,1500],[5.6,1900],[6,2200],[6.25,2000],[6.5,1450],
[6.8,700],[7,350],[7.4,300],[8,300],[8.5,200],[9,0]]}
];
var NP = 10; /* P1 .. P10 */
var IMPACT = 6/9; /* P7, where the club peaks */
var TOPP = 3/9; /* P4 */
var VMIN = -600, VMAX = 2400;
/* live readout rows */
var rows = {};
Array.prototype.forEach.call(document.querySelectorAll('.peak'), function(r){
rows[r.getAttribute('data-seg')] = {
el:r, bar:r.querySelector('.pbar i'), num:r.querySelector('.pval .n')
};
});
function clamp(x,a,b){ return x<a?a:(x>b?b:x); }
function bell(t,mu,sg){ var d=(t-mu)/sg; return Math.exp(-0.5*d*d); }
function ease(x){ return x<=0?0:(x>=1?1:x*x*x*(x*(x*6-15)+10)); } /* smootherstep */
function rgba(hex,a){
var n=parseInt(hex.slice(1),16);
return 'rgba('+((n>>16)&255)+','+((n>>8)&255)+','+(n&255)+','+a+')';
}
/* monotone cubic (Fritsch-Carlson): no overshoot, so no invented peaks */
function pchip(pts){
var n=pts.length, h=[], d=[], m=[], i;
for(i=0;i<n-1;i++){ h[i]=pts[i+1][0]-pts[i][0]; d[i]=(pts[i+1][1]-pts[i][1])/h[i]; }
m[0]=d[0]; m[n-1]=d[n-2];
for(i=1;i<n-1;i++){
if(d[i-1]*d[i]<=0){ m[i]=0; }
else {
var w1=2*h[i]+h[i-1], w2=h[i]+2*h[i-1];
m[i]=(w1+w2)/(w1/d[i-1]+w2/d[i]);
}
}
return function(x){
if(x<=pts[0][0]) return pts[0][1];
if(x>=pts[n-1][0]) return pts[n-1][1];
var k=0; while(k<n-2 && x>pts[k+1][0]) k++;
var t=(x-pts[k][0])/h[k], t2=t*t, t3=t2*t;
return (2*t3-3*t2+1)*pts[k][1] + (t3-2*t2+t)*h[k]*m[k]
+ (-2*t3+3*t2)*pts[k+1][1] + (t3-t2)*h[k]*m[k+1];
};