File size: 114,550 Bytes
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32021fd
2aa8b3a
 
 
 
 
 
 
26a7c18
 
2aa8b3a
 
0dbbc41
 
2aa8b3a
 
 
 
 
 
 
 
0dbbc41
2aa8b3a
0dbbc41
2aa8b3a
 
 
 
 
0dbbc41
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32021fd
 
 
 
 
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bb9cc0
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bb9cc0
 
 
 
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bb9cc0
2aa8b3a
 
 
9bb9cc0
 
1d4a48e
 
9bb9cc0
 
1d4a48e
 
32021fd
9bb9cc0
 
 
 
32021fd
9bb9cc0
 
 
 
1d4a48e
 
 
 
 
 
 
 
9bb9cc0
1d4a48e
 
 
 
 
32021fd
 
 
 
 
9bb9cc0
 
eab2534
fabf319
eab2534
 
 
 
 
 
 
 
fabf319
eab2534
 
 
 
 
 
 
 
 
 
fabf319
32021fd
 
 
 
 
 
 
 
 
fabf319
 
 
9bb9cc0
3c241d8
 
 
 
 
 
 
 
 
 
 
 
 
 
9bb9cc0
32021fd
2aa8b3a
38cbc41
 
 
 
1d4a48e
 
3c241d8
2aa8b3a
1d4a48e
 
 
2aa8b3a
 
 
1d4a48e
9bb9cc0
 
 
 
 
 
 
 
fabf319
32021fd
fabf319
 
32021fd
9bb9cc0
1d4a48e
32021fd
 
 
9bb9cc0
fabf319
 
3d71a3b
 
 
 
 
 
 
fabf319
3d71a3b
32021fd
 
 
 
 
 
 
3d71a3b
9bb9cc0
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32021fd
2aa8b3a
0dbbc41
2aa8b3a
 
0dbbc41
 
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38cbc41
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38cbc41
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32021fd
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26a7c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2aa8b3a
 
 
 
 
 
 
 
 
 
38cbc41
 
 
 
 
 
 
 
 
 
 
 
2aa8b3a
 
 
26a7c18
 
 
 
 
 
 
2aa8b3a
 
26a7c18
 
 
 
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
0dbbc41
 
1d4a48e
0dbbc41
1d4a48e
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
0dbbc41
1d4a48e
 
 
 
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
0dbbc41
1d4a48e
 
0dbbc41
1d4a48e
 
 
 
 
0dbbc41
 
1d4a48e
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0dbbc41
 
2aa8b3a
0dbbc41
 
 
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38cbc41
 
 
 
 
 
 
1d4a48e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38cbc41
0dbbc41
1d4a48e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38cbc41
 
1d4a48e
 
 
 
 
 
 
 
 
0dbbc41
1d4a48e
 
0dbbc41
1d4a48e
 
 
 
0dbbc41
1d4a48e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38cbc41
 
32021fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38cbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4a48e
 
 
 
 
 
 
 
 
0dbbc41
 
 
 
 
38cbc41
0dbbc41
 
 
 
 
38cbc41
 
 
 
 
 
32021fd
38cbc41
 
 
 
 
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0dbbc41
 
 
 
 
 
 
2aa8b3a
 
 
0dbbc41
2aa8b3a
0dbbc41
 
 
 
2aa8b3a
0dbbc41
2aa8b3a
0dbbc41
 
 
 
2aa8b3a
0dbbc41
 
 
2aa8b3a
0dbbc41
 
 
 
2aa8b3a
0dbbc41
 
2aa8b3a
 
0dbbc41
 
 
2aa8b3a
0dbbc41
 
2aa8b3a
 
0dbbc41
 
 
2aa8b3a
0dbbc41
 
 
 
2aa8b3a
 
 
0dbbc41
 
2aa8b3a
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
2aa8b3a
 
0dbbc41
2aa8b3a
 
0dbbc41
 
 
 
 
2aa8b3a
 
0dbbc41
2aa8b3a
0dbbc41
 
 
2aa8b3a
0dbbc41
 
 
 
2aa8b3a
 
0dbbc41
 
 
 
 
 
 
 
2aa8b3a
 
 
0dbbc41
2aa8b3a
0dbbc41
 
 
2aa8b3a
 
0dbbc41
 
 
 
 
 
2aa8b3a
 
0dbbc41
 
 
 
2aa8b3a
 
 
0dbbc41
 
2aa8b3a
0dbbc41
 
 
 
2aa8b3a
 
 
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32021fd
 
 
0dbbc41
 
 
 
32021fd
 
 
0dbbc41
 
 
 
 
32021fd
 
 
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
 
2aa8b3a
0dbbc41
 
2aa8b3a
 
 
 
 
0dbbc41
2aa8b3a
 
 
0dbbc41
2aa8b3a
 
0dbbc41
 
 
 
 
 
 
 
 
 
 
 
2aa8b3a
 
 
0dbbc41
2aa8b3a
 
0dbbc41
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9eba326
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
"""Server-rendered markup. Every HTML string in the product is built here.

Two reasons this module exists.

**Control.** Gradio's generated DOM is not a design system, and styling it is a
fight you lose slowly. Anything static or read-only is authored here as plain
HTML and handed to a ``gr.HTML``; real Gradio components are kept to the small
set that genuinely needs Python event wiring — the prompt box, the model
selector, submit, cancel, consent, and the conversation picker.

**Safety.** The moment markup is built by string concatenation, worker-supplied
text becomes an XSS vector, and the agent operator is explicitly *not* trusted
(see the community-worker warning this module renders). Agent display names,
hostnames, OS and architecture strings, RAM figures, model ids, tool refs and
energy provider names all originate on a remote machine controlled by someone
else.

So there is exactly one rule here, and it is enforced by construction:

    Every interpolated value goes through :func:`esc`. No exceptions.

The only strings that reach the output without escaping are literals written in
this file. Helpers that accept caller-supplied HTML are named ``*_raw`` and
there are only two of them, both fed by :mod:`distinct_server.ui` from text it
generated itself. ``tests/test_presentation.py`` asserts that hostile input at
every injection point comes out inert.
"""

from __future__ import annotations

import re
import secrets as _secrets
from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass
from html import escape
from pathlib import Path
from typing import Optional

from . import comparators as _comparators

__all__ = [
    "activity",
    "assessment_legend",
    "assessment_section",
    "chip",
    "comparator",
    "credit",
    "duration",
    "empty_state",
    "energy_headline",
    "energy_panel",
    "esc",
    "figure_token",
    "footer",
    "format_joules",
    "header",
    "join_this_server",
    "library_help",
    "library_summary",
    "live_steps",
    "model_evidence_card",
    "no_tools_note",
    "notice",
    "run_receipts",
    "section_heading",
    "spinner",
    "splash_copy",
    "splash_figure",
    "splash_meta",
    "usage_summary",
    "worker_cards",
    "worker_energy_line",
]


def esc(value: object) -> str:
    """Escape any value for inclusion in HTML text or a quoted attribute.

    ``quote=True`` matters: several call sites interpolate into attributes, and
    a helper that only escapes ``<>&`` would leave those open.
    """

    return escape("" if value is None else str(value), quote=True)


# --------------------------------------------------------------------------
# Header
# --------------------------------------------------------------------------


def header(identity: str, *, tagline: str, sign_in: str = "") -> str:
    """One row: wordmark left, identity right.

    ``sign_in`` makes the identity a link to the sign-in route rather than a
    label. It is passed only when nobody is signed in, so the control and the
    state cannot disagree: a permanent "Sign in" link beside "Signed in as
    Bob" is two things on screen contradicting each other, and this interface
    had exactly that for one afternoon.
    """

    if sign_in:
        right = f'<a class="c-identity c-identity--link" href="{esc(sign_in)}">{esc(identity)}</a>'
    else:
        right = f'<span class="c-identity">{esc(identity)}</span>'
    return (
        '<header class="c-header">'
        '<div class="c-header__brand">'
        '<span class="c-wordmark">distinct<span class="c-wordmark__dot">.</span></span>'
        f'<span class="c-tagline">{esc(tagline)}</span>'
        "</div>"
        + right
        + "</header>"
    )


# --------------------------------------------------------------------------
# Notice
# --------------------------------------------------------------------------


def notice() -> str:
    """The community-worker warning, compact with the detail one click away."""

    return (
        '<details class="c-notice">'
        '<summary class="c-notice__summary">'
        '<span class="c-notice__mark" aria-hidden="true">!</span>'
        '<span><strong>The operator of the agent you choose can read your prompts and '
        "its answers.</strong> Output, model identity and energy telemetry are "
        "self-reported and unverified.</span>"
        '<span class="c-notice__toggle">What is kept</span>'
        "</summary>"
        '<p class="c-notice__body">Do not send secrets or sensitive personal data. '
        "This server erases your prompt when the run finishes, and the answer once your "
        "browser has it. The transcript then lives only in this tab and is lost on "
        "reload. The worker erases its copy when the job leaves its queue. Neither side "
        "keeps prompt text for a finished run.</p>"
        "</details>"
    )


# --------------------------------------------------------------------------
# Small pieces
# --------------------------------------------------------------------------


def section_heading(text: str, *, note: str = "") -> str:
    suffix = f'<span class="c-eyebrow__note">{esc(note)}</span>' if note else ""
    return f'<p class="c-eyebrow">{esc(text)}{suffix}</p>'


def chip(text: str, *, tone: str = "neutral", glyph: str = "") -> str:
    """One chip style, one height, one radius.

    ``tone`` only ever picks a background. The status word is always inside the
    chip, so no meaning is carried by colour alone.
    """

    mark = f'<span class="c-chip__glyph" aria-hidden="true">{esc(glyph)}</span>' if glyph else ""
    return f'<span class="c-chip c-chip--{esc(tone)}">{mark}{esc(text)}</span>'


def empty_state(*, glyph: str, title: str, body: str, compact: bool = False) -> str:
    modifier = " c-empty--compact" if compact else ""
    return (
        f'<div class="c-empty{modifier}">'
        f'<span class="c-empty__glyph" aria-hidden="true">{esc(glyph)}</span>'
        f'<p class="c-empty__title">{esc(title)}</p>'
        f'<p class="c-empty__body">{esc(body)}</p>'
        "</div>"
    )


def no_tools_note(notice_text: str) -> str:
    return (
        '<p class="c-note">'
        + chip("no tools installed", tone="outline")
        + f'<span class="c-note__text">{esc(notice_text)}</span></p>'
    )


_PLATFORM_LABELS = {"win": "Windows", "nix": "macOS or Linux"}


def _command_block(command: str, *, platform: str = "") -> str:
    """One command, drawn as its own block with its own copy button.

    Two commands used to share a ``<pre>``. A block holding two lines can only
    be copied whole, and on a phone it cannot even be dragged apart, so
    somebody following the panel had to retype half of it. A block now holds
    one command and nothing else, and the button beside it copies that one.

    The button carries no copy of the text. The handler in
    :data:`distinct_server.presentation.HEAD` reads it out of the ``<pre>`` at
    click time, which makes the clipboard equal to the screen by construction.
    A second copy of the command in a ``data-`` attribute would be a second
    string to escape and a second string to get wrong.

    ``platform`` labels a command that differs between Windows and everything
    else. The label sits on the block, not only on the switch above it,
    because the switch starts on neither platform: nobody is shown a command
    for a machine that may not be theirs without the block saying whose it is.
    """

    label = _PLATFORM_LABELS.get(platform, "")
    tag = f'<span class="c-cmd__for">{esc(label)}</span>' if label else ""
    variant = f" c-cmd--{esc(platform)}" if label else ""
    whose = f'<span class="c-visually-hidden"> the {esc(label)} command</span>' if label else ""
    return (
        f'<div class="c-cmd{variant}">'
        f'<div class="c-cmd__bar">{tag}'
        '<button type="button" class="c-cmd__copy">'
        f'<span class="c-cmd__word">Copy</span>{whose}</button></div>'
        f'<pre class="c-join__cmd">{esc(command)}</pre>'
        "</div>"
    )


# WHICH MACHINE, ASKED BEFORE THE COMMANDS RATHER THAN AFTER THEM.
#
# Neither radio is checked, so both platforms' commands are on screen until
# somebody picks one. That is deliberate: a default would be right for one
# half of the readers and silently wrong for the other, and being silently
# wrong is the whole failure this panel is being rebuilt to stop. Picking
# hides the other platform; picking nothing costs a few extra lines, each of
# them labelled.
#
# The switch is two radios and CSS, with no script behind it, because this
# function returns a plain HTML string that has to work wherever it is
# rendered. The copy buttons need JavaScript and cannot avoid it; the choice
# of what a person reads should not.
_OS_SWITCH = (
    '<input class="c-os__radio" type="radio" name="distinct-os" id="distinct-os-win">'
    '<input class="c-os__radio" type="radio" name="distinct-os" id="distinct-os-nix">'
    '<div class="c-os">'
    '<span class="c-os__ask">Which machine are you on?</span>'
    '<label class="c-os__tab" for="distinct-os-win">Windows</label>'
    '<label class="c-os__tab" for="distinct-os-nix">macOS or Linux</label>'
    "</div>"
)


def join_this_server(
    *, addresses, code: str, qr: str = "", tools: str = "local", source: str = ""
) -> str:
    """The whole path from nothing to a running worker, in the order it happens.

    This panel used to be an address, a code and one command, sitting beside a
    "Download agent" button that pointed at release artifacts nobody builds.
    The button could only ever fail, and the command it accompanied assumed
    the code was already on the machine. What a volunteer actually needs is
    the three steps in between, so those are what this says: get the code,
    install it, run it.

    THE COMMANDS ARE PER-PLATFORM BECAUSE THE FOOTNOTE VERSION DID NOT WORK.

    The install step used to read ``python -m pip install -e .`` with
    a line underneath saying to use something else on Windows. People paste
    the command; they do not paste the footnote. On Windows the bare
    ``python`` is routinely an old interpreter that arrived with some other
    program, and the first volunteer to follow this panel got two failures in
    a row from it: pip 20.2.3 refusing an editable install of a project with
    no ``setup.py``, then the worker refusing to start on Python 3.9. Both
    were the panel's fault, not theirs. So the command a person copies is now
    the correct one for the machine they say they are on, and the caveat that
    used to live underneath is spelled ``py -3`` inside the command itself.

    ``py -3`` and not ``py -3.12``: the launcher picks the newest installed
    Python 3, and naming a minor version asserts something about a machine
    this server has never seen.

    ``source`` is where the code lives — this Space's own repository when the
    server knows it. Absent, the step says so plainly instead of guessing at
    a URL, because sending somebody to a repository that may not exist is
    worse than admitting the server cannot name one.

    ``addresses`` empty is a real state and is reported as one: a server bound
    to loopback is reachable by this machine and nothing else, and an agent on
    a phone will never see it. Inventing an address there would send somebody
    to debug their network over a decision this server made at start-up.
    """

    if not addresses:
        return (
            '<div class="c-join">'
            '<p class="c-join__lead">This server is bound to this machine only, so '
            "nothing else on your network can reach it. Restart it with "
            "<code>DISTINCT_BIND_HOST=0.0.0.0</code> to let an agent connect, and read "
            "what that exposes.</p></div>"
        )
    first = str(addresses[0])
    others = "".join(
        f'<li class="c-join__alt">{esc(url)}</li>' for url in list(addresses)[1:]
    )
    alternates = (
        f'<p class="c-join__note">Also reachable at {", ".join(esc(str(u)) for u in list(addresses)[1:])}.</p>'
        if others
        else ""
    )

    # STEP ONE: the code. A git clone, because a Space is a git repository and
    # a clone is the one form that also updates later with `git pull`. Same
    # command everywhere, so it carries no platform label.
    if source:
        get_it = (
            _command_block(f"git clone {source} distinct")
            + '<p class="c-join__note">Or use "Download repository" on the Files tab '
            "and unzip it.</p>"
        )
    else:
        get_it = (
            '<p class="c-join__note">This server does not know its own repository '
            "address. Ask whoever runs it.</p>"
        )

    # STEP TWO: the install. `-e` so the worker runs from the folder that was
    # cloned, which is also the folder `git pull` updates.
    #
    # THE PIP UPGRADE IS A STEP, NOT ADVICE. `py -3` fixes the interpreter and
    # not the pip inside it: Python 3.10.0 shipped pip 21.2, PEP 660 editable
    # installs landed in pip 21.3, and an old pip meeting a project with no
    # `setup.py` stops with a message about setup.py that reads as though the
    # repository is broken. One line ahead of the install turns that into a
    # non-event, so it is a line people can copy rather than a warning they
    # get to read after it has already failed.
    install = (
        _command_block("cd distinct")
        + _command_block("py -3 -m pip install --upgrade pip", platform="win")
        + _command_block("python3 -m pip install --upgrade pip", platform="nix")
        + _command_block('py -3 -m pip install -e .', platform="win")
        + _command_block('python3 -m pip install -e .', platform="nix")
        + '<p class="c-join__note">Needs Python 3.10 or newer. On Windows use '
        "<code>py -3</code>, not <code>python</code>: the bare command is often an "
        "older version left by another program. Run the pip line even if pip looks "
        "current, because pip below 21.3 cannot do this install and fails with a "
        "confusing message about <code>setup.py</code>.</p>"
    )

    # STEP THREE IS NOT "GO AND FETCH A DEPENDENCY".
    #
    # It was, briefly, and that was a bad answer to a fair complaint. The panel
    # first said nothing about llama.cpp at all, so a volunteer got through
    # every step and then met "this worker has no llama.cpp to run a model
    # with" -- a requirement the page had never named. The fix for that was a
    # step telling them to go to another project's releases page, pick the
    # right build out of a dozen, unzip it and edit their PATH, and the
    # reaction to that was the right one: *why do I have to provide this, it
    # should be baked into the repo*.
    #
    # It cannot be in the repository -- a llama.cpp release is around a hundred
    # megabytes per platform per accelerator, and a Space clone would carry all
    # of them -- but "not in the repository" was never the same thing as "your
    # problem". The worker now fetches the pinned build itself and verifies it
    # against a recorded SHA-256 before installing it, exactly as it already
    # did for model weights. See distinct_agent/runtime.py.
    #
    # So this step is no longer an instruction. It is a description of what the
    # command in the next step will do, which is here because a worker that
    # quietly downloads twenty megabytes should say so first.
    runner = (
        '<p class="c-join__note">Nothing to install by hand. Models run on '
        "<strong>llama.cpp</strong>, and the worker fetches it on first start "
        "(about 20 MB), checking it against a recorded SHA-256. If the bytes do "
        "not match, nothing is installed.</p>"
        '<p class="c-join__note">Already have a build, or want CUDA or Metal? '
        "Pass <code>--llama-server /path/to/llama-server</code>. "
        "<code>--no-fetch-runtime</code> downloads nothing. "
        "<code>--demo-runner</code> runs with no model at all: the worker still "
        "joins and still runs every tool and skill.</p>"
    )

    # STEP FOUR: the run, with this server's address and this browser's own
    # library defaults already in it.
    # NOTHING IS COPIED OUT OF THIS PAGE ANY MORE.
    #
    # This used to show a server-minted pairing code and a command with a
    # `--pair YOUR-CODE` placeholder in it, and ask the volunteer to carry the
    # one into the other. What people did instead -- reasonably, reading a
    # placeholder as a thing to fill in however they liked -- was invent a
    # value, and find out one program later that "your code wasn't valid",
    # which reads as the server rejecting them rather than as a copy that never
    # happened. Signed out, there was not even a code on the page to copy.
    #
    # The code now travels the other way: the worker invents one, prints it,
    # and waits, and it is typed into the box on this page. There is nothing
    # here to transcribe, so there is nothing to transcribe wrongly, and the
    # command is complete for everybody whether they are signed in or not.
    code_note = (
        '<p class="c-join__note">Nothing to fill in. Run it as it is.</p>'
    )
    # One line on purpose. The previous form used backslash continuations,
    # which are bash syntax: pasted into PowerShell they became stray
    # arguments and the command half-ran. A single line pastes correctly
    # into every shell anybody actually uses.
    arguments = (
        f"-m distinct_agent --server {first} "
        f"--models olmoe-1b-7b-0924-instruct --tools {tools or 'local'}"
    )
    run_it = _command_block(f"py -3 {arguments}", platform="win") + _command_block(
        f"python3 {arguments}", platform="nix"
    )
    picture = f'<div class="c-join__qr">{qr}</div>' if qr else ""
    return (
        '<div class="c-join">'
        f"{_OS_SWITCH}"
        '<ol class="c-steps-num">'
        '<li class="c-step-num"><span class="c-step-num__n">1</span>'
        '<div class="c-step-num__body"><p class="c-step-num__head">Get the code</p>'
        f"{get_it}</div></li>"
        '<li class="c-step-num"><span class="c-step-num__n">2</span>'
        '<div class="c-step-num__body"><p class="c-step-num__head">Install it</p>'
        f"{install}</div></li>"
        '<li class="c-step-num"><span class="c-step-num__n">3</span>'
        '<div class="c-step-num__body">'
        '<p class="c-step-num__head">Running models</p>'
        f"{runner}</div></li>"
        '<li class="c-step-num"><span class="c-step-num__n">4</span>'
        '<div class="c-step-num__body"><p class="c-step-num__head">Run it and claim it</p>'
        f'<code class="c-join__address">{esc(first)}</code>{alternates}{code_note}'
        f"{run_it}"
        '<p class="c-join__note">The worker prints a code and waits. Sign in, put '
        "that code into <em>Add a worker</em> on this page, and the machine is yours. "
        "Until then nobody can see it and nothing can be sent to it.</p>"
        f"{picture}</div></li>"
        # THE LAST STEP EXISTS BECAUSE PEOPLE ASK FOR IT AFTER READING THE ONE
        # BEFORE IT.
        #
        # Sharing was one clause at the end of the previous step, and the
        # question "how do several people use my machine?" kept coming back,
        # which is what a clause at the end of a step gets you. It is the
        # point of running a worker at all, so it is a step of its own, and
        # it says the non-obvious part out loud: the code is not spent by
        # the first person who enters it.
        '<li class="c-step-num"><span class="c-step-num__n">5</span>'
        '<div class="c-step-num__body">'
        '<p class="c-step-num__head">Share it</p>'
        '<p class="c-join__note">Once claimed, the worker prints an <strong>access '
        "code</strong>. Send it to anyone who should use your machine. They sign in at "
        f"{esc(first)} and paste it into <em>Add a worker</em>.</p>"
        '<p class="c-join__note">The same code works for everyone you give it to and is '
        "not used up. Nobody else can see your worker, but anyone holding the code can "
        "send it requests, so treat it like a key.</p>"
        "</div></li>"
        "</ol>"
        "</div>"
    )


def library_summary(*, shown: int, matched: int, total: int, chosen: int) -> str:
    """What the library page is currently showing, and what has been kept.

    Written out because a filtered list with no count is a list you cannot
    trust: a search that matched nothing and a search that matched everything
    look identical if all you can see is the rows. The kept count is separate
    from the shown count on purpose, since the two diverge the moment anyone
    searches, and the number people will want to check is how many they have
    chosen overall.
    """

    if not total:
        return (
            '<p class="c-note"><span class="c-note__text">'
            "This server knows of no library members.</span></p>"
        )
    if matched == 0:
        body = f"No member matches that. {total} in the library."
    elif shown < matched:
        body = f"Showing {shown} of {matched} matches, out of {total} in the library."
    elif matched < total:
        body = f"{matched} of {total} members match."
    else:
        body = f"All {total} members."
    kept = (
        "Tap a card to make it a default. It saves at once."
        if not chosen
        else f"<strong>{chosen}</strong> saved as {'a default' if chosen == 1 else 'defaults'}."
    )
    return (
        '<div class="c-libsave"><span class="c-libsave__count">'
        f"{esc(body)} {kept}</span></div>"
    )


def library_help(rows: Sequence[Mapping[str, object]]) -> str:
    """One line per offered library member: what it is called, what it does.

    A reference like ``create_pdf@1`` names a member and describes nothing.
    The picker shows the readable name; this shows the sentence, so choosing
    is not a guess. The exact ref is here too, in a code span, because it is
    what the allowlist and the run log will show and a person should be able
    to connect the two.
    """

    if not rows:
        return ""
    items = "".join(
        '<li class="c-libhelp__item">'
        f'<span class="c-libhelp__name">{esc(row.get("name", ""))}</span>'
        f'<span class="c-libhelp__kind">{esc(row.get("kind", ""))}</span>'
        f'<code class="c-libhelp__ref">{esc(row.get("ref", ""))}</code>'
        f'<span class="c-libhelp__what">{esc(row.get("description", ""))}</span>'
        "</li>"
        for row in rows
    )
    return (
        '<details class="c-libhelp">'
        '<summary class="c-libhelp__toggle">What these do</summary>'
        f'<ul class="c-libhelp__list">{items}</ul>'
        '<p class="c-libhelp__note">Selection resets after every run. A skill '
        "returns its file to this session, where you can download it.</p>"
        "</details>"
    )


# --------------------------------------------------------------------------
# Live activity: what the run is doing, and why it is not doing it yet
# --------------------------------------------------------------------------


def spinner(*, label: str = "") -> str:
    """A CSS spinner with an accessible label.

    ``aria-hidden`` on the visual mark and a visually-hidden label beside it,
    so a screen reader hears "working" once rather than reading a decoration.
    """

    hidden = f'<span class="c-visually-hidden">{esc(label)}</span>' if label else ""
    return f'<span class="c-spin" aria-hidden="true"></span>{hidden}'


def _step_mark(kind: str, ok: object) -> tuple[str, str]:
    """Geometric mark and tone for one live step. No emojis, house style."""

    if kind == "tool-call":
        return "▸", "work"
    if kind == "tool-result":
        return ("●", "ok") if ok else ("■", "warn")
    if kind == "model":
        return "◇", "neutral"
    return "·", "neutral"


def live_steps(steps: Sequence[Mapping[str, object]]) -> str:
    """The run's own trace, as it happens.

    **Every field is worker-supplied.** Tool names, argument previews and
    result previews come from a machine someone else operates, so all of them
    are escaped, and the structure is fixed here rather than taken from the
    wire. A worker can decide what its run says; it cannot decide what the
    markup is.
    """

    if not steps:
        return ""
    items: list[str] = []
    for step in steps:
        kind = str(step.get("kind") or "phase")
        mark, tone = _step_mark(kind, step.get("ok", True))
        tool = str(step.get("tool") or "")
        text = str(step.get("text") or "")
        elapsed = step.get("elapsed_ms")
        timing = (
            f'<span class="c-step__timing">{esc(elapsed)} ms</span>'
            if isinstance(elapsed, int) and not isinstance(elapsed, bool)
            else ""
        )
        name = f'<code class="c-step__tool">{esc(tool)}</code>' if tool else ""
        body = f'<span class="c-step__text">{esc(text)}</span>' if text else ""
        items.append(
            f'<li class="c-step c-step--{esc(tone)}">'
            f'<span class="c-step__mark" aria-hidden="true">{esc(mark)}</span>'
            f'<span class="c-step__body">{name}{body}</span>'
            f"{timing}</li>"
        )
    return f'<ol class="c-steps">{"".join(items)}</ol>'


def activity(
    *,
    headline: str,
    detail: str = "",
    steps: Sequence[Mapping[str, object]] = (),
    elapsed_seconds: float | None = None,
    estimate_seconds: float | None = None,
    estimate_note: str = "",
    working: bool = True,
    tone: str = "work",
) -> str:
    """The in-flight run panel: what is happening, and how long it may take.

    ``headline`` is copy this server wrote, chosen from a closed phase
    vocabulary. ``detail`` may be worker-supplied and is escaped. The estimate
    is rendered only when one exists, and it always says what it is based on:
    an estimate with no stated provenance is a promise, and this project does
    not make promises about someone else's machine.
    """

    mark = spinner(label="Working") if working else '<span class="c-activity__dot" aria-hidden="true">●</span>'
    detail_html = f'<p class="c-activity__detail">{esc(detail)}</p>' if detail else ""

    bar = ""
    timing = ""
    if elapsed_seconds is not None:
        parts = [f"{duration(elapsed_seconds)} elapsed"]
        if estimate_seconds is not None and estimate_seconds > 0:
            parts.append(f"about {duration(estimate_seconds)} expected")
            fraction = max(0.0, min(1.0, elapsed_seconds / estimate_seconds))
            # aria-valuenow on a real progressbar role: the bar is information,
            # not decoration, so it is exposed rather than hidden.
            bar = (
                '<div class="c-activity__bar" role="progressbar" aria-valuemin="0" '
                f'aria-valuemax="100" aria-valuenow="{int(fraction * 100)}">'
                f'<span class="c-activity__fill" style="width:{fraction * 100:.1f}%"></span>'
                "</div>"
            )
        timing = f'<p class="c-activity__timing">{esc(" · ".join(parts))}</p>'

    note = (
        f'<p class="c-activity__note">{esc(estimate_note)}</p>' if estimate_note else ""
    )
    return (
        f'<section class="c-activity c-activity--{esc(tone)}" aria-live="polite">'
        '<p class="c-activity__head">'
        f"{mark}"
        f'<span class="c-activity__headline">{esc(headline)}</span>'
        "</p>"
        f"{detail_html}{bar}{timing}{note}"
        f"{live_steps(steps)}"
        "</section>"
    )


def duration(seconds: float) -> str:
    """Human duration. Seconds below a minute, minutes and seconds above."""

    value = max(0, int(round(seconds)))
    if value < 60:
        return f"{value}s"
    minutes, remainder = divmod(value, 60)
    if minutes < 60:
        return f"{minutes}m {remainder:02d}s"
    hours, minutes = divmod(minutes, 60)
    return f"{hours}h {minutes:02d}m"


# --------------------------------------------------------------------------
# Worker cards — the main untrusted-input surface
# --------------------------------------------------------------------------

_STATUS_TONE: Mapping[str, tuple[str, str]] = {
    "free": ("ok", "●"),
    "busy": ("work", "◐"),
    "overloaded": ("warn", "■"),
    "draining": ("warn", "◑"),
    "offline": ("off", "○"),
    # Connected, polling, and useless: it approved nothing, so it advertises
    # nothing and matches no model. It has to be visible, because from the
    # user's side the symptom is "no compatible worker" while a worker is
    # plainly running, which reads as the server being broken.
    "unapproved": ("warn", "◌"),
}

#: Extra sentence shown under a card whose status needs one. Keyed by status
#: so a card can never acquire an explanation that does not match its state.
_STATUS_NOTE: Mapping[str, str] = {
    "unapproved": (
        "This worker is connected but approved no models, so it can accept no "
        "work. Its operator restarts it and approves a set, or passes --approve."
    ),
}


def worker_energy_line(
    *,
    joules: float | None,
    runs: int,
    measured_runs: int,
    uptime_seconds: float,
) -> str:
    """What this machine has cost, and what it costs per answer.

    Two numbers, because they answer two different questions a person actually
    has when picking a worker: what will my run cost here, and how much has
    this machine spent altogether. Both carry a comparator, and both are
    self-reported by a machine nobody here can audit, which the card says.

    The average is over *measured* runs only. Dividing a partial total by the
    full run count would quietly understate the per-run figure by exactly the
    proportion that was never measured.
    """

    if joules is None or measured_runs <= 0:
        return (
            '<p class="c-worker__energy c-worker__energy--absent">'
            "No electricity measurement from this worker. Not zero: it has no "
            "usable meter, or none of its runs returned one."
            "</p>"
        )
    average = joules / measured_runs
    floor = "" if measured_runs == runs else f" (a floor: {measured_runs} of {runs} runs measured)"
    return (
        '<p class="c-worker__energy">'
        f'<span class="c-worker__energy-figure">{esc(format_joules(average))} per run</span>'
        f'<span class="c-worker__energy-means">{esc(comparator(average))}</span>'
        f'<span class="c-worker__energy-total">'
        f"{esc(format_joules(joules))} in total since it started "
        f"{esc(duration(uptime_seconds))} ago, {esc(comparator(joules))}"
        f"{esc(floor)}</span>"
        '<span class="c-worker__energy-note">Self-reported by the worker.</span>'
        "</p>"
    )


def worker_cards(rows: Sequence[Sequence[object]], *, narrowed_by: str = "") -> str:
    """Render agent rows as calm metadata.

    **Every field below is worker-controlled** and therefore escaped. The row
    shape is the one ``DistinctUI._agent_components`` already produces, so the
    data contract is unchanged.
    """

    if not rows:
        return '<div class="c-noworker-hook">' + empty_state(
            glyph="○",
            title="No compatible worker",
            # Which of the choices above emptied the list. Without this the
            # panel said the same thing whether no worker existed at all or
            # whether one was sitting right there offering the other agent
            # mode, and the user had no way to tell those apart or to know
            # that changing one control would fix it.
            body=(
                (
                    f"A worker is available, but none of them runs the {narrowed_by} "
                    "mode. Change the mode above, or start a worker that offers "
                    "it."
                )
                if narrowed_by
                else (
                    "Nothing can run until a community agent pairs with this server "
                    "and advertises this model. Open “Run a community agent” to "
                    "start one."
                )
            ),
            compact=True,
        ) + "</div>"

    cards: list[str] = []
    for row in rows:
        values = [str(item) for item in row[:8]]
        values += [""] * (8 - len(values))
        name, status, os_arch, ram, queue, wait, energy, tools = values[:8]
        display_name, _, agent_id = name.partition(" · ")
        tone, glyph = _STATUS_TONE.get(status, ("neutral", "○"))

        meta = [chip(status, tone=tone, glyph=glyph)]
        if queue:
            meta.append(chip(f"{queue} queued"))
        if wait:
            meta.append(chip(f"~{wait} wait"))
        if os_arch:
            meta.append(chip(os_arch))
        if ram:
            meta.append(chip(ram))
        # "unavailable" is a real answer and must not read as a blank or a zero.
        if energy and energy.lower() not in {"unavailable", "none", ""}:
            meta.append(chip("energy metered"))
        else:
            meta.append(chip("energy not measured", tone="outline"))
        meta.append(chip(tools if tools and tools != "none" else "no tools", tone="outline"))

        # Position 8 onwards is the energy block, absent on older row shapes.
        energy_html = ""
        if len(row) > 8 and isinstance(row[8], Mapping):
            energy_html = worker_energy_line(
                joules=row[8].get("joules"),
                runs=int(row[8].get("runs") or 0),
                measured_runs=int(row[8].get("measured_runs") or 0),
                uptime_seconds=float(row[8].get("uptime_seconds") or 0.0),
            )
        offline = " c-worker--offline" if status == "offline" else ""
        note = _STATUS_NOTE.get(status, "")
        note_html = f'<p class="c-worker__note">{esc(note)}</p>' if note else ""
        cards.append(
            f'<article class="c-worker{offline}">'
            f'<p class="c-worker__name">{esc(display_name)}'
            f'<span class="c-worker__id">{esc(agent_id)}</span></p>'
            f'<div class="c-chips">{"".join(meta)}</div>'
            f"{energy_html}{note_html}"
            "</article>"
        )
    return f'<div class="c-workers">{"".join(cards)}</div>'


# --------------------------------------------------------------------------
# Energy — the honesty-critical surface
# --------------------------------------------------------------------------


#: The visual half of "a missing measurement is never a zero". Defined once so
#: the two energy renderers cannot drift apart, and so the glyph appears in
#: exactly one place in this file.
ABSENCE_MARK = '<span class="c-energy__dash" aria-hidden="true">\u2013</span>'


#: Everyday appliances, with the power rating each figure assumes stated on
#: its face. Ordered small to large; the first whose duration lands in a
#: readable range wins.
#:
#: WHY THIS IS ALLOWED WHEN CO2e IS NOT. The rubric forbids converting
#: use-phase energy into carbon, water, land or materials, because every one of
#: those needs a factor the assessor chose, and the choice would be invisible
#: in the result. This is not that. It is the same joules divided by a stated
#: wattage, so it is arithmetic the reader can check and reverse, and the
#: wattage is always printed beside it. "12 seconds of a 10 W bulb" tells you
#: nothing about the world that "120 J" did not; it just tells it in a unit
#: people have a body for.
#: Everyday things that draw power, with the rating each comparison assumes.
#:
#: **Why there are so many.** One comparator repeated on every figure stops
#: being read after the third time: it becomes a fixed suffix on the number,
#: which is the opposite of what it is for. A wide table means the appliance
#: changes from figure to figure, so the sentence has to be read again, and
#: reading it again is the entire mechanism by which a number in joules turns
#: into something a person has a feel for.
#:
#: **Why every entry carries its wattage.** This is what separates a
#: comparison from a conversion. "23 seconds of a 60 W laptop charger" hands
#: the reader the divisor and they can check it or ignore it; "0.03 kg of
#: CO2e" hides a factor they did not choose and cannot see. The rubric forbids
#: the second kind, and the wattage in every name is how this table stays on
#: the right side of that line.
#:
#: Figures are nominal ratings for the everyday version of each thing, which
#: is what a reader is picturing. Anything genuinely variable is described at
#: the draw it is being compared against, so a heat pump appears at its
#: running draw rather than at a seasonal average nobody would recognise.
COMPARATORS: tuple[tuple[str, float], ...] = (
    ("a 1 W smoke alarm", 1.0),
    ("a 2 W phone on standby", 2.0),
    ("a 3 W fairy-light string", 3.0),
    ("a 4 W bedside clock", 4.0),
    ("a 5 W phone charger", 5.0),
    ("a 6 W wifi router", 6.0),
    ("an 8 W set-top box", 8.0),
    ("a 10 W LED bulb", 10.0),
    ("a 12 W bicycle light", 12.0),
    ("a 15 W desk lamp", 15.0),
    ("an 18 W tablet charging", 18.0),
    ("a 20 W games controller charging", 20.0),
    ("a 25 W ceiling fan", 25.0),
    ("a 30 W laptop idling", 30.0),
    ("a 35 W broadband modem", 35.0),
    ("a 40 W old filament bulb", 40.0),
    ("a 45 W electric blanket", 45.0),
    ("a 50 W car headlamp", 50.0),
    ("a 60 W laptop charger", 60.0),
    ("a 70 W sewing machine", 70.0),
    ("an 80 W fridge running", 80.0),
    ("a 90 W desktop idling", 90.0),
    ("a 100 W standing person", 100.0),
    ("a 120 W television", 120.0),
    ("a 140 W games console", 140.0),
    ("a 160 W blender", 160.0),
    ("a 180 W electric fan heater on low", 180.0),
    ("a 200 W desktop under load", 200.0),
    ("a 250 W electric drill", 250.0),
    ("a 300 W graphics card", 300.0),
    ("a 350 W treadmill", 350.0),
    ("a 400 W chest freezer starting", 400.0),
    ("a 450 W food mixer", 450.0),
    ("a 500 W soldering station", 500.0),
    ("a 600 W microwave", 600.0),
    ("a 700 W slow cooker on high", 700.0),
    ("an 800 W coffee machine", 800.0),
    ("a 900 W dishwasher heating", 900.0),
    ("a 1 kW toaster", 1_000.0),
    ("a 1.2 kW hairdryer", 1_200.0),
    ("a 1.4 kW iron", 1_400.0),
    ("a 1.5 kW space heater", 1_500.0),
    ("a 1.6 kW vacuum cleaner", 1_600.0),
    ("a 1.8 kW washing machine heating", 1_800.0),
    ("a 2 kW kettle", 2_000.0),
    ("a 2.2 kW immersion heater", 2_200.0),
    ("a 2.4 kW patio heater", 2_400.0),
    ("a 2.6 kW tumble dryer", 2_600.0),
    ("a 3 kW oven element", 3_000.0),
    ("a 3.5 kW shower pump", 3_500.0),
    ("a 4 kW hob at full", 4_000.0),
    ("a 5 kW electric shower", 5_000.0),
    ("a 7 kW car charger", 7_000.0),
    ("an 11 kW three-phase car charger", 11_000.0),
)

#: How wide a window of appliances a figure may be described by. The window is
#: what creates the variety: rather than always taking the largest appliance a
#: figure covers, any appliance giving a duration between one second and a few
#: minutes is a fair description of it, and one of those is chosen.
_COMPARATOR_MIN_SECONDS = 1.0
_COMPARATOR_MAX_SECONDS = 400.0


def _comparator_candidates(joules: float) -> tuple[tuple[str, float], ...]:
    return tuple(
        entry
        for entry in COMPARATORS
        if _COMPARATOR_MIN_SECONDS <= joules / entry[1] <= _COMPARATOR_MAX_SECONDS
    )


def comparator(joules: float) -> str:
    """"about 12 seconds of a 10 W LED bulb", or an empty string.

    Picks from the appliances that describe this figure in a readable span of
    time, rather than always the same one, so the same number seen twice in a
    session is not accompanied by the same sentence twice. The choice is
    derived from the figure itself, which matters more than it looks: this is
    re-rendered every two seconds while a run is in flight, and a comparator
    that rolled a die on each render would flicker between appliances beside a
    number that had not changed. The same figure always yields the same
    sentence; a different figure usually yields a different one.

    Below a second of the smallest thing in the table there is no honest
    comparison to draw, and this says so rather than reaching for a unit
    nobody has an intuition for.
    """

    if not isinstance(joules, (int, float)) or isinstance(joules, bool):
        return ""
    if joules <= 0:
        return ""
    candidates = _comparator_candidates(float(joules))
    if not candidates:
        # Above the window: every appliance would give a duration too long to
        # picture, so take the largest and let the duration speak.
        if joules / COMPARATORS[-1][1] > _COMPARATOR_MAX_SECONDS:
            name, watts = COMPARATORS[-1]
            return f"about {duration(joules / watts)} of {name}"
        # Below it: under a second of the smallest thing here.
        return f"less than a second of {COMPARATORS[0][0]}"
    # Derived from the figure, not from a random source: stable across the
    # re-renders of one number, varied across different numbers.
    #
    # Mixed rather than taken modulo directly. Energy figures are not evenly
    # distributed in their low digits: they arrive rounded, and a plain
    # ``value % len`` therefore landed on the first candidate far more often
    # than any other, which produced a table of fifty-four appliances that
    # mostly said "smoke alarm". Multiplying by a large odd constant and
    # taking the high bits spreads the low-order regularity out.
    mixed = (int(round(float(joules) * 1000)) * 2_654_435_761) & 0xFFFFFFFF
    index = (mixed >> 8) % len(candidates)
    name, watts = candidates[index]
    return f"about {duration(joules / watts)} of {name}"


def energy_headline(
    joules: float | None,
    *,
    label: str,
    detail: str = "",
    absent_reason: str = "",
) -> str:
    """One energy figure, in the order a person reads it.

    The figure, then what it equates to, then the small print. That order is
    the whole change: this panel used to lead with "scope: cpu-package,
    boundary 1 of 2, measured across 3 of 3 completed runs", which is every
    true thing about the number arranged so that the number came last.

    Nothing was dropped to achieve it. The scope, the provider and the run
    counts are still on the page, one disclosure away, because they are what
    make the figure checkable. They are simply no longer the first thing
    between a user and their own answer.
    """

    if joules is None:
        return (
            f'<div class="c-usage c-usage--absent">'
            f'<p class="c-usage__label">{esc(label)}</p>'
            f'<p class="c-usage__figure">No data</p>'
            f'<p class="c-usage__means">{esc(absent_reason or "not measured, and not counted as zero")}</p>'
            "</div>"
        )
    # TWO COMPARISONS, BECAUSE THEY ANSWER DIFFERENT QUESTIONS.
    #
    # "about 1.4 mugs of tea boiled" is the one people actually read: a whole
    # familiar thing, and how much of it this is. "about 12 seconds of a 10 W
    # bulb" is exact and stays underneath it, because for very small figures a
    # fraction of a mug of tea means nothing and a span of time still does.
    equals = _comparators.describe(joules)
    span = comparator(joules)
    means_html = f'<p class="c-usage__means">{esc(equals)}</p>' if equals else ""
    if span and span != equals:
        basis = _comparators.basis(joules)
        # The working travels with the comparison. Somebody who wants to check
        # "one mug of tea is 105 kJ" can, without leaving the page.
        title = f' title="{esc(basis)}"' if basis else ""
        means_html += f'<p class="c-usage__span"{title}>{esc(span)}</p>'
    detail_html = f'<p class="c-usage__detail">{esc(detail)}</p>' if detail else ""
    return (
        '<div class="c-usage">'
        f'<p class="c-usage__label">{esc(label)}</p>'
        f'<p class="c-usage__figure">{esc(format_joules(joules))}</p>'
        f"{means_html}{detail_html}"
        "</div>"
    )


def format_joules_per_token(value: float) -> str:
    """Joules per token at a scale people can read.

    A small model's figure is millijoules; a big machine's is joules. Two
    decades of unit, chosen by size, so neither reads as zero.
    """

    if value >= 1.0:
        return f"{value:.2f} J/tok"
    return f"{value * 1000:.1f} mJ/tok"


def format_joules(joules: float) -> str:
    """A figure with a unit people read without converting.

    Every three decades gets its own prefix. This used to stop at kilojoules,
    so a worker's lifetime total rendered as "870000.00 kJ" -- six digits and a
    prefix, which is the exact thing a prefix exists to prevent. A session
    figure and a fleet figure are decades apart and both have to be readable.

    Watt-hours are not used as the headline: one run costs hundredths of one,
    and a number that starts 0.00 reads as zero however it is labelled.
    """

    if joules >= 1_000_000_000:
        return f"{joules / 1_000_000_000:.2f} GJ"
    if joules >= 1_000_000:
        return f"{joules / 1_000_000:.2f} MJ"
    if joules >= 1_000:
        return f"{joules / 1_000:.2f} kJ"
    if joules >= 10:
        return f"{joules:.1f} J"
    return f"{joules:.2f} J"


def usage_summary(sections: Sequence[Mapping[str, object]], *, detail: str = "") -> str:
    """The energy surface: figures first, measurement detail behind a toggle."""

    body = "".join(
        energy_headline(
            section.get("joules"),
            label=str(section.get("label") or ""),
            detail=str(section.get("detail") or ""),
            absent_reason=str(section.get("absent_reason") or ""),
        )
        for section in sections
    )
    small_print = (
        '<details class="c-usage__more">'
        '<summary class="c-usage__moretoggle">How this is measured</summary>'
        f'<div class="c-usage__morebody">{esc(detail)}</div>'
        "</details>"
        if detail
        else ""
    )
    return f'<section class="c-usage-panel">{body}{small_print}</section>'


#: How each kind of step is introduced. The worker names the kind from a
#: closed vocabulary and this file owns the sentence, which is the same
#: division the phase copy uses: a worker can say what happened, never how it
#: is described.
STEP_LEAD = {
    "model": ("◇", "Model"),
    "tool-call": ("→", "Called"),
    "tool-result": ("●", "Returned"),
    "phase": ("·", ""),
}


# --------------------------------------------------------------------------
# What the run made: the artifact's own content, in the answer
# --------------------------------------------------------------------------
#
# A run that produced a file used to say so with a count in the receipt and a
# row in the Outputs panel. Both are true and neither shows the thing. These
# two functions close that gap: `artifact_preview_data` reads the stored file
# once, at the moment the answer is claimed, and reduces it to a small plain
# record; `made_things` renders those records as the kind of object each one
# is — a document page, a sheet of cells, a fan of slides, the text itself.
#
# The split matters. Derivation happens once and the record rides in browser
# state beside the answer, so the transcript can re-render every two seconds
# without re-opening zip files. And because the record is data assembled here
# from bytes the worker sent, rendering it is the same trust exercise as the
# rest of this file: every string is escaped, structure comes from this
# module, and a file that cannot be parsed degrades to its name and size
# rather than to an error.

#: Reading cap per artifact. Files are small (the job budget bounds them);
#: this is the backstop that keeps a surprise out of the render path.
_PREVIEW_READ_CAP = 400_000

#: How much a compressed member may be allowed to become, and how much of a
#: whole file may be inflated across all its members.
#:
#: THE FILE ON THE WIRE IS NOT THE FILE IN MEMORY. A worker is somebody
#: else's machine, and the protocol caps what it may send, not what that
#: expands to: a 300 KB archive of compressible bytes inflates to 300 MB, and
#: a run may carry eight of them. Read whole, that is 2.4 GB and half a minute
#: of CPU inside the one process every viewer of this server shares — a worker
#: could stop the service for everybody by answering a question.
#:
#: A preview needs the first few paragraphs, rows or slides, so these are
#: generous by the standard of what is actually read and mean the arithmetic
#: cannot run away.
_MAX_INFLATED_MEMBER = 2 * 1024 * 1024
_MAX_INFLATED_TOTAL = 8 * 1024 * 1024


class _InflationRefused(Exception):
    """A member claimed, or turned out to be, more than a preview may inflate."""


def _read_member(archive, name: str, budget: list[int]) -> bytes:
    """One zip member, refused rather than inflated when it is too large.

    Two checks, because either alone is defeated. The header's ``file_size``
    is what the archive claims and can simply lie; reading one byte past the
    limit is what actually happened. ``budget`` is the file's remaining total,
    mutated as members are read, so many merely-large members cannot add up to
    the same attack that one huge one would.
    """

    try:
        info = archive.getinfo(name)
    except KeyError as exc:
        raise _InflationRefused(f"no member {name!r}") from exc
    allowed = min(_MAX_INFLATED_MEMBER, budget[0])
    if info.file_size > allowed:
        raise _InflationRefused(f"{name} declares {info.file_size} bytes")
    with archive.open(name) as handle:
        data = handle.read(allowed + 1)
    if len(data) > allowed:
        raise _InflationRefused(f"{name} is larger than it declared")
    budget[0] -= len(data)
    return data

_TEXT_SUFFIXES = {
    ".txt", ".css", ".json", ".mermaid", ".py", ".js", ".html", ".svg",
    ".yaml", ".yml", ".toml", ".xml",
}


def _xml_texts(blob: bytes, pattern: str) -> list[str]:
    import re as _re

    return [
        escape_entities(match)
        for match in _re.findall(pattern, blob.decode("utf-8", "replace"), _re.S)
    ]


def escape_entities(text: str) -> str:
    """Resolve the five XML entities OOXML writes; nothing else."""

    return (
        text.replace("&lt;", "<").replace("&gt;", ">").replace("&quot;", '"')
        .replace("&apos;", "'").replace("&amp;", "&")
    )


def _docx_preview(blob: bytes) -> Optional[Mapping[str, object]]:
    import io as _io
    import re as _re
    import zipfile as _zipfile

    try:
        with _zipfile.ZipFile(_io.BytesIO(blob)) as archive:
            document = _read_member(archive, "word/document.xml", [_MAX_INFLATED_TOTAL])
    except Exception:
        return None
    paragraphs: list[str] = []
    for paragraph in _re.findall(rb"<w:p[ >].*?</w:p>", document, _re.S):
        runs = _xml_texts(paragraph, r"<w:t[^>]*>(.*?)</w:t>")
        text = "".join(runs).strip()
        if text:
            paragraphs.append(text)
    if not paragraphs:
        return None
    return {"kind": "doc", "title": paragraphs[0][:160], "paras": paragraphs[1:5]}


def _xlsx_preview(blob: bytes) -> Optional[Mapping[str, object]]:
    import io as _io
    import re as _re
    import zipfile as _zipfile

    budget = [_MAX_INFLATED_TOTAL]
    try:
        with _zipfile.ZipFile(_io.BytesIO(blob)) as archive:
            sheet = _read_member(archive, "xl/worksheets/sheet1.xml", budget)
            try:
                shared = _read_member(archive, "xl/sharedStrings.xml", budget)
            except (KeyError, _InflationRefused):
                shared = b""
    except Exception:
        return None
    strings = _xml_texts(shared, r"<t[^>]*>(.*?)</t>") if shared else []
    rows: list[list[str]] = []
    # CELLS IN DOCUMENT ORDER, WHATEVER KIND THEY ARE.
    #
    # The first version of this pulled the numbers out with one pattern and
    # the inline strings with another, then concatenated the two lists. Every
    # mixed row came out rotated — "Widget | 4 | 9.50" rendered as
    # "4 | 9.50 | Widget" — because the text cells were appended after the
    # numeric ones rather than kept in their columns. A preview that reorders
    # somebody's spreadsheet is worse than no preview, so the cells are now
    # walked once, in order, and each is read according to its own type.
    for row in _re.findall(rb"<row[ >].*?</row>", sheet, _re.S)[:8]:
        cells: list[str] = []
        for cell in _re.findall(rb"<c[ >].*?(?:</c>|/>)", row, _re.S):
            kind = _re.search(rb'\st="(\w+)"', cell)
            kind = kind.group(1).decode() if kind else "n"
            inline = _xml_texts(cell, r"<t[^>]*>(.*?)</t>")
            if inline:
                cells.append(inline[0])
                continue
            value = _re.search(rb"<v>(.*?)</v>", cell, _re.S)
            if value is None:
                cells.append("")
                continue
            raw = escape_entities(value.group(1).decode("utf-8", "replace"))
            if kind == "s":
                try:
                    raw = strings[int(raw)]
                except (ValueError, IndexError):
                    pass
            cells.append(raw)
        while cells and cells[-1] == "":
            cells.pop()
        if cells:
            rows.append(cells[:8])
    if not rows:
        return None
    return {"kind": "sheet", "rows": rows}


def _pptx_preview(blob: bytes) -> Optional[Mapping[str, object]]:
    import io as _io
    import re as _re
    import zipfile as _zipfile

    try:
        with _zipfile.ZipFile(_io.BytesIO(blob)) as archive:
            names = sorted(
                name
                for name in archive.namelist()
                if _re.fullmatch(r"ppt/slides/slide\d+\.xml", name)
            )
            slides: list[Mapping[str, str]] = []
            budget = [_MAX_INFLATED_TOTAL]
            for name in names[:8]:
                try:
                    member = _read_member(archive, name, budget)
                except _InflationRefused:
                    continue
                texts = _xml_texts(member, r"<a:t>(.*?)</a:t>")
                texts = [text for text in (t.strip() for t in texts) if text]
                if texts:
                    slides.append({"title": texts[0][:120]})
    except Exception:
        return None
    if not slides:
        return None
    return {"kind": "deck", "slides": slides}


def _pdf_preview(blob: bytes) -> Optional[Mapping[str, object]]:
    import re as _re
    import zlib as _zlib

    pages = len(_re.findall(rb"/Type\s*/Page\b(?!s)", blob))
    # Content streams are flate-compressed (the PDF this product writes says so
    # in its own builder), so the text operators only exist after inflating.
    # Streams that will not inflate are skipped rather than guessed at, and a
    # PDF nobody can read still reports its page count, which is a fact.
    chunks = [blob]
    budget = _MAX_INFLATED_TOTAL
    for raw in _re.findall(rb"stream\r?\n(.*?)\r?\nendstream", blob, _re.S):
        if budget <= 0:
            break
        try:
            # Bounded inflation: `decompress(data, max_length)` stops at the
            # limit instead of returning however many gigabytes the stream
            # encodes. What is left in the decompressor is dropped with it.
            allowed = min(_MAX_INFLATED_MEMBER, budget)
            piece = _zlib.decompressobj().decompress(raw, allowed)
        except Exception:
            continue
        budget -= len(piece)
        chunks.append(piece)
    lines: list[str] = []
    seen: set[str] = set()
    for chunk in chunks:
        for raw in _re.findall(rb"\(((?:[^()\\]|\\.)*)\)\s*Tj", chunk):
            text = raw.decode("latin-1", "replace")
            text = _re.sub(r"\\([()\\])", r"\1", text).strip()
            if text and text not in seen:
                seen.add(text)
                lines.append(text)
        if len(lines) >= 14:
            break
    lines = lines[:14]
    if not lines and not pages:
        return None
    record: dict[str, object] = {"kind": "doc", "note": f"{pages or 1} page PDF"}
    if lines:
        record["title"] = lines[0][:160]
        record["paras"] = lines[1:5]
    else:
        record["title"] = "PDF document"
        record["paras"] = []
    return record


def _markdown_preview(text: str) -> Optional[Mapping[str, object]]:
    """Markdown as the page it describes, not as its source.

    A guide rendered as a wall of hashes and asterisks demonstrates nothing:
    the person is looking at the thing the run made, and the thing the run
    made is a document. Headings become the title, bullets keep their bullet,
    and everything else is a paragraph.
    """

    lines = [line.rstrip() for line in text.splitlines()]
    title = ""
    paras: list[str] = []
    for line in lines:
        stripped = line.strip()
        if not stripped:
            continue
        if stripped.startswith("#"):
            heading = stripped.lstrip("#").strip()
            if not title:
                title = heading[:160]
            elif len(paras) < 4:
                paras.append(heading[:200])
            continue
        if stripped.startswith(("-", "*", "+")) and len(stripped) > 1:
            stripped = "• " + stripped[1:].strip()
        cleaned = stripped.replace("**", "").replace("`", "")
        if not title:
            title = cleaned[:160]
        elif len(paras) < 4:
            paras.append(cleaned[:200])
        if len(paras) >= 4:
            break
    if not title:
        return None
    return {"kind": "doc", "title": title, "paras": paras}


def _table_preview(text: str) -> Optional[Mapping[str, object]]:
    rows = [line for line in text.splitlines() if line.strip()][:8]
    if not rows:
        return None
    split = [row.split(",") if "," in rows[0] else row.split("|") for row in rows]
    return {"kind": "sheet", "rows": [[cell.strip() for cell in row][:8] for row in split]}


def artifact_preview_data(paths: Sequence[str]) -> list[Mapping[str, object]]:
    """Small, renderable records of what a run's files contain.

    Read once per file, capped, every failure downgraded to name-and-size.
    The records are plain data so they can live in browser state beside the
    answer they belong to.
    """

    records: list[Mapping[str, object]] = []
    for item in paths:
        path = Path(str(item))
        try:
            size = path.stat().st_size
            blob = path.read_bytes()[:_PREVIEW_READ_CAP]
        except OSError:
            continue
        suffix = path.suffix.lower()
        parsed: Optional[Mapping[str, object]] = None
        if suffix == ".docx":
            parsed = _docx_preview(blob)
        elif suffix == ".xlsx":
            parsed = _xlsx_preview(blob)
        elif suffix == ".pptx":
            parsed = _pptx_preview(blob)
        elif suffix == ".pdf":
            parsed = _pdf_preview(blob)
        elif suffix == ".csv":
            parsed = _table_preview(blob.decode("utf-8", "replace"))
        elif suffix == ".md":
            parsed = _markdown_preview(blob.decode("utf-8", "replace"))
        elif suffix in _TEXT_SUFFIXES:
            text = blob.decode("utf-8", "replace").strip()
            parsed = {"kind": "text", "text": text[:1400]} if text else None
        record: dict[str, object] = {
            "name": path.name,
            "size": size,
            "kind": "file",
        }
        if parsed:
            record.update(parsed)
        records.append(record)
    return records


def _human_size(size: object) -> str:
    try:
        value = float(size)  # type: ignore[arg-type]
    except (TypeError, ValueError):
        return ""
    if value >= 1_048_576:
        return f"{value / 1_048_576:.1f} MB"
    if value >= 1024:
        return f"{value / 1024:.1f} KB"
    return f"{int(value)} B"


_MADE_KIND_WORDS = {
    "doc": "document",
    "sheet": "spreadsheet",
    "deck": "slides",
    "text": "text",
    "file": "file",
}


def made_things(previews: Sequence[Mapping[str, object]]) -> str:
    """The artifacts a run produced, rendered as the things they are.

    Structure is fixed here; every string is escaped; a record whose kind is
    unknown renders as a plain file card. The block sits inside the answer,
    after the model's words, because "make me a PDF" is answered by the PDF.
    """

    if not previews:
        return ""
    cards: list[str] = []
    for record in previews:
        if not isinstance(record, Mapping):
            continue
        name = esc(str(record.get("name") or "file"))
        kind = str(record.get("kind") or "file")
        kind_word = _MADE_KIND_WORDS.get(kind, "file")
        body = ""
        if kind == "doc":
            title = esc(str(record.get("title") or ""))
            paras = "".join(
                f"<p>{esc(str(para))}</p>" for para in record.get("paras") or ()
                if isinstance(para, str)
            )
            body = f"<h4>{title}</h4>{paras}" if title else paras
        elif kind == "sheet":
            rows = [row for row in record.get("rows") or () if isinstance(row, (list, tuple))]
            if rows:
                head = "".join(f"<th>{esc(str(cell))}</th>" for cell in rows[0])
                rest = "".join(
                    "<tr>" + "".join(f"<td>{esc(str(cell))}</td>" for cell in row) + "</tr>"
                    for row in rows[1:]
                )
                body = f'<table class="c-madegrid"><tr>{head}</tr>{rest}</table>'
        elif kind == "deck":
            slides = [
                slide for slide in record.get("slides") or () if isinstance(slide, Mapping)
            ]
            body = '<div class="c-madeslides">' + "".join(
                f'<div class="c-madeslide"><span class="c-madeslide__no">{index}</span>'
                f'<span class="c-madeslide__title">{esc(str(slide.get("title") or ""))}</span>'
                '<span class="c-madeslide__line"></span><span class="c-madeslide__line"></span>'
                "</div>"
                for index, slide in enumerate(slides, start=1)
            ) + "</div>"
        elif kind == "text":
            body = f"<pre>{esc(str(record.get('text') or ''))}</pre>"
        note = str(record.get("note") or "")
        size = _human_size(record.get("size"))
        foot_parts = [part for part in (note, size, "in Outputs, left panel") if part]
        modifier = kind if kind in ("doc", "sheet", "deck", "text") else "file"
        cards.append(
            f'<figure class="c-madecard c-made--{modifier}">'
            '<div class="c-madecard__bar">'
            '<span class="c-madecard__dot" aria-hidden="true"></span>'
            f'<span class="c-madecard__name">{name}</span>'
            f'<span class="c-madecard__kind">{esc(kind_word)}</span>'
            "</div>"
            + (f'<div class="c-madecard__body">{body}</div>' if body else "")
            + f'<figcaption class="c-made__foot">{esc(" · ".join(foot_parts))}</figcaption>'
            "</figure>"
        )
    if not cards:
        return ""
    return '<div class="c-made">' + "".join(cards) + "</div>"


def run_steps(steps: Sequence[Mapping[str, object]]) -> str:
    """The agent's turns, in order, kept after the run that took them.

    Until this existed a completed turn could show what the model finally
    said and nothing about how it got there: which tools it considered, what
    it passed them, what came back. That is most of the work, and on a small
    model it is most of what there is to judge.

    Every field is worker-supplied and escaped. The mark and the lead word
    come from the table above rather than from the wire.
    """

    if not steps:
        return ""
    items: list[str] = []
    for step in steps:
        if not isinstance(step, Mapping):
            continue
        kind = str(step.get("kind") or "phase")
        mark, lead = STEP_LEAD.get(kind, STEP_LEAD["phase"])
        tone = "work"
        if kind == "tool-result":
            tone = "ok" if step.get("ok") else "warn"
        elif kind == "tool-call":
            tone = "work"
        tool = str(step.get("tool") or "")
        tool_html = f'<code class="c-step__tool">{esc(tool)}</code>' if tool else ""
        lead_html = f'<span class="c-step__lead">{esc(lead)}</span>' if lead else ""
        items.append(
            f'<li class="c-step c-step--{tone}">'
            f'<span class="c-step__mark" aria-hidden="true">{esc(mark)}</span>'
            f'<span class="c-step__body">{lead_html}{tool_html}'
            f'<span class="c-step__text">{esc(str(step.get("text") or ""))}</span>'
            "</span></li>"
        )
    if not items:
        return ""
    return (
        '<details class="c-turns"><summary class="c-turns__toggle">'
        f"What the agent did ({len(items)} steps)</summary>"
        f'<ol class="c-steps">{"".join(items)}</ol></details>'
    )


#: The wash each conversation state gets in the rail. Faint on purpose: see
#: the note in presentation.py. A state with no entry gets no wash, which is
#: how "idle" and "completed" are drawn. Warm washes over the ivory canvas:
#: the only saturated green on the page stays the accent.
CONVERSATION_TONES = {
    "waiting": "#F7F1E1",
    "generating": "#E8F0EA",
    "failed": "#F7ECE7",
}


def conversation_tones(states: Mapping[str, str]) -> str:
    """A style block colouring each conversation row by its state.

    The list is a radio group, and a radio group gives no per-option hook to
    hang a class on, so the rule is written against the option's own value.
    That value is a conversation id this server generated, but it is read back
    out of browser state before it arrives here, so it is filtered to the
    characters an id can contain rather than trusted. An id that does not
    match is dropped: a missing wash is a cosmetic loss, and a selector built
    from unfiltered text is a way into the stylesheet.
    """

    rules: list[str] = []
    for conversation_id, state in (states or {}).items():
        tone = CONVERSATION_TONES.get(str(state))
        if not tone:
            continue
        safe = "".join(
            character
            for character in str(conversation_id)
            if character.isalnum() or character in "-_"
        )[:80]
        if not safe or safe != str(conversation_id):
            continue
        rules.append(
            f'.c-convlist label:has(input[value="{safe}"]):not(:has(input:checked))'
            f"{{background:{tone} !important;}}"
        )
    if not rules:
        return '<div class="c-railtones"></div>'
    return f'<div class="c-railtones"><style>{"".join(rules)}</style></div>'


def run_outcomes(entries: Sequence[Mapping[str, object]]) -> str:
    """Runs that ended without an answer, reported beside the conversation.

    These used to be bubbles in the transcript, which put the server's own
    account of a failure in the same place and the same shape as something a
    person said. A failure is still a fact the user has to see, so it is here,
    attached to the turn it belongs to, and it is the only thing in this file
    that reports an absence of an answer.
    """

    if not entries:
        return ""
    rows = "".join(
        f'<li class="c-outcome"><span class="c-outcome__turn">Run '
        f'{esc(str(entry.get("turn", "")))}</span>'
        f'<span class="c-outcome__text">{esc(str(entry.get("text", "")))}</span></li>'
        for entry in entries
        if isinstance(entry, Mapping)
    )
    return f'<ul class="c-outcomes">{rows}</ul>' if rows else ""


def run_receipts(entries: Sequence[Mapping[str, object]]) -> str:
    """What each answer cost and did, beside the conversation.

    This is the material that used to be appended to the model's own reply.
    Moving it out was not a matter of tidiness: a receipt inside the bubble is
    read as part of the answer, and the answer is the only thing in the
    transcript the user asked for.

    Every field here is worker-supplied and escaped, and the structure is
    fixed in this function rather than taken from the wire.
    """

    if not entries:
        return ""
    rows: list[str] = []
    previous_worker = ""
    for entry in entries:
        chips: list[str] = []
        cost = str(entry.get("cost") or "")
        means = str(entry.get("means") or "")
        calls = entry.get("calls")
        files = entry.get("files")
        if calls:
            chips.append(chip(f"{calls} tool call" + ("s" if int(calls) != 1 else "")))
        if files:
            chips.append(chip(f"{files} file" + ("s" if int(files) != 1 else ""), tone="ok"))
        worker = str(entry.get("worker") or "")
        # Named when it changes, and then not again. A session usually runs on
        # one worker, so repeating its name on every receipt added a line of
        # chrome per answer that carried no information after the first. A
        # change of worker between two answers is worth seeing, and it is the
        # only time this appears.
        if worker and worker != previous_worker:
            chips.append(chip(worker, tone="outline"))
        if worker:
            previous_worker = worker
        trace_items = entry.get("trace")
        trace_html = ""
        if isinstance(trace_items, (list, tuple)) and trace_items:
            steps = "".join(
                f'<li class="c-step c-step--{"ok" if item.get("ok") else "warn"}">'
                f'<span class="c-step__mark" aria-hidden="true">'
                f'{"●" if item.get("ok") else "■"}</span>'
                f'<span class="c-step__body">'
                f'<code class="c-step__tool">{esc(item.get("tool", "?"))}</code>'
                f'<span class="c-step__text">{esc(item.get("summary", ""))}</span>'
                "</span></li>"
                for item in trace_items
                if isinstance(item, Mapping)
            )
            trace_html = f'<ol class="c-steps">{steps}</ol>'
        # The trace above is the summary: which tools ran and what they made.
        # This is the whole sequence, including the model's own turns, folded
        # away because most of the time the summary is enough and the run is
        # over.
        turns_html = run_steps(entry.get("steps") or ())
        rows.append(
            '<article class="c-receipt">'
            f'<p class="c-receipt__head">'
            f'<span class="c-receipt__turn">{esc(entry.get("turn", ""))}</span>'
            f'<span class="c-receipt__cost">{esc(cost)}</span>'
            f'<span class="c-receipt__means">{esc(means)}</span></p>'
            # Omitted rather than emitted empty: an empty chip row still
            # reserves its height, so every receipt carried a blank strip.
            + (f'<div class="c-chips">{"".join(chips)}</div>' if chips else "")
            + f"{trace_html}{turns_html}"
            "</article>"
        )
    return (
        '<section class="c-receipts">'
        '<p class="c-eyebrow">What each answer cost'
        '<span class="c-eyebrow__note">use phase, self-reported</span></p>'
        f'{"".join(rows)}'
        "</section>"
    )


def energy_panel(
    *,
    title: str,
    boundary: str,
    lines: Iterable[str],
    caveat: str,
    never_sum: str,
    measured: bool,
    sections: Iterable[Mapping[str, object]] = (),
    headlines: Iterable[Mapping[str, object]] = (),
    small_print: str = "",
) -> str:
    """One rubric boundary, on its own surface, that never sums with the other.

    ``measured=False`` renders an em dash beside the value. An absence must be
    visually as well as semantically distinct from a measured zero — a tidy
    ``0`` in the same slot is exactly the misreading the rubric forbids.
    """

    headlines = list(headlines)
    if headlines:
        # The readable form: figure, then what it equates to, then the
        # measurement detail one disclosure away. The boundary line and the
        # never-added line stay on the face of the panel, because those are
        # claims about what the number *is* rather than notes about how it was
        # taken, and a reader who misses them misreads the figure itself.
        body = "".join(
            energy_headline(
                item.get("joules"),
                label=str(item.get("label") or ""),
                detail=str(item.get("detail") or ""),
                absent_reason=str(item.get("absent_reason") or ""),
            )
            for item in headlines
        )
        more = (
            '<details class="c-usage__more">'
            '<summary class="c-usage__moretoggle">How this is measured</summary>'
            f'<div class="c-usage__morebody">{esc(small_print)}</div>'
            "</details>"
            if small_print
            else ""
        )
        boundary_html = (
            f'<span class="c-panel__boundary">{esc(boundary)}</span>' if boundary else ""
        )
        never_html = (
            f'<p class="c-panel__never">{esc(never_sum)}</p>' if never_sum else ""
        )
        return (
            '<section class="c-panel">'
            f'<p class="c-panel__head"><span class="c-panel__title">{esc(title)}</span>'
            f"{boundary_html}</p>"
            f'<div class="c-usage-panel">{body}</div>'
            f"{more}{never_html}"
            "</section>"
        )

    sections = list(sections)
    if sections:
        # Several scopes of the same boundary, stacked. Each keeps its own
        # headline value and its own absence marker, because "no data for this
        # conversation" and "no data for this worker" are different facts and
        # one must never stand in for the other.
        blocks: list[str] = []
        for section in sections:
            heading = str(section.get("heading") or "")
            block = _energy_block(
                [str(line) for line in section.get("lines") or ()],
                measured=bool(section.get("measured")),
                quiet=bool(section.get("quiet")),
            )
            head = (
                f'<p class="c-energy__scope">{esc(heading)}</p>' if heading else ""
            )
            blocks.append(head + block)
        return (
            '<section class="c-panel">'
            f'<p class="c-panel__head"><span class="c-panel__title">{esc(title)}</span>'
            f'<span class="c-panel__boundary">{esc(boundary)}</span></p>'
            f'<div class="c-energy">{"".join(blocks)}</div>'
            f'<p class="c-panel__caveat">{esc(caveat)}</p>'
            f'<p class="c-panel__never">{esc(never_sum)}</p>'
            "</section>"
        )

    body: list[str] = []
    for index, line in enumerate(lines):
        if index == 0:
            # The dash marks an *absent measurement*. With no runs at all there
            # is nothing to have measured, so marking it would overstate the
            # case and the line is ordinary copy rather than a headline value.
            nothing_yet = "no completed runs" in line.casefold()
            if nothing_yet:
                body.append(f'<p class="c-energy__line">{esc(line)}</p>')
                continue
            mark = ABSENCE_MARK if not measured else ""
            state = " c-energy__value--absent" if not measured else ""
            body.append(f'<p class="c-energy__value{state}">{mark}{esc(line)}</p>')
        else:
            body.append(f'<p class="c-energy__line">{esc(line)}</p>')

    return (
        '<section class="c-panel">'
        f'<p class="c-panel__head"><span class="c-panel__title">{esc(title)}</span>'
        f'<span class="c-panel__boundary">{esc(boundary)}</span></p>'
        f'<div class="c-energy">{"".join(body)}</div>'
        f'<p class="c-panel__caveat">{esc(caveat)}</p>'
        f'<p class="c-panel__never">{esc(never_sum)}</p>'
        "</section>"
    )


def _energy_block(lines: Sequence[str], *, measured: bool, quiet: bool = False) -> str:
    """One scope's figure and its supporting lines.

    ``quiet`` renders every line in the supporting weight and adds no absence
    marker. It exists for the worker-lifetime block, which is real context but
    is not this session's headline number: it is self-reported by a machine
    nobody here can check, and giving it the same visual weight as a figure
    this session watched arrive would flatten exactly the difference the panel
    is trying to preserve. The dash is reserved for a *missing* measurement,
    so it must not appear beside a figure that is present but unverifiable.
    """

    body: list[str] = []
    for index, line in enumerate(lines):
        if index == 0 and not quiet:
            nothing_yet = "no completed runs" in line.casefold()
            if nothing_yet:
                body.append(f'<p class="c-energy__line">{esc(line)}</p>')
                continue
            mark = ABSENCE_MARK if not measured else ""
            state = " c-energy__value--absent" if not measured else ""
            body.append(f'<p class="c-energy__value{state}">{mark}{esc(line)}</p>')
        else:
            body.append(f'<p class="c-energy__line">{esc(line)}</p>')
    return "".join(body)


# --------------------------------------------------------------------------
# Footer
# --------------------------------------------------------------------------



# --------------------------------------------------------------------------
# Library cards
# --------------------------------------------------------------------------



#: What a member leaves behind, keyed by reference. Drawn on its card as a
#: small picture of that kind of thing.
#:
#: A RECORDING WAS THE WRONG ANSWER TO "WHAT IS THIS FOR".
#:
#: These cards used to carry a filmed demonstration: a GIF per member, with a
#: still of the run's own output underneath, 2.5 MB of them in the repository.
#: They were honest — every frame was a real run — and they answered the wrong
#: question. Somebody scanning a shelf of thirty members wants to know what
#: each one *makes*, at a glance, before deciding which to look at closely; a
#: recording only answers that after it has played, one card at a time, and
#: not at all on a phone where nothing hovers.
#:
#: A drawing of the artifact answers it instantly and identically for every
#: member, weighs a few hundred bytes, needs no recording pass to stay true
#: when a skill changes, and reads the same on any screen.
_MEMBER_OUTPUT: Mapping[str, str] = {
    "create_pdf": "page",
    "create_docx": "page",
    "create_deck": "deck",
    "create_xlsx": "sheet",
    "csv_table": "sheet",
    "frontend_design": "web",
    "theme_factory": "palette",
    "make_plan": "list",
    "todo": "list",
    "review_checklist": "list",
    "skill_scaffold": "note",
    "status_update": "note",
    "scratchpad": "note",
    "search_document": "find",
    "extract_from_text": "find",
    "verify_quote": "find",
    "calculate": "value",
    "calculate_date": "value",
    "convert_units": "value",
}

#: Everything driven by ``tailored_guide`` writes a guide in markdown, so it
#: is one shape rather than nineteen near-identical entries above.
_GUIDE_OUTPUT = "guide"

#: The palette the drawings use. Deliberately the page's own tokens rather
#: than fixed colours, so a card follows the theme instead of sitting in a
#: little rectangle of last year's palette.
_MOCK_INK = "var(--c-text-2)"
_MOCK_FAINT = "var(--c-hairline)"
_MOCK_ACCENT = "var(--c-accent)"


def _member_output(ref: str) -> str:
    """Which drawing belongs to a member, from its reference."""

    tool_id = ref.split("@", 1)[0]
    if tool_id in _MEMBER_OUTPUT:
        return _MEMBER_OUTPUT[tool_id]
    try:
        from distinct_tools.skills import REPOSITORY_SKILLS

        for skill in REPOSITORY_SKILLS:
            if skill.tool_id == tool_id and getattr(skill, "handler", "") == "tailored_guide":
                return _GUIDE_OUTPUT
    except Exception:
        pass
    return "generic"


def _rows(count: int, *, x: float, y: float, gap: float, width: float, short: int = -1) -> str:
    """Horizontal rules standing in for lines of text.

    ``short`` is the index drawn at two thirds width. A block of identical
    full-width bars reads as a barcode; one ragged edge is what makes it read
    as writing.
    """

    bars = []
    for index in range(count):
        length = width * (0.62 if index == short else 1.0)
        bars.append(
            f'<rect x="{x}" y="{y + index * gap:.1f}" width="{length:.1f}" height="2" '
            f'rx="1" fill="{_MOCK_FAINT}"/>'
        )
    return "".join(bars)


def _mockup(kind: str) -> str:
    """One small drawing of the thing a member produces.

    Inline SVG rather than an image file: it is a few hundred bytes, it is
    sharp at any size, it takes the page's colours, and there is no asset to
    fetch, cache, forget to ship, or let drift out of date.
    """

    body = {
        # A page of writing: a heading, a rule, and paragraphs.
        "page": (
            f'<rect x="34" y="8" width="92" height="88" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<rect x="44" y="20" width="46" height="5" rx="2" fill="{_MOCK_ACCENT}"/>'
            + _rows(6, x=44, y=34, gap=9, width=72, short=5)
        ),
        # Slides: one wide frame, two beneath it.
        "deck": (
            f'<rect x="26" y="10" width="108" height="46" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<rect x="36" y="22" width="42" height="5" rx="2" fill="{_MOCK_ACCENT}"/>'
            f'<rect x="36" y="34" width="66" height="2" rx="1" fill="{_MOCK_FAINT}"/>'
            f'<rect x="26" y="62" width="50" height="32" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<rect x="84" y="62" width="50" height="32" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
        ),
        # A grid, with the header row filled in.
        "sheet": (
            f'<rect x="26" y="16" width="108" height="72" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<rect x="26" y="16" width="108" height="16" fill="var(--c-fill)"/>'
            + "".join(
                f'<line x1="{26 + column * 36}" y1="16" x2="{26 + column * 36}" y2="88" '
                f'stroke="{_MOCK_FAINT}"/>'
                for column in (1, 2)
            )
            + "".join(
                f'<line x1="26" y1="{16 + row * 18}" x2="134" y2="{16 + row * 18}" '
                f'stroke="{_MOCK_FAINT}"/>'
                for row in (1, 2, 3)
            )
            + "".join(
                f'<rect x="{34 + column * 36}" y="22" width="18" height="4" rx="2" '
                f'fill="{_MOCK_ACCENT}"/>'
                for column in (0, 1, 2)
            )
        ),
        # A browser: chrome, a hero block, two columns.
        "web": (
            f'<rect x="20" y="14" width="120" height="76" rx="4" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<rect x="20" y="14" width="120" height="14" rx="4" fill="var(--c-fill)"/>'
            + "".join(
                f'<circle cx="{30 + dot * 9}" cy="21" r="2.4" fill="{_MOCK_FAINT}"/>'
                for dot in range(3)
            )
            + f'<rect x="30" y="38" width="54" height="6" rx="3" fill="{_MOCK_ACCENT}"/>'
            + _rows(2, x=30, y=52, gap=8, width=80, short=1)
            + '<rect x="30" y="72" width="46" height="10" rx="3" fill="var(--c-mint)"/>'
            + '<rect x="84" y="72" width="46" height="10" rx="3" fill="var(--c-fill)"/>'
        ),
        # A set of colours, which is what a theme is.
        "palette": (
            "".join(
                f'<rect x="{24 + column * 30}" y="{22 + row * 30}" width="24" height="24" '
                f'rx="4" fill="{fill}" stroke="{_MOCK_FAINT}"/>'
                for row, fills in enumerate(
                    (
                        (_MOCK_ACCENT, "var(--c-sage)", "var(--c-mint)", "var(--c-fill)"),
                        ("var(--c-text)", "var(--c-text-2)", "var(--c-muted)", "var(--c-surface)"),
                    )
                )
                for column, fill in enumerate(fills)
            )
        ),
        # Numbered steps.
        "list": (
            f'<rect x="30" y="10" width="100" height="84" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            + "".join(
                f'<circle cx="44" cy="{26 + row * 20}" r="5" fill="none" '
                f'stroke="{_MOCK_ACCENT}" stroke-width="1.5"/>'
                f'<rect x="56" y="{24 + row * 20}" width="{58 - row * 10}" height="3" rx="1.5" '
                f'fill="{_MOCK_FAINT}"/>'
                for row in range(4)
            )
        ),
        # A short note, torn from a pad.
        "note": (
            f'<path d="M40 12 h80 v66 l-12 12 h-68 z" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<path d="M120 78 h-12 v12 z" fill="var(--c-fill)" stroke="{_MOCK_FAINT}"/>'
            f'<rect x="50" y="26" width="38" height="5" rx="2" fill="{_MOCK_ACCENT}"/>'
            + _rows(4, x=50, y=40, gap=9, width=60, short=3)
        ),
        # A guide: a heading and bulleted method.
        "guide": (
            f'<rect x="32" y="8" width="96" height="88" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<rect x="42" y="20" width="52" height="5" rx="2" fill="{_MOCK_ACCENT}"/>'
            + "".join(
                f'<circle cx="45" cy="{40 + row * 14}" r="2" fill="{_MOCK_ACCENT}"/>'
                f'<rect x="53" y="{38 + row * 14}" width="{62 - row * 8}" height="3" rx="1.5" '
                f'fill="{_MOCK_FAINT}"/>'
                for row in range(4)
            )
        ),
        # Looking through a document for something.
        "find": (
            f'<rect x="30" y="10" width="86" height="80" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            + _rows(5, x=40, y=26, gap=12, width=62, short=4)
            + '<rect x="40" y="48" width="40" height="7" rx="2" fill="var(--c-mint)"/>'
            + f'<circle cx="112" cy="70" r="17" fill="var(--c-surface)" stroke="{_MOCK_ACCENT}" '
            f'stroke-width="2.5"/>'
            + f'<line x1="124" y1="82" x2="134" y2="92" stroke="{_MOCK_ACCENT}" '
            f'stroke-width="2.5" stroke-linecap="round"/>'
        ),
        # An answer, which is what a calculation gives back.
        "value": (
            f'<rect x="34" y="24" width="92" height="52" rx="6" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>'
            f'<rect x="46" y="36" width="26" height="4" rx="2" fill="{_MOCK_FAINT}"/>'
            f'<rect x="46" y="48" width="52" height="12" rx="3" fill="{_MOCK_ACCENT}"/>'
        ),
        "generic": (
            f'<rect x="38" y="12" width="84" height="80" rx="3" fill="var(--c-surface)" '
            f'stroke="{_MOCK_FAINT}"/>' + _rows(5, x=50, y=30, gap=11, width=60, short=4)
        ),
    }.get(kind)
    if body is None:
        body = ""
    return (
        '<svg class="c-libcard__mock" viewBox="0 0 160 104" role="img" '
        'aria-hidden="true" preserveAspectRatio="xMidYMid meet">'
        f"{body}</svg>"
    )


#: Tails that belong to the model rather than the reader. A library member's
#: description is written for tool selection, so it carries the argument list
#: and the provenance note; a card is read by a person deciding whether they
#: want the thing at all.
_CARD_TAILS = re.compile(
    r"\s*(?:Arguments?|Args|Parameters?)\s*:.*|\s*Adapted from\b.*|\s*Use it (?:when|for|before)\b.*",
    re.S | re.I,
)


def card_summary(description: str, *, limit: int = 150) -> str:
    """One sentence a person can scan, from a description written for a model.

    THE CARD AND THE MODEL WANT DIFFERENT THINGS FROM THE SAME STRING.

    A member's description is the text a model reads to decide whether to call
    it, so it is complete on purpose: what the member does, every argument, the
    formats each one accepts, and where the skill came from. That is right for
    tool selection and wrong for a card. Nine hundred characters clamped to
    four lines gives a person a paragraph cut off mid-word, which tells them
    less than one clear sentence would.

    So the card gets the first sentence with the model's tail removed, and the
    description itself is left exactly as it is. Shortening the source would
    have made the page tidier by making the tool worse, which is the wrong
    trade in a project whose members are the point.
    """

    text = " ".join(str(description or "").split())
    text = _CARD_TAILS.sub("", text).strip()
    if not text:
        return ""
    # First sentence, but only where a full stop really ends one. "e.g." and
    # a version number are not sentence boundaries; a trailing capital is, so
    # "written as YYYY-MM-DD." ends the sentence it looks like it ends.
    match = re.search(r"(?<!\be\.g)(?<!\bi\.e)(?<!\d)\.(?:\s|$)", text)
    if match and match.start() >= 40:
        text = text[: match.start() + 1]
    if len(text) > limit:
        cut = text.rfind(" ", 0, limit)
        text = text[: cut if cut > 40 else limit].rstrip(" ,;:") + "…"
    return text


def library_cards(rows: Sequence[Mapping[str, object]], chosen: Sequence[str]) -> str:
    """The library as cards a person reads, not a list of tick boxes.

    Every field is escaped: descriptions come from tool specs, which workers
    and third-party repositories can influence. The demo image is attached as
    ``data-gif`` and only swapped in by the page's own hover script, so a
    card with no recording is simply a card, not a broken image.
    """

    if not rows:
        return empty_state(
            glyph="◇",
            title="Nothing matches",
            body="No library member matches that search. Clear it to see the shelf.",
        )
    picked = set(chosen)
    cards = []
    for row in rows:
        ref = str(row.get("ref", ""))
        kind = str(row.get("kind", "tool"))
        name = str(row.get("name", ref))
        author = str(row.get("author", "distinct"))
        description = str(row.get("description", ""))
        # Every card carries one, because a shelf where some members have a
        # picture and some do not reads as a shelf where some are broken.
        output = _member_output(ref)
        media = (
            f'<figure class="c-libcard__media c-libcard__media--{esc(output)}">'
            f"{_mockup(output)}"
            f'<figcaption class="c-visually-hidden">Makes a {esc(output)}</figcaption>'
            "</figure>"
        )
        on = ref in picked
        # The card itself is the control: role, state and keyboard focus are
        # its own, and the check mark is drawn by the stylesheet from the
        # ``is-on`` class so the page script and this renderer cannot
        # disagree about what selected looks like.
        cards.append(
            f'<article class="c-libcard c-libcard--{esc(kind)}{" is-on" if on else ""}"'
            f' data-ref="{esc(ref)}" role="button" tabindex="0"'
            f' aria-pressed="{"true" if on else "false"}"'
            f' aria-label="{esc(name)}: use as a default">'
            f'<span class="c-libcard__check" aria-hidden="true"></span>'
            f"{media}"
            '<div class="c-libcard__head">'
            f'<span class="c-libcard__kind c-libcard__kind--{esc(kind)}">{esc(kind)}</span>'
            "</div>"
            f'<h3 class="c-libcard__name">{esc(name)}</h3>'
            f'<p class="c-libcard__author">by {esc(author)}</p>'
            f'<p class="c-libcard__desc">{esc(card_summary(description))}</p>'
            f'<code class="c-libcard__ref">{esc(ref)}</code>'
            "</article>"
        )
    return '<div class="c-libgrid">' + "".join(cards) + "</div>"

# --------------------------------------------------------------------------
# Better Images of AI: the artwork set, and attribution that cannot drift
# --------------------------------------------------------------------------
#
# THE SET. Lone Thomasky and Bits&Baeume's collection for Better Images of AI:
# photographs of landscapes overwritten with digital distortion, made to show
# the extraction, water use and land use that sit behind AI infrastructure. It
# is the right set for this product because the product's only claim is about
# physical cost, and because the collection contains no robot, no brain, no
# wireframe head and no blue glow. The distortion is also an unusually apt
# metaphor for what this network is for: the landscape is still legible, but
# something has been done to it that you can see if you look.
#
# LICENSING, AND WHY IT IS STRUCTURAL RATHER THAN CAREFUL. CC BY 4.0 requires
# the artist, a link to the library, a link to the licence deed, and a statement
# of changes, shown every time the image is shown. Failing to attribute
# terminates the licence, and showing artwork A beside artwork B's credit would
# be exactly that failure.
#
# So image and credit are not two values that a caller keeps in step. They are
# one frozen record, and every function that renders an image takes the whole
# record and reads both fields off it. There is no signature anywhere in this
# module that accepts a source without its credit, which is why the two cannot
# separate. ``tests/test_presentation.py`` pins it for every artwork in the set.
#
# The artist and licence half of the credit is identical across the collection
# and is the form the artist's own publisher uses. The title is what makes each
# credit specific, so it is carried per record and rendered first.


@dataclass(frozen=True)
class Artwork:
    """One artwork, with everything its licence requires, in one object."""

    slug: str
    title: str
    alt: str
    #: Intrinsic dimensions of the served asset, so the browser reserves the
    #: right box before the bytes arrive and the splash never reflows.
    width: int
    height: int

    @property
    def src(self) -> str:
        return f"{_ASSET_BASE}/{self.slug}-1040.webp"

    @property
    def thumb(self) -> str:
        return f"{_ASSET_BASE}/{self.slug}-96.webp"

    @property
    def plate_available(self) -> bool:
        """Whether the splash plate file actually exists on disk."""

        return (_ASSET_DIR / f"{self.slug}-1040.webp").is_file()

    @property
    def thumb_available(self) -> bool:
        return (_ASSET_DIR / f"{self.slug}-96.webp").is_file()

    @property
    def credit_html(self) -> str:
        """The exact credit line for *this* artwork.

        Built from the record rather than stored as prose so a new artwork
        cannot be added with another artwork's credit pasted onto it.
        """

        return (
            f"{escape(self.title, quote=True)}. Lone Thomasky &amp; Bits&amp;Bäume / "
            '<a href="https://betterimagesofai.org" target="_blank" '
            'rel="noopener noreferrer">Better Images of AI</a> / '
            '<a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" '
            'rel="noopener noreferrer">CC BY 4.0</a>. Resized for display; no artistic '
            "modification."
        )


#: Served from this repository rather than hotlinked. Redistribution is
#: permitted by CC BY 4.0 with attribution, and self-hosting is what lets the
#: payload be honest: each file is resized to the box it is displayed in and
#: converted to WebP, so the landing page of a product about energy does not
#: quietly ship six full-resolution PNGs from someone else's bandwidth.
#:
#: The directory the files actually live in, resolved absolutely for the same
#: reason ``gr.set_static_paths`` is given an absolute path in ui.py: a
#: relative URL path resolves against the process working directory, which is
#: the repository root under app.py but not under pytest or scripts run from
#: elsewhere. The two halves previously disagreed (absolute registration,
#: relative URL), so the images broke whenever the server was started from any
#: other directory even with the files present.
_ASSET_DIR = (Path(__file__).resolve().parent.parent / "assets" / "artwork")
_ASSET_BASE = f"/gradio_api/file={_ASSET_DIR.as_posix()}"

ARTWORKS: tuple[Artwork, ...] = (
    Artwork(
        slug="distorted-forest-path",
        title="Distorted Forest Path",
        alt=(
            "Aerial photograph of a small hut and a concrete path through dense green "
            "forest, broken up by bands of digital distortion."
        ),
        width=1280,
        height=1806,
    ),
    Artwork(
        slug="distorted-lake-trees",
        title="Distorted Lake Trees",
        alt=(
            "Aerial photograph of a small yellow aeroplane over a lake threaded with trees "
            "and cloud, broken up by bands of digital distortion."
        ),
        width=1280,
        height=1806,
    ),
    Artwork(
        slug="distorted-fish-school",
        title="Distorted Fish School",
        alt=(
            "Underwater photograph looking up through blue water at a circling school of "
            "fish, broken up by bands of digital distortion."
        ),
        width=1280,
        height=1806,
    ),
    Artwork(
        slug="distorted-dandelions",
        title="Distorted Dandelions",
        alt=(
            "Close photograph of dandelion seed heads, broken up by bands of digital "
            "distortion."
        ),
        width=1280,
        height=1806,
    ),
    Artwork(
        slug="distorted-sand-mine",
        title="Distorted Sand Mine",
        alt=(
            "Aerial photograph of an orange sand mine with transport lorries, broken up by "
            "bands of digital distortion."
        ),
        width=1280,
        height=1806,
    ),
    Artwork(
        slug="distorted-lava-flow",
        title="Distorted Lava Flow",
        alt="Photograph of a lava flow, broken up by bands of digital distortion.",
        width=1280,
        height=1806,
    ),
)


def choose_artwork(seed: object = None) -> Artwork:
    """Pick one artwork for one page load.

    ROTATION MECHANISM, AND WHY THIS ONE. Selection happens once per load and
    the chosen image then sits still. There is no timer and no carousel.

    A timed crossfade would mean an animation running for as long as the landing
    page is open, on a product whose entire claim is that compute costs energy
    and that the cost should be stated. A landing page that burns cycles
    redrawing itself to look calm is arguing against the thing it is selling.

    Per-load selection also makes "only fetch what you show" true by
    construction rather than by configuration: exactly one artwork reaches the
    markup, so exactly one is requested. A crossfade needs at least two decoded
    before the first frame; a carousel needs all six.

    The set is still seen, just across visits rather than within one.

    Only artworks whose plate file exists on disk are rotated. A checkout that
    has not run ``scripts/fetch_artwork.py`` (or has fetched only some of the
    set) must never emit an ``<img>`` that resolves to nothing beside a credit
    for a work that is not being displayed; :func:`splash_figure` carries the
    same guard for the case where nothing at all is available.
    """

    candidates = tuple(art for art in ARTWORKS if art.plate_available) or ARTWORKS
    if seed is None:
        return _secrets.choice(candidates)
    return candidates[hash(str(seed)) % len(candidates)]


def credit(artwork: Artwork, *, compact: bool = False) -> str:
    """Thumbnail and required credit for ``artwork``, read from one record.

    When the thumbnail file is absent the credit is not rendered at all:
    publishing an attribution for a work that is not being displayed is the
    licence failure this module exists to prevent, in the other direction.
    """

    if not artwork.thumb_available:
        return ""
    modifier = " c-credit--compact" if compact else ""
    return (
        f'<figure class="c-credit{modifier}">'
        f'<img class="c-credit__img" src="{esc(artwork.thumb)}" alt="{esc(artwork.alt)}" '
        'width="96" height="60" loading="lazy" decoding="async" />'
        f'<figcaption class="c-credit__text">{artwork.credit_html}</figcaption>'
        "</figure>"
    )


def footer(artwork: Optional[Artwork] = None) -> str:
    """Attribution, anchored to the page rather than floating mid-layout."""

    return f'<footer class="c-footer">{credit(artwork or ARTWORKS[0], compact=True)}</footer>'


# --------------------------------------------------------------------------
# Splash
# --------------------------------------------------------------------------


def _safe_url(value: object) -> str:
    """Return ``value`` only if it is an ordinary https URL, else empty.

    The first URL in this module that comes from data rather than from a
    literal. :func:`esc` makes a string safe to *place* in an attribute but does
    not make its scheme safe, so ``javascript:`` would survive escaping intact.
    Validating the scheme here is what keeps that out of an ``href``, and the
    parser-based test in ``tests/test_presentation.py`` checks the result.
    """

    text = "" if value is None else str(value).strip()
    return text if text.lower().startswith("https://") else ""


# --------------------------------------------------------------------------
# Environmental rubric: grades, and the absences that are not grades
# --------------------------------------------------------------------------
#
# One rule governs this whole surface, and it is the same rule as the energy
# panel: a missing assessment is not a bad assessment. Rendering "no published
# evidence" as an F or a D would state a measured poor result where none exists,
# which is the grade-sheet equivalent of counting an unmeasured run as zero.
#
# So the not-assessed marker differs from a grade in shape, fill, case and
# wording, not in colour: a dashed lozenge carrying the words "not assessed",
# never a letter. Every grade letter, meanwhile, renders identically to every
# other. There is no green A and no red D, partly because the palette has one
# hue and partly because colour-coding the letters would imply the very reading
# the legend exists to prevent, that a high letter means a low impact.


#: The two scales. They are different questions with different answers and a
#: reader who conflates them draws exactly the wrong conclusion, so each token
#: carries its own scale name inside it rather than relying on a column header
#: that may be scrolled away, screenshotted off, or read past.
#: Method words this surface knows, and the modifier each one paints with.
#: A word outside this map is rendered verbatim but styled as unknown rather
#: than silently promoted into one of the confident states.
_METHOD_SLUGS: Mapping[str, str] = {
    "Measured": "measured",
    "Modelled": "modelled",
    "Proxy-based": "proxy",
}


def figure_token(
    *,
    value: str = "",
    unit: str = "",
    status: str = "",
    absent_word: str = "Missing",
) -> str:
    """One published figure, or one explicit absence, as one indivisible unit.

    This replaces the letter grade that used to sit here, and the replacement is
    the point rather than a restyling. A letter was this catalogue's ranking of
    somebody else's number; a figure is the number. Three rules survive from the
    grade token because they were never about letters:

    **An absence is never a low value.** Missing is a dashed lozenge carrying a
    word, differing from a figure in shape, fill and content. Nobody measured it,
    so nothing may be drawn that looks like a small result.

    **A figure never appears without its method.** "54 MWh" measured at the
    socket and "54 MWh" inferred from a chip's rated wattage are different
    claims, and a method printed as a caption beside the number can be cropped,
    skimmed, or read as belonging to the next row. One token, one border.

    **Nothing is computed here.** Value, unit and method are passed straight
    through. There is no code path that could derive one from the others.
    """

    text = str(value).strip()
    if not text:
        word = esc(str(absent_word).strip() or "Missing")
        return (
            '<span class="c-fig c-fig--none">'
            f'<span class="c-fig__word">{word}</span></span>'
        )

    method = str(status).strip()
    slug = _METHOD_SLUGS.get(method, "unknown")
    shown = f"{text} {unit}".strip() if str(unit).strip() else text
    return (
        f'<span class="c-fig c-fig--{esc(slug)}">'
        f'<span class="c-fig__value">{esc(shown)}</span>'
        f'<span class="c-fig__method">{esc(method or "method not stated")}</span>'
        "</span>"
    )


def _source_row(entry: Mapping[str, object]) -> str:
    """One area's working: what the note says, and where the number came from."""

    label = esc(str(entry.get("label") or ""))
    note = str(entry.get("note") or "")
    href = _safe_url(entry.get("source_url"))
    cite = (
        f'<a class="c-source__link" href="{esc(href)}" target="_blank" '
        'rel="noopener noreferrer">primary source</a>'
        if href
        else ""
    )
    body = esc(note) if note else "No note recorded."
    return (
        '<p class="c-source"><span class="c-source__label">'
        f"{label}</span><span class=\"c-source__body\">{body} {cite}</span></p>"
    )


def model_evidence_card(
    *,
    name: str,
    coverage: str = "",
    summary: str = "",
    covers: str = "",
    categories: Sequence[Mapping[str, object]] = (),
    links: Sequence[Mapping[str, str]] = (),
    note: str = "",
) -> str:
    """One model release, showing what was published about it and nothing else.

    Every area is always present. An area with no published result renders the
    word rather than being dropped, because a six-row card missing three rows
    reads as a model with three areas rather than a model with three unknowns.

    The workings sit behind a disclosure rather than on the face of the card:
    six notes and six citations inline would bury the figures, and hiding them
    entirely would make the figures unverifiable. Complete, one click away.
    """

    rows = "".join(
        '<p class="c-grade-row">'
        f'<span class="c-grade-row__label">{esc(str(entry.get("label") or ""))}</span>'
        + figure_token(
            value=str(entry.get("value") or ""),
            unit=str(entry.get("unit") or ""),
            status=str(entry.get("status") or ""),
        )
        + "</p>"
        for entry in categories
    )

    link_html = "".join(
        f'<a class="c-grade-card__link" href="{esc(href)}" target="_blank" '
        f'rel="noopener noreferrer">{esc(str(link.get("label") or "Source"))}</a>'
        for link in links
        if (href := _safe_url(link.get("url")))
    )

    sources = "".join(_source_row(entry) for entry in categories)
    workings = (
        "<details class=\"c-sources\"><summary class=\"c-sources__summary\">"
        "Where these figures come from</summary>"
        f'<div class="c-sources__body">{sources}</div></details>'
        if sources
        else ""
    )

    head_coverage = (
        f'<span class="c-grade-card__coverage">{esc(coverage)}</span>' if coverage else ""
    )
    return (
        '<article class="c-grade-card">'
        '<p class="c-grade-card__head">'
        f'<span class="c-grade-card__name">{esc(name)}</span>{head_coverage}</p>'
        + (f'<p class="c-grade-card__summary">{esc(summary)}</p>' if summary else "")
        + (f'<div class="c-grade-rows">{rows}</div>' if rows else "")
        + (f'<p class="c-grade-card__covers"><strong>Covers.</strong> {esc(covers)}</p>' if covers else "")
        + workings
        + (f'<p class="c-grade-card__note">{esc(note)}</p>' if note else "")
        + (f'<p class="c-grade-card__links">{link_html}</p>' if link_html else "")
        + "</article>"
    )


def assessment_legend() -> str:
    """What the words mean, and the two readings this surface has to prevent.

    The legend used to explain two letter scales and the case where they
    disagree. There are no letters now, so it explains the two things a reader
    can still get wrong: that Missing is a result, and that a small number is
    a good model.
    """

    return (
        '<section class="c-legend">'
        '<p class="c-legend__title">How to read these</p>'
        '<div class="c-legend__pair">'
        '<div class="c-legend__scale">'
        + figure_token(value="54", unit="MWh", status="Measured")
        + '<p class="c-legend__scale-body"><strong>A figure and its method.</strong> '
        "Measured means somebody metered the hardware. Modelled means they calculated "
        "it. Numbers are reproduced exactly as published: nothing is converted, scaled "
        "or combined.</p>"
        "</div>"
        '<div class="c-legend__scale">'
        + figure_token()
        + '<p class="c-legend__scale-body"><strong>Missing is not zero.</strong> '
        "Nobody published a usable result for that area. It is not a low score and not "
        "evidence the impact was small. Every area is shown for every model, so an "
        "absence is stated rather than quietly omitted.</p>"
        "</div>"
        "</div>"
        '<p class="c-legend__example"><span class="c-legend__example-mark" '
        'aria-hidden="true">?</span><span><strong>A smaller number is not automatically '
        "a better model.</strong> One OLMo 7B shows zero carbon and zero water because it "
        "trained on Finnish hydroelectric power, while still drawing more electricity "
        "than any other 7B in its cohort. Another used four times the water of its "
        "sibling purely because of where the data centre stood. Read each figure with "
        "its area, and both with where the run happened.</span></p>"
        '<p class="c-legend__never">Cradle to release. Never added to, and never compared '
        "with, the use-phase energy this network measures per run.</p>"
        "</section>"
    )


def assessment_section(
    cards: Iterable[str],
    *,
    warning: str = "",
    further_reading: Sequence[Mapping[str, str]] = (),
) -> str:
    """The legend, the assessment set's own caveat, the cards, and the reading.

    ``warning`` is the data file's caveat and renders **before** the figures, as
    its display contract requires, never in a footnote.
    """

    body = "".join(cards)
    if not body:
        body = empty_state(
            glyph="\u25cb",
            title="No assessed releases",
            body=(
                "No model in this catalogue has a published, model-specific assessment "
                "recorded here yet. Nothing is shown until one exists."
            ),
        )
    caution = f'<p class="c-rubric__warning">{esc(warning)}</p>' if warning else ""
    reading = "".join(
        f'<a class="c-reading__link" href="{esc(href)}" target="_blank" '
        f'rel="noopener noreferrer">{esc(str(item.get("label") or "Source"))}</a>'
        for item in further_reading
        if (href := _safe_url(item.get("url")))
    )
    tail = (
        '<section class="c-reading"><p class="c-reading__title">Further reading</p>'
        f'<p class="c-reading__links">{reading}</p></section>'
        if reading
        else ""
    )
    return (
        '<div class="c-rubric">'
        + assessment_legend()
        + caution
        + f'<div class="c-grade-cards">{body}</div>'
        + tail
        + "</div>"
    )


def splash_copy() -> str:
    """The landing screen's left column, above the button.

    The display line is set in a variable font at a ``clamp()`` size with
    ``font-optical-sizing:auto``, so the browser redraws the letterforms for the
    size it lands on rather than scaling one master photographically. That is
    the whole of the "dynamic type": the only motion is a single settle on load,
    dropped under ``prefers-reduced-motion``.

    Type never sits on the photograph. Six images with wildly different colour
    would make legibility a per-image gamble, so the copy stays on the canvas
    and the plate stays beside it. Constraint first, composition second.
    """

    return (
        '<div class="c-splash__head">'
        '<p class="c-splash__mark">distinct<span class="c-wordmark__dot">.</span></p>'
        '<h1 class="c-splash__display">Small models,<br />'
        '<span class="c-splash__display-2">measured energy.</span></h1>'
        '<p class="c-splash__lede">Open-weight models run on machines people lend, '
        "with the energy read off a meter on the machine that ran them.</p>"
        "</div>"
    )


def splash_meta() -> str:
    """The line under the button. Facts, not adjectives."""

    return (
        '<p class="c-splash__meta">Open weights. Session memory only. '
        "Nothing estimated.</p>"
    )


def splash_figure(artwork: Artwork) -> str:
    """The plate and its credit, both read off one record.

    ``loading="eager"`` because this is the only image on the page and it is
    above the fold; deferring it would trade bytes for a visible gap. Intrinsic
    dimensions are declared so the box is reserved before the bytes land and the
    display line never reflows under the reader.
    """

    if not artwork.plate_available:
        # No file, no <img>, and critically no credit: a page must never carry
        # an attribution for an image it is not showing. The plate becomes a
        # plain coloured surface and the checkout instruction takes its place.
        return (
            '<figure class="c-splash__figure c-splash__figure--empty">'
            '<figcaption class="c-splash__credit">Artwork not fetched on this '
            "checkout. Run scripts/fetch_artwork.py to add the Better Images of AI "
            "collection.</figcaption>"
            "</figure>"
        )
    return (
        '<figure class="c-splash__figure">'
        f'<img class="c-splash__img" src="{esc(artwork.src)}" alt="{esc(artwork.alt)}" '
        f'width="{esc(artwork.width)}" height="{esc(artwork.height)}" '
        'loading="eager" decoding="async" />'
        f'<figcaption class="c-splash__credit">{artwork.credit_html}</figcaption>'
        "</figure>"
    )