feat: Graffen Foodtruck Website – Initial Release

SvelteKit Single-Page Website für den rumänischen Foodtruck "Graffen":
- Hero mit Vanta.js Fog-Effekt und ornamentiertem Scroll-Button
- Speisekarte mit Sommer-/Winterkarte (JSON-basiert), Allergenen, Vegetarisch/Vegan-Kennzeichnung
- Catering-Sektion mit Anlass-Karten und CTAs
- Geschichte-Sektion mit Brand-Story
- Kontakt mit Instagram/E-Mail
- Ornamentierte Buttons und Modal im mittelalterlichen Stil
- Responsive Design mit warmem Sepia/Gold-Farbschema
- Lokale Fonts (Inter + UnifrakturMaguntia via @fontsource)
- Static Adapter für einfaches Hosting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dne 2026-03-23 20:44:16 +01:00
commit 0a2f79ee5e
26 changed files with 3639 additions and 0 deletions

43
.gitignore vendored Normal file
View file

@ -0,0 +1,43 @@
# Dependencies
node_modules
# IDE
.idea
.vscode
# Dotfiles (symlinked from home)
.bash_profile
.bashrc
.zshrc
.zprofile
.profile
.gitconfig
.gitmodules
.npmrc
.ripgreprc
.mcp.json
# Claude
.claude
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

42
README.md Normal file
View file

@ -0,0 +1,42 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project
npx sv create my-app
```
To recreate this project with the same configuration:
```sh
# recreate this project
npx sv@0.12.8 create --template minimal --types ts --no-install .
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

2312
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

32
package.json Normal file
View file

@ -0,0 +1,32 @@
{
"name": "graffen-foodtruck",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.0.0",
"svelte": "^5.51.0",
"svelte-check": "^4.4.2",
"tailwindcss": "^4.0.0",
"typescript": "^5.9.3",
"vite": "^7.3.1"
},
"dependencies": {
"@fontsource/inter": "^5.2.8",
"@fontsource/unifrakturmaguntia": "^5.2.8",
"lucide-svelte": "^1.0.1",
"three": "^0.183.2",
"vanta": "^0.5.24"
}
}

32
src/app.css Normal file
View file

@ -0,0 +1,32 @@
@import '@fontsource/inter/300.css';
@import '@fontsource/inter/400.css';
@import '@fontsource/inter/500.css';
@import '@fontsource/inter/600.css';
@import '@fontsource/inter/700.css';
@import '@fontsource/unifrakturmaguntia/400.css';
@import 'tailwindcss';
@theme {
--color-parchment: #f5e6c8;
--color-gold: #c9a84c;
--color-sepia: #8b6914;
--color-brown: #4a3728;
--color-charcoal: #2a1f14;
--color-forest: #3d4a2e;
--color-cream: #faf3e3;
--font-gothic: 'UnifrakturMaguntia', serif;
--font-sans: 'Inter', system-ui, sans-serif;
}
body {
font-family: var(--font-sans);
color: var(--color-brown);
background-color: var(--color-parchment);
}
button,
[role='button'],
a {
cursor: pointer;
}

15
src/app.d.ts vendored Normal file
View file

@ -0,0 +1,15 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
declare module 'vanta/dist/vanta.fog.min';
export {};

11
src/app.html Normal file
View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="de" class="scroll-smooth">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,39 @@
<section id="geschichte" class="bg-charcoal py-20 text-cream">
<div class="mx-auto max-w-4xl px-6">
<h2 class="font-gothic mb-12 text-center text-5xl text-gold md:text-6xl">
Unsere Geschichte
</h2>
<div class="space-y-6 text-lg leading-relaxed text-cream/85">
<p>
<strong class="text-gold">Graffen</strong> &ndash; das ist rumänische Seele auf vier Rädern.
</p>
<p>
Aufgewachsen mit den Rezepten unserer Bunică aus Siebenbürgen, bringen wir die Aromen
Transsilvaniens auf die Straßen Deutschlands. Unsere Gerichte erzählen Geschichten von
nebelverhangenen Karpaten, von Bauernhöfen und von einer Küche, in der es immer nach frisch
gebackenem Brot und Kräutersuppe duftete.
</p>
<p>
Jedes Gericht wird frisch zubereitet &ndash; mit ehrlichen Zutaten, ohne Abkürzungen. Genau
so, wie es Bunică getan hat. Wir kochen nicht für Trends, sondern für den Geschmack, der
bleibt.
</p>
<p>
Der Name <em class="text-gold">&bdquo;Graffen&ldquo;</em> verbindet zwei Welten: den
deutschen Adel mit der Mystik Transsilvaniens &ndash; eine Hommage an die Grafen, die einst
in den Burgen Siebenbürgens residierten.
</p>
</div>
<!-- Decorative divider -->
<div class="mt-12 flex items-center justify-center gap-4">
<div class="h-px w-16 bg-gold/40"></div>
<span class="font-gothic text-2xl text-gold/60">&#10022;</span>
<div class="h-px w-16 bg-gold/40"></div>
</div>
</div>
</section>

View file

@ -0,0 +1,94 @@
<script lang="ts">
import OrnateButton from './OrnateButton.svelte';
</script>
<section id="catering" class="bg-brown py-20 text-cream">
<div class="mx-auto max-w-4xl px-6">
<h2 class="font-gothic mb-4 text-center text-5xl text-gold md:text-6xl">Catering</h2>
<p class="mx-auto mb-14 max-w-2xl text-center text-lg text-cream/80">
Euer Event, unser Foodtruck &ndash; für unvergessliche Momente mit authentischer rumänischer
Küche.
</p>
<!-- Anlässe Grid -->
<div class="mb-14 grid gap-6 md:grid-cols-3">
<div class="rounded-lg border border-gold/20 bg-cream/5 p-6 text-center">
<div class="mb-4 text-3xl">&#127866;</div>
<h3 class="mb-2 text-lg font-semibold text-gold">Hochzeiten</h3>
<p class="text-sm text-cream/70">
Macht euren großen Tag besonders &ndash; mit rumänischen Spezialitäten frisch vom Grill.
Rustikales Flair trifft auf Festlichkeit.
</p>
</div>
<div class="rounded-lg border border-gold/20 bg-cream/5 p-6 text-center">
<div class="mb-4 text-3xl">&#127970;</div>
<h3 class="mb-2 text-lg font-semibold text-gold">Firmenfeiern</h3>
<p class="text-sm text-cream/70">
Sommerfest, Jubiläum oder Teambuilding? Wir sorgen für zufriedene Bäuche und gute
Stimmung bei euren Mitarbeitern.
</p>
</div>
<div class="rounded-lg border border-gold/20 bg-cream/5 p-6 text-center">
<div class="mb-4 text-3xl">&#127879;</div>
<h3 class="mb-2 text-lg font-semibold text-gold">Private Feiern</h3>
<p class="text-sm text-cream/70">
Geburtstag, Gartenparty oder Familientreffen &ndash; wir kommen mit dem Foodtruck direkt
zu euch und kümmern uns ums Essen.
</p>
</div>
</div>
<!-- USPs -->
<div class="mb-14 rounded-lg border border-gold/30 bg-gold/5 p-8">
<h3 class="mb-6 text-center text-xl font-semibold text-gold">Was wir mitbringen</h3>
<div class="grid gap-4 text-sm text-cream/80 md:grid-cols-2">
<div class="flex items-start gap-3">
<span class="mt-0.5 text-gold">&#10003;</span>
<span>Unser voll ausgestatteter Foodtruck &ndash; autark und flexibel</span>
</div>
<div class="flex items-start gap-3">
<span class="mt-0.5 text-gold">&#10003;</span>
<span>Frische Zubereitung vor Ort &ndash; euer Event wird zum Erlebnis</span>
</div>
<div class="flex items-start gap-3">
<span class="mt-0.5 text-gold">&#10003;</span>
<span>Individuelle Menüplanung abgestimmt auf eure Gästezahl</span>
</div>
<div class="flex items-start gap-3">
<span class="mt-0.5 text-gold">&#10003;</span>
<span>Flexible Anpassung an Allergien und Ernährungswünsche</span>
</div>
<div class="flex items-start gap-3">
<span class="mt-0.5 text-gold">&#10003;</span>
<span>Events ab 30 Personen &ndash; von intim bis groß</span>
</div>
<div class="flex items-start gap-3">
<span class="mt-0.5 text-gold">&#10003;</span>
<span>Einsatzbereit in der gesamten Region</span>
</div>
</div>
</div>
<!-- CTA -->
<div class="text-center">
<p class="mb-8 text-cream/70">
Erzählt uns von eurem Event &ndash; wir erstellen euch ein unverbindliches Angebot.
</p>
<div class="flex flex-col items-center justify-center gap-6 sm:flex-row">
<OrnateButton href="mailto:hallo@graffen-foodtruck.de?subject=Catering-Anfrage">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
Catering anfragen
</OrnateButton>
</div>
</div>
</div>
</section>

View file

@ -0,0 +1,45 @@
<script lang="ts">
import OrnateButton from './OrnateButton.svelte';
</script>
<section id="kontakt" class="bg-parchment py-20">
<div class="mx-auto max-w-4xl px-6 text-center">
<h2 class="font-gothic mb-12 text-5xl text-brown md:text-6xl">Wo ihr uns findet</h2>
<p class="mb-10 text-lg text-sepia">
Wir sind mit unserem Foodtruck auf Wochenmärkten, Street-Food-Festivals und Events in der
Region unterwegs. <br> <br> Folgt uns auf Instagram für aktuelle Standorte und Speisekarten.
</p>
<div class="flex flex-col items-center justify-center gap-6 sm:flex-row">
<OrnateButton
href="https://instagram.com/graffen.foodtruck"
target="_blank"
rel="noopener noreferrer"
>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path
d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"
/>
</svg>
@graffen.foodtruck
</OrnateButton>
<OrnateButton href="mailto:hallo@graffen-foodtruck.de" variant="secondary">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
hallo@graffen-foodtruck.de
</OrnateButton>
</div>
<p class="mt-10 text-sepia/70">
Catering-Anfragen für private Feiern, Firmenfeste oder Events? Schreibt uns einfach!
</p>
</div>
</section>

View file

@ -0,0 +1,13 @@
<footer class="bg-charcoal py-10 text-cream/60">
<div class="mx-auto max-w-6xl px-6 text-center">
<p class="font-gothic mb-4 text-3xl text-gold/70">Graffen</p>
<p class="mb-6 text-sm">Rumänische Hausmannskost aus dem Foodtruck</p>
<div class="mb-6 flex justify-center gap-6 text-sm">
<a href="/impressum" class="transition-colors hover:text-gold">Impressum</a>
<a href="/datenschutz" class="transition-colors hover:text-gold">Datenschutz</a>
</div>
<p class="text-xs text-cream/40">&copy; 2026 Graffen &ndash; Alle Rechte vorbehalten.</p>
</div>
</footer>

View file

@ -0,0 +1,155 @@
<script lang="ts">
import { onMount } from 'svelte';
let fogEl: HTMLDivElement;
onMount(async () => {
const THREE = await import('three');
const FOG = (await import('vanta/dist/vanta.fog.min')).default;
const effect = FOG({
el: fogEl,
THREE,
mouseControls: false,
touchControls: false,
gyroControls: false,
minHeight: 200.0,
minWidth: 200.0,
highlightColor: 0x8b6914,
midtoneColor: 0x4a3728,
lowlightColor: 0x2a1f14,
baseColor: 0x1a1008,
blurFactor: 0.5,
speed: 0.8,
zoom: 1.2
});
return () => effect.destroy();
});
</script>
<section id="hero" class="relative flex h-screen items-center justify-center overflow-hidden">
<!-- Background Image -->
<div
class="absolute inset-0 bg-cover bg-center"
style="background-image: url('/graffen-hero.png');"
></div>
<!-- Overlay -->
<div class="absolute inset-0 bg-gradient-to-t from-charcoal via-charcoal/60 to-charcoal/30"></div>
<!-- Vanta Fog Layer -->
<div
bind:this={fogEl}
class="absolute inset-0"
style="mix-blend-mode: screen;"
></div>
<!-- Content -->
<div class="relative z-10 text-center">
<h1 class="font-gothic text-7xl text-gold drop-shadow-lg md:text-9xl">Graffen</h1>
<p class="mt-4 text-lg font-light tracking-wide text-cream/90 md:text-xl">
Rumänische Hausmannskost aus dem Foodtruck
</p>
</div>
<!-- Ornate Scroll Indicator -->
<div class="absolute bottom-20 z-10 flex w-full justify-center">
<a
href="#speisekarte"
class="scroll-indicator relative flex h-24 w-24 items-center justify-center rounded-full bg-charcoal/40 backdrop-blur-sm"
aria-label="Nach unten scrollen"
>
<svg
class="pointer-events-none absolute inset-0 h-full w-full overflow-visible"
viewBox="0 0 80 80"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Inner circle -->
<circle cx="40" cy="40" r="28" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.35" />
<!-- Outer circle -->
<circle cx="40" cy="40" r="38" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.5" />
<!-- Top ornament: fleur -->
<path d="M40 2 L40 -3" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" />
<path d="M36 1 C38 -4, 42 -4, 44 1" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.5" fill="none" />
<circle cx="40" cy="-4" r="1.5" fill="var(--color-gold)" fill-opacity="0.5" />
<!-- Bottom ornament: fleur -->
<path d="M40 78 L40 83" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" />
<path d="M36 79 C38 84, 42 84, 44 79" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.5" fill="none" />
<circle cx="40" cy="84" r="1.5" fill="var(--color-gold)" fill-opacity="0.5" />
<!-- Left ornament -->
<path d="M2 40 L-3 40" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" />
<path d="M1 36 C-4 38, -4 42, 1 44" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.5" fill="none" />
<circle cx="-4" cy="40" r="1.5" fill="var(--color-gold)" fill-opacity="0.5" />
<!-- Right ornament -->
<path d="M78 40 L83 40" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" />
<path d="M79 36 C84 38, 84 42, 79 44" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.5" fill="none" />
<circle cx="84" cy="40" r="1.5" fill="var(--color-gold)" fill-opacity="0.5" />
<!-- Diagonal accents -->
<path d="M12 12 C14 9, 9 14, 12 12" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" fill="none" />
<path d="M68 12 C66 9, 71 14, 68 12" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" fill="none" />
<path d="M12 68 C14 71, 9 66, 12 68" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" fill="none" />
<path d="M68 68 C66 71, 71 66, 68 68" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" fill="none" />
<!-- Tiny dots between inner and outer ring -->
<circle cx="40" cy="8" r="0.8" fill="var(--color-gold)" fill-opacity="0.3" />
<circle cx="40" cy="72" r="0.8" fill="var(--color-gold)" fill-opacity="0.3" />
<circle cx="8" cy="40" r="0.8" fill="var(--color-gold)" fill-opacity="0.3" />
<circle cx="72" cy="40" r="0.8" fill="var(--color-gold)" fill-opacity="0.3" />
<circle cx="16" cy="16" r="0.8" fill="var(--color-gold)" fill-opacity="0.25" />
<circle cx="64" cy="16" r="0.8" fill="var(--color-gold)" fill-opacity="0.25" />
<circle cx="16" cy="64" r="0.8" fill="var(--color-gold)" fill-opacity="0.25" />
<circle cx="64" cy="64" r="0.8" fill="var(--color-gold)" fill-opacity="0.25" />
</svg>
<!-- Arrow -->
<svg
class="scroll-arrow relative z-10 h-7 w-7 text-gold/80"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5v14m-6-6l6 6 6-6"
/>
</svg>
</a>
</div>
</section>
<style>
.scroll-indicator {
animation: float 2.5s ease-in-out infinite;
}
.scroll-arrow {
animation: arrow-pulse 2.5s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(8px);
}
}
@keyframes arrow-pulse {
0%, 100% {
opacity: 0.8;
}
50% {
opacity: 1;
}
}
</style>

View file

@ -0,0 +1,265 @@
<script lang="ts">
import type { MenuData } from '$lib/data/menu';
import menuJson from '$lib/data/menu.json';
import { Leaf, X } from 'lucide-svelte';
const menuData: MenuData = menuJson;
let activeTab = $state(0);
let showAllergens = $state(false);
</script>
<section id="speisekarte" class="bg-cream py-20">
<div class="mx-auto max-w-4xl px-6">
<h2 class="font-gothic mb-2 text-center text-5xl text-brown md:text-6xl">Speisekarte</h2>
<p class="mx-auto mb-10 max-w-2xl text-center text-sepia italic">
Traditionelle rumänische Rezepte &ndash; ehrlich und herzhaft. <br>
Alle
Gerichte werden frisch im Foodtruck zubereitet.
</p>
<!-- Season Tabs (Ornate Buttongroup) -->
<div class="mb-12 flex justify-center">
<div class="ornate-tabgroup relative inline-flex">
<svg
class="pointer-events-none absolute inset-0 h-full w-full"
viewBox="0 0 300 50"
preserveAspectRatio="none"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="3" y="3" width="294" height="44" rx="4" stroke="var(--color-gold)" stroke-width="1.2" stroke-opacity="0.6" />
<path d="M3 13 C3 13, 0 8, 0 3 C0 3, 5 0, 10 0 L10 2.5 C6.5 2.5, 4 4, 3 6.5 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M297 13 C297 13, 300 8, 300 3 C300 3, 295 0, 290 0 L290 2.5 C293.5 2.5, 296 4, 297 6.5 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M3 37 C3 37, 0 42, 0 47 C0 47, 5 50, 10 50 L10 47.5 C6.5 47.5, 4 46, 3 43.5 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M297 37 C297 37, 300 42, 300 47 C300 47, 295 50, 290 50 L290 47.5 C293.5 47.5, 296 46, 297 43.5 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M130 3 C137 -2, 143 -1, 150 2 C157 -1, 163 -2, 170 3" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" fill="none" />
<path d="M130 47 C137 52, 143 51, 150 48 C157 51, 163 52, 170 47" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" fill="none" />
<path d="M3 21 L0 25 L3 29" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" fill="none" />
<path d="M297 21 L300 25 L297 29" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.6" fill="none" />
<line x1="150" y1="8" x2="150" y2="42" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" />
<path d="M147 8 L150 4 L153 8" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.5" fill="none" />
<path d="M147 42 L150 46 L153 42" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.5" fill="none" />
</svg>
{#each menuData.seasons as menu, i}
<button
class="ornate-tab relative z-10 px-12 py-4 text-base font-semibold transition-colors {activeTab === i
? 'text-gold'
: 'text-brown/60 hover:text-gold/80'}"
onclick={() => (activeTab = i)}
>
{menu.season}
</button>
{/each}
</div>
</div>
<!-- Menu Content -->
{#each menuData.seasons as menu, menuIndex}
{#if activeTab === menuIndex}
{#each menu.categories as category}
<div class="mb-14 last:mb-0">
<h3
class="mb-6 border-b border-gold/40 pb-2 text-center text-2xl font-bold tracking-wide text-brown"
>
{category.title}
</h3>
<div class="grid gap-4 sm:grid-cols-2">
{#each category.items as item}
<div class="rounded-lg border border-gold/15 bg-parchment/40 p-5">
<div class="flex items-center gap-2">
<span class="text-lg font-semibold text-brown">{item.name}</span>
{#if item.vegetarian}
<span class="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[#009036]" title="vegetarisch">
<Leaf size={14} color="#FEDD00" strokeWidth={2.5} />
</span>
{/if}
{#if item.vegan}
<span class="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[#FEDD00]" title="vegan">
<Leaf size={14} color="#009036" strokeWidth={2.5} />
</span>
{/if}
{#if item.allergens || item.additives}
<sup class="text-[10px] font-normal text-sepia/50">{item.allergens}{#if item.additives}, {item.additives}{/if}</sup>
{/if}
</div>
{#if item.description}
<p class="mt-2 text-sm leading-relaxed text-sepia">{item.description}</p>
{/if}
</div>
{/each}
</div>
</div>
{/each}
{/if}
{/each}
<!-- Allergen Legend Trigger -->
<div class="mt-10 text-center">
<button
class="inline-flex items-center gap-2 text-sm text-sepia/60 transition-colors hover:text-gold"
onclick={() => (showAllergens = true)}
>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Allergene &amp; Zusatzstoffe anzeigen
</button>
</div>
</div>
</section>
<!-- Allergen Modal -->
{#if showAllergens}
<!-- Backdrop -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-charcoal/70 p-4 backdrop-blur-sm"
onkeydown={(e) => e.key === 'Escape' && (showAllergens = false)}
onclick={(e) => e.target === e.currentTarget && (showAllergens = false)}
>
<!-- Modal -->
<div class="ornate-modal relative w-full max-w-lg" role="dialog" aria-label="Allergene & Zusatzstoffe">
<!-- Ornate border (CSS) -->
<div class="pointer-events-none absolute inset-0 rounded-lg border border-gold/50"></div>
<!-- Corner ornaments (fixed-size SVGs, no distortion) -->
<!-- Top-left -->
<svg class="pointer-events-none absolute -top-2 -left-2 h-10 w-10 overflow-visible" viewBox="0 0 40 40" fill="none">
<path d="M8 32 C8 32, 2 20, 2 8 C2 8, 14 2, 26 2 L26 6 C16 6, 10 10, 8 16 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M2 8 L0 0 L8 2" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.4" fill="none" />
</svg>
<!-- Top-right -->
<svg class="pointer-events-none absolute -top-2 -right-2 h-10 w-10 overflow-visible" viewBox="0 0 40 40" fill="none">
<path d="M32 32 C32 32, 38 20, 38 8 C38 8, 26 2, 14 2 L14 6 C24 6, 30 10, 32 16 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M38 8 L40 0 L32 2" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.4" fill="none" />
</svg>
<!-- Bottom-left -->
<svg class="pointer-events-none absolute -bottom-2 -left-2 h-10 w-10 overflow-visible" viewBox="0 0 40 40" fill="none">
<path d="M8 8 C8 8, 2 20, 2 32 C2 32, 14 38, 26 38 L26 34 C16 34, 10 30, 8 24 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M2 32 L0 40 L8 38" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.4" fill="none" />
</svg>
<!-- Bottom-right -->
<svg class="pointer-events-none absolute -bottom-2 -right-2 h-10 w-10 overflow-visible" viewBox="0 0 40 40" fill="none">
<path d="M32 8 C32 8, 38 20, 38 32 C38 32, 26 38, 14 38 L14 34 C24 34, 30 30, 32 24 Z" fill="var(--color-gold)" fill-opacity="0.7" />
<path d="M38 32 L40 40 L32 38" stroke="var(--color-gold)" stroke-width="1" stroke-opacity="0.4" fill="none" />
</svg>
<!-- Top center flourish -->
<svg class="pointer-events-none absolute -top-3 left-1/2 h-6 w-20 -translate-x-1/2 overflow-visible" viewBox="0 0 80 24" fill="none">
<path d="M20 12 C28 0, 36 2, 40 10 C44 2, 52 0, 60 12" stroke="var(--color-gold)" stroke-width="1.5" stroke-opacity="0.6" fill="none" />
<circle cx="40" cy="2" r="2.5" fill="var(--color-gold)" fill-opacity="0.5" />
<path d="M10 12 L20 12" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" />
<path d="M60 12 L70 12" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" />
</svg>
<!-- Bottom center flourish -->
<svg class="pointer-events-none absolute -bottom-3 left-1/2 h-6 w-20 -translate-x-1/2 overflow-visible" viewBox="0 0 80 24" fill="none">
<path d="M20 12 C28 24, 36 22, 40 14 C44 22, 52 24, 60 12" stroke="var(--color-gold)" stroke-width="1.5" stroke-opacity="0.6" fill="none" />
<circle cx="40" cy="22" r="2.5" fill="var(--color-gold)" fill-opacity="0.5" />
<path d="M10 12 L20 12" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" />
<path d="M60 12 L70 12" stroke="var(--color-gold)" stroke-width="0.8" stroke-opacity="0.3" />
</svg>
<!-- Side diamonds: left -->
<svg class="pointer-events-none absolute top-1/2 -left-3 h-8 w-6 -translate-y-1/2 overflow-visible" viewBox="0 0 24 32" fill="none">
<path d="M20 8 L4 16 L20 24" stroke="var(--color-gold)" stroke-width="1.5" stroke-opacity="0.6" fill="none" />
<circle cx="4" cy="16" r="2" fill="var(--color-gold)" fill-opacity="0.4" />
</svg>
<!-- Side diamonds: right -->
<svg class="pointer-events-none absolute top-1/2 -right-3 h-8 w-6 -translate-y-1/2 overflow-visible" viewBox="0 0 24 32" fill="none">
<path d="M4 8 L20 16 L4 24" stroke="var(--color-gold)" stroke-width="1.5" stroke-opacity="0.6" fill="none" />
<circle cx="20" cy="16" r="2" fill="var(--color-gold)" fill-opacity="0.4" />
</svg>
<!-- Modal Content -->
<div class="relative z-10 rounded-lg bg-cream p-8 pt-6">
<!-- Close button -->
<button
class="absolute top-4 right-4 text-sepia/50 transition-colors hover:text-gold"
onclick={() => (showAllergens = false)}
aria-label="Schließen"
>
<X size={20} />
</button>
<h3 class="font-gothic mb-6 text-center text-3xl text-brown">
Allergene &amp; Zusatzstoffe
</h3>
<!-- Decorative divider -->
<div class="mb-6 flex items-center justify-center gap-3">
<div class="h-px w-12 bg-gold/40"></div>
<span class="font-gothic text-lg text-gold/50">&#10022;</span>
<div class="h-px w-12 bg-gold/40"></div>
</div>
<div class="mb-6">
<p class="mb-3 text-center text-xs font-semibold tracking-wider text-brown/60 uppercase">Allergene</p>
<div class="flex flex-wrap justify-center gap-2">
{#each menuData.allergens as a}
<span class="inline-flex items-center gap-1.5 rounded-full border border-gold/25 bg-parchment px-3 py-1.5 text-xs">
<span class="font-bold text-brown">{a.code}</span>
<span class="text-sepia">{a.label}</span>
</span>
{/each}
</div>
</div>
<div class="mb-6">
<p class="mb-3 text-center text-xs font-semibold tracking-wider text-brown/60 uppercase">Zusatzstoffe</p>
<div class="flex flex-wrap justify-center gap-2">
{#each menuData.additives as a}
<span class="inline-flex items-center gap-1.5 rounded-full border border-sepia/20 bg-parchment px-3 py-1.5 text-xs">
<span class="font-bold text-brown">{a.code}</span>
<span class="text-sepia">{a.label}</span>
</span>
{/each}
</div>
</div>
<!-- Decorative divider -->
<div class="mb-6 flex items-center justify-center gap-3">
<div class="h-px w-8 bg-gold/30"></div>
<span class="text-xs text-gold/40">&#9830;</span>
<div class="h-px w-8 bg-gold/30"></div>
</div>
<div class="flex flex-wrap items-center justify-center gap-3">
<span class="inline-flex items-center gap-1.5 rounded-full border border-[#009036]/30 bg-parchment px-3 py-1.5 text-xs">
<span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-[#009036]">
<Leaf size={12} color="#FEDD00" strokeWidth={2.5} />
</span>
<span class="text-sepia">vegetarisch</span>
</span>
<span class="inline-flex items-center gap-1.5 rounded-full border border-[#FEDD00]/40 bg-parchment px-3 py-1.5 text-xs">
<span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-[#FEDD00]">
<Leaf size={12} color="#009036" strokeWidth={2.5} />
</span>
<span class="text-sepia">vegan</span>
</span>
</div>
<p class="mt-5 text-center text-xs text-sepia/60">
Angaben in Klammern = kann Spuren enthalten
</p>
</div>
</div>
</div>
{/if}
<style>
.ornate-modal {
animation: modal-in 0.25s ease-out;
}
@keyframes modal-in {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
</style>

View file

@ -0,0 +1,89 @@
<script lang="ts">
let scrolled = $state(false);
let mobileOpen = $state(false);
function onScroll() {
scrolled = window.scrollY > 100;
}
function closeMobile() {
mobileOpen = false;
}
const links = [
{ href: '/#speisekarte', label: 'Speisekarte' },
{ href: '/#geschichte', label: 'Unsere Geschichte' },
{ href: '/#catering', label: 'Catering' },
{ href: '/#kontakt', label: 'Kontakt' }
];
</script>
<svelte:window onscroll={onScroll} />
<nav
class="fixed top-0 left-0 z-50 w-full transition-all duration-300 {scrolled
? 'bg-charcoal shadow-lg'
: 'bg-transparent'}"
>
<div class="mx-auto flex max-w-6xl items-center justify-between px-6 py-4">
<a
href="/"
class="font-gothic text-2xl transition-colors duration-300 {scrolled ? 'text-gold' : 'text-charcoal'}"
onclick={closeMobile}
>
Graffen
</a>
<!-- Desktop -->
<div class="hidden gap-8 md:flex">
{#each links as link}
<a
href={link.href}
class="text-sm font-bold tracking-wide transition-colors {scrolled ? 'text-cream/80 hover:text-gold' : 'text-charcoal hover:text-cream'}"
>
{link.label}
</a>
{/each}
</div>
<!-- Mobile Toggle -->
<button
class="md:hidden {scrolled ? 'text-cream' : 'text-charcoal'}"
onclick={() => (mobileOpen = !mobileOpen)}
aria-label="Menü"
>
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{#if mobileOpen}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
{:else}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
{/if}
</svg>
</button>
</div>
<!-- Mobile Menu -->
{#if mobileOpen}
<div class="border-t border-gold/20 bg-charcoal/95 px-6 pb-4 md:hidden">
{#each links as link}
<a
href={link.href}
class="block py-3 text-cream/80 transition-colors hover:text-gold"
onclick={closeMobile}
>
{link.label}
</a>
{/each}
</div>
{/if}
</nav>

View file

@ -0,0 +1,146 @@
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
href: string;
variant?: 'primary' | 'secondary';
target?: string;
rel?: string;
children: Snippet;
}
let { href, variant = 'primary', target, rel, children }: Props = $props();
</script>
<a {href} {target} {rel} class="ornate-btn group inline-block" data-variant={variant}>
<svg
class="ornate-frame absolute inset-0 h-full w-full"
viewBox="0 0 200 60"
preserveAspectRatio="none"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Main border -->
<rect
x="4"
y="4"
width="192"
height="52"
rx="4"
class="ornate-rect"
stroke-width="1.5"
/>
<!-- Corner ornaments: top-left -->
<path d="M4 16 C4 16, 0 10, 0 4 C0 4, 6 0, 12 0 L12 3 C8 3, 5 5, 4 8 Z" class="ornate-corner" />
<!-- top-right -->
<path d="M196 16 C196 16, 200 10, 200 4 C200 4, 194 0, 188 0 L188 3 C192 3, 195 5, 196 8 Z" class="ornate-corner" />
<!-- bottom-left -->
<path d="M4 44 C4 44, 0 50, 0 56 C0 56, 6 60, 12 60 L12 57 C8 57, 5 55, 4 52 Z" class="ornate-corner" />
<!-- bottom-right -->
<path d="M196 44 C196 44, 200 50, 200 56 C200 56, 194 60, 188 60 L188 57 C192 57, 195 55, 196 52 Z" class="ornate-corner" />
<!-- Top center flourish -->
<path d="M85 4 C90 -2, 95 -1, 100 2 C105 -1, 110 -2, 115 4" class="ornate-flourish" stroke-width="1.2" />
<!-- Bottom center flourish -->
<path d="M85 56 C90 62, 95 61, 100 58 C105 61, 110 62, 115 56" class="ornate-flourish" stroke-width="1.2" />
<!-- Side diamonds: left -->
<path d="M4 26 L0 30 L4 34" class="ornate-flourish" stroke-width="1.2" />
<!-- right -->
<path d="M196 26 L200 30 L196 34" class="ornate-flourish" stroke-width="1.2" />
</svg>
<span class="ornate-content relative z-10 inline-flex items-center gap-2">
{@render children()}
</span>
</a>
<style>
.ornate-btn {
position: relative;
padding: 0.85rem 2.2rem;
cursor: pointer;
}
.ornate-frame {
pointer-events: none;
overflow: visible;
}
/* ── Primary ── */
.ornate-btn[data-variant='primary'] .ornate-rect {
stroke: var(--color-gold);
fill: var(--color-gold);
fill-opacity: 0.1;
transition: fill-opacity 0.25s ease;
}
.ornate-btn[data-variant='primary']:hover .ornate-rect {
fill-opacity: 0.22;
}
.ornate-btn[data-variant='primary'] .ornate-corner {
fill: var(--color-gold);
opacity: 0.7;
transition: opacity 0.25s ease;
}
.ornate-btn[data-variant='primary']:hover .ornate-corner {
opacity: 1;
}
.ornate-btn[data-variant='primary'] .ornate-flourish {
stroke: var(--color-gold);
fill: none;
opacity: 0.7;
transition: opacity 0.25s ease;
}
.ornate-btn[data-variant='primary']:hover .ornate-flourish {
opacity: 1;
}
.ornate-btn[data-variant='primary'] .ornate-content {
color: var(--color-gold);
font-weight: 600;
opacity: 0.85;
transition: opacity 0.25s ease;
}
.ornate-btn[data-variant='primary']:hover .ornate-content {
opacity: 1;
}
/* ── Secondary ── */
.ornate-btn[data-variant='secondary'] .ornate-rect {
stroke: var(--color-gold);
fill: none;
opacity: 0.45;
transition: opacity 0.25s ease;
}
.ornate-btn[data-variant='secondary']:hover .ornate-rect {
opacity: 0.8;
}
.ornate-btn[data-variant='secondary'] .ornate-corner {
fill: var(--color-gold);
opacity: 0.4;
transition: opacity 0.25s ease;
}
.ornate-btn[data-variant='secondary']:hover .ornate-corner {
opacity: 0.8;
}
.ornate-btn[data-variant='secondary'] .ornate-flourish {
stroke: var(--color-gold);
fill: none;
opacity: 0.4;
transition: opacity 0.25s ease;
}
.ornate-btn[data-variant='secondary']:hover .ornate-flourish {
opacity: 0.8;
}
.ornate-btn[data-variant='secondary'] .ornate-content {
color: var(--color-gold);
font-weight: 600;
opacity: 0.7;
transition: opacity 0.25s ease;
}
.ornate-btn[data-variant='secondary']:hover .ornate-content {
opacity: 1;
}
</style>

112
src/lib/data/menu.json Normal file
View file

@ -0,0 +1,112 @@
{
"seasons": [
{
"season": "Sommerkarte",
"categories": [
{
"title": "Vom Grill",
"items": [
{
"name": "Traditionelle rumänische Cevapi (Mici)",
"description": "Hausgemachte Hackfleischröllchen aus Rind und Lamm vom Grill, serviert mit Senf und Brot",
"allergens": "A1, J"
},
{
"name": "Pulled Beef Sandwich mit Coleslaw",
"description": "Zart gegartes Rindfleisch, serviert im rustikalen Brot mit hausgemachtem Coleslaw-Salat",
"allergens": "A1, C, G, J, (L)",
"additives": "2, 3"
}
]
},
{
"title": "Getränke",
"items": [
{ "name": "Kaffee" },
{ "name": "Tee" },
{ "name": "Café Frappé" },
{ "name": "Limonade", "description": "Hausgemacht" }
]
}
]
},
{
"season": "Winterkarte",
"categories": [
{
"title": "Suppen",
"items": [
{
"name": "Bohnensuppe im Brotleib nach siebenbürgischer Art",
"description": "Traditionelle Bohnensuppe mit geräuchertem Fleisch, serviert im Bauernbrot",
"allergens": "A1, (A2), I, (G)",
"additives": "2, 3, 8"
},
{
"name": "Hühnersuppe à la Grec",
"description": "Hühnersuppe mit Reis, verfeinert mit Eigelb, Sahne und Zitrone",
"allergens": "A1, C, G, I",
"additives": "3"
},
{
"name": "Kartoffelsuppe nach siebenbürgischer Art",
"description": "Herzhafte Suppe mit Kartoffelwürfeln, geräuchertem Schweinefleisch, verfeinert mit Schmand und Estragon",
"allergens": "A1, G, I, (L)",
"additives": "2, 3, 8"
},
{
"name": "Salatsuppe nach siebenbürgischer Art",
"description": "Salatsuppe mit Speck, Omelett und Schmand",
"allergens": "A1, C, G, I"
},
{
"name": "Siebenbürgische Gemüsesuppe",
"description": "Frisches Marktgemüse, verfeinert mit Schmand und Kräutern",
"allergens": "A1, G, I",
"additives": "3, 5",
"vegetarian": true
},
{
"name": "Ungarische Gulaschsuppe (Gulyás)",
"description": "Herzhafte Rindersuppe mit Kartoffeln, Paprika und Zupfnudeln",
"allergens": "A1, C, I, L",
"additives": "3"
}
]
},
{
"title": "Vom Grill",
"items": [
{
"name": "Traditionelle rumänische Cevapi (Mici)",
"description": "Hausgemachte Hackfleischröllchen aus Rind und Lamm vom Grill, serviert mit Senf und Weißbrot",
"allergens": "A1, J"
}
]
},
{
"title": "Getränke",
"items": [
{ "name": "Kaffee" },
{ "name": "Tee" }
]
}
]
}
],
"allergens": [
{ "code": "A1", "label": "Weizen" },
{ "code": "A2", "label": "Roggen" },
{ "code": "C", "label": "Eier" },
{ "code": "G", "label": "Milch/Laktose" },
{ "code": "I", "label": "Sellerie" },
{ "code": "J", "label": "Senf" },
{ "code": "L", "label": "Lupinen" }
],
"additives": [
{ "code": "2", "label": "Konservierungsstoffe" },
{ "code": "3", "label": "Antioxidationsmittel" },
{ "code": "5", "label": "Geschmacksverstärker" },
{ "code": "8", "label": "Phosphat" }
]
}

29
src/lib/data/menu.ts Normal file
View file

@ -0,0 +1,29 @@
export interface MenuItem {
name: string;
description?: string;
allergens?: string;
additives?: string;
vegetarian?: boolean;
vegan?: boolean;
}
export interface MenuCategory {
title: string;
items: MenuItem[];
}
export interface SeasonMenu {
season: string;
categories: MenuCategory[];
}
export interface LegendEntry {
code: string;
label: string;
}
export interface MenuData {
seasons: SeasonMenu[];
allergens: LegendEntry[];
additives: LegendEntry[];
}

1
src/routes/+layout.js Normal file
View file

@ -0,0 +1 @@
export const prerender = true;

15
src/routes/+layout.svelte Normal file
View file

@ -0,0 +1,15 @@
<script lang="ts">
import '../app.css';
import Nav from '$lib/components/Nav.svelte';
import Footer from '$lib/components/Footer.svelte';
let { children } = $props();
</script>
<svelte:head>
<link rel="icon" href="/graffen-hero.png" />
</svelte:head>
<Nav />
{@render children()}
<Footer />

27
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,27 @@
<script lang="ts">
import Hero from '$lib/components/Hero.svelte';
import Menu from '$lib/components/Menu.svelte';
import About from '$lib/components/About.svelte';
import Catering from '$lib/components/Catering.svelte';
import Contact from '$lib/components/Contact.svelte';
</script>
<svelte:head>
<title>Graffen &ndash; Rumänische Hausmannskost | Foodtruck</title>
<meta
name="description"
content="Graffen Foodtruck: Traditionelle rumänische Küche aus Siebenbürgen. Sarmale, Mici, Ciorbă und mehr &ndash; frisch zubereitet auf Märkten und Events."
/>
<meta property="og:title" content="Graffen &ndash; Rumänische Hausmannskost" />
<meta
property="og:description"
content="Transsilvanische Küche auf Rädern. Ehrliche Rezepte, frische Zutaten."
/>
<meta property="og:image" content="/graffen-hero.png" />
</svelte:head>
<Hero />
<Menu />
<Catering />
<About />
<Contact />

View file

@ -0,0 +1,42 @@
<svelte:head>
<title>Datenschutz &ndash; Graffen Foodtruck</title>
</svelte:head>
<div class="mx-auto min-h-screen max-w-4xl px-6 pt-28 pb-20">
<h1 class="font-gothic mb-8 text-4xl text-brown">Datenschutzerklärung</h1>
<div class="space-y-6 text-sepia">
<div>
<h2 class="mb-2 text-xl font-semibold text-brown">1. Verantwortlicher</h2>
<p>
Daniel Baciu<br />
Graffen Foodtruck<br />
Ob. Gartenstr. 6<br />
92237 Sulzbach-Rosenberg<br />
E-Mail: hallo@graffen-foodtruck.de
</p>
</div>
<div>
<h2 class="mb-2 text-xl font-semibold text-brown">2. Hosting</h2>
<p>
Diese Webseite wird als statische Seite gehostet. Der Hoster erhebt in sogenannten
Logfiles Informationen, die Ihr Browser automatisch übermittelt (IP-Adresse, Datum und
Uhrzeit des Zugriffs, Browsertyp). Die Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO.
</p>
</div>
<div>
<h2 class="mb-2 text-xl font-semibold text-brown">3. Google Fonts</h2>
<p>
Diese Seite nutzt Google Fonts zur einheitlichen Schriftdarstellung. Beim Aufruf einer
Seite lädt Ihr Browser die benötigten Schriftarten von Google-Servern. Dabei wird Ihre
IP-Adresse an Google übermittelt. Die Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO.
</p>
</div>
<p class="text-sm text-sepia/60">
Bitte ergänze hier die vollständige Datenschutzerklärung vor Veröffentlichung der Webseite.
</p>
</div>
</div>

View file

@ -0,0 +1,27 @@
<script>
</script>
<svelte:head>
<title>Impressum &ndash; Graffen Foodtruck</title>
</svelte:head>
<div class="mx-auto min-h-screen max-w-4xl px-6 pt-28 pb-20">
<h1 class="font-gothic mb-8 text-4xl text-brown">Impressum</h1>
<div class="space-y-4 text-sepia">
<p>
Daniel Baciu<br />
Graffen Foodtruck<br />
Ob. Gartenstr. 6<br />
92237 Sulzbach-Rosenberg<br />
E-Mail: hallo@graffen-foodtruck.de
</p>
<p>
<strong>Kontakt:</strong><br />
E-Mail: hallo@graffen-foodtruck.de
</p>
</div>
</div>

BIN
static/graffen-hero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

20
svelte.config.js Normal file
View file

@ -0,0 +1,20 @@
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: undefined,
precompress: false,
strict: true
})
},
vitePlugin: {
dynamicCompileOptions: ({ filename }) =>
filename.includes('node_modules') ? undefined : { runes: true }
}
};
export default config;

20
tsconfig.json Normal file
View file

@ -0,0 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}

13
vite.config.ts Normal file
View file

@ -0,0 +1,13 @@
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
import pkg from './package.json';
export default defineConfig({
server: {
host: true,
port: 31337,
open: `http://${pkg.name}.localhost:31337`
},
plugins: [sveltekit(), tailwindcss()]
});