Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
314 changes: 314 additions & 0 deletions app/pages/vacations.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
<script setup lang="ts">
definePageMeta({
name: 'vacations',
})

useSeoMeta({
title: () => `${$t('vacations.title')} - npmx`,
description: () => $t('vacations.meta_description'),
ogTitle: () => `${$t('vacations.title')} - npmx`,
ogDescription: () => $t('vacations.meta_description'),
twitterTitle: () => `${$t('vacations.title')} - npmx`,
twitterDescription: () => $t('vacations.meta_description'),
})

defineOgImageComponent('Default', {
title: () => $t('vacations.title'),
description: () => $t('vacations.meta_description'),
})

const router = useRouter()
const canGoBack = useCanGoBack()

// --- Cosy fireplace easter egg ---
const logClicks = ref(0)
const fireVisible = ref(false)
function pokeLog() {
logClicks.value++
if (logClicks.value >= 3) {
fireVisible.value = true
}
}

// Icons that tile across the banner, repeating to fill.
// Classes must be written out statically so UnoCSS can detect them at build time.
const icons = [
'i-carbon:snowflake',
'i-carbon:mountain',
'i-carbon:tree',
'i-carbon:cafe',
'i-carbon:book',
'i-carbon:music',
'i-carbon:snowflake',
'i-carbon:star',
'i-carbon:moon',
] as const

// --- .ics calendar reminder ---
// Pick a random daytime hour (9–17) in the user's local timezone on Feb 22
// so reminders are staggered and people don't all flood in at once.
function downloadIcs() {
const hour = 9 + Math.floor(Math.random() * 9) // 9..17
const start = new Date(2026, 1, 22, hour, 0, 0) // month is 0-indexed
const end = new Date(2026, 1, 22, hour + 1, 0, 0)

// Format as UTC for the .ics file
const fmt = (d: Date) =>
d
.toISOString()
.replace(/[-:]/g, '')
.replace(/\.\d{3}/, '')

const uid = `npmx-vacations-${start.getTime()}@npmx.dev`

const ics = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//npmx//vacations//EN',
'BEGIN:VEVENT',
`DTSTART:${fmt(start)}`,
`DTEND:${fmt(end)}`,
`SUMMARY:npmx Discord is back!`,
`DESCRIPTION:The npmx team is back from vacation. Time to rejoin! https://chat.npmx.dev`,
'STATUS:CONFIRMED',
`UID:${uid}`,
'END:VEVENT',
'END:VCALENDAR',
].join('\r\n')

const blob = new Blob([ics], { type: 'text/calendar;charset=utf-8' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'npmx-discord-reminder.ics'
a.click()
URL.revokeObjectURL(url)
}

const stats = {
weeks: '4',
contributors: '160+',
commits: '1.1k+',
pr: '900+',
}
Comment thread
userquin marked this conversation as resolved.
Outdated
</script>

<template>
<main class="container flex-1 py-12 sm:py-16 overflow-x-hidden max-w-full">
<article class="max-w-2xl mx-auto">
<header class="mb-12">
<div class="max-w-2xl mx-auto py-8 bg-none flex justify-center">
<!-- Icon / Illustration -->
<div class="relative inline-block">
<div class="absolute inset-0 bg-accent/20 blur-3xl rounded-full" aria-hidden="true" />
<span class="relative text-8xl sm:text-9xl animate-bounce-slow inline-block">🏖️</span>
</div>
</div>
<div class="flex items-baseline justify-between gap-4 mb-4">
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
{{ $t('vacations.heading') }}
</h1>
<button
type="button"
class="cursor-pointer inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
@click="router.back()"
v-if="canGoBack"
>
<span class="i-carbon:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
<span class="sr-only sm:not-sr-only">{{ $t('nav.back') }}</span>
</button>
</div>
<i18n-t
keypath="vacations.subtitle"
tag="p"
scope="global"
class="text-fg-muted text-lg sm:text-xl"
>
<template #weeks>
{{ $t('vacations.stats.subtitle.weeks', [stats.weeks]) }}
</template>
<template #contributors>
{{ $t('vacations.stats.subtitle.contributors', [stats.contributors]) }}
</template>
<template #commits>
{{ $t('vacations.stats.subtitle.commits', [stats.commits]) }}
</template>
<template #npmx>
<strong>npmx</strong>
</template>
</i18n-t>
</header>

<div
class="grid grid-cols-3 justify-center gap-4 sm:gap-8 mb-8 py-8 border-y border-border/50"
>
<div class="space-y-1 text-center">
<div class="font-mono text-2xl sm:text-3xl font-bold text-fg">
{{ stats.contributors }}
</div>
<div class="text-xs sm:text-sm text-fg-subtle uppercase tracking-wider">
{{ $t('vacations.stats.contributors') }}
</div>
</div>
<div class="space-y-1 text-center">
<div class="font-mono text-2xl sm:text-3xl font-bold text-fg">{{ stats.commits }}</div>
<div class="text-xs sm:text-sm text-fg-subtle uppercase tracking-wider">
{{ $t('vacations.stats.commits') }}
</div>
</div>
<div class="space-y-1 text-center">
<div class="font-mono text-2xl sm:text-3xl font-bold text-fg">{{ stats.pr }}</div>
<div class="text-xs sm:text-sm text-fg-subtle uppercase tracking-wider">
{{ $t('vacations.stats.pr') }}
</div>
</div>
</div>

<!-- Icon banner — a single row of cosy icons, clipped to fill width -->
<div
class="relative mb-12 px-4 border border-border rounded-lg bg-bg-subtle overflow-hidden select-none"
:aria-label="$t('vacations.illustration_alt')"
role="img"
>
<div class="flex items-center gap-4 sm:gap-5 py-3 sm:py-4 w-max">
<template v-for="n in 4" :key="`set-${n}`">
<!-- Campsite icon — click it 3x to light the fire -->
<button
type="button"
class="relative shrink-0 cursor-pointer rounded transition-transform duration-200 hover:scale-110 focus-visible:outline-accent/70 w-5 h-5 sm:w-6 sm:h-6"
:aria-label="$t('vacations.poke_log')"
@click="pokeLog"
>
<span
class="absolute inset-0 i-carbon:fire w-5 h-5 sm:w-6 sm:h-6 text-orange-400 transition-opacity duration-400"
:class="fireVisible ? 'opacity-100' : 'opacity-0'"
/>
<span
class="absolute inset-0 i-carbon:campsite w-5 h-5 sm:w-6 sm:h-6 transition-colors duration-400"
:class="fireVisible ? 'text-amber-700' : ''"
/>
</button>
<span
v-for="(icon, i) in icons"
:key="`${n}-${i}`"
class="shrink-0 w-5 h-5 sm:w-6 sm:h-6 opacity-40"
:class="icon"
/>
</template>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
</div>
</div>

<section class="prose prose-invert max-w-none space-y-8">
<div>
<p class="text-fg-muted leading-relaxed mb-4">
<i18n-t keypath="vacations.intro.p1" tag="span" scope="global">
<template #some>
<span class="line-through decoration-fg">{{ $t('vacations.intro.some') }}</span>
{{ ' ' }}
<strong class="text-fg">{{ $t('vacations.intro.all') }}</strong>
</template>
</i18n-t>
</p>
<p class="text-fg-muted leading-relaxed">
{{ $t('vacations.intro.p2') }}
</p>
</div>

<!-- What's happening -->
<div>
<h2 class="text-lg text-fg-subtle uppercase tracking-wider mb-4">
{{ $t('vacations.what.title') }}
</h2>
<p class="text-fg-muted leading-relaxed mb-4">
<i18n-t keypath="vacations.what.p1" tag="span" scope="global">
<template #dates>
<strong class="text-fg">{{ $t('vacations.what.dates') }}</strong>
</template>
</i18n-t>
</p>
<ul class="space-y-3 text-fg-muted list-none p-0">
<li class="flex items-start gap-3">
<span class="text-fg-subtle shrink-0 mt-1">&mdash;</span>
<span>
<i18n-t keypath="vacations.what.discord" tag="span" scope="global">
<template #garden>
<code class="font-mono text-fg text-sm">{{ $t('vacations.what.garden') }}</code>
</template>
</i18n-t>
</span>
</li>
<li class="flex items-start gap-3">
<span class="text-fg-subtle shrink-0 mt-1">&mdash;</span>
<span>{{ $t('vacations.what.site') }}</span>
</li>
<li class="flex items-start gap-3">
<span class="text-fg-subtle shrink-0 mt-1">&mdash;</span>
<span>{{ $t('vacations.what.repo') }}</span>
</li>
</ul>
</div>

<!-- In the meantime -->
<div>
<h2 class="text-lg text-fg-subtle uppercase tracking-wider mb-4">
{{ $t('vacations.meantime.title') }}
</h2>
<p class="text-fg-muted leading-relaxed mb-4">
<i18n-t keypath="vacations.meantime.p1" tag="span" scope="global">
<template #repo>
<LinkBase to="https://repo.npmx.dev">
{{ $t('vacations.meantime.repo_link') }}
</LinkBase>
</template>
</i18n-t>
</p>
<p class="text-fg-muted leading-relaxed">
{{ $t('vacations.meantime.p2') }}
</p>
</div>

<!-- See you soon -->
<div>
<h2 class="text-lg text-fg-subtle uppercase tracking-wider mb-4">
{{ $t('vacations.return.title') }}
</h2>
<p class="text-fg-muted leading-relaxed mb-4">
{{ $t('vacations.return.p1') }}
</p>
<p class="text-fg-muted leading-relaxed mb-6">
<i18n-t keypath="vacations.return.p2" tag="span" scope="global">
<template #social>
<LinkBase to="https://social.npmx.dev">
{{ $t('vacations.return.social_link') }}
</LinkBase>
</template>
</i18n-t>
</p>

<!-- Add to calendar button -->
<ButtonBase classicon="i-carbon:calendar" @click="downloadIcs">
{{ $t('vacations.return.add_to_calendar') }}
</ButtonBase>
</div>
</section>
</article>
</main>
</template>

<style scoped>
.animate-bounce-slow {
animation: bounce 3s infinite;
}

@keyframes bounce {
0%,
100% {
transform: translateY(-5%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: translateY(0);
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}
</style>
46 changes: 46 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1109,5 +1109,51 @@
"p1": "If you encounter an accessibility barrier on {app}, please let us know by opening an issue on our {link}. We take these reports seriously and will do our best to address them.",
"link": "GitHub repository"
}
},
"vacations": {
"title": "on vacation",
"meta_description": "The npmx team is recharging. Discord reopens in a week.",
"heading": "recharging",
"subtitle": "after {weeks} of intense coding, {contributors}, and over {commits}, the {npmx} team is taking a short break, because it's a marathon, not a sprint.",
"illustration_alt": "a single row of cosy icons",
"poke_log": "Poke the campfire",
"intro": {
"p1": "We've been building npmx at a pace that has cost {some} of us sleep. That's not sustainable – and we don't want it to be the norm. So we're doing something about it: all of us are taking a week off. Together.",
"some": "some",
"all": "all",
"p2": "Go outside. Touch grass – or snow. Finish that book you started before npmx took over your evenings. Visit someone you've been meaning to visit. Do something that has nothing to do with code. We'll all be better for it."
},
"what": {
"title": "what's happening",
"p1": "Discord is closed {dates}.",
"dates": "February 14 – 21",
"discord": "All invite links are gone and channels are locked – except {garden}, which stays open for folks already there who want to keep hanging out.",
"garden": "#garden",
"site": "npmx.dev is still up. Browse packages as usual.",
"repo": "The repo is open. Issues and PRs are welcome – but expect us to be hands-off until we're back."
},
"meantime": {
"title": "in the meantime",
"p1": "You're more than welcome to keep poking around while we're away. The codebase is on {repo} – dig in, file issues, open PRs. We'll get to everything when we return.",
"repo_link": "GitHub",
"p2": "Just don't expect a fast review. We'll be somewhere near a cosy fireplace."
},
"return": {
"title": "see you soon",
"p1": "We'll come back recharged and ready for the final push to March 3rd. Things are going to get even more interesting after that – so let's make sure we're taking care of each other first.",
"p2": "Follow us {social} if you want updates in the meantime.",
"social_link": "on Bluesky",
"add_to_calendar": "remind me when Discord reopens"
},
"stats": {
"contributors": "Contributors",
"commits": "Commits",
"pr": "PRs Merged",
"subtitle": {
"weeks": "{0} weeks",
"commits": "{0} commits",
"contributors": "{0} contributors"
}
}
}
}
Loading
Loading