@opsydyn/elysia-spectral 1.3.0 → 1.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.0](https://github.com/opsydyn/elysia-spectral/compare/v1.3.1...v1.4.0) (2026-04-28)
4
+
5
+
6
+ ### Features
7
+
8
+ * **dashboard:** redesign with sharp-edge knowledge-store aesthetic ([81c3082](https://github.com/opsydyn/elysia-spectral/commit/81c3082b49a79a660d6d12e035ad57a9e2f24253))
9
+
10
+ ## [1.3.1](https://github.com/opsydyn/elysia-spectral/compare/v1.3.0...v1.3.1) (2026-04-28)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **dashboard:** stop double-encoding inlined CSS and JS assets ([a4457bf](https://github.com/opsydyn/elysia-spectral/commit/a4457bf87fc30e5a0e68a580796085f39aa49dbe))
16
+
3
17
  ## [1.3.0](https://github.com/opsydyn/elysia-spectral/compare/v1.2.0...v1.3.0) (2026-04-28)
4
18
 
5
19
 
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { a as enforceThreshold, c as strict, d as RulesetLoadError, f as defaultRulesetResolvers, g as lintOpenApi, h as recommended, i as OpenApiLintThresholdError, l as server, m as loadRuleset, n as createOpenApiLintRuntime, o as shouldFail, p as loadResolvedRuleset, r as resolveStartupMode, s as presets, t as OpenApiLintArtifactWriteError, u as resolveReporter } from "./core-BLJeXQ15.mjs";
2
2
  import { Elysia } from "elysia";
3
- //#region \0text:/home/runner/work/elysia-spectral/elysia-spectral/packages/elysia-spectral/src/output/dashboard.client.js.txt
4
- var dashboard_client_js_default = "export default \"(() => {\\n const THEME_KEY = 'elysia-spectral-theme';\\n const THEMES = ['astro', 'tron', '808'];\\n let stored = null;\\n try {\\n stored = localStorage.getItem(THEME_KEY);\\n } catch {}\\n const initial = THEMES.includes(stored) ? stored : 'astro';\\n document.documentElement.dataset.theme = initial;\\n\\n const themeSel = document.querySelector('[data-theme-switcher]');\\n if (themeSel) {\\n themeSel.value = initial;\\n themeSel.addEventListener('change', () => {\\n const next = THEMES.includes(themeSel.value) ? themeSel.value : 'astro';\\n document.documentElement.dataset.theme = next;\\n try {\\n localStorage.setItem(THEME_KEY, next);\\n } catch {}\\n });\\n }\\n\\n const rel = (iso) => {\\n const t = Date.parse(iso);\\n if (Number.isNaN(t)) return '';\\n const s = Math.round((Date.now() - t) / 1000);\\n if (s < 60) return s + 's ago';\\n if (s < 3600) return Math.round(s / 60) + 'm ago';\\n if (s < 86400) return Math.round(s / 3600) + 'h ago';\\n return Math.round(s / 86400) + 'd ago';\\n };\\n for (const el of document.querySelectorAll('[data-relative-time]')) {\\n el.textContent = rel(el.getAttribute('data-relative-time'));\\n }\\n\\n const rows = Array.from(document.querySelectorAll('[data-findings] tr'));\\n const search = document.querySelector('[data-search]');\\n const empty = document.querySelector('[data-empty-findings]');\\n const chips = Array.from(document.querySelectorAll('[data-filter]'));\\n let activeSeverity = 'all';\\n let query = '';\\n\\n const apply = () => {\\n let visible = 0;\\n for (const tr of rows) {\\n const sev = tr.getAttribute('data-severity');\\n const hay = tr.getAttribute('data-haystack') || '';\\n const sevOk = activeSeverity === 'all' || sev === activeSeverity;\\n const qOk = !query || hay.includes(query);\\n const show = sevOk && qOk;\\n tr.classList.toggle('is-hidden', !show);\\n if (show) visible += 1;\\n }\\n if (empty)\\n empty.classList.toggle('hidden', visible !== 0 || rows.length === 0);\\n };\\n\\n for (const chip of chips) {\\n const select = () => {\\n activeSeverity = chip.getAttribute('data-filter') || 'all';\\n for (const c of chips) c.classList.toggle('is-active', c === chip);\\n apply();\\n };\\n chip.addEventListener('click', select);\\n chip.addEventListener('keydown', (e) => {\\n if (e.key === 'Enter' || e.key === ' ') {\\n e.preventDefault();\\n select();\\n }\\n });\\n }\\n\\n if (search) {\\n search.addEventListener('input', () => {\\n query = search.value.trim().toLowerCase();\\n apply();\\n });\\n }\\n\\n for (const btn of document.querySelectorAll('[data-copy]')) {\\n btn.addEventListener('click', async () => {\\n const value = btn.getAttribute('data-copy') || '';\\n try {\\n await navigator.clipboard.writeText(value);\\n btn.classList.add('copied');\\n btn.textContent = 'copied';\\n setTimeout(() => {\\n btn.classList.remove('copied');\\n btn.textContent = 'copy';\\n }, 1200);\\n } catch {}\\n });\\n }\\n\\n document.addEventListener('keydown', (e) => {\\n if (\\n e.target &&\\n (e.target.tagName === 'INPUT' ||\\n e.target.tagName === 'TEXTAREA' ||\\n e.target.tagName === 'SELECT')\\n )\\n return;\\n if (e.key === 'r') {\\n const link = document.querySelector('[data-refresh]');\\n if (link) link.click();\\n }\\n if (e.key === '/' && search) {\\n e.preventDefault();\\n search.focus();\\n }\\n });\\n})();\\n\";";
3
+ //#region \0inline-text:3.mjs
4
+ var _inline_text_3_default = "(() => {\n const THEME_KEY = 'elysia-spectral-theme';\n const THEMES = ['astro', 'tron', '808'];\n let stored = null;\n try {\n stored = localStorage.getItem(THEME_KEY);\n } catch {}\n const initial = THEMES.includes(stored) ? stored : 'astro';\n document.documentElement.dataset.theme = initial;\n\n const themeSel = document.querySelector('[data-theme-switcher]');\n if (themeSel) {\n themeSel.value = initial;\n themeSel.addEventListener('change', () => {\n const next = THEMES.includes(themeSel.value) ? themeSel.value : 'astro';\n document.documentElement.dataset.theme = next;\n try {\n localStorage.setItem(THEME_KEY, next);\n } catch {}\n });\n }\n\n const rel = (iso) => {\n const t = Date.parse(iso);\n if (Number.isNaN(t)) return '';\n const s = Math.round((Date.now() - t) / 1000);\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.round(s / 60) + 'm ago';\n if (s < 86400) return Math.round(s / 3600) + 'h ago';\n return Math.round(s / 86400) + 'd ago';\n };\n for (const el of document.querySelectorAll('[data-relative-time]')) {\n el.textContent = rel(el.getAttribute('data-relative-time'));\n }\n\n const rows = Array.from(document.querySelectorAll('[data-findings] tr'));\n const search = document.querySelector('[data-search]');\n const empty = document.querySelector('[data-empty-findings]');\n const chips = Array.from(document.querySelectorAll('[data-filter]'));\n let activeSeverity = 'all';\n let query = '';\n\n const apply = () => {\n let visible = 0;\n for (const tr of rows) {\n const sev = tr.getAttribute('data-severity');\n const hay = tr.getAttribute('data-haystack') || '';\n const sevOk = activeSeverity === 'all' || sev === activeSeverity;\n const qOk = !query || hay.includes(query);\n const show = sevOk && qOk;\n tr.classList.toggle('is-hidden', !show);\n if (show) visible += 1;\n }\n if (empty)\n empty.classList.toggle('hidden', visible !== 0 || rows.length === 0);\n };\n\n for (const chip of chips) {\n const select = () => {\n activeSeverity = chip.getAttribute('data-filter') || 'all';\n for (const c of chips) c.classList.toggle('is-active', c === chip);\n apply();\n };\n chip.addEventListener('click', select);\n chip.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n select();\n }\n });\n }\n\n if (search) {\n search.addEventListener('input', () => {\n query = search.value.trim().toLowerCase();\n apply();\n });\n }\n\n for (const btn of document.querySelectorAll('[data-copy]')) {\n btn.addEventListener('click', async () => {\n const value = btn.getAttribute('data-copy') || '';\n try {\n await navigator.clipboard.writeText(value);\n btn.classList.add('copied');\n btn.textContent = 'copied';\n setTimeout(() => {\n btn.classList.remove('copied');\n btn.textContent = 'copy';\n }, 1200);\n } catch {}\n });\n }\n\n document.addEventListener('keydown', (e) => {\n if (\n e.target &&\n (e.target.tagName === 'INPUT' ||\n e.target.tagName === 'TEXTAREA' ||\n e.target.tagName === 'SELECT')\n )\n return;\n if (e.key === 'r') {\n const link = document.querySelector('[data-refresh]');\n if (link) link.click();\n }\n if (e.key === '/' && search) {\n e.preventDefault();\n search.focus();\n }\n });\n})();\n";
5
5
  //#endregion
6
- //#region \0text:/home/runner/work/elysia-spectral/elysia-spectral/packages/elysia-spectral/src/output/dashboard.css.txt
7
- var dashboard_css_default = "export default \":root {\\n color-scheme: dark;\\n --bg: #17191e;\\n --fg: #eef0f9;\\n --muted: #8a93a0;\\n --line: #262a33;\\n --surface: #1d2027;\\n --accent: #ad5dca;\\n --accent-soft: #2b7eca;\\n --pass: #23d18b;\\n --fail: #dc3657;\\n --warn: #ffc368;\\n --info: #54b9ff;\\n --hint: #545864;\\n --mono:\\n ui-monospace, SFMono-Regular, \\\"JetBrains Mono\\\", Menlo, Monaco, Consolas,\\n \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace;\\n}\\n\\n[data-theme=\\\"tron\\\"] {\\n --bg: #06080d;\\n --fg: #e6f7ff;\\n --muted: #5d8aa3;\\n --line: #0e2c3d;\\n --surface: #0a1320;\\n --accent: #38f3ff;\\n --accent-soft: #1ea7ff;\\n --pass: #7de8ff;\\n --fail: #ff3c5f;\\n --warn: #a8f5ff;\\n --info: #73d8ff;\\n --hint: #273746;\\n}\\n\\n[data-theme=\\\"808\\\"] {\\n --bg: #202020;\\n --fg: #ffffff;\\n --muted: #9a958a;\\n --line: #2a2a2a;\\n --surface: #262626;\\n --accent: #f8a125;\\n --accent-soft: #e72e2e;\\n --pass: #f1f827;\\n --fail: #e72e2e;\\n --warn: #f8a125;\\n --info: #f1f827;\\n --hint: #404040;\\n}\\n\\n* {\\n box-sizing: border-box;\\n}\\n\\nbody {\\n margin: 0;\\n font-family: var(--mono);\\n font-feature-settings:\\n \\\"calt\\\" 0,\\n \\\"liga\\\" 0,\\n \\\"ss01\\\";\\n background: var(--bg);\\n color: var(--fg);\\n font-size: 13px;\\n line-height: 1.5;\\n}\\n\\nheader {\\n position: sticky;\\n top: 0;\\n z-index: 10;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n padding: 16px 24px;\\n border-bottom: 1px solid var(--line);\\n background: color-mix(in srgb, var(--bg) 88%, transparent);\\n backdrop-filter: blur(8px);\\n overflow: hidden;\\n}\\n\\nheader::before {\\n content: \\\"\\\";\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n background:\\n radial-gradient(900px 220px at 12% -30%, var(--accent), transparent 60%),\\n radial-gradient(\\n 700px 200px at 88% -20%,\\n var(--accent-soft),\\n transparent 65%\\n );\\n opacity: 0.45;\\n pointer-events: none;\\n}\\n\\nheader > * {\\n position: relative;\\n z-index: 1;\\n}\\n\\n.brand {\\n display: flex;\\n align-items: center;\\n gap: 10px;\\n}\\n\\n.dot {\\n width: 8px;\\n height: 8px;\\n border-radius: 50%;\\n background: var(--accent);\\n box-shadow:\\n 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent),\\n 0 0 18px color-mix(in srgb, var(--accent) 60%, transparent);\\n}\\n\\nh1 {\\n margin: 0;\\n font-size: 13px;\\n font-weight: 600;\\n letter-spacing: 0.02em;\\n text-transform: uppercase;\\n}\\n\\n.actions {\\n display: flex;\\n align-items: center;\\n gap: 10px;\\n}\\n\\n.visually-hidden {\\n position: absolute;\\n width: 1px;\\n height: 1px;\\n overflow: hidden;\\n clip: rect(0 0 0 0);\\n}\\n\\n.theme-switch {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n}\\n\\n.theme-label {\\n font-size: 10px;\\n text-transform: uppercase;\\n letter-spacing: 0.08em;\\n color: var(--muted);\\n}\\n\\n.theme-switch select {\\n font-family: var(--mono);\\n font-size: 11px;\\n padding: 5px 24px 5px 10px;\\n border: 1px solid var(--line);\\n border-radius: 6px;\\n background: var(--surface)\\n url(\\\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='none' stroke='%238a93a0' stroke-width='1.4' d='M1 1l4 4 4-4'/></svg>\\\")\\n no-repeat right 8px center;\\n color: var(--fg);\\n -webkit-appearance: none;\\n appearance: none;\\n cursor: pointer;\\n}\\n.theme-switch select:hover {\\n border-color: var(--muted);\\n}\\n.theme-switch select:focus {\\n outline: none;\\n border-color: var(--accent);\\n}\\n\\nkbd {\\n font-family: var(--mono);\\n font-size: 11px;\\n padding: 2px 6px;\\n border: 1px solid var(--line);\\n border-bottom-width: 2px;\\n border-radius: 4px;\\n color: var(--muted);\\n background: var(--surface);\\n}\\n\\n.refresh {\\n color: var(--fg);\\n text-decoration: none;\\n padding: 6px 12px;\\n border: 1px solid var(--line);\\n border-radius: 6px;\\n font-size: 12px;\\n font-family: var(--mono);\\n background: var(--surface);\\n transition:\\n border-color 0.15s,\\n transform 0.05s;\\n}\\n.refresh:hover {\\n border-color: var(--accent);\\n}\\n.refresh:active {\\n transform: translateY(1px);\\n}\\n\\nmain {\\n padding: 24px;\\n max-width: 1100px;\\n margin: 0 auto;\\n}\\n\\n.banner {\\n padding: 12px 16px;\\n border-radius: 8px;\\n margin-bottom: 16px;\\n font-size: 13px;\\n}\\n.banner-pass {\\n background: color-mix(in srgb, var(--pass) 14%, transparent);\\n border: 1px solid color-mix(in srgb, var(--pass) 60%, var(--line));\\n}\\n.banner-fail {\\n background: color-mix(in srgb, var(--fail) 14%, transparent);\\n border: 1px solid color-mix(in srgb, var(--fail) 60%, var(--line));\\n}\\n.tagline {\\n display: block;\\n margin-top: 6px;\\n font-weight: 700;\\n letter-spacing: 0.12em;\\n color: var(--pass);\\n text-transform: uppercase;\\n font-size: 11px;\\n}\\n\\n.meta {\\n display: grid;\\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\\n gap: 8px;\\n margin: 0 0 16px;\\n padding: 0;\\n}\\n.meta div {\\n background: var(--surface);\\n border: 1px solid var(--line);\\n border-radius: 6px;\\n padding: 8px 12px;\\n}\\n.meta dt {\\n color: var(--muted);\\n font-size: 10px;\\n text-transform: uppercase;\\n letter-spacing: 0.06em;\\n margin-bottom: 4px;\\n}\\n.meta dd {\\n margin: 0;\\n font-size: 12px;\\n}\\n.muted-line {\\n display: block;\\n color: var(--muted);\\n font-size: 11px;\\n margin-top: 2px;\\n}\\n\\n.summary {\\n list-style: none;\\n padding: 0;\\n margin: 0 0 24px;\\n display: flex;\\n gap: 6px;\\n flex-wrap: wrap;\\n}\\n.summary li {\\n padding: 6px 12px;\\n border: 1px solid var(--line);\\n border-radius: 999px;\\n font-size: 12px;\\n color: var(--muted);\\n cursor: pointer;\\n user-select: none;\\n transition:\\n border-color 0.12s,\\n background 0.12s;\\n}\\n.summary li:hover {\\n border-color: var(--muted);\\n}\\n.summary li:focus {\\n outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent);\\n outline-offset: 2px;\\n}\\n.summary li.is-active {\\n background: color-mix(in srgb, var(--accent) 14%, transparent);\\n border-color: var(--accent);\\n color: var(--fg);\\n}\\n.summary li span {\\n color: var(--fg);\\n font-weight: 600;\\n margin-right: 6px;\\n}\\n.summary .sev-error span {\\n color: var(--fail);\\n}\\n.summary .sev-warn span {\\n color: var(--warn);\\n}\\n.summary .sev-info span {\\n color: var(--info);\\n}\\n\\nsection {\\n margin-top: 24px;\\n}\\nh2 {\\n font-size: 11px;\\n text-transform: uppercase;\\n letter-spacing: 0.06em;\\n color: var(--muted);\\n margin: 0 0 12px;\\n font-weight: 600;\\n}\\n\\n.findings-head {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n gap: 12px;\\n margin-bottom: 8px;\\n}\\n.findings-head h2 {\\n margin: 0;\\n}\\n\\n[data-search] {\\n font-family: var(--mono);\\n font-size: 12px;\\n padding: 6px 10px;\\n min-width: 240px;\\n background: var(--surface);\\n color: var(--fg);\\n border: 1px solid var(--line);\\n border-radius: 6px;\\n}\\n[data-search]:focus {\\n outline: none;\\n border-color: var(--accent);\\n}\\n\\n.artifacts {\\n list-style: none;\\n padding: 0;\\n margin: 0;\\n}\\n.artifacts li {\\n display: flex;\\n align-items: center;\\n gap: 12px;\\n padding: 6px 0;\\n border-bottom: 1px solid var(--line);\\n font-size: 12px;\\n}\\n.artifacts code {\\n color: var(--muted);\\n min-width: 160px;\\n}\\n.artifacts .path {\\n flex: 1;\\n word-break: break-all;\\n}\\n\\n.copy {\\n font-family: var(--mono);\\n font-size: 11px;\\n padding: 2px 8px;\\n border: 1px solid var(--line);\\n background: var(--surface);\\n color: var(--muted);\\n border-radius: 4px;\\n cursor: pointer;\\n}\\n.copy:hover {\\n color: var(--fg);\\n border-color: var(--muted);\\n}\\n.copy.copied {\\n color: var(--pass);\\n border-color: var(--pass);\\n}\\n\\ntable {\\n width: 100%;\\n border-collapse: collapse;\\n font-size: 12px;\\n}\\nth,\\ntd {\\n text-align: left;\\n padding: 8px 12px;\\n border-bottom: 1px solid var(--line);\\n vertical-align: top;\\n}\\nth {\\n color: var(--muted);\\n font-weight: 500;\\n font-size: 10px;\\n text-transform: uppercase;\\n letter-spacing: 0.06em;\\n}\\ntbody tr:hover {\\n background: color-mix(in srgb, var(--fg) 4%, transparent);\\n}\\ntr.is-hidden {\\n display: none;\\n}\\n\\n.badge {\\n display: inline-block;\\n padding: 2px 8px;\\n border-radius: 4px;\\n font-size: 10px;\\n text-transform: uppercase;\\n letter-spacing: 0.06em;\\n font-family: var(--mono);\\n}\\n.sev-error .badge {\\n background: color-mix(in srgb, var(--fail) 22%, transparent);\\n color: var(--fail);\\n}\\n.sev-warn .badge {\\n background: color-mix(in srgb, var(--warn) 22%, transparent);\\n color: var(--warn);\\n}\\n.sev-info .badge {\\n background: color-mix(in srgb, var(--info) 22%, transparent);\\n color: var(--info);\\n}\\n.sev-hint .badge {\\n background: color-mix(in srgb, var(--hint) 22%, transparent);\\n color: var(--hint);\\n}\\n\\n.recommendation {\\n margin: 4px 0 0;\\n color: var(--muted);\\n font-size: 11px;\\n}\\n.pointer {\\n display: block;\\n margin-top: 4px;\\n color: var(--muted);\\n font-size: 11px;\\n}\\n.empty {\\n color: var(--muted);\\n font-size: 12px;\\n}\\n.hidden {\\n display: none;\\n}\\n\";";
6
+ //#region \0inline-text:2.mjs
7
+ var _inline_text_2_default = ":root {\n color-scheme: dark;\n --bg: #0e1018;\n --fg: #eef0f9;\n --muted: #8a93a0;\n --line: #262a33;\n --surface: #161922;\n --surface-2: #1d2027;\n --accent: #ad5dca;\n --accent-soft: #2b7eca;\n --accent-2: #2dd4bf;\n --pass: #23d18b;\n --fail: #dc3657;\n --warn: #ffc368;\n --info: #54b9ff;\n --hint: #545864;\n --grid-line: rgba(255, 255, 255, 0.04);\n --grid-size: 2rem;\n --mono:\n \"IBM Plex Mono\", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,\n \"Liberation Mono\", \"Courier New\", monospace;\n --display: \"Bangers\", var(--mono);\n}\n\n[data-theme=\"tron\"] {\n --bg: #06080d;\n --fg: #e6f7ff;\n --muted: #5d8aa3;\n --line: #0e2c3d;\n --surface: #0a1320;\n --surface-2: #0d1a2b;\n --accent: #38f3ff;\n --accent-soft: #1ea7ff;\n --accent-2: #38f3ff;\n --pass: #7de8ff;\n --fail: #ff3c5f;\n --warn: #a8f5ff;\n --info: #73d8ff;\n --hint: #273746;\n --grid-line: rgba(56, 243, 255, 0.06);\n}\n\n[data-theme=\"808\"] {\n --bg: #1a1a1a;\n --fg: #ffffff;\n --muted: #9a958a;\n --line: #2a2a2a;\n --surface: #222222;\n --surface-2: #2a2a2a;\n --accent: #f8a125;\n --accent-soft: #e72e2e;\n --accent-2: #f1f827;\n --pass: #f1f827;\n --fail: #e72e2e;\n --warn: #f8a125;\n --info: #f1f827;\n --hint: #404040;\n --grid-line: rgba(248, 161, 37, 0.06);\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml {\n background:\n radial-gradient(\n circle at top left,\n color-mix(in srgb, var(--accent) 14%, transparent),\n transparent 32%\n ),\n radial-gradient(\n circle at top right,\n color-mix(in srgb, var(--accent-2) 10%, transparent),\n transparent 30%\n ),\n var(--bg);\n min-height: 100%;\n}\n\nbody {\n margin: 0;\n font-family: var(--mono);\n background:\n linear-gradient(\n 180deg,\n color-mix(in srgb, var(--bg) 92%, transparent),\n transparent 16rem\n ),\n linear-gradient(90deg, var(--grid-line) 1px, transparent 1px),\n linear-gradient(var(--grid-line) 1px, transparent 1px), transparent;\n background-size:\n auto,\n var(--grid-size) var(--grid-size),\n var(--grid-size) var(--grid-size),\n auto;\n background-position:\n 0 0,\n center top,\n center top,\n 0 0;\n color: var(--fg);\n font-size: 13px;\n line-height: 1.55;\n}\n\nheader {\n position: sticky;\n top: 0;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n background: color-mix(in srgb, var(--bg) 82%, transparent);\n backdrop-filter: blur(16px);\n box-shadow: 0 1px 0 color-mix(in srgb, var(--fg) 8%, transparent);\n overflow: hidden;\n}\n\nheader::before {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 0;\n background:\n radial-gradient(900px 220px at 12% -30%, var(--accent), transparent 60%),\n radial-gradient(\n 700px 200px at 88% -20%,\n var(--accent-soft),\n transparent 65%\n );\n opacity: 0.35;\n pointer-events: none;\n}\n\nheader::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n height: 1px;\n background: linear-gradient(\n 90deg,\n transparent,\n color-mix(in srgb, var(--accent) 55%, transparent),\n transparent\n );\n}\n\nheader > * {\n position: relative;\n z-index: 1;\n}\n\n.brand {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.dot {\n width: 10px;\n height: 10px;\n background: linear-gradient(135deg, var(--accent), var(--accent-2));\n box-shadow:\n 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent),\n 0 0 18px color-mix(in srgb, var(--accent) 60%, transparent);\n}\n\nh1 {\n margin: 0;\n font-family: var(--display);\n font-size: 22px;\n font-weight: 400;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n text-shadow: 0.04em 0.04em 0\n color-mix(in srgb, var(--accent) 22%, transparent);\n}\n\n.actions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n}\n\n.theme-switch {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 4px 10px;\n border: 1px solid var(--line);\n background: color-mix(in srgb, var(--surface) 85%, transparent);\n}\n\n.theme-label {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.12em;\n color: var(--muted);\n font-weight: 600;\n}\n\n.theme-switch select {\n font-family: var(--mono);\n font-size: 11px;\n padding: 4px 22px 4px 8px;\n border: 1px solid var(--line);\n border-radius: 0;\n background: var(--surface-2)\n url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='none' stroke='%238a93a0' stroke-width='1.4' d='M1 1l4 4 4-4'/></svg>\")\n no-repeat right 8px center;\n color: var(--fg);\n -webkit-appearance: none;\n appearance: none;\n cursor: pointer;\n}\n.theme-switch select:hover {\n border-color: var(--accent);\n}\n.theme-switch select:focus {\n outline: none;\n border-color: var(--accent);\n}\n\nkbd {\n font-family: var(--mono);\n font-size: 11px;\n padding: 3px 7px;\n border: 1px solid var(--line);\n border-bottom-width: 2px;\n border-radius: 0;\n color: var(--muted);\n background: var(--surface);\n}\n\n.refresh {\n color: var(--fg);\n text-decoration: none;\n padding: 7px 14px;\n border: 1px solid var(--line);\n border-radius: 0;\n font-size: 12px;\n font-family: var(--mono);\n font-weight: 600;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n background: var(--surface);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);\n transition:\n border-color 0.15s,\n transform 0.05s,\n color 0.15s;\n}\n.refresh:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.refresh:active {\n transform: translateY(1px);\n}\n\nmain {\n padding: 28px 24px 64px;\n max-width: 1100px;\n margin: 0 auto;\n}\n\n.banner {\n position: relative;\n padding: 16px 18px;\n border-radius: 0;\n margin-bottom: 20px;\n font-size: 13px;\n border: 1px solid var(--line);\n background: linear-gradient(\n 180deg,\n color-mix(in srgb, var(--surface) 80%, transparent),\n color-mix(in srgb, var(--bg) 92%, transparent)\n );\n box-shadow: 0 18px 44px rgba(0, 0, 0, 0.28);\n}\n.banner::before {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 3px;\n background: var(--accent);\n}\n.banner-pass::before {\n background: var(--pass);\n}\n.banner-fail::before {\n background: var(--fail);\n}\n.banner-pass {\n border-color: color-mix(in srgb, var(--pass) 45%, var(--line));\n}\n.banner-fail {\n border-color: color-mix(in srgb, var(--fail) 45%, var(--line));\n}\n.banner strong {\n font-family: var(--display);\n font-weight: 400;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n font-size: 16px;\n margin-right: 6px;\n}\n.tagline {\n display: block;\n margin-top: 8px;\n font-weight: 700;\n letter-spacing: 0.16em;\n color: var(--pass);\n text-transform: uppercase;\n font-size: 11px;\n}\n\n.meta {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 10px;\n margin: 0 0 20px;\n padding: 0;\n}\n.meta div {\n background: linear-gradient(\n 180deg,\n color-mix(in srgb, var(--surface) 90%, var(--accent) 10%),\n color-mix(in srgb, var(--bg) 96%, transparent)\n );\n border: 1px solid var(--line);\n border-radius: 0;\n padding: 10px 14px;\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.22);\n}\n.meta dt {\n color: var(--muted);\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n margin-bottom: 4px;\n font-weight: 600;\n}\n.meta dd {\n margin: 0;\n font-size: 12px;\n}\n.muted-line {\n display: block;\n color: var(--muted);\n font-size: 11px;\n margin-top: 2px;\n}\n\n.summary {\n list-style: none;\n padding: 0;\n margin: 0 0 28px;\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n.summary li {\n padding: 7px 14px;\n border: 1px solid var(--line);\n border-radius: 0;\n font-size: 12px;\n color: var(--muted);\n cursor: pointer;\n user-select: none;\n background: color-mix(in srgb, var(--surface) 80%, transparent);\n transition:\n border-color 0.12s,\n background 0.12s,\n color 0.12s;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n font-weight: 600;\n}\n.summary li:hover {\n border-color: var(--muted);\n color: var(--fg);\n}\n.summary li:focus {\n outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent);\n outline-offset: 2px;\n}\n.summary li.is-active {\n background: color-mix(in srgb, var(--accent) 16%, transparent);\n border-color: var(--accent);\n color: var(--fg);\n}\n.summary li span {\n color: var(--fg);\n font-weight: 700;\n margin-right: 8px;\n font-family: var(--display);\n letter-spacing: 0.05em;\n}\n.summary .sev-error span {\n color: var(--fail);\n}\n.summary .sev-warn span {\n color: var(--warn);\n}\n.summary .sev-info span {\n color: var(--info);\n}\n\nsection {\n margin-top: 28px;\n}\nh2 {\n font-family: var(--display);\n font-size: 14px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--fg);\n margin: 0 0 14px;\n font-weight: 400;\n}\n\n.findings-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.findings-head h2 {\n margin: 0;\n}\n\n[data-search] {\n font-family: var(--mono);\n font-size: 12px;\n padding: 8px 12px;\n min-width: 260px;\n background: var(--surface);\n color: var(--fg);\n border: 1px solid var(--line);\n border-radius: 0;\n}\n[data-search]:focus {\n outline: none;\n border-color: var(--accent);\n}\n\n.artifacts {\n list-style: none;\n padding: 0;\n margin: 0;\n border: 1px solid var(--line);\n background: color-mix(in srgb, var(--surface) 70%, transparent);\n}\n.artifacts li {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--line);\n font-size: 12px;\n}\n.artifacts li:last-child {\n border-bottom: 0;\n}\n.artifacts code {\n color: var(--muted);\n min-width: 160px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n font-weight: 600;\n font-size: 11px;\n}\n.artifacts .path {\n flex: 1;\n word-break: break-all;\n}\n\n.copy {\n font-family: var(--mono);\n font-size: 10px;\n padding: 4px 10px;\n border: 1px solid var(--line);\n border-radius: 0;\n background: var(--surface);\n color: var(--muted);\n cursor: pointer;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n font-weight: 600;\n}\n.copy:hover {\n color: var(--fg);\n border-color: var(--accent);\n}\n.copy.copied {\n color: var(--pass);\n border-color: var(--pass);\n}\n\ntable {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n border: 1px solid var(--line);\n background: color-mix(in srgb, var(--surface) 70%, transparent);\n}\nth,\ntd {\n text-align: left;\n padding: 10px 14px;\n border-bottom: 1px solid var(--line);\n vertical-align: top;\n}\ntr:last-child td {\n border-bottom: 0;\n}\nth {\n color: var(--muted);\n font-weight: 600;\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n background: color-mix(in srgb, var(--surface-2) 70%, transparent);\n}\ntbody tr:hover {\n background: color-mix(in srgb, var(--accent) 5%, transparent);\n}\ntr.is-hidden {\n display: none;\n}\n\n.badge {\n display: inline-block;\n padding: 3px 9px;\n border-radius: 0;\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.1em;\n font-family: var(--mono);\n font-weight: 700;\n border: 1px solid transparent;\n}\n.sev-error .badge {\n background: color-mix(in srgb, var(--fail) 18%, transparent);\n color: var(--fail);\n border-color: color-mix(in srgb, var(--fail) 35%, transparent);\n}\n.sev-warn .badge {\n background: color-mix(in srgb, var(--warn) 18%, transparent);\n color: var(--warn);\n border-color: color-mix(in srgb, var(--warn) 35%, transparent);\n}\n.sev-info .badge {\n background: color-mix(in srgb, var(--info) 18%, transparent);\n color: var(--info);\n border-color: color-mix(in srgb, var(--info) 35%, transparent);\n}\n.sev-hint .badge {\n background: color-mix(in srgb, var(--hint) 22%, transparent);\n color: var(--hint);\n border-color: color-mix(in srgb, var(--hint) 35%, transparent);\n}\n\n.recommendation {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 11px;\n}\n.pointer {\n display: block;\n margin-top: 6px;\n color: var(--muted);\n font-size: 11px;\n}\n.empty {\n color: var(--muted);\n font-size: 12px;\n}\n.hidden {\n display: none;\n}\n\n:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n}\n";
8
8
  //#endregion
9
9
  //#region src/output/dashboard.ts
10
10
  const renderDashboard = (input) => {
@@ -16,7 +16,10 @@ const renderDashboard = (input) => {
16
16
  <meta charset="utf-8" />
17
17
  <meta name="viewport" content="width=device-width,initial-scale=1" />
18
18
  <title>Elysia Spectral Lint Dashboard</title>
19
- <style>${dashboard_css_default}</style>
19
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
20
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
21
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bangers&family=IBM+Plex+Mono:wght@400;500;600;700&display=swap" />
22
+ <style>${_inline_text_2_default}</style>
20
23
  </head>
21
24
  <body>
22
25
  <header>
@@ -38,7 +41,7 @@ const renderDashboard = (input) => {
38
41
  </div>
39
42
  </header>
40
43
  <main>${body}</main>
41
- <script>${dashboard_client_js_default}<\/script>
44
+ <script>${_inline_text_3_default}<\/script>
42
45
  </body>
43
46
  </html>`;
44
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsydyn/elysia-spectral",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Thin Elysia plugin that lints generated OpenAPI documents with Spectral.",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "publishConfig": {