fix(frontend): hover is a colour change, never an underline

Claude-Session: https://claude.ai/code/session_01C7wFUhK99JTpbtP6i5yFK5
This commit is contained in:
Yogesh Ojha
2026-09-27 22:04:06 +05:30
parent 19298e3883
commit 72045c0a78
64 changed files with 160 additions and 137 deletions
@@ -111,7 +111,7 @@
{#snippet child(props)}
<a
{...props}
class="inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground hover:text-foreground hover:underline"
class="inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
href={url}
target="_blank"
rel="noopener noreferrer"
@@ -280,7 +280,7 @@
{#snippet child(props)}
<a
{...props}
class="w-fit text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
class="w-fit text-xs text-muted-foreground hover:text-foreground"
href={ROUTES.settings('api-keys')}
>
No API key
@@ -258,7 +258,7 @@
href={program.url ?? bountyVocabulary.url(program.platform)}
target="_blank"
rel="noreferrer noopener"
class="inline-flex items-center gap-1 font-mono text-xs hover:underline"
class="inline-flex items-center gap-1 font-mono text-xs hover:text-primary"
>
@{program.handle}
<ExternalLinkIcon class="size-3" />
@@ -267,7 +267,7 @@
{#if bountyVocabulary.platform(program.platform)?.tracks_reports}
<a
href={ROUTES.bountyReports(program.platform, program.handle)}
class="text-xs text-foreground underline-offset-4 hover:underline"
class="text-xs text-foreground hover:text-primary"
>
{program.reports_for_user}
{program.reports_for_user === 1 ? 'report' : 'reports'}{program.earnings_for_user
@@ -107,7 +107,7 @@
href={href(p)}
target={p.in_hub ? undefined : '_blank'}
rel={p.in_hub ? undefined : 'noopener noreferrer'}
class="block truncate text-sm font-medium hover:underline">{p.name}</a
class="block truncate text-sm font-medium hover:text-primary">{p.name}</a
>
<div class="flex items-center gap-1.5 text-2xs text-muted-foreground">
<span class="font-mono">@{p.handle}</span>
@@ -119,7 +119,7 @@
href={programHref}
target={report.program_in_hub ? undefined : '_blank'}
rel={report.program_in_hub ? undefined : 'noopener noreferrer'}
class="block truncate text-sm hover:underline"
class="block truncate text-sm hover:text-primary"
>
{programName}
</a>
@@ -266,7 +266,7 @@
href={programHref}
target={report.program_in_hub ? undefined : '_blank'}
rel={report.program_in_hub ? undefined : 'noopener noreferrer'}
class="truncate text-sm font-medium hover:underline">{programName}</a
class="truncate text-sm font-medium hover:text-primary">{programName}</a
>
{/if}
<span class="font-mono text-2xs text-muted-foreground">@{report.program_handle}</span>
@@ -309,7 +309,7 @@
<dd class="min-w-0">
<button
type="button"
class="truncate text-left hover:underline"
class="truncate text-left hover:text-primary"
onclick={() => onSearch(report.weakness!)}>{report.weakness}</button
>
</dd>
@@ -319,7 +319,7 @@
<dd class="flex min-w-0 items-center gap-1">
<button
type="button"
class="truncate text-left font-mono text-xs hover:underline"
class="truncate text-left font-mono text-xs hover:text-primary"
onclick={() => onSearch(report.asset_identifier!)}>{report.asset_identifier}</button
>
<CopyButton value={report.asset_identifier} class="shrink-0" />
@@ -100,7 +100,7 @@
<button
type="button"
onclick={() => onOpenProgram(event.handle, event.platform)}
class="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground hover:underline"
class="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
>
{event.program_name}
<ArrowUpRightIcon class="size-3" />
@@ -120,8 +120,9 @@
Added{result.attached ? ` · ${result.attached} attached` : ''}
</span>
{#if result.scan_id}
<a href={ROUTES.scan(result.scan_id)} class="text-primary text-xs hover:underline"
>Scan running</a
<a
href={ROUTES.scan(result.scan_id)}
class="text-primary text-xs hover:text-primary/80">Scan running</a
>
{/if}
</div>
@@ -47,12 +47,12 @@
</span>
<span class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-xs">
{#if step.software > 0}
<a href={softwareHref(step)} class="hover:underline">
<a href={softwareHref(step)} class="hover:text-primary">
{step.software.toLocaleString()} in {software.label}
</a>
{/if}
{#if step.findings > 0}
<a href={findingsHref(step)} class="hover:underline">
<a href={findingsHref(step)} class="hover:text-primary">
{step.findings.toLocaleString()} in {findings.label}
</a>
{/if}
@@ -91,7 +91,7 @@
<a
{...props}
href={link(postureQuery(key))}
class="block text-sm leading-5 hover:underline">{spec?.label ?? key}</a
class="block text-sm leading-5 hover:text-primary">{spec?.label ?? key}</a
>
{/snippet}
</Hint>
@@ -63,7 +63,7 @@
{#snippet footer()}
<span>
{total.toLocaleString()} runs in {days} days ·
<a href={ROUTES.scansWhere({ status: 'failed', range: window })} class="hover:underline">
<a href={ROUTES.scansWhere({ status: 'failed', range: window })} class="hover:text-primary">
{failed} failed
</a>
</span>
@@ -75,14 +75,14 @@
<div class={STAT}>
<a
href={actionableHref}
class="text-lg leading-tight font-semibold tracking-tight tabular-nums hover:underline {data.actionable
class="text-lg leading-tight font-semibold tracking-tight tabular-nums hover:text-primary {data.actionable
? 'text-[var(--sev-critical-ink)]'
: ''}"
>
{data.actionable.toLocaleString()}
</a>
<span class="truncate text-2xs text-muted-foreground">
actionable · <a href={actHref} class="hover:underline"
actionable · <a href={actHref} class="hover:text-primary"
>{data.act.toLocaleString()} act now</a
>
</span>
@@ -129,7 +129,7 @@
<div class="flex items-baseline justify-between gap-2">
<a
href={ROUTES.target(r.target_id)}
class="truncate text-sm font-semibold hover:underline">{r.target_value}</a
class="truncate text-sm font-semibold hover:text-primary">{r.target_value}</a
>
<span class="shrink-0 text-2xs text-muted-foreground">
{#if r.last_at}{relativeTime(r.last_at)}{/if}{#if !settled && r.scan_status}
@@ -146,13 +146,16 @@
</div>
{/each}
</div>
<a href={liveQuery(r)} class="flex justify-between gap-2 rounded-sm hover:underline">
<a href={liveQuery(r)} class="flex justify-between gap-2 rounded-sm hover:text-primary">
<span class="text-muted-foreground">Live web assets</span>
<span class="font-medium tabular-nums"
>{r.live.toLocaleString()} of {r.names.toLocaleString()}</span
>
</a>
<a href={findingsQuery(r)} class="flex justify-between gap-2 rounded-sm hover:underline">
<a
href={findingsQuery(r)}
class="flex justify-between gap-2 rounded-sm hover:text-primary"
>
<span class="text-muted-foreground">Open findings</span>
<span class="font-medium tabular-nums">{r.findings.toLocaleString()}</span>
</a>
@@ -131,14 +131,18 @@
<a
href="https://github.com/yogeshojha/rengine"
target="_blank"
class="text-sm text-primary hover:underline"
class="text-sm text-primary hover:text-primary/80"
>
github.com/yogeshojha/rengine
</a>
</div>
<div class="space-y-2">
<h4 class="font-medium">Wiki</h4>
<a href="https://rengine.wiki" target="_blank" class="text-sm text-primary hover:underline">
<a
href="https://rengine.wiki"
target="_blank"
class="text-sm text-primary hover:text-primary/80"
>
rengine.wiki
</a>
</div>
@@ -105,7 +105,7 @@
<span class="text-muted-foreground/40">·</span>
<button
type="button"
class="inline-flex items-center gap-0.5 text-2xs font-medium text-primary hover:underline"
class="inline-flex items-center gap-0.5 text-2xs font-medium text-primary hover:text-primary/80"
onclick={(e) => onAction(notification.id, e)}
>
{meta.action_label}
@@ -161,7 +161,7 @@
{/if}
{#if showAnchor}
<span class="text-muted-foreground/60">·</span>
<a href={ROUTES.target(note.target_id)} class="hover:text-foreground hover:underline">
<a href={ROUTES.target(note.target_id)} class="hover:text-foreground">
{note.target_value}
</a>
{/if}
@@ -65,7 +65,7 @@
href={meta.chatUrl(status.bot.username)}
target="_blank"
rel="noopener noreferrer"
class="font-mono text-xs text-primary hover:underline">@{status.bot.username}</a
class="font-mono text-xs text-primary hover:text-primary/80">@{status.bot.username}</a
>
{/if}
</div>
@@ -33,7 +33,7 @@
href={meta.tokenSourceUrl}
target="_blank"
rel="noopener noreferrer"
class="font-medium text-primary hover:underline">{meta.tokenSource}</a
class="font-medium text-primary hover:text-primary/80">{meta.tokenSource}</a
>
and copy its token.
</span>
@@ -77,7 +77,8 @@
href={meta.chatUrl(status.bot.username)}
target="_blank"
rel="noopener noreferrer"
class="font-mono text-sm text-primary hover:underline">@{status.bot.username}</a
class="font-mono text-sm text-primary hover:text-primary/80"
>@{status.bot.username}</a
>
{/if}
<span class="font-mono text-xs text-muted-foreground">{status.secret_masked}</span>
@@ -69,7 +69,7 @@
<div class="min-w-0">
<div class="flex items-center gap-2">
<a {href} class="truncate text-sm font-medium hover:underline">{template.name}</a>
<a {href} class="truncate text-sm font-medium hover:text-primary">{template.name}</a>
<OriginBadge builtin={template.is_builtin} />
</div>
<p class="line-clamp-1 text-xs text-muted-foreground">{template.description}</p>
@@ -160,7 +160,7 @@
{#if evidence}
<Collapsible.Root bind:open class="mt-1">
<Collapsible.Trigger
class="inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
class="inline-flex items-center gap-1 text-xs font-medium text-primary hover:text-primary/80"
>
<ChevronRight class={cn('size-3.5 transition-transform', open && 'rotate-90')} />
{open ? 'Hide' : 'Show'} what differs
@@ -75,7 +75,7 @@
</span>
<button
type="button"
class="font-medium text-primary hover:underline"
class="font-medium text-primary hover:text-primary/80"
onclick={() => (baseline = data?.suggestion?.scan_id ?? null)}
>
Compare with this run
@@ -108,7 +108,7 @@
{#each data.dimensions as d (d.dimension)}
<tr class="border-t border-border/50">
<td class="py-1.5 pr-3">
<a href={ROUTES.compare(scan.id, data.baseline.scan_id)} class="hover:underline"
<a href={ROUTES.compare(scan.id, data.baseline.scan_id)} class="hover:text-primary"
>{d.label}</a
>
{#if d.verdict.comparability !== COMPARABILITY.LIKE_FOR_LIKE}
@@ -135,7 +135,7 @@
</ScrollArea>
<a
href={ROUTES.compare(scan.id, data.baseline.scan_id)}
class="px-1 text-xs text-primary hover:underline"
class="px-1 text-xs text-primary hover:text-primary/80"
>
Open comparison
</a>
@@ -52,7 +52,7 @@
{#if scan.engine_id}
<a
href={ROUTES.engine(scan.engine_id)}
class="text-xl font-semibold tracking-tight hover:underline"
class="text-xl font-semibold tracking-tight hover:text-primary"
>
{scan.engine_name}
</a>
@@ -130,7 +130,7 @@
{#if !historyPrefs.showMedium && (findings?.medium ?? 0) > 0}
<button
type="button"
class="text-primary hover:underline"
class="text-primary hover:text-primary/80"
onclick={() => (historyPrefs.showMedium = true)}
>
Show {findings?.medium} medium
@@ -237,7 +237,10 @@
{/each}
{/each}
{#if total > (items?.length ?? 0)}
<a href={findingsHref(scanId, shown)} class="px-4 py-2 text-xs text-primary hover:underline">
<a
href={findingsHref(scanId, shown)}
class="px-4 py-2 text-xs text-primary hover:text-primary/80"
>
Open all {total} in results
</a>
{/if}
@@ -114,12 +114,12 @@
{#if added}
<a
href={ROUTES.results(WEB.tab, scan.id, { [WEB.queryParam]: 'is:new' })}
class="text-primary hover:underline"
class="text-primary hover:text-primary/80"
>
Open new {WEB.nounPlural}
</a>
{/if}
<button type="button" class="text-primary hover:underline" onclick={onCompare}>
<button type="button" class="text-primary hover:text-primary/80" onclick={onCompare}>
Compare with previous
</button>
</div>
@@ -77,7 +77,10 @@
{c.n}
{SEVERITY_LABELS[c.sev].toLowerCase()}
</span>
<a href={findingsHref(scanId, [c.sev])} class="text-2xs text-primary hover:underline">
<a
href={findingsHref(scanId, [c.sev])}
class="text-2xs text-primary hover:text-primary/80"
>
Open in results
</a>
</div>
@@ -1106,7 +1106,7 @@
{#if i > 0}<span class="text-muted-foreground/60">›</span>{/if}
<button
type="button"
class="max-w-64 truncate font-mono text-muted-foreground hover:text-foreground hover:underline"
class="max-w-64 truncate font-mono text-muted-foreground hover:text-foreground"
onclick={() => outline?.jumpTo(crumb.key)}
>
{crumb.name}
@@ -1326,7 +1326,7 @@
{/if}
<button
type="button"
class="ml-auto rounded-sm text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
class="ml-auto rounded-sm text-foreground hover:text-primary focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
onclick={() => (goneLens = false)}
>
Back to this scan
@@ -95,7 +95,7 @@
<span>·</span>
<button
type="button"
class="rounded-sm text-primary hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
class="rounded-sm text-primary hover:text-primary/80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
onclick={onShowStatic}
>
{n(hidden)} static hidden
@@ -105,7 +105,7 @@
<span>·</span>
<button
type="button"
class="rounded-sm text-primary hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
class="rounded-sm text-primary hover:text-primary/80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
onclick={onShowRootOnly}
>
{n(rootOnly)} root-only {rootOnly === 1 ? 'host' : 'hosts'} hidden
@@ -115,7 +115,7 @@
<span>·</span>
<button
type="button"
class="rounded-sm text-info hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
class="rounded-sm text-info hover:text-info/80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
onclick={onShowNew}
>
{n(summary.new)} new since the previous scan
@@ -125,7 +125,7 @@
<span>·</span>
<button
type="button"
class="rounded-sm text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
class="rounded-sm text-foreground hover:text-primary focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
onclick={onShowGone}
>
{n(summary.gone)} gone
@@ -252,7 +252,7 @@
{#if column.key === 'host'}
<button
type="button"
class="block w-full truncate text-left text-xs hover:text-primary hover:underline"
class="block w-full truncate text-left text-xs hover:text-primary"
onclick={(e) => {
e.stopPropagation();
onFilter?.(filterToken('host', endpoint.host));
@@ -137,34 +137,34 @@
><b class="font-semibold text-foreground tabular-nums">{n(brief.total)}</b> endpoints</span
>
{#if brief.probed}
<button type="button" class="hover:underline" onclick={() => onPivot('is:probed')}>
<button type="button" class="hover:text-primary" onclick={() => onPivot('is:probed')}>
<b class="font-semibold text-foreground tabular-nums">{n(brief.probed)}</b> verified
</button>
{/if}
{#if brief.with_params}
<button type="button" class="hover:underline" onclick={() => onPivot('is:param')}>
<button type="button" class="hover:text-primary" onclick={() => onPivot('is:param')}>
<b class="font-semibold text-foreground tabular-nums">{n(brief.with_params)}</b> take input
</button>
{/if}
{#if brief.api}
<button type="button" class="hover:underline" onclick={() => onPivot('class:api')}>
<button type="button" class="hover:text-primary" onclick={() => onPivot('class:api')}>
<b class="font-semibold text-foreground tabular-nums">{n(brief.api)}</b> API
</button>
{/if}
{#if brief.walled}
<button type="button" class="hover:underline" onclick={() => onPivot('is:auth')}>
<button type="button" class="hover:text-primary" onclick={() => onPivot('is:auth')}>
<b class="font-semibold text-foreground tabular-nums">{n(brief.walled)}</b> behind auth
</button>
{/if}
{#if brief.new}
<button type="button" class="hover:underline" onclick={onNew}>
<button type="button" class="hover:text-primary" onclick={onNew}>
<b class="font-semibold text-success tabular-nums">+{n(brief.new)}</b>
new{#if brief.previous_scan_at}
since {formatShortDate(brief.previous_scan_at)}{/if}
</button>
{/if}
{#if brief.findings && onFindings}
<button type="button" class="hover:underline" onclick={onFindings}>
<button type="button" class="hover:text-primary" onclick={onFindings}>
<b class="font-semibold text-destructive tabular-nums">{n(brief.findings)}</b>
{brief.findings === 1 ? 'finding' : 'findings'}
</button>
@@ -196,7 +196,7 @@
{#if hiddenParams > 0}
<button
type="button"
class="text-2xs text-muted-foreground hover:text-foreground hover:underline"
class="text-2xs text-muted-foreground hover:text-foreground"
onclick={() => (allParams = true)}
>
+{n(hiddenParams)} more
@@ -204,7 +204,7 @@
{:else if allParams && brief.params_total > PARAM_LIMIT}
<button
type="button"
class="text-2xs text-muted-foreground hover:text-foreground hover:underline"
class="text-2xs text-muted-foreground hover:text-foreground"
onclick={() => (allParams = false)}
>
Show fewer
@@ -110,7 +110,7 @@
</span>
<button
type="button"
class="rounded-sm text-primary hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
class="rounded-sm text-primary hover:text-primary/80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
onclick={onShowRootOnly}
>
Show
@@ -115,7 +115,7 @@
<button
{...props}
type="button"
class="text-2xs text-muted-foreground hover:text-primary hover:underline"
class="text-2xs text-muted-foreground hover:text-primary"
onclick={(e) => e.stopPropagation()}
>
on {leaf.hosts} hosts
@@ -165,7 +165,7 @@
{:else if leaf.host_names[0]}
<button
type="button"
class="font-mono text-2xs text-muted-foreground hover:text-primary hover:underline"
class="font-mono text-2xs text-muted-foreground hover:text-primary"
onclick={(e) => {
e.stopPropagation();
onHost(leaf.host_names[0]);
@@ -287,7 +287,7 @@
<span class="size-4 shrink-0"></span>
<button
type="button"
class="font-medium text-primary hover:underline disabled:opacity-60"
class="font-medium text-primary hover:text-primary/80 disabled:opacity-60"
disabled={loading}
onclick={() => load(page + 1)}
>
@@ -17,7 +17,7 @@
{#if host}
<button
type="button"
class="flex items-center gap-1 font-mono hover:text-primary hover:underline"
class="flex items-center gap-1 font-mono hover:text-primary"
onclick={() => onSelect(host, '/')}
>
<Globe class="size-3" />
@@ -30,7 +30,7 @@
<ChevronRight class="size-3 shrink-0 text-muted-foreground" />
<button
type="button"
class="font-mono hover:text-primary hover:underline"
class="font-mono hover:text-primary"
onclick={() => onSelect(host, '/' + segments.slice(0, i + 1).join('/') + '/')}
>
{segment}
@@ -220,7 +220,7 @@
<button
{...props}
type="button"
class="min-w-0 truncate font-mono hover:text-foreground hover:underline"
class="min-w-0 truncate font-mono hover:text-foreground"
onclick={(e) => pivot(e, filterToken('ptr', ptr[0]))}
>
<HighlightText text={ptr[0]} {term} />
@@ -244,7 +244,7 @@
class="block min-w-0 max-w-full text-left"
onclick={(e) => pivot(e, `asn:${g.asn}`)}
>
<span class="block font-mono text-xs hover:underline">AS{g.asn}</span>
<span class="block font-mono text-xs hover:text-primary">AS{g.asn}</span>
{#if g.asn_org}
<span class="block truncate text-xs text-muted-foreground">
<HighlightText text={g.asn_org} {term} />
@@ -336,7 +336,7 @@
{#if g.country}
<button
type="button"
class="text-xs text-muted-foreground hover:text-foreground hover:underline"
class="text-xs text-muted-foreground hover:text-foreground"
onclick={(e) => pivot(e, exactToken('country', g.country ?? ''))}
>
<CountryFlag code={g.country} />
@@ -348,7 +348,7 @@
{#if g.prefix}
<button
type="button"
class="min-w-0 truncate font-mono text-xs text-muted-foreground hover:text-foreground hover:underline"
class="min-w-0 truncate font-mono text-xs text-muted-foreground hover:text-foreground"
onclick={(e) => pivot(e, exactToken('prefix', g.prefix ?? ''))}
>
{g.prefix}
@@ -56,7 +56,7 @@
onpointerleave={() => (hovered = null)}
>
<CountryFlag code={dominant.name} showCode={false} />
<span class="min-w-0 flex-1 truncate text-sm font-medium group-hover:underline">
<span class="min-w-0 flex-1 truncate text-sm font-medium group-hover:text-primary">
{countryName(dominant.name)}
</span>
<span class="text-sm font-medium tabular-nums">{dominant.count.toLocaleString()}</span>
@@ -134,7 +134,7 @@
{#each severities as part (part.severity)}
<button
type="button"
class="flex items-center gap-1.5 text-xs hover:underline"
class="flex items-center gap-1.5 text-xs hover:text-primary"
onclick={() => pick(exactToken('severity', part.severity))}
>
<span
@@ -83,7 +83,7 @@
{#if !isRecheckLive(entry)}
<a
href={ROUTES.scan(entry.scan_id)}
class="mt-2 ml-4 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
class="mt-2 ml-4 inline-flex items-center gap-1 text-xs font-medium text-primary hover:text-primary/80"
>
Open focused scan <ArrowUpRight class="size-3" />
</a>
@@ -290,7 +290,7 @@
type="button"
class="font-mono text-sm tabular-nums {httpStatusTextClass(
s.status_code
)} hover:underline"
)} hover:text-primary"
onclick={() => onFilter?.(`status:${s.status_code}`)}
>
{s.status_code}
@@ -312,7 +312,7 @@
href={s.url}
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1 font-mono text-xs break-all hover:underline"
class="inline-flex items-center gap-1 font-mono text-xs break-all hover:text-primary"
>
{s.url}
<ExternalLink class="size-3 shrink-0" />
@@ -322,7 +322,7 @@
<span class="flex h-4 shrink-0 items-center">
<TechIcon name={productBrand(s.product)} class="size-3.5" />
</span>
<span class="min-w-0 text-xs leading-4 wrap-anywhere hover:underline">
<span class="min-w-0 text-xs leading-4 wrap-anywhere hover:text-primary">
<HighlightText text={software} {term} />
</span>
</span>
@@ -382,7 +382,7 @@
type="button"
class="shrink-0 font-mono text-xs tabular-nums {httpStatusTextClass(
s.status_code
)} hover:underline"
)} hover:text-primary"
onclick={(e) => pivot(e, `status:${s.status_code}`)}
>
{s.status_code}
@@ -412,7 +412,7 @@
class="block min-w-0 max-w-full text-left"
onclick={(e) => pivot(e, `asn:${s.asn}`)}
>
<span class="block font-mono text-xs hover:underline">AS{s.asn}</span>
<span class="block font-mono text-xs hover:text-primary">AS{s.asn}</span>
{#if s.asn_org}
<span class="block truncate text-xs text-muted-foreground">
<HighlightText text={s.asn_org} {term} />
@@ -428,7 +428,7 @@
{#if s.country}
<button
type="button"
class="text-xs text-muted-foreground hover:text-foreground hover:underline"
class="text-xs text-muted-foreground hover:text-foreground"
onclick={(e) => pivot(e, exactToken('country', s.country ?? ''))}
>
<CountryFlag code={s.country} />
@@ -443,7 +443,7 @@
<button
{...props}
type="button"
class="min-w-0 truncate text-xs text-muted-foreground hover:text-foreground hover:underline"
class="min-w-0 truncate text-xs text-muted-foreground hover:text-foreground"
onclick={(e) => pivot(e, exactToken('source', s.source))}
>
{PORT_SOURCE_LABELS[s.source] ?? s.source}
@@ -22,7 +22,7 @@
<button
{...props}
type="button"
class="min-w-0 truncate text-xs text-muted-foreground hover:text-foreground hover:underline"
class="min-w-0 truncate text-xs text-muted-foreground hover:text-foreground"
onclick={(event) => {
event.stopPropagation();
onFilter?.(exactToken('target', first));
@@ -105,7 +105,7 @@
{/if}
<button
type="button"
class="truncate font-mono hover:underline"
class="truncate font-mono hover:text-primary"
onclick={() => onTab(WEB.tab, hostToken)}>{v.host}</button
>
</dd>
@@ -137,14 +137,14 @@
<dd class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5">
<button
type="button"
class="font-mono hover:underline"
class="font-mono hover:text-primary"
onclick={() => onTab(IPS.tab, ipToken)}>{v.ip}</button
>
{#if asset?.country}<CountryFlag code={asset.country} showCode />{/if}
{#if asset?.asn}
<button
type="button"
class="truncate text-muted-foreground hover:text-foreground hover:underline"
class="truncate text-muted-foreground hover:text-foreground"
onclick={() => onFilter(`asn:${asset.asn}`)}
>
AS{asset.asn}{asset.asn_org ? ` · ${asset.asn_org}` : ''}
@@ -186,7 +186,7 @@
</span>
<button
type="button"
class="ml-auto text-2xs text-primary hover:underline"
class="ml-auto text-2xs text-primary hover:text-primary/80"
onclick={() => onFilter(hostToken)}
>
Filter to this {WEB.noun}
@@ -220,7 +220,7 @@
</span>
<button
type="button"
class="ml-auto text-2xs text-primary hover:underline"
class="ml-auto text-2xs text-primary hover:text-primary/80"
onclick={() => onFilter(templateToken)}
>
All {checkTotal || ''} findings from this check
@@ -308,7 +308,8 @@
{#if v.cve_ids.length}
<dt class="text-muted-foreground">CVE</dt>
<dd class="flex flex-wrap gap-x-2 font-mono">
{#each v.cve_ids as c (c)}<a href={ROUTES.cve(c)} class="hover:underline">{c}</a>{/each}
{#each v.cve_ids as c (c)}<a href={ROUTES.cve(c)} class="hover:text-primary">{c}</a
>{/each}
</dd>
{/if}
{#if v.cwe_ids.length}
@@ -181,7 +181,7 @@
<div class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
<button
type="button"
class="min-w-0 text-left text-sm leading-6 font-medium wrap-anywhere hover:underline {suppressed
class="min-w-0 text-left text-sm leading-6 font-medium wrap-anywhere hover:text-primary {suppressed
? 'text-muted-foreground line-through decoration-muted-foreground/40'
: ''}"
onclick={(e) => {
@@ -225,7 +225,7 @@
{#if projectWide && v.target_value}
<button
type="button"
class="font-medium text-foreground/80 hover:underline {NARROW.target}"
class="font-medium text-foreground/80 hover:text-primary {NARROW.target}"
onclick={(e) => pivot(e, exactToken('target', v.target_value ?? ''))}
>{v.target_value}</button
>
@@ -233,7 +233,7 @@
{/if}
<button
type="button"
class="font-mono text-muted-foreground hover:text-foreground hover:underline"
class="font-mono text-muted-foreground hover:text-foreground"
onclick={(e) => pivot(e, templateToken)}
>
<HighlightText text={v.template_id} {term} />
@@ -244,7 +244,7 @@
<button
{...props}
type="button"
class="min-w-0 truncate text-left font-mono text-foreground/80 hover:underline"
class="min-w-0 truncate text-left font-mono text-foreground/80 hover:text-primary"
onclick={(e) => pivot(e, filterToken('location', path))}
>
<span class={NARROW.origin}>{origin}</span><HighlightText text={path} {term} />
@@ -298,7 +298,7 @@
<button
{...props}
type="button"
class="min-w-0 truncate font-mono text-xs hover:underline"
class="min-w-0 truncate font-mono text-xs hover:text-primary"
onclick={(e) => pivot(e, hostToken)}
>
<HighlightText text={v.host ?? ''} {term} />
@@ -307,14 +307,14 @@
{#snippet footer()}
<button
type="button"
class="text-primary hover:underline"
class="text-primary hover:text-primary/80"
onclick={() => onFilter(hostToken)}
>
Filter to this {WEB.noun}
</button>
<button
type="button"
class="text-primary hover:underline"
class="text-primary hover:text-primary/80"
onclick={() => onTab(WEB.tab, hostToken)}
>
Open in {WEB.nounPlural}
@@ -387,7 +387,7 @@
<button
{...props}
type="button"
class="w-fit text-left text-2xs text-muted-foreground hover:text-foreground hover:underline"
class="w-fit text-left text-2xs text-muted-foreground hover:text-foreground"
onclick={(e) => pivot(e, templateToken)}
>
Same check on {others} more
@@ -408,7 +408,7 @@
{#if v.cve_ids.length}
<a
href={ROUTES.cve(v.cve_ids[0])}
class="h-6 truncate font-mono text-xs leading-6 hover:underline"
class="h-6 truncate font-mono text-xs leading-6 hover:text-primary"
onclick={stopProp}
>
{v.cve_ids[0]}{v.cve_ids.length > 1 ? ` +${v.cve_ids.length - 1}` : ''}
@@ -420,7 +420,7 @@
<button
{...props}
type="button"
class="w-fit text-left font-mono text-2xs tabular-nums hover:underline"
class="w-fit text-left font-mono text-2xs tabular-nums hover:text-primary"
onclick={(e) => pivot(e, `exploit:>=${Math.max(10, v.exploit_score - 10)}`)}
>
<span class="text-muted-foreground">Rank</span>
@@ -258,7 +258,7 @@
<button
{...props}
type="button"
class="min-w-0 font-mono text-2xs text-muted-foreground hover:text-foreground hover:underline"
class="min-w-0 font-mono text-2xs text-muted-foreground hover:text-foreground"
onclick={(e) => pivot(e, exactToken('template', it.template_id))}
>
<HighlightText text={it.template_id} {term} />
@@ -335,22 +335,20 @@
>
<button
type="button"
class="font-mono hover:text-foreground hover:underline"
class="font-mono hover:text-foreground"
onclick={() => filter(templateToken)}>{s.template_id}</button
>
{#if s.host}
<span class="text-muted-foreground/50">·</span>
<button
type="button"
class="font-mono text-foreground/90 hover:underline"
class="font-mono text-foreground/90 hover:text-primary"
onclick={() => onHost?.(hostToken)}>{s.host}</button
>
{/if}
{#each s.cve_ids.filter((c) => c.toLowerCase() !== s.template_id.toLowerCase()) as cve (cve)}
<span class="text-muted-foreground/50">·</span>
<a href={ROUTES.cve(cve)} class="font-mono hover:text-foreground hover:underline"
>{cve}</a
>
<a href={ROUTES.cve(cve)} class="font-mono hover:text-foreground">{cve}</a>
{/each}
{#if s.cvss_score != null}
<span class="text-muted-foreground/50">·</span>
@@ -582,7 +580,7 @@
{/if}
<button
type="button"
class="min-w-0 truncate font-mono text-base font-medium hover:underline"
class="min-w-0 truncate font-mono text-base font-medium hover:text-primary"
onclick={() => onHost?.(hostToken)}
>{s.host ?? s.ip}{s.port ? `:${s.port}` : ''}</button
>
@@ -630,7 +628,7 @@
<dd class="flex min-w-0 items-center gap-2 text-xs">
<button
type="button"
class="font-mono hover:underline disabled:no-underline"
class="font-mono hover:text-primary"
disabled={!onTab}
onclick={() => onTab?.(IPS.tab, exactToken('ip', s.ip ?? ''))}
>{s.ip}</button
@@ -651,7 +649,7 @@
<dd class="min-w-0 text-xs">
<button
type="button"
class="max-w-full truncate hover:underline"
class="max-w-full truncate hover:text-primary"
onclick={() => filter(`asn:${asset.asn}`)}
>
<span class="font-mono">AS{asset.asn}</span>{asset.asn_org
@@ -704,7 +702,7 @@
{@render heading(`Other findings on this ${WEB.noun}`)}
<button
type="button"
class="ml-auto text-2xs text-primary hover:underline"
class="ml-auto text-2xs text-primary hover:text-primary/80"
onclick={() => filter(hostToken)}>Filter to this {WEB.noun}</button
>
</div>
@@ -801,7 +799,7 @@
<dd>
<button
type="button"
class="text-xs hover:underline"
class="text-xs hover:text-primary"
onclick={() => onLocation?.(s.matched_at)}
>
{s.colocated}
@@ -893,7 +891,7 @@
>
<button
type="button"
class="ml-auto text-2xs text-primary hover:underline"
class="ml-auto text-2xs text-primary hover:text-primary/80"
onclick={() => filter(templateToken)}>All findings from this check</button
>
</div>
@@ -952,7 +950,7 @@
href={ref}
target="_blank"
rel="noopener noreferrer"
class="flex items-start gap-1.5 text-xs break-all text-primary hover:underline"
class="flex items-start gap-1.5 text-xs break-all text-primary hover:text-primary/80"
>
<ScrollText class="mt-0.5 size-3 shrink-0" />{ref}
</a>
@@ -962,7 +960,7 @@
href={s.template_url}
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-1.5 text-xs text-primary hover:underline"
class="flex items-center gap-1.5 text-xs text-primary hover:text-primary/80"
>
<ExternalLink class="size-3" /> View check
</a>
@@ -451,7 +451,7 @@
href={sub.final_url}
target="_blank"
rel="noreferrer noopener"
class="ml-1 font-mono break-all hover:underline">{sub.final_url}</a
class="ml-1 font-mono break-all hover:text-primary">{sub.final_url}</a
>
</div>
</div>
@@ -571,7 +571,7 @@
href={url}
target="_blank"
rel="noreferrer noopener"
class="font-mono text-sm break-all hover:underline">{url}</a
class="font-mono text-sm break-all hover:text-primary">{url}</a
>
</dd>
</div>
@@ -626,7 +626,7 @@
Covered by
<button
type="button"
class="font-mono hover:underline"
class="font-mono hover:text-primary"
onclick={() => {
const rep = surface.representative_value ?? '';
const host = rep.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
@@ -717,7 +717,7 @@
<button
{...props}
type="button"
class="text-left text-sm leading-5 hover:underline"
class="text-left text-sm leading-5 hover:text-primary"
onclick={() => onFilter?.(hygieneQuery(key))}
>
{checkLabel(key)}
@@ -775,7 +775,7 @@
<button
{...props}
type="button"
class="text-left text-sm leading-5 hover:underline"
class="text-left text-sm leading-5 hover:text-primary"
onclick={() => onFilter?.(postureQuery(key))}
>
{spec?.label ?? key}
@@ -515,7 +515,7 @@
<button
{...props}
type="button"
class="inline-flex items-center gap-1 font-mono text-xs hover:underline {httpStatusTextClass(
class="inline-flex items-center gap-1 font-mono text-xs hover:text-primary {httpStatusTextClass(
code
)}"
onclick={(e) => pivot(e, `status:${code}`)}
@@ -604,7 +604,7 @@
<button
{...props}
type="button"
class="max-w-full truncate text-left font-mono text-xs hover:underline {isPrivateIp(
class="max-w-full truncate text-left font-mono text-xs hover:text-primary {isPrivateIp(
ip
)
? 'text-warning'
@@ -72,7 +72,7 @@
{#if summary.clean > 0}
<button
type="button"
class="flex items-center gap-1 text-left text-primary hover:underline {cleanOn
class="flex items-center gap-1 text-left text-primary hover:text-primary/80 {cleanOn
? 'font-medium'
: ''}"
aria-pressed={cleanOn}
@@ -70,7 +70,7 @@
{#if summary.clean > 0}
<button
type="button"
class="flex items-center gap-1 text-left text-primary hover:underline {cleanOn
class="flex items-center gap-1 text-left text-primary hover:text-primary/80 {cleanOn
? 'font-medium'
: ''}"
aria-pressed={cleanOn}
@@ -143,7 +143,7 @@
<button
type="button"
onclick={() => onHost(host)}
class="truncate text-left font-mono text-2xs text-muted-foreground hover:text-foreground hover:underline"
class="truncate text-left font-mono text-2xs text-muted-foreground hover:text-foreground"
>
{host}
</button>
@@ -164,7 +164,7 @@
<button
type="button"
onclick={() => onFilter(g.query)}
class="truncate text-left text-2xs text-muted-foreground hover:text-foreground hover:underline"
class="truncate text-left text-2xs text-muted-foreground hover:text-foreground"
>
Open all {g.count}
</button>
@@ -81,7 +81,7 @@
</span>
<a
href={ROUTES.target(row.target_id)}
class="min-w-0 flex-1 truncate text-xs leading-5 hover:underline"
class="min-w-0 flex-1 truncate text-xs leading-5 hover:text-primary"
>
{row.target_value}
</a>
@@ -95,7 +95,7 @@
{#if row.scan_id}
<a
href={ROUTES.scan(row.scan_id)}
class="shrink-0 text-2xs leading-5 text-muted-foreground hover:text-foreground hover:underline"
class="shrink-0 text-2xs leading-5 text-muted-foreground hover:text-foreground"
>
run
</a>
@@ -168,7 +168,7 @@
<div class="flex min-w-0 items-center gap-1.5">
<a
href={ROUTES.target(target.id)}
class="truncate font-mono text-sm font-medium hover:underline"
class="truncate font-mono text-sm font-medium hover:text-primary"
onclick={stopProp}>{target.target_value}</a
>
<CopyButton
@@ -215,7 +215,9 @@
{/if}
{#if note.text}<span class="truncate">{note.text}</span>{/if}
{#if href}
<a {href} class="font-medium text-primary hover:underline">open in {IPS.label}</a>
<a {href} class="font-medium text-primary hover:text-primary/80"
>open in {IPS.label}</a
>
{/if}
</span>
<span
@@ -156,7 +156,7 @@
{/if}
<a
href={ROUTES.whatsNew({ window: '30d', target: target.id })}
class="text-xs text-muted-foreground hover:text-foreground hover:underline"
class="text-xs text-muted-foreground hover:text-foreground"
>
What's new
</a>
@@ -184,7 +184,7 @@
</span>
<span class="flex min-w-0 flex-col gap-1.5 pb-[18px]">
<span class="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm">
<a href={ROUTES.scan(s.id)} class="font-semibold hover:underline">{s.engine_name}</a>
<a href={ROUTES.scan(s.id)} class="font-semibold hover:text-primary">{s.engine_name}</a>
<span
class="rounded-full px-[7px] text-2xs font-semibold tracking-[0.02em] {SCAN_STATUS_PILL[
s.status
@@ -240,7 +240,7 @@
</span>
<span class="flex min-w-0 flex-col gap-1 pb-3.5">
<span class="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm">
<a href={ROUTES.scan(r.id)} class="font-medium hover:underline">{r.engine_name}</a>
<a href={ROUTES.scan(r.id)} class="font-medium hover:text-primary">{r.engine_name}</a>
<span
class="rounded-full px-[7px] text-2xs font-semibold tracking-[0.02em] {SCAN_STATUS_PILL[
r.status
@@ -112,7 +112,11 @@
</dl>
{#snippet footer()}
<span>{enrichedAt ? `Enriched ${relativeTime(enrichedAt)}` : 'Not enriched'}</span>
<button type="button" class="font-medium text-primary hover:underline" onclick={onRefresh}>
<button
type="button"
class="font-medium text-primary hover:text-primary/80"
onclick={onRefresh}
>
Refresh
</button>
{/snippet}
@@ -66,7 +66,7 @@
href={scanHref}
class="truncate font-mono text-sm {asset.current
? ''
: 'text-muted-foreground line-through decoration-muted-foreground/40'} hover:underline"
: 'text-muted-foreground line-through decoration-muted-foreground/40'} hover:text-primary"
>
<HighlightText text={asset.name} terms={[term]} />
</a>
@@ -287,7 +287,7 @@
</Badge>
{/if}
<a
class="inline-flex min-w-0 items-center gap-1 truncate font-mono text-2xs text-muted-foreground hover:text-foreground hover:underline"
class="inline-flex min-w-0 items-center gap-1 truncate font-mono text-2xs text-muted-foreground hover:text-foreground"
href={poc.url}
target="_blank"
rel="noopener noreferrer"
@@ -19,7 +19,7 @@
data-slot="accordion-trigger"
bind:ref
class={cn(
'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-start text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-start text-sm font-medium transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
className
)}
{...restProps}
@@ -14,7 +14,7 @@
'bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline'
link: 'text-primary hover:text-primary/80'
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
@@ -103,7 +103,10 @@
{#if entity.email}
<div class="flex items-start gap-2.5">
<Mail class="h-3.5 w-3.5 text-muted-foreground mt-0.5 shrink-0" />
<a href="mailto:{entity.email}" class="text-sm text-primary hover:underline">
<a
href="mailto:{entity.email}"
class="text-sm text-primary hover:text-primary/80"
>
{entity.email}
</a>
</div>
@@ -381,7 +381,7 @@
<p class="mt-5 text-xs text-muted-foreground">
<button
type="button"
class="font-medium text-foreground underline-offset-4 hover:underline"
class="font-medium text-foreground hover:text-primary"
onclick={() => (showImportDialog = true)}
>
Import a YAML file
@@ -475,7 +475,7 @@
href="{platformUrl}/{summary.username}"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-0.5 font-mono text-foreground hover:underline"
class="flex items-center gap-0.5 font-mono text-foreground hover:text-primary"
>
@{summary.username}<ArrowUpRight class="size-3" />
</a>
@@ -194,17 +194,17 @@
{#if headline.critical}
<a
href={headlineHref(VULNS, `severity:critical and ${NEW_IN_WINDOW}`)}
class="text-destructive hover:underline"
class="text-destructive hover:text-destructive/80"
>{plural(headline.critical, 'critical finding', 'critical findings')}</a
>
and
{/if}
<a href={headlineHref(VULNS, NEW_IN_WINDOW)} class="hover:underline"
<a href={headlineHref(VULNS, NEW_IN_WINDOW)} class="hover:text-primary"
>{plural(headline.findings, 'finding', 'findings')}</a
>
in {days} days.
<span class="font-medium text-muted-foreground">
<a href={headlineHref(WEB, NEW_IN_WINDOW)} class="hover:underline"
<a href={headlineHref(WEB, NEW_IN_WINDOW)} class="hover:text-primary"
>{plural(headline.web, 'new web asset', 'new web assets')}</a
>{headline.targets ? ` on ${plural(headline.targets, 'target', 'targets')}` : ''}.
</span>
@@ -1014,7 +1014,7 @@
{#if surfaceLink}
<a
href={surfaceLink}
class="hidden shrink-0 items-center gap-1 py-2.5 pl-2 text-xs whitespace-nowrap text-muted-foreground hover:text-foreground hover:underline sm:flex"
class="hidden shrink-0 items-center gap-1 py-2.5 pl-2 text-xs whitespace-nowrap text-muted-foreground hover:text-foreground sm:flex"
>
Across all targets
<ArrowUpRight class="size-3.5" />
@@ -152,7 +152,7 @@
{#if description.length > DESCRIPTION_CLAMP}
<button
type="button"
class="ml-1 text-xs text-foreground hover:underline"
class="ml-1 text-xs text-foreground hover:text-primary"
onclick={() => (showDescription = !showDescription)}
>
{showDescription ? 'Show less' : 'Show more'}
@@ -945,7 +945,7 @@
{#if selectedTargetIds.size >= rows.length && selectedTargetIds.size < targetsStore.pagination.totalItems}
<button
type="button"
class="text-xs font-medium text-primary hover:underline"
class="text-xs font-medium text-primary hover:text-primary/80"
onclick={handleSelectAllMatching}
>
Select all {targetsStore.pagination.totalItems} matching