feat: rebuild brand-hero intro with official Hebe + waterfall artwork

Replace the hand-traced figures in the home-page intro animation with the
official Kaiser-Natron brand vectors (Ikone "Hebe" + Waterfall), rendered as
flat mint silhouettes (dark print outline dropped) on the brand-green hero.
Lady at the origin holding the white natron; waterfall half-scale, vertically
centred to her right. Same entrance choreography/timing as before. Source SVGs
in src/assets/brand/, path data in heroFigures.js.

Drops the ~570KB traced splashPaths.js import from the home page (HomePage
chunk ~214KB -> ~70KB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-06-22 11:20:47 +01:00
parent 856bae8ca9
commit 130bf1e3af
11 changed files with 293 additions and 103 deletions

View File

@@ -272,7 +272,39 @@ The `paper` and `cream` tones keep `text-brand` (unchanged).
---
## 13. Design-system docs page
## 13. Brand-hero intro artwork → official brand assets
**Files:** `src/design-system/components/BrandHero.vue`,
`src/components/heroFigures.js`, `src/assets/brand/{hebe,waterfall}.svg`
The home-page intro animation (the in-flow figure entrance — the full-screen
`SplashIntro` overlay was already retired) previously used hand-traced
approximations of the woman + waterfall. It now uses the **official Kaiser-Natron
brand vectors**: the Ikone ("Hebe") and the Waterfall (2021 Druckdaten-Final),
converted from EPS to SVG.
- **Source SVGs** live in `src/assets/brand/` for provenance; the extracted path
data lives in `src/components/heroFigures.js` (`ladyMint`, `ladyWhite`,
`waterfall`).
- **Dark outline removed** — the brand icon's `#006648` outline tone is dropped;
the figures render as flat mint silhouettes, matching the established splash
aesthetic. Mint tones: lady `#72c1ad`, waterfall `#6eceb2`; natron handful
`#ffffff`.
- **Composition** — shared `0 0 2760 3624` viewBox: the lady at the origin
(native 1828×3624), the waterfall **half-scale** (`scale(0.5)`) to her right
and **vertically centred** against her (`translate(1793,1310)`).
- **Animation unchanged** — same choreography/timing: lady slides in from the
left (`left-m`), waterfall from the right (`right-m`), the white natron fades
in once she's landed (`mound-m`), tagline + SINCE 1881 last; same edge-feather
mask and reduced-motion handling.
> Side effect: the home-page chunk shrank ~214 KB → ~70 KB because the new
> `heroFigures.js` (~57 KB) replaces the much larger traced `splashPaths.js`
> import. `splashPaths.js` / `SplashIntro.vue` remain only as unused legacy.
---
## 14. Design-system docs page
**File:** `src/pages/design/ColorsSection.vue`

View File

@@ -1 +0,0 @@
@property --hero-feather{syntax:"<percentage>";inherits:false;initial-value:12%}.brand-hero__svg--bg[data-v-6953d538]{--hero-feather:12%;-webkit-mask-image:linear-gradient(to right, transparent 0%, #000 var(--hero-feather), #000 calc(100% - var(--hero-feather)), transparent 100%);-webkit-mask-image:linear-gradient(to right, transparent 0%, #000 var(--hero-feather), #000 calc(100% - var(--hero-feather)), transparent 100%);mask-image:linear-gradient(to right, transparent 0%, #000 var(--hero-feather), #000 calc(100% - var(--hero-feather)), transparent 100%);transition:--hero-feather .7s 1.15s;display:block}.brand-hero.go .brand-hero__svg--bg[data-v-6953d538]{--hero-feather:0%}.brand-hero__svg--portrait[data-v-6953d538]{width:100%;height:auto;max-height:56svh;margin:0 auto;display:block}.left-m[data-v-6953d538],.right-m[data-v-6953d538]{fill:#b5d8b6}.mound-m[data-v-6953d538]{fill:#fff}.mound-finger[data-v-6953d538]{fill:#b5d8b6}.layer.left-m[data-v-6953d538],.layer.mound-finger[data-v-6953d538]{opacity:0;transition:transform .8s cubic-bezier(.22,.61,.36,1) .15s,opacity .6s .15s;transform:translate(-14%)}.layer.right-m[data-v-6953d538]{opacity:0;transition:transform .8s cubic-bezier(.22,.61,.36,1) .15s,opacity .6s .15s;transform:translate(14%)}.layer.mound-m[data-v-6953d538]{opacity:0;transition:opacity .55s .7s}.brand-hero__copy[data-v-6953d538]{opacity:0;transition:opacity .7s 1.15s}.brand-hero.go .layer.left-m[data-v-6953d538],.brand-hero.go .layer.right-m[data-v-6953d538],.brand-hero.go .layer.mound-finger[data-v-6953d538]{opacity:1;transform:none}.brand-hero.go .layer.mound-m[data-v-6953d538],.brand-hero.go .brand-hero__copy[data-v-6953d538]{opacity:1}@media (prefers-reduced-motion:reduce){.layer.left-m[data-v-6953d538],.layer.right-m[data-v-6953d538],.layer.mound-m[data-v-6953d538],.layer.mound-finger[data-v-6953d538],.brand-hero__copy[data-v-6953d538]{opacity:1!important;transition:none!important;transform:none!important}}

1
dist/assets/HomePage-CI5cTFDW.css vendored Normal file
View File

@@ -0,0 +1 @@
@property --hero-feather{syntax:"<percentage>";inherits:false;initial-value:12%}.brand-hero__svg--bg[data-v-206cba0b]{--hero-feather:12%;-webkit-mask-image:linear-gradient(to right, transparent 0%, #000 var(--hero-feather), #000 calc(100% - var(--hero-feather)), transparent 100%);-webkit-mask-image:linear-gradient(to right, transparent 0%, #000 var(--hero-feather), #000 calc(100% - var(--hero-feather)), transparent 100%);mask-image:linear-gradient(to right, transparent 0%, #000 var(--hero-feather), #000 calc(100% - var(--hero-feather)), transparent 100%);transition:--hero-feather .7s 1.15s;display:block}.brand-hero.go .brand-hero__svg--bg[data-v-206cba0b]{--hero-feather:0%}.brand-hero__svg--portrait[data-v-206cba0b]{width:100%;height:auto;max-height:56svh;margin:0 auto;display:block}.left-m[data-v-206cba0b]{fill:#7bd1ad}.right-m[data-v-206cba0b]{fill:#6eceb2}.mound-m[data-v-206cba0b]{fill:#fff}.layer.left-m[data-v-206cba0b]{opacity:0;transition:transform .8s cubic-bezier(.22,.61,.36,1) .15s,opacity .6s .15s;transform:translate(-14%)}.layer.right-m[data-v-206cba0b]{opacity:0;transition:transform .8s cubic-bezier(.22,.61,.36,1) .15s,opacity .6s .15s;transform:translate(14%)}.layer.mound-m[data-v-206cba0b]{opacity:0;transition:opacity .55s .7s}.brand-hero__copy[data-v-206cba0b]{opacity:0;transition:opacity .7s 1.15s}.brand-hero.go .layer.left-m[data-v-206cba0b],.brand-hero.go .layer.right-m[data-v-206cba0b]{opacity:1;transform:none}.brand-hero.go .layer.mound-m[data-v-206cba0b],.brand-hero.go .brand-hero__copy[data-v-206cba0b]{opacity:1}@media (prefers-reduced-motion:reduce){.layer.left-m[data-v-206cba0b],.layer.right-m[data-v-206cba0b],.layer.mound-m[data-v-206cba0b],.brand-hero__copy[data-v-206cba0b]{opacity:1!important;transition:none!important;transform:none!important}}

1
dist/assets/HomePage-DTNbICzw.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/index.html vendored
View File

@@ -7,7 +7,7 @@
<meta name="theme-color" content="#ffffff" />
<title>Kaiser Natron</title>
<!-- Zeitung is self-hosted (see @font-face in src/assets/styles.css); no external font CDN. -->
<script type="module" crossorigin src="/assets/index-BVTgnu8d.js"></script>
<script type="module" crossorigin src="/assets/index-HghiGINV.js"></script>
<link rel="modulepreload" crossorigin href="/assets/preload-helper-ca-nBW7U.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-DTXUv7Wx.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-dom.esm-bundler-Bg1uJ-W7.js">

81
src/assets/brand/hebe.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +1,28 @@
<!--
BrandHero.vue
-------------------------------------------------------------------
Home-page brand hero. Replaces the previous full-screen SplashIntro
overlay: the same figure-entrance animation now plays in flow on
the page itself, so the user lands on usable chrome (nav + hero)
instead of waiting through a 2.8s overlay.
Home-page brand hero. Plays the figure-entrance intro in flow on the
page itself (it replaced the old full-screen SplashIntro overlay), so
the user lands on usable chrome (nav + hero) immediately.
Artwork is the official brand illustration (source SVGs in
`@/assets/brand/`, path data in `@/components/heroFigures.js`):
· the woman (KaiserNatron_Ikone "Hebe") flat mint silhouette, the
dark print outline dropped so the brand-green ground reads through
the negative space; the natron handful in her hands stays white.
· the waterfall (KaiserNatron_Waterfall) mint, half-scale, sitting
to her right and vertically centred against her.
Layout:
· Desktop (1218 px): the SVG sits as a centred figure inside
the LEFT half of the first fold; the tagline column sits in
the RIGHT half, also centred within its half the two halves
balance one another instead of leaving a wide trough between.
· Desktop (1218 px): illustration centred in the LEFT half, tagline
column centred in the RIGHT half the two halves balance.
· Below 1218 px: stacked illustration above, tagline below.
Animation choreography (mirrors the old splash exactly, minus the
wordmark which has no destination on the final page):
1. left-m (woman) slides in from the left
2. right-m (landscape) slides in from the right
3. mound-m (white handful of natron) fades in
Choreography:
1. left-m (woman) slides in from the left
2. right-m (waterfall) slides in from the right
3. mound-m (white natron handful) fades in
4. tagline + SINCE 1881 fade in LAST
Path data is imported from `splashPaths.js`.
-->
<template>
<section
@@ -28,43 +30,36 @@
:class="{ 'is-portrait': isPortrait, go: started }"
>
<!-- =========================================================
DESKTOP single horizontally-centred max-width container
with figure + tagline as a 2-col flex row. Removes the
absolute-positioning trough by anchoring both halves to a
shared centred container; the figure scales with column
width so it stays balanced against the tagline at every
viewport size.
DESKTOP figure + tagline as a centred 2-col flex row.
========================================================= -->
<template v-if="!isPortrait">
<div class="mx-auto flex min-h-[calc(100svh-var(--nav-h))] w-full max-w-7xl items-center px-6 md:px-10 lg:px-12">
<!-- Figure column. Width-fills its half of the container,
height auto-derives from the cropped portrait viewBox.
`max-h-[80svh]` keeps the figure from overshooting the
fold on tall ultrawide displays. -->
<div class="brand-hero__media flex w-1/2 items-center justify-center">
<svg
aria-hidden="true"
class="brand-hero__svg--bg block w-full h-auto max-h-[80svh]"
viewBox="0 380 1024 1156"
:viewBox="VIEWBOX"
preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Mound paints BEHIND the woman so her fingers read mint
where they wrap the natron handful. With the mound
on top (the splash entrance order) one finger bled
white because the mound's bounds extend slightly
past the grip outline. -->
<path class="layer right-m" fill-rule="evenodd" :d="dPortRight" />
<path class="layer mound-m" fill-rule="evenodd" :d="dPortMound" />
<path class="layer mound-finger" fill-rule="evenodd" :d="dPortMoundFinger" />
<path class="layer left-m" fill-rule="nonzero" :d="dPortLeft" />
<!-- Waterfall paints first so the woman (drawn after) reads
as the foreground. -->
<g class="layer right-m">
<g :transform="waterfallTransform">
<path v-for="(d, i) in waterfall" :key="`w${i}`" :d="d" fill-rule="nonzero" />
</g>
</g>
<g class="layer left-m">
<path v-for="(d, i) in ladyMint" :key="`l${i}`" :d="d" fill-rule="nonzero" />
</g>
<!-- Natron handful, painted last so it sits in her cupped
hands, fading in once she's landed. -->
<g class="layer mound-m">
<path v-for="(d, i) in ladyWhite" :key="`n${i}`" :d="d" fill-rule="nonzero" />
</g>
</svg>
</div>
<!-- Tagline column. Same width as the figure column so the
composition is symmetrically centred; inner copy block
is left-aligned and width-clamped so headline wrapping
stays predictable across breakpoints. -->
<div class="brand-hero__copy flex w-1/2 items-center justify-start pl-4 md:pl-6 lg:pl-8">
<div class="w-full max-w-md xl:max-w-lg 2xl:max-w-xl text-left">
<p class="mb-4 md:mb-5 text-sm md:text-base tracking-label uppercase text-cream/75">{{ t('home.brand.since') }}</p>
@@ -91,15 +86,22 @@
<div class="brand-hero__media">
<svg
class="brand-hero__svg brand-hero__svg--portrait"
viewBox="0 380 1024 1156"
:viewBox="VIEWBOX"
preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path class="layer right-m" fill-rule="evenodd" :d="dPortRight" />
<path class="layer mound-m" fill-rule="evenodd" :d="dPortMound" />
<path class="layer mound-finger" fill-rule="evenodd" :d="dPortMoundFinger" />
<path class="layer left-m" fill-rule="nonzero" :d="dPortLeft" />
<g class="layer right-m">
<g :transform="waterfallTransform">
<path v-for="(d, i) in waterfall" :key="`w${i}`" :d="d" fill-rule="nonzero" />
</g>
</g>
<g class="layer left-m">
<path v-for="(d, i) in ladyMint" :key="`l${i}`" :d="d" fill-rule="nonzero" />
</g>
<g class="layer mound-m">
<path v-for="(d, i) in ladyWhite" :key="`n${i}`" :d="d" fill-rule="nonzero" />
</g>
</svg>
</div>
@@ -121,23 +123,26 @@
<script setup>
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { RouterLink } from 'vue-router'
import {
dPortLeft,
dPortRight,
dPortMound,
dPortMoundFinger,
} from '@/components/splashPaths.js'
import { ladyMint, ladyWhite, waterfall } from '@/components/heroFigures.js'
import Button from './Button.vue'
import { useI18n } from '@/i18n/index.js'
// Bumped from the original 768/900 px split to 1218 px because at
// the desktop split layout's narrower widths the tagline column
// collides with the figure on the left.
// Combined stage. The woman is authored at the origin in her native
// 1828×3624 space (content ≈ y[96,3524]); the waterfall (native
// 1828×2018) sits half-scale to her right, vertically centred against
// her (lady mid-height ≈ 1810). The viewBox leaves room to the right
// so the waterfall has somewhere to slide in from.
const VIEWBOX = '0 0 2760 3624'
const waterfallTransform = 'translate(1793,1310) scale(0.5)'
// Bumped from the original 768/900 px split to 1218 px because at the
// desktop split layout's narrower widths the tagline column collides
// with the figure on the left.
const portraitQuery = '(max-width: 1218px)'
const isPortrait = ref(false)
// `started` flips true one RAF after mount so the layers transition
// from their initial offset/hidden state into their final position —
// the splash entrance animation, in flow on the page itself.
// the intro entrance, in flow on the page itself.
const started = ref(false)
let mql = null
let onChange = null
@@ -150,9 +155,7 @@ onMounted(() => {
mql.addEventListener ? mql.addEventListener('change', onChange) : mql.addListener(onChange)
}
// Defer the `go` flip a frame so the browser commits the initial
// (offset / hidden) state before we transition out of it. Without
// the rAF the layers paint in their final position and skip the
// transition entirely.
// (offset / hidden) state before we transition out of it.
requestAnimationFrame(() => { started.value = true })
})
@@ -164,25 +167,15 @@ onBeforeUnmount(() => {
// `t` is invoked inline in the template (not cached as setup
// constants) so the headline / SINCE 1881 / Shop button update
// reactively when the user switches locale via the navbar — calls
// resolved at setup time freeze the value at first paint.
// reactively when the user switches locale via the navbar.
const { t } = useI18n()
</script>
<style scoped>
/* Desktop SVG.
`display: block` removes the inline-svg baseline gap.
`mask-image` softens the LEFT and RIGHT edges of the artwork into
the brand-green ground while the entrance is in progress so the
mint silhouette feels less sheer at the edges. Once the entrance
has settled the feather animates back to 0 % so the figure stands
fully opaque at rest — the soft edges are an entrance effect, not
a permanent sticker frame.
The feather amount is held in `--hero-feather`; @property is what
makes the percentage interpolate (custom properties don't animate
without an explicit type registration). Browsers without
@property support snap from 12 % → 0 % at the end of the delay,
which is an acceptable graceful degradation. */
the brand-green ground while the entrance is in progress, then
feathers back to 0 % so the figure stands fully opaque at rest. */
@property --hero-feather {
syntax: '<percentage>';
inherits: false;
@@ -206,9 +199,8 @@ const { t } = useI18n()
#000 calc(100% - var(--hero-feather)),
transparent 100%
);
/* Synced with the tagline fade-in below (700ms ease 1150ms) so
the side feathers dissolve in the same beat as the copy
resolves — one smooth landing instead of two staggered ones. */
/* Synced with the tagline fade-in (700ms ease 1150ms) so the side
feathers dissolve in the same beat as the copy resolves. */
transition: --hero-feather 700ms ease 1150ms;
}
@@ -224,25 +216,19 @@ const { t } = useI18n()
margin: 0 auto;
}
/* Layer fills (matches the splash's resolved palette). */
.left-m { fill: #b5d8b6; }
.right-m { fill: #b5d8b6; }
.mound-m { fill: #ffffff; }
/* The original trace bundled a small hand/finger fragment under
the mound shape and painted it white; rendering it in the same
mint as the woman's other features makes it read as part of her
grip on the natron handful. */
.mound-finger { fill: #b5d8b6; }
/* Layer fills (authentic brand mints). `fill` inherits, so setting it
on the group cascades to every path inside. */
.left-m { fill: #7bd1ad; }
.right-m { fill: #6eceb2; }
.mound-m { fill: #ffffff; }
/* ---------- Entrance animation (replaces SplashIntro) ----------
Initial state: figures translated off to their respective sides
and hidden, mound and copy hidden. Adding `.go` to the section
transitions every layer to its resting position. Delays cascade
so the figures land first, the mound fades in once they've
landed, and the copy is the last beat — the eye reaches the
tagline only after the artwork has settled. */
.layer.left-m,
.layer.mound-finger {
/* ---------- Entrance animation ----------
Initial state: figures translated off to their respective sides and
hidden, natron and copy hidden. Adding `.go` to the section
transitions every layer to its resting position. Delays cascade so
the figures land first, the natron fades in once they've landed,
and the copy is the last beat. */
.layer.left-m {
opacity: 0;
transform: translateX(-14%);
transition: transform 800ms cubic-bezier(.22, .61, .36, 1) 150ms,
@@ -264,8 +250,7 @@ const { t } = useI18n()
}
.brand-hero.go .layer.left-m,
.brand-hero.go .layer.right-m,
.brand-hero.go .layer.mound-finger {
.brand-hero.go .layer.right-m {
opacity: 1;
transform: none;
}
@@ -276,14 +261,11 @@ const { t } = useI18n()
opacity: 1;
}
/* Reduced-motion users get the final state immediately — no slide,
no fade. The hero is purely decorative animation, so honouring
the preference doesn't cost any communicated information. */
/* Reduced-motion users get the final state immediately. */
@media (prefers-reduced-motion: reduce) {
.layer.left-m,
.layer.right-m,
.layer.mound-m,
.layer.mound-finger,
.brand-hero__copy {
opacity: 1 !important;
transform: none !important;