feat: Armarium full customization and 4-language i18n (v0.8.0)

Replaces Astro Rocket demo content with Armarium branding and adds
complete DE/FR/IT/EN translations across all pages.

Branding & content (v0.7.0):
- Add horizontal SVG logo to navbar with currentColor dark mode support
- Rewrite homepage with Armarium hero, 6 feature cards, trust bar,
  Zürich coat of arms SVG, and CTA; shared HomePage.astro component
- Add privacy page (/datenschutz) with 6 Infomaniak certification cards
  and 8-section policy (ISO 27001:2022, Swiss Hosting, nDSG/GDPR, etc.)
- Add legal notice page (/impressum)
- Rewrite about, contact, 404 pages with Armarium content
- Add features page (/projects) from projects content collection
- Add language switcher dropdown (LanguageSwitcherDropdown.astro)
- Add single launch blog post; remove all demo blog/project content
- Set up i18n foundation: astro.config.mjs, ui.ts, utils.ts

Full i18n (v0.8.0):
- Add all pages in FR/IT/EN: about, contact, blog, features, privacy,
  legal notice — 28 locale variants total
- Language switcher visible in every layout (PageLayout, BlogLayout,
  ProjectLayout, LandingLayout) with translated nav items
- Locale-aware nav and footer hrefs via nav.*.href keys in ui.ts
- Shared page components (AboutPage, ContactPage, FeaturesIndexPage,
  BlogIndexPage) accept locale prop; locale pages are 4-line wrappers
- Extend content.config.ts locale enum with de and it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel Krähenbühl
2026-04-13 21:51:21 +02:00
parent 4053bdfbc5
commit d668aa0fdf
75 changed files with 3126 additions and 3619 deletions
+167
View File
@@ -0,0 +1,167 @@
---
import PageLayout from '@/layouts/PageLayout.astro';
import Icon from '@/components/ui/primitives/Icon/Icon.astro';
import Badge from '@/components/ui/data-display/Badge/Badge.astro';
import Card from '@/components/ui/data-display/Card/Card.astro';
import Button from '@/components/ui/form/Button/Button.astro';
import { Hero } from '@/components/hero';
import { useTranslations } from '@/i18n/utils';
import type { Locale } from '@/i18n/ui';
interface Props {
locale: Locale;
}
const { locale } = Astro.props;
const t = useTranslations(locale);
---
<PageLayout
title={`${t('about.title.pre')}${t('about.title.accent')} — Armarium`}
description={t('about.desc')}
locale={locale}
>
<Hero layout="centered" size="sm">
<Badge slot="badge" variant="brand" pill>
<Icon name="info" size="sm" />
{t('about.badge')}
</Badge>
<h1 slot="title">
<span class="text-foreground [-webkit-text-fill-color:currentColor]">{t('about.title.pre')}</span>
<span class="text-brand-500 [-webkit-text-fill-color:var(--color-brand-500)]">{t('about.title.accent')}</span>
</h1>
<p slot="description">{t('about.desc')}</p>
</Hero>
<!-- Mission -->
<section class="py-[var(--space-section-md)] bg-brand-500/8 dark:bg-background-secondary">
<div class="mx-auto max-w-6xl px-6">
<div class="grid gap-12 lg:grid-cols-2">
<div class="flex flex-col gap-4" data-reveal>
<div class="flex flex-col gap-6">
<Badge variant="brand" pill class="self-start">{t('about.mission.badge')}</Badge>
<h2 class="font-display text-3xl md:text-4xl font-bold text-foreground">
{t('about.mission.title')}
</h2>
</div>
<p class="text-lg text-foreground-muted leading-relaxed">{t('about.mission.p1')}</p>
<p class="text-lg text-foreground-muted leading-relaxed">{t('about.mission.p2')}</p>
</div>
<div class="flex flex-col gap-4 h-full" data-reveal data-reveal-delay="1">
<Card variant="elevated" hover class="flex-1 flex flex-col justify-center">
<div class="flex items-start gap-4">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-500/5 flex items-center justify-center text-brand-500 shrink-0">
<Icon name="wallet" size="md" />
</div>
<div class="flex-1 min-w-0">
<p class="text-xs font-bold tracking-wide text-foreground-muted mb-1">{t('about.app.label')}</p>
<h3 class="text-base font-semibold text-foreground">{t('about.app.title')}</h3>
<p class="text-sm text-foreground-muted leading-relaxed mt-1.5">{t('about.app.desc')}</p>
<div class="flex flex-wrap gap-1.5 mt-3">
<Badge size="sm" variant="brand">Angular</Badge>
<Badge size="sm" variant="brand">Budget</Badge>
<Badge size="sm" variant="brand">{t('about.privacy.label')}</Badge>
</div>
</div>
</div>
</Card>
<Card variant="elevated" hover class="flex-1 flex flex-col justify-center">
<div class="flex items-start gap-4">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-500/5 flex items-center justify-center text-brand-500 shrink-0">
<Icon name="shield-check" size="md" />
</div>
<div class="flex-1 min-w-0">
<p class="text-xs font-bold tracking-wide text-foreground-muted mb-1">{t('about.privacy.label')}</p>
<h3 class="text-base font-semibold text-foreground">{t('about.privacy.title')}</h3>
<p class="text-sm text-foreground-muted leading-relaxed mt-1.5">{t('about.privacy.desc')}</p>
</div>
</div>
</Card>
</div>
</div>
</div>
</section>
<!-- Values -->
<section class="py-[var(--space-section-md)] border-t border-border">
<div class="mx-auto max-w-6xl px-6">
<div class="mb-8 text-center flex flex-col items-center gap-4" data-reveal>
<div class="flex flex-col items-center gap-6">
<Badge variant="brand" pill>{t('about.values.badge')}</Badge>
<h2 class="font-display text-4xl font-bold text-foreground">{t('about.values.title')}</h2>
</div>
</div>
<div class="grid gap-8 sm:grid-cols-2 lg:grid-cols-4" data-reveal data-reveal-delay="1">
<Card hover>
<div class="flex items-start gap-4">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-500/5 flex items-center justify-center text-brand-500 shrink-0">
<Icon name="eye" size="md" />
</div>
<div class="flex-1 min-w-0">
<h3 class="text-base font-semibold text-foreground">{t('about.v1.title')}</h3>
<p class="text-sm text-foreground-muted leading-relaxed mt-1.5">{t('about.v1.desc')}</p>
</div>
</div>
</Card>
<Card hover>
<div class="flex items-start gap-4">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-500/5 flex items-center justify-center text-brand-500 shrink-0">
<Icon name="zap" size="md" />
</div>
<div class="flex-1 min-w-0">
<h3 class="text-base font-semibold text-foreground">{t('about.v2.title')}</h3>
<p class="text-sm text-foreground-muted leading-relaxed mt-1.5">{t('about.v2.desc')}</p>
</div>
</div>
</Card>
<Card hover>
<div class="flex items-start gap-4">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-500/5 flex items-center justify-center text-brand-500 shrink-0">
<Icon name="lock" size="md" />
</div>
<div class="flex-1 min-w-0">
<h3 class="text-base font-semibold text-foreground">{t('about.v3.title')}</h3>
<p class="text-sm text-foreground-muted leading-relaxed mt-1.5">{t('about.v3.desc')}</p>
</div>
</div>
</Card>
<!-- Made in Zürich -->
<Card hover>
<div class="flex items-start gap-4">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-500/5 flex items-center justify-center shrink-0">
<svg class="w-6 h-7" viewBox="0 0 32 36" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="Zürich" role="img">
<defs><clipPath id="zh-about"><path d="M16 1L1 7V23L6 30L16 34L26 30L31 23V7L16 1Z"/></clipPath></defs>
<path d="M16 1L1 7V23L6 30L16 34L26 30L31 23V7L16 1Z" fill="white"/>
<polygon points="1,7 16,1 31,7 1,34" fill="#003DA5" clip-path="url(#zh-about)"/>
<path d="M16 1L1 7V23L6 30L16 34L26 30L31 23V7L16 1Z" fill="none" stroke="#003DA5" stroke-width="1.5"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<h3 class="text-base font-semibold text-foreground">{t('about.v4.title')}</h3>
<p class="text-sm text-foreground-muted leading-relaxed mt-1.5">{t('about.v4.desc')}</p>
</div>
</div>
</Card>
</div>
</div>
</section>
<!-- CTA -->
<section class="py-[var(--space-section-md)] bg-background">
<div class="mx-auto max-w-2xl px-6 text-center" data-reveal>
<h2 class="font-display text-4xl font-bold text-foreground mb-4 text-balance">{t('about.cta.title')}</h2>
<p class="text-lg text-foreground-muted mb-8 text-balance">{t('about.cta.desc')}</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<Button size="lg" href="/register">
{t('cta.register')}
<Icon name="arrow-right" size="sm" />
</Button>
<Button size="lg" variant="outline" href={locale === 'de' ? '/' : `/${locale}/`}>
{t('about.cta.back')}
</Button>
</div>
</div>
</section>
</PageLayout>