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
20 changes: 20 additions & 0 deletions app/components/ColorScheme/Img.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script setup lang="ts">
const props = defineProps<{
lightSrc: string
darkSrc: string
}>()
</script>

<template>
<img
:src="props.darkSrc"
class="color-mode-img"
:style="`--light-src: url('${props.lightSrc}')`"
/>
</template>

<style>
.light .color-mode-img {
content: var(--light-src);
}
</style>
138 changes: 138 additions & 0 deletions app/components/Landing/IntroHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<script setup lang="ts">
import { ACTIVE_NOODLES, PERMANENT_NOODLES, type Noodle } from '../Noodle'

const { env } = useAppConfig().buildInfo

const activeNoodlesData = ACTIVE_NOODLES.map(noodle => ({
key: noodle.key,
date: noodle.date,
timezone: noodle.timezone,
tagline: noodle.tagline,
}))

const permanentNoodlesData = PERMANENT_NOODLES.map(noodle => ({
key: noodle.key,
tagline: noodle.tagline,
}))

onPrehydrate(el => {
const tagline = el.querySelector<HTMLElement>('#intro-header-tagline')
const defaultLogo = el.querySelector<HTMLElement>('#intro-header-noodle-default')

if (!tagline || !defaultLogo) return

let permanentNoodles
try {
permanentNoodles = JSON.parse(el.dataset.permanentNoodles as string) as Noodle[]
} catch {
return
}
const activePermanentNoodle = permanentNoodles?.find(noodle =>
new URLSearchParams(window.location.search).has(noodle.key),
)

if (activePermanentNoodle) {
const permanentNoodleLogo = el.querySelector<HTMLElement>(
`#intro-header-noodle-${activePermanentNoodle.key}`,
)

if (!permanentNoodleLogo) return

permanentNoodleLogo.style.display = 'block'
defaultLogo.style.display = 'none'
if (activePermanentNoodle.tagline === false) {
tagline.style.display = 'none'
}
return
}

let activeNoodles
try {
activeNoodles = JSON.parse(el.dataset.activeNoodles as string) as Noodle[]
} catch {
return
}

const currentActiveNoodles = activeNoodles.filter(noodle => {
const todayDate = new Intl.DateTimeFormat('en-US', {
timeZone: noodle.timezone === 'auto' ? undefined : noodle.timezone,
month: '2-digit',
day: '2-digit',
year: 'numeric',
}).format(new Date())

const noodleDate =
noodle.date &&
new Intl.DateTimeFormat('en-US', {
timeZone: noodle.timezone === 'auto' ? undefined : noodle.timezone,
month: '2-digit',
day: '2-digit',
year: 'numeric',
}).format(new Date(noodle.date))
return todayDate === noodleDate
})

if (!currentActiveNoodles.length) return

const roll = Math.floor(Math.random() * currentActiveNoodles.length)
const selectedNoodle = currentActiveNoodles[roll]

if (!selectedNoodle) return

const noodleLogo = el.querySelector<HTMLElement>(`#intro-header-noodle-${selectedNoodle.key}`)

if (!defaultLogo || !noodleLogo || !tagline) return

defaultLogo.style.display = 'none'
noodleLogo.style.display = 'block'
if (selectedNoodle.tagline === false) {
tagline.style.display = 'none'
}
})
</script>

<template>
<div
:data-active-noodles="JSON.stringify(activeNoodlesData)"
:data-permanent-noodles="JSON.stringify(permanentNoodlesData)"
>
<h1 class="sr-only">
{{ $t('alt_logo') }}
</h1>
<div
id="intro-header-noodle-default"
class="relative mb-6 w-fit mx-auto motion-safe:animate-fade-in motion-safe:animate-fill-both"
aria-hidden="true"
>
<AppLogo id="npmx-index-h1-logo-normal" class="w-42 h-auto sm:w-58 md:w-70" />
<span
id="npmx-index-h1-logo-env"
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"
>
{{ env === 'release' ? 'alpha' : env }}
</span>
</div>
<component
v-for="noodle in PERMANENT_NOODLES"
:key="noodle.key"
:id="`intro-header-noodle-${noodle.key}`"
class="hidden"
aria-hidden="true"
:is="noodle.logo"
/>
<component
v-for="noodle in ACTIVE_NOODLES"
:key="noodle.key"
:id="`intro-header-noodle-${noodle.key}`"
class="hidden"
aria-hidden="true"
:is="noodle.logo"
/>
<p
id="intro-header-tagline"
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"
>
{{ $t('tagline') }}
</p>
</div>
</template>
76 changes: 0 additions & 76 deletions app/components/LandingLogo.vue

This file was deleted.

19 changes: 19 additions & 0 deletions app/components/Noodle/Artemis/Logo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<div>
<ColorSchemeImg
width="400"
class="mb-8 motion-safe:animate-fade-in motion-safe:animate-scale-in w-80 sm:w-100"
dark-src="/extra/npmx-dark-artemis.svg"
light-src="/extra/npmx-light-artemis.svg"
alt=""
/>
<ColorSchemeImg
width="1440"
height="455"
class="absolute bottom-0 inset-is-0 w-full h-auto mix-blend-lighten light:mix-blend-darken motion-safe:animate-fade-in"
dark-src="/extra/moon-dark.png"
light-src="/extra/moon-light.png"
alt=""
/>
</div>
</template>
8 changes: 8 additions & 0 deletions app/components/Noodle/Kawaii/Logo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<img
width="400"
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"
src="/extra/npmx-cute.svg"
:alt="$t('alt_logo_kawaii')"
/>
</template>
47 changes: 47 additions & 0 deletions app/components/Noodle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import NoodleKawaiiLogo from './Kawaii/Logo.vue'
import NoodleArtemisLogo from './Artemis/Logo.vue'
// import NoodleTkawaiiLogo from './Tkawaii/Logo.vue'

export type Noodle = {
// Unique identifier for the noodle
key: string
// Timezone for the noodle (default is auto, i.e. user's timezone)
timezone?: string
// Date for the noodle
date?: string
// Logo for the noodle - could be any component. Relative parent - intro section
logo: Component
// Show npmx tagline or not (default is true)
tagline?: boolean
}

// Archive noodles - might be shown on special page
// export const ARCHIVE_NOODLES: Noodle[] = [
// {
// key: 'tkawaii',
// date: '2026-04-08T12:00:00UTC',
// timezone: 'auto',
// logo: NoodleTkawaiiLogo,
// tagline: false,
// },
// ]

// Permanent noodles - always shown on specific query param (e.g. ?kawaii)
export const PERMANENT_NOODLES: Noodle[] = [
{
key: 'kawaii',
logo: NoodleKawaiiLogo,
tagline: false,
},
]

// Active noodles - shown based on date and timezone
export const ACTIVE_NOODLES: Noodle[] = [
{
key: 'artemis',
logo: NoodleArtemisLogo,
date: '2026-04-08T12:00:00Z',
timezone: 'America/Los_Angeles',
tagline: true,
},
]
4 changes: 2 additions & 2 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ defineOgImageComponent('Default', {

<template>
<main>
<section class="container min-h-screen flex flex-col">
<section class="relative container min-h-screen flex flex-col overflow-hidden">
<header
class="flex-1 flex flex-col items-center justify-center text-center pt-20 pb-4 md:pb-8 lg:pb-20"
>
<LandingLogo class="w-42 h-auto sm:w-58 md:w-70" />
<LandingIntroHeader />
<search
class="w-full max-w-2xl motion-safe:animate-slide-up motion-safe:animate-fill-both"
style="animation-delay: 0.2s"
Expand Down
Binary file added public/extra/moon-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/extra/moon-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions public/extra/npmx-dark-artemis.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions public/extra/npmx-light-artemis.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 19 additions & 3 deletions test/nuxt/a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ import {
BlueskyPostEmbed,
BuildEnvironment,
ButtonBase,
LandingLogo,
LandingIntroHeader,
NoodleKawaiiLogo,
NoodleArtemisLogo,
LinkBase,
CallToAction,
CodeDirectoryListing,
Expand Down Expand Up @@ -354,9 +356,23 @@ describe('component accessibility audits', () => {
})
})

describe('LandingLogo', () => {
describe('LandingIntroHeader', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(LandingLogo)
const component = await mountSuspended(LandingIntroHeader)
const results = await runAxe(component)
expect(results.violations).toEqual([])
})
})

describe('Noodles', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(NoodleKawaiiLogo)
const results = await runAxe(component)
expect(results.violations).toEqual([])
})

it('should have no accessibility violations', async () => {
const component = await mountSuspended(NoodleArtemisLogo)
const results = await runAxe(component)
expect(results.violations).toEqual([])
})
Expand Down
Loading
Loading