akari-video 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/src/migrate-command.mjs +11 -2
  3. package/vendor/.akari-capability-sources.json +3 -0
  4. package/vendor/docs/contract-2026-07-25-project-structure-v0.md +17 -0
  5. package/vendor/docs/contract-2026-08-02-preview-parity.md +39 -0
  6. package/vendor/packages/akari-launcher/package.json +1 -1
  7. package/vendor/packages/analysis-report/package.json +4 -8
  8. package/vendor/packages/analysis-report/render-analysis-report.mjs +544 -0
  9. package/vendor/packages/analysis-report/template.html +2023 -0
  10. package/vendor/packages/edit-lint/src/edit-lint.mjs +20 -0
  11. package/vendor/packages/edit-store/lib/caption-display.d.ts +10 -0
  12. package/vendor/packages/edit-store/lib/caption-display.js +57 -0
  13. package/vendor/packages/edit-store/lib/index.d.ts +1 -0
  14. package/vendor/packages/edit-store/lib/index.js +1 -0
  15. package/vendor/packages/edit-store/lib/internal-model.js +20 -8
  16. package/vendor/packages/edit-store/lib/migrate/index.d.ts +2 -0
  17. package/vendor/packages/edit-store/lib/migrate/index.js +32 -15
  18. package/vendor/packages/edit-store/lib/transition-visual.d.ts +23 -0
  19. package/vendor/packages/edit-store/lib/transition-visual.js +141 -0
  20. package/vendor/packages/edit-store/lib/webview-kernel.d.ts +2 -0
  21. package/vendor/packages/edit-store/lib/webview-kernel.js +139 -0
  22. package/vendor/packages/frame-engine/README.ja.md +24 -0
  23. package/vendor/packages/frame-engine/README.md +24 -0
  24. package/vendor/packages/frame-engine/package.json +27 -0
  25. package/vendor/packages/preview-engine/README.md +38 -0
  26. package/vendor/packages/preview-server/package.json +1 -1
@@ -0,0 +1,2023 @@
1
+ <!doctype html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta
7
+ http-equiv="Content-Security-Policy"
8
+ content="default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'none'; font-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'"
9
+ />
10
+ <title>AKARI Video 分析レポート</title>
11
+ <!--
12
+ AKARI 分析レポート 安全な差し替え規約:
13
+ - このファイルは UI 正本(不変)。データは一切含まない。
14
+ - データは #akari-analysis-report-data の script[type="application/json"] ブロックへ
15
+ render-analysis-report.mjs が埋め込む(マークアップへの直接焼き込みは禁止)。
16
+ - 各ブロックの data-block-id は #akari-analysis-report-blocks の別 script ブロック
17
+ (render-analysis-report.mjs が導出する blocks マニフェスト)を参照して付与する。
18
+ data-block-id の値を DOM 構築ロジック側で新規に作り出さない(SSOT は CLI 側)。
19
+ - 埋め込まれた値は必ず textContent / createElement で DOM に反映し、innerHTML へ
20
+ 未検証の文字列を流し込まない。
21
+ - 読み取り専用レポートであり、決定 UI(選択肢・ツマミ・確定ボタン)は一切持たない。
22
+ - 外部オリジンへは一切接続しない(fetch / XHR / WebSocket なし)。
23
+ -->
24
+ <style>
25
+ :root {
26
+ color-scheme: light dark;
27
+ --page: #f4f6fb;
28
+ --surface: #ffffff;
29
+ --surface-muted: #eef2fa;
30
+ --ink: #172033;
31
+ --muted: #58647a;
32
+ --line: #ccd4e3;
33
+ --accent: #304ffe;
34
+ --accent-dark: #1633c8;
35
+ --accent-soft: #eef1ff;
36
+ --fact: #126a3a;
37
+ --fact-bg: #e3f5ea;
38
+ --interp: #6a2fb0;
39
+ --interp-bg: #f1e6ff;
40
+ --warn: #7a4c00;
41
+ --warn-bg: #fff1c8;
42
+ --empty: #3a4a63;
43
+ --empty-bg: #eef2fa;
44
+ --shadow: 0 14px 38px rgba(25, 38, 74, 0.09);
45
+ --radius: 18px;
46
+ --space-1: 0.35rem;
47
+ --space-2: 0.65rem;
48
+ --space-3: 1rem;
49
+ --space-4: 1.5rem;
50
+ --space-5: 2.25rem;
51
+ }
52
+
53
+ @media (prefers-color-scheme: dark) {
54
+ :root {
55
+ --page: #0e1424;
56
+ --surface: #161e34;
57
+ --surface-muted: #1d2740;
58
+ --ink: #e7ecfb;
59
+ --muted: #9aa7c7;
60
+ --line: #2c3859;
61
+ --accent: #7c93ff;
62
+ --accent-dark: #a9b8ff;
63
+ --accent-soft: #202b52;
64
+ --fact: #6fe0a6;
65
+ --fact-bg: #10321f;
66
+ --interp: #d3aaff;
67
+ --interp-bg: #2c1c48;
68
+ --warn: #ffcf70;
69
+ --warn-bg: #3a2c05;
70
+ --empty: #b7c2de;
71
+ --empty-bg: #1d2740;
72
+ --shadow: 0 14px 38px rgba(0, 0, 0, 0.4);
73
+ }
74
+ }
75
+
76
+ * {
77
+ box-sizing: border-box;
78
+ }
79
+
80
+ html {
81
+ background: var(--page);
82
+ color: var(--ink);
83
+ font-family:
84
+ -apple-system, BlinkMacSystemFont, "Hiragino Sans", "Yu Gothic UI",
85
+ "Yu Gothic", Meiryo, sans-serif;
86
+ line-height: 1.65;
87
+ scroll-behavior: smooth;
88
+ }
89
+
90
+ body {
91
+ margin: 0;
92
+ min-width: 18rem;
93
+ }
94
+
95
+ a {
96
+ color: var(--accent-dark);
97
+ text-underline-offset: 0.16em;
98
+ }
99
+
100
+ a:hover {
101
+ text-decoration-thickness: 0.16em;
102
+ }
103
+
104
+ a:focus-visible,
105
+ [tabindex]:focus-visible,
106
+ summary:focus-visible {
107
+ outline: 3px solid #ffb000;
108
+ outline-offset: 3px;
109
+ border-radius: 4px;
110
+ }
111
+
112
+ img {
113
+ display: block;
114
+ max-width: 100%;
115
+ height: auto;
116
+ }
117
+
118
+ .skip-link {
119
+ position: fixed;
120
+ z-index: 100;
121
+ top: 0.5rem;
122
+ left: 0.5rem;
123
+ padding: 0.65rem 0.9rem;
124
+ border-radius: 8px;
125
+ background: var(--surface);
126
+ color: var(--ink);
127
+ transform: translateY(-160%);
128
+ }
129
+
130
+ .skip-link:focus {
131
+ transform: translateY(0);
132
+ }
133
+
134
+ .report-header {
135
+ color: #ffffff;
136
+ background:
137
+ radial-gradient(
138
+ circle at 85% 20%,
139
+ rgba(114, 137, 255, 0.55),
140
+ transparent 28rem
141
+ ),
142
+ linear-gradient(135deg, #101833, #1f2f66);
143
+ }
144
+
145
+ .report-header__inner,
146
+ .report-nav__inner,
147
+ main,
148
+ .report-footer {
149
+ width: min(76rem, calc(100% - 2rem));
150
+ margin-inline: auto;
151
+ }
152
+
153
+ .report-header__inner {
154
+ padding: clamp(2.2rem, 6vw, 4.4rem) 0 clamp(1.8rem, 4vw, 3rem);
155
+ }
156
+
157
+ .eyebrow {
158
+ margin: 0 0 var(--space-2);
159
+ color: #cbd5ff;
160
+ font-size: 0.78rem;
161
+ font-weight: 800;
162
+ letter-spacing: 0.16em;
163
+ text-transform: uppercase;
164
+ }
165
+
166
+ h1,
167
+ h2,
168
+ h3 {
169
+ line-height: 1.25;
170
+ text-wrap: balance;
171
+ }
172
+
173
+ h1 {
174
+ max-width: 34ch;
175
+ margin: 0;
176
+ font-size: clamp(1.7rem, 4.6vw, 3rem);
177
+ letter-spacing: -0.03em;
178
+ overflow-wrap: anywhere;
179
+ }
180
+
181
+ h2 {
182
+ margin: 0 0 var(--space-3);
183
+ font-size: clamp(1.4rem, 2.6vw, 1.9rem);
184
+ letter-spacing: -0.02em;
185
+ }
186
+
187
+ h3 {
188
+ margin: 0 0 var(--space-2);
189
+ font-size: 1.05rem;
190
+ }
191
+
192
+ p,
193
+ ul,
194
+ ol,
195
+ dl,
196
+ blockquote {
197
+ margin-top: 0;
198
+ }
199
+
200
+ .report-meta {
201
+ display: grid;
202
+ grid-template-columns: repeat(4, minmax(0, 1fr));
203
+ gap: var(--space-3);
204
+ margin: var(--space-4) 0 0;
205
+ }
206
+
207
+ .report-meta > div {
208
+ padding: var(--space-3);
209
+ border: 1px solid rgba(255, 255, 255, 0.24);
210
+ border-radius: 12px;
211
+ background: rgba(255, 255, 255, 0.08);
212
+ }
213
+
214
+ dt {
215
+ color: var(--muted);
216
+ font-size: 0.8rem;
217
+ font-weight: 800;
218
+ }
219
+
220
+ .report-meta dt {
221
+ color: #dbe2ff;
222
+ }
223
+
224
+ dd {
225
+ margin: 0.15rem 0 0;
226
+ overflow-wrap: anywhere;
227
+ }
228
+
229
+ .report-nav {
230
+ position: sticky;
231
+ top: 0;
232
+ z-index: 20;
233
+ border-bottom: 1px solid var(--line);
234
+ background: var(--surface);
235
+ }
236
+
237
+ .report-nav__inner {
238
+ display: flex;
239
+ flex-wrap: wrap;
240
+ gap: 0.35rem 0.15rem;
241
+ padding: 0.6rem 0;
242
+ }
243
+
244
+ .report-nav a {
245
+ display: inline-block;
246
+ padding: 0.35rem 0.6rem;
247
+ border-radius: 999px;
248
+ color: var(--muted);
249
+ font-size: 0.82rem;
250
+ font-weight: 700;
251
+ text-decoration: none;
252
+ white-space: nowrap;
253
+ }
254
+
255
+ .report-nav a:hover {
256
+ color: var(--accent-dark);
257
+ background: var(--accent-soft);
258
+ }
259
+
260
+ main {
261
+ padding: var(--space-5) 0 4.5rem;
262
+ }
263
+
264
+ .report-section {
265
+ margin-top: var(--space-5);
266
+ padding: clamp(1.1rem, 3vw, 2rem);
267
+ border: 1px solid var(--line);
268
+ border-radius: var(--radius);
269
+ background: var(--surface);
270
+ box-shadow: var(--shadow);
271
+ }
272
+
273
+ .report-section:first-child {
274
+ margin-top: 0;
275
+ }
276
+
277
+ .section-kicker {
278
+ display: inline-block;
279
+ margin-bottom: var(--space-2);
280
+ color: var(--accent-dark);
281
+ font-size: 0.76rem;
282
+ font-weight: 850;
283
+ letter-spacing: 0.12em;
284
+ }
285
+
286
+ .section-lead {
287
+ max-width: 72ch;
288
+ margin-bottom: var(--space-3);
289
+ color: var(--muted);
290
+ }
291
+
292
+ .badge {
293
+ display: inline-flex;
294
+ align-items: center;
295
+ gap: 0.3rem;
296
+ padding: 0.12rem 0.55rem;
297
+ border: 1px solid currentColor;
298
+ border-radius: 999px;
299
+ font-size: 0.72rem;
300
+ font-weight: 800;
301
+ letter-spacing: 0.02em;
302
+ white-space: nowrap;
303
+ }
304
+
305
+ .badge--fact {
306
+ color: var(--fact);
307
+ background: var(--fact-bg);
308
+ }
309
+
310
+ .badge--interpretation {
311
+ color: var(--interp);
312
+ background: var(--interp-bg);
313
+ }
314
+
315
+ .badge--warn {
316
+ color: var(--warn);
317
+ background: var(--warn-bg);
318
+ }
319
+
320
+ .badge-row {
321
+ display: flex;
322
+ flex-wrap: wrap;
323
+ align-items: center;
324
+ gap: 0.4rem;
325
+ margin-bottom: 0.5rem;
326
+ }
327
+
328
+ .empty-state {
329
+ margin: var(--space-3) 0;
330
+ padding: var(--space-3);
331
+ border: 1px dashed var(--line);
332
+ border-radius: 12px;
333
+ background: var(--empty-bg);
334
+ color: var(--empty);
335
+ font-weight: 650;
336
+ }
337
+
338
+ .asset-block + .asset-block {
339
+ margin-top: var(--space-4);
340
+ padding-top: var(--space-4);
341
+ border-top: 1px solid var(--line);
342
+ }
343
+
344
+ .asset-block__title {
345
+ display: flex;
346
+ flex-wrap: wrap;
347
+ align-items: baseline;
348
+ gap: 0.5rem;
349
+ margin-bottom: 0.15rem;
350
+ }
351
+
352
+ .asset-block__source {
353
+ margin: 0 0 var(--space-3);
354
+ color: var(--muted);
355
+ font-size: 0.82rem;
356
+ overflow-wrap: anywhere;
357
+ }
358
+
359
+ /* --- section 1: timeline --- */
360
+ .timeline-bar {
361
+ position: relative;
362
+ display: flex;
363
+ overflow: hidden;
364
+ min-height: 3.4rem;
365
+ border: 1px solid var(--line);
366
+ border-radius: 10px;
367
+ }
368
+
369
+ .timeline-segment {
370
+ position: relative;
371
+ display: flex;
372
+ align-items: center;
373
+ min-width: 2.5rem;
374
+ padding: 0.4rem 0.55rem;
375
+ border-right: 1px solid var(--line);
376
+ background: var(--surface-muted);
377
+ font-size: 0.78rem;
378
+ cursor: default;
379
+ }
380
+
381
+ .timeline-segment:last-child {
382
+ border-right: 0;
383
+ }
384
+
385
+ .timeline-segment:nth-child(even) {
386
+ background: color-mix(in srgb, var(--surface-muted) 60%, transparent);
387
+ }
388
+
389
+ .timeline-segment__label {
390
+ overflow: hidden;
391
+ color: var(--ink);
392
+ font-weight: 700;
393
+ text-overflow: ellipsis;
394
+ white-space: nowrap;
395
+ }
396
+
397
+ .timeline-marker {
398
+ position: absolute;
399
+ top: 0.2rem;
400
+ display: inline-block;
401
+ width: 0.55rem;
402
+ height: 0.55rem;
403
+ border-radius: 999px;
404
+ border: 1px solid var(--surface);
405
+ transform: translateX(-50%);
406
+ }
407
+
408
+ .timeline-marker--trouble {
409
+ background: #d1495b;
410
+ }
411
+
412
+ .timeline-marker--filler {
413
+ background: #8b93a7;
414
+ }
415
+
416
+ .timeline-marker--hook {
417
+ background: #d99a1b;
418
+ }
419
+
420
+ .timeline-marker--highlight {
421
+ background: #2f9e6b;
422
+ }
423
+
424
+ .timeline-legend {
425
+ display: flex;
426
+ flex-wrap: wrap;
427
+ gap: 0.75rem;
428
+ margin-top: 0.5rem;
429
+ color: var(--muted);
430
+ font-size: 0.78rem;
431
+ }
432
+
433
+ .timeline-legend span {
434
+ display: inline-flex;
435
+ align-items: center;
436
+ gap: 0.3rem;
437
+ }
438
+
439
+ .timeline-legend i {
440
+ display: inline-block;
441
+ width: 0.6rem;
442
+ height: 0.6rem;
443
+ border-radius: 999px;
444
+ }
445
+
446
+ .keyframe-strip {
447
+ display: flex;
448
+ overflow-x: auto;
449
+ gap: 0.5rem;
450
+ margin-top: var(--space-3);
451
+ padding-bottom: 0.35rem;
452
+ }
453
+
454
+ .keyframe-chip {
455
+ flex: 0 0 auto;
456
+ width: 9.5rem;
457
+ overflow: hidden;
458
+ border: 1px solid var(--line);
459
+ border-radius: 10px;
460
+ background: var(--surface-muted);
461
+ font-size: 0.74rem;
462
+ }
463
+
464
+ .keyframe-chip img {
465
+ width: 100%;
466
+ aspect-ratio: 16 / 9;
467
+ object-fit: cover;
468
+ background: #17223b;
469
+ }
470
+
471
+ .keyframe-chip__note-only {
472
+ display: flex;
473
+ align-items: center;
474
+ min-height: 3.6rem;
475
+ padding: 0.5rem;
476
+ color: var(--muted);
477
+ }
478
+
479
+ .keyframe-chip__body {
480
+ padding: 0.4rem 0.5rem;
481
+ color: var(--muted);
482
+ }
483
+
484
+ .keyframe-chip__time {
485
+ display: block;
486
+ margin-bottom: 0.15rem;
487
+ color: var(--ink);
488
+ font-weight: 750;
489
+ }
490
+
491
+ /* --- section 2: transcript --- */
492
+ .transcript-chapter {
493
+ margin-top: var(--space-3);
494
+ padding: var(--space-3);
495
+ border: 1px solid var(--line);
496
+ border-radius: 12px;
497
+ background: var(--surface-muted);
498
+ }
499
+
500
+ .transcript-chapter summary {
501
+ display: flex;
502
+ flex-wrap: wrap;
503
+ align-items: center;
504
+ gap: 0.5rem;
505
+ color: var(--ink);
506
+ font-weight: 750;
507
+ cursor: pointer;
508
+ list-style: none;
509
+ }
510
+
511
+ .transcript-chapter summary::-webkit-details-marker {
512
+ display: none;
513
+ }
514
+
515
+ .transcript-chapter summary::before {
516
+ content: "▸";
517
+ color: var(--accent-dark);
518
+ }
519
+
520
+ .transcript-chapter[open] summary::before {
521
+ content: "▾";
522
+ }
523
+
524
+ .transcript-chapter__range {
525
+ color: var(--muted);
526
+ font-size: 0.8rem;
527
+ font-weight: 600;
528
+ }
529
+
530
+ .transcript-list {
531
+ margin: var(--space-3) 0 0;
532
+ padding: 0;
533
+ list-style: none;
534
+ }
535
+
536
+ .transcript-line {
537
+ display: grid;
538
+ grid-template-columns: 4.4rem minmax(0, 1fr);
539
+ gap: 0.6rem;
540
+ padding: 0.3rem 0;
541
+ border-top: 1px solid var(--line);
542
+ font-size: 0.9rem;
543
+ }
544
+
545
+ .transcript-line:first-child {
546
+ border-top: 0;
547
+ }
548
+
549
+ .transcript-line__time {
550
+ color: var(--muted);
551
+ font-size: 0.76rem;
552
+ font-variant-numeric: tabular-nums;
553
+ }
554
+
555
+ /* --- section 3: fact cards --- */
556
+ .fact-card {
557
+ margin-top: var(--space-3);
558
+ padding: var(--space-3);
559
+ border: 1px solid var(--line);
560
+ border-radius: 12px;
561
+ background: var(--surface-muted);
562
+ }
563
+
564
+ .fact-card h4 {
565
+ margin: 0 0 0.5rem;
566
+ font-size: 0.92rem;
567
+ }
568
+
569
+ .fact-card h5 {
570
+ margin: var(--space-3) 0 0.4rem;
571
+ font-size: 0.86rem;
572
+ color: var(--muted);
573
+ }
574
+
575
+ .fact-tally {
576
+ display: flex;
577
+ flex-wrap: wrap;
578
+ gap: 0.5rem 1rem;
579
+ margin: 0 0 var(--space-3);
580
+ color: var(--muted);
581
+ font-size: 0.85rem;
582
+ }
583
+
584
+ .event-chip {
585
+ margin-top: 0.5rem;
586
+ padding: 0.55rem 0.7rem;
587
+ border: 1px solid var(--line);
588
+ border-radius: 10px;
589
+ background: var(--surface);
590
+ font-size: 0.86rem;
591
+ }
592
+
593
+ .event-chip__meta {
594
+ display: flex;
595
+ flex-wrap: wrap;
596
+ align-items: center;
597
+ gap: 0.4rem;
598
+ margin-bottom: 0.25rem;
599
+ color: var(--muted);
600
+ font-size: 0.78rem;
601
+ }
602
+
603
+ .overlay-note {
604
+ margin-top: 0.5rem;
605
+ padding-top: 0.5rem;
606
+ border-top: 1px dashed var(--line);
607
+ }
608
+
609
+ /* --- section 4 / 5 tables --- */
610
+ .table-scroll {
611
+ max-width: 100%;
612
+ margin-top: var(--space-3);
613
+ overflow-x: auto;
614
+ border: 1px solid var(--line);
615
+ border-radius: 12px;
616
+ }
617
+
618
+ table {
619
+ width: 100%;
620
+ min-width: 36rem;
621
+ border-collapse: collapse;
622
+ background: var(--surface);
623
+ font-size: 0.88rem;
624
+ }
625
+
626
+ caption {
627
+ padding: 0.75rem 0.9rem;
628
+ color: var(--ink);
629
+ background: var(--surface-muted);
630
+ font-weight: 800;
631
+ text-align: left;
632
+ }
633
+
634
+ th,
635
+ td {
636
+ padding: 0.65rem 0.8rem;
637
+ border-top: 1px solid var(--line);
638
+ text-align: left;
639
+ vertical-align: top;
640
+ }
641
+
642
+ thead th {
643
+ color: #ffffff;
644
+ background: #25345f;
645
+ border-top: 0;
646
+ }
647
+
648
+ tbody th {
649
+ min-width: 6rem;
650
+ color: var(--ink);
651
+ background: var(--surface-muted);
652
+ }
653
+
654
+ .relation-card {
655
+ margin-top: var(--space-3);
656
+ padding: var(--space-3);
657
+ border: 1px solid var(--line);
658
+ border-radius: 12px;
659
+ background: var(--surface-muted);
660
+ }
661
+
662
+ blockquote {
663
+ margin: 0.4rem 0 0;
664
+ padding-left: var(--space-3);
665
+ border-left: 4px solid var(--accent);
666
+ color: var(--ink);
667
+ }
668
+
669
+ details > summary {
670
+ color: var(--accent-dark);
671
+ font-weight: 750;
672
+ cursor: pointer;
673
+ }
674
+
675
+ details.evidence-toggle {
676
+ margin-top: 0.5rem;
677
+ }
678
+
679
+ details.evidence-toggle > summary {
680
+ font-size: 0.82rem;
681
+ }
682
+
683
+ details.evidence-toggle[open] > summary {
684
+ margin-bottom: 0.35rem;
685
+ }
686
+
687
+ .provenance-list {
688
+ margin: 0;
689
+ padding: 0;
690
+ list-style: none;
691
+ }
692
+
693
+ .provenance-list li {
694
+ padding: 0.6rem 0;
695
+ border-top: 1px solid var(--line);
696
+ }
697
+
698
+ .provenance-list li:first-child {
699
+ border-top: 0;
700
+ }
701
+
702
+ .provenance-list dt {
703
+ color: var(--muted);
704
+ font-size: 0.78rem;
705
+ }
706
+
707
+ .provenance-list dd {
708
+ margin-top: 0.1rem;
709
+ overflow-wrap: anywhere;
710
+ }
711
+
712
+ .report-footer {
713
+ padding: 0 0 3rem;
714
+ color: var(--muted);
715
+ font-size: 0.82rem;
716
+ }
717
+
718
+ .visually-hidden {
719
+ position: absolute !important;
720
+ width: 1px !important;
721
+ height: 1px !important;
722
+ padding: 0 !important;
723
+ margin: -1px !important;
724
+ overflow: hidden !important;
725
+ clip: rect(0, 0, 0, 0) !important;
726
+ white-space: nowrap !important;
727
+ border: 0 !important;
728
+ }
729
+
730
+ .fatal-error {
731
+ margin: var(--space-4) auto;
732
+ max-width: 60rem;
733
+ padding: var(--space-3);
734
+ border: 2px solid var(--warn);
735
+ border-radius: 12px;
736
+ background: var(--warn-bg);
737
+ color: var(--warn);
738
+ font-weight: 700;
739
+ }
740
+
741
+ @media (max-width: 46rem) {
742
+ .report-header__inner,
743
+ .report-nav__inner,
744
+ main,
745
+ .report-footer {
746
+ width: min(100% - 1rem, 76rem);
747
+ }
748
+
749
+ .report-meta {
750
+ grid-template-columns: repeat(2, minmax(0, 1fr));
751
+ }
752
+
753
+ .report-section {
754
+ padding: 1rem;
755
+ border-radius: 12px;
756
+ }
757
+
758
+ .transcript-line {
759
+ grid-template-columns: 3.6rem minmax(0, 1fr);
760
+ }
761
+ }
762
+
763
+ @media (prefers-reduced-motion: reduce) {
764
+ html {
765
+ scroll-behavior: auto;
766
+ }
767
+ }
768
+
769
+ /*
770
+ * 注釈選択導線(contract-2026-07-26-doc-image-annotations §1/§4-1)。
771
+ * ペン描画は設けない — クリック選択のハイライトとピンのみ。
772
+ */
773
+ [data-block-id] {
774
+ cursor: pointer;
775
+ }
776
+
777
+ .akari-annotation-selected {
778
+ outline: 2px solid var(--accent);
779
+ outline-offset: 3px;
780
+ border-radius: 6px;
781
+ }
782
+
783
+ .akari-annotation-pin {
784
+ display: inline-block;
785
+ margin-left: 0.4em;
786
+ cursor: pointer;
787
+ font-size: 0.85em;
788
+ line-height: 1;
789
+ opacity: 0.85;
790
+ }
791
+
792
+ .akari-annotation-pin:hover {
793
+ opacity: 1;
794
+ }
795
+
796
+ .akari-annotation-pin[data-status="resolved"] {
797
+ filter: grayscale(1);
798
+ opacity: 0.55;
799
+ }
800
+
801
+ .akari-annotation-reveal-flash {
802
+ animation: akari-annotation-reveal-flash 1.6s ease-out;
803
+ }
804
+
805
+ @keyframes akari-annotation-reveal-flash {
806
+ 0%,
807
+ 30% {
808
+ background-color: var(--accent-soft);
809
+ }
810
+ 100% {
811
+ background-color: transparent;
812
+ }
813
+ }
814
+
815
+ @media (prefers-reduced-motion: reduce) {
816
+ .akari-annotation-reveal-flash {
817
+ animation: none;
818
+ }
819
+ }
820
+ </style>
821
+ </head>
822
+ <body>
823
+ <a class="skip-link" href="#main-content">本文へ移動</a>
824
+
825
+ <header class="report-header">
826
+ <div class="report-header__inner">
827
+ <p class="eyebrow">AKARI VIDEO / ANALYSIS REPORT(読み取り専用)</p>
828
+ <h1 id="report-title" data-mount="report-title">分析レポート</h1>
829
+ <dl class="report-meta" aria-label="レポート基本情報">
830
+ <div>
831
+ <dt>生成日時</dt>
832
+ <dd data-mount="meta-generated-at">—</dd>
833
+ </div>
834
+ <div>
835
+ <dt>対象素材</dt>
836
+ <dd data-mount="meta-asset-count">—</dd>
837
+ </div>
838
+ <div>
839
+ <dt>検出イベント数</dt>
840
+ <dd data-mount="meta-event-count">—</dd>
841
+ </div>
842
+ <div>
843
+ <dt>取材台帳</dt>
844
+ <dd data-mount="meta-question-count">—</dd>
845
+ </div>
846
+ </dl>
847
+ </div>
848
+ </header>
849
+
850
+ <nav class="report-nav" aria-label="章ジャンプナビ">
851
+ <div class="report-nav__inner">
852
+ <a href="#section-timeline">1. タイムライン</a>
853
+ <a href="#section-transcript">2. 文字起こし</a>
854
+ <a href="#section-facts">3. 素材別事実カード</a>
855
+ <a href="#section-relations">4. 関係・浮いた素材(解釈)</a>
856
+ <a href="#section-questions">5. 取材台帳(解釈)</a>
857
+ <a href="#section-provenance">6. 来歴</a>
858
+ </div>
859
+ </nav>
860
+
861
+ <main id="main-content">
862
+ <div id="fatal-error-mount"></div>
863
+
864
+ <section
865
+ class="report-section"
866
+ id="section-timeline"
867
+ aria-labelledby="heading-timeline"
868
+ >
869
+ <span class="section-kicker">SECTION 1</span>
870
+ <h2 id="heading-timeline">1. 素材タイムライン帯</h2>
871
+ <p class="section-lead">
872
+ 素材ごとの章・トラブル・フィラー・見どころ区間を帯として俯瞰する。事実層(analysis.json
873
+ の events / keyframes)のみで構成する。バーとキーフレームの chip は元の秒数を
874
+ <code>data-seek</code> に保持する(プレビュー連動は別タスクのスコープ)。
875
+ </p>
876
+ <div data-mount="timeline"></div>
877
+ </section>
878
+
879
+ <section
880
+ class="report-section"
881
+ id="section-transcript"
882
+ aria-labelledby="heading-transcript"
883
+ >
884
+ <span class="section-kicker">SECTION 2</span>
885
+ <h2 id="heading-transcript">2. 文字起こし</h2>
886
+ <p class="section-lead">
887
+ 章単位で折りたたんだ transcript(事実層)。各行は発話区間の開始秒を
888
+ <code>data-seek</code> として保持する。
889
+ </p>
890
+ <div data-mount="transcript"></div>
891
+ </section>
892
+
893
+ <section
894
+ class="report-section"
895
+ id="section-facts"
896
+ aria-labelledby="heading-facts"
897
+ >
898
+ <span class="section-kicker">SECTION 3</span>
899
+ <h2 id="heading-facts">3. 素材別 事実カード</h2>
900
+ <p class="section-lead">
901
+ events(trouble / filler / hook / highlight)の要約と tracks
902
+ の検出状況。各素材カードの先頭には解釈層の「素材の読み」(role /
903
+ summary、章単位の sections があれば併記)を表示する。本筋内で起きたトラブル実況は、
904
+ 対応する解釈層の <code>trouble_overlap</code> フラグをこの節で併記する
905
+ (節 5「浮いた素材」には出さない)。
906
+ </p>
907
+ <div data-mount="facts"></div>
908
+ </section>
909
+
910
+ <section
911
+ class="report-section"
912
+ id="section-relations"
913
+ aria-labelledby="heading-relations"
914
+ >
915
+ <span class="section-kicker">SECTION 4</span>
916
+ <h2 id="heading-relations">4. 解釈: 素材間関係・浮いた素材</h2>
917
+ <p class="section-lead">
918
+ 同一人物・同一場所・同一時間帯・内容の重複など、素材の読みとして客観的に特定できる
919
+ 関係(relations)と、orphan / unclear の検出結果を示す(構成の提案はここでは行わない
920
+ — 方向性はチャットで判断する)。本筋内トラブル(trouble_overlap)はここには出さない
921
+ (節 3 を参照)。
922
+ </p>
923
+ <div data-mount="relations"></div>
924
+ </section>
925
+
926
+ <section
927
+ class="report-section"
928
+ id="section-questions"
929
+ aria-labelledby="heading-questions"
930
+ >
931
+ <span class="section-kicker">SECTION 5</span>
932
+ <h2 id="heading-questions">5. 取材台帳</h2>
933
+ <p class="section-lead">
934
+ open_questions の状態表示のみ。質問行為自体はチャットで行う — このレポートは台帳。
935
+ </p>
936
+ <div data-mount="questions"></div>
937
+ </section>
938
+
939
+ <section
940
+ class="report-section"
941
+ id="section-provenance"
942
+ aria-labelledby="heading-provenance"
943
+ >
944
+ <span class="section-kicker">SECTION 6</span>
945
+ <h2 id="heading-provenance">6. 来歴</h2>
946
+ <p class="section-lead">
947
+ 2 パス目に与えた文脈の全記録(interpretation.inputs)。analysis.json
948
+ は変更していない。
949
+ </p>
950
+ <div data-mount="provenance"></div>
951
+ </section>
952
+ </main>
953
+
954
+ <footer class="report-footer">
955
+ <p>
956
+ この HTML は外部 CSS・JavaScript・画像・ネットワーク接続を一切参照しない自己完結の読み取り専用レポートです。
957
+ 決定 UI は持ちません。方向性の判断はチャットで行ってください。
958
+ </p>
959
+ </footer>
960
+
961
+ <script type="application/json" id="akari-analysis-report-data">
962
+ {"__AKARI_ANALYSIS_REPORT_DATA__": true}
963
+ </script>
964
+ <script type="application/json" id="akari-analysis-report-blocks">
965
+ {"__AKARI_ANALYSIS_REPORT_BLOCKS__": true}
966
+ </script>
967
+ <script>
968
+ (() => {
969
+ "use strict";
970
+
971
+ const FLAG_LABELS = {
972
+ orphan: "浮き(orphan)",
973
+ unclear: "不明瞭(unclear)",
974
+ trouble_overlap: "本筋内トラブル(trouble_overlap)",
975
+ };
976
+
977
+ const EVENT_LABELS = {
978
+ trouble: "トラブル",
979
+ filler: "フィラー",
980
+ hook: "見どころ",
981
+ highlight: "ハイライト",
982
+ };
983
+
984
+ // #akari-analysis-report-blocks から読み込む blocks マニフェスト(main() で設定)。
985
+ // data-block-id の値は常にここ由来 — DOM 構築側で id 文字列を新規に組み立てない。
986
+ let blocksManifest = null;
987
+
988
+ function setBlockId(node, id) {
989
+ if (id) node.setAttribute("data-block-id", id);
990
+ }
991
+
992
+ function hasOwn(object, key) {
993
+ return Object.prototype.hasOwnProperty.call(object, key);
994
+ }
995
+
996
+ function isRecord(value) {
997
+ return (
998
+ value !== null && typeof value === "object" && !Array.isArray(value)
999
+ );
1000
+ }
1001
+
1002
+ function el(tag, options, children) {
1003
+ const node = document.createElement(tag);
1004
+ if (options) {
1005
+ if (options.class) node.className = options.class;
1006
+ if (options.text !== undefined) node.textContent = options.text;
1007
+ if (options.attrs) {
1008
+ for (const [key, value] of Object.entries(options.attrs)) {
1009
+ if (value !== undefined && value !== null) {
1010
+ node.setAttribute(key, String(value));
1011
+ }
1012
+ }
1013
+ }
1014
+ if (options.html !== undefined) {
1015
+ // 固定の静的マークアップ断片のみ許可(ユーザー由来値は使わない呼び出しでのみ使用)。
1016
+ node.innerHTML = options.html;
1017
+ }
1018
+ }
1019
+ for (const child of children || []) {
1020
+ if (child) node.append(child);
1021
+ }
1022
+ return node;
1023
+ }
1024
+
1025
+ function badge(kind) {
1026
+ if (kind === "fact") {
1027
+ return el("span", { class: "badge badge--fact", text: "事実" });
1028
+ }
1029
+ return el("span", {
1030
+ class: "badge badge--interpretation",
1031
+ text: "解釈",
1032
+ });
1033
+ }
1034
+
1035
+ function emptyState(text) {
1036
+ return el("p", { class: "empty-state", text });
1037
+ }
1038
+
1039
+ function formatTime(seconds) {
1040
+ if (typeof seconds !== "number" || !Number.isFinite(seconds)) {
1041
+ return "--:--";
1042
+ }
1043
+ const total = Math.max(0, Math.round(seconds));
1044
+ const h = Math.floor(total / 3600);
1045
+ const m = Math.floor((total % 3600) / 60);
1046
+ const s = total % 60;
1047
+ const pad = (n) => String(n).padStart(2, "0");
1048
+ return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
1049
+ }
1050
+
1051
+ function formatRange(start, end) {
1052
+ return `${formatTime(start)}–${formatTime(end)}`;
1053
+ }
1054
+
1055
+ function overlaps(aStart, aEnd, bStart, bEnd) {
1056
+ return aStart < bEnd && bStart < aEnd;
1057
+ }
1058
+
1059
+ function chaptersOf(analysis) {
1060
+ const chapters = (analysis.events || [])
1061
+ .filter((event) => event.type === "chapter")
1062
+ .slice()
1063
+ .sort((a, b) => a.t - b.t);
1064
+ const duration = durationOf(analysis);
1065
+ return chapters.map((chapter, index) => {
1066
+ const next = chapters[index + 1];
1067
+ return {
1068
+ title: chapter.title,
1069
+ start: chapter.t,
1070
+ end: next ? next.t : duration,
1071
+ };
1072
+ });
1073
+ }
1074
+
1075
+ function durationOf(analysis) {
1076
+ let max = 0;
1077
+ for (const seg of analysis.transcript || []) {
1078
+ if (typeof seg.end === "number") max = Math.max(max, seg.end);
1079
+ }
1080
+ for (const kf of analysis.keyframes || []) {
1081
+ if (typeof kf.t === "number") max = Math.max(max, kf.t);
1082
+ }
1083
+ for (const event of analysis.events || []) {
1084
+ if (typeof event.end === "number") max = Math.max(max, event.end);
1085
+ if (typeof event.t === "number") max = Math.max(max, event.t);
1086
+ }
1087
+ return max;
1088
+ }
1089
+
1090
+ function renderFatalError(message) {
1091
+ const mount = document.getElementById("fatal-error-mount");
1092
+ mount.append(
1093
+ el(
1094
+ "p",
1095
+ { class: "fatal-error", attrs: { role: "alert" } },
1096
+ [],
1097
+ ),
1098
+ );
1099
+ mount.firstChild.textContent =
1100
+ "レポートデータの読み込みに失敗しました: " + message;
1101
+ }
1102
+
1103
+ function renderHeader(bundle) {
1104
+ const assets = bundle.interpretation.assets || [];
1105
+ const questions = bundle.interpretation.open_questions || [];
1106
+ const eventCount = (bundle.assets || []).reduce(
1107
+ (sum, asset) => sum + (asset.analysis.events || []).length,
1108
+ 0,
1109
+ );
1110
+
1111
+ const titleNode = document.querySelector('[data-mount="report-title"]');
1112
+ if (assets.length === 1) {
1113
+ titleNode.textContent = assets[0].ref;
1114
+ } else if (assets.length > 1) {
1115
+ titleNode.textContent = `複数素材プロジェクト(${assets.length} 件)`;
1116
+ } else {
1117
+ titleNode.textContent = "分析レポート";
1118
+ }
1119
+
1120
+ document.querySelector('[data-mount="meta-generated-at"]').textContent =
1121
+ bundle.generatedAt || "—";
1122
+ document.querySelector('[data-mount="meta-asset-count"]').textContent =
1123
+ `${assets.length} 本`;
1124
+ document.querySelector('[data-mount="meta-event-count"]').textContent =
1125
+ `${eventCount} 件`;
1126
+
1127
+ const answered = questions.filter((q) => q.status === "answered").length;
1128
+ document.querySelector('[data-mount="meta-question-count"]').textContent =
1129
+ questions.length === 0
1130
+ ? "0 件(取材事項なし)"
1131
+ : `${questions.length} 件(${answered} 件回答済み)`;
1132
+ }
1133
+
1134
+ // --- Section 1: タイムライン帯 ---
1135
+ function renderTimeline(bundle) {
1136
+ const mount = document.querySelector('[data-mount="timeline"]');
1137
+ const assets = bundle.assets || [];
1138
+
1139
+ for (const asset of assets) {
1140
+ const analysis = asset.analysis;
1141
+ const duration = durationOf(analysis);
1142
+ const chapters = chaptersOf(analysis);
1143
+
1144
+ const block = el("div", { class: "asset-block" });
1145
+ setBlockId(block, blocksManifest.byRef[asset.ref]?.timeline);
1146
+ block.append(
1147
+ el("div", { class: "badge-row" }, [
1148
+ badge("fact"),
1149
+ el("strong", { text: asset.ref }),
1150
+ ]),
1151
+ );
1152
+ block.append(
1153
+ el("p", {
1154
+ class: "asset-block__source",
1155
+ text: `source: ${analysis.source}`,
1156
+ }),
1157
+ );
1158
+
1159
+ const bar = el("div", { class: "timeline-bar" });
1160
+ if (chapters.length === 0) {
1161
+ bar.append(
1162
+ el("div", {
1163
+ class: "timeline-segment",
1164
+ text: "章情報なし",
1165
+ }),
1166
+ );
1167
+ }
1168
+ for (const chapter of chapters) {
1169
+ const span = Math.max(chapter.end - chapter.start, 0.001);
1170
+ const segment = el(
1171
+ "div",
1172
+ {
1173
+ class: "timeline-segment",
1174
+ attrs: {
1175
+ style: `flex-grow:${span}`,
1176
+ "data-seek": chapter.start,
1177
+ title: `${chapter.title}(${formatRange(chapter.start, chapter.end)})`,
1178
+ },
1179
+ },
1180
+ [
1181
+ el("span", {
1182
+ class: "timeline-segment__label",
1183
+ text: chapter.title,
1184
+ }),
1185
+ ],
1186
+ );
1187
+
1188
+ for (const event of analysis.events || []) {
1189
+ if (event.type === "chapter") continue;
1190
+ const evStart = event.start ?? event.t;
1191
+ const evEnd = event.end ?? event.t;
1192
+ if (evStart === undefined) continue;
1193
+ if (
1194
+ !overlaps(
1195
+ chapter.start,
1196
+ chapter.end,
1197
+ evStart,
1198
+ evEnd ?? evStart,
1199
+ )
1200
+ ) {
1201
+ continue;
1202
+ }
1203
+ const left =
1204
+ ((evStart - chapter.start) / span) * 100;
1205
+ const marker = el("span", {
1206
+ class: `timeline-marker timeline-marker--${event.type}`,
1207
+ attrs: {
1208
+ style: `left:${Math.min(Math.max(left, 0), 100)}%`,
1209
+ "data-seek": evStart,
1210
+ title: `${EVENT_LABELS[event.type] || event.type}(${formatTime(evStart)})`,
1211
+ },
1212
+ });
1213
+ segment.append(marker);
1214
+ }
1215
+
1216
+ bar.append(segment);
1217
+ }
1218
+ block.append(bar);
1219
+
1220
+ block.append(
1221
+ el("div", { class: "timeline-legend" }, [
1222
+ el("span", {}, [
1223
+ el("i", { attrs: { style: "background:#d1495b" } }),
1224
+ document.createTextNode("トラブル"),
1225
+ ]),
1226
+ el("span", {}, [
1227
+ el("i", { attrs: { style: "background:#8b93a7" } }),
1228
+ document.createTextNode("フィラー"),
1229
+ ]),
1230
+ el("span", {}, [
1231
+ el("i", { attrs: { style: "background:#d99a1b" } }),
1232
+ document.createTextNode("見どころ"),
1233
+ ]),
1234
+ el("span", {}, [
1235
+ el("i", { attrs: { style: "background:#2f9e6b" } }),
1236
+ document.createTextNode("ハイライト"),
1237
+ ]),
1238
+ ]),
1239
+ );
1240
+
1241
+ const keyframes = (analysis.keyframes || [])
1242
+ .slice()
1243
+ .sort((a, b) => a.t - b.t);
1244
+ if (keyframes.length > 0) {
1245
+ const strip = el("div", { class: "keyframe-strip" });
1246
+ for (const kf of keyframes) {
1247
+ const chip = el("figure", {
1248
+ class: "keyframe-chip",
1249
+ attrs: { "data-seek": kf.t },
1250
+ });
1251
+ if (kf.imageSrc) {
1252
+ chip.append(
1253
+ el("img", {
1254
+ attrs: {
1255
+ src: kf.imageSrc,
1256
+ alt: `${formatTime(kf.t)} のキーフレーム`,
1257
+ loading: "lazy",
1258
+ "data-block-id": blocksManifest.byRef[asset.ref]?.images?.[kf.path],
1259
+ },
1260
+ }),
1261
+ );
1262
+ chip.append(
1263
+ el("figcaption", { class: "keyframe-chip__body" }, [
1264
+ el("span", {
1265
+ class: "keyframe-chip__time",
1266
+ text: formatTime(kf.t),
1267
+ }),
1268
+ document.createTextNode(kf.note),
1269
+ ]),
1270
+ );
1271
+ } else {
1272
+ chip.append(
1273
+ el("figcaption", { class: "keyframe-chip__note-only" }, [
1274
+ el("span", {
1275
+ class: "keyframe-chip__time",
1276
+ text: formatTime(kf.t) + "・画像なし: ",
1277
+ }),
1278
+ document.createTextNode(kf.note),
1279
+ ]),
1280
+ );
1281
+ }
1282
+ strip.append(chip);
1283
+ }
1284
+ block.append(strip);
1285
+ }
1286
+
1287
+ mount.append(block);
1288
+ }
1289
+ }
1290
+
1291
+ // --- Section 2: 文字起こし ---
1292
+ function renderTranscript(bundle) {
1293
+ const mount = document.querySelector('[data-mount="transcript"]');
1294
+ const assets = bundle.assets || [];
1295
+
1296
+ for (const [assetIndex, asset] of assets.entries()) {
1297
+ const analysis = asset.analysis;
1298
+ const chapters = chaptersOf(analysis);
1299
+ const transcript = (analysis.transcript || [])
1300
+ .slice()
1301
+ .sort((a, b) => a.start - b.start);
1302
+
1303
+ const block = el("div", { class: "asset-block" });
1304
+ block.append(
1305
+ el("div", { class: "badge-row" }, [
1306
+ badge("fact"),
1307
+ el("strong", { text: asset.ref }),
1308
+ ]),
1309
+ );
1310
+
1311
+ if (chapters.length === 0) {
1312
+ if (transcript.length === 0) {
1313
+ block.append(emptyState("transcript が空です。"));
1314
+ } else {
1315
+ const details = buildTranscriptDetails(
1316
+ "(章情報なし)",
1317
+ 0,
1318
+ durationOf(analysis),
1319
+ transcript,
1320
+ assetIndex === 0,
1321
+ blocksManifest.byRef[asset.ref]?.chapters?.unchaptered,
1322
+ );
1323
+ block.append(details);
1324
+ }
1325
+ } else {
1326
+ for (const [index, chapter] of chapters.entries()) {
1327
+ const segments = transcript.filter(
1328
+ (seg) => seg.start >= chapter.start && seg.start < chapter.end,
1329
+ );
1330
+ const details = buildTranscriptDetails(
1331
+ chapter.title,
1332
+ chapter.start,
1333
+ chapter.end,
1334
+ segments,
1335
+ assetIndex === 0 && index === 0,
1336
+ blocksManifest.byRef[asset.ref]?.chapters?.[String(chapter.start)],
1337
+ );
1338
+ block.append(details);
1339
+ }
1340
+ }
1341
+
1342
+ mount.append(block);
1343
+ }
1344
+ }
1345
+
1346
+ function buildTranscriptDetails(title, start, end, segments, openByDefault, blockIdValue) {
1347
+ const details = el("details", {
1348
+ class: "transcript-chapter",
1349
+ attrs: openByDefault ? { open: "" } : {},
1350
+ });
1351
+ setBlockId(details, blockIdValue);
1352
+ details.append(
1353
+ el("summary", {}, [
1354
+ document.createTextNode(title + " "),
1355
+ el("span", {
1356
+ class: "transcript-chapter__range",
1357
+ text: `(${formatRange(start, end)}・${segments.length} 発話)`,
1358
+ }),
1359
+ ]),
1360
+ );
1361
+
1362
+ if (segments.length === 0) {
1363
+ details.append(emptyState("この章に対応する発話はありません。"));
1364
+ } else {
1365
+ const list = el("ul", { class: "transcript-list" });
1366
+ for (const seg of segments) {
1367
+ list.append(
1368
+ el(
1369
+ "li",
1370
+ {
1371
+ class: "transcript-line",
1372
+ attrs: { "data-seek": seg.start },
1373
+ },
1374
+ [
1375
+ el("span", {
1376
+ class: "transcript-line__time",
1377
+ text: formatTime(seg.start),
1378
+ }),
1379
+ el("span", { text: seg.text }),
1380
+ ],
1381
+ ),
1382
+ );
1383
+ }
1384
+ details.append(list);
1385
+ }
1386
+ return details;
1387
+ }
1388
+
1389
+ // 素材の読み(interpretation.assets[].role / .summary / .sections[])。
1390
+ // 2026-07-22 A3.2 multiasset-dogfood で「書くだけ書いて画面に出ない」ことが発覚した
1391
+ // 3 フィールドの描画(「素材の読みは存続」裁定の実装漏れ対応)。arc は対象外
1392
+ // (構成案は引き続き非表示 — A2.1 改訂)。
1393
+ function renderAssetReading(interpAsset) {
1394
+ const card = el("div", { class: "fact-card" });
1395
+ card.append(el("h4", { text: "素材の読み(role / summary)" }));
1396
+ card.append(
1397
+ el("div", { class: "badge-row" }, [
1398
+ badge("interpretation"),
1399
+ el("strong", { text: interpAsset.role }),
1400
+ ]),
1401
+ );
1402
+ card.append(el("p", { text: interpAsset.summary }));
1403
+
1404
+ const sections = interpAsset.sections || [];
1405
+ if (sections.length > 0) {
1406
+ card.append(el("h5", { text: "章単位の読み(sections)" }));
1407
+ for (const section of sections) {
1408
+ const chip = el("div", {
1409
+ class: "event-chip",
1410
+ attrs: { "data-seek": section.start },
1411
+ });
1412
+ chip.append(
1413
+ el("div", { class: "event-chip__meta" }, [
1414
+ badge("interpretation"),
1415
+ el("strong", { text: section.title }),
1416
+ el("span", {
1417
+ text: `${section.role}・${formatRange(section.start, section.end)}`,
1418
+ }),
1419
+ ]),
1420
+ );
1421
+ const evidenceDetails = el("details", {
1422
+ class: "evidence-toggle",
1423
+ });
1424
+ evidenceDetails.append(el("summary", { text: "根拠を見る" }));
1425
+ evidenceDetails.append(
1426
+ el("blockquote", { text: section.evidence }),
1427
+ );
1428
+ chip.append(evidenceDetails);
1429
+ card.append(chip);
1430
+ }
1431
+ }
1432
+ return card;
1433
+ }
1434
+
1435
+ // --- Section 3: 素材別事実カード ---
1436
+ function renderFacts(bundle) {
1437
+ const mount = document.querySelector('[data-mount="facts"]');
1438
+ const assets = bundle.assets || [];
1439
+ const interpAssetsByRef = new Map(
1440
+ (bundle.interpretation.assets || []).map((a) => [a.ref, a]),
1441
+ );
1442
+
1443
+ for (const asset of assets) {
1444
+ const analysis = asset.analysis;
1445
+ const events = analysis.events || [];
1446
+ const chapterCount = events.filter((e) => e.type === "chapter").length;
1447
+ const troubleEvents = events.filter((e) => e.type === "trouble");
1448
+ const fillerEvents = events.filter((e) => e.type === "filler");
1449
+ const hookEvents = events.filter((e) => e.type === "hook");
1450
+ const highlightEvents = events.filter((e) => e.type === "highlight");
1451
+ const tracks = analysis.tracks || {};
1452
+ const interpAsset = interpAssetsByRef.get(asset.ref);
1453
+ const troubleOverlapFlags = interpAsset
1454
+ ? (interpAsset.flags || []).filter(
1455
+ (f) => f.type === "trouble_overlap",
1456
+ )
1457
+ : [];
1458
+
1459
+ const block = el("div", { class: "asset-block" });
1460
+ setBlockId(block, blocksManifest.byRef[asset.ref]?.facts);
1461
+ block.append(
1462
+ el("div", { class: "badge-row" }, [
1463
+ badge("fact"),
1464
+ el("strong", { text: asset.ref }),
1465
+ ]),
1466
+ );
1467
+
1468
+ if (interpAsset) {
1469
+ block.append(renderAssetReading(interpAsset));
1470
+ }
1471
+
1472
+ block.append(
1473
+ el("div", { class: "fact-tally" }, [
1474
+ el("span", { text: `章: ${chapterCount}` }),
1475
+ el("span", { text: `トラブル: ${troubleEvents.length}` }),
1476
+ el("span", { text: `フィラー: ${fillerEvents.length}` }),
1477
+ el("span", { text: `見どころ: ${hookEvents.length}` }),
1478
+ el("span", { text: `ハイライト: ${highlightEvents.length}` }),
1479
+ el("span", {
1480
+ text: `話者トラック: ${(tracks.speakers || []).length} / 顔トラック: ${(tracks.faces || []).length} / 人物マット: ${tracks.person_matte ? "あり" : "なし"}`,
1481
+ }),
1482
+ ]),
1483
+ );
1484
+
1485
+ const card = el("div", { class: "fact-card" });
1486
+ card.append(el("h4", { text: "トラブル(events[trouble])" }));
1487
+ if (troubleEvents.length === 0) {
1488
+ card.append(emptyState("トラブルイベントはありません。"));
1489
+ } else {
1490
+ for (const trouble of troubleEvents) {
1491
+ const chip = el("div", { class: "event-chip" });
1492
+ chip.dataset.seek = String(trouble.start);
1493
+ chip.append(
1494
+ el("div", { class: "event-chip__meta" }, [
1495
+ badge("fact"),
1496
+ el("span", {
1497
+ text: formatRange(trouble.start, trouble.end),
1498
+ }),
1499
+ ]),
1500
+ );
1501
+ chip.append(el("p", { text: trouble.note }));
1502
+
1503
+ const overlapping = troubleOverlapFlags.filter((flag) =>
1504
+ overlaps(trouble.start, trouble.end, flag.start ?? trouble.start, flag.end ?? trouble.end),
1505
+ );
1506
+ for (const flag of overlapping) {
1507
+ const overlay = el("div", { class: "overlay-note" });
1508
+ overlay.append(
1509
+ el("div", { class: "event-chip__meta" }, [
1510
+ badge("interpretation"),
1511
+ el("span", { text: "本筋内トラブル(trouble_overlap)" }),
1512
+ ]),
1513
+ );
1514
+ overlay.append(el("p", { text: flag.note || "" }));
1515
+ const evidenceDetails = el("details", {
1516
+ class: "evidence-toggle",
1517
+ });
1518
+ evidenceDetails.append(
1519
+ el("summary", { text: "根拠を見る" }),
1520
+ );
1521
+ evidenceDetails.append(
1522
+ el("blockquote", { text: flag.evidence }),
1523
+ );
1524
+ overlay.append(evidenceDetails);
1525
+ chip.append(overlay);
1526
+ }
1527
+ card.append(chip);
1528
+ }
1529
+ }
1530
+ block.append(card);
1531
+
1532
+ const fillerCard = el("div", { class: "fact-card" });
1533
+ fillerCard.append(el("h4", { text: "フィラー(events[filler])" }));
1534
+ if (fillerEvents.length === 0) {
1535
+ fillerCard.append(emptyState("フィラーイベントはありません。"));
1536
+ } else {
1537
+ for (const filler of fillerEvents) {
1538
+ fillerCard.append(
1539
+ el(
1540
+ "div",
1541
+ {
1542
+ class: "event-chip",
1543
+ attrs: { "data-seek": filler.start },
1544
+ },
1545
+ [
1546
+ el("div", { class: "event-chip__meta" }, [
1547
+ badge("fact"),
1548
+ el("span", {
1549
+ text: formatRange(filler.start, filler.end),
1550
+ }),
1551
+ ]),
1552
+ ],
1553
+ ),
1554
+ );
1555
+ }
1556
+ }
1557
+ block.append(fillerCard);
1558
+
1559
+ if (hookEvents.length > 0 || highlightEvents.length > 0) {
1560
+ const hookCard = el("div", { class: "fact-card" });
1561
+ hookCard.append(el("h4", { text: "見どころ・ハイライト" }));
1562
+ for (const hook of hookEvents) {
1563
+ const scoreText = hook.score
1564
+ ? Object.entries(hook.score)
1565
+ .map(([k, v]) => `${k}=${v}`)
1566
+ .join(" / ")
1567
+ : "";
1568
+ hookCard.append(
1569
+ el(
1570
+ "div",
1571
+ {
1572
+ class: "event-chip",
1573
+ attrs: { "data-seek": hook.start },
1574
+ },
1575
+ [
1576
+ el("div", { class: "event-chip__meta" }, [
1577
+ badge("fact"),
1578
+ el("span", {
1579
+ text: `見どころ ${formatRange(hook.start, hook.end)}`,
1580
+ }),
1581
+ ]),
1582
+ el("p", { text: scoreText }),
1583
+ ],
1584
+ ),
1585
+ );
1586
+ }
1587
+ for (const highlight of highlightEvents) {
1588
+ hookCard.append(
1589
+ el(
1590
+ "div",
1591
+ {
1592
+ class: "event-chip",
1593
+ attrs: { "data-seek": highlight.start },
1594
+ },
1595
+ [
1596
+ el("div", { class: "event-chip__meta" }, [
1597
+ badge("fact"),
1598
+ el("span", {
1599
+ text: `ハイライト ${formatRange(highlight.start, highlight.end)}`,
1600
+ }),
1601
+ ]),
1602
+ el("blockquote", { text: highlight.quote }),
1603
+ el("p", { text: highlight.reason }),
1604
+ ],
1605
+ ),
1606
+ );
1607
+ }
1608
+ block.append(hookCard);
1609
+ }
1610
+
1611
+ mount.append(block);
1612
+ }
1613
+ }
1614
+
1615
+ // --- Section 4: 関係・浮いた素材 ---
1616
+ function renderRelations(bundle) {
1617
+ const mount = document.querySelector('[data-mount="relations"]');
1618
+ const interpAssets = bundle.interpretation.assets || [];
1619
+
1620
+ const relationsHeading = el("h3", { text: "素材間関係" });
1621
+ mount.append(relationsHeading);
1622
+ const totalRelations = interpAssets.reduce(
1623
+ (sum, a) => sum + (a.relations || []).length,
1624
+ 0,
1625
+ );
1626
+ if (interpAssets.length === 1) {
1627
+ mount.append(emptyState("単一素材のため素材間関係はありません"));
1628
+ } else if (totalRelations === 0) {
1629
+ mount.append(emptyState("複数素材間の関係: 走査済み・該当なし"));
1630
+ } else {
1631
+ for (const asset of interpAssets) {
1632
+ for (const relation of asset.relations || []) {
1633
+ const card = el("div", { class: "relation-card" });
1634
+ const relationKey = JSON.stringify([relation.target, relation.kind]);
1635
+ setBlockId(card, blocksManifest.byRef[asset.ref]?.relations?.[relationKey]);
1636
+ card.append(
1637
+ el("div", { class: "badge-row" }, [
1638
+ badge("interpretation"),
1639
+ el("strong", {
1640
+ text: `${asset.ref} → ${relation.target}(${relation.kind})`,
1641
+ }),
1642
+ ]),
1643
+ );
1644
+ if (relation.note) card.append(el("p", { text: relation.note }));
1645
+ const evidenceDetails = el("details", {
1646
+ class: "evidence-toggle",
1647
+ });
1648
+ evidenceDetails.append(el("summary", { text: "根拠を見る" }));
1649
+ evidenceDetails.append(
1650
+ el("blockquote", { text: relation.evidence }),
1651
+ );
1652
+ card.append(evidenceDetails);
1653
+ mount.append(card);
1654
+ }
1655
+ }
1656
+ }
1657
+
1658
+ mount.append(el("h3", { text: "浮いた素材(orphan / unclear)" }));
1659
+ const orphanFlags = [];
1660
+ for (const asset of interpAssets) {
1661
+ for (const flag of asset.flags || []) {
1662
+ if (flag.type === "trouble_overlap") continue;
1663
+ orphanFlags.push({ asset, flag });
1664
+ }
1665
+ }
1666
+ if (orphanFlags.length === 0) {
1667
+ mount.append(emptyState("浮いた素材: 走査済み・該当なし"));
1668
+ } else {
1669
+ for (const { asset, flag } of orphanFlags) {
1670
+ const card = el("div", { class: "relation-card" });
1671
+ card.append(
1672
+ el("div", { class: "badge-row" }, [
1673
+ badge("interpretation"),
1674
+ el("strong", {
1675
+ text: `${asset.ref}: ${FLAG_LABELS[flag.type] || flag.type}`,
1676
+ }),
1677
+ ]),
1678
+ );
1679
+ if (flag.start !== undefined) {
1680
+ card.setAttribute("data-seek", String(flag.start));
1681
+ }
1682
+ if (flag.note) card.append(el("p", { text: flag.note }));
1683
+ const evidenceDetails = el("details", {
1684
+ class: "evidence-toggle",
1685
+ });
1686
+ evidenceDetails.append(el("summary", { text: "根拠を見る" }));
1687
+ evidenceDetails.append(
1688
+ el("blockquote", { text: flag.evidence }),
1689
+ );
1690
+ card.append(evidenceDetails);
1691
+ mount.append(card);
1692
+ }
1693
+ }
1694
+ }
1695
+
1696
+ // --- Section 5: 取材台帳 ---
1697
+ function renderQuestions(bundle) {
1698
+ const mount = document.querySelector('[data-mount="questions"]');
1699
+ const questions = bundle.interpretation.open_questions || [];
1700
+
1701
+ mount.append(
1702
+ el("div", { class: "badge-row" }, [badge("interpretation")]),
1703
+ );
1704
+
1705
+ if (questions.length === 0) {
1706
+ mount.append(
1707
+ emptyState(
1708
+ "取材事項なし — 素材の文脈だけで根拠付きで筋が通りました",
1709
+ ),
1710
+ );
1711
+ return;
1712
+ }
1713
+
1714
+ const wrapper = el("div", { class: "table-scroll" });
1715
+ const table = el("table");
1716
+ table.append(
1717
+ el("caption", { text: "取材台帳(open_questions)" }),
1718
+ );
1719
+ const thead = el("thead");
1720
+ const headRow = el("tr");
1721
+ for (const label of ["状態", "質問", "埋める情報", "回答"]) {
1722
+ headRow.append(el("th", { attrs: { scope: "col" }, text: label }));
1723
+ }
1724
+ thead.append(headRow);
1725
+ table.append(thead);
1726
+
1727
+ const tbody = el("tbody");
1728
+ for (const question of questions) {
1729
+ const row = el("tr");
1730
+ setBlockId(row, blocksManifest.questions?.[question.id]);
1731
+ const statusBadge =
1732
+ question.status === "answered"
1733
+ ? el("span", {
1734
+ class: "badge badge--fact",
1735
+ text: "回答済み",
1736
+ })
1737
+ : el("span", { class: "badge badge--warn", text: "未回答" });
1738
+ row.append(el("td", {}, [statusBadge]));
1739
+ row.append(el("td", { text: question.question }));
1740
+ row.append(el("td", { text: question.fills }));
1741
+ row.append(el("td", { text: question.answer || "—" }));
1742
+ tbody.append(row);
1743
+ }
1744
+ table.append(tbody);
1745
+ wrapper.append(table);
1746
+ mount.append(wrapper);
1747
+ }
1748
+
1749
+ // --- Section 6: 来歴 ---
1750
+ function renderProvenance(bundle) {
1751
+ const mount = document.querySelector('[data-mount="provenance"]');
1752
+ setBlockId(mount, blocksManifest.provenance);
1753
+ const inputs = bundle.interpretation.inputs || {};
1754
+ const analyses = inputs.analyses || [];
1755
+ const context = inputs.context;
1756
+
1757
+ mount.append(el("h3", { text: "参照した事実層(analyses)" }));
1758
+ mount.append(
1759
+ el("div", { class: "badge-row" }, [badge("fact")]),
1760
+ );
1761
+ const analysesList = el("ul", { class: "provenance-list" });
1762
+ for (const ref of analyses) {
1763
+ analysesList.append(
1764
+ el("li", {}, [
1765
+ el("dl", {}, [
1766
+ el("dt", { text: "path" }),
1767
+ el("dd", { text: ref.path }),
1768
+ el("dt", { text: "source" }),
1769
+ el("dd", { text: ref.source }),
1770
+ ]),
1771
+ ]),
1772
+ );
1773
+ }
1774
+ mount.append(analysesList);
1775
+
1776
+ mount.append(el("h3", { text: "2 パス目に与えた文脈(context)" }));
1777
+ mount.append(
1778
+ el("div", { class: "badge-row" }, [badge("interpretation")]),
1779
+ );
1780
+
1781
+ if (!context) {
1782
+ mount.append(emptyState("context の記録はありません。"));
1783
+ return;
1784
+ }
1785
+
1786
+ const contextList = el("ul", { class: "provenance-list" });
1787
+
1788
+ contextList.append(
1789
+ el("li", {}, [
1790
+ el("dl", {}, [
1791
+ el("dt", { text: "intake" }),
1792
+ el("dd", {
1793
+ text: context.intake
1794
+ ? JSON.stringify(context.intake)
1795
+ : "該当なし(本プロジェクトに intake.json なし)",
1796
+ }),
1797
+ ]),
1798
+ ]),
1799
+ );
1800
+
1801
+ contextList.append(
1802
+ el("li", {}, [
1803
+ el("dl", {}, [
1804
+ el("dt", { text: "past_projects" }),
1805
+ el("dd", {
1806
+ text:
1807
+ (context.past_projects || []).length === 0
1808
+ ? "参照なし"
1809
+ : context.past_projects
1810
+ .map((p) => `${p.ref}: ${p.notes}`)
1811
+ .join(" / "),
1812
+ }),
1813
+ ]),
1814
+ ]),
1815
+ );
1816
+
1817
+ contextList.append(
1818
+ el("li", {}, [
1819
+ el("dl", {}, [
1820
+ el("dt", { text: "interview" }),
1821
+ el("dd", {
1822
+ text:
1823
+ (context.interview || []).length === 0
1824
+ ? "取材実績なし"
1825
+ : context.interview
1826
+ .map((qa) => `Q: ${qa.q} / A: ${qa.a}`)
1827
+ .join(" | "),
1828
+ }),
1829
+ ]),
1830
+ ]),
1831
+ );
1832
+
1833
+ if (context.notes) {
1834
+ contextList.append(
1835
+ el("li", {}, [
1836
+ el("dl", {}, [
1837
+ el("dt", { text: "notes" }),
1838
+ el("dd", { text: context.notes }),
1839
+ ]),
1840
+ ]),
1841
+ );
1842
+ }
1843
+
1844
+ mount.append(contextList);
1845
+ }
1846
+
1847
+ function main() {
1848
+ const dataScript = document.getElementById(
1849
+ "akari-analysis-report-data",
1850
+ );
1851
+ const blocksScript = document.getElementById(
1852
+ "akari-analysis-report-blocks",
1853
+ );
1854
+ let bundle;
1855
+ try {
1856
+ bundle = JSON.parse(dataScript.textContent);
1857
+ } catch (error) {
1858
+ renderFatalError("JSON の解析に失敗しました(" + error.message + ")");
1859
+ return;
1860
+ }
1861
+
1862
+ if (
1863
+ !isRecord(bundle) ||
1864
+ !isRecord(bundle.interpretation) ||
1865
+ !Array.isArray(bundle.assets)
1866
+ ) {
1867
+ renderFatalError(
1868
+ "データの形が想定と異なります(bundle.interpretation / bundle.assets が必要です)",
1869
+ );
1870
+ return;
1871
+ }
1872
+
1873
+ try {
1874
+ blocksManifest = JSON.parse(blocksScript.textContent);
1875
+ } catch (error) {
1876
+ renderFatalError(
1877
+ "block-id マニフェストの解析に失敗しました(" + error.message + ")",
1878
+ );
1879
+ return;
1880
+ }
1881
+
1882
+ if (!isRecord(blocksManifest) || !isRecord(blocksManifest.byRef)) {
1883
+ renderFatalError(
1884
+ "block-id マニフェストの形が想定と異なります(byRef が必要です)",
1885
+ );
1886
+ return;
1887
+ }
1888
+
1889
+ renderHeader(bundle);
1890
+ renderTimeline(bundle);
1891
+ renderTranscript(bundle);
1892
+ renderFacts(bundle);
1893
+ renderRelations(bundle);
1894
+ renderQuestions(bundle);
1895
+ renderProvenance(bundle);
1896
+ }
1897
+
1898
+ main();
1899
+
1900
+ // --- 注釈選択導線(内部契約 contract-2026-07-26-doc-image-annotations §1/§4-1)---
1901
+ //
1902
+ // HTML ブロックは要素直接選択が主(ペンは設けない)。ブロックをクリックすると選択
1903
+ // ハイライトを表示し、シェル側(akari-annotations 拡張)へ通知する。
1904
+ //
1905
+ // 統合点調査(doc-annotation-ui タスク report.md 参照): このレポートは akari-surfaces
1906
+ // 拡張の WebviewWidget として開かれ、その注入ブリッジ script が acquireVsCodeApi() を
1907
+ // 単一取得済みのため、ここから vscode.postMessage 相当を新規に呼ぶ経路は無い。代わりに
1908
+ // Theia core が常時提供する `command:<id>?<jsonArgs>` リンク(CommandOpenHandler)を使う
1909
+ // — akari-annotations 拡張が WebviewContentOptions.enableCommandUris でこのコマンドの
1910
+ // みを許可し、クリックを akari.annotations.selectDocBlock コマンドとして受け取る。
1911
+ // 逆方向(シェル → レポート)は widget.sendMessage(...) がそのまま window の 'message'
1912
+ // イベントとして届くため、素の addEventListener で受け取れる(acquireVsCodeApi 不要)。
1913
+ function selectDocBlock(blockId) {
1914
+ const link = document.createElement("a");
1915
+ link.href =
1916
+ "command:akari.annotations.selectDocBlock?" +
1917
+ encodeURIComponent(JSON.stringify([blockId]));
1918
+ link.style.display = "none";
1919
+ document.body.appendChild(link);
1920
+ link.click();
1921
+ link.remove();
1922
+ }
1923
+
1924
+ // 画像ブロック(`<img data-block-id>`、image-annotation-pen タスク・契約 §1/§4-2)の
1925
+ // クリック導線。HTML ブロックは要素直接選択(上の selectDocBlock)だが、画像は
1926
+ // クリック → ポップアッププレビュー → ペン領域指示という別経路のため、別コマンドで
1927
+ // 通知する(同じ command: URI bridge に相乗り・akari-annotations 拡張の
1928
+ // enableCommandUris がこのコマンドも許可する)。imageSrc は img の src 属性値
1929
+ // (render-analysis-report.mjs が埋め込む「レポート自身からの相対パス」)をそのまま渡し、
1930
+ // シェル側でレポートの絶対パスを起点に解決する。
1931
+ function selectImageBlock(blockId, imageSrc) {
1932
+ const link = document.createElement("a");
1933
+ link.href =
1934
+ "command:akari.annotations.selectImage?" +
1935
+ encodeURIComponent(JSON.stringify([blockId, imageSrc]));
1936
+ link.style.display = "none";
1937
+ document.body.appendChild(link);
1938
+ link.click();
1939
+ link.remove();
1940
+ }
1941
+
1942
+ let selectedBlockElement = null;
1943
+
1944
+ function markSelected(element) {
1945
+ if (selectedBlockElement === element) return;
1946
+ if (selectedBlockElement) {
1947
+ selectedBlockElement.classList.remove("akari-annotation-selected");
1948
+ }
1949
+ selectedBlockElement = element;
1950
+ if (selectedBlockElement) {
1951
+ selectedBlockElement.classList.add("akari-annotation-selected");
1952
+ }
1953
+ }
1954
+
1955
+ document.addEventListener("click", (event) => {
1956
+ const element = event.target.closest && event.target.closest("[data-block-id]");
1957
+ if (!element) return;
1958
+ const blockId = element.getAttribute("data-block-id");
1959
+ if (element.tagName === "IMG") {
1960
+ // 画像ブロックは選択ハイライト + doc: 選択導線を経由せず、直接ポップアップを開く
1961
+ // (契約 §4-1: 画像面はクリック→ポップアッププレビュー→ペン領域指示)。
1962
+ selectImageBlock(blockId, element.getAttribute("src"));
1963
+ return;
1964
+ }
1965
+ // <a> や <button> 等、クリックが別の意味を持つ子要素の中でも data-block-id を持つ
1966
+ // 祖先が見つかれば選択扱いにする(本テンプレートはブロックへのリンク/ボタンを
1967
+ // 現状持たないが、将来の拡張に対して壊れない挙動にしておく)。
1968
+ markSelected(element);
1969
+ selectDocBlock(blockId);
1970
+ });
1971
+
1972
+ // シェル側(akari-annotations 拡張)からの push を受け取る。acquireVsCodeApi 不要
1973
+ // (widget.sendMessage は window への postMessage としてそのまま届くため)。
1974
+ window.addEventListener("message", (event) => {
1975
+ const message = event.data;
1976
+ if (!message || typeof message !== "object") return;
1977
+ if (message.type === "akari-doc-annotations" && Array.isArray(message.blocks)) {
1978
+ renderAnnotationPins(message.blocks);
1979
+ } else if (message.type === "akari-doc-annotation-reveal" && typeof message.blockId === "string") {
1980
+ revealBlock(message.blockId);
1981
+ }
1982
+ });
1983
+
1984
+ function renderAnnotationPins(blocks) {
1985
+ document.querySelectorAll(".akari-annotation-pin").forEach((pin) => pin.remove());
1986
+ for (const entry of blocks) {
1987
+ if (!entry || typeof entry.blockId !== "string") continue;
1988
+ const target = document.querySelector(
1989
+ `[data-block-id="${cssEscape(entry.blockId)}"]`,
1990
+ );
1991
+ if (!target) continue;
1992
+ const pin = document.createElement("span");
1993
+ pin.className = "akari-annotation-pin";
1994
+ pin.dataset.status = typeof entry.status === "string" ? entry.status : "open";
1995
+ pin.title = "注釈あり(クリックで選択)";
1996
+ pin.textContent = "📌";
1997
+ pin.addEventListener("click", (event) => {
1998
+ event.stopPropagation();
1999
+ markSelected(target);
2000
+ selectDocBlock(entry.blockId);
2001
+ });
2002
+ target.insertAdjacentElement("afterend", pin);
2003
+ }
2004
+ }
2005
+
2006
+ function revealBlock(blockId) {
2007
+ const target = document.querySelector(`[data-block-id="${cssEscape(blockId)}"]`);
2008
+ if (!target) return;
2009
+ target.scrollIntoView({ block: "center", behavior: "smooth" });
2010
+ markSelected(target);
2011
+ target.classList.add("akari-annotation-reveal-flash");
2012
+ window.setTimeout(() => target.classList.remove("akari-annotation-reveal-flash"), 1600);
2013
+ }
2014
+
2015
+ function cssEscape(value) {
2016
+ return window.CSS && CSS.escape
2017
+ ? CSS.escape(value)
2018
+ : String(value).replace(/["\\]/g, "\\$&");
2019
+ }
2020
+ })();
2021
+ </script>
2022
+ </body>
2023
+ </html>