module HtmlReportAssets

Overview

The Noir brand mark (docs/static/images/logo-s.png) embedded as base64 so the report stays a single self-contained file.

Defined in:

output_builder/html_assets/css.cr
output_builder/html_assets/js.cr
output_builder/html_assets/logo.cr

Constant Summary

LOGO_PNG_BASE64 = "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"
SCRIPTS = "(function () {\n var root = document.documentElement;\n\n function toArray(nodes) { return Array.prototype.slice.call(nodes); }\n\n // Reflect the active theme on the toggle control (a11y state).\n function syncThemeButtons() {\n var dark = root.getAttribute(\"data-theme\") === \"dark\";\n toArray(document.querySelectorAll('[data-action=\"toggle-theme\"]')).forEach(function (b) {\n b.setAttribute(\"aria-pressed\", String(dark));\n b.setAttribute(\"aria-label\", dark ? \"Switch to light theme\" : \"Switch to dark theme\");\n });\n }\n syncThemeButtons();\n\n // Theme toggle with persistence (initial theme is set in <head>).\n document.addEventListener(\"click\", function (e) {\n var btn = e.target.closest && e.target.closest('[data-action=\"toggle-theme\"]');\n if (!btn) return;\n var next = root.getAttribute(\"data-theme\") === \"dark\" ? \"light\" : \"dark\";\n root.setAttribute(\"data-theme\", next);\n try { localStorage.setItem(\"noir-theme\", next); } catch (err) {}\n syncThemeButtons();\n });\n\n // Cards <-> compact table view, persisted (initial value set in <head>).\n function syncViewButtons() {\n var mode = root.getAttribute(\"data-view\") === \"table\" ? \"table\" : \"cards\";\n toArray(document.querySelectorAll('[data-action=\"set-view\"]')).forEach(function (b) {\n b.setAttribute(\"aria-pressed\", String(b.getAttribute(\"data-view-mode\") === mode));\n });\n }\n syncViewButtons();\n\n document.addEventListener(\"click\", function (e) {\n var btn = e.target.closest && e.target.closest('[data-action=\"set-view\"]');\n if (!btn) return;\n var mode = btn.getAttribute(\"data-view-mode\") === \"table\" ? \"table\" : \"cards\";\n if (mode === \"table\") { root.setAttribute(\"data-view\", \"table\"); } else { root.removeAttribute(\"data-view\"); }\n try { localStorage.setItem(\"noir-view\", mode); } catch (err) {}\n syncViewButtons();\n });\n\n // Grouped <-> flat rendering of the endpoint list, persisted.\n function syncGroupButtons() {\n var on = root.getAttribute(\"data-group\") !== \"off\";\n toArray(document.querySelectorAll('[data-action=\"toggle-group\"]')).forEach(function (b) {\n b.setAttribute(\"aria-pressed\", String(on));\n });\n }\n syncGroupButtons();\n\n document.addEventListener(\"click\", function (e) {\n var btn = e.target.closest && e.target.closest('[data-action=\"toggle-group\"]');\n if (!btn) return;\n var turningOff = root.getAttribute(\"data-group\") !== \"off\";\n if (turningOff) { root.setAttribute(\"data-group\", \"off\"); } else { root.removeAttribute(\"data-group\"); }\n try { localStorage.setItem(\"noir-group\", turningOff ? \"off\" : \"on\"); } catch (err) {}\n syncGroupButtons();\n });\n\n // Collapse / expand a path group.\n document.addEventListener(\"click\", function (e) {\n var btn = e.target.closest && e.target.closest('[data-action=\"toggle-group-collapse\"]');\n if (!btn) return;\n var group = btn.closest(\".group\");\n if (!group) return;\n var collapsed = group.classList.toggle(\"collapsed\");\n btn.setAttribute(\"aria-expanded\", String(!collapsed));\n });\n\n // Collapse / expand an endpoint card.\n document.addEventListener(\"click\", function (e) {\n var header = e.target.closest && e.target.closest('[data-action=\"toggle-card\"]');\n if (!header) return;\n var card = header.closest(\".card\");\n if (!card) return;\n var collapsed = card.classList.toggle(\"collapsed\");\n header.setAttribute(\"aria-expanded\", String(!collapsed));\n });\n\n // Copy URL / copy-as-curl with a fallback for file:// clipboard limits.\n var copyStatus = document.getElementById(\"copy-status\");\n\n function fallbackCopy(text) {\n var ta = document.createElement(\"textarea\");\n ta.value = text;\n ta.setAttribute(\"readonly\", \"\");\n ta.style.position = \"fixed\";\n ta.style.left = \"-9999px\";\n document.body.appendChild(ta);\n ta.select();\n var ok = false;\n try { ok = document.execCommand(\"copy\"); } catch (err) {}\n document.body.removeChild(ta);\n return ok;\n }\n\n function flashCopied(btn, ok, label) {\n if (!ok) return;\n btn.classList.add(\"copied\");\n setTimeout(function () { btn.classList.remove(\"copied\"); }, 1400);\n if (copyStatus) copyStatus.textContent = label;\n }\n\n function copyText(text, btn, label) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(\n function () { flashCopied(btn, true, label); },\n function () { flashCopied(btn, fallbackCopy(text), label); }\n );\n } else {\n flashCopied(btn, fallbackCopy(text), label);\n }\n }\n\n document.addEventListener(\"click\", function (e) {\n var btn = e.target.closest && e.target.closest('[data-action=\"copy-url\"], [data-action=\"copy-curl\"]');\n if (!btn) return;\n var card = btn.closest(\".card\");\n if (!card) return;\n var isCurl = btn.getAttribute(\"data-action\") === \"copy-curl\";\n var text = card.getAttribute(isCurl ? \"data-curl\" : \"data-url\") || \"\";\n if (text) copyText(text, btn, isCurl ? \"Copied curl command\" : \"Copied URL\");\n });\n\n // Endpoint search + HTTP-method chip filter (combined with AND).\n var search = document.getElementById(\"endpoint-search\");\n var methodChips = toArray(document.querySelectorAll(\"[data-filter-method]\"));\n var endpointCards = toArray(document.querySelectorAll(\"[data-endpoint]\"));\n var endpointCount = document.getElementById(\"endpoint-count\");\n var endpointEmpty = document.getElementById(\"endpoint-no-results\");\n var endpointSection = endpointEmpty ? endpointEmpty.closest(\".section\") : null;\n var groups = toArray(document.querySelectorAll(\".group[data-group-key]\"));\n\n function applyEndpointFilter() {\n var q = (search ? search.value : \"\").trim().toLowerCase();\n var active = methodChips\n .filter(function (c) { return c.getAttribute(\"aria-pressed\") === \"true\"; })\n .map(function (c) { return c.getAttribute(\"data-filter-method\"); });\n var shown = 0;\n endpointCards.forEach(function (card) {\n var okText = !q || (card.getAttribute(\"data-text\") || \"\").indexOf(q) !== -1;\n var okMethod = active.length === 0 || active.indexOf(card.getAttribute(\"data-method\")) !== -1;\n var visible = okText && okMethod;\n card.hidden = !visible;\n if (visible) shown++;\n });\n groups.forEach(function (group) {\n var cards = toArray(group.querySelectorAll(\"[data-endpoint]\"));\n var visible = cards.filter(function (c) { return !c.hidden; }).length;\n var count = group.querySelector(\"[data-group-count]\");\n if (count) {\n count.textContent = visible === cards.length ? String(cards.length) : visible + \" / \" + cards.length;\n }\n group.hidden = visible === 0;\n });\n if (endpointSection) {\n endpointSection.classList.toggle(\"filtering\", Boolean(q) || active.length > 0);\n }\n if (endpointCount) {\n endpointCount.textContent = shown === endpointCards.length ? String(shown) : shown + \" / \" + endpointCards.length;\n }\n if (endpointEmpty) endpointEmpty.classList.toggle(\"show\", shown === 0);\n }\n\n if (search) search.addEventListener(\"input\", applyEndpointFilter);\n methodChips.forEach(function (chip) {\n chip.addEventListener(\"click\", function () {\n chip.setAttribute(\"aria-pressed\", chip.getAttribute(\"aria-pressed\") === \"true\" ? \"false\" : \"true\");\n applyEndpointFilter();\n });\n });\n\n // Passive-finding severity chip filter.\n var sevChips = toArray(document.querySelectorAll(\"[data-filter-severity]\"));\n var passiveCards = toArray(document.querySelectorAll(\"[data-passive]\"));\n var passiveCount = document.getElementById(\"passive-count\");\n var passiveEmpty = document.getElementById(\"passive-no-results\");\n\n function applyPassiveFilter() {\n var active = sevChips\n .filter(function (c) { return c.getAttribute(\"aria-pressed\") === \"true\"; })\n .map(function (c) { return c.getAttribute(\"data-filter-severity\"); });\n var shown = 0;\n passiveCards.forEach(function (card) {\n var visible = active.length === 0 || active.indexOf(card.getAttribute(\"data-severity\")) !== -1;\n card.hidden = !visible;\n if (visible) shown++;\n });\n if (passiveCount) {\n passiveCount.textContent = shown === passiveCards.length ? String(shown) : shown + \" / \" + passiveCards.length;\n }\n if (passiveEmpty) passiveEmpty.classList.toggle(\"show\", shown === 0);\n }\n\n sevChips.forEach(function (chip) {\n chip.addEventListener(\"click\", function () {\n chip.setAttribute(\"aria-pressed\", chip.getAttribute(\"aria-pressed\") === \"true\" ? \"false\" : \"true\");\n applyPassiveFilter();\n });\n });\n})();"
STYLES = "/* ===== OWASP Noir report theme ==================================\n Dark-tech base with restrained semantic color: hue is reserved\n for meaning (HTTP method risk, finding severity, one emerald\n brand accent). Chrome stays neutral in both themes. */\n:root {\n --bg: #fafafa;\n --bg-subtle: #f3f3f4;\n --surface: #fdfdfd;\n --ink: #111114;\n --ink-2: #404046;\n --ink-3: #6f6f7a;\n --line: #e6e6e9;\n --line-2: #d2d2d8;\n --fill: #141418;\n --on-fill: #f7f7f8;\n --hover: #f0f0f2;\n --selection: rgba(17, 17, 20, 0.12);\n --accent: #ee2a22;\n --font-sans: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n --font-mono: ui-monospace, \"SF Mono\", SFMono-Regular, \"JetBrains Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n /* semantic method hues (AA on --bg / tint backgrounds) */\n --m-get: #047857;\n --m-get-soft: rgba(4, 120, 87, 0.08);\n --m-get-line: rgba(4, 120, 87, 0.38);\n --m-post: #854d0e;\n --m-post-soft: rgba(180, 83, 9, 0.10);\n --m-post-line: rgba(180, 83, 9, 0.42);\n --m-put: #1d4ed8;\n --m-put-soft: rgba(29, 78, 216, 0.08);\n --m-put-line: rgba(29, 78, 216, 0.38);\n --m-patch: #6d28d9;\n --m-patch-soft: rgba(109, 40, 217, 0.08);\n --m-patch-line: rgba(109, 40, 217, 0.38);\n --m-delete: #be123c;\n --m-delete-soft: rgba(190, 18, 60, 0.08);\n --m-delete-line: rgba(190, 18, 60, 0.38);\n /* semantic severity hues */\n --sev-critical-bg: #b91c1c;\n --sev-critical-ink: #fdf2f2;\n --sev-high: #b91c1c;\n --sev-high-soft: rgba(185, 28, 28, 0.08);\n --sev-high-line: rgba(185, 28, 28, 0.40);\n --sev-medium: #854d0e;\n --sev-medium-soft: rgba(180, 83, 9, 0.10);\n --sev-medium-line: rgba(180, 83, 9, 0.42);\n --sev-low: #475569;\n --sev-low-soft: rgba(71, 85, 105, 0.08);\n --sev-low-line: rgba(71, 85, 105, 0.38);\n}\n[data-theme=\"dark\"] {\n --bg: #050507;\n --bg-subtle: #0a0a0e;\n --surface: #0b0b10;\n --ink: #ededf0;\n --ink-2: #b4b4c2;\n --ink-3: #74748a;\n --line: #1a1a22;\n --line-2: #2a2a36;\n --fill: #ededf0;\n --on-fill: #0b0b10;\n --hover: #131319;\n --selection: rgba(237, 237, 240, 0.16);\n --accent: #ff6a5e;\n --m-get: #34d399;\n --m-get-soft: rgba(52, 211, 153, 0.09);\n --m-get-line: rgba(52, 211, 153, 0.34);\n --m-post: #fbbf24;\n --m-post-soft: rgba(251, 191, 36, 0.09);\n --m-post-line: rgba(251, 191, 36, 0.34);\n --m-put: #60a5fa;\n --m-put-soft: rgba(96, 165, 250, 0.09);\n --m-put-line: rgba(96, 165, 250, 0.34);\n --m-patch: #a78bfa;\n --m-patch-soft: rgba(167, 139, 250, 0.09);\n --m-patch-line: rgba(167, 139, 250, 0.34);\n --m-delete: #f87171;\n --m-delete-soft: rgba(248, 113, 113, 0.09);\n --m-delete-line: rgba(248, 113, 113, 0.34);\n --sev-critical-bg: #e05252;\n --sev-critical-ink: #0b0b10;\n --sev-high: #f87171;\n --sev-high-soft: rgba(248, 113, 113, 0.09);\n --sev-high-line: rgba(248, 113, 113, 0.36);\n --sev-medium: #fbbf24;\n --sev-medium-soft: rgba(251, 191, 36, 0.09);\n --sev-medium-line: rgba(251, 191, 36, 0.34);\n --sev-low: #94a3b8;\n --sev-low-soft: rgba(148, 163, 184, 0.09);\n --sev-low-line: rgba(148, 163, 184, 0.34);\n}\n* { margin: 0; padding: 0; box-sizing: border-box; }\n::selection { background: var(--selection); }\nhtml { scroll-behavior: smooth; }\nbody {\n font-family: var(--font-sans);\n background: var(--bg);\n color: var(--ink);\n line-height: 1.6;\n font-size: 15px;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n transition: background-color 0.2s ease, color 0.2s ease;\n}\n.container { max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; }\na { color: inherit; }\n.visually-hidden {\n position: absolute;\n width: 1px; height: 1px;\n margin: -1px; padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n border: 0;\n}\n\n/* ===== Header ================================================= */\n.report-header {\n border-top: 2px solid var(--accent);\n border-bottom: 1px solid var(--line);\n background: var(--bg);\n}\n.report-header .container {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding-top: 1.75rem;\n padding-bottom: 1.75rem;\n}\n.brand { display: flex; align-items: center; gap: 0.85rem; min-width: 0; }\n.brand-mark {\n width: 38px; height: 38px;\n flex-shrink: 0;\n display: block;\n}\n.brand-text { display: flex; flex-direction: column; min-width: 0; }\n.brand-eyebrow {\n font-family: var(--font-mono);\n font-size: 0.66rem;\n letter-spacing: 0.28em;\n text-transform: uppercase;\n color: var(--ink-3);\n}\n.brand-title {\n font-family: var(--font-mono);\n font-size: 1.4rem;\n font-weight: 700;\n letter-spacing: -0.02em;\n line-height: 1.1;\n}\n.header-actions { display: flex; align-items: center; gap: 1.25rem; flex-shrink: 0; }\n.header-tagline {\n font-family: var(--font-mono);\n font-size: 0.72rem;\n color: var(--ink-3);\n text-align: right;\n}\n.theme-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px; height: 40px;\n padding: 0;\n background: var(--surface);\n color: var(--ink);\n border: 1px solid var(--line-2);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;\n}\n.theme-toggle:hover { background: var(--hover); border-color: var(--ink-3); }\n.theme-toggle:active { transform: scale(0.95); }\n.theme-toggle:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }\n.theme-toggle svg { width: 17px; height: 17px; display: block; }\n.theme-toggle .icon-sun { display: none; }\n[data-theme=\"dark\"] .theme-toggle .icon-sun { display: block; }\n[data-theme=\"dark\"] .theme-toggle .icon-moon { display: none; }\n\n/* ===== Layout ================================================= */\nmain.container { padding-top: 2.5rem; padding-bottom: 2.5rem; }\n.section { margin-bottom: 3rem; }\n.section-title {\n font-family: var(--font-mono);\n font-size: 0.8rem;\n font-weight: 700;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: var(--ink-2);\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding-bottom: 0.6rem;\n margin-bottom: 1.25rem;\n border-bottom: 1px solid var(--line);\n}\n.section-title::before {\n content: \"\";\n width: 9px; height: 9px;\n background: var(--accent);\n flex-shrink: 0;\n}\n.section-count {\n margin-left: auto;\n font-weight: 500;\n color: var(--ink-3);\n letter-spacing: 0.08em;\n font-variant-numeric: tabular-nums;\n}\n\n/* ===== Controls: search, filter chips, view toggle ============ */\n.controls {\n position: sticky;\n top: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n flex-wrap: wrap;\n padding: 0.7rem 0;\n margin-bottom: 1.25rem;\n background: var(--bg);\n border-bottom: 1px solid var(--line);\n}\n.search { position: relative; flex: 1 1 260px; display: flex; align-items: center; }\n.search svg {\n position: absolute; left: 0.65rem;\n width: 15px; height: 15px;\n color: var(--ink-3);\n pointer-events: none;\n}\n.search input {\n width: 100%;\n font-family: var(--font-mono);\n font-size: 0.82rem;\n color: var(--ink);\n background: var(--surface);\n border: 1px solid var(--line-2);\n padding: 0.5rem 0.6rem 0.5rem 1.95rem;\n}\n.search input::placeholder { color: var(--ink-3); }\n.search input:focus { outline: none; border-color: var(--accent); }\n.search input::-webkit-search-cancel-button { -webkit-appearance: none; }\n.chips { display: flex; gap: 0.4rem; flex-wrap: wrap; }\n.chip {\n font-family: var(--font-mono);\n font-size: 0.68rem;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n padding: 0.4rem 0.65rem;\n color: var(--ink-2);\n background: var(--surface);\n border: 1px solid var(--line-2);\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n}\n.chip:hover { border-color: var(--ink-3); color: var(--ink); }\n.chip[aria-pressed=\"true\"] { background: var(--fill); color: var(--on-fill); border-color: var(--fill); }\n.chip:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }\n.chip.chip-get[aria-pressed=\"true\"] { color: var(--m-get); background: var(--m-get-soft); border-color: var(--m-get-line); }\n.chip.chip-post[aria-pressed=\"true\"] { color: var(--m-post); background: var(--m-post-soft); border-color: var(--m-post-line); }\n.chip.chip-put[aria-pressed=\"true\"] { color: var(--m-put); background: var(--m-put-soft); border-color: var(--m-put-line); }\n.chip.chip-patch[aria-pressed=\"true\"] { color: var(--m-patch); background: var(--m-patch-soft); border-color: var(--m-patch-line); }\n.chip.chip-delete[aria-pressed=\"true\"] { color: var(--m-delete); background: var(--m-delete-soft); border-color: var(--m-delete-line); }\n.chip.chip-critical[aria-pressed=\"true\"] { color: var(--sev-critical-ink); background: var(--sev-critical-bg); border-color: var(--sev-critical-bg); }\n.chip.chip-high[aria-pressed=\"true\"] { color: var(--sev-high); background: var(--sev-high-soft); border-color: var(--sev-high-line); }\n.chip.chip-medium[aria-pressed=\"true\"] { color: var(--sev-medium); background: var(--sev-medium-soft); border-color: var(--sev-medium-line); }\n.chip.chip-low[aria-pressed=\"true\"] { color: var(--sev-low); background: var(--sev-low-soft); border-color: var(--sev-low-line); }\n.view-seg { display: inline-flex; border: 1px solid var(--line-2); }\n.view-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n font-family: var(--font-mono);\n font-size: 0.68rem;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n padding: 0.4rem 0.65rem;\n color: var(--ink-3);\n background: var(--surface);\n border: none;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease;\n}\n.view-btn + .view-btn { border-left: 1px solid var(--line-2); }\n.view-btn:hover { color: var(--ink); }\n.view-btn[aria-pressed=\"true\"] { background: var(--fill); color: var(--on-fill); }\n.view-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }\n.view-btn svg { width: 13px; height: 13px; display: block; }\n.no-results { display: none; }\n.no-results.show { display: block; }\n\n/* ===== Summary stat strip ===================================== */\n.summary {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n border: 1px solid var(--line);\n background: var(--surface);\n margin-bottom: 3rem;\n}\n.summary-card {\n padding: 1.5rem 1.5rem;\n border-left: 1px solid var(--line);\n}\n.summary-card:first-child { border-left: none; }\n.summary-card h3 {\n font-family: var(--font-mono);\n font-size: 2.4rem;\n font-weight: 700;\n line-height: 1;\n letter-spacing: -0.03em;\n font-variant-numeric: tabular-nums;\n}\n.summary-card p {\n color: var(--ink-3);\n font-family: var(--font-mono);\n font-size: 0.68rem;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n margin-top: 0.55rem;\n}\n\n/* ===== Path groups ============================================ */\n.group { border: 1px solid var(--line); margin-bottom: -1px; }\n.group-header {\n width: 100%;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n font-family: var(--font-mono);\n font-size: 0.74rem;\n font-weight: 600;\n letter-spacing: 0.04em;\n text-align: left;\n color: var(--ink-2);\n background: var(--bg-subtle);\n border: none;\n border-bottom: 1px solid var(--line);\n padding: 0.55rem 1rem;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease;\n}\n.group-header:hover { background: var(--hover); color: var(--ink); }\n.group-header:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }\n.group-name { word-break: break-all; }\n.group-count {\n margin-left: auto;\n font-weight: 500;\n color: var(--ink-3);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.group .card { border-left: none; border-right: none; }\n.group .card:last-child { border-bottom: none; margin-bottom: 0; }\n.group.collapsed .group-header { border-bottom: none; }\n.group.collapsed .group-header .chevron { transform: rotate(-90deg); }\n.group.collapsed .group-body { display: none; }\n.section.filtering .group.collapsed .group-body { display: block; }\nhtml[data-group=\"off\"] .group-header { display: none; }\nhtml[data-group=\"off\"] .group { border: none; margin: 0; }\nhtml[data-group=\"off\"] .group .card { border: 1px solid var(--line); margin-bottom: -1px; }\nhtml[data-group=\"off\"] .group.collapsed .group-body { display: block; }\n\n/* ===== Cards ================================================== */\n.card {\n background: var(--surface);\n border: 1px solid var(--line);\n margin-bottom: -1px;\n}\n.card:hover { border-color: var(--line-2); position: relative; z-index: 1; }\n.card-header { display: flex; align-items: stretch; }\n.card-toggle {\n flex: 1 1 auto;\n min-width: 0;\n padding: 0.85rem 1rem;\n display: flex;\n align-items: center;\n gap: 0.7rem;\n flex-wrap: wrap;\n}\nbutton.card-toggle {\n font: inherit;\n color: inherit;\n text-align: left;\n background: transparent;\n border: none;\n cursor: pointer;\n transition: background-color 0.12s ease;\n}\nbutton.card-toggle:hover { background: var(--hover); }\nbutton.card-toggle:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }\n.chevron {\n width: 16px; height: 16px;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--ink-3);\n transition: transform 0.2s ease;\n}\n.chevron svg { width: 12px; height: 12px; display: block; }\n.chevron-spacer { width: 16px; flex-shrink: 0; }\n.card.collapsed .chevron { transform: rotate(-90deg); }\n.card-collapse {\n display: grid;\n grid-template-rows: 1fr;\n transition: grid-template-rows 0.22s ease;\n}\n.card.collapsed .card-collapse { grid-template-rows: 0fr; }\n.card-collapse > .card-pane { overflow: hidden; min-height: 0; }\n.url {\n font-family: var(--font-mono);\n font-size: 0.88rem;\n font-weight: 500;\n word-break: break-all;\n}\n.card-details {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n flex-wrap: wrap;\n min-width: 0;\n}\n.card-meta {\n font-family: var(--font-mono);\n font-size: 0.66rem;\n color: var(--ink-3);\n letter-spacing: 0.05em;\n white-space: nowrap;\n}\n.card-actions {\n display: flex;\n align-items: center;\n gap: 0.15rem;\n padding: 0 0.6rem;\n border-left: 1px solid var(--line);\n}\n.copy-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px; height: 30px;\n padding: 0;\n background: transparent;\n color: var(--ink-3);\n border: 1px solid transparent;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;\n}\n.copy-btn:hover { color: var(--ink); background: var(--hover); border-color: var(--line-2); }\n.copy-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }\n.copy-btn svg { width: 14px; height: 14px; display: block; }\n.copy-btn .icon-check { display: none; }\n.copy-btn.copied { color: var(--accent); }\n.copy-btn.copied .icon-copy { display: none; }\n.copy-btn.copied .icon-check { display: block; }\n\n/* ===== Method badges: semantic risk hues ====================== */\n.method-badge {\n display: inline-block;\n padding: 0.2rem 0.6rem;\n font-family: var(--font-mono);\n font-size: 0.7rem;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n border: 1px solid var(--line-2);\n min-width: 4.6em;\n text-align: center;\n flex-shrink: 0;\n}\n.method-get { color: var(--m-get); background: var(--m-get-soft); border-color: var(--m-get-line); }\n.method-post { color: var(--m-post); background: var(--m-post-soft); border-color: var(--m-post-line); }\n.method-put { color: var(--m-put); background: var(--m-put-soft); border-color: var(--m-put-line); }\n.method-patch { color: var(--m-patch); background: var(--m-patch-soft); border-color: var(--m-patch-line); }\n.method-delete { color: var(--m-delete); background: var(--m-delete-soft); border-color: var(--m-delete-line); }\n.method-default { background: transparent; color: var(--ink-3); border-color: var(--line-2); border-style: dashed; }\n\n.protocol-badge {\n font-family: var(--font-mono);\n font-size: 0.66rem;\n padding: 0.12rem 0.45rem;\n border: 1px solid var(--line-2);\n color: var(--ink-3);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n}\n.tag-badge {\n display: inline-block;\n font-family: var(--font-mono);\n font-size: 0.66rem;\n padding: 0.12rem 0.45rem;\n background: var(--bg-subtle);\n border: 1px solid var(--line);\n color: var(--ink-2);\n}\n\n/* ===== Card body ============================================= */\n.card-body {\n padding: 0 1rem 1rem;\n border-top: 1px solid var(--line);\n padding-top: 1rem;\n}\n.params-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.84rem;\n}\n.params-table th, .params-table td {\n padding: 0.5rem 0.6rem;\n text-align: left;\n border-bottom: 1px solid var(--line);\n}\n.params-table tr:last-child td { border-bottom: none; }\n.params-table th {\n font-family: var(--font-mono);\n font-size: 0.64rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.12em;\n color: var(--ink-3);\n}\n.params-table td:first-child { font-family: var(--font-mono); }\n.params-table td:last-child { font-family: var(--font-mono); color: var(--ink-2); word-break: break-all; }\n\n/* ===== Param-type chips: uniform monochrome ================== */\n.param-type {\n display: inline-block;\n font-family: var(--font-mono);\n padding: 0.1rem 0.4rem;\n font-size: 0.64rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n border: 1px solid var(--line-2);\n color: var(--ink-2);\n}\n.param-query, .param-json, .param-form,\n.param-header, .param-cookie, .param-path { background: var(--bg-subtle); }\n.param-path, .param-header { background: var(--ink); color: var(--on-fill); border-color: var(--ink); }\n\n/* ===== Severity badges: semantic ramp ======================== */\n.severity-critical { background: var(--sev-critical-bg); color: var(--sev-critical-ink); border-color: var(--sev-critical-bg); }\n.severity-high { color: var(--sev-high); background: var(--sev-high-soft); border-color: var(--sev-high-line); }\n.severity-medium { color: var(--sev-medium); background: var(--sev-medium-soft); border-color: var(--sev-medium-line); }\n.severity-low { color: var(--sev-low); background: var(--sev-low-soft); border-color: var(--sev-low-line); }\n.severity-info { background: transparent; color: var(--ink-3); border-color: var(--line-2); }\n.passive-card .card-header { padding: 0.85rem 1rem; align-items: center; gap: 0.7rem; flex-wrap: wrap; }\n\n.code-path {\n font-family: var(--font-mono);\n font-size: 0.76rem;\n color: var(--ink-3);\n margin-top: 0.35rem;\n}\n.code-path .marker { color: var(--ink-2); }\n.card-body p { font-size: 0.86rem; }\n.card-body p + p { margin-top: 0.5rem; }\n.card-body code {\n font-family: var(--font-mono);\n font-size: 0.8rem;\n background: var(--bg-subtle);\n border: 1px solid var(--line);\n padding: 0.05rem 0.3rem;\n}\n\n.empty-state {\n text-align: center;\n padding: 3rem 1rem;\n color: var(--ink-3);\n font-family: var(--font-mono);\n font-size: 0.85rem;\n border: 1px dashed var(--line-2);\n}\n\n/* ===== Compact table view ==================================== */\n.table-head { display: none; }\n@media (min-width: 721px) {\n html[data-view=\"table\"] .table-head {\n display: grid;\n grid-template-columns: 16px 6.8em minmax(0, 1fr) auto;\n column-gap: 0.7rem;\n align-items: center;\n padding: 0.45rem 1rem;\n padding-right: 102px;\n font-family: var(--font-mono);\n font-size: 0.62rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.14em;\n color: var(--ink-3);\n background: var(--bg-subtle);\n border: 1px solid var(--line);\n border-bottom: none;\n }\n html[data-view=\"table\"] .table-head .th-details { justify-self: end; }\n html[data-view=\"table\"] .card[data-endpoint] .card-toggle {\n display: grid;\n grid-template-columns: 16px 6.8em minmax(0, 1fr) auto;\n column-gap: 0.7rem;\n align-items: center;\n padding: 0.5rem 1rem;\n }\n html[data-view=\"table\"] .card[data-endpoint] .url {\n font-size: 0.8rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: normal;\n }\n html[data-view=\"table\"] .card[data-endpoint] .card-details {\n justify-self: end;\n flex-wrap: nowrap;\n overflow: hidden;\n }\n html[data-view=\"table\"] .card[data-endpoint] .card-actions {\n width: 86px;\n justify-content: flex-end;\n }\n /* Table view is a compact scan mode: bodies stay in the cards view. */\n html[data-view=\"table\"] .card[data-endpoint] .card-collapse { display: none; }\n html[data-view=\"table\"] .card[data-endpoint] .chevron { visibility: hidden; }\n html[data-view=\"table\"] .card[data-endpoint] button.card-toggle { cursor: default; }\n html[data-view=\"table\"] .card[data-endpoint] button.card-toggle:hover { background: transparent; }\n}\n\n/* ===== Footer ================================================ */\nfooter {\n border-top: 1px solid var(--line);\n padding: 2rem 0;\n margin-top: 1rem;\n}\nfooter .container {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 1rem;\n flex-wrap: wrap;\n color: var(--ink-3);\n font-family: var(--font-mono);\n font-size: 0.75rem;\n}\nfooter a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--line-2); }\nfooter a:hover { border-bottom-color: var(--ink); }\n\n@media (max-width: 720px) {\n .summary { grid-template-columns: repeat(2, 1fr); }\n .summary-card:nth-child(3) { border-left: none; }\n .summary-card:nth-child(n+3) { border-top: 1px solid var(--line); }\n .header-tagline { display: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n * { transition: none !important; scroll-behavior: auto !important; }\n}\n\n@media print {\n body { background: #fff; color: #000; }\n .report-header, footer { border-color: #ccc; }\n .card { break-inside: avoid; }\n .card.collapsed .card-collapse { grid-template-rows: 1fr; }\n .group.collapsed .group-body { display: block; }\n .chevron, .theme-toggle, .controls, .card-actions, .table-head { display: none; }\n}"
THEME_BOOT = "(function () {\n try {\n var saved = localStorage.getItem(\"noir-theme\");\n if (saved === \"dark\" || saved === \"light\") {\n document.documentElement.setAttribute(\"data-theme\", saved);\n } else if (window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\").matches) {\n document.documentElement.setAttribute(\"data-theme\", \"dark\");\n }\n if (localStorage.getItem(\"noir-view\") === \"table\") {\n document.documentElement.setAttribute(\"data-view\", \"table\");\n }\n if (localStorage.getItem(\"noir-group\") === \"off\") {\n document.documentElement.setAttribute(\"data-group\", \"off\");\n }\n } catch (e) {}\n})();"

Applies persisted theme/view/group preferences before first paint so the page never flashes the wrong state.