Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ const style = computed(() => ({
<template>
<Tooltip>
<div flex="~ gap-1 items-center" text-sm pl1 pr2 rounded border-l-3 border :style>
<div v-if="parsed.namespace" text-xs op-fade>
<!-- Intentionally full-opacity: `op-fade` (65% light / 55% dark) dilutes
the already contrast-tuned hash color further toward the badge's own
background, dragging worst-case hues under 4.5:1 (axe-core-measured,
e.g. ~6.9:1 undiluted down to ~3.5:1). Same class of bug as
`Percentage.vue`'s percentage span. -->
<div v-if="parsed.namespace" text-xs>
{{ parsed.namespace }}:
</div>
<div rounded-full font-mono>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ const timepassed = computed(() => rawPayload.value?.timestamp ? Date.now() - raw
<h1 text-lg p4 flex="~ gap-3 items-center">
<UiLogo w-9 h-9 alt="Logo" class="hover:animate-spin-reverse" />
<div flex="~ col gap-0" leading-none>
<span font-700 text-primary>Node Modules</span>
<!-- `dark:text-primary-300` — the DEFAULT shade (#49833e) only clears
~4.14:1 against the dark-mode page background (axe-core-measured;
this 18px-bold text is just under axe's large-text exemption). -->
<span font-700 text-primary dark:text-primary-300>Node Modules</span>
<div flex="~ gap-1 items-end">
<div op75>
Inspector
Expand Down Expand Up @@ -116,7 +119,14 @@ const timepassed = computed(() => rawPayload.value?.timestamp ? Date.now() - raw
</NuxtLink>
<NuxtLink flex="~ gap-2 items-center" :to="{ path: '/report/install-size', hash: location.hash }">
<div i-catppuccin-binary icon-catppuccin flex-none />
<DisplayFileSizeBadge :bytes="totalWorkspaceSize" :percent="false" rounded-full text-xs color="primary" />
<!-- `color="primary"` isn't a real prop of `FileSizeBadge` — it falls
through onto the root element as a bare attribute, and (as an
attributify `text-primary` equivalent) only wins over the
component's own internal `color-scale-*` class by generation-order
luck. `!`-suffixed classes force it, and `-700`/`dark:-300` clear
4.5:1 at this badge's small text size (`text-primary` DEFAULT was
4.37:1, axe-core-measured). -->
<DisplayFileSizeBadge :bytes="totalWorkspaceSize" :percent="false" rounded-full text-xs class="text-primary-700! dark:text-primary-300!" />
<span ml--0.5>total node_modules size</span>
</NuxtLink>
<div v-if="timepassed >= mins10" flex="~ gap-2 items-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,21 +372,24 @@ const thirdPartyServices = computed(() => {
<div border="b base" w-2 />
<button
flex-1 border border-base rounded-t-lg p1 flex="~ items-center justify-center gap-1" transition-margin
:class="settings.packageDetailsTab === 'dependents' ? 'text-primary border-b-transparent' : 'saturate-0 hover:bg-hover mt-5px'"
:class="settings.packageDetailsTab === 'dependents' ? 'text-primary-700 dark:text-primary-300 border-b-transparent' : 'saturate-0 hover:bg-hover mt-5px'"
@click="settings.packageDetailsTab = 'dependents'"
>
<span :class="settings.packageDetailsTab === 'dependents' ? '' : 'op30'">Used by</span>
<!-- `color-muted`, not `op30` — op30's alpha blend only clears
~1.85:1 against the page background (axe-core-measured). -->
<span :class="settings.packageDetailsTab === 'dependents' ? '' : 'color-muted'">Used by</span>
<DisplayNumberBadge
:value="settings.deepDependenciesTree ? payloads.available.flatDependents(pkg).length : payloads.available.dependents(pkg).length"
/>
</button>
<div border="b base" w-2 />
<button
flex-1 border border-base rounded-t-lg p1 flex="~ items-center justify-center gap-1" transition-margin
:class="settings.packageDetailsTab === 'dependencies' ? 'text-primary border-b-transparent' : 'saturate-0 hover:bg-hover mt-5px'"
:class="settings.packageDetailsTab === 'dependencies' ? 'text-primary-700 dark:text-primary-300 border-b-transparent' : 'saturate-0 hover:bg-hover mt-5px'"
@click="settings.packageDetailsTab = 'dependencies'"
>
<span :class="settings.packageDetailsTab === 'dependencies' ? '' : 'op30'">Deps on</span>
<!-- See the `color-muted` note on the "Used by" tab above. -->
<span :class="settings.packageDetailsTab === 'dependencies' ? '' : 'color-muted'">Deps on</span>
<DisplayNumberBadge
:value="settings.deepDependenciesTree ? payloads.available.flatDependencies(pkg).length : payloads.available.dependencies(pkg).length"
/>
Expand Down Expand Up @@ -433,7 +436,11 @@ const thirdPartyServices = computed(() => {
type="dependents"
/>
</template>
<div v-else op-mute italic text-center py3>
<!-- `color-muted` (a solid neutral-600/400 shade), not `op-mute` (30%
opacity) — op-mute's alpha blend only clears ~1.85:1 against the
page background (axe-core-measured); this is real body text, not
a decorative/disabled affordance, so it needs the readable shade. -->
<div v-else color-muted italic text-center py3>
No dependents
</div>
</template>
Expand Down Expand Up @@ -465,7 +472,8 @@ const thirdPartyServices = computed(() => {
type="dependencies"
/>
</template>
<div v-else op-mute italic text-center pb4>
<!-- See the `color-muted` note on the dependents empty state above. -->
<div v-else color-muted italic text-center pb4>
No dependencies
</div>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
Made with
<a href="https://nuxt.com" target="_blank" rel="noopener" flex="~ gap-1.1 items-center">
<div i-logos-nuxt-icon text-0.6rem mr--0.6 />
<span text-hex-01dc82>Nuxt</span>
<!-- Nuxt's brand green (#01dc82) only clears ~1.8:1 on white
(axe-core-measured) — darken for light mode, keep the bright brand
color (already ~10.4:1 on #111) for dark mode. -->
<span text-hex-005424 dark:text-hex-01dc82>Nuxt</span>
</a>
and
<div i-ph-heart-duotone text-rose />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ const classesOuter = computed(() => {

if (isSelected.value) {
if (isFocused.value)
list.push('ring-3 ring-teal:25! text-teal-600 dark:text-teal-300 border-teal!')
list.push('ring-3 ring-teal:25! text-teal-700 dark:text-teal-300 border-teal!')
else
list.push('ring-3 ring-primary:25! text-primary-600 dark:text-primary-300')
// text-primary-600 (#49833e) only clears ~4.05:1 against the bg-primary:10
// tint (axe-core-measured) — bump to 700 for a comfortable ~5.8:1.
list.push('ring-3 ring-primary:25! text-primary-700 dark:text-primary-300')
}

if (props.pkg?.private)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ const total = computed(() => props.nodes.reduce((acc, { value }) => acc + value,
text-center text-xs px1.5 py1 flex gap-x-0.5 cursor-default
>
<span>{{ node.name }}</span>
<span v-if="percentage" op-fade>{{ `${+(node.value * 100 / total).toFixed(1)}%` }}</span>
<!-- Intentionally full-opacity: `op-fade` (65% light / 55% dark) blends the
already WCAG-AA-tuned `badge-color-*` text 35-45% toward its own tinted
background, dragging every color under 4.5:1 (axe-core-measured). -->
<span v-if="percentage">{{ `${+(node.value * 100 / total).toFixed(1)}%` }}</span>
</div>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<template>
<h2 mt-6 mb-3 op-fade font-bold flex="~ gap-1 items-center">
<!-- `color-muted`, not `op-fade` — every caller nests a `DisplayNumberBadge`
count here, and `op-fade`'s opacity dilutes that badge's own
already-tuned `color-muted` text down to ~3.1:1 against its background
(axe-core-measured), since CSS `opacity` on this ancestor also
multiplies through non-color children like the badge's own bg tint. -->
<h2 mt-6 mb-3 color-muted font-bold flex="~ gap-1 items-center">
<slot />
</h2>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -276,26 +276,32 @@ onUnmounted(() => {

<template>
<div flex="~ gap-2 items-center wrap">
<!-- Two axe-core-measured contrast fixes on every active tab below:
`btn-action` always applies `op75`, so `op100!` is needed to stop the
active color being diluted toward its 5%-tint background; and the
plain `text-primary` DEFAULT shade is tuned for its own solid swatch,
not for sitting on that same near-white/near-black tint, so
`-700`/`dark:-300` (matching `PackageBorder.vue`) replace it. -->
<NuxtLink
btn-action as="button"
:to="{ path: '/chart/treemap', hash: location.hash }"
active-class="text-primary bg-primary:5"
active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!"
>
<div i-ph-checkerboard-duotone />
Treemap
</NuxtLink>
<NuxtLink
btn-action as="button"
:to="{ path: '/chart/sunburst', hash: location.hash }"
active-class="text-primary bg-primary:5"
active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!"
>
<div i-ph-chart-donut-duotone />
Sunburst
</NuxtLink>
<NuxtLink
btn-action as="button"
:to="{ path: '/chart/flamegraph', hash: location.hash }"
active-class="text-primary bg-primary:5"
active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!"
>
<div i-ph-chart-bar-horizontal-duotone />
Flamegraph
Expand Down
8 changes: 6 additions & 2 deletions packages/node-modules-inspector/src/app/pages/compare.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ function reset() {
</div>
<div grid="~ cols-[1fr_max-content_1fr] gap-2">
<div>
<div text-yellow p2 text-center op75>
<!-- `-700`/`dark:-300`, not the DEFAULT shade + `op75` — even at 700,
op75's dilution drops this under 4.5:1 (axe-core-measured, ~3.1:1),
so drop the opacity too rather than chase a shade that survives it. -->
<div text-yellow-700 dark:text-yellow-300 p2 text-center>
Compare Group A
</div>
<OptionPackageMultiSelectInput v-model:selected="selectedA" :excludes="selectedAll">
Expand All @@ -76,7 +79,8 @@ function reset() {
vs
</div>
<div>
<div text-purple p2 text-center op75>
<!-- See the note on "Compare Group A" above. -->
<div text-purple-700 dark:text-purple-300 p2 text-center>
Compare Group B
</div>
<OptionPackageMultiSelectInput v-model:selected="selectedB" :excludes="selectedAll">
Expand Down
18 changes: 12 additions & 6 deletions packages/node-modules-inspector/src/app/pages/grid/[...grid].vue
Original file line number Diff line number Diff line change
Expand Up @@ -155,27 +155,33 @@ const groups = computed<Group[]>(() => {
<div op-fade>
Group by
</div>
<NuxtLink btn-action as="button" :to="{ path: '/grid/depth', hash: location.hash }" active-class="text-primary bg-primary:5">
<!-- Two axe-core-measured contrast fixes on every active tab below:
`btn-action` always applies `op75`, so `op100!` is needed to stop
the active color being diluted toward its 5%-tint background; and
the plain `text-primary` DEFAULT shade is tuned for its own solid
swatch, not for sitting on that same near-white/near-black tint, so
`-700`/`dark:-300` (matching `PackageBorder.vue`) replace it. -->
<NuxtLink btn-action as="button" :to="{ path: '/grid/depth', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-stack-simple-duotone />
Depth
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/grid/module-type', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/grid/module-type', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-file-code-duotone />
Module Type
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/grid/clusters', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/grid/clusters', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-exclude-duotone />
Clusters
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/grid/authors', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/grid/authors', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-user-circle-duotone />
Authors
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/grid/licenses', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/grid/licenses', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-file-text-duotone />
License
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/grid/provenance', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/grid/provenance', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph:circle-wavy-check-duotone />
Provenance
</NuxtLink>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,54 @@ const selected = computed(() => params.report?.[0] || 'all')

<template>
<div flex="~ gap-2 items-center wrap">
<NuxtLink btn-action as="button" :to="{ path: '/report', hash: location.hash }" exact-active-class="text-primary bg-primary:5">
<!-- Two axe-core-measured contrast fixes on every active tab below:
`btn-action` always applies `op75`, so `op100!` is needed to stop the
active color being diluted toward its 5%-tint background; and the
plain `text-*`/DEFAULT shade is tuned for its own solid swatch, not
for sitting on that same near-white/near-black tint, so `-700` /
`dark:-300` (matching the shades used elsewhere, e.g. `PackageBorder.vue`)
replace it. -->
<NuxtLink btn-action as="button" :to="{ path: '/report', hash: location.hash }" exact-active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-grid-four-duotone />
All
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/funding', hash: location.hash }" active-class="text-rose bg-rose:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/funding', hash: location.hash }" active-class="text-rose-700 dark:text-rose-300 bg-rose:5 op100!">
<div i-ph-heart-duotone />
Funding
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/dependencies', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/dependencies', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-link-simple-duotone />
Dependencies
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/deprecated', hash: location.hash }" active-class="text-red bg-red:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/deprecated', hash: location.hash }" active-class="text-red-700 dark:text-red-300 bg-red:5 op100!">
<div i-ph-warning-duotone />
Deprecated
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/vulnerabilities', hash: location.hash }" active-class="text-red bg-red:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/vulnerabilities', hash: location.hash }" active-class="text-red-700 dark:text-red-300 bg-red:5 op100!">
<div i-ph-warning-duotone />
Vulnerabilities
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/multiple-versions', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/multiple-versions', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-copy-duotone />
Multiple Versions
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/maintainer-actions', hash: location.hash }" active-class="text-amber bg-amber:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/maintainer-actions', hash: location.hash }" active-class="text-amber-700 dark:text-amber-300 bg-amber:5 op100!">
<div i-ph-pipe-wrench-duotone />
Maintainer Actions
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/install-size', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/install-size', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-package-duotone />
Install Size
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/time', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/time', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-clock-duotone />
Publish Time
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/node-engines', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/node-engines', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-hexagon-duotone />
Node Engines
</NuxtLink>
<NuxtLink btn-action as="button" :to="{ path: '/report/licenses', hash: location.hash }" active-class="text-primary bg-primary:5">
<NuxtLink btn-action as="button" :to="{ path: '/report/licenses', hash: location.hash }" active-class="text-primary-700 dark:text-primary-300 bg-primary:5 op100!">
<div i-ph-scales-duotone />
Licenses
</NuxtLink>
Expand Down
17 changes: 13 additions & 4 deletions packages/node-modules-inspector/src/app/utils/color.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Color from 'colorjs.io'
import { isDark } from '../composables/dark'

export function getHashColorFromString(
Expand All @@ -7,15 +8,23 @@ export function getHashColorFromString(
let hash = 0
for (let i = 0; i < name.length; i++)
hash = name.charCodeAt(i) + ((hash << 5) - hash)
const h = hash % 360
const h = ((hash % 360) + 360) % 360
return getHsla(h, opacity)
}

// Named `getHsla` for backwards compatibility, but renders OKLCH, not HSL.
// HSL's lightness isn't perceptually uniform across hues — a fixed L=40%
// looks near-black for blue/purple but near-white for yellow/green, so the
// old `hsla(hue, 65%, 40%)` formula swung from ~7.7:1 down to ~2:1 contrast
// against the page background depending purely on hue (axe-core-measured on
// ClusterBadge cluster-name tooltips). OKLCH's L is roughly perceptually
// uniform, so a single L holds ~7:1+ minimum contrast across the full hue
// wheel in both modes.
export function getHsla(
hue: number,
opacity: number | string = 1,
) {
const saturation = isDark.value ? 50 : 65
const lightness = isDark.value ? 60 : 40
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${opacity})`
const lightness = isDark.value ? 0.75 : 0.42
const chroma = 0.15
return new Color('oklch', [lightness, chroma, hue], Number(opacity)).toString({ precision: 4 })
}
Loading
Loading