@analitikisler/ai-tag-debugger 0.16.0 → 0.18.0

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.
@@ -0,0 +1,1180 @@
1
+ // The report's stylesheet: one constant per part, joined in this order: tokens, components, shell, product (the few
2
+ // rules the product's markup needs beyond the mockup's), then the sections in rail order. Each section's builder edits
3
+ // only its own constant, so parallel changes never touch the same lines.
4
+ // No url(data:...) anywhere (test/html-shell.test.js checks it): icons come from the inline sprite. The search icon the
5
+ // mockup drew as a data URI in the filter bar is the sprite's ui-search, placed by the filter bar's markup.
6
+ import { brandCss } from "../whitelabel.js";
7
+
8
+ // ---------- tokens: colours (light, then dark under both selectors), spacing, radii, fonts ----------
9
+ const TOKENS = `/* Direction D tokens. Light on :root; dark twice (OS preference unless the page forces light, and the forced dark theme),
10
+ so <html data-theme> beats the OS both ways. Every -fg passes 4.5:1 on --card, --page and its own -bg in both modes;
11
+ every base passes 3:1 on --card (VISUAL.md has the table). Builders reference these names only, never a hex. */
12
+ :root{
13
+ color-scheme:light dark;
14
+ --page:#F6F7F9;--card:#FFFFFF;--well:#EEF1F6;--fg:#111827;--muted:#4B5567;--border:#D9DEE8;--border-strong:#C3CAD8;
15
+ --accent:#2E6CDB;--accent-fg:#2459C0;--accent-bg:#EBF1FE;
16
+ --shadow-sm:0 1px 2px rgb(16 24 40 / 6%);--shadow-md:0 1px 2px rgb(16 24 40 / 5%),0 4px 16px -4px rgb(16 24 40 / 10%);
17
+ --sev-broken:#D92D20;--sev-broken-fg:#B42318;--sev-broken-bg:#FEE4E2;
18
+ --sev-risk:#9B3B9A;--sev-risk-fg:#86338A;--sev-risk-bg:#F7E8F7;
19
+ --sev-warning:#C07B03;--sev-warning-fg:#7A5A00;--sev-warning-bg:#FBF0D0;
20
+ --sev-info:#5B6B8C;--sev-info-fg:#3F4D6B;--sev-info-bg:#E9EDF5;
21
+ --sev-ok:#17805A;--sev-ok-fg:#167A55;--sev-ok-bg:#E1F3EA;
22
+ --owner-developer:#5550C0;--owner-developer-fg:#5A58C2;--owner-developer-bg:#EDEFFE;
23
+ --owner-gtm:#0593BF;--owner-gtm-fg:#026F90;--owner-gtm-bg:#DFF4FE;
24
+ --owner-marketing:#A72A6A;--owner-marketing-fg:#AE3A72;--owner-marketing-bg:#FEEAF1;
25
+ --plat-ga4:#D17102;--plat-ga4-fg:#9C5301;--plat-ga4-bg:#FEECE0;
26
+ --plat-meta:#3F87FE;--plat-meta-fg:#105FD9;--plat-meta-bg:#E8F1FE;
27
+ --plat-google_ads:#299E48;--plat-google_ads-fg:#02782E;--plat-google_ads-bg:#DFF8E1;
28
+ --vp-desktop:#08908C;--vp-desktop-fg:#097376;--vp-desktop-bg:#D2F9FA;
29
+ --vp-mobile:#799506;--vp-mobile-fg:#596F01;--vp-mobile-bg:#EBF5D8;
30
+ --consent-granted:#019F68;--consent-granted-fg:#167A55;--consent-granted-bg:#E1F3EA;
31
+ --consent-denied:#B1011B;--consent-denied-fg:#B1011B;--consent-denied-bg:#FFEFEF;
32
+ --consent-none:#5F636C;--consent-none-fg:#555963;--consent-none-bg:#EEF0F4;
33
+ --plat-tiktok:#1F2433;--plat-tiktok-fg:#252B3B;--plat-tiktok-bg:#E9EBF0;
34
+ /* Aliases: a semantic family that means the same as a status wears its colours, defined once so dark mode follows. */
35
+ --run-completed:var(--sev-ok);--run-completed-fg:var(--sev-ok-fg);--run-completed-bg:var(--sev-ok-bg);
36
+ --run-incomplete:var(--sev-warning);--run-incomplete-fg:var(--sev-warning-fg);--run-incomplete-bg:var(--sev-warning-bg);
37
+ --group-failed:var(--sev-broken);--group-failed-fg:var(--sev-broken-fg);--group-failed-bg:var(--sev-broken-bg);
38
+ --group-unverified:var(--sev-info);--group-unverified-fg:var(--sev-info-fg);--group-unverified-bg:var(--sev-info-bg);
39
+ --group-passed:var(--sev-ok);--group-passed-fg:var(--sev-ok-fg);--group-passed-bg:var(--sev-ok-bg);
40
+ --score-fail:var(--sev-broken);--score-fail-fg:var(--sev-broken-fg);--score-fail-bg:var(--sev-broken-bg);
41
+ --score-mid:var(--sev-warning);--score-mid-fg:var(--sev-warning-fg);--score-mid-bg:var(--sev-warning-bg);
42
+ --score-pass:var(--sev-ok);--score-pass-fg:var(--sev-ok-fg);--score-pass-bg:var(--sev-ok-bg);
43
+ --score-track:var(--well);
44
+ --s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:24px;--s6:32px;--s7:48px;
45
+ --r-sm:4px;--r-md:8px;--r-lg:12px;--r-pill:999px;
46
+ --font-sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;
47
+ --font-mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
48
+ --rail:200px;--top:48px}
49
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
50
+ --page:#0B0F19;--card:#141927;--well:#1B2235;--fg:#EEF2FF;--muted:#9AA4BD;--border:#263050;--border-strong:#34406A;
51
+ --accent:#4F8EF7;--accent-fg:#8CBBFF;--accent-bg:#1A2F5C;
52
+ --shadow-sm:none;--shadow-md:none;
53
+ --sev-broken:#F04438;--sev-broken-fg:#FDA29B;--sev-broken-bg:#3D1D1D;
54
+ --sev-risk:#D78AD6;--sev-risk-fg:#E3A6E0;--sev-risk-bg:#34203A;
55
+ --sev-warning:#E8B44A;--sev-warning-fg:#F5C451;--sev-warning-bg:#352B10;
56
+ --sev-info:#8191B8;--sev-info-fg:#B4C0DC;--sev-info-bg:#1E2536;
57
+ --sev-ok:#3FB98A;--sev-ok-fg:#5BD39A;--sev-ok-bg:#113128;
58
+ --owner-developer:#6967E1;--owner-developer-fg:#A2A8FE;--owner-developer-bg:#262748;
59
+ --owner-gtm:#059AC7;--owner-gtm-fg:#3CBFF0;--owner-gtm-bg:#013041;
60
+ --owner-marketing:#C64082;--owner-marketing-fg:#F490BA;--owner-marketing-bg:#421E2D;
61
+ --plat-ga4:#B76300;--plat-ga4-fg:#F09C59;--plat-ga4-bg:#412308;
62
+ --plat-meta:#2974F0;--plat-meta-fg:#83B2FE;--plat-meta-bg:#192B49;
63
+ --plat-google_ads:#078C38;--plat-google_ads-fg:#70C47E;--plat-google_ads-bg:#133319;
64
+ --vp-desktop:#029394;--vp-desktop-fg:#0FC6CB;--vp-desktop-bg:#023234;
65
+ --vp-mobile:#728D05;--vp-mobile-fg:#A2BC59;--vp-mobile-bg:#263009;
66
+ --consent-granted:#0AB074;--consent-granted-fg:#5BD39A;--consent-granted-bg:#113128;
67
+ --consent-denied:#D01C29;--consent-denied-fg:#FF8A85;--consent-denied-bg:#4A1418;
68
+ --consent-none:#747982;--consent-none-fg:#ABB0B9;--consent-none-bg:#282B31;
69
+ --plat-tiktok:#E4E8F1;--plat-tiktok-fg:#E4E8F1;--plat-tiktok-bg:#272C3A;}}
70
+ :root[data-theme="dark"]{
71
+ --page:#0B0F19;--card:#141927;--well:#1B2235;--fg:#EEF2FF;--muted:#9AA4BD;--border:#263050;--border-strong:#34406A;
72
+ --accent:#4F8EF7;--accent-fg:#8CBBFF;--accent-bg:#1A2F5C;
73
+ --shadow-sm:none;--shadow-md:none;
74
+ --sev-broken:#F04438;--sev-broken-fg:#FDA29B;--sev-broken-bg:#3D1D1D;
75
+ --sev-risk:#D78AD6;--sev-risk-fg:#E3A6E0;--sev-risk-bg:#34203A;
76
+ --sev-warning:#E8B44A;--sev-warning-fg:#F5C451;--sev-warning-bg:#352B10;
77
+ --sev-info:#8191B8;--sev-info-fg:#B4C0DC;--sev-info-bg:#1E2536;
78
+ --sev-ok:#3FB98A;--sev-ok-fg:#5BD39A;--sev-ok-bg:#113128;
79
+ --owner-developer:#6967E1;--owner-developer-fg:#A2A8FE;--owner-developer-bg:#262748;
80
+ --owner-gtm:#059AC7;--owner-gtm-fg:#3CBFF0;--owner-gtm-bg:#013041;
81
+ --owner-marketing:#C64082;--owner-marketing-fg:#F490BA;--owner-marketing-bg:#421E2D;
82
+ --plat-ga4:#B76300;--plat-ga4-fg:#F09C59;--plat-ga4-bg:#412308;
83
+ --plat-meta:#2974F0;--plat-meta-fg:#83B2FE;--plat-meta-bg:#192B49;
84
+ --plat-google_ads:#078C38;--plat-google_ads-fg:#70C47E;--plat-google_ads-bg:#133319;
85
+ --vp-desktop:#029394;--vp-desktop-fg:#0FC6CB;--vp-desktop-bg:#023234;
86
+ --vp-mobile:#728D05;--vp-mobile-fg:#A2BC59;--vp-mobile-bg:#263009;
87
+ --consent-granted:#0AB074;--consent-granted-fg:#5BD39A;--consent-granted-bg:#113128;
88
+ --consent-denied:#D01C29;--consent-denied-fg:#FF8A85;--consent-denied-bg:#4A1418;
89
+ --consent-none:#747982;--consent-none-fg:#ABB0B9;--consent-none-bg:#282B31;
90
+ --plat-tiktok:#E4E8F1;--plat-tiktok-fg:#E4E8F1;--plat-tiktok-bg:#272C3A;}`;
91
+ // ---------- end of tokens ----------
92
+
93
+ // ---------- components: chips, badges, cards, tabs, filter bar, tables, scorecard ----------
94
+ const COMPONENTS = `/* Direction D shared components. Written against CONTRACT.md "Generic behaviors"; no section ids here.
95
+ Colours only through tokens.css. Order: reset, type, chips, badges, cards, tabs, filter bar, tables,
96
+ info grid, scorecard, print. */
97
+
98
+ /* ---------- reset and type ---------- */
99
+ *,*::before,*::after{box-sizing:border-box}
100
+ html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
101
+ body{margin:0;background:var(--page);color:var(--fg);font:400 14px/1.5 var(--font-sans);-webkit-font-smoothing:antialiased}
102
+ h1,h2,h3,h4,h5,h6,p,ul,ol,figure{margin:0}
103
+ ul,ol{padding:0}
104
+ h1{font-size:24px;line-height:1.2;font-weight:700;letter-spacing:-.01em;overflow-wrap:anywhere}
105
+ h2{font-size:17px;line-height:1.3;font-weight:600}
106
+ h3,h4{font-size:14px;line-height:1.4;font-weight:600}
107
+ h5,h6{font-size:12px;line-height:1.4;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
108
+ b,strong{font-weight:600}
109
+ a{color:var(--accent-fg);text-decoration:none}
110
+ a:hover{text-decoration:underline}
111
+ code,kbd,pre,.mono{font-family:var(--font-mono)}
112
+ code{font-size:12.5px;background:var(--well);padding:1px 5px;border-radius:var(--r-sm);overflow-wrap:anywhere}
113
+ pre{font-size:12px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere;background:var(--well);padding:var(--s2) var(--s3);border-radius:var(--r-sm)}
114
+ svg{flex:none}
115
+ .ic{width:16px;height:16px;flex:none;vertical-align:-3px}
116
+ .muted{color:var(--muted)}
117
+ .xs{font-size:12.5px}
118
+ .num,.tnum{font-variant-numeric:tabular-nums}
119
+ .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
120
+ :focus-visible{outline:2px solid var(--accent);outline-offset:2px}
121
+ .n{display:inline-block;min-width:18px;padding:0 5px;border-radius:var(--r-sm);background:var(--well);color:var(--muted);font:500 11px/17px var(--font-mono);text-align:center;font-variant-numeric:tabular-nums}
122
+ .btn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border:1px solid var(--border);border-radius:var(--r-sm);background:var(--card);color:var(--fg);font:500 12.5px var(--font-sans);cursor:pointer}
123
+ .btn:hover{background:var(--well)}
124
+ .chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
125
+
126
+ /* ---------- chips ----------
127
+ <span class="chip chip-<kind> <value>">[icon] [<span class="k">key</span>] text</span>
128
+ Each value sets --k (mark), --k-fg (text) and --k-bg (tint); one rule paints them all. */
129
+ .chip{--k:var(--muted);--k-fg:var(--muted);--k-bg:var(--well);
130
+ display:inline-flex;align-items:center;gap:5px;height:22px;padding:0 8px;border:1px solid var(--border);border-radius:var(--r-sm);
131
+ border-color:color-mix(in srgb,var(--k) 38%,var(--border));
132
+ background:var(--card);color:var(--k-fg);font:500 12px/1 var(--font-sans);font-variant-numeric:tabular-nums;white-space:nowrap;vertical-align:middle}
133
+ .chip>svg{color:var(--k)}
134
+ .chip>svg{width:13px;height:13px}
135
+ .chip>svg:first-child{margin-left:-3px}
136
+ .chip b{font-weight:700}
137
+ .chip .k{color:var(--fg);font-weight:400}
138
+ .chip.mono{font-family:var(--font-mono);font-size:11.5px}
139
+ .chip .dot,.dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--k,var(--muted));flex:none}
140
+ .chip.lg{height:28px;padding:0 12px;gap:6px;font-size:13px}
141
+ .chip.lg>svg{width:15px;height:15px}
142
+ .chip.plain{--k-bg:var(--card);border-color:var(--border);color:var(--fg)}
143
+ a.chip:hover{text-decoration:none;border-color:var(--k)}
144
+
145
+ .chip-sev.broken{--k:var(--sev-broken);--k-fg:var(--sev-broken-fg);--k-bg:var(--sev-broken-bg)}
146
+ .chip-sev.risk{--k:var(--sev-risk);--k-fg:var(--sev-risk-fg);--k-bg:var(--sev-risk-bg)}
147
+ .chip-sev.warning{--k:var(--sev-warning);--k-fg:var(--sev-warning-fg);--k-bg:var(--sev-warning-bg)}
148
+ .chip-sev.info{--k:var(--sev-info);--k-fg:var(--sev-info-fg);--k-bg:var(--sev-info-bg)}
149
+ .chip-sev.ok{--k:var(--sev-ok);--k-fg:var(--sev-ok-fg);--k-bg:var(--sev-ok-bg)}
150
+ .chip-consent.granted{--k:var(--consent-granted);--k-fg:var(--consent-granted-fg);--k-bg:var(--consent-granted-bg)}
151
+ .chip-consent.denied{--k:var(--consent-denied);--k-fg:var(--consent-denied-fg);--k-bg:var(--consent-denied-bg)}
152
+ .chip-consent.none{--k:var(--consent-none);--k-fg:var(--consent-none-fg);--k-bg:var(--consent-none-bg)}
153
+ .chip-owner.developer{--k:var(--owner-developer);--k-fg:var(--owner-developer-fg);--k-bg:var(--owner-developer-bg)}
154
+ .chip-owner.gtm{--k:var(--owner-gtm);--k-fg:var(--owner-gtm-fg);--k-bg:var(--owner-gtm-bg)}
155
+ .chip-owner.marketing{--k:var(--owner-marketing);--k-fg:var(--owner-marketing-fg);--k-bg:var(--owner-marketing-bg)}
156
+ .chip-plat.ga4{--k:var(--plat-ga4);--k-fg:var(--plat-ga4-fg);--k-bg:var(--plat-ga4-bg)}
157
+ .chip-plat.meta{--k:var(--plat-meta);--k-fg:var(--plat-meta-fg);--k-bg:var(--plat-meta-bg)}
158
+ .chip-plat.google_ads{--k:var(--plat-google_ads);--k-fg:var(--plat-google_ads-fg);--k-bg:var(--plat-google_ads-bg)}
159
+ .chip-plat.tiktok{--k:var(--plat-tiktok);--k-fg:var(--plat-tiktok-fg);--k-bg:var(--plat-tiktok-bg)}
160
+ .chip-vp.desktop{--k:var(--vp-desktop);--k-fg:var(--vp-desktop-fg);--k-bg:var(--vp-desktop-bg)}
161
+ .chip-vp.mobile{--k:var(--vp-mobile);--k-fg:var(--vp-mobile-fg);--k-bg:var(--vp-mobile-bg)}
162
+ .chip-run.completed{--k:var(--run-completed);--k-fg:var(--run-completed-fg);--k-bg:var(--run-completed-bg)}
163
+ .chip-run.incomplete{--k:var(--run-incomplete);--k-fg:var(--run-incomplete-fg);--k-bg:var(--run-incomplete-bg)}
164
+ .chip-group.failed{--k:var(--group-failed);--k-fg:var(--group-failed-fg);--k-bg:var(--group-failed-bg)}
165
+ .chip-group.unverified{--k:var(--group-unverified);--k-fg:var(--group-unverified-fg);--k-bg:var(--group-unverified-bg)}
166
+ .chip-group.passed{--k:var(--group-passed);--k-fg:var(--group-passed-fg);--k-bg:var(--group-passed-bg)}
167
+ /* a zero count keeps its word and glyph but drops the colour, so the eye lands on what is there */
168
+ .chip.zero{--k-fg:var(--muted);--k-bg:var(--well);--k:var(--border-strong);border-color:var(--border);border-style:dashed}
169
+ /* Denied shares broken's red on purpose; it is told apart by weight: a deeper red, an outline and a bold value,
170
+ in the mono key/value form, never with the octagon. Broken is a filled tint with the octagon glyph. */
171
+ .chip-consent{font-family:var(--font-mono);font-size:11.5px}
172
+ .chip-consent.denied{border-color:var(--k);font-weight:700}
173
+ .chip-consent.granted{font-weight:600}
174
+ .chip-consent.none{font-weight:500}
175
+
176
+ /* ---------- severity badge (C's, unchanged in shape) ---------- */
177
+ .sev-broken{--sev:var(--sev-broken);--sev-fg:var(--sev-broken-fg);--sev-bg:var(--sev-broken-bg)}
178
+ .sev-risk{--sev:var(--sev-risk);--sev-fg:var(--sev-risk-fg);--sev-bg:var(--sev-risk-bg)}
179
+ .sev-warning{--sev:var(--sev-warning);--sev-fg:var(--sev-warning-fg);--sev-bg:var(--sev-warning-bg)}
180
+ .sev-info{--sev:var(--sev-info);--sev-fg:var(--sev-info-fg);--sev-bg:var(--sev-info-bg)}
181
+ .sev-ok{--sev:var(--sev-ok);--sev-fg:var(--sev-ok-fg);--sev-bg:var(--sev-ok-bg)}
182
+ .badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px 2px 6px;border:1px solid var(--border);border-radius:var(--r-sm);font:500 12px/16px var(--font-sans);white-space:nowrap;background:var(--card);color:var(--muted)}
183
+ .badge>svg{width:14px;height:14px}
184
+ .badge[class*=sev-]{color:var(--sev-fg);border-color:color-mix(in srgb,var(--sev) 38%,var(--border))}
185
+ .badge[class*=sev-]>svg{color:var(--sev)}
186
+
187
+ /* ---------- cards ----------
188
+ <details class="card [sev-x]"><summary>…</summary><div class="body">…</div></details>, collapsed unless [open].
189
+ A plain <div class="card"> is the Summary's static card. */
190
+ .card{display:block;background:var(--card);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:var(--shadow-sm);min-width:0}
191
+ .card+.card{margin-top:var(--s2)}
192
+ .card[class*=sev-]{border-left:3px solid var(--sev)}
193
+ .card>.body{padding:var(--s3) var(--s4) var(--s4)}
194
+ div.card>.body{padding-top:var(--s4)}
195
+ details.card>summary{display:flex;align-items:center;gap:var(--s2) var(--s3);flex-wrap:wrap;min-height:44px;padding:10px 40px 10px var(--s4);position:relative;cursor:pointer;list-style:none;border-radius:calc(var(--r-md) - 1px);user-select:none}
196
+ details.card>summary::-webkit-details-marker{display:none}
197
+ details.card>summary::after{content:"";position:absolute;right:18px;top:50%;width:7px;height:7px;margin-top:-5px;border-right:1.75px solid var(--muted);border-bottom:1.75px solid var(--muted);transform:rotate(-45deg);transition:transform .15s}
198
+ details.card[open]>summary::after{transform:rotate(45deg);margin-top:-6px}
199
+ details.card>summary:hover{background:var(--well)}
200
+ details.card>summary:hover::after{border-color:var(--fg)}
201
+ details.card>summary:focus-visible{outline-offset:-2px}
202
+ .revealed:focus,.revealed:focus-visible{outline:none}
203
+ details.card[open]>summary{border-bottom:1px solid var(--border);border-bottom-left-radius:0;border-bottom-right-radius:0}
204
+ details.card>summary .t{font-weight:600;min-width:0;overflow-wrap:anywhere}
205
+ details.card>summary .end{margin-left:auto;display:inline-flex;align-items:center;gap:6px}
206
+ /* nested cards sit inside a body: hairline only, no second shadow */
207
+ .card .card{box-shadow:none}
208
+ .card .card>summary{min-height:38px;padding-top:7px;padding-bottom:7px}
209
+
210
+ /* ---------- tabs ----------
211
+ <div class="tabs" data-tabs><div role="tablist"><button role="tab" aria-selected aria-controls>Label <span class="n">9</span></button></div>
212
+ <div role="tabpanel" id data-label="Label">…</div></div>. shell.js adds .js and the hidden attributes. */
213
+ .tabs{min-width:0}
214
+ .tabs [role=tablist]{display:flex;gap:2px;border-bottom:1px solid var(--border);overflow-x:auto;scrollbar-width:none;position:relative}
215
+ .tabs [role=tablist]::-webkit-scrollbar{display:none}
216
+ .tabs [role=tab]{appearance:none;background:none;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;padding:8px 12px;color:var(--muted);font:500 13px/1.3 var(--font-sans);white-space:nowrap;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:color .15s,border-color .15s}
217
+ .tabs [role=tab]:hover{color:var(--fg)}
218
+ .tabs [role=tab][aria-selected=true]{color:var(--fg);border-bottom-color:var(--accent)}
219
+ .tabs [role=tab][aria-selected=true] .n{background:var(--accent-bg);color:var(--accent-fg)}
220
+ .tabs [role=tab]:focus-visible{outline-offset:-2px}
221
+ .tabs [role=tabpanel]{padding-top:var(--s3);min-width:0}
222
+ .tabs [role=tabpanel][hidden]{display:none}
223
+ /* without JS the buttons do nothing: hide them and stack the panels under their data-label */
224
+ .tabs:not(.js) [role=tablist]{display:none}
225
+ .tabs:not(.js) [role=tabpanel]{display:block}
226
+ .tabs:not(.js) [role=tabpanel]+[role=tabpanel]{margin-top:var(--s3);border-top:1px solid var(--border)}
227
+ .tabs:not(.js) [role=tabpanel][data-label]::before{content:attr(data-label);display:block;margin-bottom:var(--s2);font:600 12px/1.4 var(--font-sans);color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
228
+
229
+ /* ---------- filter bar ----------
230
+ <div class="filterbar" data-filterbar="#LIST"><span class="fl">Severity</span><button data-filter="sev=broken"
231
+ aria-pressed="false" class="chip-sev broken"><span class="dot"></span>Broken <span class="n">2</span></button>…
232
+ <input type="search" data-filter-search placeholder="Search"><p data-filter-count>4 of 4</p></div> */
233
+ .filterbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:var(--s2) var(--s3);background:var(--card);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:var(--shadow-sm)}
234
+ .filterbar .fl{font:600 11px/1 var(--font-sans);color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin:0 2px 0 6px}
235
+ .filterbar .fl:first-child{margin-left:0}
236
+ .filterbar .sep{width:1px;align-self:stretch;margin:2px 4px;background:var(--border)}
237
+ .filterbar [data-filter]{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border:1px solid var(--border);border-radius:var(--r-sm);background:var(--card);color:var(--fg);font:500 12.5px/1 var(--font-sans);white-space:nowrap;cursor:pointer;font-variant-numeric:tabular-nums}
238
+ .filterbar [data-filter]:hover{border-color:var(--border-strong);background:var(--well)}
239
+ .filterbar [data-filter][aria-pressed=true]{background:var(--accent-bg);border-color:var(--accent);color:var(--accent-fg)}
240
+ .filterbar [data-filter][aria-pressed=true]::before{content:"";width:8px;height:4px;margin:-3px 0 0 -1px;border-left:1.75px solid currentColor;border-bottom:1.75px solid currentColor;transform:rotate(-45deg)}
241
+ .filterbar [data-filter] svg{width:14px;height:14px}
242
+ .filterbar [data-filter-search]{flex:1 1 180px;min-width:0;max-width:320px;height:30px;margin-left:auto;padding:0 12px 0 32px;border:1px solid var(--border);border-radius:var(--r-sm);background:var(--page) no-repeat 11px 50%/14px 14px;color:var(--fg);font:400 13px var(--font-sans);}
243
+ .filterbar [data-filter-search]::placeholder{color:var(--muted)}
244
+ .filterbar [data-filter-search]:focus{outline:2px solid var(--accent);outline-offset:0;border-color:var(--accent)}
245
+ .filterbar [data-filter-count]{flex-basis:100%;margin:2px 0 0;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
246
+ .filterbar [data-filter-count]:empty{display:none}
247
+ html:not(.js) .filterbar{display:none}
248
+
249
+ /* ---------- tables ---------- */
250
+ .tscroll{overflow-x:auto;position:relative;border:1px solid var(--border);border-radius:var(--r-md);background:var(--card)}
251
+ .tbl{width:100%;border-collapse:collapse;font-size:13px;font-variant-numeric:tabular-nums}
252
+ .tbl th,.tbl td{text-align:left;padding:7px 10px;border-bottom:1px solid var(--border);vertical-align:middle;overflow-wrap:anywhere}
253
+ .tbl thead th{font-weight:500;font-size:12px;color:var(--muted);background:var(--well);white-space:nowrap}
254
+ .tbl tbody th{font-weight:500}
255
+ .tbl th:first-child,.tbl td:first-child{padding-left:var(--s4)}
256
+ .tbl th:last-child,.tbl td:last-child{padding-right:var(--s4)}
257
+ .tbl tbody tr:last-child>*{border-bottom:0}
258
+ .tbl tbody tr:hover>*{background:color-mix(in srgb,var(--well) 55%,transparent)}
259
+ .tbl .num{text-align:right;font-family:var(--font-mono);white-space:nowrap}
260
+ .tbl .bad{color:var(--sev-broken-fg);font-weight:600}
261
+ .tbl .dim{color:var(--muted)}
262
+
263
+ /* ---------- info grid (Summary option cards) ----------
264
+ <div class="grid"><div class="info"><span class="ic-plate"><svg/></span><span class="lbl">Vendors</span>
265
+ <span class="val">2 of 4</span><span class="sub">GA4 · Meta</span></div></div> */
266
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:var(--s3)}
267
+ .grid>.info{display:grid;grid-template-columns:auto minmax(0,1fr);grid-template-areas:"ic lbl" "val val" "sub sub";align-items:center;gap:6px var(--s2);padding:14px var(--s4);background:var(--card);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:var(--shadow-sm);min-width:0}
268
+ .info .ic-plate{grid-area:ic;display:grid;place-items:center;width:26px;height:26px;border-radius:var(--r-sm);background:var(--well);color:var(--muted)}
269
+ .info .ic-plate svg{width:15px;height:15px}
270
+ .info .lbl{grid-area:lbl;font-size:12.5px;font-weight:500;color:var(--muted)}
271
+ .info .val{grid-area:val;margin-top:4px;font-size:20px;line-height:1.2;font-weight:650;letter-spacing:-.01em;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
272
+ .info .val small{font-size:13px;font-weight:500;color:var(--muted);letter-spacing:0}
273
+ .info .sub{grid-area:sub;font-size:12.5px;color:var(--muted);display:flex;flex-wrap:wrap;gap:4px;align-items:center;min-width:0}
274
+ .grid>.info.wide{grid-column:span 2}
275
+ @media (max-width:520px){.grid>.info.wide{grid-column:auto}}
276
+
277
+ /* ---------- scorecard ----------
278
+ A ring, not a bare number: the arc shows "25 of 100" as a share at a glance and carries the band colour
279
+ as a printable stroke, so the card itself never needs a tint.
280
+ <div class="scorecard band-fail">
281
+ <div class="sc-ring" role="img" aria-label="Score 25 of 100"><svg viewBox="0 0 120 120"><circle class="trk" cx="60" cy="60" r="52"/>
282
+ <circle class="arc" cx="60" cy="60" r="52" pathLength="100" style="--score:25"/></svg><span class="sc-num">25</span><span class="sc-of">/100</span></div>
283
+ <p class="sc-verdict"><svg class="ic"><use href="#sev-broken"/></svg>Fail</p>
284
+ <p class="sc-head">Purchase tracking is broken on mobile.</p>
285
+ <ul class="sc-costs"><li class="sev-broken"><span class="pts">−60</span>…</li></ul>
286
+ <div class="sc-chips chips">…chip-sev lg…</div>
287
+ </div> */
288
+ .band-fail{--band:var(--score-fail);--band-fg:var(--score-fail-fg);--band-bg:var(--score-fail-bg)}
289
+ .band-mid{--band:var(--score-mid);--band-fg:var(--score-mid-fg);--band-bg:var(--score-mid-bg)}
290
+ .band-pass{--band:var(--score-pass);--band-fg:var(--score-pass-fg);--band-bg:var(--score-pass-bg)}
291
+ .scorecard{display:grid;grid-template-columns:120px minmax(0,1fr);grid-template-areas:"ring verdict" "ring head" "ring costs" "chips chips";column-gap:var(--s6);align-items:start;padding:var(--s5) var(--s6);background:var(--card);border:1px solid var(--border);border-top:4px solid var(--band);border-radius:var(--r-lg);box-shadow:var(--shadow-md)}
292
+ .sc-ring{grid-area:ring;position:relative;width:120px;height:120px;display:grid;place-items:center;align-self:center}
293
+ .sc-ring svg{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg)}
294
+ .sc-ring .trk{fill:none;stroke:var(--score-track);stroke-width:10}
295
+ .sc-ring .arc{fill:none;stroke:var(--band);stroke-width:10;stroke-linecap:round;stroke-dasharray:var(--score) 100}
296
+ .sc-num{position:relative;font:700 44px/1 var(--font-sans);letter-spacing:-.03em;color:var(--fg);margin-top:-8px}
297
+ .sc-of{position:absolute;left:0;right:0;top:calc(50% + 18px);text-align:center;font:500 12px/1 var(--font-mono);color:var(--muted)}
298
+ .sc-verdict{grid-area:verdict;align-self:end;display:flex;align-items:center;gap:8px;font:700 30px/1.1 var(--font-sans);letter-spacing:-.02em;color:var(--sev,var(--band))}
299
+ /* the verdict is large text (24–30px bold), where 3:1 is the bar; the solid band colour clears it in both modes
300
+ and keeps the amber of "Needs attention" from turning brown, as the 4.5:1 text step would */
301
+ .sc-verdict .ic{width:26px;height:26px}
302
+ .sc-head{grid-area:head;margin-top:6px;font-size:17px;line-height:1.4;font-weight:500;color:var(--fg);max-width:60ch}
303
+ .sc-costs{grid-area:costs;list-style:none;margin-top:var(--s3);display:grid;gap:6px}
304
+ .sc-costs li{display:grid;grid-template-columns:44px minmax(0,1fr);align-items:baseline;gap:var(--s2);font-size:13.5px;line-height:1.45}
305
+ .sc-costs .pts{font:600 13px/1 var(--font-mono);color:var(--sev-fg,var(--band-fg));font-variant-numeric:tabular-nums}
306
+ .sc-costs .pts+span{min-width:0;overflow-wrap:anywhere}
307
+ .sc-chips{grid-area:chips;margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--border)}
308
+ .scorecard.band-pass .sc-head{color:var(--muted)}
309
+ @media (max-width:640px){
310
+ .scorecard{grid-template-columns:88px minmax(0,1fr);grid-template-areas:"ring verdict" "ring head" "costs costs" "chips chips";column-gap:var(--s4);padding:var(--s4)}
311
+ .sc-ring{width:88px;height:88px;align-self:start}
312
+ .sc-num{font-size:32px;margin-top:-6px}
313
+ .sc-of{top:calc(50% + 12px);font-size:10.5px}
314
+ .sc-verdict{font-size:24px;gap:6px}
315
+ .sc-verdict .ic{width:20px;height:20px}
316
+ .sc-head{font-size:15px;margin-top:4px}
317
+ .sc-costs{margin-top:var(--s4)}
318
+ .sc-costs li{font-size:13px}
319
+ .sc-chips{margin-top:var(--s4);padding-top:var(--s3)}
320
+ }
321
+
322
+ /* ---------- motion and print ---------- */
323
+ @media (prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}
324
+ @media print{
325
+ body{background:#fff;font-size:12.5px}
326
+ .card,.grid>.info,.scorecard,.filterbar{box-shadow:none}
327
+ .chip,.badge,.n{background:none!important;border:1px solid currentColor}
328
+ .chip.zero{border-color:var(--border)}
329
+ .tbl thead th{background:none;border-bottom:1px solid #000}
330
+ .info .ic-plate{background:none}
331
+ details.card>summary:hover{background:none}
332
+ details.card>summary::after,.filterbar,.tabs [role=tablist]{display:none}
333
+ .tabs [role=tabpanel],.tabs [role=tabpanel][hidden]{display:block}
334
+ .tabs [role=tabpanel][data-label]::before{content:attr(data-label);display:block;margin:var(--s2) 0;font:600 11px/1.4 var(--font-sans);text-transform:uppercase;letter-spacing:.04em}
335
+ /* writePdf prints with JavaScript off, so a card marked data-print-open is opened by CSS alone */
336
+ details.card[data-print-open]::details-content{content-visibility:visible;display:block}
337
+ details.card[data-print-open]>summary{border-bottom:1px solid var(--border)}
338
+ .card,.grid>.info,.scorecard,.tbl tr{break-inside:avoid}
339
+ .scorecard{border-top-width:4px}
340
+ }`;
341
+ // ---------- end of components ----------
342
+
343
+ // ---------- shell: top bar, rail, layout, 390 px, no-JS and print ----------
344
+ const SHELL = `/* Page shell: skip link, top bar, rail (expanded 200 px, collapsed 48 px, strip under 720 px), main, section switching,
345
+ expand/collapse row, reveal flash, footer, no-JS and print. Driven by js/shell.js through html.js, html[data-rail],
346
+ body[data-state] and the hidden attribute. */
347
+
348
+ /* JS hides with the hidden attribute; a section rule that sets display must not bring a hidden row back on screen. */
349
+ @media screen{[hidden]{display:none!important}}
350
+
351
+ /* shell-level helpers (C had them; components.css does not) */
352
+ .js-only{display:none!important}
353
+ html.js .js-only{display:inline-flex!important}
354
+ html.js .sec-tools.js-only{display:flex!important}
355
+ .logo{display:inline-flex}
356
+ .logo .l-dark{display:none}
357
+ html[data-theme="dark"] .logo .l-light{display:none}
358
+ html[data-theme="dark"] .logo .l-dark{display:inline}
359
+ @media (prefers-color-scheme:dark){html:not([data-theme="light"]) .logo .l-light{display:none}html:not([data-theme="light"]) .logo .l-dark{display:inline}}
360
+ .eyebrow{font:600 11px/1.4 var(--font-sans);text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
361
+ .mock-note{display:flex;gap:var(--s2);align-items:flex-start;padding:var(--s2) var(--s3);margin:var(--s3) 0 var(--s4);border:1px dashed var(--sev-info);border-radius:var(--r-sm);color:var(--sev-info-fg);font-size:12.5px}
362
+ .mock-note .ic{margin-top:2px}
363
+
364
+ .skip{position:absolute;left:var(--s4);top:-48px;z-index:50;padding:var(--s2) var(--s3);background:var(--card);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--fg)}
365
+ .skip:focus{top:var(--s2)}
366
+
367
+ /* ---------- top bar ---------- */
368
+ .top{position:sticky;top:0;z-index:30;height:var(--top);display:flex;align-items:center;gap:var(--s3);padding:0 var(--s4);background:var(--card);border-bottom:1px solid var(--border);white-space:nowrap}
369
+ .top .brand{display:inline-flex;align-items:center;flex:none}
370
+ .top .l-mark{display:none}
371
+ .top-site{display:flex;align-items:baseline;gap:var(--s2);min-width:0;overflow:hidden}
372
+ .top-site b{font-weight:600;overflow:hidden;text-overflow:ellipsis}
373
+ .top-date{font-size:12.5px;font-variant-numeric:tabular-nums}
374
+ .top-verdict{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 9px;border:1px solid var(--border);border-color:color-mix(in srgb,var(--v) 45%,var(--border));border-radius:var(--r-sm);background:var(--card);color:var(--v-fg);font:500 12.5px/1 var(--font-sans);font-variant-numeric:tabular-nums;flex:none}
375
+ .top-verdict b{font-weight:700;font-size:14px}
376
+ .top-verdict .sc-l{color:var(--muted)}
377
+ .top-verdict:hover{text-decoration:none;filter:brightness(.97)}
378
+ .top-verdict .sc{font-weight:500;color:var(--muted)}
379
+ .v-fail{--v:var(--score-fail);--v-fg:var(--score-fail-fg);--v-bg:var(--score-fail-bg)}
380
+ .v-mid{--v:var(--score-mid);--v-fg:var(--score-mid-fg);--v-bg:var(--score-mid-bg)}
381
+ .v-pass{--v:var(--score-pass);--v-fg:var(--score-pass-fg);--v-bg:var(--score-pass-bg)}
382
+ .top-end{margin-left:auto;display:flex;align-items:center;gap:var(--s2)}
383
+ .byline{display:inline-flex;align-items:center;gap:var(--s2);font-size:12px;color:var(--muted);margin-right:var(--s2)}
384
+ .mock-switch{align-items:center;gap:.3em;height:28px;padding:0 10px;border:1px dashed var(--border-strong);border-radius:var(--r-sm);background:none;color:var(--muted);font:500 12px var(--font-sans);cursor:pointer}
385
+ .mock-switch:hover{color:var(--fg);border-color:var(--muted)}
386
+ .mock-switch[aria-pressed=true]{border-color:var(--sev-info);color:var(--sev-info-fg);background:var(--sev-info-bg)}
387
+ .icon-btn{align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:0;border-radius:var(--r-sm);background:none;color:var(--muted);cursor:pointer}
388
+ .icon-btn:hover{background:var(--well);color:var(--fg)}
389
+
390
+ /* ---------- shell and rail ---------- */
391
+ .shell{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:start}
392
+ .rail{position:sticky;top:var(--top);z-index:20;width:var(--rail);height:calc(100vh - var(--top));overflow:hidden auto;padding:var(--s2) 4px;border-right:1px solid var(--border);background:var(--card)}
393
+ html.rail-anim .rail{transition:width .2s ease}
394
+ .rail ul{list-style:none;margin:0;padding:0;display:grid;gap:2px}
395
+ .r-head{display:flex;margin-bottom:var(--s2);padding-bottom:var(--s2);border-bottom:1px solid var(--border)}
396
+ .r-toggle,.rail a{position:relative;display:flex;align-items:center;gap:10px;height:36px;padding:0 10px 0 11px;border-radius:var(--r-sm);color:var(--fg);font-size:13.5px;white-space:nowrap}
397
+ .r-toggle{width:40px;border:0;background:none;color:var(--muted);cursor:pointer}
398
+ .r-toggle .ic{transform:rotate(180deg);transition:transform .2s}
399
+ .r-toggle:hover,.rail a[href]:hover{background:var(--well);text-decoration:none}
400
+ .rail a[aria-disabled]{color:var(--muted);opacity:.6}
401
+ .rail a[aria-current]{background:var(--accent-bg);color:var(--accent-fg);font-weight:600;box-shadow:inset 3px 0 var(--accent)}
402
+ .rail .ic{width:18px;height:18px}
403
+ .r-l{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;transition:opacity .15s}
404
+ .r-t{display:inline-flex;align-items:center;min-width:20px;justify-content:center;padding:0 6px;border-radius:var(--r-pill);background:var(--t-bg);color:var(--t-fg);font:500 11.5px/18px var(--font-mono);font-variant-numeric:tabular-nums}
405
+ .r-t .ic{width:14px;height:14px;color:var(--t)}
406
+ .r-t:has(.ic){background:none;padding:0}
407
+ .r-d{display:none;position:absolute;left:26px;top:7px;width:6px;height:6px;border-radius:50%;background:var(--t);box-shadow:0 0 0 2px var(--card)}
408
+ [data-tone=broken]{--t:var(--sev-broken);--t-fg:var(--sev-broken-fg);--t-bg:var(--sev-broken-bg)}
409
+ [data-tone=risk]{--t:var(--sev-risk);--t-fg:var(--sev-risk-fg);--t-bg:var(--sev-risk-bg)}
410
+ [data-tone=warning]{--t:var(--sev-warning);--t-fg:var(--sev-warning-fg);--t-bg:var(--sev-warning-bg)}
411
+ [data-tone=info]{--t:var(--sev-info);--t-fg:var(--sev-info-fg);--t-bg:var(--sev-info-bg)}
412
+ [data-tone=ok]{--t:var(--sev-ok);--t-fg:var(--sev-ok-fg);--t-bg:var(--sev-ok-bg)}
413
+ [data-tone=completed]{--t:var(--run-completed);--t-fg:var(--run-completed-fg);--t-bg:var(--run-completed-bg)}
414
+ [data-tone=incomplete]{--t:var(--run-incomplete);--t-fg:var(--run-incomplete-fg);--t-bg:var(--run-incomplete-bg)}
415
+ [data-tone=neutral]{--t:var(--muted);--t-fg:var(--muted);--t-bg:var(--well)}
416
+ /* tooltip from data-tip: always on the toggle, on the links only when collapsed */
417
+ .rail [data-tip]::after{content:attr(data-tip);position:absolute;left:calc(100% + 8px);top:50%;transform:translateY(-50%);padding:4px 8px;border-radius:var(--r-sm);background:var(--fg);color:var(--card);font:500 12px/1.4 var(--font-sans);white-space:nowrap;pointer-events:none;opacity:0;visibility:hidden;z-index:40;box-shadow:var(--shadow-md)}
418
+ .r-toggle:is(:hover,:focus-visible)::after{opacity:1;visibility:visible}
419
+ @media (min-width:720px){
420
+ html[data-rail=collapsed] .rail{width:48px;overflow:visible}
421
+ html[data-rail=collapsed] .r-toggle .ic{transform:none}
422
+ html[data-rail=collapsed] .r-l,html[data-rail=collapsed] .r-t{opacity:0;width:0;min-width:0;padding:0;overflow:hidden}
423
+ html[data-rail=collapsed] .r-d{display:block}
424
+ html[data-rail=collapsed] .rail a[data-tip]:is(:hover,:focus-visible)::after{opacity:1;visibility:visible}
425
+ }
426
+
427
+ /* ---------- main and sections ---------- */
428
+ main{min-width:0;padding:var(--s5) var(--s6) var(--s7)}
429
+ main:focus{outline:none}
430
+ .sec{position:relative}
431
+ .sec>h2:focus{outline:none}
432
+ main [id]{scroll-margin-top:calc(var(--top) + var(--s3))}
433
+ html:not(.js) main>.sec+.sec{margin-top:var(--s7);padding-top:var(--s5);border-top:1px solid var(--border)}
434
+ /* The failed run is a mockup-only state reached through the top-bar switch, so without JS it stays out of the page. */
435
+ html:not(.js) main>.sec[id^="failed-"]{display:none}
436
+ /* expand / collapse all, top right of the section, level with its h2 */
437
+ .sec-tools{position:absolute;top:-4px;right:0;gap:var(--s1)}
438
+ .sec-tools .btn{height:26px;padding:0 8px;font-size:12px;color:var(--muted)}
439
+ .sec-tools .btn:hover{color:var(--fg)}
440
+ .sec:has(>.sec-tools) h2{padding-right:190px}
441
+ .fb-clear{height:28px}
442
+ /* revealed element: a 1.5 s accent outline; the tint fades unless motion is reduced */
443
+ .flash{animation:aitd-flash 1.5s ease-out}
444
+ @keyframes aitd-flash{from{background-color:var(--accent-bg)}}
445
+
446
+ .footer{display:flex;align-items:center;gap:var(--s4);padding:var(--s4);border-top:1px solid var(--border);background:var(--card)}
447
+ .footer img{vertical-align:-3px}
448
+
449
+ /* ---------- under 720 px: the rail becomes a strip under the top bar ---------- */
450
+ @media (max-width:719.98px){
451
+ :root{--top:44px}
452
+ .top{gap:var(--s2);padding:0 var(--s3)}
453
+ .top .l-lockup,.top-site .eyebrow,.top-date,.byline{display:none}
454
+ .top .l-mark{display:block}
455
+ .mock-switch .m-pre{display:none}
456
+ .shell{display:block}
457
+ .rail{top:var(--top);width:auto;height:auto;padding:0;border-right:0;border-bottom:1px solid var(--border);overflow:visible}
458
+ .r-head{display:none}
459
+ .rail ul{display:flex;gap:0;padding:0 var(--s2);overflow-x:auto;scrollbar-width:none;-webkit-mask-image:linear-gradient(to right,black calc(100% - 28px),transparent);mask-image:linear-gradient(to right,black calc(100% - 28px),transparent)}
460
+ .rail ul::-webkit-scrollbar{display:none}
461
+ .rail a{height:40px;gap:6px;padding:0 10px;border-radius:0;font-size:13px;border-bottom:2px solid transparent}
462
+ .rail a>.ic{display:none}
463
+ .rail a[aria-current]{background:none;box-shadow:none;border-bottom-color:var(--accent)}
464
+ .rail [data-tip]::after{display:none}
465
+ main{padding:var(--s4) var(--s4) var(--s6)}
466
+ main [id]{scroll-margin-top:calc(var(--top) + 42px + var(--s3))}
467
+ .sec:has(>.sec-tools) h2{padding-right:150px}
468
+ .sec-tools .btn{padding:0 6px}
469
+ }
470
+
471
+ /* ---------- print: the no-JS layout, sections stacked one per page ---------- */
472
+ @media print{
473
+ :root{--top:0px}
474
+ .top,.rail,.skip,.sec-tools,.mock-note,.fb-clear,html.js .js-only,html.js .sec-tools.js-only{display:none!important}
475
+ .shell{display:block}
476
+ main{padding:0}
477
+ main>.sec[hidden],[data-select-pane][hidden],[data-inspector][hidden],[data-filter-group][hidden],[data-text][hidden]{display:block!important}
478
+ li[data-text][hidden]{display:list-item!important}
479
+ tr[data-text][hidden]{display:table-row!important}
480
+ main>.sec[id^="failed-"]{display:none!important}
481
+ main>.sec+.sec{break-before:page}
482
+ .flash{outline:0!important;animation:none}
483
+ }`;
484
+ // ---------- end of shell ----------
485
+
486
+ // ---------- product: dark-glyph plates (TikTok, X), initials for a platform without artwork, a white label's name ----------
487
+ const PRODUCT = `.plate{fill:var(--card);stroke:var(--border);stroke-width:1}
488
+ .plat-fb{display:inline-grid;place-items:center;border-radius:var(--r-sm);background:var(--well);color:var(--muted);font:500 10px var(--font-mono);flex:none}
489
+ .plat-fb.s12{width:12px;height:12px;font-size:8px}
490
+ .plat-fb.s14{width:14px;height:14px;font-size:9px}
491
+ .plat-fb.s16{width:16px;height:16px}
492
+ .plat-fb.s18{width:18px;height:18px;font-size:10px}
493
+ .plat-fb.s20{width:20px;height:20px;font-size:11px}
494
+ .brand-name{font:600 16px var(--font-sans);color:var(--fg)}`;
495
+ // ---------- end of product ----------
496
+
497
+ // ---------- summary: #summary ----------
498
+ const SUMMARY = `/* Summary (SPEC §2): scorecard with status chips, then six "about this run" cards.
499
+ Scoped by .sm, the class on both #summary and #failed-summary; components.css supplies .scorecard, .chip, .grid>.info. */
500
+ .sm>.scorecard{margin-top:var(--s4)}
501
+ .sm>.sm-about{margin-top:var(--s5)}
502
+
503
+ /* scorecard: a larger ring than the gallery's 120px; this is the one big thing on the screen */
504
+ .sm .scorecard{grid-template-columns:136px minmax(0,1fr);row-gap:0;padding:var(--s5) var(--s6) var(--s4)}
505
+ .sm .sc-ring{width:136px;height:136px}
506
+ .sm .sc-ring .trk,.sm .sc-ring .arc{stroke-width:9}
507
+ .sm .sc-num{font-size:48px;margin-top:-10px}
508
+ .sm .sc-of{top:calc(50% + 20px)}
509
+ .sm .sc-num.sm-none{margin-top:0;color:var(--muted);font-weight:500}
510
+ .sm .sc-verdict{margin-top:2px}
511
+ .sm .sc-head{margin-top:var(--s1)}
512
+ /* the model's summary (one line, under the headline) and the baseline's accepted line (p.meta, under the chips) get
513
+ rows of their own; with nothing in them the rows take no height. The class name stays out of the stylesheet, so
514
+ a page holds it only when a finding was accepted. */
515
+ .sm .scorecard{grid-template-areas:"ring verdict" "ring head" "ring ai" "ring costs" "chips chips" "acc acc"}
516
+ .sm .sc-ai{grid-area:ai;margin-top:var(--s1);max-width:72ch;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
517
+ .sm .sc-ai .ai-l{margin-right:4px;font:600 11px/1 var(--font-sans);text-transform:uppercase;letter-spacing:.04em}
518
+ .sm .scorecard>p.meta{grid-area:acc;margin-top:var(--s2);font-size:12.5px;color:var(--muted)}
519
+ .sm .info .val .sm-note{font-size:14px;font-weight:500;color:var(--muted)}
520
+
521
+ /* explanation lines: one subgrid so points, text, platform chips and the where chip line up across lines */
522
+ .sm .sc-costs{grid-template-columns:44px auto auto auto minmax(0,1fr);column-gap:var(--s3);row-gap:0;margin-top:var(--s3)}
523
+ .sm .sc-costs li{grid-column:1/-1;display:grid;grid-template-columns:subgrid;font-size:14px}
524
+ .sm .sc-costs li>a{grid-column:1/-1;display:grid;grid-template-columns:subgrid;align-items:center;min-height:32px;padding:3px var(--s2);margin:0 calc(-1 * var(--s2));border-radius:var(--r-sm);color:var(--fg)}
525
+ .sm .sc-costs li>a:hover{background:var(--well);text-decoration:none}
526
+ .sm .sc-costs .pts{grid-column:1;font-size:14px}
527
+ .sm .sc-costs .what{grid-column:2;min-width:0}
528
+ .sm .sc-costs .pl{grid-column:3;display:flex;gap:4px}
529
+ .sm .sc-costs .wh{grid-column:4;display:flex;gap:4px}
530
+ .sm .sc-costs.nopts{grid-template-columns:auto auto auto minmax(0,1fr)}
531
+ .sm .sc-costs.nopts .what{grid-column:1}
532
+ .sm .sc-costs.nopts .pl{grid-column:2}
533
+ .sm .sc-costs.nopts .wh{grid-column:3}
534
+ .sm .sc-costs code{font-size:12.5px}
535
+ .sm .sc-chips{margin-top:var(--s4);padding-top:var(--s4)}
536
+ .sm .sc-chips .chip.zero{cursor:default}
537
+
538
+ /* about this run: 3 / 2 / 1 columns, every card the same height */
539
+ .sm .sm-about{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:1fr;gap:var(--s3)}
540
+ .sm .sm-about>.info{grid-template-rows:auto 1fr auto;align-items:start;row-gap:var(--s2);padding:var(--s4)}
541
+ .sm .info .lbl{align-self:center;display:inline-flex;align-items:center;gap:2px;justify-self:start;color:var(--muted)}
542
+ .sm .info .lbl svg{opacity:.55}
543
+ .sm .info .lbl:hover{color:var(--accent-fg);text-decoration:none}
544
+ .sm .info .lbl:hover svg{opacity:1}
545
+ .sm .info .val{margin-top:0;align-self:center;font-size:18px}
546
+ .sm .info .val .chip{vertical-align:2px}
547
+ .sm .info .sub{align-self:end}
548
+ .sm .sm-chipval .val .chips{gap:4px}
549
+ /* a vendor that was not seen keeps its word but loses colour, logo included (as .chip.zero) */
550
+ .sm .chip.sm-unseen{--k-fg:var(--muted);--k-bg:transparent;border-color:var(--border);border-style:dashed}
551
+ .sm .chip.sm-unseen svg{filter:grayscale(1);opacity:.5}
552
+
553
+ /* journeys matrix: rows = journeys, columns = viewports, cell = run state + logos seen */
554
+ .sm-matrix{border-collapse:collapse;font-size:13px;line-height:1}
555
+ .sm-matrix th,.sm-matrix td{padding:0}
556
+ .sm-matrix thead th{padding:0 0 4px 9px;text-align:left;color:var(--muted)}
557
+ .sm-matrix tbody th{padding-right:var(--s2);text-align:left;font-weight:500;color:var(--fg)}
558
+ .sm-matrix td a{display:inline-flex;align-items:center;gap:5px;padding:3px 8px 3px 6px;border-radius:var(--r-sm);color:var(--fg)}
559
+ .sm-matrix td a:hover{background:var(--well);text-decoration:none}
560
+ .sm-matrix .chip{width:20px;height:20px;padding:0;justify-content:center}
561
+ .sm-matrix .chip>svg:first-child{margin:0}
562
+ .sm-logo{display:inline-flex}
563
+
564
+ /* four vendor chips wrap 3 + 1 in a third-width card; two by two reads as a set */
565
+ @media (min-width:1024px){.sm .sm-chipval .val .chips{display:grid;grid-template-columns:repeat(2,max-content)}}
566
+ @media (max-width:1023px){.sm .sm-about{grid-template-columns:repeat(2,minmax(0,1fr))}}
567
+ @media (max-width:900px){
568
+ .sm .sc-costs,.sm .sc-costs.nopts{display:grid;grid-template-columns:minmax(0,1fr)}
569
+ .sm .sc-costs li,.sm .sc-costs li>a{grid-column:auto;grid-template-columns:none}
570
+ .sm .sc-costs li>a{display:flex;flex-wrap:wrap;align-items:center;gap:4px var(--s2);padding:var(--s1) var(--s2)}
571
+ .sm .sc-costs .pts{flex:0 0 36px}
572
+ .sm .sc-costs .what{flex:1 1 calc(100% - 52px)}
573
+ .sm .sc-costs:not(.nopts) .pl{margin-left:44px}
574
+ .sm .sc-costs:not(.nopts) .pl:empty+.sr+.wh,.sm .sc-costs:not(.nopts) .pl:empty~.wh{margin-left:44px}
575
+ }
576
+ @media (max-width:640px){
577
+ .sm .scorecard{grid-template-columns:96px minmax(0,1fr);grid-template-areas:"ring verdict" "ring head" "ai ai" "costs costs" "chips chips" "acc acc";padding:var(--s4)}
578
+ .sm .sc-ring{width:96px;height:96px}
579
+ .sm .sc-num{font-size:34px;margin-top:-6px}
580
+ .sm .sc-of{top:calc(50% + 13px)}
581
+ }
582
+ @media (max-width:599px){.sm .sm-about{grid-template-columns:minmax(0,1fr);grid-auto-rows:auto}}
583
+ @media print{
584
+ .sm .sc-ai{white-space:normal}
585
+ .sm .sc-costs li>a{padding-left:0;padding-right:0;margin:0}
586
+ .sm .sm-about{grid-template-columns:repeat(3,minmax(0,1fr))}
587
+ .sm .chip.sm-unseen{border-style:dashed}
588
+ }`;
589
+ // ---------- end of summary ----------
590
+
591
+ // ---------- findings: #findings ----------
592
+ const FINDINGS = `/* Findings (Direction D). Everything is scoped under .fnd so no rule leaks into another section.
593
+ Generic pieces (.card, .tabs, .chip, .filterbar, .badge, .n) come from components.css; this file only tunes them. */
594
+
595
+ /* ---------- filter bar: one line at 1280, a "Filters (n)" fold under 720 ---------- */
596
+ .fnd .filterbar{position:relative;margin-top:var(--s2);gap:8px 10px;padding:8px 12px;counter-reset:fc}
597
+ /* desktop: row 1 = severity, owner, clear and "n of m"; row 2 = platform, journey, viewport and the search.
598
+ The groups wrapper dissolves so every piece can be ordered; ::after forces the row break. */
599
+ .fnd .fb-groups{display:contents}
600
+ .fnd .fb-groups>.fg{order:1}
601
+ .fnd .filterbar::after{content:"";order:4;flex:1 0 100%;height:0;margin-top:-8px}
602
+ .fnd .fb-rest{order:5;display:flex;flex-wrap:wrap;align-items:center;gap:6px 0;min-width:0}
603
+ .fnd .fg{display:inline-flex;align-items:center;gap:4px;flex:none}
604
+ .fnd .fg+.fg,.fnd .fb-rest .fg+.fg{margin-left:8px;padding-left:8px;border-left:1px solid var(--border)}
605
+ .fnd .filterbar .fl{margin:0 4px 0 0;font-size:10.5px;letter-spacing:.06em}
606
+ .fnd .filterbar [data-filter]{height:26px;padding:0 8px;gap:5px;font-size:12px}
607
+ .fnd .filterbar [data-filter] .n{min-width:0;padding:0;background:none;font-size:11px;line-height:1;color:var(--muted)}
608
+ .fnd .filterbar [data-filter][aria-pressed=true] .n{color:var(--accent-fg)}
609
+ .fnd .filterbar [data-filter]:disabled,.fnd .filterbar [data-filter].zero{color:var(--muted);cursor:default;background:transparent;border-style:dashed}
610
+ .fnd .filterbar [data-filter]:disabled .dot{background:var(--border-strong)}
611
+ .fnd .filterbar [data-filter].chip-vp svg{color:var(--k)}
612
+ .fnd .filterbar [data-filter][aria-pressed=true] svg{color:currentColor}
613
+ .fnd .filterbar [data-filter][aria-pressed=true]{counter-increment:fc}
614
+ /* the search field's icon is the sprite's ui-search inside the label (the CSS may carry no data URI) */
615
+ .fnd .fb-search{order:6;position:relative;display:flex;align-items:center;flex:1 1 120px;min-width:100px;max-width:260px;margin-left:auto}
616
+ .fnd .fb-search>svg{position:absolute;left:11px;width:14px;height:14px;color:var(--muted);pointer-events:none}
617
+ .fnd .filterbar [data-filter-search]{flex:1 1 auto;width:100%;max-width:none;margin-left:0;height:28px}
618
+ .fnd .fb-clear{order:2;margin-left:auto;height:28px;padding:0 10px;border:0;border-radius:var(--r-pill);background:none;color:var(--accent-fg);font:500 12.5px var(--font-sans);cursor:pointer}
619
+ .fnd .fb-clear:hover{background:var(--accent-bg)}
620
+ .fnd .fb-clear[hidden]~[data-filter-count]{margin-left:auto}
621
+ .fnd .filterbar [data-filter-count]{order:3;flex:none;margin:0 0 0 -4px;min-width:46px;text-align:right;font:500 12px var(--font-mono);color:var(--muted);white-space:nowrap}
622
+ .fnd .filterbar [data-filter-count]:empty{display:block}
623
+ .fnd .fb-fold{display:none}
624
+ @media (max-width:719px){
625
+ .fnd .filterbar{padding:8px}
626
+ .fnd .fb-search{order:1;flex:1 1 100px;min-width:100px;max-width:none;margin-left:0}
627
+ .fnd .fb-fold{order:2;display:inline-block;appearance:none;-webkit-appearance:none;margin:0;height:28px;padding:0 12px;border:1px solid var(--border);border-radius:var(--r-pill);background:var(--card);color:var(--fg);cursor:pointer;font:500 12.5px/26px var(--font-sans);white-space:nowrap}
628
+ .fnd .fb-fold::after{content:attr(data-label)}
629
+ .fnd .filterbar:has([aria-pressed=true]) .fb-fold{border-color:var(--accent);background:var(--accent-bg);color:var(--accent-fg)}
630
+ .fnd .filterbar:has([aria-pressed=true]) .fb-fold::after{content:attr(data-label) " (" counter(fc) ")"}
631
+ .fnd .filterbar:has(.fb-fold:focus-visible) .fb-fold{outline:2px solid var(--accent);outline-offset:2px}
632
+ .fnd .filterbar::after{display:none}
633
+ .fnd .fb-clear{order:3;margin-left:0}
634
+ .fnd .filterbar [data-filter-count]{order:4;margin:0}
635
+ /* folded: the boxes stay (so the pressed chips are counted) but cannot be seen or focused */
636
+ .fnd .fb-groups{order:5;flex:1 1 100%;display:flex;flex-direction:column;align-items:stretch;gap:10px;max-height:0;overflow:hidden;visibility:hidden}
637
+ .fnd .filterbar:has(.fb-fold:checked) .fb-groups{max-height:none;overflow:visible;visibility:visible;padding-top:8px;border-top:1px solid var(--border)}
638
+ .fnd .fb-groups>.fg,.fnd .fb-rest{order:0}
639
+ .fnd .fb-rest{display:flex;flex-direction:column;align-items:stretch;gap:10px}
640
+ .fnd .fg{flex-wrap:wrap;gap:6px}
641
+ .fnd .fg+.fg,.fnd .fb-rest .fg+.fg{margin:0;padding:0;border:0}
642
+ .fnd .filterbar .fl{flex-basis:100%;margin:0}
643
+ .fnd .filterbar [data-filter]{height:30px}
644
+ }
645
+
646
+ /* ---------- groups: a line with the group chip and its count ---------- */
647
+ .fnd-list{margin-top:var(--s4)}
648
+ .fnd .group{margin-top:var(--s4)}
649
+ .fnd .group:first-child{margin-top:0}
650
+ .fnd .group>h3,.fnd .group>summary>h3{display:flex;align-items:center;gap:var(--s3);flex:1;margin:0}
651
+ .fnd .group>h3::after,.fnd .group>summary>h3::after{content:"";flex:1;height:1px;background:var(--border)}
652
+ .fnd .group>h3{margin-bottom:var(--s2)}
653
+ .fnd details.group>summary{display:flex;align-items:center;position:relative;padding:2px 28px var(--s2) 0;cursor:pointer;list-style:none;user-select:none}
654
+ .fnd details.group>summary::-webkit-details-marker{display:none}
655
+ .fnd details.group>summary::after{content:"";position:absolute;right:16px;top:50%;width:7px;height:7px;margin-top:-6px;border-right:1.75px solid var(--muted);border-bottom:1.75px solid var(--muted);transform:rotate(-45deg);transition:transform .15s}
656
+ .fnd details.group[open]>summary::after{transform:rotate(45deg);margin-top:-7px}
657
+ .fnd details.group>summary:hover::after{border-color:var(--fg)}
658
+ .fnd details.group>summary:focus-visible{outline-offset:2px;border-radius:var(--r-sm)}
659
+ .fnd .chip.lg b{font-weight:700}
660
+ .fnd .g-rows>.card+.card{margin-top:6px}
661
+ .fnd [hidden]{display:none!important}
662
+
663
+ /* ---------- the collapsed row: badge · title · owner · platform · where, in aligned columns ---------- */
664
+ .fnd details.card>summary,.fnd .card.line{display:grid;grid-template-columns:92px minmax(0,1fr) 112px 150px 132px;align-items:center;column-gap:var(--s3);row-gap:6px;flex-wrap:nowrap;min-height:44px;padding:8px 40px 8px var(--s3)}
665
+ .fnd .card.line{min-height:38px;padding-top:6px;padding-bottom:6px;position:relative}
666
+ .fnd .card>summary>.badge,.fnd .card.line>.badge{justify-self:start}
667
+ .fnd .card .f-t{font-weight:500;min-width:0;overflow-wrap:anywhere;line-height:1.4}
668
+ .fnd .card.line .f-t{font-weight:400}
669
+ .fnd .card .f-t .muted{font-weight:400}
670
+ .fnd .f-t{grid-column:2}
671
+ .fnd .c-owner{grid-column:3}
672
+ .fnd .c-plat{grid-column:4}
673
+ .fnd .c-where{grid-column:5}
674
+ .fnd .card.line .f-t{grid-column:2 / 4}
675
+ .fnd .card.line .c-owner{display:none}
676
+ .fnd .c-owner,.fnd .c-plat,.fnd .c-where{display:flex;gap:4px;align-items:center;min-width:0}
677
+ .fnd .card.line .c-owner{display:none}
678
+ .fnd .run-multi svg:first-child{margin-left:0}
679
+ .fnd .run-multi .vp-desktop{color:var(--vp-desktop-fg)}
680
+ .fnd .run-multi .vp-mobile{color:var(--vp-mobile-fg)}
681
+ .fnd .run-multi{gap:3px}
682
+ .fnd .card .f-t code{font-size:12px}
683
+ .fnd .card.line .go{position:absolute;right:12px;top:50%;margin-top:-10px;display:grid;place-items:center;width:20px;height:20px;color:var(--muted);border-radius:var(--r-sm)}
684
+ .fnd .card.line .go:hover{color:var(--fg);background:var(--well);text-decoration:none}
685
+ .fnd .card.line .go svg{width:14px;height:14px}
686
+ .fnd .card.line.flash,.fnd .card.flash{animation:aitd-flash 1.5s ease-out}
687
+ @media (max-width:719px){
688
+ .fnd details.card>summary,.fnd .card.line{display:flex;flex-wrap:wrap;column-gap:8px;padding:8px 36px 8px 12px}
689
+ .fnd .card>summary>.badge,.fnd .card.line>.badge{order:1}
690
+ .fnd .card .c-where{order:2;margin-left:auto}
691
+ .fnd .card .f-t,.fnd .card.line .f-t{order:3;flex:1 0 100%}
692
+ .fnd .card .c-owner{order:4}
693
+ .fnd .card .c-plat{order:5}
694
+ .fnd .c-owner:empty,.fnd .c-plat:empty,.fnd .c-where:empty{display:none}
695
+ .fnd .card.line .go{right:8px;top:12px;margin-top:0}
696
+ }
697
+
698
+ /* ---------- the open row ---------- */
699
+ .fnd .card>.body{padding:0 var(--s4) var(--s3)}
700
+ .fnd .card .tabs [role=tab]{padding:8px 12px 7px}
701
+ .fnd .card .tabs [role=tabpanel]{padding-top:var(--s3)}
702
+ .fnd-impact{display:flex;gap:8px;align-items:center;padding:6px 10px;margin-bottom:var(--s3);border-radius:var(--r-sm);background:var(--sev-bg);color:var(--sev-fg);font-size:13px;line-height:1.4}
703
+ .fnd-impact .ic{width:14px;height:14px}
704
+ .fnd-kv{width:100%;border-collapse:collapse;font-size:13px}
705
+ .fnd-kv th,.fnd-kv td{text-align:left;padding:5px 0;border-bottom:1px solid var(--border);vertical-align:middle;overflow-wrap:anywhere}
706
+ .fnd-kv tr:last-child>*{border-bottom:0}
707
+ .fnd-kv th{width:128px;padding-right:var(--s3);font-weight:500;color:var(--muted);font-size:12.5px;white-space:nowrap}
708
+ .fnd-kv td .chip{margin-right:4px}
709
+ .fnd-kv td code{font-size:12px}
710
+ .fnd-fine{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--s3)}
711
+ .fnd-line{margin-top:var(--s3);font-size:13px}
712
+ .fnd-raw{margin-top:var(--s2);font-size:12.5px}
713
+ .fnd-raw>summary{cursor:pointer;color:var(--accent-fg);list-style:none;display:inline-flex;align-items:center;gap:4px}
714
+ .fnd-raw>summary::-webkit-details-marker{display:none}
715
+ .fnd-raw>summary::before{content:"";width:6px;height:6px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform .15s}
716
+ .fnd-raw[open]>summary::before{transform:rotate(45deg)}
717
+ .fnd-raw pre{margin-top:var(--s2);max-height:240px;overflow:auto}
718
+ .fnd-fix{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;font-size:13.5px}
719
+ .fnd-snip{margin-top:var(--s3);padding:var(--s3);overflow-x:auto;white-space:pre}
720
+ .fnd-snip code,.fnd pre code{background:none;padding:0;font-size:12px;overflow-wrap:normal}
721
+ .fnd-where{list-style:none;display:grid;gap:6px}
722
+ .fnd-where li{display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px;font-size:13px}
723
+ .fnd-step{min-width:0}
724
+ .fnd .fnd-h{margin:var(--s4) 0 var(--s2);display:flex;align-items:center;gap:8px;font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
725
+ .fnd .fnd-h .chip{text-transform:none;letter-spacing:0}
726
+ .fnd .fnd-h>svg{width:14px;height:14px}
727
+ .fnd-fix>span{min-width:0}
728
+ .fnd-recipe{list-style:none;counter-reset:rc;display:grid;gap:2px;margin:0;padding:var(--s2) var(--s3);background:var(--well);border-radius:var(--r-sm)}
729
+ .fnd-recipe li{counter-increment:rc;display:flex;gap:10px;font-size:12px;line-height:1.6}
730
+ .fnd-recipe li::before{content:counter(rc);min-width:18px;text-align:right;color:var(--muted);font:500 11px/1.9 var(--font-mono)}
731
+ .fnd-recipe code{background:none;padding:0;overflow-wrap:anywhere}
732
+ .fnd-links{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5);font-size:13px}
733
+ .fnd-links a{display:inline-flex;align-items:center;gap:4px}
734
+
735
+ @media print{
736
+ .fnd .filterbar{display:none}
737
+ .fnd details.group[data-filter-group]::details-content{content-visibility:visible;display:block}
738
+ .fnd details.group>summary::after{display:none}
739
+ .fnd .card{break-inside:avoid;box-shadow:none}
740
+ .fnd-impact{background:none!important;border:1px solid currentColor}
741
+ .fnd-recipe{background:none;border:1px solid var(--border)}
742
+ }
743
+
744
+ .fnd .fnd-empty{display:flex;align-items:center;gap:6px;padding:var(--s4);border:1px dashed var(--border);border-radius:var(--r-md)}
745
+
746
+ /* ---------- product data the mockup did not show: the check's detail, the model's explanation, GTM tags, accepted ---------- */
747
+ .fnd-detail{margin-bottom:var(--s3);font-size:13px;overflow-wrap:anywhere}
748
+ .fnd-detail code{font-size:12px}
749
+ .fnd-ai{margin-top:var(--s3);padding:var(--s2) var(--s3);border-left:2px solid var(--border);color:var(--muted);font-size:13px}
750
+ .fnd-ai p+p,.fnd-ai p+ol{margin-top:6px}
751
+ .fnd-ai-h{display:flex;align-items:center;gap:6px;font-weight:500}
752
+ .fnd-ai-h .ic{width:14px;height:14px}
753
+ p.fnd-ai{border-left:0;padding:0}
754
+ .fnd .fix-steps{margin:6px 0 0;padding-left:20px;display:grid;gap:4px}
755
+ .fnd .gtm-tags th{white-space:normal}
756
+ .fnd .gtm-why{margin-top:4px;color:var(--muted)}
757
+ .fnd .note{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:var(--s3) 0 0;color:var(--muted)}
758
+ .fnd .f-t>.badge{margin-left:6px;vertical-align:1px}
759
+ .fnd .fnd-none{display:flex;align-items:center;gap:6px;padding:var(--s2) 0;font-size:13px}
760
+ .fnd .g-rows:empty{display:none}
761
+ a.chip.chip-sev:hover{text-decoration:none}
762
+
763
+ /* ---------- changed since last run: a collapsed card between the filter bar and the groups ---------- */
764
+ .fnd .fnd-changes{margin-top:var(--s3)}
765
+ .fnd details.fnd-changes>summary{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s3);padding:8px 40px 8px var(--s4)}
766
+ .fnd details.fnd-changes>summary h3{font-size:14px}
767
+ .fnd .fnd-changes>.body{padding:var(--s3) var(--s4)}
768
+ .fnd-change-list{list-style:none;display:grid;gap:6px;margin-top:var(--s2);font-size:13px}
769
+ .fnd-change-list li{display:flex;flex-wrap:wrap;align-items:center;gap:6px;overflow-wrap:anywhere}`;
770
+ // ---------- end of findings ----------
771
+
772
+ // ---------- journeys: #journeys ----------
773
+ const JOURNEYS = `/* Journeys (Direction D): journey tabs + viewport switch, step timeline, step pane with Hits / Data Layer / Requests tabs.
774
+ Section-scoped; generic .tabs/.card/.chip come from components.css. No max-width anywhere: the pane takes what is left. */
775
+ #journeys{min-width:0}
776
+
777
+ /* ---------- picker: journey tabs, viewport switch on the same line (right) ---------- */
778
+ #journeys .jr-pick{position:relative}
779
+ #journeys .jr-pick.js>[role=tablist]{padding-right:230px}
780
+ #journeys .jr-pick>[role=tablist]>[role=tab]{font-size:14px;padding:9px 14px;text-transform:capitalize}
781
+ #journeys .jr-gl{display:inline-flex;gap:2px}
782
+ #journeys .jr-g{display:inline-flex;color:var(--sev-fg)}
783
+ #journeys .jr-g .ic{width:13px;height:13px;vertical-align:0}
784
+ /* the viewport switch ([data-vp-switch]): a two-segment control; the pressed segment takes its viewport hue (identity, not status) */
785
+ #journeys .jr-vp{display:inline-flex;gap:0;padding:2px;border:1px solid var(--border);border-radius:var(--r-pill);background:var(--well);margin:var(--s3) 0 var(--s2)}
786
+ #journeys .jr-pick.js .jr-vp{position:absolute;top:3px;right:0;margin:0}
787
+ html:not(.js) #journeys .jr-vp{display:none}
788
+ #journeys .jr-vpb{--k:var(--vp-desktop);--k-fg:var(--vp-desktop-fg);--k-bg:var(--vp-desktop-bg);appearance:none;display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 11px;border:0;border-radius:var(--r-pill);background:none;color:var(--muted);font:500 12.5px/1 var(--font-sans);white-space:nowrap;cursor:pointer}
789
+ #journeys .jr-vpb.mobile{--k:var(--vp-mobile);--k-fg:var(--vp-mobile-fg);--k-bg:var(--vp-mobile-bg)}
790
+ #journeys .jr-vpb:hover{color:var(--fg)}
791
+ #journeys .jr-vpb .ic{width:14px;height:14px}
792
+ #journeys .jr-vpb[aria-pressed=true]{background:var(--k-bg);color:var(--k-fg);box-shadow:inset 0 0 0 1px var(--k);font-weight:600}
793
+ #journeys .jr-pick>[role=tabpanel]{padding-top:var(--s3)}
794
+ #journeys .jr-run[hidden]{display:none}
795
+ html:not(.js) #journeys .jr-run+.jr-run{margin-top:var(--s5)}
796
+ html.js #journeys .jr-runh{display:none}
797
+
798
+ /* ---------- status line: run state chip, three numbers, the goal on one line ---------- */
799
+ #journeys .jr-status{display:flex;align-items:center;gap:var(--s2) var(--s3);min-width:0;margin-bottom:var(--s3);font-size:12.5px}
800
+ #journeys .jr-nums{white-space:nowrap;font-variant-numeric:tabular-nums}
801
+ #journeys .jr-goal{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted)}
802
+
803
+ /* ---------- inspector: step timeline | pane ---------- */
804
+ #journeys .jr-insp{display:grid;grid-template-columns:clamp(296px,22vw,380px) minmax(0,1fr);gap:var(--s4);align-items:start}
805
+ html:not(.js) #journeys .jr-insp{grid-template-columns:minmax(0,1fr)}
806
+ html:not(.js) #journeys .jr-steps{display:none}
807
+ #journeys .jr-steps{position:sticky;top:calc(var(--top) + var(--s3));max-height:calc(100vh - var(--top) - var(--s5));overflow:auto;background:var(--card);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:var(--shadow-sm);padding:var(--s1) 0 var(--s2);scrollbar-width:thin}
808
+ #journeys .jr-steps ul{list-style:none;margin:0;padding:0}
809
+ /* the timeline: one 2px line through the number column, from the first page to the last step */
810
+ #journeys .jr-steps li{position:relative}
811
+ #journeys .jr-steps li::before{content:"";position:absolute;left:24px;top:0;bottom:0;width:2px;background:var(--border)}
812
+ #journeys .jr-steps li:first-child::before{top:18px}
813
+ #journeys .jr-steps li:last-child::before{bottom:50%}
814
+ #journeys .jr-pg{display:grid;grid-template-columns:24px auto minmax(0,1fr);column-gap:10px;align-items:baseline;padding:8px 12px 2px 13px}
815
+ #journeys .jr-pg+.jr-st,#journeys .jr-st+.jr-pg{margin-top:0}
816
+ #journeys .jr-st+.jr-pg{margin-top:4px}
817
+ #journeys .jr-pg-i{align-self:center;position:relative;z-index:1;display:grid;place-items:center;width:24px;height:24px;border-radius:var(--r-sm);background:var(--well);border:1px solid var(--border);color:var(--muted)}
818
+ #journeys .jr-pg-t{font-weight:600;font-size:13px;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
819
+ #journeys .jr-pg-p{justify-self:start;background:none;padding:0;font-size:11.5px;line-height:1.3;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
820
+ #journeys .jr-st>button{appearance:none;position:relative;width:100%;display:grid;grid-template-columns:24px minmax(0,1fr) repeat(var(--jr-plats,2),30px) 14px;column-gap:6px;align-items:center;min-height:32px;padding:3px 12px 3px 13px;border:0;background:none;color:var(--fg);font:13px/1.3 var(--font-sans);text-align:left;cursor:pointer}
821
+ #journeys .jr-st>button>.jr-t{margin-left:4px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
822
+ #journeys .jr-st>button:hover{background:var(--well)}
823
+ #journeys .jr-st>button:focus-visible{outline-offset:-2px}
824
+ #journeys .jr-st>button:is([aria-selected=true],[aria-current=true],[aria-current=step],[aria-pressed=true],.selected){background:var(--accent-bg);box-shadow:inset 3px 0 0 var(--accent)}
825
+ #journeys .jr-st>button:is([aria-selected=true],[aria-current=true],[aria-current=step],[aria-pressed=true],.selected)>.jr-t{font-weight:600}
826
+ #journeys .jr-n{position:relative;z-index:1;display:grid;place-items:center;width:24px;height:24px;border-radius:50%;background:var(--card);border:1.5px solid var(--border-strong);color:var(--fg);font:600 11px/1 var(--font-mono);font-variant-numeric:tabular-nums}
827
+ #journeys .jr-st.zero .jr-n{border-style:dashed;color:var(--muted)}
828
+ #journeys .jr-st.zero .jr-t{color:var(--muted)}
829
+ #journeys .jr-st[class*=sev-] .jr-n{background:var(--sev-bg);border-color:var(--sev);color:var(--sev-fg)}
830
+ #journeys .jr-hc{display:flex;justify-content:flex-end}
831
+ #journeys .jr-hc .chip{height:18px;padding:0 5px 0 4px;gap:2px;font:600 11px/1 var(--font-mono)}
832
+ #journeys .jr-hc .chip>svg{width:11px;height:11px;margin-left:0}
833
+ #journeys .jr-gs{display:flex;justify-content:center}
834
+
835
+ /* ---------- pane ---------- */
836
+ #journeys .jr-panes{min-width:0;container:jrpane/inline-size}
837
+ #journeys .jr-pane{padding:0}
838
+ #journeys .jr-pane+.jr-pane{margin-top:var(--s3)}
839
+ #journeys .jr-pane:focus{outline:none}
840
+ #journeys .jr-pane[data-select-pane][hidden]{display:none}
841
+ #journeys .jr-ph{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px var(--s3);align-items:center;padding:var(--s3) var(--s4) 0}
842
+ #journeys .jr-ph h3{display:flex;align-items:center;gap:10px;font-size:16px;line-height:1.3;min-width:0}
843
+ #journeys .jr-ph h3 .jr-n{width:26px;height:26px;font-size:12px}
844
+ #journeys .jr-pane[class*=sev-]{border-left:1px solid var(--border)}
845
+ #journeys .jr-pane[class*=sev-] .jr-ph .jr-n{background:var(--sev-bg);border-color:var(--sev);color:var(--sev-fg)}
846
+ #journeys .jr-ph-e{display:inline-flex;gap:6px;align-items:center}
847
+ #journeys .jr-ph-m{grid-column:1/-1;display:flex;flex-wrap:wrap;gap:0 var(--s2);padding-left:36px;font:12px/1.5 var(--font-mono);color:var(--muted);min-width:0}
848
+ #journeys .jr-ph-m>span{overflow-wrap:anywhere}
849
+ #journeys .jr-ph-m>span+span::before{content:"·";margin-right:var(--s2)}
850
+ #journeys .jr-pane>.tabs{padding:0 var(--s4) var(--s4)}
851
+ #journeys .jr-pane>.tabs>[role=tablist]{margin-top:var(--s2)}
852
+ #journeys .jr-pane>.tabs>[role=tablist]>[role=tab][disabled]{opacity:.45;cursor:default}
853
+ #journeys .jr-pane>.tabs:not(.js)>[role=tabpanel].jr-none{display:none}
854
+ #journeys .jr-pane>p.jr-empty{padding:var(--s2) var(--s4) var(--s4) 52px}
855
+ #journeys .jr-empty{margin:0}
856
+ #journeys .jr-list>.card+.card{margin-top:6px}
857
+
858
+ /* hit, push rows: fixed columns so platform, event, consent, severity and time line up from row to row */
859
+ #journeys .jr-list>details.card>summary{display:grid;align-items:center;column-gap:10px;row-gap:4px;min-height:40px;padding:7px 36px 7px 12px}
860
+ #journeys .jr-list>details.card>summary::after{right:15px}
861
+ #journeys .jr-hit>summary{grid-template-columns:auto minmax(0,1fr) auto;grid-template-areas:"p e s" "k k k" "c c t"}
862
+ #journeys .jr-c-p{grid-area:p;display:flex}
863
+ #journeys .jr-hit .jr-ev{grid-area:e}
864
+ #journeys .jr-hit .jr-c-k{grid-area:k}
865
+ #journeys .jr-c-c{grid-area:c;display:flex;gap:4px;flex-wrap:wrap;min-width:0}
866
+ #journeys .jr-c-s{grid-area:s;display:flex}
867
+ #journeys .jr-hit .jr-tm{grid-area:t}
868
+ #journeys .jr-c-s:empty,#journeys .jr-c-k:empty{display:none}
869
+ #journeys code.jr-ev{background:none;padding:0;font-size:13px;font-weight:600;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
870
+ #journeys .jr-c-k{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;min-width:0;font-size:12.5px}
871
+ #journeys .jr-kv{display:inline-flex;gap:5px;align-items:baseline;min-width:0;white-space:nowrap}
872
+ #journeys .jr-kv .k{color:var(--muted);font:11.5px var(--font-mono)}
873
+ #journeys .jr-kv .v{font:12.5px var(--font-mono);overflow:hidden;text-overflow:ellipsis}
874
+ #journeys .jr-miss{height:20px}
875
+ #journeys .jr-miss .k{color:inherit;font-weight:600;font-family:var(--font-mono)}
876
+ #journeys .jr-tm{font:11.5px var(--font-mono);color:var(--muted);white-space:nowrap;text-align:right;font-variant-numeric:tabular-nums}
877
+ #journeys .chip-consent .k{color:inherit;font-weight:400;opacity:.85}
878
+ #journeys .jr-push>summary{grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"e t" "k k"}
879
+ @container jrpane (min-width:520px){
880
+ #journeys .jr-hit>summary{grid-template-columns:76px minmax(0,1fr) 112px 84px 46px;grid-template-areas:"p e c s t" ". k k k k"}
881
+ }
882
+ @container jrpane (min-width:820px){
883
+ #journeys .jr-hit>summary{grid-template-columns:76px 140px minmax(0,1fr) 112px 84px 46px;grid-template-areas:"p e k c s t"}
884
+ #journeys .jr-push>summary{grid-template-columns:minmax(0,220px) minmax(0,1fr) 46px;grid-template-areas:"e k t"}
885
+ }
886
+ #journeys .jr-push .jr-ev{grid-area:e;font-weight:500}
887
+ #journeys .jr-push .jr-c-k{grid-area:k;gap:4px 6px}
888
+ #journeys .jr-push .jr-tm{grid-area:t}
889
+
890
+ /* open hit: flag line, parameters | items, raw request */
891
+ #journeys .jr-list>.card>.body{padding:var(--s3)}
892
+ #journeys .jr-flag{display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;margin:0 0 var(--s3);padding:6px 10px;border-radius:var(--r-sm);background:var(--sev-bg);color:var(--sev-fg);font-size:12.5px}
893
+ #journeys .jr-flag .ic{margin-top:2px}
894
+ #journeys .jr-flag>span{flex:1 1 200px;min-width:0}
895
+ #journeys .jr-hb{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:var(--s3) var(--s4)}
896
+ #journeys .jr-h{display:flex;align-items:center;gap:6px;margin-bottom:6px;font-size:12px;line-height:1.4;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
897
+ #journeys .jr-hb .tbl{font-size:12.5px;border:1px solid var(--border);border-radius:var(--r-sm);border-collapse:separate;border-spacing:0}
898
+ #journeys .jr-hb .tbl th,#journeys .jr-hb .tbl td{padding:4px 8px}
899
+ #journeys .jr-kvt tbody th{width:38%;font:11.5px var(--font-mono);color:var(--muted);vertical-align:top}
900
+ #journeys .jr-kvt td{font:12px var(--font-mono)}
901
+ #journeys .jr-raw{margin-top:var(--s3)}
902
+ #journeys .jr-raw>summary{flex-wrap:nowrap;font-size:12.5px}
903
+ #journeys .jr-raw>summary .t{flex:none;font-weight:500;white-space:nowrap}
904
+ #journeys .jr-raw>summary .end{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}
905
+ #journeys .jr-raw pre,#journeys .jr-push pre{margin:0}
906
+ #journeys .jr-push>.body{padding:var(--s2)}
907
+
908
+ /* requests: one compact table, URL truncated (full URL in the title) */
909
+ #journeys .jr-req{table-layout:fixed;font-size:12.5px;min-width:640px}
910
+ #journeys .jr-req th,#journeys .jr-req td{padding:5px 8px}
911
+ #journeys .jr-req th:nth-child(1){width:112px}
912
+ #journeys .jr-req th:nth-child(2){width:64px}
913
+ #journeys .jr-req th:nth-child(3){width:150px}
914
+ #journeys .jr-req th:nth-child(5){width:116px}
915
+ #journeys .jr-req th:nth-child(6){width:64px}
916
+ #journeys .jr-url span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}
917
+ #journeys .jr-req td.mono{font-size:12px}
918
+
919
+ /* a request's events (one per line in a GA4 batch), its status with the blocked chip, notes under the table */
920
+ #journeys .jr-rev{display:flex;flex-wrap:wrap;gap:0 6px;align-items:baseline}
921
+ #journeys .jr-rev+.jr-rev{margin-top:2px}
922
+ #journeys .jr-rev code{background:none;padding:0;font-size:12px;font-weight:600}
923
+ #journeys .jr-rev .muted{font-size:11.5px}
924
+ #journeys .jr-st-c{display:flex;flex-wrap:wrap;gap:4px 6px;align-items:center}
925
+ #journeys .jr-st-c .chip{height:20px;font-size:11.5px}
926
+ #journeys .jr-note-s{margin-top:var(--s2)}
927
+
928
+ /* the plan line under the status: the funnel sentence, then a chip per planned event that failed */
929
+ #journeys .jr-plan{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s2);margin:calc(-1 * var(--s1)) 0 var(--s3);font-size:12.5px;min-width:0}
930
+ #journeys .jr-plan-h{font:600 11px/1.4 var(--font-sans);color:var(--muted);text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}
931
+ #journeys .jr-plan-t{display:inline-flex;align-items:center;gap:5px;min-width:0}
932
+ #journeys .jr-plan-t .jr-bad{color:var(--sev-broken)}
933
+ #journeys .jr-plan-t .jr-good{color:var(--sev-ok)}
934
+ #journeys .jr-plan .chip code{background:none;padding:0;font-size:11.5px;color:inherit;font-weight:600}
935
+ /* why an incomplete run stopped */
936
+ #journeys .jr-stop{display:flex;gap:6px;align-items:flex-start;margin:calc(-1 * var(--s1)) 0 var(--s3);font-size:12.5px;color:var(--sev-warning-fg);overflow-wrap:anywhere}
937
+ #journeys .jr-stop .ic{color:var(--sev-warning);margin-top:2px}
938
+ /* platform parity on a step: the same event on two platforms that disagree */
939
+ #journeys .jr-par{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin:var(--s2) var(--s4) 0;padding:6px 10px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:12.5px}
940
+ #journeys .jr-par>.ic{color:var(--sev-warning)}
941
+ #journeys .jr-par-h{font-weight:600}
942
+ #journeys .jr-par b{color:var(--sev-warning-fg)}
943
+ #journeys .jr-pp{display:inline-flex;align-items:center;gap:5px}
944
+ #journeys .jr-pp code{background:none;padding:0;font-size:12px}
945
+
946
+ /* platform chips carry plat(): icon and name side by side; glossary tips on parameter names */
947
+ #journeys .chip .plat{display:inline-flex;align-items:center;gap:5px}
948
+ #journeys .chip .plat>svg{width:13px;height:13px}
949
+ #journeys .q{position:relative;display:inline-grid;place-items:center;width:15px;height:15px;margin-left:5px;border:1px solid var(--muted);border-radius:50%;font:500 10px var(--font-sans);color:var(--muted);cursor:help;vertical-align:1px}
950
+ #journeys .q:hover{border-color:var(--accent);color:var(--accent-fg)}
951
+ #journeys .q:hover::after{content:attr(data-tip);position:absolute;left:0;top:calc(100% + 6px);z-index:5;width:260px;padding:8px 10px;background:var(--fg);color:var(--page);font:400 12px/1.45 var(--font-sans);border-radius:var(--r-sm);white-space:normal;text-align:left;text-transform:none;letter-spacing:0}
952
+ #journeys .jr-kvt .pk{font-family:var(--font-mono)}
953
+ #journeys .jr-kvt tr.pr td{font-family:var(--font-sans)}
954
+ #journeys .jr-batch{margin-bottom:6px}
955
+ #journeys .jr-ctx{margin-top:var(--s2)}
956
+ #journeys .jr-ctx>summary{font-size:12.5px}
957
+ #journeys .jr-ctx>summary .t{font-weight:500}
958
+ #journeys .jr-ctx>.body{padding:var(--s2)}
959
+ /* source of a hit: the dataLayer push and the request behind it, each linking to its row */
960
+ #journeys .jr-src{list-style:none;display:grid;gap:6px;font-size:12.5px}
961
+ #journeys .jr-src li{display:flex;flex-wrap:wrap;gap:2px 8px;align-items:baseline;min-width:0}
962
+ #journeys .jr-src .lbl{flex:0 0 76px;color:var(--muted);font-size:11.5px}
963
+ #journeys .jr-src .mono{font-size:12px;overflow-wrap:anywhere}
964
+ #journeys .jr-src .muted{flex:1 1 200px}
965
+ /* items: one row per item, the extra fields as key value pairs */
966
+ #journeys .jr-ts{overflow-x:auto}
967
+ #journeys .jr-it td{vertical-align:top}
968
+ #journeys .jr-ikv{display:block;white-space:nowrap;font-size:12px}
969
+ #journeys .jr-ikv code{background:none;padding:0;color:var(--muted);font-size:11.5px}
970
+
971
+ /* ---------- 900 px and below: step strip over the pane ---------- */
972
+ @media (max-width:900px){
973
+ #journeys .jr-pick.js>[role=tablist]{padding-right:96px}
974
+ #journeys .jr-pick>[role=tablist]>[role=tab]{padding:9px 10px}
975
+ #journeys .jr-vpl{display:none}
976
+ #journeys .jr-vpb{padding:0 9px}
977
+ #journeys .jr-status{flex-wrap:wrap}
978
+ #journeys .jr-goal{flex-basis:100%}
979
+ #journeys .jr-insp{grid-template-columns:minmax(0,1fr);gap:var(--s3)}
980
+ #journeys .jr-steps{position:relative;top:auto;max-height:none;overflow-x:auto;overflow-y:hidden;padding:6px;scrollbar-width:none}
981
+ #journeys .jr-steps ul{display:flex;align-items:center;gap:4px;width:max-content}
982
+ #journeys .jr-steps li::before{display:none}
983
+ #journeys .jr-pg{display:block;width:1px;align-self:stretch;margin:4px 4px!important;padding:0;background:var(--border)}
984
+ #journeys .jr-pg:first-child{display:none}
985
+ #journeys .jr-pg>*{display:none}
986
+ #journeys .jr-st>button{display:flex;gap:3px;width:auto;min-height:36px;padding:4px 6px;border-radius:var(--r-sm)}
987
+ #journeys .jr-st>button>.jr-t,#journeys .jr-hc{display:none}
988
+ #journeys .jr-gs:empty{display:none}
989
+ #journeys .jr-st>button:is([aria-selected=true],[aria-current=true],[aria-current=step],[aria-pressed=true],.selected){box-shadow:inset 0 -3px 0 var(--accent)}
990
+ #journeys .jr-ph{padding:var(--s3) var(--s3) 0}
991
+ #journeys .jr-ph-m{padding-left:0}
992
+ #journeys .jr-pane>.tabs{padding:0 var(--s3) var(--s3)}
993
+ #journeys .jr-pane>.tabs>[role=tablist]>[role=tab]{padding:8px 9px}
994
+ }
995
+
996
+ /* ---------- print: the no-JS layout; unflagged steps print as their header line only ---------- */
997
+ @media print{
998
+ #journeys .jr-steps{display:none}
999
+ #journeys .jr-insp{grid-template-columns:minmax(0,1fr)}
1000
+ #journeys .jr-pane,#journeys .jr-pick [role=tabpanel]{display:block!important}
1001
+ #journeys .jr-pane:not([data-flagged])>.tabs,#journeys .jr-pane:not([data-flagged])>p.jr-empty{display:none}
1002
+ #journeys .jr-pane:not([data-flagged]) .jr-ph{padding-bottom:var(--s2)}
1003
+ #journeys .jr-pane{break-inside:avoid;box-shadow:none}
1004
+ /* on paper a step is its hits; the dataLayer and the requests stay in the HTML and capture.json */
1005
+ #journeys .jr-pane [role=tabpanel][id$="-dl"],#journeys .jr-pane [role=tabpanel][id$="-req"]{display:none!important}
1006
+ #journeys .jr-pane:not([data-flagged]){padding-bottom:0}
1007
+ #journeys .jr-flag{background:none;border:1px solid currentColor}
1008
+ #journeys .q{display:none}
1009
+ #journeys .jr-vp{display:none}
1010
+ #journeys .jr-run[hidden]{display:block!important}
1011
+ #journeys .jr-runh{display:inline-flex!important}
1012
+ #journeys .jr-pane>.tabs:not(.js)>[role=tabpanel].jr-none,#journeys .jr-pane>.tabs>[role=tabpanel].jr-none{display:none}
1013
+ }`;
1014
+ // ---------- end of journeys ----------
1015
+
1016
+ // ---------- consent: #consent ----------
1017
+ const CONSENT = `/* Consent: platform status lines, then the Consent Mode and Scenarios cards. Colours only through tokens;
1018
+ denied/granted/none come from .chip-consent in components.css (denied is the crimson red token, never amber). */
1019
+ #consent .cs-list{list-style:none;margin:var(--s3) 0 var(--s2);padding:0;display:grid;gap:var(--s2)}
1020
+ #consent .cs-row{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s2) var(--s3);min-height:48px;padding:8px var(--s4);margin:0}
1021
+ #consent .cs-cells{display:flex;flex-wrap:wrap;align-items:center;gap:6px var(--s4);min-width:0}
1022
+ #consent .cs-p{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
1023
+ #consent .cs-k{font-size:12px;color:var(--muted)}
1024
+ #consent .cs-go{margin-left:auto;display:inline-flex;align-items:center;gap:2px;font-size:12.5px;font-weight:500}
1025
+ #consent .card+.card{margin-top:var(--s2)}
1026
+ #consent details.card>summary .chip.plain{color:var(--muted)}
1027
+ /* Consent Mode body */
1028
+ #consent .cm-list{list-style:none;margin:0;padding:0;display:grid;gap:var(--s2)}
1029
+ #consent .cm-list li{display:grid;grid-template-columns:64px minmax(0,220px) minmax(0,1fr);align-items:center;gap:var(--s2) var(--s3);padding:8px var(--s3);border:1px solid var(--border);border-radius:var(--r-sm);background:var(--page)}
1030
+ #consent .cm-l{font-weight:600}
1031
+ #consent .cm-w{font-size:12.5px;color:var(--muted)}
1032
+ #consent .cm-s{display:flex;flex-wrap:wrap;gap:6px}
1033
+ #consent .cm-rep{margin-top:var(--s2);display:flex;align-items:center;gap:5px}
1034
+ #consent .cm-timing{list-style:none;margin:0 0 var(--s3);padding:0;display:grid;gap:6px}
1035
+ #consent .cm-timing li{display:flex;align-items:center;flex-wrap:wrap;gap:4px var(--s3)}
1036
+ #consent .cm-timing .chip{min-width:92px}
1037
+ #consent .cm-runs{margin:var(--s3) 0 var(--s2)}
1038
+ #consent .cm-find{margin-top:var(--s2)}
1039
+ #consent .cs-none{margin:var(--s2) 0 0}
1040
+ /* scenarios table */
1041
+ #consent .cs-tag{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}
1042
+ #consent .cs-tag code{white-space:nowrap}
1043
+ #consent .cs-c{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}
1044
+ #consent .cs-c .num{font-size:13px;min-width:12px;text-align:right}
1045
+ #consent .cs-tbl td.cs-bad{background:color-mix(in srgb,var(--sev-risk-bg) 55%,transparent)}
1046
+ #consent .cs-tbl tbody tr:hover>td.cs-bad{background:var(--sev-risk-bg)}
1047
+ #consent .cs-c a{display:inline-flex;align-items:center;font-size:12.5px;font-weight:500}
1048
+ @media (max-width:900px){
1049
+ #consent .cm-list li{grid-template-columns:minmax(0,1fr)}
1050
+ #consent .cs-go{margin-left:0}
1051
+ }
1052
+ @media print{
1053
+ #consent .cs-row{box-shadow:none;break-inside:avoid}
1054
+ }
1055
+ /* journeys.css still has a bare .t (mono time); keep the card title independent of it */
1056
+ #consent details.card>summary>.t{font:600 14px/1.4 var(--font-sans);color:var(--fg);margin-left:0;white-space:normal}
1057
+ #consent .cs-tbl{min-width:560px}
1058
+ #consent .cs-tbl td,#consent .cs-tbl th{white-space:nowrap}`;
1059
+ // ---------- end of consent ----------
1060
+
1061
+ // ---------- scope: #coverage, #inventory and #run ----------
1062
+ const SCOPE = `/* Scope (Coverage, Inventory, Run). Selectors are scoped by section id; colours only through tokens. */
1063
+ /* journeys.css still has a bare .t (mono time); keep the card titles independent of it */
1064
+ #coverage details.card>summary>.t,#inventory details.card>summary>.t,#run details.card>summary>.t{font:600 14px/1.4 var(--font-sans);color:var(--fg);margin-left:0;white-space:normal}
1065
+ #coverage .card+.card,#run .card{margin-top:var(--s2)}
1066
+
1067
+ /* ----- Coverage ----- */
1068
+ #coverage .cv-bar{display:inline-flex;gap:2px;align-items:center;height:14px;padding:0 2px}
1069
+ #coverage .cv-bar i{width:7px;height:14px;border-radius:2px;background:var(--sev-ok)}
1070
+ #coverage .cv-bar i.params{background:var(--sev-broken)}
1071
+ #coverage .cv-bar i.missing{background:var(--sev-warning)}
1072
+ #coverage .cv-pcs{gap:6px}
1073
+ #coverage .cv-pc .chip{gap:4px}
1074
+ #coverage .cv-pc .chip b{font-family:var(--font-mono);font-size:11px}
1075
+ #coverage .filterbar{margin-bottom:var(--s3);box-shadow:none}
1076
+ #coverage .cv-tbl .cv-c{text-align:center;white-space:nowrap}
1077
+ #coverage .cv-tbl thead .cv-c{text-align:center}
1078
+ #coverage .cv-tbl .cv-g{padding:0 5px;height:22px}
1079
+ #coverage .cv-tbl .cv-g>svg:first-child{margin-left:0}
1080
+ #coverage .cv-ev{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}
1081
+ #coverage .cv-ev code{white-space:nowrap}
1082
+ #coverage .cv-needs code{margin:0 3px 3px 0;display:inline-block;font-size:11.5px}
1083
+ #coverage code.cv-miss{background:var(--sev-broken-bg);color:var(--sev-broken-fg);font-weight:600}
1084
+ #coverage .cv-tbl tbody tr[data-st=params]>th{box-shadow:inset 3px 0 0 var(--sev-broken)}
1085
+ #coverage .cv-tbl tbody tr[data-st=missing]>th{box-shadow:inset 3px 0 0 var(--sev-warning)}
1086
+ #coverage .cv-key{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);margin-top:var(--s3)}
1087
+ /* first column stays put while the run columns scroll at 390 */
1088
+ #coverage .cv-scroll .cv-tbl th:first-child{position:sticky;left:0;z-index:1;background:var(--card)}
1089
+ #coverage .cv-scroll .cv-tbl thead th:first-child{background:var(--well)}
1090
+
1091
+ /* ----- Inventory ----- */
1092
+ #inventory .inv-rows{list-style:none;display:grid;gap:var(--s2);margin-top:var(--s3)}
1093
+ #inventory .inv-rows>li>.card{margin:0}
1094
+ #inventory .inv-note{margin-top:var(--s3)}
1095
+ #inventory .inv-note+.inv-note{margin-top:var(--s1)}
1096
+ #inventory .inv-line{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s2) var(--s3);min-height:44px;padding:10px var(--s4)}
1097
+ #inventory .inv-line .chip-sev{opacity:.95}
1098
+ #inventory .inv-m{font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums}
1099
+ #inventory .inv-m b{color:var(--fg);font-family:var(--font-mono);font-weight:600}
1100
+ #inventory .inv-m.zero b{color:var(--muted);font-weight:500}
1101
+ #inventory .inv-list{list-style:none;margin:0;padding:0;display:grid;gap:6px}
1102
+ #inventory .inv-list li{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s2);min-width:0}
1103
+ #inventory .inv-list li>svg{color:var(--muted)}
1104
+ #inventory .inv-n{font-size:12.5px;color:var(--muted)}
1105
+ #inventory .inv-n b{color:var(--fg);font-family:var(--font-mono);font-weight:600}
1106
+ #inventory .inv-list .chip.plain{height:20px;font-size:11.5px}
1107
+
1108
+ /* ----- Run ----- */
1109
+ #run .run-facts{margin:var(--s3) 0 var(--s2);padding:var(--s2) var(--s4);display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 var(--s5);background:var(--card);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:var(--shadow-sm)}
1110
+ #run .run-facts>div{display:grid;grid-template-columns:84px minmax(0,1fr);align-items:center;gap:var(--s2);min-height:38px;padding:5px 0;border-bottom:1px solid var(--border)}
1111
+ #run .run-facts>div.wide{grid-column:1/-1}
1112
+ #run .run-facts>div:nth-last-child(-n+1){border-bottom:0}
1113
+ #run .run-facts dt{font:600 11px/1.2 var(--font-sans);color:var(--muted);text-transform:uppercase;letter-spacing:.05em}
1114
+ #run .run-facts dd{margin:0;display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-width:0}
1115
+ #run .run-facts code{margin:0}
1116
+ #run>p.meta{margin:0 0 var(--s2);font-size:12.5px;color:var(--muted)}
1117
+ #run .run-facts code.file{display:inline-flex;align-items:center;gap:5px;color:var(--muted)}
1118
+ #run .tm-tbl .tm-d{display:inline-block;width:80px;height:6px;margin-right:var(--s2);vertical-align:middle;border-radius:3px;background:var(--well);overflow:hidden;text-align:left}
1119
+ #run .tm-tbl .tm-bar{display:block;height:100%;border-radius:3px;background:var(--muted)}
1120
+ #run .tm-tbl .tm-bar.desktop{background:var(--vp-desktop)}
1121
+ #run .tm-tbl .tm-bar.mobile{background:var(--vp-mobile)}
1122
+ #run .tm-s{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
1123
+
1124
+ @media (max-width:900px){
1125
+ #run .run-facts{grid-template-columns:minmax(0,1fr);padding:var(--s1) var(--s3)}
1126
+ #run .run-facts>div{grid-template-columns:78px minmax(0,1fr)}
1127
+ #run .run-facts>div:nth-last-child(2).wide{border-bottom:1px solid var(--border)}
1128
+ }
1129
+ @media print{
1130
+ #run .run-facts{box-shadow:none;break-inside:avoid}
1131
+ #inventory .inv-line{box-shadow:none;break-inside:avoid}
1132
+ }
1133
+ /* tables keep their shape and scroll inside .tscroll on a phone */
1134
+ #coverage .cv-tbl{min-width:640px}
1135
+ #coverage .cv-needs code{white-space:nowrap}
1136
+ #run .tm-tbl{min-width:540px}
1137
+ #coverage .cv-tbl td,#coverage .cv-tbl th,#run .tm-tbl td{white-space:nowrap}
1138
+ #coverage .cv-tbl td.cv-needs{white-space:normal;min-width:150px}
1139
+ #coverage .cv-tbl.cv-conv{min-width:760px}
1140
+ #coverage .cv-conv thead th.cv-c{white-space:normal;overflow-wrap:normal;line-height:1.25}
1141
+ #run .tm-tbl{min-width:600px}
1142
+ #run .tm-s{flex-wrap:nowrap}
1143
+ /* ----- additions for the product: search icon, links, plan vs site, trend ----- */
1144
+ /* the search field's icon is a sprite symbol laid over the input; CSS may not carry a data URI */
1145
+ #coverage .cv-search{position:relative;display:flex;flex:1 1 180px;min-width:0;max-width:320px;margin-left:auto}
1146
+ #coverage .cv-search>svg{position:absolute;left:11px;top:50%;margin-top:-7px;width:14px;height:14px;color:var(--muted);pointer-events:none}
1147
+ #coverage .cv-search [data-filter-search]{flex:1 1 auto;max-width:none;margin-left:0;width:100%}
1148
+ #coverage a.cv-run,#run a.chip-run{text-decoration:none}
1149
+ #coverage .cv-ev .badge{height:20px;font-size:11.5px}
1150
+ #coverage .cv-lead{margin-bottom:var(--s3)}
1151
+ #coverage .cv-none{margin-top:var(--s3)}
1152
+ #coverage .pv-tbl td.xs{white-space:normal;min-width:220px}
1153
+ #coverage .pv-scroll{margin-top:var(--s3)}
1154
+ #coverage .pv-h{margin-top:var(--s4)}
1155
+ #coverage .pv-changes{margin:var(--s1) 0 0 var(--s4)}
1156
+ #run .run-facts dd>.xs{line-height:1.4}
1157
+ #run .tr-body{display:grid;gap:var(--s3)}
1158
+ #run .tr-chart{display:grid;gap:var(--s2)}
1159
+ #run .spark{display:block;max-width:100%;height:auto}
1160
+ #run .spark polyline{fill:none;stroke:var(--sev);stroke-width:1.75;stroke-linejoin:round}
1161
+ #run .spark circle{fill:var(--sev);stroke:var(--card);stroke-width:1}
1162
+ #run .spark circle.hollow{fill:var(--card);stroke:var(--sev);stroke-width:1.5}
1163
+ #run .spark-legend{display:flex;flex-wrap:wrap;gap:4px 12px}
1164
+ #run .spark-legend i{display:inline-block;width:10px;height:3px;background:var(--sev);margin-right:5px;vertical-align:middle;border-radius:2px}
1165
+ #run .tr-tbl{min-width:420px}
1166
+ #run .tr-tbl th[scope=row]{white-space:nowrap}
1167
+ #run .tr-h{margin-top:var(--s2)}
1168
+ #run .tr-list{list-style:none;display:grid;gap:6px}
1169
+ #run .tr-list li{display:flex;align-items:baseline;gap:var(--s2);font-size:13px}
1170
+ #run .tr-list li>.badge{flex:none}
1171
+ @media print{#run .spark{print-color-adjust:exact;-webkit-print-color-adjust:exact}}`;
1172
+ // ---------- end of scope ----------
1173
+
1174
+ export const CSS = [TOKENS, COMPONENTS, SHELL, PRODUCT, SUMMARY, FINDINGS, JOURNEYS, CONSENT, SCOPE].join("\n");
1175
+
1176
+ /** The shared parts on their own, for pages that are not the report (plan.html loads tokens, components and product, not the shell). */
1177
+ export { TOKENS, COMPONENTS, SHELL, PRODUCT };
1178
+
1179
+ /** The white label's accent colour, written for the light theme and under both dark selectors (whitelabel.js). */
1180
+ export { brandCss };