Repository navigation
Expand file tree
/
Copy pathamber-console.css
More file actions
5050 lines (4377 loc) · 206 KB
/
Copy pathamber-console.css
File metadata and controls
5050 lines (4377 loc) · 206 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
/*! Amber Console 2.1.0 | BSD-3-Clause | https://github.com/DutchDiederik/AmberConsole
* Generated by scripts/build.mjs — edit files in src/, not this one.
* Fonts in ../fonts/ are SIL OFL 1.1; see fonts/OFL-*.txt.
*/
/* ===========================================================================
Amber Console — a monochrome amber-terminal CSS framework
BSD-3-Clause licensed; see LICENSE. Fonts in fonts/ are SIL OFL 1.1; see
fonts/OFL-*.txt.
https://github.com/DutchDiederik/AmberConsole
NO VERSION NUMBER LIVES IN src/. package.json is the single source of truth;
scripts/build.mjs stamps it into the dist/ banners. A version written here
would be inlined into the bundle beside the generated one and drift on the
first release that forgets it.
This file is an @import list and nothing else. Every rule lives in one of the
files below, so you can drop any single component you do not need.
To embed inside an existing app rather than owning the page, swap
base/reset.css for base/reset-scoped.css and wrap your markup in .ac-root.
To skip the vendored webfonts, swap tokens/fonts.css for tokens/fonts-cdn.css.
Either way, rebuild: `npm run build`.
=========================================================================== */
/* --- Tokens: the entire API. Override any of these at runtime. --- */
/* >>> src/amber-console.css -> tokens/fonts.css */
/* ===========================================================================
Amber Console — self-hosted webfonts
Two bitmap faces, both SIL Open Font License 1.1 (see fonts/OFL-*.txt):
VT323 everything at 18px and up -> --ac-font-terminal
Silkscreen 8-10px micro labels ONLY -> --ac-font-micro
These are era-correct SUBSTITUTES, not the original face. The source hardware
used a mask-ROM bitmap font with no digital release; VT323 is a digitization of
the DEC VT320 terminal ROM. To swap in a licensed face closer to the hardware,
replace --ac-font-terminal in tokens/typography.css and nothing else.
GENERATED by scripts/fetch-fonts.mjs — do not edit by hand.
Paths are relative and resolve without a server (file:// works):
from src/tokens/fonts.css -> ../fonts/
from dist/amber-console.css -> ../fonts/ (rewritten by scripts/build.mjs)
Prefer not to vendor the binaries? Swap this import for tokens/fonts-cdn.css
in amber-console.css.
=========================================================================== */
/* VT323 400 — vietnamese */
@font-face {
font-family: "VT323";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("../fonts/VT323-Regular-vietnamese.woff2") format("woff2");
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* VT323 400 — latin-ext */
@font-face {
font-family: "VT323";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("../fonts/VT323-Regular-latin-ext.woff2") format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* VT323 400 — latin */
@font-face {
font-family: "VT323";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("../fonts/VT323-Regular.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Silkscreen 400 — latin-ext */
@font-face {
font-family: "Silkscreen";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("../fonts/Silkscreen-Regular-latin-ext.woff2") format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Silkscreen 400 — latin */
@font-face {
font-family: "Silkscreen";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("../fonts/Silkscreen-Regular.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* >>> src/amber-console.css -> tokens/colors.css */
/* TECHNOLOGY, THEN EMITTER — the palettes.
*
* It takes TWO attributes on the root element to name a palette, because the
* same color word means different physics on different glass:
*
* data-ac-tech "plasma" (a gas gap emitting directly) or
* "crt" (a phosphor, which emits and then PERSISTS).
* data-ac-emitter which gas or phosphor within that technology.
*
* Every block below is selected by BOTH, so a mismatched pair selects nothing
* rather than silently rendering the wrong hardware.
*
* plasma / neon 24deg. The default. What .ac-bloom simulates.
* plasma / helium pale pink. plasma / argon pale lavender.
* plasma / krypton pale violet-white.
*
* THE SEVEN PHOSPHORS SPLIT IN TWO, AND THE SPLIT IS WHAT THE TUBE WAS FOR
* rather than what colour it is. It decides whether a palette is a reasonable
* default for an interface or a deliberate costume, so the demo board groups
* them the same way.
*
* GENERAL PURPOSE — built to be read, for hours, by a person.
* crt / p3 38deg amber. The classic terminal, and .ac-crt's pair.
* crt / p1 green, willemite. The early scope and terminal tube.
* crt / p4 television white. A BLEND of two emitters.
* crt / p31 green. The monitor and scope phosphor most people picture.
*
* SPECIALIZED — built for an instrument, where reading was not the job. Each
* is legible here, but each is a costume with a reason attached.
* crt / p7 radar. Blue flash over a yellow-green afterglow that
* holds for seconds so a sweep leaves a picture behind.
* crt / p39 long-persistence yellow-green. Same trade as P7 without
* the two coatings: no flicker at any refresh, paid in smear.
* crt / p11 photographic recording. Blue is where film emulsion is
* most sensitive; nobody read text off one on purpose.
*
* THE FOUR GENERAL TUBES ALL DECAY IN UNDER ~25ms, which is under the ~80ms the
* eye needs to read a decay as a decay rather than as a switch — so on those the
* optional effects module has nothing visible to add, and says so by disabling
* its own switch. Persistence you can SEE is the specialized group's whole
* point. See ENGINE_FLOOR_MS in ../amber-console.js.
*
* P1/P39 and P7/P11 share a ramp on purpose — same compound, different decay.
* Note that puts one of each pair on either side of the split: the difference
* between a terminal and an instrument here is time, not colour.
*
* WHAT EVERY BLOCK MUST DEFINE: the five --ac-emit-* stops, the three surfaces,
* --ac-on-fill, the four --ac-halo-* glow triples, and --ac-halo-scatter / --ac-halo-spread.
* Every other color token is an alias or is built from those, so adding a
* palette means adding one block and nothing else.
*
* A BLOCK MAY ALSO CARRY TIME, where the emitter has a documented decay:
* --ac-persist, --ac-flicker and --ac-decay-ease. These are consumed WITH A CAP
* by tokens/effects.css, not raw. The four gases declare none of them and fall
* back to its :root defaults.
*
* --ac-persist-fast IS THE ODD ONE OUT: it is DOCUMENTATION, not an input. A
* two-layer phosphor declares it to record how fast its FIRST coating decays,
* and nothing reads it — no rule, no keyframe, no script. It exists because
* keyframe offsets cannot read a custom property, so ac-ghost-cascade in
* sim/afterglow.css has to hardcode the crossover as a percentage, and without
* this token there would be no machine-readable record of the figure that
* percentage was computed from. Do not wire it up expecting it to do something.
*
* EVERY PALETTE IS GATED. scripts/contrast.mjs runs the full pair table against
* each block independently — a ratio that passes under one emitter and fails
* under another is a failure. Do not add a stop to one block without the others.
*
* GENERATED. Nine of the eleven blocks are computed by scripts/derive-gas.mjs
* and must not be hand-edited; `npm test` re-derives them and fails on drift.
* Neon and P3 predate the script and stay hand-built.
*
* DEPRECATED, removed in 3.0: data-ac-gas="neon" | "amber". Migrate to
* data-ac-tech="plasma" data-ac-emitter="neon" / ="crt" ="p3".
*
* Why 24deg, why the phosphor bands are back-solved, why saturation is an
* output rather than a setting, and why P7's halo is not its ink:
* see RATIONALE.md § tokens/colors.css.
*/
:root,
[data-ac-tech="plasma"][data-ac-emitter="neon"],
[data-ac-gas="neon"] {
/* Surfaces — warm blacks (the unlit gas panel) */
--ac-screen: #100600; /* base panel */
--ac-screen-raised: #1b0c02; /* subtly lifted region */
--ac-screen-well: #060200; /* recessed input wells */
/* Discharge scale — ONE hue, five intensities. Never introduce a second hue
WITHIN a palette; these blocks are hardware, not decoration.
HAND-BUILT, and stays that way: this ramp was solved hue-first at maximum
saturation, which is not what derive-gas.mjs does, and neither of its gamut
strategies reproduces it. Each stop is solved to a contrast ratio against
--ac-screen (10.5 / 7.0 / 5.2 / 3.4) — rotating hue at constant lightness drops
--ac-emit-50 to 2.79:1 and silently fails the 3:1 non-text gate. Re-solve,
never re-tint; scripts/contrast.mjs is the check.
Why the hue is 24deg and why regenerating neon would be a mistake:
RATIONALE.md § Hue — why neon is 24deg. */
--ac-emit-100: #ffa86d; /* hot highlight / focus — 10.57:1 */
--ac-emit-90: #ff6b08; /* primary discharge — 7.02:1 */
--ac-emit-70: #dd5800; /* secondary — 5.22:1 */
--ac-emit-50: #ab4500; /* dim / disabled — 3.42:1 */
--ac-emit-30: #5b2500; /* trace / ghost — 1.63:1, decorative */
--ac-on-fill: #1e0c00;
/* THE GLOW SOURCE — four points on the discharge hue, as bare "r, g, b"
triples so tokens/effects.css can build every halo with its own alphas:
rgba(var(--ac-halo-1), 0.4). That indirection is why the palettes switch
cleanly; hardcoding a halo puts an amber corona around neon text.
--ac-halo-scatter is how much light reaches the halo, with NEON AT 1.00 BY
DEFINITION — every other palette's figure is stated against it — and
--ac-halo-spread is its square root. See RATIONALE.md § Scattering. */
--ac-halo-scatter: 1;
--ac-halo-spread: 1;
--ac-halo-1: 255, 120, 30; /* core halo */
--ac-halo-2: 255, 109, 12; /* mid */
--ac-halo-3: 255, 102, 0; /* deep / outermost */
--ac-halo-4: 255, 140, 64; /* pale — hot cores and the retrace band */
}
/* --- CRT / P3 — the amber phosphor, and the source design system's ramp. Same
structure as the block above, same contrast gate. NOT a gas: this is a
phosphor on a tube, which is why it is namespaced under a different
technology and not sitting beside neon. --- */
[data-ac-tech="crt"][data-ac-emitter="p3"],
[data-ac-gas="amber"] {
--ac-screen: #0d0700;
--ac-screen-raised: #170e02;
--ac-screen-well: #060200;
--ac-emit-100: #ffd052; /* hot highlight / focus — 13.74:1 */
--ac-emit-90: #ffae1e; /* primary discharge — 10.81:1 */
--ac-emit-70: #cd8817; /* secondary — 6.81:1 */
--ac-emit-50: #8d5b10; /* dim / disabled — 3.47:1 */
--ac-emit-30: #4a2f08; /* trace / ghost — 1.62:1, decorative */
--ac-on-fill: #1a0e00;
--ac-halo-1: 255, 174, 30;
--ac-halo-2: 255, 150, 12;
--ac-halo-3: 255, 120, 0;
--ac-halo-4: 255, 186, 64;
/* Derived from the band model, replacing a placeholder held at neon's 1.00
that was wrong by 23%. The RAMP above stays hand-built (emit: false in
scripts/data/emitters.json); only the halo width was ever a placeholder.
See RATIONALE.md § Scattering. */
--ac-halo-scatter: 1.23;
--ac-halo-spread: 1.11;
/* Persistence: 25ms to 10% (medium). 22% still lit at the next frame.
--ac-flicker is the same quantity read the other way; the WCAG amplitude
budget that consumes it lives in tokens/effects.css, in one place. */
--ac-persist: 25ms;
--ac-flicker: 0.785;
/* The decay curve, sampled from the exponential — a shape no single
cubic-bezier expresses, which is why it is a linear(). */
--ac-decay-ease: linear(0, 0.013 0.5%, 0.023 0.9%, 0.041 1.6%, 0.072 2.9%, 0.127 5.3%, 0.218 9.5%, 0.362 17%, 0.564 31%, 0.802 56%, 1.000 100%);
}
/* --- PLASMA / HELIUM — pale pink / salmon-white.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The line data, its source
and the reason this gas uses the line selection it does are in
scripts/data/emitters.json.
CIE 1931 2-deg integral over all 15 listed lines: x=0.3944 y=0.2986.
Inside sRGB as measured; no gamut mapping needed.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.40:1 on --ac-emit-90. --- */
[data-ac-tech="plasma"][data-ac-emitter="helium"] {
--ac-screen: #0e0809;
--ac-screen-raised: #1b1214;
--ac-screen-well: #060404;
--ac-emit-100: #ffa2b9;
--ac-emit-90: #dc7d96;
--ac-emit-70: #bc6a80;
--ac-emit-50: #925163;
--ac-emit-30: #502a34;
--ac-on-fill: #201216;
--ac-halo-1: 255, 158, 183;
--ac-halo-2: 255, 153, 180;
--ac-halo-3: 255, 150, 178;
--ac-halo-4: 255, 170, 191;
/* Rayleigh: this gas throws 26% more light into the halo than neon. */
--ac-halo-scatter: 1.26;
--ac-halo-spread: 1.12;
}
/* --- PLASMA / ARGON — pale violet-lavender.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The line data, its source
and the reason this gas uses the line selection it does are in
scripts/data/emitters.json.
CIE 1931 2-deg integral over 43 of 102 listed lines: x=0.2163 y=0.1047.
Outside sRGB at the luminance --ac-emit-90 needs, so it is
desaturated 35% toward D65 — the gas is paler here than it is in the tube.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.37:1 on --ac-emit-90. --- */
[data-ac-tech="plasma"][data-ac-emitter="argon"] {
--ac-screen: #0b0812;
--ac-screen-raised: #171222;
--ac-screen-well: #050309;
--ac-emit-100: #c9b0ff;
--ac-emit-90: #af81ff;
--ac-emit-70: #9f5dff;
--ac-emit-50: #8925f1;
--ac-emit-30: #4b1088;
--ac-on-fill: #1b112c;
--ac-halo-1: 186, 149, 255;
--ac-halo-2: 182, 143, 255;
--ac-halo-3: 180, 138, 255;
--ac-halo-4: 194, 164, 255;
/* Rayleigh: this gas throws 49% more light into the halo than neon. */
--ac-halo-scatter: 1.49;
--ac-halo-spread: 1.22;
}
/* --- PLASMA / KRYPTON — pale violet-white.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The line data, its source
and the reason this gas uses the line selection it does are in
scripts/data/emitters.json.
CIE 1931 2-deg integral over all 95 listed lines: x=0.3153 y=0.2551.
Inside sRGB as measured; no gamut mapping needed.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.35:1 on --ac-emit-90. --- */
[data-ac-tech="plasma"][data-ac-emitter="krypton"] {
--ac-screen: #0b080b;
--ac-screen-raised: #181318;
--ac-screen-well: #050405;
--ac-emit-100: #eaa5eb;
--ac-emit-90: #bf86c0;
--ac-emit-70: #a372a3;
--ac-emit-50: #7f587f;
--ac-emit-30: #442e45;
--ac-on-fill: #1b141b;
--ac-halo-1: 251, 185, 252;
--ac-halo-2: 241, 173, 242;
--ac-halo-3: 229, 162, 230;
--ac-halo-4: 254, 201, 255;
/* Rayleigh: this gas throws 31% more light into the halo than neon. */
--ac-halo-scatter: 1.31;
--ac-halo-spread: 1.14;
}
/* --- CRT / P1 — willemite green.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The band data, its source
and the reason this phosphor uses the band shape it does are in
scripts/data/emitters.json.
Band 529nm FWHM 48nm: x=0.2154 y=0.7110.
Outside sRGB at the luminance --ac-emit-90 needs, so it is
desaturated 57% toward D65 — the phosphor is paler here than it is in the tube.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.39:1 on --ac-emit-90. --- */
[data-ac-tech="crt"][data-ac-emitter="p1"] {
--ac-screen: #000c03;
--ac-screen-raised: #001908;
--ac-screen-well: #000601;
--ac-emit-100: #00d871;
--ac-emit-90: #00b05b;
--ac-emit-70: #00964d;
--ac-emit-50: #00753a;
--ac-emit-30: #003f1c;
--ac-on-fill: #001a08;
--ac-halo-1: 0, 237, 125;
--ac-halo-2: 0, 225, 118;
--ac-halo-3: 0, 212, 111;
--ac-halo-4: 46, 255, 140;
/* Rayleigh: this phosphor throws 63% more light into the halo than neon. */
--ac-halo-scatter: 1.63;
--ac-halo-spread: 1.28;
/* Persistence: 24ms to 10% (medium).
20% still lit when the next frame arrives.
--ac-flicker is the inter-frame modulation depth these two share; the
amplitude budget that keeps it under the WCAG flash threshold is in
tokens/effects.css, in one place, on purpose. */
--ac-persist: 24ms;
--ac-flicker: 0.798;
/* The decay curve itself, sampled from the exponential.
A shape no single cubic-bezier expresses, which is why it is a linear(). */
--ac-decay-ease: linear(0, 0.013 0.5%, 0.023 0.9%, 0.041 1.6%, 0.072 2.9%, 0.127 5.3%, 0.218 9.5%, 0.362 17%, 0.564 31%, 0.802 56%, 1.000 100%);
}
/* --- CRT / P4 — white.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The band data, its source
and the reason this phosphor uses the band shape it does are in
scripts/data/emitters.json.
2 bands 466nm FWHM 76nm + 572nm FWHM 130nm x0.5: x=0.2701 y=0.3000.
Inside sRGB as measured; no gamut mapping needed.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.37:1 on --ac-emit-90. --- */
[data-ac-tech="crt"][data-ac-emitter="p4"] {
--ac-screen: #08090b;
--ac-screen-raised: #121516;
--ac-screen-well: #040405;
--ac-emit-100: #9dc1d8;
--ac-emit-90: #7f9db0;
--ac-emit-70: #6c8696;
--ac-emit-50: #536874;
--ac-emit-30: #2b373e;
--ac-on-fill: #121618;
--ac-halo-1: 176, 211, 234;
--ac-halo-2: 165, 201, 223;
--ac-halo-3: 153, 189, 211;
--ac-halo-4: 197, 228, 248;
/* Rayleigh: this phosphor throws 55% more light into the halo than neon. */
--ac-halo-scatter: 1.55;
--ac-halo-spread: 1.24;
/* Persistence: 0.06ms to 10% (medium-short).
Fully dark between frames — flicker at maximum, no persistence to see.
--ac-flicker is the inter-frame modulation depth these two share; the
amplitude budget that keeps it under the WCAG flash threshold is in
tokens/effects.css, in one place, on purpose. */
--ac-persist: 0.06ms;
--ac-flicker: 1.000;
}
/* --- CRT / P7 — blue flash over a yellow-green afterglow.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The band data, its source
and the reason this phosphor uses the band shape it does are in
scripts/data/emitters.json.
Band 466nm FWHM 76nm: x=0.1384 y=0.1502.
Outside sRGB at the luminance --ac-emit-90 needs, so it is
desaturated 29% toward D65 — the phosphor is paler here than it is in the tube.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.35:1 on --ac-emit-90.
TWO EMITTERS, IN SEQUENCE. --ac-emit-* is the ZnS:Ag FLASH (x=0.1384 y=0.1502),
which is what the beam writes. --ac-halo-* is the (Zn,Cd)S:Cu AFTERGLOW
(x=0.3553 y=0.5373), which is what the screen holds after it. The halo
therefore does NOT track the ink here, which inverts the rule stated at the
top of tokens/effects.css and is the reason P7 exists as a part number. --- */
[data-ac-tech="crt"][data-ac-emitter="p7"] {
--ac-screen: #040a11;
--ac-screen-raised: #0b1520;
--ac-screen-well: #020409;
--ac-emit-100: #8bc0ff;
--ac-emit-90: #00a0fb;
--ac-emit-70: #0088d7;
--ac-emit-50: #0069a8;
--ac-emit-30: #00385d;
--ac-on-fill: #03182a;
--ac-halo-1: 156, 226, 72;
--ac-halo-2: 145, 215, 54;
--ac-halo-3: 135, 203, 40;
--ac-halo-4: 176, 242, 107;
/* Rayleigh: this phosphor throws 47% more light into the halo than neon. */
--ac-halo-scatter: 1.47;
--ac-halo-spread: 1.21;
/* THE FLASH, as a halo triple. --ac-halo-* above is the AFTERGLOW, which is what
this screen holds; this is the layer the beam actually writes, and the ghost
keyframe starts here before crossing to the afterglow within one frame.
--ac-ghost-anim is how this palette tells tokens/effects.css it decays
through two hues rather than one, without effects.css naming it. */
--ac-halo-flash: 175, 209, 255;
--ac-ghost-anim: ac-ghost-cascade;
/* Persistence: 400ms to 10%, visible tail to 3000ms (long).
91% still lit when the next frame arrives.
--ac-flicker is the inter-frame modulation depth these two share; the
amplitude budget that keeps it under the WCAG flash threshold is in
tokens/effects.css, in one place, on purpose. */
--ac-persist: 3000ms;
--ac-flicker: 0.091;
--ac-persist-fast: 0.04ms;
/* Long enough that the next ON edge lands before this one has
drained: the alarm throbs rather than chops. */
--ac-blink-anim: ac-blink-decay-long;
/* The decay curve itself, sampled from the Becquerel power law I0/(1+t/t0)^1.4.
A shape no single cubic-bezier expresses, which is why it is a linear(). */
--ac-decay-ease: linear(0, 0.186 0.5%, 0.296 0.9%, 0.441 1.6%, 0.602 2.9%, 0.750 5.3%, 0.862 9.5%, 0.932 17%, 0.971 31%, 0.991 56%, 1.000 100%);
}
/* --- CRT / P11 — blue.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The band data, its source
and the reason this phosphor uses the band shape it does are in
scripts/data/emitters.json.
Band 466nm FWHM 76nm: x=0.1384 y=0.1502.
Outside sRGB at the luminance --ac-emit-90 needs, so it is
desaturated 29% toward D65 — the phosphor is paler here than it is in the tube.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.35:1 on --ac-emit-90. --- */
[data-ac-tech="crt"][data-ac-emitter="p11"] {
--ac-screen: #040a11;
--ac-screen-raised: #0b1520;
--ac-screen-well: #020409;
--ac-emit-100: #8bc0ff;
--ac-emit-90: #00a0fb;
--ac-emit-70: #0088d7;
--ac-emit-50: #0069a8;
--ac-emit-30: #00385d;
--ac-on-fill: #03182a;
--ac-halo-1: 87, 174, 255;
--ac-halo-2: 70, 170, 255;
--ac-halo-3: 56, 167, 255;
--ac-halo-4: 119, 184, 255;
/* Rayleigh: this phosphor throws 104% more light into the halo than neon. */
--ac-halo-scatter: 2.04;
--ac-halo-spread: 1.43;
/* Persistence: 0.035ms to 10% (medium-short).
Fully dark between frames — flicker at maximum, no persistence to see.
--ac-flicker is the inter-frame modulation depth these two share; the
amplitude budget that keeps it under the WCAG flash threshold is in
tokens/effects.css, in one place, on purpose. */
--ac-persist: 0.035ms;
--ac-flicker: 1.000;
}
/* --- CRT / P31 — green.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The band data, its source
and the reason this phosphor uses the band shape it does are in
scripts/data/emitters.json.
Band 513nm FWHM 78nm: x=0.2079 y=0.5295.
Outside sRGB at the luminance --ac-emit-90 needs, so it is
desaturated 45% toward D65 — the phosphor is paler here than it is in the tube.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.36:1 on --ac-emit-90. --- */
[data-ac-tech="crt"][data-ac-emitter="p31"] {
--ac-screen: #010c06;
--ac-screen-raised: #03180e;
--ac-screen-well: #010503;
--ac-emit-100: #00d791;
--ac-emit-90: #00af76;
--ac-emit-70: #009564;
--ac-emit-50: #00744c;
--ac-emit-30: #003e27;
--ac-on-fill: #001b0e;
--ac-halo-1: 0, 235, 160;
--ac-halo-2: 0, 223, 151;
--ac-halo-3: 0, 210, 142;
--ac-halo-4: 0, 254, 173;
/* Rayleigh: this phosphor throws 67% more light into the halo than neon. */
--ac-halo-scatter: 1.67;
--ac-halo-spread: 1.29;
/* Persistence: 0.038ms to 10% (medium-short).
Fully dark between frames — flicker at maximum, no persistence to see.
--ac-flicker is the inter-frame modulation depth these two share; the
amplitude budget that keeps it under the WCAG flash threshold is in
tokens/effects.css, in one place, on purpose. */
--ac-persist: 0.038ms;
--ac-flicker: 1.000;
}
/* --- CRT / P39 — yellow-green, long persistence.
GENERATED by scripts/derive-gas.mjs. Do not edit these values by hand;
`npm test` re-derives and fails on drift. The band data, its source
and the reason this phosphor uses the band shape it does are in
scripts/data/emitters.json.
Band 529nm FWHM 52nm: x=0.2248 y=0.6962.
Outside sRGB at the luminance --ac-emit-90 needs, so it is
desaturated 54% toward D65 — the phosphor is paler here than it is in the tube.
Stops solved to 10.5 / 7.0 / 5.2 / 3.4 / 1.63 : 1 against this palette's
own --ac-screen; --ac-on-fill lands at 6.39:1 on --ac-emit-90. --- */
[data-ac-tech="crt"][data-ac-emitter="p39"] {
--ac-screen: #000c03;
--ac-screen-raised: #001908;
--ac-screen-well: #000601;
--ac-emit-100: #00d86e;
--ac-emit-90: #00b059;
--ac-emit-70: #00964b;
--ac-emit-50: #007538;
--ac-emit-30: #003f1b;
--ac-on-fill: #001a07;
--ac-halo-1: 0, 237, 121;
--ac-halo-2: 0, 225, 115;
--ac-halo-3: 0, 212, 108;
--ac-halo-4: 49, 255, 137;
/* Rayleigh: this phosphor throws 62% more light into the halo than neon. */
--ac-halo-scatter: 1.62;
--ac-halo-spread: 1.27;
/* Persistence: 400ms to 10%, visible tail to 2000ms (long).
91% still lit when the next frame arrives.
--ac-flicker is the inter-frame modulation depth these two share; the
amplitude budget that keeps it under the WCAG flash threshold is in
tokens/effects.css, in one place, on purpose. */
--ac-persist: 2000ms;
--ac-flicker: 0.091;
/* Long enough that the next ON edge lands before this one has
drained: the alarm throbs rather than chops. */
--ac-blink-anim: ac-blink-decay-long;
/* The decay curve itself, sampled from the Becquerel power law I0/(1+t/t0)^1.6.
A shape no single cubic-bezier expresses, which is why it is a linear(). */
--ac-decay-ease: linear(0, 0.118 0.5%, 0.197 0.9%, 0.313 1.6%, 0.465 2.9%, 0.632 5.3%, 0.781 9.5%, 0.889 17%, 0.953 31%, 0.985 56%, 1.000 100%);
}
/* Aliases and structural tokens — defined once per PALETTE SCOPE, for every
palette. Anything that is the same shape regardless of which gas is in the
panel belongs here; anything that is a color belongs in a palette block above.
THE SELECTOR LIST IS NOT DECORATION, AND `:root` ALONE WAS A FOOTGUN. These
are indirections — `--ac-ink: var(--ac-emit-90)` — and a var() is substituted
AT THE ELEMENT THAT DECLARES IT, then inherits as a resolved color. Declared
only on :root, they resolve once against whatever palette the ROOT carries and
every descendant inherits that answer.
So a consumer following the embed guidance in base/reset-scoped.css, who puts
data-ac-tech / data-ac-emitter on .ac-root rather than on <html>, changed the
five --ac-emit-* stops on that subtree and nothing else: --ac-ink, --ac-fill
and --ac-stroke stayed resolved from :root's neon. Half a palette, silently —
the surfaces moved and the text did not.
.ac-root IS THE WHOLE FIX, AND THE ATTRIBUTE SELECTORS ARE DELIBERATELY NOT
HERE. `[data-ac-tech][data-ac-emitter]` would cover every element that can
carry a palette — and would weigh (0,2,0), which beats a consumer's own
`:root { --ac-ink: … }` at (0,1,0) and silently breaks the one override the
REFERENCE chapter tells people they may write. .ac-root weighs the same
(0,1,0) as :root and wins for its subtree by PROXIMITY rather than by weight,
which is exactly the behaviour wanted: closer scope, same authority.
base/print.css carries .ac-root in its own re-point list for the same reason —
drop it there and a scoped embed prints in amber. */
:root,
.ac-root {
--ac-ink: var(--ac-emit-90);
--ac-ink-bright: var(--ac-emit-100);
--ac-ink-dim: var(--ac-emit-70);
--ac-ink-faint: var(--ac-emit-50);
--ac-ink-trace: var(--ac-emit-30);
/* Inverse video (solid discharge block, dark text) */
--ac-fill: var(--ac-emit-90);
--ac-fill-bright: var(--ac-emit-100);
--ac-stroke: var(--ac-emit-90);
--ac-stroke-dim: var(--ac-emit-50);
/* No red, no green. Alarm/danger = inverse video + blink, never a new hue. */
}
/* >>> src/amber-console.css -> tokens/typography.css */
:root {
--ac-font-terminal: "VT323", "Courier New", ui-monospace, monospace;
--ac-font-micro: "Silkscreen", "VT323", ui-monospace, monospace;
/* VT323 is a bitmap face: it needs LARGE sizes. Never below 18px — below that
the grid collapses and it turns to mud. --ac-type-micro is Silkscreen's,
never VT323's; components/badge.css states the same rule from the other end.
TWO DELIBERATE EXCEPTIONS, AND BOTH ARE GLYPHS RATHER THAN TEXT.
components/select.css sets the dropdown chevron at 14px and
components/nav.css sets the mark block at 16px, both in the terminal face.
The floor exists because a bitmap face loses its letterforms when it is
resampled small, and neither of these HAS a letterform to lose: they are
solid geometry, one triangle and one filled square, and they read the same
at any size. Nothing anybody has to READ goes below 18px. Add to that list
only for another single non-alphanumeric glyph, and say so at the site. */
--ac-type-display: 44px; /* page titles, banners */
--ac-type-title: 30px; /* panel/soft-key headers */
--ac-type-body: 22px; /* default UI text */
--ac-type-small: 18px; /* field labels, legends */
--ac-type-micro: 8px; /* Silkscreen only — bit labels, badges */
--ac-tracking-display: 0.1em;
--ac-tracking-body: 0.04em;
--ac-tracking-micro: 0.08em;
--ac-leading: 1.15; /* terminal rows sit tight */
}
/* >>> src/amber-console.css -> tokens/spacing.css */
:root {
/* Character-cell grid: everything snaps to 4px half-cells */
--ac-space-1: 4px;
--ac-space-2: 8px;
--ac-space-3: 12px;
--ac-space-4: 16px;
--ac-space-6: 24px;
--ac-space-8: 32px;
--ac-space-12: 48px;
--ac-border-w: 2px; /* every stroke is 2px — no hairlines */
/* THE CORNER, QUANTIZED TO THE GRID. 2px is what a gas-discharge panel
addressing a coarse cell matrix could actually draw, and it is the figure
.ac-badge and .ac-banner have always used literally.
This is the BASE value, not the whole story: components/classic.css spends
it on a real chamfer where `corner-shape` exists, adds the extruded key
edge, and provides the .ac-rounded opt-out. Those live there so this file
stays what it says it is — a spacing scale.
It is stated here rather than only there so that dropping classic.css
degrades to a plain 2px corner instead of leaving --ac-radius undefined,
which would make `border-radius: var(--ac-radius)` invalid and square off
every control in the framework. */
--ac-radius: 2px;
--ac-radius-sm: 2px;
}
/* >>> src/amber-console.css -> tokens/effects.css */
:root {
/* NOTHING IN THIS FILE NAMES A COLOR. Every rgba() is built from the --ac-halo-N
triples in tokens/colors.css, which is what makes the display switch the
panel and its glow together. Hardcode one and it keeps an amber corona
around neon text. (On P7 the halo is a DIFFERENT spectrum from the ink, and
that is correct rather than a bug — RATIONALE.md § P7.)
The wide layers scale by --ac-halo-scatter / --ac-halo-spread, per palette, both
falling back to 1. THE 2px LAYER IS NOT SCALED: it is the glyph's own lit
edge, not scattered light. RADIUS NEVER SCALES WITH DRIVE — only alpha.
THE DRIVE TIERS ARE ARITHMETIC, NOT TASTE. Each stop's luminance is already
fixed by the contrast ratio it was solved to, so the ratios fall out:
--ac-ink, --ac-stroke emit-90 7.00:1 drive 1.00
--ac-ink-dim emit-70 5.20:1 drive 0.70
--ac-ink-faint, -dim emit-50 3.40:1 drive 0.41
--ac-ink-trace emit-30 1.63:1 drive 0.11 -> none
THE TOKEN FAMILIES DO NOT LINE UP and the names actively mislead:
--ac-ink-dim is emit-70 but --ac-stroke-dim is emit-50. The tier is chosen by
DRIVE LEVEL, never by whether the token has "dim" in it.
EACH TIER IS ITS OWN FULLY-RESOLVED TOKEN rather than one --ac-drive
multiplier, and that is not a stylistic choice — the multiplier version
fails SILENTLY. Before rebuilding it that way, read RATIONALE.md § Why the
tiers are three tokens and not one multiplier. The derivation behind the
table, and why the box tier is separate, are in the same section. */
--ac-drive-dim: 0.7;
--ac-drive-faint: 0.41;
--ac-glow-text:
0 0 2px rgba(var(--ac-halo-1), 0.6),
0 0 calc(9px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-2), calc(0.26 * var(--ac-halo-scatter, 1))),
0 0 calc(24px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-3), calc(0.1 * var(--ac-halo-scatter, 1)));
--ac-glow-text-dim:
0 0 2px rgba(var(--ac-halo-1), calc(0.6 * var(--ac-drive-dim))),
0 0 calc(9px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-2), calc(0.26 * var(--ac-halo-scatter, 1) * var(--ac-drive-dim))),
0 0 calc(24px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-3), calc(0.1 * var(--ac-halo-scatter, 1) * var(--ac-drive-dim)));
--ac-glow-text-faint:
0 0 2px rgba(var(--ac-halo-1), calc(0.6 * var(--ac-drive-faint))),
0 0 calc(9px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-2), calc(0.26 * var(--ac-halo-scatter, 1) * var(--ac-drive-faint))),
0 0 calc(24px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-3), calc(0.1 * var(--ac-halo-scatter, 1) * var(--ac-drive-faint)));
--ac-glow-box:
0 0 calc(8px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-1), calc(0.32 * var(--ac-halo-scatter, 1))),
inset 0 0 10px rgba(var(--ac-halo-1), 0.07);
--ac-glow-box-dim:
0 0 calc(8px * var(--ac-halo-spread, 1)) rgba(var(--ac-halo-1), calc(0.32 * var(--ac-halo-scatter, 1) * var(--ac-drive-faint))),
inset 0 0 10px rgba(var(--ac-halo-1), calc(0.07 * var(--ac-drive-faint)));
/* THE EXTRUDED KEY EDGE — the one shadow here that is NOT a halo, so it takes
neither --ac-halo-spread nor --ac-halo-scatter: those describe light scattering
through glass, and this is geometry. It stays the same size when the palette
changes, while its color still follows --ac-halo-1.
TWO LAYERS BECAUSE IT IS AN EDGE, NOT A GHOST: a bright 2px lip against the
stroke, then 3px more at lower drive falling away from it. The alphas are
set against the bloom rather than the screen — see RATIONALE.md § The
extruded key edge.
Consumed by components/classic.css and nothing else. Override to blunt or
kill the extrusion wholesale; print does exactly that. */
--ac-edge-3d:
2px 2px 0 rgba(var(--ac-halo-1), 0.92),
5px 5px 0 rgba(var(--ac-halo-1), 0.6);
/* A KEY PRESSED IN, AND IT IS A RECESS RATHER THAN A SECOND BAND.
This used to mirror --ac-edge-3d: the same bright discharge-colored band,
moved to the top-left. Geometrically that was the sunken key's near wall and
it was self-consistent — and it read wrong in both directions:
Same-hue palettes a pressed key is INVERSE VIDEO, so its fill is already
the discharge color. A band in that same color beside
that same fill is invisible: on neon a latched .ac-btn
--filled showed no 3D at all, which is the "some
buttons have no shadow" report.
P7 halo and ink are different spectra there, so the band
did show — as a green outline around a blue block.
That reads as a highlight, not a recess: "inverted
instead of depressed".
A bright band cannot mean two opposite things. Raised is the only state that
gets one, so the two can never be confused: PROUD = a lit face outside the
box, PRESSED = a shadow inside it.
INSET, so it paints over the fill rather than beside it, and the fill is
always bright when this token is used — :active, --filled and aria-pressed
all set inverse video. Positive offsets on an inset shadow paint the TOP and
LEFT inner edges, which is where a recess shades.
BLACK ALPHA RATHER THAN A PALETTE COLOR, on the same reasoning the cell mesh
uses: over a lit fill it darkens, and over a dark surface it has nothing to
take away. That makes it correct on all eleven palettes without naming one.
A SHADOW RATHER THAN A TRANSFORM, and that is still load-bearing:
`translate` DOES contribute to scrollable overflow, so every latched key
flush against its container pushed 4px past it.
See RATIONALE.md § The extruded key edge. */
--ac-edge-3d-in:
inset 2px 2px 0 rgba(0, 0, 0, 0.34),
inset 5px 5px 0 rgba(0, 0, 0, 0.18);
/* PERSISTENCE IS A PROPERTY OF THE EMITTER, not of this file. A palette that
knows its own decay declares --ac-persist, --ac-flicker and a sampled
--ac-decay-ease in tokens/colors.css; the tokens below are derived from it.
This block is the fallback for every palette that declares nothing — all
four gases — so they render unchanged at 105ms / 60ms.
THE CAP IS THE WHOLE POINT OF min(), and it is a usability decision made
against the physics on purpose: P7's tail is 3000ms, and a dialog painted
for three seconds after it closes is a bug that can cite a source.
--ac-decay things that must actually LEAVE — [hidden], a closing
dialog. Capped at 250ms.
--ac-decay-fast de-energizing controls and hover release. Capped at
60ms — this fires on every control the pointer crosses.
--ac-persist-tail ghosts and residual patches. NOT capped; pure
decoration, and where the long phosphors earn their keep.
See RATIONALE.md § The persistence caps. */
--ac-persist: 105ms;
--ac-decay: min(var(--ac-persist), 250ms);
--ac-decay-fast: min(var(--ac-persist), 60ms);
--ac-persist-tail: var(--ac-persist);
--ac-decay-ease: cubic-bezier(0.1, 0.72, 0.22, 1);
/* HOW MUCH OF THE IMAGE IS GONE BY THE NEXT REFRESH, 0..1 — the other half of
--ac-persist and not an independent knob. A palette that declares one
declares both, from one decay constant, in scripts/derive-gas.mjs.
1 is the default and means "no palette said", which resolves the flicker
amplitude below to exactly the value it had before persistence existed. */
--ac-flicker: 1;
/* The cell matrix. A plasma panel is not a lit surface, it is a grid of gas
gaps at wire intersections, and the barrier ribs between them do not emit.
--ac-mesh-pitch is the cell spacing; --ac-mesh-wire is the per-axis alpha of
one rib.
THE ALPHA IS A BUDGET, NOT A TASTE SETTING. 0.075 lands the mean loss at
0.050 — exactly what the CRT scanlines beside it cost, so turning plasma on
costs the same legibility the tube already does. scripts/contrast.mjs cannot
see an overlay, so nothing will fail if this is raised: redo the arithmetic
first, in RATIONALE.md § The cell matrix alpha is a budget. */
--ac-mesh-pitch: 3px;
--ac-mesh-wire: 0.075;
/* Blink, as an inherited pair rather than a selector list. base/blink.css
holds the keyframe, the sites that read these, and the full contract. */
--ac-blink-name: ac-blink;
--ac-blink-ease: steps(1);
}
/* ---------------------------------------------------------------------------
THE HALO IS INHERITED, NOT REQUESTED.
The glow is light scattered in the glass, and the glass has no idea which
component a lit cell belongs to. So it is applied ONCE here and inherits to
every descendant, rather than being opted into per component.
That direction matters more than it looks: when glow was opt-in, thirty-four
elements were still rendering flat because nobody remembered to ask.
Inheriting inverts the failure — a new component glows because it is lit.
See RATIONALE.md § The halo is inherited, not requested.
WHAT MUST THEN SUPPRESS IT, and why it is a short list rather than a long one:
--ac-ink-dim / --ac-ink-faint glow is the signal of energization, so a disabled
control that glows is a lie about the hardware.
inverse video dark text on a lit block is the UNLIT part of the
block. Unlit cells do not scatter.
Both are enumerable, and scripts/check-prohibitions.mjs enforces the first —
a rule that sets dim or faint ink without switching the halo off fails the
build, because that is the half of this that drifts.
SUPPRESSION IS ALWAYS `text-shadow: none`, NEVER A TOKEN OVERRIDE.
var(--ac-glow-text) is resolved HERE, at the frame, and the resolved shadow list
is what inherits — a descendant setting its own --ac-glow-text changes nothing.
Print and forced colors need no rule of their own; both already blank it
upstream. --------------------------------------------------------------- */
.ac-screen,
.ac-root {
text-shadow: var(--ac-glow-text);
}
/* Blink is law-2 emphasis rather than a simulation — every display technology
here blinks — and components/ depends on it, so it is base and not sim. */
/* >>> src/amber-console.css -> base/blink.css */
/* ===========================================================================
Blink — law-2 emphasis, and the one animation that is NOT a simulation.
Every display technology in this framework blinks, so switching it off says
nothing about which hardware is being simulated. That is why it is base/ and
not sim/: components/input.css and components/meter.css are blink sites and
would lose their alarm if a consumer dropped the simulations.
The decay variants of these keyframes live with the persistence engine, in
sim/afterglow.css, because that is what softens the OFF edge.
=========================================================================== */
/* BLINK IS DRIVEN BY TWO INHERITED TOKENS, NOT BY A SELECTOR LIST.
---------------------------------------------------------------------------
Every blink site reads the same pair, and every environment that wants to
change blink sets the pair ONCE instead of restating where the blink sites
are. Custom properties inherit, so a declaration on the root or on the frame
reaches all of them without a specificity fight — which is what the four
mirrored copies of this list used to be paying for.
--ac-blink-name which keyframe, or `none` to stop blinking
--ac-blink-ease its timing function
--ac-blink-force the same two, but reserved for the ROOT. Read FIRST at
--ac-blink-force-ease every site, and never declared on .ac-afterglow, so
an environment override cannot lose to the simulation
simply for being further from the element. That inversion
is exactly what the doubled `[data-ac-style-blink="off"]
.ac-afterglow ...` selectors existed to defeat.
ADDING A BLINK SITE is now one declaration pair on the site itself. Nothing
else in this file, and nothing in print.css or a11y.css, has to learn it.
The two defaults are declared in tokens/effects.css, NOT here. This file is the
contract and the keyframes; the token block that seeds them sits with the other
tokens, because a consumer overriding blink overrides it there. */
/* Hard on/off blink — no fades on a plasma panel */
@keyframes ac-blink { 0%, 54% { opacity: 1; } 55%, 100% { opacity: 0.1; } }
.ac-blink {
animation-name: var(--ac-blink-force, var(--ac-blink-name));
animation-duration: 1.1s;
animation-timing-function: var(--ac-blink-force-ease, var(--ac-blink-ease));
animation-iteration-count: infinite;
}
/* Block cursor — same pair, its own 1s cycle. */
.ac-cursor::after {
content: "█";
margin-left: 2px;
animation-name: var(--ac-blink-force, var(--ac-blink-name));
animation-duration: 1s;
animation-timing-function: var(--ac-blink-force-ease, var(--ac-blink-ease));
animation-iteration-count: infinite;
}