Files
dezky/apps/website/pages/partners.vue
T
Ronni Baslund bd96ef0a33 feat(website): partner application form on /partners
Replace the bare partner CTA with a "Become a partner" section: a pitch
column (with a secondary "book a partner call" link to /demo) plus an
application form — name, company, work email, website, partnership type
(reseller / white-label / not sure) and message. On submit it composes a
prefilled email to info@dezky.eu (interim, no backend), to be swapped for
a real intake/CRM later. Bilingual and responsive.
2026-06-06 19:28:35 +02:00

310 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref } from 'vue'
import { useTheme, useCopy } from '~/composables/useLanding'
definePageMeta({ layout: 'page' })
const t = useTheme()
const copy = useCopy()
const c = computed(() => copy.value.pages.partners)
const openFaq = ref<number | null>(0)
function toggleFaq(i: number) {
openFaq.value = openFaq.value === i ? null : i
}
// Partner application form — interim: composes a prefilled email to
// info@dezky.eu (no backend). Swap for a real intake/CRM later.
const pName = ref('')
const pCompany = ref('')
const pEmail = ref('')
const pWebsite = ref('')
const pType = ref('')
const pMessage = ref('')
function submitPartner() {
const f = c.value.form
const subject = `Partner application — ${(pCompany.value || pName.value).trim()}`
const body = [
`${f.nameLabel}: ${pName.value}`,
`${f.companyLabel}: ${pCompany.value}`,
`${f.emailLabel}: ${pEmail.value}`,
`${f.websiteLabel}: ${pWebsite.value}`,
`${f.typeLabel}: ${pType.value}`,
'',
pMessage.value,
].join('\n')
window.location.href = `mailto:info@dezky.eu?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`
}
const formEyebrow = computed(() => ({
fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.value.fgDim,
letterSpacing: '0.12em', textTransform: 'uppercase' as const,
}))
const formH2 = computed(() => ({
fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(28px, 3.6vw, 44px)',
letterSpacing: '-0.03em', lineHeight: 1.05, margin: '14px 0 0', color: t.value.fg, textWrap: 'balance' as const,
}))
const fieldLabel = computed(() => ({
display: 'block', marginBottom: '8px', fontFamily: '\'JetBrains Mono\', monospace',
fontSize: '11px', color: t.value.fgMuted, letterSpacing: '0.06em', textTransform: 'uppercase' as const,
}))
const inputStyle = computed(() => ({
width: '100%', padding: '12px 14px', border: `1px solid ${t.value.border}`, borderRadius: '4px',
background: t.value.bg, color: t.value.fg, fontFamily: '\'Inter\', sans-serif', fontSize: '15px',
boxSizing: 'border-box' as const,
}))
useHead({ title: () => `${copy.value.pages.partners.label} · dezky` })
</script>
<template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<!-- What you get -->
<LandingContainer pad="clamp(40px, 6vw, 56px) clamp(20px, 5vw, 64px) 0">
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: '20px' }">{{ c.benefitsLabel }}</div>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }">
<div
v-for="(b, i) in c.benefits" :key="i"
:style="{
padding: '28px', background: t.surface,
borderTop: i > 1 ? `1px solid ${t.border}` : 'none',
borderLeft: i % 2 === 1 ? `1px solid ${t.border}` : 'none',
}"
>
<div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '20px', color: t.fg, letterSpacing: '-0.015em' }">{{ b[0] }}</div>
<p :style="{ marginTop: '10px', fontFamily: '\'Inter\', sans-serif', fontSize: '15px', lineHeight: 1.6, color: t.fgMuted, margin: '10px 0 0', textWrap: 'pretty' }">{{ b[1] }}</p>
</div>
</div>
</LandingContainer>
<!-- Margin calculator -->
<LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div class="partners-section-header">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(30px, 3.6vw, 48px)', letterSpacing: '-0.03em', lineHeight: 1.0, margin: 0, color: t.fg }">{{ c.calc.heading }}</h2>
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.calc.label }}</div>
</div>
<LandingPartnerCalculator />
<p :style="{ marginTop: '16px', fontFamily: '\'Inter\', sans-serif', fontSize: '13px', color: t.fgDim }">{{ c.calc.note }}</p>
</LandingContainer>
<!-- CSP vs Dezky comparison -->
<LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div class="partners-section-header">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(30px, 3.6vw, 48px)', letterSpacing: '-0.03em', lineHeight: 1.0, margin: 0, color: t.fg }">{{ c.compare.heading }}</h2>
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.compare.label }}</div>
</div>
<div :style="{ border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }">
<div class="partners-compare-grid partners-compare-header" :style="{ background: t.surface, borderBottom: `1px solid ${t.borderStrong}` }">
<div :style="{ padding: '18px 24px' }" />
<div :style="{ padding: '18px 24px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '13px', color: t.fgMuted }">{{ c.compare.cols[0] }}</div>
<div :style="{ padding: '18px 24px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '13px', fontWeight: 600, color: t.fg, background: `${t.signal}22` }">{{ c.compare.cols[1] }}</div>
</div>
<div
v-for="(row, i) in c.compare.rows" :key="i"
class="partners-compare-grid"
:style="{ borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, fontFamily: '\'Inter\', sans-serif', fontSize: '15px' }"
>
<div class="pc-cat" :style="{ padding: '18px 24px', color: t.fgMuted, fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', letterSpacing: '0.04em', textTransform: 'uppercase', alignSelf: 'center' }">{{ row[0] }}</div>
<div class="pc-val" :style="{ padding: '18px 24px', color: t.fgMuted }">
<span class="pc-key">{{ c.compare.cols[0] }}</span>{{ row[1] }}
</div>
<div class="pc-val" :style="{ padding: '18px 24px', color: t.fg, fontWeight: 600, background: `${t.signal}11` }">
<span class="pc-key">{{ c.compare.cols[1] }}</span>{{ row[2] }}
</div>
</div>
</div>
</LandingContainer>
<!-- Partner tiers -->
<LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div class="partners-section-header">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(30px, 3.6vw, 48px)', letterSpacing: '-0.03em', lineHeight: 1.0, margin: 0, color: t.fg }">{{ c.tiers.heading }}</h2>
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.tiers.label }}</div>
</div>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }">
<div
v-for="(tier, i) in c.tiers.items" :key="i"
:style="{ padding: '32px 28px', background: t.surface, borderLeft: i === 0 ? 'none' : `1px solid ${t.border}`, display: 'flex', flexDirection: 'column', gap: '20px' }"
>
<div>
<div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '20px', color: t.fg, letterSpacing: '-0.015em' }">{{ tier[0] }}</div>
<div :style="{ marginTop: '4px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.04em' }">{{ tier[1] }}</div>
</div>
<div :style="{ display: 'flex', alignItems: 'baseline', gap: '8px' }">
<span :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '40px', letterSpacing: '-0.03em', color: t.fg }">{{ tier[2] }}</span>
<span :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgMuted }">margin</span>
</div>
<div :style="{ display: 'flex', flexDirection: 'column', gap: '10px' }">
<div
v-for="(perk, j) in tier[3]" :key="j"
:style="{ display: 'flex', gap: '10px', fontFamily: '\'Inter\', sans-serif', fontSize: '14px', lineHeight: 1.45, color: t.fg }"
>
<span :style="{ color: t.signal, flexShrink: 0, fontWeight: 700 }"></span>
<span>{{ perk }}</span>
</div>
</div>
</div>
</div>
<p :style="{ marginTop: '16px', fontFamily: '\'Inter\', sans-serif', fontSize: '13px', color: t.fgDim }">{{ c.tiers.note }}</p>
</LandingContainer>
<!-- How to get started -->
<LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: '32px' }">{{ c.stepsLabel }}</div>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 220px), 1fr))', gap: '40px' }">
<div v-for="(step, i) in c.steps" :key="i" :style="{ paddingTop: '20px', borderTop: `1px solid ${t.borderStrong}` }">
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', color: t.fgDim, letterSpacing: '0.06em' }">step {{ step[0] }}</div>
<div :style="{ marginTop: '20px', fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '24px', color: t.fg, letterSpacing: '-0.02em' }">{{ step[1] }}</div>
<p :style="{ marginTop: '12px', fontFamily: '\'Inter\', sans-serif', fontSize: '15px', lineHeight: 1.6, color: t.fgMuted, textWrap: 'pretty' }">{{ step[2] }}</p>
</div>
</div>
</LandingContainer>
<!-- Partner FAQ -->
<LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div class="partners-section-header">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(30px, 3.6vw, 48px)', letterSpacing: '-0.03em', lineHeight: 1.0, margin: 0, color: t.fg }">{{ c.faq.heading }}</h2>
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.faq.label }}</div>
</div>
<div>
<div
v-for="(item, i) in c.faq.items" :key="i"
:style="{ borderTop: `1px solid ${t.border}`, borderBottom: i === c.faq.items.length - 1 ? `1px solid ${t.border}` : 'none' }"
>
<button
:style="{ width: '100%', background: 'transparent', border: 'none', padding: '24px 0', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '24px', cursor: 'pointer', textAlign: 'left' }"
@click="toggleFaq(i)"
>
<span :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '18px', color: t.fg, letterSpacing: '-0.015em' }">{{ item[0] }}</span>
<span :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '20px', color: t.fgMuted, flexShrink: 0 }">{{ openFaq === i ? '' : '+' }}</span>
</button>
<p v-if="openFaq === i" :style="{ margin: '0', padding: '0 0 24px', maxWidth: '720px', fontFamily: '\'Inter\', sans-serif', fontSize: '15px', lineHeight: 1.6, color: t.fgMuted, textWrap: 'pretty' }">{{ item[1] }}</p>
</div>
</div>
</LandingContainer>
<!-- Become a partner -->
<LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<div :style="{ borderTop: `1px solid ${t.borderStrong}`, paddingTop: 'clamp(40px, 5vw, 56px)' }">
<div class="partner-form-grid" :style="{ display: 'grid', gap: '48px', alignItems: 'start' }">
<!-- Pitch -->
<div>
<div :style="formEyebrow">{{ c.form.label }}</div>
<h2 :style="formH2">{{ c.form.heading }}</h2>
<p :style="{ marginTop: '20px', fontFamily: '\'Inter\', sans-serif', fontSize: '18px', lineHeight: 1.6, color: t.fgMuted, maxWidth: '420px', textWrap: 'pretty' }">{{ c.form.lede }}</p>
<button
:style="{ marginTop: '28px', background: 'transparent', border: 'none', padding: 0, cursor: 'pointer', fontFamily: '\'Inter\', sans-serif', fontSize: '15px', color: t.fgMuted, textDecoration: 'underline', textUnderlineOffset: '3px' }"
@click="navigateTo('/demo')"
>{{ c.cta }} </button>
</div>
<!-- Application form -->
<form
@submit.prevent="submitPartner"
:style="{ padding: 'clamp(24px, 4vw, 36px)', border: `1px solid ${t.border}`, borderRadius: '4px', background: t.surface, display: 'flex', flexDirection: 'column', gap: '18px' }"
>
<div>
<label :style="fieldLabel">{{ c.form.nameLabel }}</label>
<input v-model="pName" type="text" required :placeholder="c.form.namePh" :style="inputStyle">
</div>
<div>
<label :style="fieldLabel">{{ c.form.companyLabel }}</label>
<input v-model="pCompany" type="text" required :placeholder="c.form.companyPh" :style="inputStyle">
</div>
<div>
<label :style="fieldLabel">{{ c.form.emailLabel }}</label>
<input v-model="pEmail" type="email" required :placeholder="c.form.emailPh" :style="inputStyle">
</div>
<div>
<label :style="fieldLabel">{{ c.form.websiteLabel }}</label>
<input v-model="pWebsite" type="text" :placeholder="c.form.websitePh" :style="inputStyle">
</div>
<div>
<label :style="fieldLabel">{{ c.form.typeLabel }}</label>
<select v-model="pType" :style="{ ...inputStyle, appearance: 'none', cursor: 'pointer' }">
<option value="" disabled>{{ c.form.typePh }}</option>
<option v-for="ty in c.form.types" :key="ty" :value="ty">{{ ty }}</option>
</select>
</div>
<div>
<label :style="fieldLabel">{{ c.form.messageLabel }}</label>
<textarea v-model="pMessage" rows="3" :placeholder="c.form.messagePh" :style="{ ...inputStyle, resize: 'vertical' }" />
</div>
<button
type="submit"
:style="{ marginTop: '4px', background: t.signal, color: t.fg, border: 'none', padding: '16px 24px', fontFamily: '\'Inter\', sans-serif', fontSize: '15px', fontWeight: 600, borderRadius: '4px', cursor: 'pointer', letterSpacing: '-0.005em' }"
>{{ c.form.submit }} </button>
<p :style="{ margin: 0, fontFamily: '\'Inter\', sans-serif', fontSize: '13px', lineHeight: 1.5, color: t.fgDim }">{{ c.form.note }}</p>
</form>
</div>
</div>
</LandingContainer>
</template>
<style scoped>
/* Asymmetric 2-col section headers: heading + label side-by-side on desktop, stacked on mobile */
.partners-section-header {
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 40px;
align-items: end;
margin-bottom: 32px;
}
/* Asymmetric 3-col compare table: label + csp-col + dezky-col */
.partners-compare-grid {
display: grid;
grid-template-columns: 1.3fr 1fr 1fr;
}
/* Become-a-partner: pitch + form side-by-side, stacked on mobile */
.partner-form-grid {
grid-template-columns: 0.85fr 1.15fr;
}
/* Per-cell column label only appears once the table stacks on mobile. */
.pc-key {
display: none;
}
@media (max-width: 768px) {
.partners-section-header {
grid-template-columns: 1fr;
}
.partners-compare-grid {
grid-template-columns: 1fr;
}
.partner-form-grid {
grid-template-columns: 1fr;
}
/* Header row redundant once each value is labelled inline. */
.partners-compare-header {
display: none;
}
.pc-cat {
padding-bottom: 4px !important;
}
.pc-val {
padding-top: 10px !important;
padding-bottom: 12px !important;
}
.pc-key {
display: block;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: rgba(10, 10, 10, 0.5);
margin-bottom: 4px;
font-weight: 500;
}
}
</style>