updates to new sections and design system page

This commit is contained in:
Dorian
2026-04-23 15:12:43 +01:00
parent f001d65c7f
commit d57fee8fc7
18 changed files with 223 additions and 60 deletions

1
dist/assets/About--uXlb06p.js vendored Normal file
View File

@@ -0,0 +1 @@
import{C as e,T as t,c as n,j as r,l as i,lt as a,p as o,r as s,s as c,st as l,u}from"./runtime-core.esm-bundler-BD0e4RlP.js";import{t as d}from"./_plugin-vue_export-helper-BOai-rQB.js";import{t as f}from"./Badge-DcvgVKep.js";var p={id:`about`,class:`bg-cream text-ink`},m={class:`mx-auto w-full max-w-6xl px-6 py-16 sm:px-8 sm:py-20 md:px-12 md:py-24 lg:px-16 lg:py-28`},h={class:`flex flex-col items-center text-center gap-4 max-w-3xl mx-auto`},g={key:1,class:`font-display font-normal leading-[1.05] tracking-tight text-ink`,style:{"font-size":`clamp(2.25rem, 5vw, 3.75rem)`}},_={key:2,class:`text-lg leading-relaxed text-muted max-w-2xl`},v={key:0,class:`timeline-track hidden md:grid md:grid-cols-3 mt-16`,"aria-hidden":`true`},y={key:1,class:`timeline-list mt-6 md:mt-8 grid gap-0 md:gap-6 md:grid-cols-3`},b={class:`timeline-mobile-marker`},x=d({__name:`About`,props:{eyebrow:{type:String,default:``},headline:{type:String,default:``},sub:{type:String,default:``},milestones:{type:Array,default:()=>[],validator:e=>e.every(e=>e&&typeof e==`object`&&typeof e.year==`string`&&typeof e.title==`string`&&typeof e.text==`string`)}},setup(d){let x=[{card:`bg-cream border-line`,title:`text-ink`,body:`text-muted`,pill:`pill-paper`},{card:`bg-paper border-line`,title:`text-ink`,body:`text-muted`,pill:`pill-brand-soft`},{card:`bg-brand border-transparent`,title:`text-cream`,body:`text-cream/80`,pill:`pill-accent`}];return(S,C)=>(e(),u(`section`,p,[c(`div`,m,[c(`div`,h,[d.eyebrow?(e(),n(f,{key:0,variant:`brand`},{default:r(()=>[o(a(d.eyebrow),1)]),_:1})):i(``,!0),d.headline?(e(),u(`h2`,g,a(d.headline),1)):i(``,!0),d.sub?(e(),u(`p`,_,a(d.sub),1)):i(``,!0)]),d.milestones.length?(e(),u(`div`,v,[(e(!0),u(s,null,t(d.milestones.slice(0,3),(t,n)=>(e(),u(`div`,{key:`track-`+n,class:`timeline-cell`},[c(`span`,{class:l([`timeline-pill`,x[n].pill])},a(t.year),3)]))),128))])):i(``,!0),d.milestones.length?(e(),u(`ol`,y,[(e(!0),u(s,null,t(d.milestones.slice(0,3),(t,n)=>(e(),u(`li`,{key:t.year+t.title,class:`timeline-item flex flex-col`},[c(`div`,b,[c(`span`,{class:l([`timeline-pill`,x[n].pill])},a(t.year),3)]),c(`div`,{class:l([`flex flex-col gap-3 rounded-md border p-6 md:p-7`,x[n].card])},[c(`h3`,{class:l([`font-display text-2xl font-normal leading-tight`,x[n].title])},a(t.title),3),c(`p`,{class:l([`text-sm leading-relaxed`,x[n].body])},a(t.text),3)],2)]))),128))])):i(``,!0)])]))}},[[`__scopeId`,`data-v-39877af8`]]);export{x as t};

1
dist/assets/About-C5UZrRYy.css vendored Normal file
View File

@@ -0,0 +1 @@
.timeline-pill[data-v-39877af8]{border-radius:var(--radius-pill);font-family:var(--font-sans);letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;white-space:nowrap;z-index:1;border:1px solid #0000;justify-content:center;align-items:center;padding:.375rem .875rem;font-size:.75rem;font-weight:700;display:inline-flex;position:relative}.pill-paper[data-v-39877af8]{background:var(--color-paper);color:var(--color-brand);border-color:var(--color-line-strong)}.pill-brand-soft[data-v-39877af8]{background:color-mix(in srgb, var(--color-brand) 14%, var(--color-cream));color:var(--color-brand);border-color:color-mix(in srgb, var(--color-brand) 28%, transparent)}.pill-accent[data-v-39877af8]{background:var(--color-accent);color:var(--color-brand);border-color:color-mix(in srgb, var(--color-accent) 70%, var(--color-brand))}.timeline-track[data-v-39877af8]{align-items:center;margin-bottom:.5rem;position:relative}.timeline-track[data-v-39877af8]:before{content:"";background:var(--color-line-strong);height:1px;position:absolute;top:50%;left:16.6667%;right:16.6667%;transform:translateY(-.5px)}.timeline-cell[data-v-39877af8]{justify-content:center;display:flex}@media (width<=767px){.timeline-mobile-marker[data-v-39877af8]{justify-content:center;align-items:center;padding:1.75rem 0;display:flex;position:relative}.timeline-mobile-marker[data-v-39877af8]:before{content:"";background:var(--color-line-strong);width:1px;position:absolute;top:0;bottom:50%;left:50%;transform:translate(-.5px)}.timeline-mobile-marker[data-v-39877af8]:after{content:"";background:var(--color-line-strong);width:1px;position:absolute;top:50%;bottom:0;left:50%;transform:translate(-.5px)}.timeline-item:first-child .timeline-mobile-marker[data-v-39877af8]:before{display:none}}@media (width>=768px){.timeline-mobile-marker[data-v-39877af8]{display:none}}

View File

@@ -1 +0,0 @@
import{C as e,T as t,c as n,j as r,l as i,lt as a,p as o,r as s,s as c,st as l,u}from"./runtime-core.esm-bundler-BD0e4RlP.js";import{t as d}from"./Badge-DcvgVKep.js";var f={id:`about`,class:`bg-cream text-ink`},p={class:`mx-auto w-full max-w-6xl px-6 py-16 sm:px-8 sm:py-20 md:px-12 md:py-24 lg:px-16 lg:py-28`},m={class:`flex flex-col items-center text-center gap-4 max-w-3xl mx-auto`},h={key:1,class:`font-display font-normal leading-[1.05] tracking-tight text-ink`,style:{"font-size":`clamp(2.25rem, 5vw, 3.75rem)`}},g={key:2,class:`text-lg leading-relaxed text-muted max-w-2xl`},_={key:0,class:`mt-10 md:mt-16 grid gap-5 md:grid-cols-3 md:gap-6`},v={__name:`About`,props:{eyebrow:{type:String,default:``},headline:{type:String,default:``},sub:{type:String,default:``},milestones:{type:Array,default:()=>[],validator:e=>e.every(e=>e&&typeof e==`object`&&typeof e.year==`string`&&typeof e.title==`string`&&typeof e.text==`string`)}},setup(v){let y=[{card:`bg-cream border-line`,year:`text-muted`,title:`text-ink`,body:`text-muted`},{card:`bg-paper border-line`,year:`text-muted`,title:`text-ink`,body:`text-muted`},{card:`bg-brand border-transparent`,year:`text-accent`,title:`text-cream`,body:`text-cream/80`}];return(b,x)=>(e(),u(`section`,f,[c(`div`,p,[c(`div`,m,[v.eyebrow?(e(),n(d,{key:0,variant:`brand`},{default:r(()=>[o(a(v.eyebrow),1)]),_:1})):i(``,!0),v.headline?(e(),u(`h2`,h,a(v.headline),1)):i(``,!0),v.sub?(e(),u(`p`,g,a(v.sub),1)):i(``,!0)]),v.milestones.length?(e(),u(`ol`,_,[(e(!0),u(s,null,t(v.milestones.slice(0,3),(t,n)=>(e(),u(`li`,{key:t.year+t.title,class:l([`flex flex-col gap-3 rounded-md border p-6 md:p-7`,y[n].card])},[c(`span`,{class:l([`text-xs tracking-label uppercase`,y[n].year])},a(t.year),3),c(`h3`,{class:l([`font-display text-2xl font-normal leading-tight`,y[n].title])},a(t.title),3),c(`p`,{class:l([`text-sm leading-relaxed`,y[n].body])},a(t.text),3)],2))),128))])):i(``,!0)])]))}};export{v as t};

View File

@@ -1 +1 @@
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-BD0e4RlP.js";import{t as a}from"./i18n-DrTQks20.js";import{t as o}from"./About-CtiMkkHe.js";var s={class:`min-h-screen bg-cream`},c={__name:`AboutPreview`,setup(c){let{t:l}=a(),u=r(()=>[1,2,3].map(e=>({year:l(`about.milestone.${e}.year`),title:l(`about.milestone.${e}.title`),text:l(`about.milestone.${e}.text`)})));return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`about.eyebrow`),headline:t(l)(`about.headline`),sub:t(l)(`about.sub`),milestones:u.value},null,8,[`eyebrow`,`headline`,`sub`,`milestones`])]))}};export{c as default};
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-BD0e4RlP.js";import{t as a}from"./i18n-DrTQks20.js";import{t as o}from"./About--uXlb06p.js";var s={class:`min-h-screen bg-cream`},c={__name:`AboutPreview`,setup(c){let{t:l}=a(),u=r(()=>[1,2,3].map(e=>({year:l(`about.milestone.${e}.year`),title:l(`about.milestone.${e}.title`),text:l(`about.milestone.${e}.text`)})));return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`about.eyebrow`),headline:t(l)(`about.headline`),sub:t(l)(`about.sub`),milestones:u.value},null,8,[`eyebrow`,`headline`,`sub`,`milestones`])]))}};export{c as default};

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-BD0e4RlP.js";import{t as a}from"./i18n-DrTQks20.js";import{t as o}from"./Revitalization-Dbf9J8TA.js";var s={class:`min-h-screen bg-brand`},c={__name:`RevitalizationPreview`,setup(c){let{t:l}=a(),u=r(()=>[l(`revit.feature.1.title`),l(`revit.feature.2.title`),l(`revit.feature.3.title`)]);return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`revit.eyebrow`),headline:t(l)(`revit.headline.a`),"headline-em":t(l)(`revit.headline.em`),sub:t(l)(`revit.sub`),features:u.value,"notify-cta":t(l)(`revit.notifyCta`)},null,8,[`eyebrow`,`headline`,`headline-em`,`sub`,`features`,`notify-cta`])]))}};export{c as default};

View File

@@ -0,0 +1 @@
import{C as e,G as t,m as n,o as r,u as i}from"./runtime-core.esm-bundler-BD0e4RlP.js";import{t as a}from"./i18n-DrTQks20.js";import{t as o}from"./Revitalization-Dbf9J8TA.js";var s={class:`min-h-screen bg-brand`},c={__name:`RevitalizationPreview`,setup(c){let{t:l}=a(),u=r(()=>[{title:l(`revit.feature.1.title`),icon:`⚗️`},{title:l(`revit.feature.2.title`),icon:`💊`},{title:l(`revit.feature.3.title`),icon:`🌿`}]);return(r,a)=>(e(),i(`div`,s,[n(o,{eyebrow:t(l)(`revit.eyebrow`),headline:t(l)(`revit.headline.a`),"headline-em":t(l)(`revit.headline.em`),sub:t(l)(`revit.sub`),features:u.value,"notify-cta":t(l)(`revit.notifyCta`)},null,8,[`eyebrow`,`headline`,`headline-em`,`sub`,`features`,`notify-cta`])]))}};export{c as default};

View File

@@ -4,9 +4,9 @@ import{C as e,G as t,c as n,j as r,lt as i,m as a,s as o}from"./runtime-core.esm
:headline-em="t('revit.headline.em')"
:sub="t('revit.sub')"
:features="[
t('revit.feature.1.title'),
t('revit.feature.2.title'),
t('revit.feature.3.title'),
{ title: t('revit.feature.1.title'), icon: '⚗️' },
{ title: t('revit.feature.2.title'), icon: '💊' },
{ title: t('revit.feature.3.title'), icon: '🌿' },
]"
:notify-cta="t('revit.notifyCta')"
@notify="captureEmail()"

File diff suppressed because one or more lines are too long

1
dist/assets/index-CMSTRSqf.css 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

4
dist/index.html vendored
View File

@@ -12,13 +12,13 @@
href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,200;1,9..144,400;1,9..144,600&family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400&display=swap"
rel="stylesheet"
/>
<script type="module" crossorigin src="/assets/index-CClpXQy1.js"></script>
<script type="module" crossorigin src="/assets/index-x_BEHWZf.js"></script>
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BOai-rQB.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-BD0e4RlP.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-dom.esm-bundler-CcSFQHoC.js">
<link rel="modulepreload" crossorigin href="/assets/pinia-DQoUg3qR.js">
<link rel="modulepreload" crossorigin href="/assets/vue-router-QRVDVSxc.js">
<link rel="stylesheet" crossorigin href="/assets/index-C8HKZcXS.css">
<link rel="stylesheet" crossorigin href="/assets/index-CMSTRSqf.css">
</head>
<body>
<div id="app"></div>

View File

@@ -24,9 +24,16 @@ body {
border-radius: 4px;
}
/* Room above anchor targets when scrolling */
/* Room above anchor targets when scrolling — must clear the sticky
navbar. The navbar is taller on md+ (logo scales up), so the value
bumps up at the md breakpoint. */
[id] {
scroll-margin-top: 2rem;
scroll-margin-top: 5.5rem;
}
@media (min-width: 768px) {
[id] {
scroll-margin-top: 7rem;
}
}
/* Typography helpers */

View File

@@ -2,7 +2,7 @@
import Badge from './Badge.vue'
/**
* About — a centered intro + three-up milestone gallery.
* About — a centered intro + milestone timeline.
*
* Content is supplied entirely via props. Milestones are expected as an
* array of `{ year, title, text }` entries; extras past the third are
@@ -11,6 +11,11 @@ import Badge from './Badge.vue'
* The three cards cycle the Card primitive's tones left-to-right —
* paper / cream / brand — so the row echoes the design system's three
* canonical surfaces rather than repeating a single tone.
*
* Timeline shape: pill-line-pill-line-pill. Horizontal above the cards
* on md+ (each pill centered above its card, connected by a hairline),
* vertical between pills on mobile (short line segments above every
* non-first pill so the column reads as one continuous track).
*/
defineProps({
eyebrow: { type: String, default: '' },
@@ -38,21 +43,28 @@ defineProps({
const CARD_TONES = [
{
card: 'bg-cream border-line',
year: 'text-muted',
title: 'text-ink',
body: 'text-muted',
// Milestone-1 pill: cream card is the section's baseline tone, so
// the pill uses the paper surface + brand ink to punch against it.
pill: 'pill-paper',
},
{
card: 'bg-paper border-line',
year: 'text-muted',
title: 'text-ink',
body: 'text-muted',
// Milestone-2 pill: paper card is bright, so the pill inverts to
// the brand-soft wash with brand ink for a calmer contrast.
pill: 'pill-brand-soft',
},
{
card: 'bg-brand border-transparent',
year: 'text-accent',
title: 'text-cream',
body: 'text-cream/80',
// Milestone-3 pill: brand card is dark green, so the pill lifts
// to accent (warm yellow) on brand ink — the strongest stop on
// the timeline, matching the climactic "Heute" milestone.
pill: 'pill-accent',
},
]
</script>
@@ -74,35 +86,178 @@ const CARD_TONES = [
</p>
</div>
<!-- Three timeline milestones. Stacks on mobile; three columns from
md up, each rendered as a paper card so the cream section reads
as a gallery rather than a flat list. -->
<!-- Desktop-only horizontal timeline track. Rendered as its own row
above the cards (hidden on mobile), so the pill card vertical
pairing stays clean and we don't have to juggle two layouts in
one grid. Track line is a pseudo-element inset from both edges
so it terminates at the outer pills. -->
<div
v-if="milestones.length"
class="timeline-track hidden md:grid md:grid-cols-3 mt-16"
aria-hidden="true"
>
<div
v-for="(milestone, i) in milestones.slice(0, 3)"
:key="'track-' + i"
class="timeline-cell"
>
<span :class="['timeline-pill', CARD_TONES[i].pill]">{{ milestone.year }}</span>
</div>
</div>
<!-- Three timeline milestones. On mobile each card is preceded by
its own pill (with a short connecting line above every
non-first pill) so the column reads as one continuous
timeline. On md+ the pill is hidden here because the
horizontal track above already covers it. -->
<ol
v-if="milestones.length"
class="mt-10 md:mt-16 grid gap-5 md:grid-cols-3 md:gap-6"
class="timeline-list mt-6 md:mt-8 grid gap-0 md:gap-6 md:grid-cols-3"
>
<li
v-for="(milestone, i) in milestones.slice(0, 3)"
:key="milestone.year + milestone.title"
:class="[
'flex flex-col gap-3 rounded-md border p-6 md:p-7',
CARD_TONES[i].card,
]"
class="timeline-item flex flex-col"
>
<span :class="['text-xs tracking-label uppercase', CARD_TONES[i].year]">
{{ milestone.year }}
</span>
<h3
<div class="timeline-mobile-marker">
<span :class="['timeline-pill', CARD_TONES[i].pill]">{{ milestone.year }}</span>
</div>
<div
:class="[
'font-display text-2xl font-normal leading-tight',
CARD_TONES[i].title,
'flex flex-col gap-3 rounded-md border p-6 md:p-7',
CARD_TONES[i].card,
]"
>{{ milestone.title }}</h3>
<p :class="['text-sm leading-relaxed', CARD_TONES[i].body]">
{{ milestone.text }}
</p>
>
<h3
:class="[
'font-display text-2xl font-normal leading-tight',
CARD_TONES[i].title,
]"
>{{ milestone.title }}</h3>
<p :class="['text-sm leading-relaxed', CARD_TONES[i].body]">
{{ milestone.text }}
</p>
</div>
</li>
</ol>
</div>
</section>
</template>
<style scoped>
/* Shared pill — typography and shape are constant across viewports
so the horizontal and vertical renders read as one component.
Per-card tones below paint the surface, ink, and border so each
pill complements its sibling card. `position: relative; z-index:1`
lifts the pill above the connecting line so the pill's background
visually punches a hole in the timeline. */
.timeline-pill {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.375rem 0.875rem;
border-radius: var(--radius-pill);
border: 1px solid transparent;
font-family: var(--font-sans);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: var(--tracking-eyebrow);
text-transform: uppercase;
white-space: nowrap;
position: relative;
z-index: 1;
}
/* Per-card pill tones. Background must be opaque so the pill masks
the connecting line behind it. */
.pill-paper {
background: var(--color-paper);
color: var(--color-brand);
border-color: var(--color-line-strong);
}
.pill-brand-soft {
background: color-mix(in srgb, var(--color-brand) 14%, var(--color-cream));
color: var(--color-brand);
border-color: color-mix(in srgb, var(--color-brand) 28%, transparent);
}
.pill-accent {
background: var(--color-accent);
color: var(--color-brand);
border-color: color-mix(in srgb, var(--color-accent) 70%, var(--color-brand));
}
/* ——— Desktop: horizontal pill-line-pill track ———————————— */
/* Layout (display: grid) lives on the Tailwind utilities (`hidden
md:grid md:grid-cols-3`) so we never set `display` here — that
would beat the responsive `hidden` utility on mobile. */
.timeline-track {
position: relative;
align-items: center;
margin-bottom: 0.5rem;
}
/* Hairline that runs under the pills. Inset to the column centers
so the line terminates at the outer pills rather than bleeding
past the grid. Percentages assume three equal columns. */
.timeline-track::before {
content: '';
position: absolute;
left: 16.6667%;
right: 16.6667%;
top: 50%;
height: 1px;
background: var(--color-line-strong);
transform: translateY(-0.5px);
}
.timeline-cell {
display: flex;
justify-content: center;
}
/* ——— Mobile: vertical pill-line-pill with cards interleaved ———
Wrapped in a max-width media query so the mobile marker is fully
removed from the layout on md+. (Scoped-style specificity beats
Tailwind utilities here, so a `md:hidden` class would not be
enough on its own.) */
@media (max-width: 767px) {
.timeline-mobile-marker {
display: flex;
justify-content: center;
align-items: center;
padding: 1.75rem 0;
position: relative;
}
/* Line above pill — spans the full top padding so it visually
meets the card above. Suppressed on the first marker (start
of the track). */
.timeline-mobile-marker::before {
content: '';
position: absolute;
top: 0;
bottom: 50%;
left: 50%;
width: 1px;
transform: translateX(-0.5px);
background: var(--color-line-strong);
}
/* Line below pill — spans the bottom padding down to the next
card so the pill feels threaded onto a continuous track. */
.timeline-mobile-marker::after {
content: '';
position: absolute;
top: 50%;
bottom: 0;
left: 50%;
width: 1px;
transform: translateX(-0.5px);
background: var(--color-line-strong);
}
.timeline-item:first-child .timeline-mobile-marker::before {
display: none;
}
}
@media (min-width: 768px) {
.timeline-mobile-marker {
display: none;
}
}
</style>

View File

@@ -187,24 +187,24 @@ onMounted(() => {
</script>
<template>
<!-- First fold on md+ the wrapper is exactly one viewport tall and the
centering row below the navbar vertically centres the hero inside
the remaining space. On mobile the Hero's stacked split layout
(image + copy + CTAs) is taller than a phone viewport, so we drop
the height cap and let the section flow at its natural height;
otherwise `overflow-hidden` clips the CTAs, which is what the
mobile screenshot showed. -->
<div class="flex flex-col bg-brand md:h-svh md:overflow-hidden">
<Navbar
variant="brand"
layout="standard"
:items="navItems"
:cart-count="cart.count"
:products="products"
@cart="cartOpen = true"
@search="onSearchSelect"
/>
<!-- First fold the navbar lives OUTSIDE the fold wrapper so its
`position: sticky` escapes the wrapper's containing block and
sticks to the document scroll instead. Previously the wrapper
had `md:overflow-hidden`, which made the browser treat the
wrapper as sticky's scrollport the navbar scrolled away with
it. The wrapper is now `md:min-h-svh` (no overflow clip), so
the hero still fills the viewport on md+ without trapping
sticky. -->
<Navbar
variant="brand"
layout="standard"
:items="navItems"
:cart-count="cart.count"
:products="products"
@cart="cartOpen = true"
@search="onSearchSelect"
/>
<div class="flex flex-col bg-brand md:min-h-svh">
<div class="md:flex-1 md:flex md:items-center">
<Hero
class="w-full"

View File

@@ -27,9 +27,9 @@ const src = '/design/preview/revitalization'
:headline-em="t('revit.headline.em')"
:sub="t('revit.sub')"
:features="[
t('revit.feature.1.title'),
t('revit.feature.2.title'),
t('revit.feature.3.title'),
{ title: t('revit.feature.1.title'), icon: '⚗️' },
{ title: t('revit.feature.2.title'), icon: '💊' },
{ title: t('revit.feature.3.title'), icon: '🌿' },
]"
:notify-cta="t('revit.notifyCta')"
@notify="captureEmail()"

View File

@@ -6,9 +6,9 @@ import { useI18n } from '@/i18n/index.js'
const { t } = useI18n()
const features = computed(() => [
t('revit.feature.1.title'),
t('revit.feature.2.title'),
t('revit.feature.3.title'),
{ title: t('revit.feature.1.title'), icon: '⚗️' },
{ title: t('revit.feature.2.title'), icon: '💊' },
{ title: t('revit.feature.3.title'), icon: '🌿' },
])
</script>