feat(website): gated coming-soon holding page with email signup
Add a standalone bilingual /coming-soon page (branded, dark, email signup via mailto:info@dezky.eu, fires a waitlist-signup Umami event, noindex) plus a global middleware that redirects every route to the locale-correct holding page while NUXT_PUBLIC_COMING_SOON=true. - Env-gated (default off, so dev and the live site are unaffected); flip the env in Coolify to show/hide the holding page with no code change. - Preview the real site behind the gate via ?preview=<previewToken> (NUXT_PUBLIC_PREVIEW_TOKEN), which sets a 7-day cookie. - Locale-preserving redirects (/da/* -> /da/coming-soon), no loops.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
// Gated "coming soon" holding page. Shown for every route while
|
||||
// NUXT_PUBLIC_COMING_SOON=true (see middleware/coming-soon.global.ts). Interim
|
||||
// signup composes an email to info@dezky.eu — swap for a real list later.
|
||||
import { ref } from 'vue'
|
||||
import { C } from '~/utils/landingTokens'
|
||||
import { useCopy, useLang, track } from '~/composables/useLanding'
|
||||
|
||||
definePageMeta({ layout: false })
|
||||
|
||||
const copy = useCopy()
|
||||
const lang = useLang()
|
||||
const c = computed(() => copy.value.waitlist)
|
||||
const switchLocalePath = useSwitchLocalePath()
|
||||
|
||||
const email = ref('')
|
||||
function submit() {
|
||||
const subject = lang.value === 'da' ? 'Skriv mig op til dezky' : 'Add me to the dezky waitlist'
|
||||
const body = lang.value === 'da'
|
||||
? `Skriv mig op til ventelisten: ${email.value}`
|
||||
: `Please add me to the waitlist: ${email.value}`
|
||||
track('waitlist-signup')
|
||||
window.location.href = `mailto:info@dezky.eu?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`
|
||||
}
|
||||
|
||||
function segStyle(code: 'en' | 'da') {
|
||||
const active = lang.value === code
|
||||
return {
|
||||
padding: '5px 9px', border: 'none', cursor: 'pointer', borderRadius: '3px',
|
||||
fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', letterSpacing: '0.08em',
|
||||
background: active ? C.bone : 'transparent',
|
||||
color: active ? C.carbon : 'rgba(244,243,238,0.6)',
|
||||
fontWeight: active ? 700 : 500,
|
||||
}
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: () => (lang.value === 'da' ? 'dezky — snart klar' : 'dezky — coming soon'),
|
||||
meta: [{ name: 'robots', content: 'noindex, nofollow' }],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="{ minHeight: '100vh', background: C.carbon, color: C.bone, display: 'flex', flexDirection: 'column', padding: 'clamp(24px, 5vw, 56px)' }">
|
||||
<!-- Top bar: wordmark + language switcher -->
|
||||
<div :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }">
|
||||
<div :style="{ display: 'flex', alignItems: 'center', gap: '12px' }">
|
||||
<BrandNodeMark :size="32" :fg="C.signal" :accent="C.carbon" />
|
||||
<span :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontWeight: 600, fontSize: '16px', letterSpacing: '-0.02em' }">dezky</span>
|
||||
</div>
|
||||
<div :style="{ display: 'inline-flex', alignItems: 'center', gap: '2px', border: '1px solid rgba(244,243,238,0.2)', borderRadius: '5px', padding: '2px' }">
|
||||
<NuxtLink :to="switchLocalePath('en')" :style="segStyle('en')">EN</NuxtLink>
|
||||
<NuxtLink :to="switchLocalePath('da')" :style="segStyle('da')">DA</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Center -->
|
||||
<div :style="{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', maxWidth: '620px' }">
|
||||
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', letterSpacing: '0.18em', textTransform: 'uppercase', color: C.signal }">{{ c.eyebrow }}</div>
|
||||
<h1 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(40px, 8vw, 84px)', letterSpacing: '-0.04em', lineHeight: 0.98, margin: '20px 0 0', textWrap: 'balance' }">{{ c.heading }}</h1>
|
||||
<p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: 'clamp(16px, 2.4vw, 19px)', lineHeight: 1.55, color: 'rgba(244,243,238,0.72)', margin: '28px 0 0', textWrap: 'pretty' }">{{ c.body }}</p>
|
||||
|
||||
<form @submit.prevent="submit" :style="{ display: 'flex', gap: '12px', marginTop: '40px', flexWrap: 'wrap', maxWidth: '480px' }">
|
||||
<input
|
||||
v-model="email" type="email" required :placeholder="c.emailPh"
|
||||
:style="{ flex: 1, minWidth: '220px', padding: '15px 18px', borderRadius: '4px', border: '1px solid rgba(244,243,238,0.2)', background: 'rgba(244,243,238,0.05)', color: C.bone, fontFamily: '\'Inter\', sans-serif', fontSize: '15px', boxSizing: 'border-box' }"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
:style="{ background: C.signal, color: C.carbon, border: 'none', padding: '15px 26px', fontFamily: '\'Inter\', sans-serif', fontSize: '15px', fontWeight: 600, borderRadius: '4px', cursor: 'pointer', whiteSpace: 'nowrap' }"
|
||||
>{{ c.submit }} →</button>
|
||||
</form>
|
||||
<p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '13px', color: 'rgba(244,243,238,0.45)', margin: '16px 0 0' }">{{ c.note }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: 'rgba(244,243,238,0.4)' }">dezky.eu · info@dezky.eu</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user