forked from agelesslinux/agelesslinux.org
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfax.html
More file actions
873 lines (789 loc) · 42.1 KB
/
fax.html
File metadata and controls
873 lines (789 loc) · 42.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fax Your Rep — Ageless Linux</title>
<meta name="description" content="Send a one-page fax to your California legislator about AB 1043. No age verification required.">
<!-- Open Graph / social sharing -->
<meta property="og:title" content="Fax Your Rep — Ageless Linux">
<meta property="og:description" content="No age verification required to send a fax. A CYOA-style form to message your California legislator about AB 1043.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://agelesslinux.org/fax.html">
<meta property="og:image" content="https://agelesslinux.org/meta.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Fax Your Rep — Ageless Linux">
<meta name="twitter:description" content="No age verification required to send a fax.">
<meta name="twitter:image" content="https://agelesslinux.org/meta.jpg">
<link rel="alternate" type="application/rss+xml" title="Ageless Linux: Updates" href="/rss.xml">
<script src="base.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body class="page-fax">
<nav>
<div class="inner">
<a href="index.html" class="logo">AGELESS<span> LINUX</span></a>
<div class="links">
<a href="index.html">Home</a>
<a href="map.html">State Laws</a>
<a href="lobbyists.html">Lobbyists</a>
<a href="distros.html">Distros</a>
<a href="fax.html" class="active">Fax Rep</a>
<a href="download.html">Download</a>
<a href="hardware.html">Hardware</a>
<a href="citations.html">Citations</a></div>
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">
<img src="ast/sun.svg" class="sun-icon" alt="Switch to light mode">
<img src="ast/moon.svg" class="moon-icon" alt="Switch to dark mode">
</button>
</div>
</nav>
<main>
<div class="page-header">
<div class="container">
<h2>Fax Your Rep</h2>
<h1>A legal document that happens to be <em>furious</em>.</h1>
<p>
One page. One legislator. Section numbers cited. Sent by fax, because
<span class="legal-cite">Cal. Civ. Code § 1798.500(i)</span> does not regulate fax machines.
Every California state legislator voted yes on AB 1043 — 76-0 in the Assembly, 38-0 in the Senate.
This form lets you tell one of them why that was a mistake.
</p>
<p style="margin-top:16px;">
<em>No age verification required to send a fax.</em>
</p>
</div>
</div>
<section>
<div class="container">
<!-- Stepper -->
<div class="stepper" id="stepper">
<span class="pip" data-step="address">1. Address</span>
<span class="pip" data-step="legislators">2. Legislator</span>
<span class="pip" data-step="persona">3. Persona</span>
<span class="pip" data-step="cyoa">4. The Argument</span>
<span class="pip" data-step="compose">5. Compose</span>
<span class="pip" data-step="verify">6. Verify</span>
<span class="pip" data-step="send">7. Send</span>
</div>
<div class="wizard">
<!-- STEP 1: ADDRESS -->
<div class="step active" data-step="address">
<div class="card">
<h3>Enter your address</h3>
<p style="margin-bottom:20px;">
We use the U.S. Census Bureau geocoder to map your address to congressional
and state legislative districts. Your address is not stored, not logged, and
not used for anything besides the lookup. No age is collected.
</p>
<input type="text" id="addr-field" class="addr-input"
placeholder="425 Market St, San Francisco, CA 94105"
autocomplete="street-address">
<div class="addr-hint">Full street address, city, state, ZIP. California launch only.</div>
<div id="addr-error"></div>
<div style="margin-top:20px;">
<button class="btn btn-primary" id="btn-lookup">
<span id="lookup-spinner" style="display:none;"><span class="loader"></span></span>
Find my legislators
</button>
</div>
<div class="ageless-note">
AB 1043 § 1798.501(a)(1) requires operating systems to collect your child's
birth date. This form collects your street address for one HTTP request and throws
it away. The contrast is intentional.
</div>
</div>
</div>
<!-- STEP 2: LEGISLATORS -->
<div class="step" data-step="legislators">
<div class="card">
<h3>Pick a legislator to fax</h3>
<p id="leg-summary" style="color:var(--text-dim); font-size:14px; margin-bottom:8px;"></p>
<p style="font-size:14px; margin-bottom:12px;">
California state legislators voted <strong>76-0</strong> in the Assembly and
<strong>38-0</strong> in the Senate. Every person on this list who represents
you in Sacramento voted yes. Federal representatives did not vote on AB 1043
(it is state law) — fax them about the federal
<a href="#" onclick="return showFederalNote(event)">KIDS Act</a> and COPPA 2.0 instead.
</p>
<div class="leg-list" id="leg-list"></div>
<div style="margin-top:20px; display:flex; gap:10px; flex-wrap:wrap;">
<button class="btn btn-outline" onclick="goto('address')">← Change address</button>
<a href="fax-transparency.html" class="btn btn-outline">The Shame Wall →</a>
</div>
</div>
</div>
<!-- STEP 3: PERSONA -->
<div class="step" data-step="persona">
<div class="card">
<h3>What concerns you most?</h3>
<p style="font-size:14px; margin-bottom:8px;">
Pick the frame that fits you. Each path cites the same statute and makes the
same demand; the emotional lens is different. You can edit the text before sending.
</p>
<div class="persona-grid" id="persona-grid">
<div class="persona-card" data-persona="parent">
<div class="ptitle">Persona 01</div>
<div class="pname">The Parent</div>
<div class="pdesc">I already manage my kids' screen time. AB 1043 replaces my judgment with a state mandate and stores my child's birth date in every OS they touch.</div>
</div>
<div class="persona-card" data-persona="developer">
<div class="ptitle">Persona 02</div>
<div class="pname">The Developer</div>
<div class="pdesc">I build or maintain software. AB 1043 calls me an "operating system provider" and owes the state a real-time age verification API I cannot build.</div>
</div>
<div class="persona-card" data-persona="student">
<div class="ptitle">Persona 03</div>
<div class="pname">The Student</div>
<div class="pdesc">I taught myself to code on a Linux computer. AB 1043 defines me as a "user" — which the law defines as "a child" — and punishes honest age self-declaration.</div>
</div>
<div class="persona-card" data-persona="privacy">
<div class="ptitle">Persona 04</div>
<div class="pname">The Privacy Advocate</div>
<div class="pdesc">70,000 government IDs were stolen from Discord's age verification vendor in 58 hours. AB 1043 builds the next breach target at the OS level.</div>
</div>
</div>
<div style="margin-top:10px;">
<button class="btn btn-outline" onclick="goto('legislators')">← Back</button>
</div>
</div>
</div>
<!-- STEP 4: CYOA EDUCATION -->
<div class="step" data-step="cyoa">
<div class="card">
<h3 id="cyoa-title">The Argument</h3>
<p style="font-size:14px; color:var(--text-dim); margin-bottom:0;">
The form is the argument. By the time you finish, you will understand AB 1043
well enough to answer questions about it. Each step teaches one thing about
the statute.
</p>
<div id="cyoa-container"></div>
<div class="cyoa-nav">
<button class="btn btn-outline" id="btn-cyoa-prev">← Back</button>
<button class="btn btn-primary" id="btn-cyoa-next">Continue →</button>
<button class="btn btn-outline" id="btn-cyoa-skip">Skip to compose</button>
</div>
</div>
</div>
<!-- STEP 5: COMPOSE -->
<div class="step" data-step="compose">
<div class="card">
<h3>Compose your fax</h3>
<p style="font-size:14px; margin-bottom:8px;">
The body below is the template for your persona. Customize it, add personal
detail, cut what you do not want to say. Keep it to one page — the preview
will warn you if you overrun. Section citations are load-bearing; leave them in.
</p>
<div class="compose">
<div>
<label for="body-edit">Fax body</label>
<textarea id="body-edit" spellcheck="true"></textarea>
<div style="margin-top:8px; font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--text-dim);">
<span id="char-count">0</span> characters ·
<span id="word-count">0</span> words
</div>
</div>
<div>
<label>Live preview</label>
<div class="fax-preview" id="fax-preview"></div>
</div>
</div>
<div style="margin-top:20px; display:flex; gap:10px; flex-wrap:wrap;">
<button class="btn btn-outline" onclick="goto('cyoa')">← Back</button>
<button class="btn btn-primary" onclick="goto('verify')">Review & send →</button>
</div>
</div>
</div>
<!-- STEP 6: VERIFY -->
<div class="step" data-step="verify">
<div class="card">
<h3>Before we send</h3>
<p style="font-size:14px; margin-bottom:16px;">
We verify that you are a real person with a working email address before
spending the project's fax budget. We do not verify your age. We do not
verify your ID. We do not verify your handwriting.
</p>
<div class="verify-row">
<label for="sender-email">Your email</label>
<input type="email" id="sender-email" placeholder="[email protected]" autocomplete="email">
</div>
<div class="cert-row">
<input type="checkbox" id="cert-district">
<label for="cert-district">
I certify that I am a constituent of
<strong id="cert-leg-name">[legislator]</strong>
(or an interested party within California), that this fax reflects my own
views, and that I am authorized to send it.
<span class="legal-cite">CA Gov. Code § 11120 (open meetings)</span> and
<span class="legal-cite">47 U.S.C. § 227(b)(1)(C)</span>
(TCPA fax restrictions) govern this message.
</label>
</div>
<div class="cert-row">
<input type="checkbox" id="cert-age">
<label for="cert-age">
I acknowledge that this form has not asked for my age and will not ask
for my age. <em>No age verification required to send a fax.</em>
</label>
</div>
<div id="verify-error"></div>
<div style="margin-top:20px; display:flex; gap:10px; flex-wrap:wrap;">
<button class="btn btn-outline" onclick="goto('compose')">← Back</button>
<button class="btn btn-primary" id="btn-send">
<span id="send-spinner" style="display:none;"><span class="loader"></span></span>
Send the fax
</button>
</div>
</div>
</div>
<!-- STEP 7: SEND / RESULT -->
<div class="step" data-step="send">
<div class="card">
<h3>Delivery status</h3>
<div class="status-box" id="status-box">
<div class="label">Status</div>
<div class="big" id="status-big">—</div>
<div id="status-detail" style="color:var(--text-dim); font-size:14px;"></div>
</div>
<div id="result-share" style="display:none;">
<h4>Share the fax</h4>
<p style="font-size:14px; margin-bottom:8px;">
Screenshot the preview below and post it. The project is a civil disobedience
campaign; attention is part of the mechanism.
</p>
<div class="share-row">
<a class="btn btn-outline" id="share-twitter" target="_blank" rel="noopener">Share on X</a>
<a class="btn btn-outline" id="share-mastodon" target="_blank" rel="noopener">Share on Mastodon</a>
<a class="btn btn-outline" id="share-bluesky" target="_blank" rel="noopener">Share on Bluesky</a>
<button class="btn btn-outline" onclick="copyShareLink()">Copy link</button>
</div>
<h4>The fax that was sent</h4>
<div class="fax-preview" id="fax-preview-final" style="min-height:600px;"></div>
</div>
<div style="margin-top:20px; display:flex; gap:10px; flex-wrap:wrap;">
<a href="fax.html" class="btn btn-outline">Send another fax</a>
<a href="fax-transparency.html" class="btn btn-outline">The Shame Wall →</a>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div>Ageless Linux · FFwF Robotics LLC · John McCardle (BDFL)</div>
<div><a href="https://ffwf.net/contact">Contact</a> · <a href="https://github.com/agelesslinux">GitHub</a> · <a href="rss.xml">RSS</a></div>
<div class="legal-footer">
Licensed under the Unlicense (SPDX: Unlicense). Addresses submitted to this form
are forwarded once to the U.S. Census Bureau geocoder and then discarded.
No age data is collected at any step. If a back-end endpoint is unavailable,
this form will tell you plainly and point you at
<a href="https://ffwf.net/contact">ffwf.net/contact</a>.
</div>
</div>
</footer>
<script>
/* ============================================================
Fax Your Rep — CYOA form
Static frontend. Expects the following serverless endpoints:
GET /api/fax/lookup?address=ENCODED
POST /api/fax/send (body: {legislator, persona, body, sender_email})
GET /api/fax/status?id=FAX_ID
If any endpoint is unavailable the UI surfaces the failure and
points the user at ffwf.net/contact. No mock data anywhere.
============================================================ */
const API = {
lookup: '/api/fax/lookup',
send: '/api/fax/send',
status: '/api/fax/status',
};
/* These templates mirror agelesslinux/data/fax.py MESSAGES for preview
accuracy. The authoritative copy is the server version; keep in sync. */
const PERSONA_TEMPLATES = {
parent: `I am a California parent. I already manage my children's screen time using the parental controls built into every device I own. Apple Screen Time, Google Family Link — these tools work. I chose them. I configured them. I decide what my children can access.
AB 1043 replaces my judgment with a state mandate. Section 1798.501(a)(1) requires every operating system to collect my child's birth date at account setup. This is not optional. This is not at my discretion. The state has decided that my parenting is insufficient.
My child's birth date will now be stored by every operating system they touch. In September 2025, 70,000 government IDs were stolen from Discord's age verification vendor. You voted to create the next breach target — with my child's data.
The only thing my child will learn from this law is how to lie to a computer. That is not child safety. That is Prohibition — not the policy, but the pedagogy.
I am the parent. Not Sacramento.
Amend AB 1043 to make age collection opt-in at the parent's discretion, or repeal it entirely, before January 1, 2027.`,
developer: `I maintain open-source software used by thousands of people. AB 1043 says I am an "operating system provider."
Section 1798.500(g) defines that term as anyone who "develops, licenses, or controls the operating system software" on any general purpose computing device. No exemption for open source. No exemption for volunteers. No exemption for non-commercial projects.
A calculator firmware developer — a calculator — chose to geo-block California rather than face $7,500 per child in fines under Section 1798.503(a). Compliance costs start at $20,000 for small businesses. Most open-source projects have zero revenue.
Google, Meta, Snap, and OpenAI publicly endorsed this bill. They already collect age data. They already comply at zero marginal cost. The 600+ volunteer Linux distributions cannot. A law that the largest companies in the world already meet, and that hundreds of small projects cannot, is not a child safety law. It is a compliance moat.
The companies that benefit from OS-level age verification funded the organization that advocated for it. Common Sense Media receives funding from Chan Zuckerberg Initiative, Bezos Foundation, and Gates Foundation.
Exempt non-commercial and open-source software from AB 1043, or repeal it entirely, before January 1, 2027.`,
student: `I am a California student. I taught myself to code on a Linux computer. Under AB 1043, the operating system I used to learn would need to collect my birth date and tell every application I open that I am a minor.
Section 1798.500(i) defines "user" as "a child that is the primary user of the device." Adults are "account holders." I am not a person in this law. I am a category to be regulated.
Under Section 1798.501(b)(2)(A), every developer who receives the age signal has "deemed actual knowledge" of my age bracket. If I am honest about being under 18, applications will restrict what I can access. If I lie and say I am 21, everything works normally. You are teaching me that honesty is punished and that legal compliance prompts are obstacles to bypass.
I started programming when I was 12. The open-source tools I learned with would be illegal to distribute in California without a real-time age verification API that their volunteer maintainers cannot build.
Do not make it harder for students to learn. Amend or repeal AB 1043 before January 1, 2027.`,
privacy: `In September 2025, 70,000 government ID photos were stolen from Discord's age verification vendor Persona. The breach lasted 58 hours. Criminal group Scattered LAPSUS$ Hunters claimed 1.5TB of data from 5.5 million users.
That is what age verification produces. Not safety. A target.
AB 1043 requires every operating system to collect birth dates under Section 1798.501(a)(1) and send age signals to every application under Section 1798.501(a)(2). This creates surveillance infrastructure at the OS level. Today it is self-declaration. Industry analysts describe self-declaration as "getting the door open." Tomorrow it is biometric verification.
Cory Doctorow: "If a system can determine someone's age, it can determine who they are." Professor Steven Bellovin at Columbia confirmed this in peer-reviewed research. There is no technical architecture that separates age from identity.
15 million Americans lack driver's licenses. 43% of transgender Americans lack identity documents matching their actual identity. 18% of Black adults lack licenses versus 5% of white adults. Age verification is not neutral.
Do not build surveillance infrastructure that will be breached, repurposed, or weaponized. Repeal AB 1043.`,
};
/* Per-persona educational walkthrough — each step teaches one thing about AB 1043. */
const CYOA_STEPS = {
parent: [
{
label: "Step 1 of 3 — You already have the tools",
head: "Apple Screen Time. Google Family Link. iOS Content Restrictions.",
body: `<p>Every device sold in California in the last decade ships with parental controls
that a parent configures. You choose what your child can install, what they can
watch, what hours they can use the device. You made those choices.</p>
<p>AB 1043 does not replace a broken system. It replaces a working one — with a
government mandate your child cannot opt out of and you cannot turn off.</p>`,
},
{
label: "Step 2 of 3 — The breach you just signed up for",
head: "September 2025: 70,000 government IDs stolen in 58 hours.",
body: `<p><span class="legal-cite">Cal. Civ. Code § 1798.501(a)(1)</span> requires
every operating system to <em>collect</em> your child's birth date. Not "offer to
collect." Collect.</p>
<p>Discord's age verification vendor Persona was breached for 58 hours in
September 2025. The stolen data included 70,000 scans of government-issued photo
ID. Your child's OS is next. The law requires it to hold the data.</p>`,
},
{
label: "Step 3 of 3 — The ask",
head: "Amend AB 1043 to make parental age collection opt-in.",
body: `<p>The minimum repair to AB 1043 is a parental-choice exemption: a parent can
decline age collection on behalf of their child, and the OS treats that child as
an unverified account holder. The statute already distinguishes "account holder"
(adult) from "user" (child). Give parents the choice.</p>
<p>Full repeal is cleaner. Either is better than what you passed.</p>`,
},
],
developer: [
{
label: "Step 1 of 4 — You are now an operating system provider",
head: "§ 1798.500(g) covers your 200-star side project.",
body: `<p>"Operating system provider" means anyone who "develops, licenses, or controls
the operating system software" on any general purpose computing device. No
revenue threshold. No user-count threshold. No exemption for open source, for
volunteers, or for non-commercial projects.</p>
<p>If you maintain a Linux distribution — or Home Assistant, or a Raspberry Pi
image, or a calculator firmware — you are one.</p>`,
},
{
label: "Step 2 of 4 — The real-time API you do not have",
head: "§ 1798.501(a)(2) requires a live signal to every app.",
body: `<p>The statute does not ask you to collect an age at install time. It requires
you to <em>broadcast</em> an age range signal to every application that requests
it, in real time, for the lifetime of the device. That is a service, not a file
format. Services cost money. They require identity binding you do not have.</p>
<p>Google and Apple already run these services. You do not.</p>`,
},
{
label: "Step 3 of 4 — Seven thousand five hundred dollars per child",
head: "§ 1798.503(a) fines start at $7,500 per affected minor.",
body: `<p>A calculator firmware project — a calculator — chose to geo-block California
rather than risk these fines. Compliance advisories quote a starting figure of
$20,000 for small businesses. The going rate to comply is more than the going
rate for most open-source software to exist.</p>`,
},
{
label: "Step 4 of 4 — The ask",
head: "Exempt open source and non-commercial software.",
body: `<p>Every other state privacy statute with teeth — CCPA, CPRA — carries revenue
and user-count thresholds that spare small developers. AB 1043 has none. Add
them. Exempt non-commercial and open-source software. Or repeal the statute.</p>
<p>The companies that already comply asked for this bill. Ask yourself why.</p>`,
},
],
student: [
{
label: "Step 1 of 3 — The law calls you infrastructure",
head: "§ 1798.500(i): a \"user\" is a child. Adults are \"account holders.\"",
body: `<p>Read the statute carefully. In AB 1043, an adult is an "account holder" — a
person. A person under 18 is a "user" — a category. The law treats everyone under
18 as something the operating system manages, not someone the operating system
serves.</p>
<p>Your legislator voted for a statute that does not consider you a person in
the text. Start there.</p>`,
},
{
label: "Step 2 of 3 — Honesty is punished, lying is rewarded",
head: "§ 1798.501(b)(2)(A): \"deemed actual knowledge\" reaches every app.",
body: `<p>If you tell the OS you are 16 — the truth — every application that requests
an age signal will receive "under 18" and, per § 1798.501(b)(2)(A), must treat
you accordingly. Degraded experience. Restricted features. Educational content
you can no longer access.</p>
<p>If you tell the OS you are 21 — a lie — the signal says "adult" and every
application works exactly as designed. The law teaches you that honesty costs.</p>`,
},
{
label: "Step 3 of 3 — The ask",
head: "Do not make it harder to learn.",
body: `<p>Before you had this law, a 16-year-old could install Debian on a used
ThinkPad and teach themselves Python. After this law, the maintainers of that
Debian image face $7,500 per child in fines for the privilege of enabling that
education. Nothing about that equation serves children.</p>
<p>Repeal or amend AB 1043 before January 1, 2027.</p>`,
},
],
privacy: [
{
label: "Step 1 of 4 — The breach is a feature, not a bug",
head: "70,000 government IDs. 58 hours. 5.5 million users affected.",
body: `<p>Scattered LAPSUS$ Hunters claimed 1.5 TB of data from Discord's age
verification vendor Persona in September 2025. This is what age verification
infrastructure produces: not safety, but a breach target with a social security
number attached.</p>
<p>AB 1043 builds one of these per operating system.</p>`,
},
{
label: "Step 2 of 4 — The door does not close",
head: "\"Self-declaration is getting the door open.\" — industry analyst",
body: `<p>The statute accepts self-declaration today. Industry analysts openly describe
this as "getting the door open." The logical endpoint of an OS-level age signal
is an OS-level identity binding — biometric, documentary, or both. The statute
does not prevent this. It sets the table for it.</p>`,
},
{
label: "Step 3 of 4 — Age verification is not neutral",
head: "15 million Americans have no driver's license.",
body: `<p>43% of transgender Americans lack identity documents that match their lived
identity. 18% of Black adults lack a driver's license, versus 5% of white adults.
Any age verification regime that accretes toward document checks — as this one
will — discriminates by design.</p>
<p>Cory Doctorow, Steven Bellovin at Columbia, and the CDT have all written that
there is no technical architecture that separates age from identity.</p>`,
},
{
label: "Step 4 of 4 — The ask",
head: "Repeal AB 1043. Do not build the infrastructure.",
body: `<p>Every expansion of surveillance infrastructure has been justified on child
safety. Every expansion has been repurposed for something else. The door does
not close. The only way to prevent AB 1043 from becoming the scaffolding for
biometric OS-level identity verification is to not build it.</p>
<p>Repeal. Not amend. Repeal.</p>`,
},
],
};
const STATE = {
step: 'address',
address: null,
districts: null,
legislators: [],
legislator: null,
persona: null,
cyoaIndex: 0,
body: '',
faxId: null,
};
/* ---------- step navigation ---------- */
const STEPS_ORDER = ['address','legislators','persona','cyoa','compose','verify','send'];
function goto(step) {
STATE.step = step;
document.querySelectorAll('.wizard .step').forEach(el => {
el.classList.toggle('active', el.dataset.step === step);
});
const pips = document.querySelectorAll('.stepper .pip');
const idx = STEPS_ORDER.indexOf(step);
pips.forEach((p, i) => {
p.classList.remove('active', 'done');
if (i === idx) p.classList.add('active');
else if (i < idx) p.classList.add('done');
});
window.scrollTo({ top: document.querySelector('section').offsetTop - 60, behavior: 'smooth' });
if (step === 'cyoa') renderCyoa();
if (step === 'compose') renderCompose();
if (step === 'verify') renderVerify();
}
/* ---------- step 1: address lookup ---------- */
async function doLookup() {
const addr = document.getElementById('addr-field').value.trim();
const errEl = document.getElementById('addr-error');
errEl.innerHTML = '';
if (!addr) {
errEl.innerHTML = errorBox('Address required', 'Please enter a full street address, city, state, and ZIP.');
return;
}
document.getElementById('lookup-spinner').style.display = 'inline-block';
document.getElementById('btn-lookup').disabled = true;
try {
const url = API.lookup + '?address=' + encodeURIComponent(addr);
const resp = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' } });
if (!resp.ok) {
const txt = await resp.text().catch(() => '');
throw new Error('Lookup returned HTTP ' + resp.status + (txt ? (': ' + txt.slice(0, 200)) : ''));
}
const data = await resp.json();
if (!data.legislators || !data.legislators.length) {
throw new Error('The geocoder matched your address but no legislators were returned. Try a nearby address.');
}
STATE.address = data.address || addr;
STATE.districts = data.districts || null;
STATE.legislators = data.legislators;
renderLegislators();
goto('legislators');
} catch (err) {
errEl.innerHTML = errorBox(
'Lookup failed',
escHtml(err.message || String(err)) +
'<br><br>The district-lookup endpoint is not yet deployed at this host. Until it is, you can send a fax manually — every California state legislator\u2019s Capitol fax is <code>(916) 319-21XX</code> (Assembly, district number padded) or <code>(916) 651-49XX</code> (Senate). Or <a href="https://ffwf.net/contact">contact ffwf.net</a> for help.'
);
} finally {
document.getElementById('lookup-spinner').style.display = 'none';
document.getElementById('btn-lookup').disabled = false;
}
}
/* ---------- step 2: legislator list ---------- */
function renderLegislators() {
const listEl = document.getElementById('leg-list');
const sumEl = document.getElementById('leg-summary');
const d = STATE.districts || {};
sumEl.textContent = 'Matched: ' + (STATE.address || '(address)') +
(d.state ? ' \u2014 ' + d.state : '') +
(d.congressional != null ? ' CD-' + String(d.congressional).padStart(2,'0') : '') +
(d.state_senate != null ? ', SD-' + String(d.state_senate).padStart(2,'0') : '') +
(d.state_assembly != null ? ', AD-' + String(d.state_assembly).padStart(2,'0') : '');
listEl.innerHTML = '';
STATE.legislators.forEach((leg, i) => {
const hasFax = !!(leg.fax && (Array.isArray(leg.fax) ? leg.fax.length : leg.fax));
const faxDisplay = hasFax
? (Array.isArray(leg.fax) ? leg.fax[0] : leg.fax)
: 'no public fax on file';
const voteLine = (leg.level === 'state' && (leg.district || '').startsWith('CA'))
? 'Voted YES on AB 1043 (76-0 Assembly / 38-0 Senate; every CA legislator voted yes).'
: (leg.level === 'federal')
? 'AB 1043 is state law. Fax about KIDS Act (H.R. 7757) / COPPA 2.0 (S. 836).'
: '';
const card = document.createElement('div');
card.className = 'leg-card' + (hasFax ? '' : ' no-fax');
card.dataset.idx = i;
card.innerHTML = `
<div>
<div class="leg-name">${escHtml(leg.name)}</div>
<div class="leg-meta">${escHtml(leg.office)} — ${escHtml(leg.district)} (${escHtml(leg.party || 'Unknown')})</div>
${voteLine ? `<div class="leg-vote">${escHtml(voteLine)}</div>` : ''}
</div>
<div class="leg-fax ${hasFax ? 'ok' : 'missing'}">${escHtml(faxDisplay)}</div>
`;
if (hasFax) {
card.addEventListener('click', () => {
document.querySelectorAll('.leg-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
STATE.legislator = leg;
setTimeout(() => goto('persona'), 200);
});
}
listEl.appendChild(card);
});
}
/* ---------- step 3: persona ---------- */
document.getElementById('persona-grid').addEventListener('click', (e) => {
const card = e.target.closest('.persona-card');
if (!card) return;
document.querySelectorAll('.persona-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
STATE.persona = card.dataset.persona;
STATE.cyoaIndex = 0;
setTimeout(() => goto('cyoa'), 200);
});
/* ---------- step 4: cyoa walkthrough ---------- */
function renderCyoa() {
const steps = CYOA_STEPS[STATE.persona];
if (!steps) { goto('compose'); return; }
if (STATE.cyoaIndex >= steps.length) { STATE.cyoaIndex = steps.length - 1; }
const cur = steps[STATE.cyoaIndex];
document.getElementById('cyoa-title').textContent = 'The Argument — ' + personaName(STATE.persona);
document.getElementById('cyoa-container').innerHTML = `
<div class="cyoa-card">
<div class="cyoa-step-label">${escHtml(cur.label)}</div>
<div class="cyoa-headline">${cur.head}</div>
<div class="cyoa-body">${cur.body}</div>
</div>
`;
document.getElementById('btn-cyoa-prev').disabled = STATE.cyoaIndex === 0;
document.getElementById('btn-cyoa-next').textContent =
(STATE.cyoaIndex >= steps.length - 1) ? 'Compose fax \u2192' : 'Continue \u2192';
}
document.getElementById('btn-cyoa-prev').addEventListener('click', () => {
if (STATE.cyoaIndex > 0) { STATE.cyoaIndex--; renderCyoa(); }
});
document.getElementById('btn-cyoa-next').addEventListener('click', () => {
const steps = CYOA_STEPS[STATE.persona];
if (STATE.cyoaIndex >= steps.length - 1) { goto('compose'); }
else { STATE.cyoaIndex++; renderCyoa(); }
});
document.getElementById('btn-cyoa-skip').addEventListener('click', () => goto('compose'));
/* ---------- step 5: compose ---------- */
function renderCompose() {
const textarea = document.getElementById('body-edit');
if (!STATE.body) {
STATE.body = PERSONA_TEMPLATES[STATE.persona] || '';
}
textarea.value = STATE.body;
updateCounts();
renderPreview();
}
document.getElementById('body-edit').addEventListener('input', (e) => {
STATE.body = e.target.value;
updateCounts();
renderPreview();
});
function updateCounts() {
const t = STATE.body || '';
document.getElementById('char-count').textContent = t.length;
document.getElementById('word-count').textContent = t.trim().split(/\s+/).filter(Boolean).length;
}
function renderPreview(targetId) {
const el = document.getElementById(targetId || 'fax-preview');
if (!el) return;
const leg = STATE.legislator || {};
const today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const bill = (leg.level === 'state' && (leg.district || '').startsWith('CA'))
? { num: 'AB 1043', name: 'Digital Age Assurance Act' }
: { num: 'AB 1043', name: 'Digital Age Assurance Act (state) / KIDS Act (federal)' };
const bodyHtml = (STATE.body || '').split(/\n\n+/).map(p => `<p>${escHtml(p).replace(/\n/g,'<br>')}</p>`).join('');
el.innerHTML = `
<div class="fp-header">AGELESS LINUX</div>
<div class="fp-subhead">agelesslinux.org • FFwF Robotics LLC • Unlicense</div>
<div class="fp-addr">
<div class="row"><span class="label">TO:</span><span>${escHtml(leg.name || '[legislator]')}</span></div>
<div class="row"><span></span><span>${escHtml(leg.office || '')} — ${escHtml(leg.district || '')}</span></div>
<div class="row"><span class="label">RE:</span><span>${escHtml(bill.num)} — ${escHtml(bill.name)}</span></div>
<div class="row"><span class="label">DATE:</span><span>${escHtml(today)}</span></div>
</div>
<div class="fp-body">${bodyHtml}<p style="font-style:italic;margin-top:14px;">Sent by a constituent via agelesslinux.org.</p></div>
<div class="fp-footer"><span>No age verification required to send a fax.</span><span>agelesslinux.org</span></div>
`;
}
/* ---------- step 6: verify ---------- */
function renderVerify() {
document.getElementById('cert-leg-name').textContent = (STATE.legislator && STATE.legislator.name) || '[legislator]';
}
document.getElementById('btn-send').addEventListener('click', async () => {
const errEl = document.getElementById('verify-error');
errEl.innerHTML = '';
const email = document.getElementById('sender-email').value.trim();
const district = document.getElementById('cert-district').checked;
const age = document.getElementById('cert-age').checked;
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errEl.innerHTML = errorBox('Email required', 'Enter a working email address we can use to verify and send you the delivery receipt.');
return;
}
if (!district) {
errEl.innerHTML = errorBox('Constituent certification required', 'Please certify that you are a constituent or interested party.');
return;
}
if (!age) {
errEl.innerHTML = errorBox('Acknowledgement required', 'Please acknowledge that this form does not ask for your age.');
return;
}
if (!STATE.legislator) {
errEl.innerHTML = errorBox('No legislator selected', 'Go back and pick a legislator to fax.');
return;
}
if (!STATE.body || STATE.body.trim().length < 40) {
errEl.innerHTML = errorBox('Body too short', 'Write or restore the fax body before sending.');
return;
}
document.getElementById('send-spinner').style.display = 'inline-block';
document.getElementById('btn-send').disabled = true;
try {
const resp = await fetch(API.send, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
legislator: STATE.legislator,
persona: STATE.persona,
body: STATE.body,
sender_email: email,
}),
});
if (!resp.ok) {
const txt = await resp.text().catch(() => '');
throw new Error('Send returned HTTP ' + resp.status + (txt ? (': ' + txt.slice(0,240)) : ''));
}
const data = await resp.json();
STATE.faxId = data.fax_id || data.id || null;
goto('send');
renderPreview('fax-preview-final');
updateStatus(data.status || 'queued', data.detail || 'Waiting for Telnyx to accept the fax.');
if (STATE.faxId) pollStatus();
renderShare();
} catch (err) {
errEl.innerHTML = errorBox(
'Send failed',
escHtml(err.message || String(err)) +
'<br><br>The Telnyx send endpoint is not yet deployed at this host. You can still copy the fax body from the compose step and submit it manually via <a href="https://ffwf.net/contact">ffwf.net/contact</a> or any fax-by-email service.'
);
} finally {
document.getElementById('send-spinner').style.display = 'none';
document.getElementById('btn-send').disabled = false;
}
});
async function pollStatus() {
if (!STATE.faxId) return;
let tries = 0;
const maxTries = 30;
while (tries < maxTries) {
tries++;
await new Promise(r => setTimeout(r, 4000));
try {
const r = await fetch(API.status + '?id=' + encodeURIComponent(STATE.faxId));
if (!r.ok) continue;
const d = await r.json();
updateStatus(d.status || 'sending', d.detail || '');
if (d.status === 'delivered' || d.status === 'failed') return;
} catch (_) { /* keep polling */ }
}
}
function updateStatus(status, detail) {
const box = document.getElementById('status-box');
box.classList.remove('delivered', 'failed');
if (status === 'delivered') box.classList.add('delivered');
if (status === 'failed') box.classList.add('failed');
document.getElementById('status-big').textContent = status.toUpperCase();
document.getElementById('status-detail').textContent = detail || '';
if (status === 'delivered' || status === 'failed' || status === 'queued' || status === 'sending') {
document.getElementById('result-share').style.display = 'block';
}
}
/* ---------- share ---------- */
function renderShare() {
const leg = STATE.legislator || {};
const text = `I just faxed ${leg.name || 'my rep'} about AB 1043 at agelesslinux.org/fax — no age verification required to send a fax.`;
const url = 'https://agelesslinux.org/fax.html';
document.getElementById('share-twitter').href = 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(text) + '&url=' + encodeURIComponent(url);
document.getElementById('share-mastodon').href = 'https://mastodon.social/share?text=' + encodeURIComponent(text + ' ' + url);
document.getElementById('share-bluesky').href = 'https://bsky.app/intent/compose?text=' + encodeURIComponent(text + ' ' + url);
}
function copyShareLink() {
navigator.clipboard.writeText('https://agelesslinux.org/fax.html').catch(() => {});
}
/* ---------- helpers ---------- */
function personaName(p) {
return { parent: 'The Parent', developer: 'The Developer', student: 'The Student', privacy: 'The Privacy Advocate' }[p] || '';
}
function errorBox(title, msg) {
return `<div class="error-box"><div class="error-title">${escHtml(title)}</div><div>${msg}</div></div>`;
}
function escHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function showFederalNote(e) {
e.preventDefault();
alert('AB 1043 is California state law. The closest federal equivalents are the KIDS Act (H.R. 7757, passed House committee Mar 6 2026) and COPPA 2.0 (S. 836, passed Senate unanimously Mar 5-6 2026). Fax your US senators and representative about those.');
return false;
}
/* ---------- boot ---------- */
document.getElementById('btn-lookup').addEventListener('click', doLookup);
document.getElementById('addr-field').addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); doLookup(); }
});
goto('address');
</script>
</body>
</html>