@lockhawk/core 0.2.3 → 0.2.4

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/dist/index.d.ts CHANGED
@@ -547,10 +547,12 @@ declare function toSarifString(result: ScanResult): string;
547
547
  * Produce a self-contained HTML report for a scan result.
548
548
  *
549
549
  * The page is fully server-rendered: no client-side JavaScript, no embedded
550
- * data blob, no external resources. Every dynamic value is HTML-escaped, the
551
- * layout flows normally within a width-constrained container, and long advisory
552
- * text lives in a collapsed, contained block — so the report cannot overflow the
553
- * viewport or render attacker-controlled advisory text as live markup.
550
+ * data blob, no external resources (fonts included). Every dynamic value is
551
+ * HTML-escaped, the layout flows within a width-constrained container, and long
552
+ * advisory text lives in a collapsed, contained block — so the report cannot
553
+ * overflow the viewport or render attacker-controlled advisory text as live
554
+ * markup. Copy affordances use CSS `user-select: all` (one click selects a whole
555
+ * command/version) rather than a clipboard script, preserving the no-JS model.
554
556
  */
555
557
  declare function toHtml(result: ScanResult): string;
556
558
 
package/dist/index.js CHANGED
@@ -2147,12 +2147,12 @@ function toHtml(result) {
2147
2147
  return renderHtml(result);
2148
2148
  }
2149
2149
  var SEVERITY_COLORS = {
2150
- critical: "#b4232c",
2151
- high: "#d9822b",
2152
- medium: "#c9a227",
2153
- low: "#3a7bd5",
2154
- unknown: "#7a7a7a",
2155
- none: "#3a9e54"
2150
+ critical: "#ff5d67",
2151
+ high: "#ff9f43",
2152
+ medium: "#ffd23f",
2153
+ low: "#5b9dff",
2154
+ unknown: "#8b95a5",
2155
+ none: "#34d399"
2156
2156
  };
2157
2157
  function esc(text) {
2158
2158
  return text.replace(
@@ -2168,41 +2168,83 @@ function httpHref(url) {
2168
2168
  return void 0;
2169
2169
  }
2170
2170
  }
2171
+ var MARK = `<svg class="mark" viewBox="0 0 48 48" fill="none" aria-hidden="true">
2172
+ <defs>
2173
+ <linearGradient id="lh-g" x1="6" y1="4" x2="40" y2="44" gradientUnits="userSpaceOnUse">
2174
+ <stop stop-color="#ffc266"/><stop offset="1" stop-color="#ff5d67"/>
2175
+ </linearGradient>
2176
+ </defs>
2177
+ <path d="M24 4 40 11v13c0 9-7 15.2-16 20-9-4.8-16-11-16-20V11Z" stroke="url(#lh-g)" stroke-width="2.2" stroke-linejoin="round"/>
2178
+ <path d="M14 18.5 24 15l10 3.5" stroke="url(#lh-g)" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
2179
+ <circle cx="24" cy="23" r="3.2" fill="url(#lh-g)"/>
2180
+ <path d="M24 26.2V32" stroke="url(#lh-g)" stroke-width="2.2" stroke-linecap="round"/>
2181
+ </svg>`;
2182
+ var CHEVRON = `<svg class="chev" width="11" height="11" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
2183
+ function renderFix(rec) {
2184
+ const match = rec.match(/`([^`]+)`/);
2185
+ const command = match?.[1];
2186
+ const pending = /^No fixed version/i.test(rec);
2187
+ const prose = command ? rec.replace(/\s*\(e\.g\.[^)]*\)/, "") : rec;
2188
+ const bar = command ? `<div class="cmdbar"><code class="cmd" title="Click to select, then \u2318/Ctrl+C to copy">${esc(
2189
+ command
2190
+ )}</code><span class="hint">click to select</span></div>` : "";
2191
+ return `<div class="fix${pending ? " fix-pending" : ""}">
2192
+ <div class="fix-label">${CHEVRON.replace("chev", "spark")}${pending ? "No fix available yet" : "Recommended fix"}</div>
2193
+ <p class="fix-text">${esc(prose)}</p>${bar}
2194
+ </div>`;
2195
+ }
2171
2196
  function renderHtml(result) {
2172
2197
  const { summary } = result;
2173
2198
  const cards = ["critical", "high", "medium", "low", "unknown"].map(
2174
- (sev) => `
2175
- <div class="card" style="border-top:4px solid ${SEVERITY_COLORS[sev]}">
2199
+ (sev, i) => `
2200
+ <div class="card${summary[sev] === 0 ? " zero" : ""}" style="--c:${SEVERITY_COLORS[sev]};--i:${i}">
2176
2201
  <div class="count">${summary[sev]}</div><div class="label">${sev}</div>
2177
2202
  </div>`
2178
2203
  ).join("");
2179
- const rows = result.findings.map((f) => {
2204
+ const rows = result.findings.map((f, i) => {
2180
2205
  const path = f.dependencyPaths[0]?.join(" \u203A ") ?? f.packageName;
2181
- const refs = f.references.slice(0, 3).map((u) => httpHref(u)).filter((u) => u !== void 0).map((u) => `<a href="${esc(u)}" target="_blank" rel="noopener">link</a>`).join(" ");
2182
- const rec = f.recommendation ? `<p class="rec"><strong>Recommendation:</strong> ${esc(f.recommendation)}</p>` : "";
2206
+ const refs = f.references.slice(0, 3).map((u) => httpHref(u)).filter((u) => u !== void 0).map((u) => `<a class="ref" href="${esc(u)}" target="_blank" rel="noopener">link</a>`).join("");
2207
+ const fixed = f.fixedVersions[0] ? `<span class="fixchip" title="Click to select, then \u2318/Ctrl+C to copy">${esc(
2208
+ f.fixedVersions[0]
2209
+ )}</span>` : '<span class="nofix">\u2014</span>';
2183
2210
  return `
2184
- <tr>
2185
- <td><span class="pill" style="background:${SEVERITY_COLORS[f.severity.level]}">${esc(
2211
+ <tr class="main" style="--i:${i}">
2212
+ <td><span class="pill" style="--c:${SEVERITY_COLORS[f.severity.level]}">${esc(
2186
2213
  f.severity.level
2187
2214
  )}${f.severity.score ? ` ${f.severity.score}` : ""}</span></td>
2188
- <td><code>${esc(f.packageName)}@${esc(f.version)}</code><div class="scope">${esc(f.scope)}${f.direct ? " \xB7 direct" : ""}</div></td>
2189
- <td><a href="https://osv.dev/${encodeURIComponent(f.id)}" target="_blank" rel="noopener">${esc(
2215
+ <td><span class="pkg">${esc(f.packageName)}@${esc(f.version)}</span><div class="scope">${esc(
2216
+ f.scope
2217
+ )}${f.direct ? " \xB7 direct" : ""}</div></td>
2218
+ <td><a class="advid" href="https://osv.dev/${encodeURIComponent(
2190
2219
  f.id
2191
- )}</a></td>
2192
- <td>${esc(f.summary)}<div class="path">${esc(path)}</div></td>
2193
- <td>${f.fixedVersions[0] ? esc(f.fixedVersions[0]) : "\u2014"}</td>
2220
+ )}" target="_blank" rel="noopener">${esc(f.id)}</a></td>
2221
+ <td><div class="summary">${esc(f.summary)}</div><div class="path">${esc(path)}</div></td>
2222
+ <td>${fixed}</td>
2194
2223
  <td class="refs">${refs}</td>
2195
2224
  </tr>
2196
2225
  <tr class="detail-row">
2197
2226
  <td colspan="6">
2227
+ ${f.recommendation ? renderFix(f.recommendation) : ""}
2198
2228
  <details>
2199
- <summary>Advisory details</summary>
2200
- ${rec}<pre class="advisory">${esc(f.details ?? f.summary)}</pre>
2229
+ <summary>${CHEVRON}<span>Advisory details</span></summary>
2230
+ <div class="detail-body">
2231
+ <div class="advisory-wrap">
2232
+ <div class="advisory-head"><span class="dots"><i></i><i></i><i></i></span><span class="advisory-title">advisory \xB7 ${esc(
2233
+ f.id
2234
+ )}</span></div>
2235
+ <pre class="advisory">${esc(f.details ?? f.summary)}</pre>
2236
+ </div>
2237
+ </div>
2201
2238
  </details>
2202
2239
  </td>
2203
2240
  </tr>`;
2204
2241
  }).join("");
2205
- const dbWarnings = result.database.warnings.map((w) => `<div class="warn">\u26A0 ${esc(w)}</div>`).join("");
2242
+ const dbWarnings = result.database.warnings.map((w) => `<div class="warn"><span class="warn-i">\u26A0</span>${esc(w)}</div>`).join("");
2243
+ const resultsMeta = result.findings.length === 0 ? "" : `<div class="results-meta">
2244
+ <span><b>${summary.total}</b> findings</span><span class="sep">\xB7</span>
2245
+ <span><b>${summary.vulnerablePackages}</b> packages</span><span class="sep">\xB7</span>
2246
+ <span><b>${summary.fixable}</b> fixable</span>
2247
+ </div>`;
2206
2248
  return `<!doctype html>
2207
2249
  <html lang="en">
2208
2250
  <head>
@@ -2210,51 +2252,158 @@ function renderHtml(result) {
2210
2252
  <meta name="viewport" content="width=device-width, initial-scale=1">
2211
2253
  <title>lockhawk report \u2014 ${esc(result.target.root.name)}</title>
2212
2254
  <style>
2213
- :root { color-scheme: light dark; }
2255
+ :root {
2256
+ --bg:#070a12; --panel:#11151e; --panel-2:#141926; --elev:#171d2b;
2257
+ --border:#222a3a; --border-soft:#1b2230;
2258
+ --text:#f1f4fa; --muted:#aeb8cc; --faint:#808b9f;
2259
+ --accent:#ffc266; --fix:#48e0a6;
2260
+ --glass: rgba(255,255,255,.055); --glass-2: rgba(255,255,255,.09);
2261
+ --glass-line: rgba(255,255,255,.14); --glass-hi: inset 0 1px 0 rgba(255,255,255,.10);
2262
+ --glass-dark: rgba(8,11,18,.42);
2263
+ --blur: blur(18px) saturate(160%);
2264
+ --mono: ui-monospace,'SF Mono','JetBrains Mono','Cascadia Code','Fira Code',Menlo,Consolas,monospace;
2265
+ --sans: -apple-system,BlinkMacSystemFont,'SF Pro Text','Segoe UI',system-ui,Roboto,sans-serif;
2266
+ color-scheme: dark;
2267
+ }
2214
2268
  * { box-sizing: border-box; }
2215
2269
  html, body { max-width: 100%; overflow-x: hidden; }
2216
- body { font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; background: #f6f7f9; color: #1c1e21; }
2217
- header { background: #11161d; color: #fff; padding: 24px 32px; }
2218
- header h1 { margin: 0 0 4px; font-size: 20px; }
2219
- header .meta { color: #9aa4b2; font-size: 13px; overflow-wrap: anywhere; }
2220
- main { max-width: 1100px; margin: 0 auto; padding: 24px clamp(16px, 4vw, 32px) 40px; }
2221
- .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 12px; margin-bottom: 8px; }
2222
- .card { background: #fff; border-radius: 8px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
2223
- .card .count { font-size: 28px; font-weight: 700; }
2224
- .card .label { text-transform: capitalize; color: #6b7280; font-size: 13px; }
2225
- .warn { background: #fff7e6; border: 1px solid #ffd591; color: #874d00; padding: 8px 12px; border-radius: 6px; margin: 12px 0; font-size: 13px; overflow-wrap: anywhere; }
2226
- .table-wrap { margin-top: 16px; overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
2227
- table { width: 100%; border-collapse: collapse; background: #fff; }
2228
- th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #eef0f3; vertical-align: top; font-size: 13px; overflow-wrap: anywhere; word-break: break-word; }
2229
- th { background: #f0f2f5; font-size: 12px; text-transform: uppercase; letter-spacing: .03em; color: #6b7280; white-space: nowrap; }
2230
- .pill { color: #fff; padding: 2px 8px; border-radius: 999px; font-size: 12px; text-transform: capitalize; white-space: nowrap; }
2231
- .scope, .path { color: #6b7280; font-size: 12px; }
2232
- .path { margin-top: 4px; }
2233
- .refs { white-space: nowrap; }
2234
- code { background: #eef0f3; padding: 1px 5px; border-radius: 4px; max-width: 100%; }
2235
- .detail-row td { border-bottom: 2px solid #eef0f3; padding-top: 0; }
2236
- details { font-size: 13px; }
2237
- summary { cursor: pointer; color: #3a7bd5; font-size: 12px; padding: 4px 0; width: max-content; }
2238
- .rec { margin: 8px 0; font-size: 13px; }
2239
- pre.advisory { white-space: pre-wrap; word-break: break-word; overflow-x: auto; max-width: 100%; background: #f6f7f9; border: 1px solid #eef0f3; border-radius: 6px; padding: 10px 12px; margin: 6px 0 4px; font-size: 12.5px; }
2240
- .empty { background: #fff; border-radius: 8px; padding: 40px; text-align: center; color: #3a9e54; font-size: 18px; box-shadow: 0 1px 3px rgba(0,0,0,.08); margin-top: 16px; }
2241
- footer { max-width: 1100px; margin: 0 auto; padding: 16px clamp(16px, 4vw, 32px) 40px; color: #9aa4b2; font-size: 12px; }
2270
+ body {
2271
+ margin: 0; min-height: 100vh; font: 15px/1.6 var(--sans); color: var(--text);
2272
+ background:
2273
+ radial-gradient(820px 620px at 8% -8%, rgba(91,157,255,.22), transparent 58%),
2274
+ radial-gradient(760px 600px at 94% -4%, rgba(255,93,103,.16), transparent 56%),
2275
+ radial-gradient(900px 760px at 80% 108%, rgba(72,224,166,.16), transparent 58%),
2276
+ radial-gradient(720px 640px at 16% 98%, rgba(255,179,71,.13), transparent 58%),
2277
+ linear-gradient(180deg, #0a1020, #070a12 60%);
2278
+ background-attachment: fixed;
2279
+ -webkit-font-smoothing: antialiased;
2280
+ }
2281
+
2282
+ /* ---- Header / wordmark ------------------------------------------------ */
2283
+ header { position: relative; overflow: hidden; padding: 30px clamp(18px,5vw,40px) 28px; background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.03)); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border-bottom: 1px solid var(--glass-line); box-shadow: var(--glass-hi); }
2284
+ header::before { content:''; position:absolute; inset:0; pointer-events:none;
2285
+ background: radial-gradient(620px 220px at 10% 0%, rgba(255,194,102,.12), transparent 70%),
2286
+ radial-gradient(520px 240px at 58% -25%, rgba(255,93,103,.10), transparent 70%); }
2287
+ header::after { content:''; position:absolute; inset:0; opacity:.35; pointer-events:none;
2288
+ background-image: linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px), linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);
2289
+ background-size: 34px 34px; -webkit-mask: linear-gradient(180deg,#000,transparent); mask: linear-gradient(180deg,#000,transparent); }
2290
+ header > * { position: relative; }
2291
+ .brand { display:flex; align-items:center; gap:14px; }
2292
+ .mark { width:38px; height:38px; flex:none; filter: drop-shadow(0 3px 12px rgba(255,93,103,.3)); }
2293
+ .wordmark { font-family: var(--mono); font-size: clamp(22px,4vw,27px); letter-spacing:-.02em; display:flex; align-items:center; }
2294
+ .wordmark .lock { color: var(--muted); font-weight:500; }
2295
+ .wordmark .hawk { color: var(--text); font-weight:700; }
2296
+ .caret { display:inline-block; width:9px; height:1em; margin-left:5px; background: var(--accent); border-radius:1px; box-shadow:0 0 12px var(--accent); animation: blink 1.2s steps(1) infinite; }
2297
+ .meta { margin-top:12px; color: var(--muted); font:12.5px/1.7 var(--mono); letter-spacing:.01em; overflow-wrap:anywhere; }
2298
+ .meta b { color: var(--text); font-weight:600; }
2299
+
2300
+ /* ---- Layout ----------------------------------------------------------- */
2301
+ main { max-width: 1120px; margin: 0 auto; padding: 26px clamp(16px,4vw,32px) 48px; }
2302
+ .cards { display:grid; grid-template-columns: repeat(auto-fit, minmax(116px,1fr)); gap:14px; }
2303
+ .card { position:relative; overflow:hidden; background: linear-gradient(180deg, var(--glass-2), var(--glass)); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border:1px solid var(--glass-line); border-radius:16px; padding:16px 18px; box-shadow: 0 12px 36px -18px rgba(0,0,0,.75), var(--glass-hi); animation: rise .55s both cubic-bezier(.2,.7,.2,1); animation-delay: calc(var(--i)*55ms); }
2304
+ .card::before { content:''; position:absolute; top:0; left:0; right:0; height:3px; background:var(--c); box-shadow:0 0 16px var(--c); }
2305
+ .card .count { font:700 32px/1 var(--mono); font-variant-numeric: tabular-nums; letter-spacing:-.03em; color:var(--c); text-shadow: 0 0 24px color-mix(in srgb, var(--c) 45%, transparent); }
2306
+ .card .label { margin-top:7px; color:var(--muted); font-size:11.5px; text-transform:uppercase; letter-spacing:.09em; }
2307
+ .card.zero .count { color: var(--faint); text-shadow:none; }
2308
+ .card.zero::before { box-shadow:none; opacity:.45; }
2309
+
2310
+ .warn { display:flex; gap:9px; align-items:flex-start; background: rgba(255,210,63,.1); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); border:1px solid rgba(255,210,63,.32); color:#ffe6a6; padding:10px 14px; border-radius:12px; margin-top:14px; font-size:13px; overflow-wrap:anywhere; }
2311
+ .warn-i { color: var(--accent); }
2312
+
2313
+ .results-meta { display:flex; gap:14px; flex-wrap:wrap; align-items:center; margin:26px 2px 0; color:var(--muted); font:12px/1 var(--mono); letter-spacing:.04em; }
2314
+ .results-meta b { color:var(--text); font-weight:600; }
2315
+ .results-meta .sep { color:var(--faint); }
2316
+
2317
+ /* ---- Table ------------------------------------------------------------ */
2318
+ .table-wrap { margin-top:14px; border:1px solid var(--glass-line); border-radius:18px; overflow:hidden; background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.025)); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); box-shadow: 0 22px 60px -32px rgba(0,0,0,.85), var(--glass-hi); }
2319
+ .scroll { overflow-x:auto; -webkit-overflow-scrolling:touch; }
2320
+ table { width:100%; border-collapse:collapse; }
2321
+ thead th { text-align:left; background: rgba(255,255,255,.05); color:var(--muted); font:600 11px/1 var(--mono); text-transform:uppercase; letter-spacing:.08em; padding:14px 16px; border-bottom:1px solid var(--glass-line); white-space:nowrap; }
2322
+ tbody td { padding:15px 16px; border-bottom:1px solid rgba(255,255,255,.07); vertical-align:top; font-size:13.5px; overflow-wrap:anywhere; word-break:break-word; }
2323
+ tbody tr.main { animation: rise .5s both ease; animation-delay: calc(var(--i)*30ms + 120ms); }
2324
+ tbody tr.main:hover { background: rgba(255,255,255,.05); }
2325
+
2326
+ .pill { display:inline-flex; align-items:center; gap:7px; font:600 11.5px/1 var(--mono); padding:6px 10px; border-radius:8px; white-space:nowrap; text-transform:capitalize; color:var(--c); background: color-mix(in srgb, var(--c) 15%, transparent); border:1px solid color-mix(in srgb, var(--c) 34%, transparent); }
2327
+ .pill::before { content:''; width:7px; height:7px; border-radius:50%; background:var(--c); box-shadow:0 0 9px var(--c); flex:none; }
2328
+
2329
+ .pkg { display:inline-block; font-family:var(--mono); font-size:12.5px; color:var(--text); background: rgba(255,255,255,.07); border:1px solid var(--glass-line); padding:4px 9px; border-radius:8px; }
2330
+ .scope { margin-top:7px; color:var(--faint); font:11px/1 var(--mono); text-transform:uppercase; letter-spacing:.06em; }
2331
+ .advid { font-family:var(--mono); font-size:12.5px; color:var(--accent); text-decoration:none; border-bottom:1px solid transparent; }
2332
+ .advid:hover { border-bottom-color: color-mix(in srgb, var(--accent) 60%, transparent); }
2333
+ .summary { color:var(--text); }
2334
+ .path { margin-top:7px; color:var(--faint); font:11.5px/1.5 var(--mono); }
2335
+
2336
+ .fixchip { display:inline-block; font-family:var(--mono); font-size:12.5px; color:var(--fix); background: color-mix(in srgb, var(--fix) 13%, transparent); border:1px solid color-mix(in srgb, var(--fix) 30%, transparent); padding:4px 10px; border-radius:8px; cursor:copy; white-space:nowrap; user-select:all; -webkit-user-select:all; transition: background .15s ease; }
2337
+ .fixchip:hover { background: color-mix(in srgb, var(--fix) 22%, transparent); }
2338
+ .nofix { color:var(--faint); }
2339
+ .refs { white-space:nowrap; }
2340
+ .ref { display:inline-block; font:11.5px/1 var(--mono); color:var(--muted); text-decoration:none; padding:4px 8px; border:1px solid var(--glass-line); border-radius:7px; margin:0 4px 4px 0; background: rgba(255,255,255,.04); transition: color .15s, border-color .15s, background .15s; }
2341
+ .ref:hover { color:var(--text); border-color: rgba(255,255,255,.28); background: rgba(255,255,255,.08); }
2342
+
2343
+ /* ---- Disclosure / advisory ------------------------------------------- */
2344
+ .detail-row td { padding:0 16px 4px; background: rgba(0,0,0,.16); border-bottom:1px solid rgba(255,255,255,.07); }
2345
+ details { margin:0; }
2346
+ summary { list-style:none; cursor:pointer; display:inline-flex; align-items:center; gap:9px; color:var(--muted); font:600 11px/1 var(--mono); letter-spacing:.08em; text-transform:uppercase; padding:13px 0; user-select:none; transition: color .15s ease; }
2347
+ summary::-webkit-details-marker { display:none; }
2348
+ summary:hover { color:var(--text); }
2349
+ summary .chev { color:var(--accent); transition: transform .25s cubic-bezier(.2,.7,.2,1); flex:none; }
2350
+ details[open] summary .chev { transform: rotate(90deg); }
2351
+ .detail-body { padding-bottom:18px; animation: fade .3s both ease; }
2352
+
2353
+ .fix { border:1px solid color-mix(in srgb, var(--fix) 28%, transparent); background: linear-gradient(180deg, color-mix(in srgb, var(--fix) 12%, transparent), rgba(255,255,255,.02)); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); border-radius:13px; padding:13px 15px; margin:12px 0; box-shadow: var(--glass-hi); }
2354
+ .fix-label { display:flex; align-items:center; gap:7px; font:700 10px/1 var(--mono); letter-spacing:.12em; text-transform:uppercase; color:var(--fix); margin-bottom:9px; }
2355
+ .fix-label .spark { color:var(--fix); }
2356
+ .fix-pending { border-color: color-mix(in srgb, var(--accent) 32%, transparent); background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 11%, transparent), rgba(255,255,255,.02)); }
2357
+ .fix-pending .fix-label, .fix-pending .spark { color: var(--accent); }
2358
+ .fix-text { margin:0; color:var(--text); font-size:13px; }
2359
+ .fix-text + .cmdbar { margin-top:11px; }
2360
+ .cmdbar { display:flex; align-items:center; gap:14px; flex-wrap:wrap; background: rgba(4,7,12,.55); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); border:1px solid var(--glass-line); border-radius:11px; padding:11px 14px; transition: border-color .18s ease, background .18s ease; }
2361
+ .cmdbar:hover { border-color: color-mix(in srgb, var(--fix) 55%, transparent); background: rgba(4,7,12,.68); }
2362
+ .cmdbar .cmd { font-family:var(--mono); font-size:13px; color:#d7f7e8; cursor:copy; white-space:pre-wrap; word-break:break-all; user-select:all; -webkit-user-select:all; }
2363
+ .cmdbar .cmd::before { content:'$\\00a0\\00a0'; color:var(--fix); user-select:none; -webkit-user-select:none; }
2364
+ .cmdbar .hint { margin-left:auto; font:11px/1 var(--sans); color:var(--muted); white-space:nowrap; }
2365
+
2366
+ .advisory-wrap { border:1px solid var(--glass-line); border-radius:13px; overflow:hidden; background: var(--glass-dark); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); box-shadow: var(--glass-hi); }
2367
+ .advisory-head { display:flex; align-items:center; gap:11px; padding:10px 15px; background: rgba(255,255,255,.05); border-bottom:1px solid var(--glass-line); font:600 10.5px/1 var(--mono); letter-spacing:.07em; text-transform:uppercase; color:var(--muted); }
2368
+ .dots { display:flex; gap:6px; }
2369
+ .dots i { width:9px; height:9px; border-radius:50%; background: rgba(255,255,255,.18); }
2370
+ pre.advisory { margin:0; padding:15px 16px; white-space:pre-wrap; word-break:break-word; overflow-x:auto; max-width:100%; font-family:var(--mono); font-size:12.5px; line-height:1.65; color:#d2dae8; }
2371
+
2372
+ .empty { background: linear-gradient(180deg, var(--glass-2), var(--glass)); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border:1px solid var(--glass-line); border-radius:18px; padding:48px; text-align:center; color:var(--fix); font:600 19px/1.4 var(--sans); margin-top:16px; box-shadow: var(--glass-hi); }
2373
+ footer { max-width:1120px; margin:0 auto; padding:18px clamp(16px,4vw,32px) 44px; color:var(--faint); font:12px/1.6 var(--mono); letter-spacing:.02em; }
2374
+
2375
+ /* Entrance animates position only \u2014 content stays fully opaque/visible throughout. */
2376
+ @keyframes rise { from { transform: translateY(12px); } to { transform: none; } }
2377
+ @keyframes fade { from { opacity:0; } to { opacity:1; } }
2378
+ @keyframes blink { 50% { opacity:0; } }
2379
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation:none !important; transition:none !important; } }
2380
+
2381
+ @media print {
2382
+ body { background:#fff; color:#111; }
2383
+ header, .card, .table-wrap, .advisory-wrap, .cmdbar, .fix, thead th, .warn, .empty {
2384
+ background:#fff !important; -webkit-backdrop-filter:none !important; backdrop-filter:none !important;
2385
+ box-shadow:none !important; border-color:#ddd !important; color:#111;
2386
+ }
2387
+ .caret { display:none; }
2388
+ pre.advisory, .fix-text, .summary, .pkg { color:#111 !important; }
2389
+ }
2242
2390
  </style>
2243
2391
  </head>
2244
2392
  <body>
2245
2393
  <header>
2246
- <h1>lockhawk report</h1>
2247
- <div class="meta">${esc(result.target.root.name)}${result.target.root.version ? `@${esc(result.target.root.version)}` : ""} \xB7 ${esc(result.target.manager)} \xB7 ${result.stats.totalPackages} packages \xB7 scanned ${esc(
2394
+ <div class="brand">${MARK}<div class="wordmark"><span class="lock">lock</span><span class="hawk">hawk</span><span class="caret"></span></div></div>
2395
+ <div class="meta"><b>${esc(result.target.root.name)}${result.target.root.version ? `@${esc(result.target.root.version)}` : ""}</b> \xB7 ${esc(result.target.manager)} \xB7 ${result.stats.totalPackages} packages \xB7 scanned ${esc(
2248
2396
  result.scannedAt
2249
2397
  )}</div>
2250
2398
  </header>
2251
2399
  <main>
2252
2400
  <div class="cards">${cards}</div>
2253
2401
  ${dbWarnings}
2254
- ${result.findings.length === 0 ? '<div class="empty">\u2713 No known vulnerabilities found.</div>' : `<div class="table-wrap"><table>
2402
+ ${result.findings.length === 0 ? '<div class="empty">\u2713 No known vulnerabilities found.</div>' : `${resultsMeta}
2403
+ <div class="table-wrap"><div class="scroll"><table>
2255
2404
  <thead><tr><th>Severity</th><th>Package</th><th>Advisory</th><th>Summary &amp; path</th><th>Fixed in</th><th>Refs</th></tr></thead>
2256
2405
  <tbody>${rows}</tbody>
2257
- </table></div>`}
2406
+ </table></div></div>`}
2258
2407
  </main>
2259
2408
  <footer>Generated by lockhawk v${esc(result.tool.version)} \xB7 data from OSV.dev \xB7 database: ${esc(
2260
2409
  result.database.source