feat(website): responsive / mobile layouts

Make the marketing site mobile-friendly across every page and section.
Desktop appearance is unchanged; all breakpoint logic targets <=768px.

- Fluid section padding via clamp(); equal grids use auto-fit/minmax,
  asymmetric grids stack to one column via scoped-CSS media queries
- Nav: real hamburger menu on mobile (links, lang toggle, login, CTA)
- ProductMockup: scales the whole dashboard to fit (zoom) instead of
  reflowing its internals into a tall stack
- Lower oversized heading clamp() minimums so titles no longer overflow
  at ~390px (hero, page headers, final CTA, brand cover/chapter)
- HowItWorks: row-gap when steps stack so node markers clear the text
- Compare + partners tables: stacked rows now label each value with its
  column (Dezky vs hyperscaler / CSP) instead of an ambiguous header
- Footer columns, tiers, calculator and tables stack cleanly on mobile
This commit is contained in:
Ronni Baslund
2026-06-06 15:55:35 +02:00
parent bc0697c3e8
commit d668b1b6a6
28 changed files with 721 additions and 142 deletions
@@ -13,7 +13,7 @@ const route = useRoute()
<template> <template>
<LandingPageHeader :label="copy.pages.comingSoonKicker" :title="title" :intro="body" /> <LandingPageHeader :label="copy.pages.comingSoonKicker" :title="title" :intro="body" />
<LandingContainer pad="48px 64px 160px"> <LandingContainer pad="clamp(32px, 5vw, 48px) clamp(20px, 5vw, 64px) clamp(56px, 8vw, 160px)">
<LandingBtn variant="primary" size="lg" @click="goToSection('#final-cta', route.path)">{{ copy.pages.ctaDemo }} </LandingBtn> <LandingBtn variant="primary" size="lg" @click="goToSection('#final-cta', route.path)">{{ copy.pages.ctaDemo }} </LandingBtn>
</LandingContainer> </LandingContainer>
</template> </template>
+68 -10
View File
@@ -8,15 +8,15 @@ const copy = useCopy()
<template> <template>
<section :style="{ background: t.bg, color: t.fg }"> <section :style="{ background: t.bg, color: t.fg }">
<LandingContainer pad="140px 64px"> <LandingContainer pad="clamp(56px, 8vw, 140px) clamp(20px, 5vw, 64px)">
<LandingSectionLabel :label="copy.compare.label" /> <LandingSectionLabel :label="copy.compare.label" />
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '80px', alignItems: 'end', marginBottom: '56px' }"> <div class="compare-intro-grid" :style="{ display: 'grid', gap: '80px', alignItems: 'end', marginBottom: '56px' }">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.compare.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.compare.heading }}</h2>
<p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '17px', lineHeight: 1.6, maxWidth: '460px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.compare.lede }}</p> <p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '17px', lineHeight: 1.6, maxWidth: '460px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.compare.lede }}</p>
</div> </div>
<div :style="{ border: `1px solid ${t.borderStrong}` }"> <div :style="{ border: `1px solid ${t.borderStrong}`, overflowX: 'auto' }">
<div :style="{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr', background: t.fg, color: t.bg }"> <div class="compare-table-grid compare-header" :style="{ display: 'grid', background: t.fg, color: t.bg }">
<div :style="{ padding: '20px 28px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', letterSpacing: '0.12em', textTransform: 'uppercase', opacity: 0.6 }">kategori</div> <div :style="{ padding: '20px 28px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', letterSpacing: '0.12em', textTransform: 'uppercase', opacity: 0.6 }">kategori</div>
<div :style="{ padding: '20px 28px', display: 'flex', alignItems: 'center', gap: '10px', borderLeft: `1px solid ${t.fgDim}` }"> <div :style="{ padding: '20px 28px', display: 'flex', alignItems: 'center', gap: '10px', borderLeft: `1px solid ${t.fgDim}` }">
<BrandNodeMark :size="18" :fg="t.bg" :accent="t.signal" /> <BrandNodeMark :size="18" :fg="t.bg" :accent="t.signal" />
@@ -26,21 +26,79 @@ const copy = useCopy()
</div> </div>
<div <div
v-for="(row, i) in copy.compare.rows" :key="i" v-for="(row, i) in copy.compare.rows" :key="i"
class="compare-table-grid"
:style="{ :style="{
display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr', display: 'grid',
borderTop: `1px solid ${t.border}`, borderTop: `1px solid ${t.border}`,
background: i % 2 === 0 ? t.bg : t.bgAlt, background: i % 2 === 0 ? t.bg : t.bgAlt,
fontFamily: '\'Inter\', sans-serif', fontSize: '15px', fontFamily: '\'Inter\', sans-serif', fontSize: '15px',
}" }"
> >
<div :style="{ padding: '22px 28px', color: t.fgMuted }">{{ row[0] }}</div> <div class="cmp-cat" :style="{ padding: '22px 28px', color: t.fgMuted }">{{ row[0] }}</div>
<div :style="{ padding: '22px 28px', color: t.fg, fontWeight: 600, borderLeft: `1px solid ${t.border}`, display: 'flex', alignItems: 'center', gap: '10px' }"> <div class="cmp-val" :style="{ padding: '22px 28px', color: t.fg, fontWeight: 600, borderLeft: `1px solid ${t.border}` }">
<span :style="{ width: '4px', height: '4px', background: t.signal, borderRadius: '999px', flexShrink: 0 }" /> <span class="cmp-key" :style="{ color: t.fgMuted }">{{ copy.compare.cols[0] }}</span>
{{ row[1] }} <span :style="{ display: 'flex', alignItems: 'center', gap: '10px' }">
<span :style="{ width: '4px', height: '4px', background: t.signal, borderRadius: '999px', flexShrink: 0 }" />
{{ row[1] }}
</span>
</div>
<div class="cmp-val" :style="{ padding: '22px 28px', color: t.fgMuted, borderLeft: `1px solid ${t.border}` }">
<span class="cmp-key" :style="{ color: t.fgMuted }">{{ copy.compare.cols[1] }}</span>
{{ row[2] }}
</div> </div>
<div :style="{ padding: '22px 28px', color: t.fgMuted, borderLeft: `1px solid ${t.border}` }">{{ row[2] }}</div>
</div> </div>
</div> </div>
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
.compare-intro-grid {
grid-template-columns: 1.1fr 1fr;
}
.compare-table-grid {
grid-template-columns: 1.2fr 1fr 1fr;
}
/* The per-cell column label only appears once the table stacks on mobile. */
.cmp-key {
display: none;
}
@media (max-width: 768px) {
.compare-intro-grid {
grid-template-columns: 1fr;
gap: 24px !important;
}
.compare-table-grid {
grid-template-columns: 1fr;
}
/* Header row is redundant once each value is labelled inline. */
.compare-header {
display: none;
}
/* Category becomes the card title; values drop the column divider + tighten. */
.cmp-cat {
font-weight: 600;
padding-bottom: 2px !important;
}
.cmp-val {
border-left: none !important;
padding-top: 12px !important;
padding-bottom: 14px !important;
}
.cmp-key {
display: block;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 500;
margin-bottom: 5px;
}
}
</style>
@@ -4,7 +4,7 @@ withDefaults(defineProps<{
pad?: string pad?: string
max?: number max?: number
}>(), { }>(), {
pad: '120px 64px', pad: 'clamp(56px, 8vw, 120px) clamp(20px, 5vw, 64px)',
max: 1280, max: 1280,
}) })
</script> </script>
+1 -1
View File
@@ -9,7 +9,7 @@ const copy = useCopy()
<template> <template>
<section :style="{ background: t.bg, color: t.fg }"> <section :style="{ background: t.bg, color: t.fg }">
<LandingContainer pad="140px 64px" :max="960"> <LandingContainer pad="clamp(56px, 8vw, 140px) clamp(20px, 5vw, 64px)" :max="960">
<LandingSectionLabel :label="copy.faq.label" /> <LandingSectionLabel :label="copy.faq.label" />
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.faq.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.faq.heading }}</h2>
<div :style="{ marginTop: '56px' }"> <div :style="{ marginTop: '56px' }">
+3 -3
View File
@@ -11,14 +11,14 @@ const copy = useCopy()
<div :style="{ position: 'absolute', right: '-180px', bottom: '-180px', opacity: 0.05 }"> <div :style="{ position: 'absolute', right: '-180px', bottom: '-180px', opacity: 0.05 }">
<BrandNodeMark :size="640" :fg="C.carbon" :accent="C.signal" /> <BrandNodeMark :size="640" :fg="C.carbon" :accent="C.signal" />
</div> </div>
<LandingContainer pad="140px 64px"> <LandingContainer pad="clamp(56px, 8vw, 140px) clamp(20px, 5vw, 64px)">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(48px, 6vw, 96px)', letterSpacing: '-0.04em', lineHeight: 0.98, margin: 0, color: C.bone, textWrap: 'balance', maxWidth: '900px' }"> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(32px, 6vw, 96px)', letterSpacing: '-0.04em', lineHeight: 0.98, margin: 0, color: C.bone, textWrap: 'balance', maxWidth: '900px' }">
<template v-for="(part, i) in copy.finalCta.heading" :key="i"> <template v-for="(part, i) in copy.finalCta.heading" :key="i">
<template v-if="typeof part === 'string'">{{ part }} </template> <template v-if="typeof part === 'string'">{{ part }} </template>
<span v-else :style="{ color: C.signal }">{{ part.hl }}</span> <span v-else :style="{ color: C.signal }">{{ part.hl }}</span>
</template> </template>
</h2> </h2>
<div :style="{ marginTop: '28px', maxWidth: '520px', fontFamily: '\'Inter\', sans-serif', fontSize: '19px', color: 'rgba(244,243,238,0.7)' }">{{ copy.finalCta.sub }}</div> <div :style="{ marginTop: '28px', maxWidth: 'min(100%, 520px)', fontFamily: '\'Inter\', sans-serif', fontSize: '19px', color: 'rgba(244,243,238,0.7)' }">{{ copy.finalCta.sub }}</div>
<div :style="{ marginTop: '40px' }"> <div :style="{ marginTop: '40px' }">
<button :style="{ <button :style="{
background: C.signal, color: C.carbon, border: 'none', background: C.signal, color: C.carbon, border: 'none',
+51 -10
View File
@@ -23,8 +23,9 @@ function onLink(e: MouseEvent, href: string) {
<template> <template>
<footer :style="{ background: C.bone, color: C.carbon, borderTop: `1px solid ${t.border}` }"> <footer :style="{ background: C.bone, color: C.carbon, borderTop: `1px solid ${t.border}` }">
<LandingContainer pad="80px 64px 40px"> <LandingContainer pad="clamp(56px, 8vw, 80px) clamp(20px, 5vw, 64px) clamp(28px, 4vw, 40px)">
<div :style="{ display: 'grid', gridTemplateColumns: '1.4fr repeat(4, 1fr)', gap: '48px' }"> <!-- Lockup + 4 link columns: desktop 1.4fr repeat(4,1fr), mobile stack -->
<div class="footer-main-grid">
<div> <div>
<BrandNodeLockup :scale="0.75" :fg="C.carbon" :accent="C.signal" /> <BrandNodeLockup :scale="0.75" :fg="C.carbon" :accent="C.signal" />
<div :style="{ marginTop: '20px', fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: 'rgba(10,10,10,0.6)', maxWidth: '280px', lineHeight: 1.5 }">{{ copy.footer.tagline }}</div> <div :style="{ marginTop: '20px', fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: 'rgba(10,10,10,0.6)', maxWidth: '280px', lineHeight: 1.5 }">{{ copy.footer.tagline }}</div>
@@ -34,20 +35,24 @@ function onLink(e: MouseEvent, href: string) {
<div>{{ copy.footer.legal.addr }}</div> <div>{{ copy.footer.legal.addr }}</div>
</div> </div>
</div> </div>
<div v-for="(col, i) in copy.footer.cols" :key="i"> <!-- Four link columns reflow: 2 per row on tablet, 1 on small mobile -->
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: 'rgba(10,10,10,0.45)', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: '18px' }">{{ col[0] }}</div> <div class="footer-link-columns">
<div :style="{ display: 'flex', flexDirection: 'column', gap: '12px' }"> <div v-for="(col, i) in copy.footer.cols" :key="i">
<NuxtLink <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: 'rgba(10,10,10,0.45)', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: '18px' }">{{ col[0] }}</div>
v-for="(link, j) in col[1]" :key="j" :to="link[1]" <div :style="{ display: 'flex', flexDirection: 'column', gap: '12px' }">
:style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: 'rgba(10,10,10,0.78)' }" <NuxtLink
@click="onLink($event, link[1])" v-for="(link, j) in col[1]" :key="j" :to="link[1]"
>{{ link[0] }}</NuxtLink> :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: 'rgba(10,10,10,0.78)' }"
@click="onLink($event, link[1])"
>{{ link[0] }}</NuxtLink>
</div>
</div> </div>
</div> </div>
</div> </div>
<div :style="{ <div :style="{
marginTop: '64px', paddingTop: '28px', borderTop: `1px solid ${t.border}`, marginTop: '64px', paddingTop: '28px', borderTop: `1px solid ${t.border}`,
display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '24px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '24px',
flexWrap: 'wrap',
fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: 'rgba(10,10,10,0.45)', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: 'rgba(10,10,10,0.45)',
}"> }">
<div>{{ copy.footer.copyright }}</div> <div>{{ copy.footer.copyright }}</div>
@@ -62,3 +67,39 @@ function onLink(e: MouseEvent, href: string) {
</LandingContainer> </LandingContainer>
</footer> </footer>
</template> </template>
<style scoped>
/* Desktop: lockup column (1.4fr) + one cell that spans the four link columns (4fr) */
.footer-main-grid {
display: grid;
grid-template-columns: 1.4fr 4fr;
gap: 48px;
}
/* The four link columns always live in their own sub-grid so they can reflow
independently: 4 across on desktop, 2 on tablet, 1 on small mobile. */
.footer-link-columns {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 48px;
}
@media (max-width: 768px) {
/* Stack lockup on top, link columns below */
.footer-main-grid {
grid-template-columns: 1fr;
}
/* Two columns on tablet/large mobile, collapses to 1 on narrow phones */
.footer-link-columns {
grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr));
gap: 32px;
}
}
@media (max-width: 400px) {
.footer-link-columns {
grid-template-columns: 1fr;
}
}
</style>
+4 -4
View File
@@ -23,8 +23,8 @@ const brush = {
</script> </script>
<template> <template>
<section :style="{ background: t.bg, color: t.fg, paddingTop: '80px' }"> <section :style="{ background: t.bg, color: t.fg, paddingTop: 'clamp(40px, 6vw, 80px)' }">
<LandingContainer pad="60px 64px 0"> <LandingContainer pad="clamp(40px, 5vw, 60px) clamp(20px, 5vw, 64px) 0">
<div :style="{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '56px' }"> <div :style="{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '56px' }">
<span :style="{ width: '6px', height: '6px', borderRadius: '999px', background: t.signal, boxShadow: `0 0 0 4px ${t.signal}33` }" /> <span :style="{ width: '6px', height: '6px', borderRadius: '999px', background: t.signal, boxShadow: `0 0 0 4px ${t.signal}33` }" />
<span :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', color: t.fgMuted, letterSpacing: '0.04em', whiteSpace: 'nowrap' }">{{ copy.hero.eyebrow }}</span> <span :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', color: t.fgMuted, letterSpacing: '0.04em', whiteSpace: 'nowrap' }">{{ copy.hero.eyebrow }}</span>
@@ -32,7 +32,7 @@ const brush = {
<h1 :style="{ <h1 :style="{
fontFamily: '\'Inter Tight\', \'Inter\', sans-serif', fontFamily: '\'Inter Tight\', \'Inter\', sans-serif',
fontWeight: 600, fontSize: 'clamp(56px, 7.2vw, 112px)', letterSpacing: '-0.04em', fontWeight: 600, fontSize: 'clamp(38px, 7.2vw, 112px)', letterSpacing: '-0.04em',
lineHeight: 0.96, margin: 0, textWrap: 'balance', color: t.fg, lineHeight: 0.96, margin: 0, textWrap: 'balance', color: t.fg,
}"> }">
<template v-for="(part, i) in headline" :key="i"> <template v-for="(part, i) in headline" :key="i">
@@ -61,7 +61,7 @@ const brush = {
</div> </div>
</LandingContainer> </LandingContainer>
<LandingContainer pad="80px 64px 120px"> <LandingContainer pad="clamp(40px, 6vw, 80px) clamp(20px, 5vw, 64px) clamp(56px, 8vw, 120px)">
<LandingProductMockup /> <LandingProductMockup />
</LandingContainer> </LandingContainer>
</section> </section>
+11 -2
View File
@@ -8,10 +8,10 @@ const copy = useCopy()
<template> <template>
<section :style="{ background: t.bg, color: t.fg }"> <section :style="{ background: t.bg, color: t.fg }">
<LandingContainer pad="120px 64px"> <LandingContainer pad="clamp(56px, 8vw, 120px) clamp(20px, 5vw, 64px)">
<LandingSectionLabel :label="copy.how.label" /> <LandingSectionLabel :label="copy.how.label" />
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.how.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.how.heading }}</h2>
<div :style="{ marginTop: '80px', display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '0', position: 'relative' }"> <div class="how-steps" :style="{ marginTop: '80px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', position: 'relative' }">
<div <div
v-for="(step, i) in copy.how.steps" :key="i" v-for="(step, i) in copy.how.steps" :key="i"
:style="{ padding: '40px 36px 0 0', borderTop: `1px solid ${t.borderStrong}`, position: 'relative' }" :style="{ padding: '40px 36px 0 0', borderTop: `1px solid ${t.borderStrong}`, position: 'relative' }"
@@ -29,3 +29,12 @@ const copy = useCopy()
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
/* Timeline steps touch (gap 0) when side-by-side. When they stack on mobile,
add a row gap so each step's top node marker clears the previous step's text. */
.how-steps { gap: 0; }
@media (max-width: 768px) {
.how-steps { row-gap: 52px; }
}
</style>
+119 -4
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
// Sticky top nav with logo, anchor links, language toggle, login + demo CTA. // Sticky top nav with logo, anchor links, language toggle, login + demo CTA.
// Ported from landing-sections.jsx Nav (light mode, production subset). // Ported from landing-sections.jsx Nav (light mode, production subset).
import { computed } from 'vue' import { computed, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { APP_URL } from '~/utils/landingTokens' import { APP_URL } from '~/utils/landingTokens'
import { useTheme, useCopy, useLang, toggleLang, scrollToAnchor, goToSection } from '~/composables/useLanding' import { useTheme, useCopy, useLang, toggleLang, scrollToAnchor, goToSection } from '~/composables/useLanding'
@@ -11,6 +11,8 @@ const copy = useCopy()
const lang = useLang() const lang = useLang()
const route = useRoute() const route = useRoute()
const mobileOpen = ref(false)
const items = computed(() => [ const items = computed(() => [
{ label: copy.value.nav.product, href: '/#suite' }, { label: copy.value.nav.product, href: '/#suite' },
{ label: copy.value.nav.security, href: '/#sovereignty' }, { label: copy.value.nav.security, href: '/#sovereignty' },
@@ -32,6 +34,12 @@ function onNav(e: MouseEvent, href: string) {
e.preventDefault() e.preventDefault()
scrollToAnchor(href.slice(href.indexOf('#'))) scrollToAnchor(href.slice(href.indexOf('#')))
} }
mobileOpen.value = false
}
function onMobileLink(e: MouseEvent, href: string) {
onNav(e, href)
mobileOpen.value = false
} }
</script> </script>
@@ -43,7 +51,8 @@ function onNav(e: MouseEvent, href: string) {
borderBottom: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`,
}"> }">
<div :style="{ <div :style="{
maxWidth: '1280px', margin: '0 auto', padding: '18px 64px', maxWidth: '1280px', margin: '0 auto',
padding: `18px clamp(20px, 5vw, 64px)`,
display: 'flex', alignItems: 'center', justifyContent: 'space-between', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}"> }">
<NuxtLink to="/" :style="{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer' }" @click="onLogo"> <NuxtLink to="/" :style="{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer' }" @click="onLogo">
@@ -51,7 +60,8 @@ function onNav(e: MouseEvent, href: string) {
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontWeight: 600, fontSize: '16px', letterSpacing: '-0.02em', color: t.fg }">dezky</div> <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontWeight: 600, fontSize: '16px', letterSpacing: '-0.02em', color: t.fg }">dezky</div>
</NuxtLink> </NuxtLink>
<nav :style="{ display: 'flex', alignItems: 'center', gap: '36px' }"> <!-- Desktop nav cluster hidden on mobile via scoped CSS -->
<nav class="nav-desktop" :style="{ display: 'flex', alignItems: 'center', gap: '36px' }">
<NuxtLink <NuxtLink
v-for="(it, i) in items" :key="i" :to="it.href" v-for="(it, i) in items" :key="i" :to="it.href"
:style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: t.fgMuted, letterSpacing: '-0.005em' }" :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: t.fgMuted, letterSpacing: '-0.005em' }"
@@ -59,7 +69,8 @@ function onNav(e: MouseEvent, href: string) {
>{{ it.label }}</NuxtLink> >{{ it.label }}</NuxtLink>
</nav> </nav>
<div :style="{ display: 'flex', alignItems: 'center', gap: '14px' }"> <!-- Desktop CTA cluster hidden on mobile via scoped CSS -->
<div class="nav-desktop" :style="{ display: 'flex', alignItems: 'center', gap: '14px' }">
<button <button
:style="{ :style="{
background: 'transparent', border: `1px solid ${t.border}`, background: 'transparent', border: `1px solid ${t.border}`,
@@ -72,6 +83,110 @@ function onNav(e: MouseEvent, href: string) {
<a :href="APP_URL" :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: t.fgMuted }">{{ copy.nav.login }}</a> <a :href="APP_URL" :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: t.fgMuted }">{{ copy.nav.login }}</a>
<LandingBtn variant="primary" @click="goToSection('#final-cta', route.path)">{{ copy.nav.cta }} </LandingBtn> <LandingBtn variant="primary" @click="goToSection('#final-cta', route.path)">{{ copy.nav.cta }} </LandingBtn>
</div> </div>
<!-- Hamburger visible only on mobile via scoped CSS -->
<button
class="nav-hamburger"
:aria-label="mobileOpen ? 'Close menu' : 'Open menu'"
:aria-expanded="mobileOpen"
:style="{
flexDirection: 'column', justifyContent: 'center', alignItems: 'center',
gap: '5px', background: 'transparent', border: 'none', cursor: 'pointer',
padding: '6px', borderRadius: '4px',
}"
@click="mobileOpen = !mobileOpen"
>
<!-- Three bar lines; top/bottom rotate to X when open -->
<span :style="{
display: 'block', width: '22px', height: '2px',
background: t.fg, borderRadius: '2px',
transition: 'transform 0.2s, opacity 0.2s',
transform: mobileOpen ? 'translateY(7px) rotate(45deg)' : 'none',
}" />
<span :style="{
display: 'block', width: '22px', height: '2px',
background: t.fg, borderRadius: '2px',
transition: 'opacity 0.2s',
opacity: mobileOpen ? 0 : 1,
}" />
<span :style="{
display: 'block', width: '22px', height: '2px',
background: t.fg, borderRadius: '2px',
transition: 'transform 0.2s, opacity 0.2s',
transform: mobileOpen ? 'translateY(-7px) rotate(-45deg)' : 'none',
}" />
</button>
</div>
<!-- Mobile dropdown panel JS-toggled, SSR-safe (visibility is CSS-controlled above 768px) -->
<div
v-if="mobileOpen"
class="nav-mobile-panel"
:style="{
background: 'rgba(250,250,247,0.97)',
borderTop: `1px solid ${t.border}`,
padding: '24px clamp(20px, 5vw, 64px) 32px',
display: 'flex', flexDirection: 'column', gap: '0',
}"
>
<NuxtLink
v-for="(it, i) in items" :key="i" :to="it.href"
:style="{
fontFamily: '\'Inter\', sans-serif', fontSize: '16px', color: t.fgMuted,
letterSpacing: '-0.005em', padding: '14px 0',
borderBottom: `1px solid ${t.border}`,
display: 'block',
}"
@click="onMobileLink($event, it.href)"
>{{ it.label }}</NuxtLink>
<div :style="{ display: 'flex', alignItems: 'center', gap: '14px', paddingTop: '24px', flexWrap: 'wrap' }">
<button
:style="{
background: 'transparent', border: `1px solid ${t.border}`,
color: t.fgMuted, fontSize: '11px', padding: '6px 10px', borderRadius: '4px',
fontFamily: '\'JetBrains Mono\', monospace', letterSpacing: '0.06em', textTransform: 'uppercase',
whiteSpace: 'nowrap',
}"
@click="toggleLang()"
>{{ lang === 'da' ? 'da · en' : 'en · da' }}</button>
<a
:href="APP_URL"
:style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '14px', color: t.fgMuted }"
@click="mobileOpen = false"
>{{ copy.nav.login }}</a>
<LandingBtn variant="primary" @click="() => { goToSection('#final-cta', route.path); mobileOpen = false }">{{ copy.nav.cta }} </LandingBtn>
</div>
</div> </div>
</header> </header>
</template> </template>
<style scoped>
/* Desktop clusters: visible by default, hidden on mobile */
.nav-desktop {
display: flex;
}
/* Hamburger: hidden by default, shown on mobile */
.nav-hamburger {
display: none;
}
/* Mobile panel: always rendered when v-if is true, but constrained to mobile */
@media (max-width: 768px) {
.nav-desktop {
display: none !important;
}
.nav-hamburger {
display: flex;
}
}
/* Ensure the mobile panel is not shown on desktop even if somehow open */
@media (min-width: 769px) {
.nav-mobile-panel {
display: none !important;
}
}
</style>
@@ -11,7 +11,7 @@ const copy = useCopy()
</script> </script>
<template> <template>
<LandingContainer pad="120px 64px 0"> <LandingContainer pad="clamp(56px, 8vw, 120px) clamp(20px, 5vw, 64px) 0">
<NuxtLink <NuxtLink
to="/" to="/"
:style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', color: t.fgMuted, letterSpacing: '0.04em', display: 'inline-flex', alignItems: 'center', gap: '8px' }" :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', color: t.fgMuted, letterSpacing: '0.04em', display: 'inline-flex', alignItems: 'center', gap: '8px' }"
@@ -24,7 +24,7 @@ const copy = useCopy()
<h1 :style="{ <h1 :style="{
fontFamily: '\'Inter Tight\', \'Inter\', sans-serif', fontWeight: 600, fontFamily: '\'Inter Tight\', \'Inter\', sans-serif', fontWeight: 600,
fontSize: 'clamp(40px, 5.4vw, 76px)', letterSpacing: '-0.035em', lineHeight: 1.0, fontSize: 'clamp(30px, 5.4vw, 76px)', letterSpacing: '-0.035em', lineHeight: 1.0,
margin: 0, textWrap: 'balance', color: t.fg, maxWidth: '900px', margin: 0, textWrap: 'balance', color: t.fg, maxWidth: '900px',
}">{{ title }}</h1> }">{{ title }}</h1>
@@ -41,10 +41,15 @@ const annual = computed(() => monthly.value * 12)
const dkk = new Intl.NumberFormat('da-DK', { style: 'currency', currency: 'DKK', maximumFractionDigits: 0 }) const dkk = new Intl.NumberFormat('da-DK', { style: 'currency', currency: 'DKK', maximumFractionDigits: 0 })
const fmtMonthly = computed(() => dkk.format(monthly.value)) const fmtMonthly = computed(() => dkk.format(monthly.value))
const fmtAnnual = computed(() => dkk.format(annual.value)) const fmtAnnual = computed(() => dkk.format(annual.value))
// Expose theme border color to scoped CSS via v-bind() so media-query-driven
// border direction changes (left on desktop, top on mobile) can still use the
// reactive theme value without window reads.
const borderColor = computed(() => t.value.border)
</script> </script>
<template> <template>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }"> <div class="calc-grid" :style="{ display: 'grid', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }">
<!-- Controls --> <!-- Controls -->
<div :style="{ padding: '36px', background: t.surface, display: 'flex', flexDirection: 'column', gap: '28px', justifyContent: 'center' }"> <div :style="{ padding: '36px', background: t.surface, display: 'flex', flexDirection: 'column', gap: '28px', justifyContent: 'center' }">
<div> <div>
@@ -74,7 +79,7 @@ const fmtAnnual = computed(() => dkk.format(annual.value))
</div> </div>
<!-- Output --> <!-- Output -->
<div :style="{ padding: '36px', background: t.bgAlt, borderLeft: `1px solid ${t.border}`, display: 'flex', flexDirection: 'column', justifyContent: 'center' }"> <div class="calc-output" :style="{ padding: '36px', background: t.bgAlt, display: 'flex', flexDirection: 'column', justifyContent: 'center' }">
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgMuted, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.monthlyLabel }}</div> <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgMuted, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.monthlyLabel }}</div>
<div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(40px, 5vw, 60px)', letterSpacing: '-0.03em', lineHeight: 1.0, color: t.fg, marginTop: '8px' }">{{ fmtMonthly }}</div> <div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(40px, 5vw, 60px)', letterSpacing: '-0.03em', lineHeight: 1.0, color: t.fg, marginTop: '8px' }">{{ fmtMonthly }}</div>
<div :style="{ marginTop: '20px', paddingTop: '20px', borderTop: `1px solid ${t.border}`, fontFamily: '\'Inter\', sans-serif', fontSize: '15px', color: t.fgMuted }"> <div :style="{ marginTop: '20px', paddingTop: '20px', borderTop: `1px solid ${t.border}`, fontFamily: '\'Inter\', sans-serif', fontSize: '15px', color: t.fgMuted }">
@@ -83,3 +88,24 @@ const fmtAnnual = computed(() => dkk.format(annual.value))
</div> </div>
</div> </div>
</template> </template>
<style scoped>
/* Desktop: two equal columns; output panel has a left divider. */
.calc-grid {
grid-template-columns: 1fr 1fr;
}
.calc-output {
border-left: 1px solid v-bind(borderColor);
}
/* Mobile: stack to single column; swap left divider to a top divider. */
@media (max-width: 768px) {
.calc-grid {
grid-template-columns: 1fr;
}
.calc-output {
border-left: none;
border-top: 1px solid v-bind(borderColor);
}
}
</style>
@@ -18,7 +18,7 @@ const initial = computed(() => (props.name[0] ?? '').toUpperCase())
<template> <template>
<div :style="{ <div :style="{
background: bg, border: `1px solid ${border}`, borderRadius: '4px', background: bg, border: `1px solid ${border}`, borderRadius: '4px',
padding: '20px 22px', display: 'flex', alignItems: 'center', gap: '16px', padding: '20px 22px', display: 'flex', alignItems: 'center', gap: '16px', flexWrap: 'wrap',
opacity: placeholder ? 0.55 : 1, opacity: placeholder ? 0.55 : 1,
borderStyle: placeholder ? 'dashed' : 'solid', borderStyle: placeholder ? 'dashed' : 'solid',
}"> }">
@@ -28,7 +28,7 @@ const initial = computed(() => (props.name[0] ?? '').toUpperCase())
fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 700, fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 700,
fontSize: '22px', color: '#FFF', letterSpacing: '-0.02em', flexShrink: 0, fontSize: '22px', color: '#FFF', letterSpacing: '-0.02em', flexShrink: 0,
}">{{ initial }}</div> }">{{ initial }}</div>
<div :style="{ flex: 1 }"> <div :style="{ flex: 1, minWidth: 0 }">
<div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '17px', color: fg, letterSpacing: '-0.02em' }">{{ name }}</div> <div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '17px', color: fg, letterSpacing: '-0.02em' }">{{ name }}</div>
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: 'rgba(0,0,0,0.5)', marginTop: '2px' }">{{ subtitle }}</div> <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: 'rgba(0,0,0,0.5)', marginTop: '2px' }">{{ subtitle }}</div>
</div> </div>
+14 -3
View File
@@ -8,9 +8,9 @@ const copy = useCopy()
<template> <template>
<section id="pricing" :style="{ background: t.bgAlt, color: t.fg, borderTop: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`, scrollMarginTop: '72px' }"> <section id="pricing" :style="{ background: t.bgAlt, color: t.fg, borderTop: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`, scrollMarginTop: '72px' }">
<LandingContainer pad="120px 64px"> <LandingContainer pad="clamp(56px, 8vw, 120px) clamp(20px, 5vw, 64px)">
<LandingSectionLabel :label="copy.pricing.label" /> <LandingSectionLabel :label="copy.pricing.label" />
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '80px', alignItems: 'center' }"> <div class="pricing-grid" :style="{ display: 'grid', gap: '80px', alignItems: 'center' }">
<div> <div>
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.pricing.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.pricing.heading }}</h2>
<div :style="{ marginTop: '28px', maxWidth: '520px' }"> <div :style="{ marginTop: '28px', maxWidth: '520px' }">
@@ -23,7 +23,7 @@ const copy = useCopy()
<div :style="{ background: t.surface, border: `1px solid ${t.border}`, borderRadius: '6px', padding: '36px 36px' }"> <div :style="{ background: t.surface, border: `1px solid ${t.border}`, borderRadius: '6px', padding: '36px 36px' }">
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.1em', textTransform: 'uppercase' }">{{ copy.pricing.teaser }}</div> <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.1em', textTransform: 'uppercase' }">{{ copy.pricing.teaser }}</div>
<div :style="{ display: 'flex', alignItems: 'baseline', gap: '12px', marginTop: '12px' }"> <div :style="{ display: 'flex', alignItems: 'baseline', gap: '12px', marginTop: '12px' }">
<span :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontSize: '96px', fontWeight: 600, letterSpacing: '-0.045em', lineHeight: 1, color: t.fg }">{{ copy.pricing.price }}</span> <span :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontSize: 'clamp(56px, 10vw, 96px)', fontWeight: 600, letterSpacing: '-0.045em', lineHeight: 1, color: t.fg }">{{ copy.pricing.price }}</span>
<span :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '13px', color: t.fgMuted }">{{ copy.pricing.unit }}</span> <span :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '13px', color: t.fgMuted }">{{ copy.pricing.unit }}</span>
</div> </div>
<div :style="{ height: '1px', background: t.border, margin: '28px 0' }" /> <div :style="{ height: '1px', background: t.border, margin: '28px 0' }" />
@@ -33,3 +33,14 @@ const copy = useCopy()
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
.pricing-grid {
grid-template-columns: 1fr 1fr;
}
@media (max-width: 768px) {
.pricing-grid {
grid-template-columns: 1fr;
}
}
</style>
+13 -2
View File
@@ -7,9 +7,9 @@ const copy = useCopy()
<template> <template>
<section :style="{ background: t.bg, color: t.fg }"> <section :style="{ background: t.bg, color: t.fg }">
<LandingContainer pad="120px 64px"> <LandingContainer pad="clamp(56px, 8vw, 120px) clamp(20px, 5vw, 64px)">
<LandingSectionLabel :label="copy.problem.label" /> <LandingSectionLabel :label="copy.problem.label" />
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: '80px' }"> <div class="problem-grid" :style="{ display: 'grid', gap: '80px' }">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.problem.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.problem.heading }}</h2>
<div :style="{ display: 'flex', flexDirection: 'column', gap: '18px', paddingTop: '12px' }"> <div :style="{ display: 'flex', flexDirection: 'column', gap: '18px', paddingTop: '12px' }">
<p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '18px', lineHeight: 1.6, maxWidth: '620px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.problem.p1 }}</p> <p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '18px', lineHeight: 1.6, maxWidth: '620px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.problem.p1 }}</p>
@@ -19,3 +19,14 @@ const copy = useCopy()
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
.problem-grid {
grid-template-columns: 1fr 1.4fr;
}
@media (max-width: 768px) {
.problem-grid {
grid-template-columns: 1fr;
}
}
</style>
@@ -2,13 +2,47 @@
// Stylized customer-portal dashboard shown under the hero. Illustrative only — // Stylized customer-portal dashboard shown under the hero. Illustrative only —
// labels are intentionally Danish in both languages. Ported from // labels are intentionally Danish in both languages. Ported from
// landing-sections.jsx ProductMockup (light mode). // landing-sections.jsx ProductMockup (light mode).
import { computed } from 'vue' import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { C } from '~/utils/landingTokens' import { C } from '~/utils/landingTokens'
import { useTheme, useDark } from '~/composables/useLanding' import { useTheme, useDark } from '~/composables/useLanding'
const t = useTheme() const t = useTheme()
const dark = useDark() const dark = useDark()
// The mockup is a fixed-layout dashboard. On desktop it runs fluid/full-width;
// when the available width drops below its comfortable design width we scale
// the WHOLE thing down (CSS zoom reflows, so no height hacks) instead of
// reflowing the internal dashboard — keeps it a recognisable mini-dashboard on
// phones. SSR-safe: renders at zoom 1 on the server, recomputes after mount.
const DESIGN_W = 760
const frame = ref<HTMLElement | null>(null)
const zoom = ref(1)
const frameWidth = ref('100%')
let ro: ResizeObserver | null = null
function recompute() {
const parent = frame.value?.parentElement
if (!parent) return
const w = parent.clientWidth
if (w >= DESIGN_W) {
zoom.value = 1
frameWidth.value = '100%'
} else {
frameWidth.value = `${DESIGN_W}px`
zoom.value = Math.max(0.4, w / DESIGN_W)
}
}
onMounted(() => {
recompute()
const parent = frame.value?.parentElement
if (parent && typeof ResizeObserver !== 'undefined') {
ro = new ResizeObserver(recompute)
ro.observe(parent)
}
})
onBeforeUnmount(() => ro?.disconnect())
const m = computed(() => ({ const m = computed(() => ({
bg: dark.value ? '#171715' : '#FFFFFF', bg: dark.value ? '#171715' : '#FFFFFF',
border: dark.value ? 'rgba(255,255,255,0.08)' : 'rgba(10,10,10,0.08)', border: dark.value ? 'rgba(255,255,255,0.08)' : 'rgba(10,10,10,0.08)',
@@ -39,10 +73,11 @@ const recent: [string, string, string][] = [
</script> </script>
<template> <template>
<div :style="{ <div ref="frame" :style="{
background: m.bg, border: `1px solid ${m.border}`, borderRadius: '8px', background: m.bg, border: `1px solid ${m.border}`, borderRadius: '8px',
boxShadow: dark ? '0 40px 80px rgba(0,0,0,0.5)' : '0 30px 80px rgba(10,10,10,0.08)', boxShadow: dark ? '0 40px 80px rgba(0,0,0,0.5)' : '0 30px 80px rgba(10,10,10,0.08)',
overflow: 'hidden', overflow: 'hidden',
width: frameWidth, zoom,
}"> }">
<!-- Window chrome --> <!-- Window chrome -->
<div :style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '14px 18px', borderBottom: `1px solid ${m.border}` }"> <div :style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '14px 18px', borderBottom: `1px solid ${m.border}` }">
@@ -52,7 +87,7 @@ const recent: [string, string, string][] = [
<div :style="{ marginLeft: '16px', padding: '4px 12px', background: m.subtle, borderRadius: '4px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: m.muted }">app.dezky.eu / dashboard</div> <div :style="{ marginLeft: '16px', padding: '4px 12px', background: m.subtle, borderRadius: '4px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: m.muted }">app.dezky.eu / dashboard</div>
</div> </div>
<div :style="{ display: 'grid', gridTemplateColumns: '220px 1fr', minHeight: '460px' }"> <div class="mockup-body" :style="{ display: 'grid', minHeight: '460px' }">
<!-- Sidebar --> <!-- Sidebar -->
<div :style="{ borderRight: `1px solid ${m.border}`, padding: '20px 0' }"> <div :style="{ borderRight: `1px solid ${m.border}`, padding: '20px 0' }">
<div :style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '0 20px', marginBottom: '24px' }"> <div :style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '0 20px', marginBottom: '24px' }">
@@ -84,8 +119,8 @@ const recent: [string, string, string][] = [
</div> </div>
<!-- Main --> <!-- Main -->
<div :style="{ padding: '28px 32px' }"> <div :style="{ padding: 'clamp(16px, 4vw, 28px) clamp(16px, 4vw, 32px)' }">
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }"> <div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', flexWrap: 'wrap', gap: '8px' }">
<div> <div>
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: m.muted, letterSpacing: '0.1em', textTransform: 'uppercase' }">indbakke</div> <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: m.muted, letterSpacing: '0.1em', textTransform: 'uppercase' }">indbakke</div>
<div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '28px', color: m.fg, marginTop: '6px', letterSpacing: '-0.02em' }">god morgen, anne</div> <div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '28px', color: m.fg, marginTop: '6px', letterSpacing: '-0.02em' }">god morgen, anne</div>
@@ -95,7 +130,7 @@ const recent: [string, string, string][] = [
</div> </div>
</div> </div>
<div :style="{ marginTop: '24px', display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px' }"> <div :style="{ marginTop: '24px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 120px), 1fr))', gap: '12px' }">
<div v-for="(s, i) in stats" :key="i" :style="{ padding: '16px 18px', background: m.subtle, borderRadius: '4px' }"> <div v-for="(s, i) in stats" :key="i" :style="{ padding: '16px 18px', background: m.subtle, borderRadius: '4px' }">
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '10px', color: m.muted, letterSpacing: '0.1em', textTransform: 'uppercase' }">{{ s[2] }}</div> <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '10px', color: m.muted, letterSpacing: '0.1em', textTransform: 'uppercase' }">{{ s[2] }}</div>
<div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontSize: '28px', fontWeight: 600, color: m.fg, marginTop: '4px' }">{{ s[1] }}</div> <div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontSize: '28px', fontWeight: 600, color: m.fg, marginTop: '4px' }">{{ s[1] }}</div>
@@ -107,8 +142,8 @@ const recent: [string, string, string][] = [
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '10px', color: m.muted, letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: '12px' }">seneste · indbakke</div> <div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '10px', color: m.muted, letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: '12px' }">seneste · indbakke</div>
<div <div
v-for="(r, i) in recent" :key="i" v-for="(r, i) in recent" :key="i"
class="mockup-mail-row"
:style="{ :style="{
display: 'grid', gridTemplateColumns: '200px 1fr 60px',
padding: '12px 0', borderTop: i === 0 ? `1px solid ${m.border}` : 'none', padding: '12px 0', borderTop: i === 0 ? `1px solid ${m.border}` : 'none',
borderBottom: `1px solid ${m.border}`, borderBottom: `1px solid ${m.border}`,
fontFamily: '\'Inter\', sans-serif', fontSize: '13px', alignItems: 'center', fontFamily: '\'Inter\', sans-serif', fontSize: '13px', alignItems: 'center',
@@ -123,3 +158,15 @@ const recent: [string, string, string][] = [
</div> </div>
</div> </div>
</template> </template>
<style scoped>
/* Fixed dashboard layout — the whole mockup is scaled to fit on small screens
(see the zoom logic in <script>), so the internal grid stays desktop-shaped. */
.mockup-body {
grid-template-columns: 220px 1fr;
}
.mockup-mail-row {
display: grid;
grid-template-columns: 200px 1fr 60px;
}
</style>
@@ -25,7 +25,7 @@ const labelParts = computed(() => {
<template> <template>
<section id="sovereignty" :style="{ background: t.invert, color: t.invertFg, scrollMarginTop: '72px' }"> <section id="sovereignty" :style="{ background: t.invert, color: t.invertFg, scrollMarginTop: '72px' }">
<LandingContainer pad="140px 64px"> <LandingContainer pad="clamp(56px, 8vw, 140px) clamp(20px, 5vw, 64px)">
<div :style="{ <div :style="{
display: 'flex', alignItems: 'center', gap: '14px', display: 'flex', alignItems: 'center', gap: '14px',
paddingBottom: '24px', marginBottom: '56px', paddingBottom: '24px', marginBottom: '56px',
@@ -37,7 +37,7 @@ const labelParts = computed(() => {
<span></span> <span></span>
<span>{{ labelParts.text }}</span> <span>{{ labelParts.text }}</span>
</div> </div>
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '96px' }"> <div class="sovereignty-main-grid" :style="{ display: 'grid', gap: '96px' }">
<div> <div>
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.invertFg }">{{ copy.sovereignty.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.invertFg }">{{ copy.sovereignty.heading }}</h2>
<div :style="{ marginTop: '36px', display: 'flex', flexDirection: 'column', gap: '18px', maxWidth: '540px' }"> <div :style="{ marginTop: '36px', display: 'flex', flexDirection: 'column', gap: '18px', maxWidth: '540px' }">
@@ -47,8 +47,9 @@ const labelParts = computed(() => {
<div :style="{ paddingTop: '8px' }"> <div :style="{ paddingTop: '8px' }">
<div <div
v-for="(row, i) in copy.sovereignty.checks" :key="i" v-for="(row, i) in copy.sovereignty.checks" :key="i"
class="sovereignty-check-row"
:style="{ :style="{
display: 'grid', gridTemplateColumns: '1fr 1.4fr', padding: '20px 0', display: 'grid', padding: '20px 0',
borderTop: i === 0 ? `1px solid ${rule18}` : 'none', borderTop: i === 0 ? `1px solid ${rule18}` : 'none',
borderBottom: `1px solid ${rule10}`, borderBottom: `1px solid ${rule10}`,
fontFamily: '\'Inter\', sans-serif', fontSize: '14px', alignItems: 'baseline', fontFamily: '\'Inter\', sans-serif', fontSize: '14px', alignItems: 'baseline',
@@ -62,3 +63,23 @@ const labelParts = computed(() => {
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
.sovereignty-main-grid {
grid-template-columns: 1.1fr 1fr;
}
.sovereignty-check-row {
grid-template-columns: 1fr 1.4fr;
}
@media (max-width: 768px) {
.sovereignty-main-grid {
grid-template-columns: 1fr;
}
.sovereignty-check-row {
grid-template-columns: 1fr;
}
}
</style>
+24 -3
View File
@@ -8,17 +8,18 @@ const copy = useCopy()
<template> <template>
<section id="stack" :style="{ background: t.bg, color: t.fg, scrollMarginTop: '72px' }"> <section id="stack" :style="{ background: t.bg, color: t.fg, scrollMarginTop: '72px' }">
<LandingContainer pad="140px 64px"> <LandingContainer pad="clamp(56px, 8vw, 140px) clamp(20px, 5vw, 64px)">
<LandingSectionLabel :label="copy.stack.label" /> <LandingSectionLabel :label="copy.stack.label" />
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '80px', alignItems: 'end', marginBottom: '56px' }"> <div class="stack-intro-grid" :style="{ display: 'grid', gap: '80px', alignItems: 'end', marginBottom: '56px' }">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.stack.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.stack.heading }}</h2>
<p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '20px', lineHeight: 1.5, maxWidth: '480px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.stack.lede }}</p> <p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '20px', lineHeight: 1.5, maxWidth: '480px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.stack.lede }}</p>
</div> </div>
<div> <div>
<div <div
v-for="(row, i) in copy.stack.rows" :key="i" v-for="(row, i) in copy.stack.rows" :key="i"
class="stack-row-grid"
:style="{ :style="{
display: 'grid', gridTemplateColumns: '1.1fr 1.6fr 1.3fr', display: 'grid',
gap: '24px', padding: '24px 0', gap: '24px', padding: '24px 0',
borderTop: i === 0 ? `1px solid ${t.borderStrong}` : 'none', borderTop: i === 0 ? `1px solid ${t.borderStrong}` : 'none',
borderBottom: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`,
@@ -33,3 +34,23 @@ const copy = useCopy()
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
.stack-intro-grid {
grid-template-columns: 1.1fr 1fr;
}
.stack-row-grid {
grid-template-columns: 1.1fr 1.6fr 1.3fr;
}
@media (max-width: 768px) {
.stack-intro-grid {
grid-template-columns: 1fr;
}
.stack-row-grid {
grid-template-columns: 1fr;
}
}
</style>
+15 -3
View File
@@ -8,13 +8,13 @@ const copy = useCopy()
<template> <template>
<section id="suite" :style="{ background: t.bgAlt, color: t.fg, borderTop: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`, scrollMarginTop: '72px' }"> <section id="suite" :style="{ background: t.bgAlt, color: t.fg, borderTop: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`, scrollMarginTop: '72px' }">
<LandingContainer pad="120px 64px"> <LandingContainer pad="clamp(56px, 8vw, 120px) clamp(20px, 5vw, 64px)">
<LandingSectionLabel :label="copy.suite.label" /> <LandingSectionLabel :label="copy.suite.label" />
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '80px', alignItems: 'end', marginBottom: '64px' }"> <div class="suite-intro-grid" :style="{ display: 'grid', gap: '80px', alignItems: 'end', marginBottom: '64px' }">
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.suite.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.suite.heading }}</h2>
<p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '20px', lineHeight: 1.5, maxWidth: '520px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.suite.lede }}</p> <p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '20px', lineHeight: 1.5, maxWidth: '520px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.suite.lede }}</p>
</div> </div>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '0', border: `1px solid ${t.border}` }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 220px), 1fr))', gap: '0', border: `1px solid ${t.border}` }">
<div <div
v-for="(card, i) in copy.suite.cards" :key="i" v-for="(card, i) in copy.suite.cards" :key="i"
:style="{ :style="{
@@ -49,3 +49,15 @@ const copy = useCopy()
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
.suite-intro-grid {
grid-template-columns: 1.1fr 1fr;
}
@media (max-width: 768px) {
.suite-intro-grid {
grid-template-columns: 1fr;
}
}
</style>
+14 -3
View File
@@ -30,12 +30,12 @@ const partnerCards = computed(() =>
<template> <template>
<section id="whitelabel" :style="{ background: sectionBg, color: t.fg, borderTop: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`, scrollMarginTop: '72px' }"> <section id="whitelabel" :style="{ background: sectionBg, color: t.fg, borderTop: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`, scrollMarginTop: '72px' }">
<LandingContainer pad="120px 64px"> <LandingContainer pad="clamp(56px, 8vw, 120px) clamp(20px, 5vw, 64px)">
<LandingSectionLabel :label="copy.whitelabel.label" /> <LandingSectionLabel :label="copy.whitelabel.label" />
<div :style="{ display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '80px', alignItems: 'start' }"> <div class="whitelabel-grid" :style="{ display: 'grid', gap: 'clamp(40px, 5vw, 80px)', alignItems: 'start' }">
<div> <div>
<h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.whitelabel.heading }}</h2> <h2 :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: 'clamp(36px, 4.4vw, 64px)', letterSpacing: '-0.032em', lineHeight: 1.0, margin: 0, textWrap: 'balance', color: t.fg }">{{ copy.whitelabel.heading }}</h2>
<div :style="{ marginTop: '32px', maxWidth: '520px' }"> <div :style="{ marginTop: '32px', maxWidth: 'min(100%, 520px)' }">
<p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '20px', lineHeight: 1.5, maxWidth: '640px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.whitelabel.lede }}</p> <p :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '20px', lineHeight: 1.5, maxWidth: '640px', color: t.fgMuted, margin: 0, textWrap: 'pretty' }">{{ copy.whitelabel.lede }}</p>
</div> </div>
<div :style="{ marginTop: '32px', display: 'flex', flexDirection: 'column', gap: '12px' }"> <div :style="{ marginTop: '32px', display: 'flex', flexDirection: 'column', gap: '12px' }">
@@ -65,3 +65,14 @@ const partnerCards = computed(() =>
</LandingContainer> </LandingContainer>
</section> </section>
</template> </template>
<style scoped>
.whitelabel-grid {
grid-template-columns: 1.2fr 1fr;
}
@media (max-width: 768px) {
.whitelabel-grid {
grid-template-columns: 1fr;
}
}
</style>
+13 -2
View File
@@ -1,3 +1,14 @@
<style scoped>
.about-body-grid {
grid-template-columns: 1.2fr 1fr;
}
@media (max-width: 768px) {
.about-body-grid {
grid-template-columns: 1fr;
}
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import { useTheme, useCopy } from '~/composables/useLanding' import { useTheme, useCopy } from '~/composables/useLanding'
@@ -13,8 +24,8 @@ useHead({ title: () => `${copy.value.pages.about.label} · dezky` })
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<LandingContainer pad="64px 64px 160px"> <LandingContainer pad="clamp(56px, 8vw, 64px) clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<div :style="{ display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '80px', alignItems: 'start' }"> <div class="about-body-grid" :style="{ display: 'grid', gap: '80px', alignItems: 'start' }">
<div :style="{ display: 'flex', flexDirection: 'column', gap: '24px' }"> <div :style="{ display: 'flex', flexDirection: 'column', gap: '24px' }">
<p <p
v-for="(para, i) in c.body" :key="i" v-for="(para, i) in c.body" :key="i"
+108 -42
View File
@@ -24,10 +24,10 @@ function frame(bg: string, height?: number, fg?: string) {
} }
const eyebrow = { fontFamily: mono, fontSize: '11px', letterSpacing: '0.18em', textTransform: 'uppercase' as const, fontWeight: 500, opacity: 0.7 } const eyebrow = { fontFamily: mono, fontSize: '11px', letterSpacing: '0.18em', textTransform: 'uppercase' as const, fontWeight: 500, opacity: 0.7 }
const h1 = { fontFamily: tight, fontWeight: 600, fontSize: 'clamp(48px, 6vw, 72px)', letterSpacing: '-0.035em', lineHeight: 0.95, margin: '24px 0 0', textWrap: 'balance' as const } const h1 = { fontFamily: tight, fontWeight: 600, fontSize: 'clamp(32px, 6vw, 72px)', letterSpacing: '-0.035em', lineHeight: 0.95, margin: '24px 0 0', textWrap: 'balance' as const }
const h2 = { fontFamily: tight, fontWeight: 600, fontSize: 'clamp(36px, 4.5vw, 52px)', letterSpacing: '-0.03em', lineHeight: 1, margin: '20px 0 56px', textWrap: 'balance' as const } const h2 = { fontFamily: tight, fontWeight: 600, fontSize: 'clamp(36px, 4.5vw, 52px)', letterSpacing: '-0.03em', lineHeight: 1, margin: '20px 0 56px', textWrap: 'balance' as const }
const caption = { fontFamily: mono, fontSize: '10px', letterSpacing: '0.12em', textTransform: 'uppercase' as const, color: 'rgba(0,0,0,0.5)', marginTop: '10px' } const caption = { fontFamily: mono, fontSize: '10px', letterSpacing: '0.12em', textTransform: 'uppercase' as const, color: 'rgba(0,0,0,0.5)', marginTop: '10px' }
const page = { padding: '96px 80px', borderBottom: `1px solid ${C.fog}` } const page = { padding: 'clamp(56px, 8vw, 96px) clamp(20px, 5vw, 80px)', borderBottom: `1px solid ${C.fog}` }
const bigSwatches = [ const bigSwatches = [
{ name: 'Carbon', hex: C.carbon, rgb: '10 10 10', role: 'Foreground · containers · type', dark: true }, { name: 'Carbon', hex: C.carbon, rgb: '10 10 10', role: 'Foreground · containers · type', dark: true },
@@ -99,11 +99,11 @@ const doDont: DoDont[] = [
<template> <template>
<div :style="{ background: C.paper, color: C.carbon }"> <div :style="{ background: C.paper, color: C.carbon }">
<!-- 00 · COVER --> <!-- 00 · COVER -->
<section :style="{ background: C.carbon, color: C.bone, padding: '80px 80px', minHeight: '86vh', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', position: 'relative', overflow: 'hidden' }"> <section :style="{ background: C.carbon, color: C.bone, padding: 'clamp(56px, 8vw, 80px) clamp(20px, 5vw, 80px)', minHeight: '86vh', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', position: 'relative', overflow: 'hidden' }">
<div :style="{ position: 'absolute', right: '-160px', bottom: '-160px', opacity: 0.06 }"> <div :style="{ position: 'absolute', right: '-160px', bottom: '-160px', opacity: 0.06 }">
<BrandNodeMark :size="720" :fg="C.carbon" :accent="C.signal" /> <BrandNodeMark :size="720" :fg="C.carbon" :accent="C.signal" />
</div> </div>
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', position: 'relative' }"> <div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', position: 'relative', flexWrap: 'wrap', gap: '16px' }">
<div :style="{ display: 'flex', alignItems: 'center', gap: '14px' }"> <div :style="{ display: 'flex', alignItems: 'center', gap: '14px' }">
<div :style="{ width: '56px', height: '56px', background: C.bone, borderRadius: '14px', display: 'flex', alignItems: 'center', justifyContent: 'center' }"> <div :style="{ width: '56px', height: '56px', background: C.bone, borderRadius: '14px', display: 'flex', alignItems: 'center', justifyContent: 'center' }">
<BrandNodeMark :size="44" :fg="C.carbon" :accent="C.signal" /> <BrandNodeMark :size="44" :fg="C.carbon" :accent="C.signal" />
@@ -117,12 +117,12 @@ const doDont: DoDont[] = [
</div> </div>
<div :style="{ position: 'relative' }"> <div :style="{ position: 'relative' }">
<div :style="eyebrow">The dezky brand</div> <div :style="eyebrow">The dezky brand</div>
<h1 :style="{ fontFamily: tight, fontWeight: 600, fontSize: 'clamp(64px, 11vw, 144px)', letterSpacing: '-0.045em', lineHeight: 0.9, margin: '32px 0 0', maxWidth: '90%' }"> <h1 :style="{ fontFamily: tight, fontWeight: 600, fontSize: 'clamp(34px, 11vw, 144px)', letterSpacing: '-0.045em', lineHeight: 0.9, margin: '32px 0 0', maxWidth: '90%' }">
Quiet software,<br> Quiet software,<br>
<span :style="{ color: C.signal }">sovereign data.</span> <span :style="{ color: C.signal }">sovereign data.</span>
</h1> </h1>
</div> </div>
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', color: 'rgba(255,255,255,0.55)', fontFamily: mono, fontSize: '11px', position: 'relative' }"> <div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', color: 'rgba(255,255,255,0.55)', fontFamily: mono, fontSize: '11px', position: 'relative', flexWrap: 'wrap', gap: '8px' }">
<div>dezky · brand system</div> <div>dezky · brand system</div>
<div>00 / 06</div> <div>00 / 06</div>
</div> </div>
@@ -133,14 +133,14 @@ const doDont: DoDont[] = [
<div :style="eyebrow">01 · Logo</div> <div :style="eyebrow">01 · Logo</div>
<h1 :style="h1">The mark</h1> <h1 :style="h1">The mark</h1>
<p :style="pStyle(560, true)">A lowercase <i>d</i> built as a unified letterform full bowl, rounded ascender, contained in a squircle. The node-dot in the upper-right is the one moving piece of the brand: a signal, a presence.</p> <p :style="pStyle(560, true)">A lowercase <i>d</i> built as a unified letterform full bowl, rounded ascender, contained in a squircle. The node-dot in the upper-right is the one moving piece of the brand: a signal, a presence.</p>
<div :style="{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '24px', marginTop: '64px' }"> <div class="logo-main-grid" :style="{ display: 'grid', gap: '24px', marginTop: '64px' }">
<div :style="frame(C.carbon, 520)"><BrandNodeMark :size="360" :fg="C.carbon" :accent="C.signal" /></div> <div :style="frame(C.carbon, 520)"><BrandNodeMark :size="360" :fg="C.carbon" :accent="C.signal" /></div>
<div :style="{ display: 'grid', gridTemplateRows: '1fr 1fr', gap: '24px', height: '520px' }"> <div :style="{ display: 'grid', gridTemplateRows: '1fr 1fr', gap: '24px', height: '520px' }">
<div :style="frame(C.signal)"><BrandNodeMark :size="180" :fg="C.signal" :accent="C.carbon" /></div> <div :style="frame(C.signal)"><BrandNodeMark :size="180" :fg="C.signal" :accent="C.carbon" /></div>
<div :style="frame(C.bone)"><BrandNodeMark :size="180" :fg="C.carbon" :accent="C.signal" /></div> <div :style="frame(C.bone)"><BrandNodeMark :size="180" :fg="C.carbon" :accent="C.signal" /></div>
</div> </div>
</div> </div>
<div :style="{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr', gap: '24px', marginTop: '8px' }"> <div class="logo-caption-row" :style="{ display: 'grid', gap: '24px', marginTop: '8px' }">
<div :style="caption">Primary · on carbon</div> <div :style="caption">Primary · on carbon</div>
<div :style="caption">Reversed · on signal</div> <div :style="caption">Reversed · on signal</div>
<div :style="caption">Light · on bone</div> <div :style="caption">Light · on bone</div>
@@ -151,9 +151,9 @@ const doDont: DoDont[] = [
<section :style="{ ...page, background: C.bone }"> <section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">01.1 · Anatomy</div> <div :style="eyebrow">01.1 · Anatomy</div>
<h2 :style="h2">Constructed, not drawn.</h2> <h2 :style="h2">Constructed, not drawn.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: '64px', alignItems: 'center' }"> <div class="anatomy-grid" :style="{ display: 'grid', gap: '64px', alignItems: 'center' }">
<div :style="frame(C.paper, 520)"> <div :style="frame(C.paper, 520)">
<svg viewBox="0 0 360 360" width="380" height="380"> <svg viewBox="0 0 360 360" width="380" height="380" style="max-width:100%;height:auto;">
<defs> <defs>
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"> <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(0,0,0,0.05)" stroke-width="0.5" /> <path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(0,0,0,0.05)" stroke-width="0.5" />
@@ -182,14 +182,14 @@ const doDont: DoDont[] = [
</div> </div>
<div> <div>
<h3 :style="h3Style(0)">Geometry</h3> <h3 :style="h3Style(0)">Geometry</h3>
<table :style="{ width: '100%', borderCollapse: 'collapse', fontFamily: inter, fontSize: '14px' }"> <div :style="{ overflowX: 'auto' }"><table :style="{ width: '100%', borderCollapse: 'collapse', fontFamily: inter, fontSize: '14px' }">
<tbody> <tbody>
<tr v-for="([k, v], i) in specRows" :key="i" :style="{ borderBottom: `1px solid ${C.fog}` }"> <tr v-for="([k, v], i) in specRows" :key="i" :style="{ borderBottom: `1px solid ${C.fog}` }">
<td :style="{ padding: '12px 0', color: 'rgba(0,0,0,0.55)', width: '40%' }">{{ k }}</td> <td :style="{ padding: '12px 0', color: 'rgba(0,0,0,0.55)', width: '40%' }">{{ k }}</td>
<td :style="{ padding: '12px 0', fontFamily: mono, fontSize: '13px' }">{{ v }}</td> <td :style="{ padding: '12px 0', fontFamily: mono, fontSize: '13px' }">{{ v }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table></div>
<p :style="pStyle(520, true)">Every measurement is a multiple of the stem weight (7u). Don't redraw the mark — use the master SVG.</p> <p :style="pStyle(520, true)">Every measurement is a multiple of the stem weight (7u). Don't redraw the mark — use the master SVG.</p>
</div> </div>
</div> </div>
@@ -199,7 +199,7 @@ const doDont: DoDont[] = [
<section :style="{ ...page, background: C.paper }"> <section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">01.2 · Clear space · Minimum size</div> <div :style="eyebrow">01.2 · Clear space · Minimum size</div>
<h2 :style="h2">Give it room.</h2> <h2 :style="h2">Give it room.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '32px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '32px' }">
<div> <div>
<div :style="frame(C.fog, 360)"> <div :style="frame(C.fog, 360)">
<div :style="{ position: 'relative' }"> <div :style="{ position: 'relative' }">
@@ -212,7 +212,7 @@ const doDont: DoDont[] = [
</div> </div>
<div> <div>
<div :style="frame(C.fog, 360)"> <div :style="frame(C.fog, 360)">
<div :style="{ display: 'flex', alignItems: 'flex-end', gap: '48px' }"> <div :style="{ display: 'flex', alignItems: 'flex-end', gap: '48px', flexWrap: 'wrap', justifyContent: 'center' }">
<div v-for="s in [16, 24, 48, 96]" :key="s" :style="{ textAlign: 'center' }"> <div v-for="s in [16, 24, 48, 96]" :key="s" :style="{ textAlign: 'center' }">
<BrandNodeMark :size="s" :fg="C.carbon" :accent="C.signal" /> <BrandNodeMark :size="s" :fg="C.carbon" :accent="C.signal" />
<div :style="{ fontFamily: mono, fontSize: '10px', color: 'rgba(0,0,0,0.5)', marginTop: '12px' }">{{ s }}px</div> <div :style="{ fontFamily: mono, fontSize: '10px', color: 'rgba(0,0,0,0.5)', marginTop: '12px' }">{{ s }}px</div>
@@ -230,15 +230,15 @@ const doDont: DoDont[] = [
<div :style="eyebrow">01.3 · Wordmark · Lockup</div> <div :style="eyebrow">01.3 · Wordmark · Lockup</div>
<h2 :style="h2">Letters set in JetBrains Mono.</h2> <h2 :style="h2">Letters set in JetBrains Mono.</h2>
<div :style="frame(C.paper, 300)"> <div :style="frame(C.paper, 300)">
<div :style="{ fontFamily: mono, fontWeight: 600, fontSize: '140px', letterSpacing: '-0.04em', color: C.carbon, lineHeight: 1 }">dezky</div> <div :style="{ fontFamily: mono, fontWeight: 600, fontSize: 'clamp(48px, 15vw, 140px)', letterSpacing: '-0.04em', color: C.carbon, lineHeight: 1 }">dezky</div>
</div> </div>
<div :style="caption">Wordmark · 100% scale</div> <div :style="caption">Wordmark · 100% scale</div>
<h3 :style="h3Style()">Horizontal lockup</h3> <h3 :style="h3Style()">Horizontal lockup</h3>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '24px' }">
<div :style="frame(C.paper, 220)"><BrandNodeLockup :scale="1.4" :fg="C.carbon" :accent="C.signal" /></div> <div :style="frame(C.paper, 220)"><BrandNodeLockup :scale="1.4" :fg="C.carbon" :accent="C.signal" /></div>
<div :style="frame(C.carbon, 220)"><BrandNodeLockup :scale="1.4" :fg="C.carbon" :accent="C.signal" :wordmark="C.bone" /></div> <div :style="frame(C.carbon, 220)"><BrandNodeLockup :scale="1.4" :fg="C.carbon" :accent="C.signal" :wordmark="C.bone" /></div>
</div> </div>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '8px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '24px', marginTop: '8px' }">
<div :style="caption">Light surface</div> <div :style="caption">Light surface</div>
<div :style="caption">Dark surface</div> <div :style="caption">Dark surface</div>
</div> </div>
@@ -249,7 +249,7 @@ const doDont: DoDont[] = [
<section :style="{ ...page, background: C.paper }"> <section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">01.4 · Do · Don't</div> <div :style="eyebrow">01.4 · Do · Don't</div>
<h2 :style="h2">How not to use it.</h2> <h2 :style="h2">How not to use it.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '20px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 200px), 1fr))', gap: '20px' }">
<div v-for="(row, i) in doDont" :key="i"> <div v-for="(row, i) in doDont" :key="i">
<div :style="{ background: row.bg, borderRadius: '4px', height: '180px', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative', overflow: 'hidden' }"> <div :style="{ background: row.bg, borderRadius: '4px', height: '180px', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative', overflow: 'hidden' }">
<div :style="{ transform: row.transform || 'none' }"> <div :style="{ transform: row.transform || 'none' }">
@@ -272,7 +272,7 @@ const doDont: DoDont[] = [
<div :style="eyebrow">02 · Color</div> <div :style="eyebrow">02 · Color</div>
<h1 :style="h1">Two colors do the work.</h1> <h1 :style="h1">Two colors do the work.</h1>
<p :style="pStyle(560, true)">Carbon and Signal carry the brand. Everything else is supporting cast. Signal is loud — reserve it for the mark, the node-dot, and primary calls to action. Never tint, never gradient.</p> <p :style="pStyle(560, true)">Carbon and Signal carry the brand. Everything else is supporting cast. Signal is loud — reserve it for the mark, the node-dot, and primary calls to action. Never tint, never gradient.</p>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '80px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '24px', marginTop: '80px' }">
<div v-for="c in bigSwatches" :key="c.name"> <div v-for="c in bigSwatches" :key="c.name">
<div :style="{ background: c.hex, height: '280px', borderRadius: '4px', display: 'flex', alignItems: 'flex-end', padding: '24px', color: c.dark ? C.bone : C.carbon, boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }"> <div :style="{ background: c.hex, height: '280px', borderRadius: '4px', display: 'flex', alignItems: 'flex-end', padding: '24px', color: c.dark ? C.bone : C.carbon, boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }">
<div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '36px', letterSpacing: '-0.02em' }">{{ c.name }}</div> <div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '36px', letterSpacing: '-0.02em' }">{{ c.name }}</div>
@@ -285,7 +285,7 @@ const doDont: DoDont[] = [
</div> </div>
</div> </div>
<h3 :style="h3Style()">Surfaces & type</h3> <h3 :style="h3Style()">Surfaces & type</h3>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 200px), 1fr))', gap: '16px' }">
<div v-for="c in surfaces" :key="c.name"> <div v-for="c in surfaces" :key="c.name">
<div :style="{ background: c.hex, height: '140px', borderRadius: '4px', boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }" /> <div :style="{ background: c.hex, height: '140px', borderRadius: '4px', boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }" />
<div :style="{ marginTop: '12px' }"> <div :style="{ marginTop: '12px' }">
@@ -296,7 +296,7 @@ const doDont: DoDont[] = [
</div> </div>
</div> </div>
<h3 :style="h3Style()">Semantic</h3> <h3 :style="h3Style()">Semantic</h3>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 200px), 1fr))', gap: '16px' }">
<div v-for="c in semantic" :key="c.name"> <div v-for="c in semantic" :key="c.name">
<div :style="{ background: c.hex, height: '140px', borderRadius: '4px', boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }" /> <div :style="{ background: c.hex, height: '140px', borderRadius: '4px', boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }" />
<div :style="{ marginTop: '12px' }"> <div :style="{ marginTop: '12px' }">
@@ -313,7 +313,7 @@ const doDont: DoDont[] = [
<div :style="eyebrow">02.1 · Color in use</div> <div :style="eyebrow">02.1 · Color in use</div>
<h2 :style="h2">The 70 · 20 · 10 rule.</h2> <h2 :style="h2">The 70 · 20 · 10 rule.</h2>
<p :style="pStyle(560, true)"><b>70%</b> Bone / Paper · <b>20%</b> Carbon · <b>10%</b> Signal. Surface stays calm; the brand interrupts only at moments of consequence.</p> <p :style="pStyle(560, true)"><b>70%</b> Bone / Paper · <b>20%</b> Carbon · <b>10%</b> Signal. Surface stays calm; the brand interrupts only at moments of consequence.</p>
<div :style="{ display: 'grid', gridTemplateColumns: '7fr 2fr 1fr', gap: '4px', height: '80px', marginTop: '32px' }"> <div class="color-ratio-bar" :style="{ display: 'grid', gap: '4px', height: '80px', marginTop: '32px' }">
<div :style="{ background: C.paper, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">70 · paper / bone</div> <div :style="{ background: C.paper, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">70 · paper / bone</div>
<div :style="{ background: C.carbon, color: C.bone, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: mono, fontSize: '11px' }">20 · carbon</div> <div :style="{ background: C.carbon, color: C.bone, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: mono, fontSize: '11px' }">20 · carbon</div>
<div :style="{ background: C.signal, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: mono, fontSize: '11px', color: C.carbon }">10</div> <div :style="{ background: C.signal, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: mono, fontSize: '11px', color: C.carbon }">10</div>
@@ -325,9 +325,9 @@ const doDont: DoDont[] = [
<div :style="eyebrow">03 · Typography</div> <div :style="eyebrow">03 · Typography</div>
<h1 :style="h1">Inter Tight for voice. JetBrains Mono for evidence.</h1> <h1 :style="h1">Inter Tight for voice. JetBrains Mono for evidence.</h1>
<p :style="pStyle(620, true)">Inter Tight carries the brand's confident, modern register used for everything from hero copy to subheadings. JetBrains Mono is reserved for the wordmark, labels, code, data, and quantitative details anywhere the brand wants to feel exact.</p> <p :style="pStyle(620, true)">Inter Tight carries the brand's confident, modern register used for everything from hero copy to subheadings. JetBrains Mono is reserved for the wordmark, labels, code, data, and quantitative details anywhere the brand wants to feel exact.</p>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '48px', marginTop: '80px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '48px', marginTop: '80px' }">
<div> <div>
<div :style="{ background: C.fog, borderRadius: '4px', padding: '48px 32px', fontFamily: tight, fontWeight: 500, fontSize: '220px', lineHeight: 0.9, letterSpacing: '-0.04em', color: C.carbon }">Aa</div> <div :style="{ background: C.fog, borderRadius: '4px', padding: '48px 32px', fontFamily: tight, fontWeight: 500, fontSize: 'clamp(80px, 18vw, 220px)', lineHeight: 0.9, letterSpacing: '-0.04em', color: C.carbon }">Aa</div>
<div :style="{ marginTop: '24px' }"> <div :style="{ marginTop: '24px' }">
<div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '24px', letterSpacing: '-0.02em' }">Inter Tight</div> <div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '24px', letterSpacing: '-0.02em' }">Inter Tight</div>
<div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)', marginTop: '4px' }">Display · UI · prose · 400 · 500 · 600 · 700</div> <div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)', marginTop: '4px' }">Display · UI · prose · 400 · 500 · 600 · 700</div>
@@ -335,7 +335,7 @@ const doDont: DoDont[] = [
</div> </div>
</div> </div>
<div> <div>
<div :style="{ background: C.fog, borderRadius: '4px', padding: '48px 32px', fontFamily: mono, fontWeight: 500, fontSize: '220px', lineHeight: 0.9, letterSpacing: '-0.04em', color: C.carbon }">Aa</div> <div :style="{ background: C.fog, borderRadius: '4px', padding: '48px 32px', fontFamily: mono, fontWeight: 500, fontSize: 'clamp(80px, 18vw, 220px)', lineHeight: 0.9, letterSpacing: '-0.04em', color: C.carbon }">Aa</div>
<div :style="{ marginTop: '24px' }"> <div :style="{ marginTop: '24px' }">
<div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '24px', letterSpacing: '-0.02em' }">JetBrains Mono</div> <div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '24px', letterSpacing: '-0.02em' }">JetBrains Mono</div>
<div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)', marginTop: '4px' }">Wordmark · code · labels · 400 · 500 · 600</div> <div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)', marginTop: '4px' }">Wordmark · code · labels · 400 · 500 · 600</div>
@@ -350,7 +350,7 @@ const doDont: DoDont[] = [
<div :style="eyebrow">03.1 · Scale</div> <div :style="eyebrow">03.1 · Scale</div>
<h2 :style="h2">One scale, ratio 1.25.</h2> <h2 :style="h2">One scale, ratio 1.25.</h2>
<div :style="{ display: 'flex', flexDirection: 'column', gap: '18px' }"> <div :style="{ display: 'flex', flexDirection: 'column', gap: '18px' }">
<div v-for="r in typeScale" :key="r.token" :style="{ display: 'grid', gridTemplateColumns: '120px 80px 1fr 1fr', gap: '24px', alignItems: 'baseline', borderBottom: `1px solid ${C.fog}`, paddingBottom: '14px' }"> <div v-for="r in typeScale" :key="r.token" class="type-scale-row" :style="{ display: 'grid', gap: '24px', alignItems: 'baseline', borderBottom: `1px solid ${C.fog}`, paddingBottom: '14px' }">
<div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">{{ r.token }}</div> <div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">{{ r.token }}</div>
<div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">{{ r.px }} / {{ Math.round(r.px * 1.25) }}</div> <div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">{{ r.px }} / {{ Math.round(r.px * 1.25) }}</div>
<div :style="{ fontFamily: tight, fontWeight: r.weight, fontSize: `${Math.min(r.px, 40)}px`, letterSpacing: r.px > 40 ? '-0.025em' : '-0.01em', lineHeight: 1 }">dezky</div> <div :style="{ fontFamily: tight, fontWeight: r.weight, fontSize: `${Math.min(r.px, 40)}px`, letterSpacing: r.px > 40 ? '-0.025em' : '-0.01em', lineHeight: 1 }">dezky</div>
@@ -364,7 +364,7 @@ const doDont: DoDont[] = [
<div :style="eyebrow">04 · Voice</div> <div :style="eyebrow">04 · Voice</div>
<h1 :style="h1">Direct. Lowercase. Earned.</h1> <h1 :style="h1">Direct. Lowercase. Earned.</h1>
<p :style="pStyle(620, true)">dezky doesn't shout — it ships. Sentences are short, verbs do the work, and we talk about sovereignty in plain terms. No exclamation points, no emoji, no AI metaphors, no fear-mongering.</p> <p :style="pStyle(620, true)">dezky doesn't shout — it ships. Sentences are short, verbs do the work, and we talk about sovereignty in plain terms. No exclamation points, no emoji, no AI metaphors, no fear-mongering.</p>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '32px', marginTop: '80px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '32px', marginTop: '80px' }">
<div> <div>
<div :style="{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }"> <div :style="{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }">
<div :style="{ width: '18px', height: '18px', borderRadius: '999px', background: C.ok, color: '#fff', fontSize: '11px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }">✓</div> <div :style="{ width: '18px', height: '18px', borderRadius: '999px', background: C.ok, color: '#fff', fontSize: '11px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }">✓</div>
@@ -385,7 +385,7 @@ const doDont: DoDont[] = [
</div> </div>
</div> </div>
<h3 :style="h3Style()">Tone shifts by surface</h3> <h3 :style="h3Style()">Tone shifts by surface</h3>
<table :style="{ width: '100%', borderCollapse: 'collapse', fontFamily: inter }"> <div :style="{ overflowX: 'auto' }"><table :style="{ width: '100%', borderCollapse: 'collapse', fontFamily: inter }">
<thead> <thead>
<tr :style="{ borderBottom: `1px solid ${C.carbon}` }"> <tr :style="{ borderBottom: `1px solid ${C.carbon}` }">
<th v-for="hh in ['Surface', 'Tone', 'Example']" :key="hh" :style="{ textAlign: 'left', padding: '12px 0', fontSize: '11px', fontFamily: mono, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.55)' }">{{ hh }}</th> <th v-for="hh in ['Surface', 'Tone', 'Example']" :key="hh" :style="{ textAlign: 'left', padding: '12px 0', fontSize: '11px', fontFamily: mono, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(0,0,0,0.55)' }">{{ hh }}</th>
@@ -398,7 +398,7 @@ const doDont: DoDont[] = [
<td :style="{ padding: '14px 0', fontFamily: inter, fontSize: '14px' }">{{ row[2] }}</td> <td :style="{ padding: '14px 0', fontFamily: inter, fontSize: '14px' }">{{ row[2] }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table></div>
</section> </section>
<!-- 05 · APPLICATIONS --> <!-- 05 · APPLICATIONS -->
@@ -406,14 +406,14 @@ const doDont: DoDont[] = [
<div :style="eyebrow">05 · Applications</div> <div :style="eyebrow">05 · Applications</div>
<h1 :style="h1">In the world.</h1> <h1 :style="h1">In the world.</h1>
<p :style="pStyle(560, true)">Reference renders across the surfaces dezky lives on. Treat them as the canonical reductions of the system.</p> <p :style="pStyle(560, true)">Reference renders across the surfaces dezky lives on. Treat them as the canonical reductions of the system.</p>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '64px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '24px', marginTop: '64px' }">
<div :style="frame(C.carbon, 420)"> <div :style="frame(C.carbon, 420)">
<div :style="{ width: '220px', height: '220px', background: C.carbon, borderRadius: '50px', boxShadow: '0 24px 80px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.04)', display: 'flex', alignItems: 'center', justifyContent: 'center' }"> <div :style="{ width: 'min(100%, 220px)', height: '220px', background: C.carbon, borderRadius: '50px', boxShadow: '0 24px 80px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.04)', display: 'flex', alignItems: 'center', justifyContent: 'center' }">
<BrandNodeMark :size="170" :fg="C.carbon" :accent="C.signal" /> <BrandNodeMark :size="170" :fg="C.carbon" :accent="C.signal" />
</div> </div>
</div> </div>
<div :style="frame(C.paper, 420)"> <div :style="frame(C.paper, 420)">
<div :style="{ display: 'flex', alignItems: 'center', gap: '48px' }"> <div :style="{ display: 'flex', alignItems: 'center', gap: '48px', flexWrap: 'wrap', justifyContent: 'center' }">
<div v-for="s in [64, 32, 16]" :key="s" :style="{ textAlign: 'center' }"> <div v-for="s in [64, 32, 16]" :key="s" :style="{ textAlign: 'center' }">
<BrandNodeMark :size="s" :fg="C.carbon" :accent="C.signal" /> <BrandNodeMark :size="s" :fg="C.carbon" :accent="C.signal" />
<div :style="{ fontFamily: mono, fontSize: '10px', color: 'rgba(0,0,0,0.5)', marginTop: '14px' }">{{ s }} × {{ s }}</div> <div :style="{ fontFamily: mono, fontSize: '10px', color: 'rgba(0,0,0,0.5)', marginTop: '14px' }">{{ s }} × {{ s }}</div>
@@ -421,7 +421,7 @@ const doDont: DoDont[] = [
</div> </div>
</div> </div>
</div> </div>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '8px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: '24px', marginTop: '8px' }">
<div :style="caption">iOS app icon · 1024 master</div> <div :style="caption">iOS app icon · 1024 master</div>
<div :style="caption">Favicon set</div> <div :style="caption">Favicon set</div>
</div> </div>
@@ -431,16 +431,16 @@ const doDont: DoDont[] = [
<section :style="{ ...page, background: C.bone }"> <section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">05.1 · Web</div> <div :style="eyebrow">05.1 · Web</div>
<h2 :style="h2">Marketing hero.</h2> <h2 :style="h2">Marketing hero.</h2>
<div :style="{ background: C.carbon, borderRadius: '8px', padding: '64px 56px', color: C.bone, boxShadow: '0 24px 80px rgba(0,0,0,0.2)' }"> <div :style="{ background: C.carbon, borderRadius: '8px', padding: 'clamp(32px, 5vw, 64px) clamp(20px, 4vw, 56px)', color: C.bone, boxShadow: '0 24px 80px rgba(0,0,0,0.2)' }">
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '80px' }"> <div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '80px', flexWrap: 'wrap', gap: '16px' }">
<BrandNodeLockup :scale="0.7" :fg="C.carbon" :accent="C.signal" :wordmark="C.bone" /> <BrandNodeLockup :scale="0.7" :fg="C.carbon" :accent="C.signal" :wordmark="C.bone" />
<div :style="{ display: 'flex', gap: '28px', alignItems: 'center', fontFamily: mono, fontSize: '12px', color: 'rgba(255,255,255,0.65)' }"> <div :style="{ display: 'flex', gap: '28px', alignItems: 'center', fontFamily: mono, fontSize: '12px', color: 'rgba(255,255,255,0.65)', flexWrap: 'wrap' }">
<span>product</span><span>security</span><span>pricing</span><span>log in</span> <span>product</span><span>security</span><span>pricing</span><span>log in</span>
<span :style="{ background: C.signal, color: C.carbon, padding: '6px 14px', borderRadius: '4px', fontWeight: 600 }">book a demo</span> <span :style="{ background: C.signal, color: C.carbon, padding: '6px 14px', borderRadius: '4px', fontWeight: 600 }">book a demo</span>
</div> </div>
</div> </div>
<div :style="{ fontFamily: mono, fontSize: '12px', color: C.signal, letterSpacing: '0.08em' }">// sovereign productivity · v1.0</div> <div :style="{ fontFamily: mono, fontSize: '12px', color: C.signal, letterSpacing: '0.08em' }">// sovereign productivity · v1.0</div>
<h1 :style="{ fontFamily: tight, fontWeight: 600, fontSize: '92px', letterSpacing: '-0.035em', lineHeight: 0.95, margin: '24px 0', maxWidth: '1000px' }"> <h1 :style="{ fontFamily: tight, fontWeight: 600, fontSize: 'clamp(36px, 8vw, 92px)', letterSpacing: '-0.035em', lineHeight: 0.95, margin: '24px 0', maxWidth: '1000px' }">
Your digital workplace.<br> Your digital workplace.<br>
<span :style="{ color: C.signal }">Data that stays in the EU.</span> <span :style="{ color: C.signal }">Data that stays in the EU.</span>
</h1> </h1>
@@ -452,16 +452,16 @@ const doDont: DoDont[] = [
<section :style="{ ...page, background: C.paper }"> <section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">05.2 · Social</div> <div :style="eyebrow">05.2 · Social</div>
<h2 :style="h2">Avatars & headers.</h2> <h2 :style="h2">Avatars & headers.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: '180px 1fr', gap: '24px', alignItems: 'start' }"> <div class="social-grid" :style="{ display: 'grid', gap: '24px', alignItems: 'start' }">
<div> <div>
<div :style="{ width: '180px', height: '180px', borderRadius: '50%', background: C.carbon, display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }"> <div :style="{ width: 'min(100%, 180px)', height: '180px', borderRadius: '50%', background: C.carbon, display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.05)' }">
<BrandNodeMark :size="130" :fg="C.carbon" :accent="C.signal" /> <BrandNodeMark :size="130" :fg="C.carbon" :accent="C.signal" />
</div> </div>
<div :style="caption">Avatar · circular crop</div> <div :style="caption">Avatar · circular crop</div>
</div> </div>
<div> <div>
<div :style="{ height: '320px', background: C.signal, borderRadius: '8px', position: 'relative', overflow: 'hidden', display: 'flex', alignItems: 'center', padding: '0 64px' }"> <div :style="{ height: '320px', background: C.signal, borderRadius: '8px', position: 'relative', overflow: 'hidden', display: 'flex', alignItems: 'center', padding: '0 64px' }">
<div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '88px', letterSpacing: '-0.035em', lineHeight: 0.95, color: C.carbon, maxWidth: '60%' }">your data<br>stays home.</div> <div :style="{ fontFamily: tight, fontWeight: 600, fontSize: 'clamp(36px, 8vw, 88px)', letterSpacing: '-0.035em', lineHeight: 0.95, color: C.carbon, maxWidth: '60%' }">your data<br>stays home.</div>
<div :style="{ position: 'absolute', right: '64px', top: '50%', transform: 'translateY(-50%)' }"> <div :style="{ position: 'absolute', right: '64px', top: '50%', transform: 'translateY(-50%)' }">
<BrandNodeMark :size="220" :fg="C.signal" :accent="C.carbon" /> <BrandNodeMark :size="220" :fg="C.signal" :accent="C.carbon" />
</div> </div>
@@ -472,10 +472,10 @@ const doDont: DoDont[] = [
</section> </section>
<!-- Closing --> <!-- Closing -->
<section :style="{ background: C.carbon, color: C.bone, padding: '120px 80px', minHeight: '60vh', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }"> <section :style="{ background: C.carbon, color: C.bone, padding: 'clamp(56px, 8vw, 120px) clamp(20px, 5vw, 80px)', minHeight: '60vh', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }">
<div> <div>
<div :style="eyebrow">End</div> <div :style="eyebrow">End</div>
<h1 :style="{ fontFamily: tight, fontWeight: 600, fontSize: '96px', letterSpacing: '-0.04em', lineHeight: 0.95, margin: '24px 0 0', maxWidth: '900px' }"> <h1 :style="{ fontFamily: tight, fontWeight: 600, fontSize: 'clamp(28px, 10vw, 96px)', letterSpacing: '-0.04em', lineHeight: 0.95, margin: '24px 0 0', maxWidth: '900px' }">
Use it well.<br> Use it well.<br>
<span :style="{ color: C.signal }">Don't redraw it.</span> <span :style="{ color: C.signal }">Don't redraw it.</span>
</h1> </h1>
@@ -486,3 +486,69 @@ const doDont: DoDont[] = [
</section> </section>
</div> </div>
</template> </template>
<style scoped>
/* 01 Logo — 2fr 1fr main grid */
.logo-main-grid {
grid-template-columns: 2fr 1fr;
}
@media (max-width: 768px) {
.logo-main-grid {
grid-template-columns: 1fr;
}
}
/* 01 Logo — 2fr 1fr 1fr caption row */
.logo-caption-row {
grid-template-columns: 2fr 1fr 1fr;
}
@media (max-width: 768px) {
.logo-caption-row {
grid-template-columns: 1fr;
}
}
/* 01.1 Anatomy — 1.3fr 1fr grid */
.anatomy-grid {
grid-template-columns: 1.3fr 1fr;
}
@media (max-width: 768px) {
.anatomy-grid {
grid-template-columns: 1fr;
}
}
/* 02.1 Color ratio bar — 7fr 2fr 1fr */
.color-ratio-bar {
grid-template-columns: 7fr 2fr 1fr;
}
@media (max-width: 768px) {
.color-ratio-bar {
grid-template-columns: 1fr;
height: auto;
}
.color-ratio-bar > div {
min-height: 48px;
}
}
/* 03.1 Type scale rows — 120px 80px 1fr 1fr */
.type-scale-row {
grid-template-columns: 120px 80px 1fr 1fr;
}
@media (max-width: 768px) {
.type-scale-row {
grid-template-columns: 1fr;
}
}
/* 05.2 Social — 180px 1fr */
.social-grid {
grid-template-columns: 180px 1fr;
}
@media (max-width: 768px) {
.social-grid {
grid-template-columns: 1fr;
}
}
</style>
+15 -2
View File
@@ -13,11 +13,12 @@ useHead({ title: () => `${copy.value.pages.changelog.label} · dezky` })
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<LandingContainer pad="56px 64px 160px"> <LandingContainer pad="clamp(56px, 8vw, 56px) clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<div :style="{ maxWidth: '760px' }"> <div :style="{ maxWidth: '760px' }">
<div <div
v-for="(entry, i) in c.entries" :key="i" v-for="(entry, i) in c.entries" :key="i"
:style="{ display: 'grid', gridTemplateColumns: '160px 1fr', gap: '32px', padding: '32px 0', borderTop: `1px solid ${t.border}`, alignItems: 'start' }" class="changelog-entry"
:style="{ display: 'grid', gap: '32px', padding: '32px 0', borderTop: `1px solid ${t.border}`, alignItems: 'start' }"
> >
<div> <div>
<div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '20px', color: t.fg, letterSpacing: '-0.015em' }">{{ entry[0] }}</div> <div :style="{ fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '20px', color: t.fg, letterSpacing: '-0.015em' }">{{ entry[0] }}</div>
@@ -36,3 +37,15 @@ useHead({ title: () => `${copy.value.pages.changelog.label} · dezky` })
</div> </div>
</LandingContainer> </LandingContainer>
</template> </template>
<style scoped>
.changelog-entry {
grid-template-columns: 160px 1fr;
}
@media (max-width: 768px) {
.changelog-entry {
grid-template-columns: 1fr;
}
}
</style>
+2 -2
View File
@@ -15,8 +15,8 @@ useHead({ title: () => `${copy.value.pages.contact.label} · dezky` })
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<LandingContainer pad="56px 64px 160px"> <LandingContainer pad="clamp(56px, 8vw, 56px) clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '24px', maxWidth: '760px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 320px), 1fr))', gap: '24px', maxWidth: '760px' }">
<a <a
:href="`mailto:${c.email}`" :href="`mailto:${c.email}`"
:style="{ display: 'block', padding: '28px', border: `1px solid ${t.border}`, borderRadius: '4px', background: t.surface }" :style="{ display: 'block', padding: '28px', border: `1px solid ${t.border}`, borderRadius: '4px', background: t.surface }"
+14 -2
View File
@@ -19,7 +19,7 @@ useHead({ title: () => `${copy.value.pages.dpa.title} · dezky` })
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<LandingContainer pad="48px 64px 160px"> <LandingContainer pad="clamp(32px, 5vw, 48px) clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<div :style="{ maxWidth: '760px' }"> <div :style="{ maxWidth: '760px' }">
<!-- Draft / legal-review banner --> <!-- Draft / legal-review banner -->
<div :style="{ padding: '18px 22px', borderRadius: '4px', border: `1px solid ${C.warn}55`, background: `${C.warn}14`, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', lineHeight: 1.55, color: t.fg }"> <div :style="{ padding: '18px 22px', borderRadius: '4px', border: `1px solid ${C.warn}55`, background: `${C.warn}14`, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', lineHeight: 1.55, color: t.fg }">
@@ -41,7 +41,8 @@ useHead({ title: () => `${copy.value.pages.dpa.title} · dezky` })
<div :style="{ border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden', margin: '0 0 40px' }"> <div :style="{ border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden', margin: '0 0 40px' }">
<div <div
v-for="(row, i) in c.subprocessors" :key="i" v-for="(row, i) in c.subprocessors" :key="i"
:style="{ display: 'grid', gridTemplateColumns: '1.2fr 1.5fr 1fr', gap: '16px', padding: '16px 20px', borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, background: t.surface, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', alignItems: 'baseline' }" class="subprocessor-row"
:style="{ display: 'grid', gap: '16px', padding: '16px 20px', borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, background: t.surface, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', alignItems: 'baseline' }"
> >
<span :style="{ color: t.fg, fontWeight: 600 }">{{ row[0] }}</span> <span :style="{ color: t.fg, fontWeight: 600 }">{{ row[0] }}</span>
<span :style="{ color: t.fgMuted }">{{ row[1] }}</span> <span :style="{ color: t.fgMuted }">{{ row[1] }}</span>
@@ -67,3 +68,14 @@ useHead({ title: () => `${copy.value.pages.dpa.title} · dezky` })
</div> </div>
</LandingContainer> </LandingContainer>
</template> </template>
<style scoped>
.subprocessor-row {
grid-template-columns: 1.2fr 1.5fr 1fr;
}
@media (max-width: 768px) {
.subprocessor-row {
grid-template-columns: 1fr;
}
}
</style>
+2 -2
View File
@@ -15,8 +15,8 @@ useHead({ title: () => `${copy.value.pages.migration.label} · dezky` })
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<LandingContainer pad="56px 64px 80px"> <LandingContainer pad="clamp(56px, 8vw, 56px) clamp(20px, 5vw, 64px) clamp(56px, 8vw, 80px)">
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '40px' }"> <div :style="{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: '40px' }">
<div v-for="(step, i) in c.steps" :key="i"> <div v-for="(step, i) in c.steps" :key="i">
<div :style="{ paddingTop: '20px', borderTop: `1px solid ${t.borderStrong}` }"> <div :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="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', color: t.fgDim, letterSpacing: '0.06em' }">step {{ step[0] }}</div>
+78 -19
View File
@@ -22,9 +22,9 @@ useHead({ title: () => `${copy.value.pages.partners.label} · dezky` })
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<!-- What you get --> <!-- What you get -->
<LandingContainer pad="56px 64px 0"> <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="{ 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(2, 1fr)', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }"> <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 <div
v-for="(b, i) in c.benefits" :key="i" v-for="(b, i) in c.benefits" :key="i"
:style="{ :style="{
@@ -40,8 +40,8 @@ useHead({ title: () => `${copy.value.pages.partners.label} · dezky` })
</LandingContainer> </LandingContainer>
<!-- Margin calculator --> <!-- Margin calculator -->
<LandingContainer pad="72px 64px 0"> <LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '40px', alignItems: 'end', marginBottom: '32px' }"> <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> <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 :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.calc.label }}</div>
</div> </div>
@@ -50,35 +50,40 @@ useHead({ title: () => `${copy.value.pages.partners.label} · dezky` })
</LandingContainer> </LandingContainer>
<!-- CSP vs Dezky comparison --> <!-- CSP vs Dezky comparison -->
<LandingContainer pad="72px 64px 0"> <LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '40px', alignItems: 'end', marginBottom: '32px' }"> <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> <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 :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.compare.label }}</div>
</div> </div>
<div :style="{ border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }"> <div :style="{ border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }">
<div :style="{ display: 'grid', gridTemplateColumns: '1.3fr 1fr 1fr', background: t.surface, borderBottom: `1px solid ${t.borderStrong}` }"> <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' }" />
<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', 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 :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>
<div <div
v-for="(row, i) in c.compare.rows" :key="i" v-for="(row, i) in c.compare.rows" :key="i"
:style="{ display: 'grid', gridTemplateColumns: '1.3fr 1fr 1fr', borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, fontFamily: '\'Inter\', sans-serif', fontSize: '15px' }" class="partners-compare-grid"
:style="{ borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, fontFamily: '\'Inter\', sans-serif', fontSize: '15px' }"
> >
<div :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-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 :style="{ padding: '18px 24px', color: t.fgMuted }">{{ row[1] }}</div> <div class="pc-val" :style="{ padding: '18px 24px', color: t.fgMuted }">
<div :style="{ padding: '18px 24px', color: t.fg, fontWeight: 600, background: `${t.signal}11` }">{{ row[2] }}</div> <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>
</div> </div>
</LandingContainer> </LandingContainer>
<!-- Partner tiers --> <!-- Partner tiers -->
<LandingContainer pad="72px 64px 0"> <LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '40px', alignItems: 'end', marginBottom: '32px' }"> <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> <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 :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.tiers.label }}</div>
</div> </div>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }"> <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 <div
v-for="(tier, i) in c.tiers.items" :key="i" 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' }" :style="{ padding: '32px 28px', background: t.surface, borderLeft: i === 0 ? 'none' : `1px solid ${t.border}`, display: 'flex', flexDirection: 'column', gap: '20px' }"
@@ -106,9 +111,9 @@ useHead({ title: () => `${copy.value.pages.partners.label} · dezky` })
</LandingContainer> </LandingContainer>
<!-- How to get started --> <!-- How to get started -->
<LandingContainer pad="72px 64px 0"> <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="{ 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(3, 1fr)', gap: '40px' }"> <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 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="{ 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> <div :style="{ marginTop: '20px', fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '24px', color: t.fg, letterSpacing: '-0.02em' }">{{ step[1] }}</div>
@@ -118,8 +123,8 @@ useHead({ title: () => `${copy.value.pages.partners.label} · dezky` })
</LandingContainer> </LandingContainer>
<!-- Partner FAQ --> <!-- Partner FAQ -->
<LandingContainer pad="72px 64px 0"> <LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) 0">
<div :style="{ display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: '40px', alignItems: 'end', marginBottom: '32px' }"> <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> <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 :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ c.faq.label }}</div>
</div> </div>
@@ -141,7 +146,61 @@ useHead({ title: () => `${copy.value.pages.partners.label} · dezky` })
</LandingContainer> </LandingContainer>
<!-- CTA --> <!-- CTA -->
<LandingContainer pad="72px 64px 160px"> <LandingContainer pad="clamp(48px, 7vw, 72px) clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<LandingBtn variant="primary" size="lg" @click="goToSection('#final-cta', route.path)">{{ c.cta }} </LandingBtn> <LandingBtn variant="primary" size="lg" @click="goToSection('#final-cta', route.path)">{{ c.cta }} </LandingBtn>
</LandingContainer> </LandingContainer>
</template> </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;
}
/* 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;
}
/* 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>
+16 -2
View File
@@ -19,7 +19,7 @@ useHead({ title: () => `${copy.value.pages.privacy.title} · dezky` })
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<LandingContainer pad="48px 64px 160px"> <LandingContainer pad="48px clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<div :style="{ maxWidth: '760px' }"> <div :style="{ maxWidth: '760px' }">
<!-- Draft / legal-review banner --> <!-- Draft / legal-review banner -->
<div :style="{ padding: '18px 22px', borderRadius: '4px', border: `1px solid ${C.warn}55`, background: `${C.warn}14`, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', lineHeight: 1.55, color: t.fg }"> <div :style="{ padding: '18px 22px', borderRadius: '4px', border: `1px solid ${C.warn}55`, background: `${C.warn}14`, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', lineHeight: 1.55, color: t.fg }">
@@ -41,7 +41,8 @@ useHead({ title: () => `${copy.value.pages.privacy.title} · dezky` })
<div :style="{ border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden', margin: '0 0 40px' }"> <div :style="{ border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden', margin: '0 0 40px' }">
<div <div
v-for="(row, i) in c.recipients" :key="i" v-for="(row, i) in c.recipients" :key="i"
:style="{ display: 'grid', gridTemplateColumns: '1.2fr 1.5fr 1fr', gap: '16px', padding: '16px 20px', borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, background: t.surface, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', alignItems: 'baseline' }" class="recipients-row"
:style="{ gap: '16px', padding: '16px 20px', borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, background: t.surface, fontFamily: '\'Inter\', sans-serif', fontSize: '14px', alignItems: 'baseline' }"
> >
<span :style="{ color: t.fg, fontWeight: 600 }">{{ row[0] }}</span> <span :style="{ color: t.fg, fontWeight: 600 }">{{ row[0] }}</span>
<span :style="{ color: t.fgMuted }">{{ row[1] }}</span> <span :style="{ color: t.fgMuted }">{{ row[1] }}</span>
@@ -67,3 +68,16 @@ useHead({ title: () => `${copy.value.pages.privacy.title} · dezky` })
</div> </div>
</LandingContainer> </LandingContainer>
</template> </template>
<style scoped>
.recipients-row {
display: grid;
grid-template-columns: 1.2fr 1.5fr 1fr;
}
@media (max-width: 768px) {
.recipients-row {
grid-template-columns: 1fr;
}
}
</style>
+24 -3
View File
@@ -13,11 +13,13 @@ useHead({ title: () => `${copy.value.pages.roadmap.label} · dezky` })
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" />
<LandingContainer pad="56px 64px 160px"> <LandingContainer pad="clamp(56px, 8vw, 56px) clamp(20px, 5vw, 64px) clamp(80px, 12vw, 160px)">
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden' }"> <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 <div
v-for="(col, i) in c.columns" :key="i" v-for="(col, i) in c.columns" :key="i"
:style="{ padding: '32px 28px', borderLeft: i === 0 ? 'none' : `1px solid ${t.border}`, background: t.surface, minHeight: '260px' }" class="roadmap-col"
:class="{ 'roadmap-col--first': i === 0 }"
:style="{ padding: '32px 28px', background: t.surface, minHeight: '260px', '--roadmap-border': t.border }"
> >
<div :style="{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '24px' }"> <div :style="{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '24px' }">
<span :style="{ width: '6px', height: '6px', borderRadius: '999px', background: i === 0 ? t.signal : t.fgDim }" /> <span :style="{ width: '6px', height: '6px', borderRadius: '999px', background: i === 0 ? t.signal : t.fgDim }" />
@@ -33,3 +35,22 @@ useHead({ title: () => `${copy.value.pages.roadmap.label} · dezky` })
</div> </div>
</LandingContainer> </LandingContainer>
</template> </template>
<style scoped>
.roadmap-col {
border-left: 1px solid var(--roadmap-border);
border-top: none;
}
.roadmap-col--first {
border-left: none;
}
@media (max-width: 768px) {
.roadmap-col {
border-left: none;
border-top: 1px solid var(--roadmap-border);
}
.roadmap-col--first {
border-top: none;
}
}
</style>