Skip to content

Commit f45b326

Browse files
authored
Merge branch 'main' into chore/use-dedupePeers
2 parents 604b444 + cfbc6ef commit f45b326

34 files changed

+689
-98
lines changed

app/components/CollapsibleSection.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,13 @@ function toggle() {
6262
}
6363
6464
const ariaLabel = computed(() => {
65-
const action = isOpen.value ? 'Collapse' : 'Expand'
66-
return props.title ? `${action} ${props.title}` : action
65+
if (!props.title) {
66+
return isOpen.value ? $t('common.collapse') : $t('common.expand')
67+
}
68+
69+
return isOpen.value
70+
? $t('common.collapse_with_name', { name: props.title })
71+
: $t('common.expand_with_name', { name: props.title })
6772
})
6873
useHead({
6974
style: [

app/components/ColorScheme/Img.vue

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script setup lang="ts">
2+
const props = defineProps<{
3+
lightSrc: string
4+
darkSrc: string
5+
}>()
6+
</script>
7+
8+
<template>
9+
<img
10+
:src="props.darkSrc"
11+
class="color-mode-img"
12+
:style="`--light-src: url('${props.lightSrc}')`"
13+
/>
14+
</template>
15+
16+
<style>
17+
.light .color-mode-img {
18+
content: var(--light-src);
19+
}
20+
</style>

app/components/Compare/FacetQuadrantChart.vue

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,52 @@ const config = computed<VueUiQuadrantConfig>(() => {
383383
})
384384
"
385385
/>
386+
387+
<foreignObject :x="0" :y="30" style="overflow: visible" :width="svg.width" :height="12">
388+
<div class="flex items-center justify-center gap-2">
389+
<TooltipApp interactive>
390+
<button
391+
data-dom-to-png-ignore
392+
type="button"
393+
class="i-lucide:info w-3.5 h-3.5 text-fg-muted cursor-help"
394+
:aria-label="$t('compare.quadrant_chart.explanation.tooltip_help_efficiency')"
395+
/>
396+
<template #content>
397+
<div class="flex flex-col gap-3">
398+
<p class="text-xs text-fg-muted">
399+
{{ $t('compare.quadrant_chart.explanation.efficiency') }}
400+
</p>
401+
</div>
402+
</template>
403+
</TooltipApp>
404+
</div>
405+
</foreignObject>
406+
407+
<foreignObject
408+
:x="svg.width - 40"
409+
:y="svg.height / 2 - 8"
410+
style="overflow: visible"
411+
:width="20"
412+
:height="12"
413+
>
414+
<div class="flex items-center justify-center gap-2">
415+
<TooltipApp interactive>
416+
<button
417+
data-dom-to-png-ignore
418+
type="button"
419+
class="i-lucide:info w-3.5 h-3.5 text-fg-muted cursor-help"
420+
:aria-label="$t('compare.quadrant_chart.explanation.tooltip_help_adoption')"
421+
/>
422+
<template #content>
423+
<div class="flex flex-col gap-3">
424+
<p class="text-xs text-fg-muted">
425+
{{ $t('compare.quadrant_chart.explanation.adoption') }}
426+
</p>
427+
</div>
428+
</template>
429+
</TooltipApp>
430+
</div>
431+
</foreignObject>
386432
</template>
387433

388434
<template #menuIcon="{ isOpen }">
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<script setup lang="ts">
2+
import { ACTIVE_NOODLES, PERMANENT_NOODLES, type Noodle } from '../Noodle'
3+
4+
const { env } = useAppConfig().buildInfo
5+
6+
const activeNoodlesData = ACTIVE_NOODLES.map(noodle => ({
7+
key: noodle.key,
8+
date: noodle.date,
9+
dateTo: noodle.dateTo,
10+
timezone: noodle.timezone,
11+
tagline: noodle.tagline,
12+
}))
13+
14+
const permanentNoodlesData = PERMANENT_NOODLES.map(noodle => ({
15+
key: noodle.key,
16+
tagline: noodle.tagline,
17+
}))
18+
19+
onPrehydrate(el => {
20+
const tagline = el.querySelector<HTMLElement>('#intro-header-tagline')
21+
const defaultLogo = el.querySelector<HTMLElement>('#intro-header-noodle-default')
22+
23+
if (!tagline || !defaultLogo) return
24+
25+
let permanentNoodles
26+
try {
27+
permanentNoodles = JSON.parse(el.dataset.permanentNoodles as string) as Noodle[]
28+
} catch {
29+
return
30+
}
31+
const activePermanentNoodle = permanentNoodles?.find(noodle =>
32+
new URLSearchParams(window.location.search).has(noodle.key),
33+
)
34+
35+
if (activePermanentNoodle) {
36+
const permanentNoodleLogo = el.querySelector<HTMLElement>(
37+
`#intro-header-noodle-${activePermanentNoodle.key}`,
38+
)
39+
40+
if (!permanentNoodleLogo) return
41+
42+
permanentNoodleLogo.style.display = 'block'
43+
defaultLogo.style.display = 'none'
44+
if (activePermanentNoodle.tagline === false) {
45+
tagline.style.display = 'none'
46+
}
47+
return
48+
}
49+
50+
let activeNoodles
51+
try {
52+
activeNoodles = JSON.parse(el.dataset.activeNoodles as string) as Noodle[]
53+
} catch {
54+
return
55+
}
56+
57+
const currentActiveNoodles = activeNoodles.filter(noodle => {
58+
const todayDate = new Date()
59+
const todayDateRaw = new Intl.DateTimeFormat('en-US', {
60+
timeZone: noodle.timezone === 'auto' ? undefined : noodle.timezone,
61+
month: '2-digit',
62+
day: '2-digit',
63+
year: 'numeric',
64+
}).format(todayDate)
65+
66+
const noodleDateFrom = new Date(noodle.date!)
67+
if (!noodle.dateTo) {
68+
const noodleDateFromRaw = new Intl.DateTimeFormat('en-US', {
69+
timeZone: noodle.timezone === 'auto' ? undefined : noodle.timezone,
70+
month: '2-digit',
71+
day: '2-digit',
72+
year: 'numeric',
73+
}).format(noodleDateFrom)
74+
return todayDateRaw === noodleDateFromRaw
75+
}
76+
const noodleDateTo = new Date(noodle.dateTo!)
77+
return todayDate >= noodleDateFrom && todayDate <= noodleDateTo
78+
})
79+
80+
if (!currentActiveNoodles.length) return
81+
82+
const roll = Math.floor(Math.random() * currentActiveNoodles.length)
83+
const selectedNoodle = currentActiveNoodles[roll]
84+
85+
if (!selectedNoodle) return
86+
87+
const noodleLogo = el.querySelector<HTMLElement>(`#intro-header-noodle-${selectedNoodle.key}`)
88+
89+
if (!defaultLogo || !noodleLogo || !tagline) return
90+
91+
defaultLogo.style.display = 'none'
92+
noodleLogo.style.display = 'block'
93+
if (selectedNoodle.tagline === false) {
94+
tagline.style.display = 'none'
95+
}
96+
})
97+
</script>
98+
99+
<template>
100+
<div
101+
:data-active-noodles="JSON.stringify(activeNoodlesData)"
102+
:data-permanent-noodles="JSON.stringify(permanentNoodlesData)"
103+
>
104+
<h1 class="sr-only">
105+
{{ $t('alt_logo') }}
106+
</h1>
107+
<div
108+
id="intro-header-noodle-default"
109+
class="relative mb-6 w-fit mx-auto motion-safe:animate-fade-in motion-safe:animate-fill-both"
110+
aria-hidden="true"
111+
>
112+
<AppLogo id="npmx-index-h1-logo-normal" class="w-42 h-auto sm:w-58 md:w-70" />
113+
<span
114+
id="npmx-index-h1-logo-env"
115+
class="text-sm sm:text-base md:text-lg transform-origin-br font-mono tracking-widest text-accent absolute -bottom-4 -inset-ie-1.5"
116+
>
117+
{{ env === 'release' ? 'alpha' : env }}
118+
</span>
119+
</div>
120+
<component
121+
v-for="noodle in PERMANENT_NOODLES"
122+
:key="noodle.key"
123+
:id="`intro-header-noodle-${noodle.key}`"
124+
class="hidden"
125+
aria-hidden="true"
126+
:is="noodle.logo"
127+
/>
128+
<component
129+
v-for="noodle in ACTIVE_NOODLES"
130+
:key="noodle.key"
131+
:id="`intro-header-noodle-${noodle.key}`"
132+
class="hidden"
133+
aria-hidden="true"
134+
:is="noodle.logo"
135+
/>
136+
<p
137+
id="intro-header-tagline"
138+
class="text-fg-muted text-lg sm:text-xl max-w-xl mb-12 lg:mb-14 motion-safe:animate-slide-up motion-safe:animate-fill-both delay-100"
139+
>
140+
{{ $t('tagline') }}
141+
</p>
142+
</div>
143+
</template>

app/components/LandingLogo.vue

Lines changed: 0 additions & 76 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<div>
3+
<ColorSchemeImg
4+
width="400"
5+
class="mb-8 motion-safe:animate-fade-in motion-safe:animate-scale-in w-80 sm:w-100"
6+
dark-src="/extra/npmx-dark-artemis.svg"
7+
light-src="/extra/npmx-light-artemis.svg"
8+
alt=""
9+
/>
10+
<ColorSchemeImg
11+
width="1440"
12+
height="455"
13+
class="absolute bottom-0 inset-is-0 w-full h-auto mix-blend-lighten light:mix-blend-darken motion-safe:animate-fade-in"
14+
dark-src="/extra/moon-dark.png"
15+
light-src="/extra/moon-light.png"
16+
alt=""
17+
/>
18+
</div>
19+
</template>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<template>
2+
<img
3+
width="400"
4+
class="mb-8 motion-safe:animate-fade-in motion-safe:animate-scale-in motion-safe:hover:scale-105 motion-safe:transition w-80 sm:w-100"
5+
src="/extra/npmx-cute.svg"
6+
:alt="$t('alt_logo_kawaii')"
7+
/>
8+
</template>

app/components/Noodle/index.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import NoodleKawaiiLogo from './Kawaii/Logo.vue'
2+
import NoodleArtemisLogo from './Artemis/Logo.vue'
3+
// import NoodleTkawaiiLogo from './Tkawaii/Logo.vue'
4+
5+
export type Noodle = {
6+
// Unique identifier for the noodle
7+
key: string
8+
// Timezone for the noodle (default is auto, i.e. user's timezone)
9+
timezone?: string
10+
// Date for the noodle
11+
date?: string
12+
// Date to for the noodle
13+
dateTo?: string
14+
// Logo for the noodle - could be any component. Relative parent - intro section
15+
logo: Component
16+
// Show npmx tagline or not (default is true)
17+
tagline?: boolean
18+
}
19+
20+
// Archive noodles - might be shown on special page
21+
// export const ARCHIVE_NOODLES: Noodle[] = [
22+
// {
23+
// key: 'tkawaii',
24+
// date: '2026-04-08T12:00:00UTC',
25+
// timezone: 'auto',
26+
// logo: NoodleTkawaiiLogo,
27+
// tagline: false,
28+
// },
29+
// ]
30+
31+
// Permanent noodles - always shown on specific query param (e.g. ?kawaii)
32+
export const PERMANENT_NOODLES: Noodle[] = [
33+
{
34+
key: 'kawaii',
35+
logo: NoodleKawaiiLogo,
36+
tagline: false,
37+
},
38+
]
39+
40+
// Active noodles - shown based on date and timezone
41+
export const ACTIVE_NOODLES: Noodle[] = [
42+
{
43+
key: 'artemis',
44+
logo: NoodleArtemisLogo,
45+
date: '2026-04-08T12:00:00Z',
46+
dateTo: '2026-04-12T01:00:00Z',
47+
timezone: 'America/Los_Angeles',
48+
tagline: true,
49+
},
50+
]

0 commit comments

Comments
 (0)