feat(website): brand guide page + NodeMark/NodeLockup variants

Replace the /brand stub with the full Brand Guide from the Claude Design
handoff (cover, logo, color, typography, voice, applications), rendered inside
the site layout, English-only, with illustrative copy adapted to Dezky's real
EU-sovereign voice. Extend NodeMark with a `variant` (donut/solid/outline) and
NodeLockup with a separate `wordmark` colour so the mark reads correctly on
dark surfaces. Drops the old brand stub copy.
This commit is contained in:
Ronni Baslund
2026-06-05 22:09:36 +02:00
parent ed660b9a81
commit f447b13c83
4 changed files with 519 additions and 64 deletions
+5 -1
View File
@@ -7,6 +7,10 @@ const props = withDefaults(defineProps<{
scale?: number scale?: number
fg?: string fg?: string
accent?: string accent?: string
// Wordmark colour. Defaults to `fg`; pass separately when the squircle and
// the wordmark need different colours (e.g. on a dark surface the squircle is
// carbon — invisible — while the wordmark must stay light).
wordmark?: string
}>(), { }>(), {
scale: 1, scale: 1,
fg: C.carbon, fg: C.carbon,
@@ -22,7 +26,7 @@ const props = withDefaults(defineProps<{
fontWeight: 600, fontWeight: 600,
fontSize: `${56 * scale * 0.78}px`, fontSize: `${56 * scale * 0.78}px`,
letterSpacing: '-0.04em', letterSpacing: '-0.04em',
color: fg, color: wordmark || fg,
lineHeight: 0.9, lineHeight: 0.9,
}">dezky</span> }">dezky</span>
</div> </div>
+38 -5
View File
@@ -1,18 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
// Dezky "Node" mark — a lowercase d (donut style) inside a squircle, with a // Dezky "Node" mark — a lowercase d inside a squircle, with a corner node-dot.
// corner node-dot. Geometry is the locked set from the brand handoff // Geometry is the locked set from the brand handoff (logos.jsx NodeMark +
// (logos.jsx NodeMark + LOCKED). The squircle paints in `fg`; the letterform // LOCKED). The squircle paints in `fg`; the letterform and dot paint in
// and dot paint in `accent` (electric chartreuse) — the design's intent. // `accent` (electric chartreuse). `variant` controls the bowl rendering:
// donut/solid paint the filled letter (with counter); outline knocks out an
// eroded copy so only a uniform rim survives.
import { computed } from 'vue'
import { C, LOCKED } from '~/utils/landingTokens' import { C, LOCKED } from '~/utils/landingTokens'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
size?: number size?: number
fg?: string fg?: string
accent?: string accent?: string
variant?: 'donut' | 'solid' | 'outline'
}>(), { }>(), {
size: 96, size: 96,
fg: C.carbon, fg: C.carbon,
accent: C.signal, accent: C.signal,
variant: 'donut',
}) })
const { bowlR, stemW, dotR, contR } = LOCKED const { bowlR, stemW, dotR, contR } = LOCKED
@@ -40,12 +45,40 @@ const stemPath =
`a ${capR} ${capR} 0 0 1 ${stemW} 0 ` + `a ${capR} ${capR} 0 0 1 ${stemW} 0 ` +
`L ${stemRight} ${stemBottom} ` + `L ${stemRight} ${stemBottom} ` +
`L ${stemX} ${stemBottom} Z` `L ${stemX} ${stemBottom} Z`
// Eroded inner copy for the outline variant (a uniform rim survives).
const outline = computed(() => {
const maxOw = (overlap - 1.4) / 2
const ow = Math.max(1.1, Math.min(stemW * 0.32, maxOw))
const ibowlR = bowlR - ow
const iholeR = holeR + ow
const istemX = stemX + ow
const istemW = Math.max(1.2, stemW - 2 * ow)
const istemRight = istemX + istemW
const icapR = istemW / 2
const istemTop = stemTop + ow
return {
bowl: `M ${cx - ibowlR} ${cy} a ${ibowlR} ${ibowlR} 0 1 0 ${ibowlR * 2} 0 a ${ibowlR} ${ibowlR} 0 1 0 ${-ibowlR * 2} 0 Z`,
counter: `M ${cx - iholeR} ${cy} a ${iholeR} ${iholeR} 0 1 0 ${iholeR * 2} 0 a ${iholeR} ${iholeR} 0 1 0 ${-iholeR * 2} 0 Z`,
stem: `M ${istemX} ${istemTop + icapR} a ${icapR} ${icapR} 0 0 1 ${istemW} 0 L ${istemRight} ${stemBottom} L ${istemX} ${stemBottom} Z`,
}
})
</script> </script>
<template> <template>
<svg :width="size" :height="size" viewBox="0 0 100 100" aria-label="dezky node mark"> <svg :width="size" :height="size" viewBox="0 0 100 100" aria-label="dezky node mark">
<rect x="8" y="8" width="84" height="84" :rx="contR" :fill="fg" /> <rect x="8" y="8" width="84" height="84" :rx="contR" :fill="fg" />
<g :fill="accent"> <template v-if="variant === 'outline'">
<path :d="`${bowlPath} ${counterPath}`" fill-rule="evenodd" :fill="accent" />
<path :d="stemPath" :fill="accent" />
<path :d="`${outline.bowl} ${outline.counter}`" fill-rule="evenodd" :fill="fg" />
<path :d="outline.stem" :fill="fg" />
</template>
<g v-else-if="variant === 'solid'" :fill="accent">
<path :d="bowlPath" />
<path :d="stemPath" />
</g>
<g v-else :fill="accent">
<path :d="`${bowlPath} ${counterPath}`" fill-rule="evenodd" /> <path :d="`${bowlPath} ${counterPath}`" fill-rule="evenodd" />
<path :d="stemPath" /> <path :d="stemPath" />
</g> </g>
+476 -26
View File
@@ -1,38 +1,488 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTheme, useCopy } from '~/composables/useLanding' // Dezky Brand Guide — ported from the Claude Design handoff (guide.jsx).
// Long-scroll document: Cover → Logo → Color → Typography → Voice →
// Applications. English-only by design; rendered inside the site layout (Nav +
// Footer). Illustrative copy adapted to Dezky's real EU-sovereign positioning.
import { C } from '~/utils/landingTokens'
definePageMeta({ layout: 'page' }) definePageMeta({ layout: 'page' })
const t = useTheme() useHead({ title: 'dezky · brand guide v1.0' })
const copy = useCopy()
const c = computed(() => copy.value.pages.brand)
useHead({ title: () => `${copy.value.pages.brand.label} · dezky` }) const mono = '"JetBrains Mono", ui-monospace, monospace'
const tight = '"Inter Tight", "Inter", sans-serif'
const inter = '"Inter", sans-serif'
function pStyle(max = 640, dim = false) {
return { fontFamily: inter, fontSize: '16px', lineHeight: 1.6, maxWidth: `${max}px`, color: dim ? 'rgba(0,0,0,0.65)' : 'currentColor', margin: '0 0 16px', textWrap: 'pretty' as const }
}
function h3Style(mt = 64) {
return { fontFamily: tight, fontWeight: 600, fontSize: '24px', letterSpacing: '-0.02em', lineHeight: 1.15, margin: `${mt}px 0 16px` }
}
function frame(bg: string, height?: number, fg?: string) {
return { background: bg, color: fg, height: height ? `${height}px` : '100%', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' as const, overflow: 'hidden' }
}
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 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 page = { padding: '96px 80px', borderBottom: `1px solid ${C.fog}` }
const bigSwatches = [
{ name: 'Carbon', hex: C.carbon, rgb: '10 10 10', role: 'Foreground · containers · type', dark: true },
{ name: 'Signal', hex: C.signal, rgb: '212 255 58', role: 'Brand accent · CTAs · the node-dot', dark: false },
]
const surfaces = [
{ name: 'Bone', hex: C.bone, role: 'Primary surface' },
{ name: 'Paper', hex: C.paper, role: 'Elevated surface' },
{ name: 'Fog', hex: C.fog, role: 'Dividers · inputs' },
{ name: 'Slate', hex: C.slate, role: 'Secondary type' },
]
const semantic = [
{ name: 'Success', hex: C.ok, role: 'Positive states' },
{ name: 'Warning', hex: C.warn, role: 'Caution' },
{ name: 'Error', hex: C.bad, role: 'Errors · destructive' },
]
const specRows: [string, string][] = [
['Squircle', '84 × 84 unit · radius 22'],
['Bowl', 'full circle · r 14'],
['Stem', '7 wide · rounded ascender cap'],
['Counter', 'r 6 · centered in bowl'],
['Node-dot', 'r 4 · upper-right corner'],
['Clear space', '= squircle corner radius'],
]
const typeScale = [
{ token: 'display-xl', px: 96, weight: 600, label: 'Page hero · landings only' },
{ token: 'display', px: 64, weight: 600, label: 'Section heroes' },
{ token: 'h1', px: 40, weight: 600, label: 'Page titles' },
{ token: 'h2', px: 28, weight: 600, label: 'Section titles' },
{ token: 'h3', px: 20, weight: 600, label: 'Subsections' },
{ token: 'body-lg', px: 18, weight: 400, label: 'Intro paragraphs' },
{ token: 'body', px: 16, weight: 400, label: 'Default body' },
{ token: 'caption', px: 13, weight: 500, label: 'Metadata · labels' },
]
const soundLike = [
'Your data stays in the EU.',
'Mail, files, video, chat — one login.',
'No lock-in. Export everything, anytime.',
'Built in Denmark, under European law.',
'Switch from Microsoft 365 with zero downtime.',
]
const dontSound = [
'Revolutionize your workflow!',
'AI-powered, blazingly-fast platform.',
'We empower teams to unlock potential.',
'Synergy. Holistic. Solutions.',
'Let\'s make magic together ✨',
]
const toneRows: [string, string, string][] = [
['Marketing site', 'Confident, direct', 'Your digital workplace. Data that stays in the EU.'],
['Product UI', 'Plain, instructional', 'Add a user to get started.'],
['Errors', 'Honest, useful', 'Couldn\'t reach the server. We\'re retrying.'],
['Empty states', 'Helpful, slightly dry', 'No files yet. Drag one in or invite your team.'],
['Docs', 'Direct, practical', 'Point your domain\'s MX record at Dezky.'],
]
type DoDont = { ok: boolean, label: string, bg: string, fg: string, accent: string, variant?: 'donut' | 'solid' | 'outline', transform?: string }
const doDont: DoDont[] = [
{ ok: true, label: 'On carbon', bg: C.carbon, fg: C.carbon, accent: C.signal },
{ ok: true, label: 'On signal', bg: C.signal, fg: C.signal, accent: C.carbon },
{ ok: true, label: 'On bone', bg: C.bone, fg: C.carbon, accent: C.signal },
{ ok: true, label: 'Monochrome', bg: C.bone, fg: C.carbon, accent: C.bone },
{ ok: false, label: 'No gradients', bg: 'linear-gradient(135deg,#ff4dcb,#7a5aff)', fg: C.carbon, accent: C.signal },
{ ok: false, label: 'No rotation', bg: C.bone, fg: C.carbon, accent: C.signal, transform: 'rotate(-14deg)' },
{ ok: false, label: 'No stretching', bg: C.bone, fg: C.carbon, accent: C.signal, transform: 'scaleX(1.4)' },
{ ok: false, label: 'No off-brand color', bg: C.bone, fg: C.carbon, accent: '#ff4dcb' },
]
</script> </script>
<template> <template>
<LandingPageHeader :label="c.label" :title="c.title" :intro="c.intro" /> <div :style="{ background: C.paper, color: C.carbon }">
<!-- 00 · COVER -->
<LandingContainer pad="56px 64px 160px"> <section :style="{ background: C.carbon, color: C.bone, padding: '80px 80px', minHeight: '86vh', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', position: 'relative', overflow: 'hidden' }">
<div :style="{ display: 'flex', flexDirection: 'column', gap: '0', border: `1px solid ${t.border}`, borderRadius: '4px', overflow: 'hidden', maxWidth: '820px' }"> <div :style="{ position: 'absolute', right: '-160px', bottom: '-160px', opacity: 0.06 }">
<div <BrandNodeMark :size="720" :fg="C.carbon" :accent="C.signal" />
v-for="(rule, i) in c.rules" :key="i"
:style="{ display: 'grid', gridTemplateColumns: '160px 1fr', gap: '24px', padding: '24px 28px', borderTop: i === 0 ? 'none' : `1px solid ${t.border}`, background: t.surface, alignItems: 'baseline' }"
>
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase' }">{{ rule[0] }}</div>
<div :style="{ fontFamily: '\'Inter\', sans-serif', fontSize: '15px', lineHeight: 1.6, color: t.fg }">{{ rule[1] }}</div>
</div> </div>
</div> <div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', position: 'relative' }">
<div :style="{ display: 'flex', alignItems: 'center', gap: '14px' }">
<div :style="{ marginTop: '48px' }"> <div :style="{ width: '56px', height: '56px', background: C.bone, borderRadius: '14px', display: 'flex', alignItems: 'center', justifyContent: 'center' }">
<div :style="{ fontFamily: '\'JetBrains Mono\', monospace', fontSize: '11px', color: t.fgDim, letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: '20px' }">{{ c.colorsLabel }}</div> <BrandNodeMark :size="44" :fg="C.carbon" :accent="C.signal" />
<div :style="{ display: 'flex', gap: '20px', flexWrap: 'wrap' }"> </div>
<div v-for="(col, i) in c.colors" :key="i" :style="{ width: '180px' }"> <div :style="{ fontFamily: mono, fontWeight: 600, fontSize: '18px', letterSpacing: '-0.02em', color: C.bone }">dezky</div>
<div :style="{ height: '120px', borderRadius: '4px', background: col[1], border: `1px solid ${t.border}` }" /> </div>
<div :style="{ marginTop: '12px', fontFamily: '\'Inter Tight\', sans-serif', fontWeight: 600, fontSize: '15px', color: t.fg }">{{ col[0] }}</div> <div :style="{ textAlign: 'right' }">
<div :style="{ marginTop: '2px', fontFamily: '\'JetBrains Mono\', monospace', fontSize: '12px', color: t.fgMuted }">{{ col[1] }}</div> <div :style="eyebrow">Brand Guide · v1.0</div>
<div :style="{ fontFamily: mono, fontSize: '11px', marginTop: '8px', color: 'rgba(255,255,255,0.5)' }">June 2026</div>
</div> </div>
</div> </div>
</div> <div :style="{ position: 'relative' }">
</LandingContainer> <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%' }">
Quiet software,<br>
<span :style="{ color: C.signal }">sovereign data.</span>
</h1>
</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>dezky · brand system</div>
<div>00 / 06</div>
</div>
</section>
<!-- 01 · LOGO -->
<section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">01 · Logo</div>
<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>
<div :style="{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '24px', marginTop: '64px' }">
<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="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>
</div>
<div :style="{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr', gap: '24px', marginTop: '8px' }">
<div :style="caption">Primary · on carbon</div>
<div :style="caption">Reversed · on signal</div>
<div :style="caption">Light · on bone</div>
</div>
</section>
<!-- 01.1 Anatomy -->
<section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">01.1 · Anatomy</div>
<h2 :style="h2">Constructed, not drawn.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: '64px', alignItems: 'center' }">
<div :style="frame(C.paper, 520)">
<svg viewBox="0 0 360 360" width="380" height="380">
<defs>
<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" />
</pattern>
</defs>
<rect width="360" height="360" fill="url(#grid)" />
<g transform="translate(40, 40) scale(2.8)">
<rect x="8" y="8" width="84" height="84" rx="22" :fill="C.carbon" />
<g :fill="C.signal">
<path d="M 34.425 52 a 14 14 0 1 0 28 0 a 14 14 0 1 0 -28 0 Z M 41.925 52 a 6.5 6.5 0 1 0 13 0 a 6.5 6.5 0 1 0 -13 0 Z" fill-rule="evenodd" />
<path d="M 58.575 29.5 a 3.5 3.5 0 0 1 7 0 L 65.575 66 L 58.575 66 Z" />
<circle cx="74" cy="26" r="4" />
</g>
</g>
<g font-family="'JetBrains Mono', monospace" font-size="9" fill="rgba(0,0,0,0.6)">
<line x1="40" y1="240" x2="320" y2="240" stroke="rgba(0,0,0,0.25)" stroke-dasharray="3,3" />
<text x="324" y="244">baseline</text>
<line x1="40" y1="162" x2="320" y2="162" stroke="rgba(0,0,0,0.25)" stroke-dasharray="3,3" />
<text x="324" y="166">x-height</text>
<line x1="40" y1="106" x2="320" y2="106" stroke="rgba(0,0,0,0.25)" stroke-dasharray="3,3" />
<text x="324" y="110">ascender</text>
<text x="100" y="290" :fill="C.carbon" font-weight="500">r 14 · bowl</text>
<text x="100" y="304" fill="rgba(0,0,0,0.5)">7w · stem</text>
</g>
</svg>
</div>
<div>
<h3 :style="h3Style(0)">Geometry</h3>
<table :style="{ width: '100%', borderCollapse: 'collapse', fontFamily: inter, fontSize: '14px' }">
<tbody>
<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', fontFamily: mono, fontSize: '13px' }">{{ v }}</td>
</tr>
</tbody>
</table>
<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>
</section>
<!-- 01.2 Clear space / min size -->
<section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">01.2 · Clear space · Minimum size</div>
<h2 :style="h2">Give it room.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '32px' }">
<div>
<div :style="frame(C.fog, 360)">
<div :style="{ position: 'relative' }">
<div :style="{ position: 'absolute', inset: '-44px', border: '1px dashed rgba(0,0,0,0.3)', borderRadius: '12px' }" />
<BrandNodeMark :size="140" :fg="C.carbon" :accent="C.signal" />
</div>
</div>
<div :style="caption">Clear space</div>
<p :style="pStyle(520, true)">Maintain clear space equal to <b>half the squircle dimension</b> on every side. No type, edges, or other marks intrude.</p>
</div>
<div>
<div :style="frame(C.fog, 360)">
<div :style="{ display: 'flex', alignItems: 'flex-end', gap: '48px' }">
<div v-for="s in [16, 24, 48, 96]" :key="s" :style="{ textAlign: 'center' }">
<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>
</div>
</div>
<div :style="caption">Minimum sizes</div>
<p :style="pStyle(520, true)"><b>24 px</b> minimum for digital · <b>16 px</b> only as a favicon (drop the node-dot if it disappears). <b>12 mm</b> minimum for print.</p>
</div>
</div>
</section>
<!-- 01.3 Wordmark / lockup -->
<section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">01.3 · Wordmark · Lockup</div>
<h2 :style="h2">Letters set in JetBrains Mono.</h2>
<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>
<div :style="caption">Wordmark · 100% scale</div>
<h3 :style="h3Style()">Horizontal lockup</h3>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px' }">
<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>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '8px' }">
<div :style="caption">Light surface</div>
<div :style="caption">Dark surface</div>
</div>
<p :style="pStyle(520, true)">Gap between mark and wordmark is fixed at <b>0.25× the mark height</b>. Wordmark cap-height aligns with the squircle height — never larger.</p>
</section>
<!-- 01.4 Do / Don't -->
<section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">01.4 · Do · Don't</div>
<h2 :style="h2">How not to use it.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '20px' }">
<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="{ transform: row.transform || 'none' }">
<BrandNodeMark :size="96" :fg="row.fg" :accent="row.accent" :variant="row.variant || 'donut'" />
</div>
<svg v-if="!row.ok" viewBox="0 0 100 100" preserveAspectRatio="none" :style="{ position: 'absolute', inset: 0, width: '100%', height: '100%' }">
<line x1="0" y1="100" x2="100" y2="0" :stroke="C.bad" stroke-width="1.5" />
</svg>
</div>
<div :style="{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '10px' }">
<div :style="{ width: '16px', height: '16px', borderRadius: '999px', background: row.ok ? C.ok : C.bad, color: '#fff', fontSize: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }">{{ row.ok ? '' : '' }}</div>
<div :style="{ fontSize: '12px', fontWeight: 500 }">{{ row.label }}</div>
</div>
</div>
</div>
</section>
<!-- 02 · COLOR -->
<section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">02 · Color</div>
<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>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '80px' }">
<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="{ fontFamily: tight, fontWeight: 600, fontSize: '36px', letterSpacing: '-0.02em' }">{{ c.name }}</div>
</div>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', marginTop: '16px', fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.65)' }">
<div><div :style="{ color: 'rgba(0,0,0,0.4)' }">HEX</div>{{ c.hex }}</div>
<div><div :style="{ color: 'rgba(0,0,0,0.4)' }">RGB</div>{{ c.rgb }}</div>
<div><div :style="{ color: 'rgba(0,0,0,0.4)' }">ROLE</div>{{ c.role }}</div>
</div>
</div>
</div>
<h3 :style="h3Style()">Surfaces & type</h3>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }">
<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="{ marginTop: '12px' }">
<div :style="{ fontFamily: inter, fontWeight: 600, fontSize: '14px' }">{{ c.name }}</div>
<div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">{{ c.hex }}</div>
<div :style="{ fontFamily: inter, fontSize: '12px', color: 'rgba(0,0,0,0.55)', marginTop: '4px', lineHeight: 1.45 }">{{ c.role }}</div>
</div>
</div>
</div>
<h3 :style="h3Style()">Semantic</h3>
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }">
<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="{ marginTop: '12px' }">
<div :style="{ fontFamily: inter, fontWeight: 600, fontSize: '14px' }">{{ c.name }}</div>
<div :style="{ fontFamily: mono, fontSize: '11px', color: 'rgba(0,0,0,0.5)' }">{{ c.hex }}</div>
<div :style="{ fontFamily: inter, fontSize: '12px', color: 'rgba(0,0,0,0.55)', marginTop: '4px', lineHeight: 1.45 }">{{ c.role }}</div>
</div>
</div>
</div>
</section>
<!-- 02.1 Color in use -->
<section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">02.1 · Color in use</div>
<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>
<div :style="{ display: 'grid', gridTemplateColumns: '7fr 2fr 1fr', 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.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>
</section>
<!-- 03 · TYPOGRAPHY -->
<section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">03 · Typography</div>
<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>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '48px', marginTop: '80px' }">
<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="{ marginTop: '24px' }">
<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: tight, fontSize: '15px', marginTop: '16px', color: 'rgba(0,0,0,0.75)', lineHeight: 1.5 }">The quick brown fox jumps over the lazy dog. 0123456789</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="{ marginTop: '24px' }">
<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: '15px', marginTop: '16px', color: 'rgba(0,0,0,0.75)', lineHeight: 1.5 }">data.stays_in_eu() // 0123456789</div>
</div>
</div>
</div>
</section>
<!-- 03.1 Scale -->
<section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">03.1 · Scale</div>
<h2 :style="h2">One scale, ratio 1.25.</h2>
<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 :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: 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: inter, fontSize: '13px', color: 'rgba(0,0,0,0.55)' }">{{ r.label }}</div>
</div>
</div>
</section>
<!-- 04 · VOICE -->
<section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">04 · Voice</div>
<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>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '32px', marginTop: '80px' }">
<div>
<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="{ fontFamily: tight, fontWeight: 600, fontSize: '16px' }">We sound like</div>
</div>
<div :style="{ display: 'flex', flexDirection: 'column', gap: '14px' }">
<div v-for="(s, i) in soundLike" :key="i" :style="{ padding: '14px 18px', background: 'rgba(31,138,91,0.06)', borderLeft: `2px solid ${C.ok}`, fontFamily: tight, fontSize: '16px', color: C.carbon, lineHeight: 1.4 }">{{ s }}</div>
</div>
</div>
<div>
<div :style="{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }">
<div :style="{ width: '18px', height: '18px', borderRadius: '999px', background: C.bad, color: '#fff', fontSize: '11px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 }">✕</div>
<div :style="{ fontFamily: tight, fontWeight: 600, fontSize: '16px' }">We don't sound like</div>
</div>
<div :style="{ display: 'flex', flexDirection: 'column', gap: '14px' }">
<div v-for="(s, i) in dontSound" :key="i" :style="{ padding: '14px 18px', background: 'rgba(226,48,48,0.05)', borderLeft: `2px solid ${C.bad}`, fontFamily: tight, fontSize: '16px', color: C.carbon, lineHeight: 1.4 }">{{ s }}</div>
</div>
</div>
</div>
<h3 :style="h3Style()">Tone shifts by surface</h3>
<table :style="{ width: '100%', borderCollapse: 'collapse', fontFamily: inter }">
<thead>
<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>
</tr>
</thead>
<tbody :style="{ fontSize: '14px', color: 'rgba(0,0,0,0.75)' }">
<tr v-for="(row, i) in toneRows" :key="i" :style="{ borderBottom: `1px solid ${C.fog}` }">
<td :style="{ padding: '14px 0', width: '20%' }">{{ row[0] }}</td>
<td :style="{ padding: '14px 0', width: '30%' }">{{ row[1] }}</td>
<td :style="{ padding: '14px 0', fontFamily: inter, fontSize: '14px' }">{{ row[2] }}</td>
</tr>
</tbody>
</table>
</section>
<!-- 05 · APPLICATIONS -->
<section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">05 · Applications</div>
<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>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '64px' }">
<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' }">
<BrandNodeMark :size="170" :fg="C.carbon" :accent="C.signal" />
</div>
</div>
<div :style="frame(C.paper, 420)">
<div :style="{ display: 'flex', alignItems: 'center', gap: '48px' }">
<div v-for="s in [64, 32, 16]" :key="s" :style="{ textAlign: 'center' }">
<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>
</div>
</div>
</div>
<div :style="{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px', marginTop: '8px' }">
<div :style="caption">iOS app icon · 1024 master</div>
<div :style="caption">Favicon set</div>
</div>
</section>
<!-- 05.1 Web -->
<section :style="{ ...page, background: C.bone }">
<div :style="eyebrow">05.1 · Web</div>
<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="{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '80px' }">
<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)' }">
<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>
</div>
</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' }">
Your digital workplace.<br>
<span :style="{ color: C.signal }">Data that stays in the EU.</span>
</h1>
<p :style="{ fontFamily: inter, fontSize: '20px', color: 'rgba(255,255,255,0.7)', maxWidth: '620px', lineHeight: 1.5 }">Mail, files, video, chat and SSO fully integrated, EU-hosted, no lock-in.</p>
</div>
</section>
<!-- 05.3 Social -->
<section :style="{ ...page, background: C.paper }">
<div :style="eyebrow">05.2 · Social</div>
<h2 :style="h2">Avatars & headers.</h2>
<div :style="{ display: 'grid', gridTemplateColumns: '180px 1fr', gap: '24px', alignItems: 'start' }">
<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)' }">
<BrandNodeMark :size="130" :fg="C.carbon" :accent="C.signal" />
</div>
<div :style="caption">Avatar · circular crop</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="{ 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="{ position: 'absolute', right: '64px', top: '50%', transform: 'translateY(-50%)' }">
<BrandNodeMark :size="220" :fg="C.signal" :accent="C.carbon" />
</div>
</div>
<div :style="caption">Header · 1500 × 500</div>
</div>
</div>
</section>
<!-- Closing -->
<section :style="{ background: C.carbon, color: C.bone, padding: '120px 80px', minHeight: '60vh', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }">
<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' }">
Use it well.<br>
<span :style="{ color: C.signal }">Don't redraw it.</span>
</h1>
</div>
<div :style="{ display: 'flex', justifyContent: 'flex-end', alignItems: 'flex-end', fontFamily: mono, fontSize: '11px', color: 'rgba(255,255,255,0.55)', marginTop: '64px' }">
<div>dezky · brand system · v1.0</div>
</div>
</section>
</div>
</template> </template>
-32
View File
@@ -156,22 +156,6 @@ export const COPY = {
addressLabel: 'Adresse', addressLabel: 'Adresse',
cvrLabel: 'CVR', cvrLabel: 'CVR',
}, },
brand: {
label: 'brand',
title: 'Brug vores brand korrekt.',
intro: 'Retningslinjer for navn, logo og farver. Skal du bruge logofiler, så kontakt os.',
rules: [
['Navn', 'Altid “Dezky” — ét ord, stort begyndelsesbogstav. Aldrig i versaler i løbende tekst.'],
['Logo', 'Brug node-mærket med tilstrækkelig luft omkring. Forvræng, rotér eller omfarv det ikke.'],
['Tone', 'Direkte, teknisk, uden hype. Vi sælger ikke med frygt — vi forklarer.'],
],
colorsLabel: 'Farver',
colors: [
['Signal', '#D4FF3A'],
['Carbon', '#0A0A0A'],
['Bone', '#F4F3EE'],
],
},
roadmap: { roadmap: {
label: 'roadmap', label: 'roadmap',
title: 'Hvor vi er på vej hen.', title: 'Hvor vi er på vej hen.',
@@ -443,22 +427,6 @@ export const COPY = {
addressLabel: 'Address', addressLabel: 'Address',
cvrLabel: 'Company reg.', cvrLabel: 'Company reg.',
}, },
brand: {
label: 'brand',
title: 'Use our brand correctly.',
intro: 'Guidelines for the name, logo and colours. Need logo files? Get in touch.',
rules: [
['Name', 'Always “Dezky” — one word, capital D. Never all caps in running text.'],
['Logo', 'Use the node mark with enough clear space. Don\'t distort, rotate or recolour it.'],
['Tone', 'Direct, technical, no hype. We don\'t sell with fear — we explain.'],
],
colorsLabel: 'Colours',
colors: [
['Signal', '#D4FF3A'],
['Carbon', '#0A0A0A'],
['Bone', '#F4F3EE'],
],
},
roadmap: { roadmap: {
label: 'roadmap', label: 'roadmap',
title: 'Where we\'re headed.', title: 'Where we\'re headed.',