Neues Hero-Bild: Amin bei der Arbeit
- Hero zeigt Amin mit Schere und Kamm am Kunden (Desktop 2000/1000 px, eigener Hochformat-Ausschnitt fürs Handy per <picture>) - Headline und Button auf Desktop unten links, damit Gesichter, Schere und Kamm frei bleiben; auf dem Handy weiterhin zentriert - Oben stärker abgedunkelt: Navigation (mix-blend-difference) auf dem hellen Studiohintergrund jetzt mit ca. 6:1 statt 2:1 Kontrast - Menü: "Preise" zeigt ein Detail (Hände, Schere, Kamm), "Team" ein 3 × 2 Raster aller Porträts - Nicht mehr genutzte Fotos entfernt, Vorschaubild (og.jpg) und README aktualisiert Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBHsCnWNXAB2Le3gAEsivF
No files matched your search
@@ -25,15 +25,19 @@ npm run preview # Build lokal ansehen
|
|||||||
| Was | Wo |
|
| Was | Wo |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Preise, Dauer, Leistungen, Öffnungszeiten, Team, Kontakt | `src/data/salon.ts` |
|
| Preise, Dauer, Leistungen, Öffnungszeiten, Team, Kontakt | `src/data/salon.ts` |
|
||||||
| Fotos (Hero, Über uns, Menü, Fotoraster) | `src/assets/photos/*.webp` |
|
| Fotos (Hero, Über uns, Menü) | `src/assets/photos/*.webp` |
|
||||||
| Team-Porträts | `src/assets/team/*.webp` |
|
| Team-Porträts | `src/assets/team/*.webp` |
|
||||||
| Schriftzug oben in der Mitte | `src/components/Nav.tsx` |
|
| Schriftzug oben in der Mitte | `src/components/Nav.tsx` |
|
||||||
| Favicon, App-Icon, Vorschaubild zum Teilen | `public/` |
|
| Favicon, App-Icon, Vorschaubild zum Teilen | `public/` |
|
||||||
| Impressum & Datenschutz | `impressum.html`, `datenschutz.html` |
|
| Impressum & Datenschutz | `impressum.html`, `datenschutz.html` |
|
||||||
|
|
||||||
**Fotos:** Hero, „Über uns“ und Menü nutzen die Fotos von der Logo-Wand im Salon (Setmore-Seite). Für den
|
**Hero:** Amin bei der Arbeit (per Bild-KI erstellt), abgelegt als `hero.webp` (2000 px), `hero-1000.webp`
|
||||||
Hero lohnt sich ein eigenes, hochauflösendes Foto (mind. 2400 px breit), z. B. ein Barber bei der Arbeit.
|
und `hero-mobile.webp` (Hochformat-Ausschnitt fürs Handy). Die Headline steht unten links, damit Gesichter,
|
||||||
Einfach `salon-wand.webp` bzw. `salon-wand-800.webp` ersetzen.
|
Schere und Kamm frei bleiben. Beim Austauschen alle drei Dateien ersetzen, den Handy-Ausschnitt so wählen,
|
||||||
|
dass Gesicht und Hände darin liegen. `hero-detail.webp` (Hände mit Schere und Kamm) erscheint im Menü.
|
||||||
|
|
||||||
|
**Salon-Fotos:** „Über uns“ und der Menüpunkt „Kontakt“ zeigen das Logo an der Wand im Salon
|
||||||
|
(`emblem.webp`, `emblem-breit-900.webp`, aus den Fotos der Setmore-Seite).
|
||||||
|
|
||||||
**Team-Porträts:** Einheitliche Studio-Porträts (per Bild-KI aus den Originalfotos erstellt), je zweimal
|
**Team-Porträts:** Einheitliche Studio-Porträts (per Bild-KI aus den Originalfotos erstellt), je zweimal
|
||||||
abgelegt: `name.webp` (1000 px breit, Team-Liste) und `name-thumb.webp` (320 × 320, Fotoraster und
|
abgelegt: `name.webp` (1000 px breit, Team-Liste) und `name-thumb.webp` (320 × 320, Fotoraster und
|
||||||
@@ -48,7 +52,8 @@ Setmore-Seite hinter `products=`.
|
|||||||
- [ ] Platzhalter in `impressum.html` und `datenschutz.html` ausfüllen (`[…]`) und rechtlich prüfen lassen
|
- [ ] Platzhalter in `impressum.html` und `datenschutz.html` ausfüllen (`[…]`) und rechtlich prüfen lassen
|
||||||
- [ ] Domain in `.env` bei `VITE_SITE_URL` eintragen (für das Vorschaubild bei WhatsApp/Facebook)
|
- [ ] Domain in `.env` bei `VITE_SITE_URL` eintragen (für das Vorschaubild bei WhatsApp/Facebook)
|
||||||
- [ ] Rollen im Team prüfen (`src/data/salon.ts`)
|
- [ ] Rollen im Team prüfen (`src/data/salon.ts`)
|
||||||
- [ ] Einverständnis des Teams für die Verwendung der Fotos einholen
|
- [ ] Einverständnis des Teams für die Verwendung der Fotos einholen (Porträts und Hero sind KI-Bilder
|
||||||
|
nach echten Fotos – vor der Veröffentlichung freigeben lassen)
|
||||||
|
|
||||||
Der Live-Status „Jetzt geöffnet“ rechnet mit den Öffnungszeiten aus `src/data/salon.ts` und
|
Der Live-Status „Jetzt geöffnet“ rechnet mit den Öffnungszeiten aus `src/data/salon.ts` und
|
||||||
kennt keine Feiertage.
|
kennt keine Feiertage.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 229 KiB |
@@ -13,36 +13,43 @@ export function Hero() {
|
|||||||
return (
|
return (
|
||||||
<section id="top" ref={ref} className="relative isolate h-svh min-h-[560px] overflow-hidden bg-ink text-paper">
|
<section id="top" ref={ref} className="relative isolate h-svh min-h-[560px] overflow-hidden bg-ink text-paper">
|
||||||
<motion.div style={{ y }} className="absolute inset-0 -z-20">
|
<motion.div style={{ y }} className="absolute inset-0 -z-20">
|
||||||
<motion.img
|
{/* Handy: eigener Hochformat-Ausschnitt mit Amin und der Schere */}
|
||||||
src={image('photos/salon-wand')}
|
<picture>
|
||||||
srcSet={`${image('photos/salon-wand-800')} 800w, ${image('photos/salon-wand')} 1317w`}
|
<source media="(max-width: 767px)" srcSet={image('photos/hero-mobile')} />
|
||||||
sizes="100vw"
|
<motion.img
|
||||||
alt={`Barber vor dem Logo an der Wand im Salon ${salon.name}`}
|
src={image('photos/hero')}
|
||||||
fetchPriority="high"
|
srcSet={`${image('photos/hero-1000')} 1000w, ${image('photos/hero')} 2000w`}
|
||||||
initial={{ scale: 1.15 }}
|
sizes="100vw"
|
||||||
animate={{ scale: 1 }}
|
alt={`${salon.name} schneidet einem Kunden mit Schere und Kamm die Haare`}
|
||||||
transition={{ duration: 2.2, ease: EASE_OUT_EXPO }}
|
fetchPriority="high"
|
||||||
className="size-full object-cover object-[50%_60%] md:object-[50%_100%]"
|
initial={{ scale: 1.15 }}
|
||||||
/>
|
animate={{ scale: 1 }}
|
||||||
|
transition={{ duration: 2.2, ease: EASE_OUT_EXPO }}
|
||||||
|
className="size-full object-cover md:object-[50%_25%]"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<div aria-hidden="true" className="absolute inset-0 -z-10 bg-ink/35" />
|
<div aria-hidden="true" className="absolute inset-0 -z-10 bg-ink/30" />
|
||||||
<div aria-hidden="true" className="absolute inset-x-0 top-0 -z-10 h-40 bg-linear-to-b from-ink/60 to-transparent" />
|
{/* oben dunkler, damit die Navigation auf dem hellen Studiohintergrund lesbar bleibt */}
|
||||||
<div aria-hidden="true" className="absolute inset-x-0 bottom-0 -z-10 h-2/3 bg-linear-to-t from-ink/70 to-transparent" />
|
<div aria-hidden="true" className="absolute inset-x-0 top-0 -z-10 h-44 bg-linear-to-b from-ink/80 to-transparent" />
|
||||||
|
<div aria-hidden="true" className="absolute inset-x-0 bottom-0 -z-10 h-3/5 bg-linear-to-t from-ink/75 to-transparent" />
|
||||||
|
<div aria-hidden="true" className="absolute inset-y-0 left-0 -z-10 hidden w-3/5 bg-linear-to-r from-ink/35 to-transparent md:block" />
|
||||||
|
|
||||||
<div className="flex h-full flex-col items-center justify-end px-4 pb-10 text-center md:pb-14">
|
{/* Text unten links: Gesichter, Schere und Kamm bleiben frei */}
|
||||||
|
<div className="flex h-full flex-col items-center justify-end px-4 pb-10 text-center md:items-start md:px-8 md:pb-12 md:text-left">
|
||||||
<RevealLines
|
<RevealLines
|
||||||
as="h1"
|
as="h1"
|
||||||
onLoad
|
onLoad
|
||||||
delay={0.3}
|
delay={0.3}
|
||||||
srLabel={`${salon.name} – Barber & Friseur in ${salon.city}. Dein Look, unser Handwerk.`}
|
srLabel={`${salon.name} – Barber & Friseur in ${salon.city}. Dein Look, unser Handwerk.`}
|
||||||
className="display text-[clamp(1.55rem,6.4vw,5.75rem)]"
|
className="display text-[clamp(1.55rem,4.2vw,4.5rem)]"
|
||||||
lines={['Dein Look.', 'Unser Handwerk.']}
|
lines={['Dein Look.', 'Unser Handwerk.']}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 12 }}
|
initial={{ opacity: 0, y: 12 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1, delay: 0.9, ease: EASE_OUT_EXPO }}
|
transition={{ duration: 1, delay: 0.9, ease: EASE_OUT_EXPO }}
|
||||||
className="mt-6 md:mt-8"
|
className="mt-6 md:mt-7"
|
||||||
>
|
>
|
||||||
<Button tone="light" />
|
<Button tone="light" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { AnimatePresence, motion } from 'motion/react'
|
import { AnimatePresence, motion } from 'motion/react'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { BOOKING_URL, image, salon } from '../data/salon'
|
import { BOOKING_URL, image, salon, team } from '../data/salon'
|
||||||
import { cn } from '../lib/cn'
|
import { cn } from '../lib/cn'
|
||||||
import { EASE_OUT_EXPO } from '../lib/motion'
|
import { EASE_OUT_EXPO } from '../lib/motion'
|
||||||
import { scrollToHash, unlockScroll } from '../lib/scroll'
|
import { scrollToHash, unlockScroll } from '../lib/scroll'
|
||||||
import { GridLines } from './GridLines'
|
import { GridLines } from './GridLines'
|
||||||
|
|
||||||
|
// Ein Bild oder – beim Team – ein 3 × 2 Raster aus allen Porträts
|
||||||
const items = [
|
const items = [
|
||||||
{ href: '#preise', label: 'Preise', photo: image('photos/emblem-quadrat') },
|
{ href: '#preise', label: 'Preise', photos: [image('photos/hero-detail')] },
|
||||||
{ href: '#team', label: 'Team', photo: image('photos/mo-wand') },
|
{ href: '#team', label: 'Team', photos: team.map((m) => m.thumb) },
|
||||||
{ href: '#kontakt', label: 'Kontakt', photo: image('photos/emblem-breit-900') },
|
{ href: '#kontakt', label: 'Kontakt', photos: [image('photos/emblem-breit-900')] },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** Vollbild-Menü: große Links, daneben wechselt das Bild zum Link unter dem Mauszeiger. */
|
/** Vollbild-Menü: große Links, daneben wechselt das Bild zum Link unter dem Mauszeiger. */
|
||||||
@@ -72,15 +73,18 @@ export function Menu({ open, onClose }: { open: boolean; onClose: () => void })
|
|||||||
|
|
||||||
<div className="relative hidden md:block">
|
<div className="relative hidden md:block">
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<img
|
<div
|
||||||
key={item.href}
|
key={item.href}
|
||||||
src={item.photo}
|
|
||||||
alt=""
|
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute inset-0 size-full object-cover transition-all duration-700 ease-out-expo',
|
'absolute inset-0 grid gap-[2px] transition-all duration-700 ease-out-expo',
|
||||||
|
item.photos.length > 1 && 'grid-cols-3',
|
||||||
active === i ? 'scale-100 opacity-100' : 'scale-105 opacity-0',
|
active === i ? 'scale-100 opacity-100' : 'scale-105 opacity-0',
|
||||||
)}
|
)}
|
||||||
/>
|
>
|
||||||
|
{item.photos.map((src) => (
|
||||||
|
<img key={src} src={src} alt="" className="size-full min-h-0 object-cover" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||