fix: Logo deutlich groesser, Video im eigenen Seitenverhaeltnis

- Logo ueber dem Headerbild bis 112 px hoch, nach dem Scrollen 64 px,
  im Fussbereich 80 px; Kopfzeile waechst mit
- Videos aus dem Medienverzeichnis direkt mit Steuerleiste, quer oder
  hoch, erstes Bild als Vorschau; ein einzelnes Video breit und mittig
- Abschnitt heisst "Stimmen": Models erzaehlen, wie sie ARTISTA erleben

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-02 06:30:08 +02:00
1 parent 84c8f2f32b
commit a37f12a60f
7 files changed
+50 -44

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@
@layer base { @layer base {
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
scroll-padding-top: 5rem; scroll-padding-top: 5.5rem;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
scrollbar-color: var(--color-stone) var(--color-plaster); scrollbar-color: var(--color-stone) var(--color-plaster);
accent-color: var(--color-rosso); accent-color: var(--color-rosso);
+1 -1
View File
@@ -53,7 +53,7 @@ export function Hero({ pricing, media, video }: { pricing: PublicPricing; media:
<div aria-hidden className="absolute inset-0 bg-[linear-gradient(180deg,rgb(35_33_31/0.6)_0%,rgb(35_33_31/0.45)_45%,rgb(35_33_31/0.75)_100%)]" /> <div aria-hidden className="absolute inset-0 bg-[linear-gradient(180deg,rgb(35_33_31/0.6)_0%,rgb(35_33_31/0.45)_45%,rgb(35_33_31/0.75)_100%)]" />
</div> </div>
<div className="container-edge flex flex-col items-center pb-20 pt-32 text-center"> <div className="container-edge flex flex-col items-center pb-20 pt-36 text-center md:pt-44">
<h1 id="hero-title" className="hero-fade max-w-4xl text-[clamp(2.75rem,8vw,6.25rem)] font-extrabold leading-[0.98] tracking-[-0.03em]"> <h1 id="hero-title" className="hero-fade max-w-4xl text-[clamp(2.75rem,8vw,6.25rem)] font-extrabold leading-[0.98] tracking-[-0.03em]">
<span className="sr-only">ARTISTA </span> <span className="sr-only">ARTISTA </span>
{hero.title} {hero.title}
+1 -1
View File
@@ -7,7 +7,7 @@ export function SiteFooter({ facts, showDrafts, logo }: { facts: PublicCampFacts
<footer id="site-footer" className="border-t border-line bg-plaster"> <footer id="site-footer" className="border-t border-line bg-plaster">
<div className="container-edge grid gap-10 py-14 md:grid-cols-12"> <div className="container-edge grid gap-10 py-14 md:grid-cols-12">
<div className="md:col-span-5"> <div className="md:col-span-5">
<p className="flex h-10 items-center text-2xl"> <p className="flex h-20 items-center text-2xl">
<Logo src={logo} /> <Logo src={logo} />
</p> </p>
<p className="mt-1 text-stone">Modelcamp 2027</p> <p className="mt-1 text-stone">Modelcamp 2027</p>
+9 -2
View File
@@ -82,7 +82,9 @@ export function SiteHeader({ logoLight, logoDark }: { logoLight: string | null;
Zum Inhalt springen Zum Inhalt springen
</a> </a>
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<div className="container-edge grid h-[4.5rem] grid-cols-[1fr_auto_1fr] items-center gap-4"> <div
className={`container-edge grid grid-cols-[1fr_auto_1fr] items-center gap-4 transition-[height] duration-300 ${solid ? "h-20" : "h-24 md:h-32"}`}
>
<div className="flex items-center"> <div className="flex items-center">
<button <button
ref={menuButton} ref={menuButton}
@@ -99,7 +101,12 @@ export function SiteHeader({ logoLight, logoDark }: { logoLight: string | null;
</nav> </nav>
</div> </div>
<a href="#top" aria-label="ARTISTA Modelcamp 2027, zum Seitenanfang" className="flex h-8 items-center text-xl md:h-10 md:text-2xl"> {/* Logo ueber dem Headerbild gross, nach dem Scrollen kompakter. */}
<a
href="#top"
aria-label="ARTISTA Modelcamp 2027, zum Seitenanfang"
className={`flex items-center text-xl transition-[height] duration-300 md:text-2xl ${solid ? "h-16" : "h-20 md:h-28"}`}
>
<Logo src={solid ? logoDark : logoLight} /> <Logo src={solid ? logoDark : logoLight} />
</a> </a>
+29 -33
View File
@@ -1,47 +1,43 @@
"use client";
import { useState } from "react";
import type { Media } from "@/content/site"; import type { Media } from "@/content/site";
import { MediaFrame } from "./MediaFrame"; import { MediaFrame } from "./MediaFrame";
type Slot = { name: string | null; caption: string | null; poster: Media | null; src: string | null }; type Slot = { name: string | null; caption: string | null; poster: Media | null; src: string | null };
/** /**
* Ein Reel im Hochformat (9:16) aus dem eigenen Medienverzeichnis. Das * Ein Video aus dem eigenen Medienverzeichnis, im eigenen Seitenverhaeltnis
* Video laedt erst nach Klick (preload="none") und startet nie von selbst * (quer oder hoch). Es startet nie von selbst; geladen werden vorab nur die
* mit Ton. Kein externer Anbieter, keine Datenuebertragung an Dritte. * Metadaten und das erste Bild als Vorschau (#t=0.1), falls kein
* Vorschaubild hinterlegt ist. Kein externer Anbieter.
*/ */
export function VideoSlot({ slot, index, showDrafts }: { slot: Slot; index: number; showDrafts: boolean }) { export function VideoSlot({ slot, index, showDrafts }: { slot: Slot; index: number; showDrafts: boolean }) {
const [playing, setPlaying] = useState(false); const label = slot.name ? `Video von ${slot.name}` : `Video ${index + 1}`;
const label = slot.name ? `Reel von ${slot.name} abspielen` : `Reel ${index + 1} abspielen`; const poster = slot.poster?.kind === "image" ? slot.poster.src : undefined;
const poster: Media = slot.poster ?? { kind: "placeholder", label: `Reel ${index + 1} folgt`, tone: "studio" };
return ( return (
<figure> <figure>
<div className="relative aspect-[9/16] overflow-hidden rounded-2xl bg-ink"> {slot.src ? (
{playing && slot.src ? ( <video
<video src={slot.src} controls autoPlay playsInline preload="none" className="h-full w-full object-cover"> src={poster ? slot.src : `${slot.src}#t=0.1`}
<track kind="captions" /> poster={poster}
</video> controls
) : ( playsInline
<> preload="metadata"
<MediaFrame media={poster} aspect="h-full w-full" sizes="(min-width: 640px) 33vw, 100vw" showLabel={showDrafts} /> aria-label={label}
{slot.src && ( className="mx-auto block max-h-[80vh] w-full rounded-2xl bg-ink object-contain"
<button >
type="button" <track kind="captions" />
onClick={() => setPlaying(true)} </video>
aria-label={label} ) : (
className="absolute inset-0 m-auto flex h-20 w-20 items-center justify-center rounded-full bg-paper/90 text-ink transition-transform hover:scale-105" <div className="relative aspect-[9/16] overflow-hidden rounded-2xl">
> <MediaFrame
<svg viewBox="0 0 24 24" className="ml-1 h-8 w-8" aria-hidden> media={slot.poster ?? { kind: "placeholder", label: `Video ${index + 1} folgt`, tone: "studio" }}
<path d="M7 4.5v15l12-7.5z" fill="currentColor" /> aspect="h-full w-full"
</svg> sizes="(min-width: 640px) 33vw, 100vw"
</button> showLabel={showDrafts}
)} />
</> </div>
)} )}
</div> {(slot.name || slot.caption || (showDrafts && !slot.src)) && (
{(slot.name || slot.caption || showDrafts) && (
<figcaption className="mt-4"> <figcaption className="mt-4">
{slot.name ? <span className="block font-semibold">{slot.name}</span> : showDrafts && <span className="draft-flag">Name und Freigabe folgen</span>} {slot.name ? <span className="block font-semibold">{slot.name}</span> : showDrafts && <span className="draft-flag">Name und Freigabe folgen</span>}
{slot.caption && <span className="mt-1 block opacity-80">{slot.caption}</span>} {slot.caption && <span className="mt-1 block opacity-80">{slot.caption}</span>}
+7 -4
View File
@@ -95,14 +95,17 @@ export function AboutSection({ showDrafts }: { showDrafts: boolean }) {
} }
/** /**
* Drei Reels im Hochformat. Leere Slots erscheinen nur in Entwicklung und * Bis zu drei Videos (Models ueber ARTISTA). Ein einzelnes Video steht
* Staging; oeffentlich nie ein erfundenes Testimonial als Ersatz. * breit in der Mitte, mehrere im Raster. Leere Slots erscheinen nur in
* Entwicklung und Staging; oeffentlich nie ein erfundenes Testimonial.
*/ */
export function ReelsSection({ showDrafts, reels }: { showDrafts: boolean; reels: Reel[] }) { export function ReelsSection({ showDrafts, reels }: { showDrafts: boolean; reels: Reel[] }) {
const { reels: content } = site; const { reels: content } = site;
const slots = content.slots.map((slot, index) => ({ ...slot, ...(reels[index] ?? { src: null, poster: null }) })); const slots = content.slots.map((slot, index) => ({ ...slot, ...(reels[index] ?? { src: null, poster: null }) }));
const visible = showDrafts ? slots : slots.filter((s) => s.src); const filled = slots.filter((s) => s.src);
const visible = showDrafts ? [...filled, ...slots.filter((s) => !s.src)] : filled;
if (visible.length === 0) return null; if (visible.length === 0) return null;
const single = visible.length === 1;
return ( return (
<section id="reels" aria-labelledby="reels-title" className="on-dark bg-ink py-24 text-paper md:py-32"> <section id="reels" aria-labelledby="reels-title" className="on-dark bg-ink py-24 text-paper md:py-32">
<div className="container-edge"> <div className="container-edge">
@@ -110,7 +113,7 @@ export function ReelsSection({ showDrafts, reels }: { showDrafts: boolean; reels
<h2 id="reels-title" className="section-title">{content.title}</h2> <h2 id="reels-title" className="section-title">{content.title}</h2>
<p className="max-w-md text-paper/75">{content.lead}</p> <p className="max-w-md text-paper/75">{content.lead}</p>
</div> </div>
<ul className="mx-auto mt-12 grid max-w-5xl gap-6 sm:grid-cols-3 md:gap-8"> <ul className={`mx-auto mt-12 grid gap-6 md:gap-8 ${single ? "max-w-4xl" : "max-w-5xl sm:grid-cols-3"}`}>
{visible.map((slot, index) => ( {visible.map((slot, index) => (
<li key={index}> <li key={index}>
<VideoSlot slot={slot} index={index} showDrafts={showDrafts} /> <VideoSlot slot={slot} index={index} showDrafts={showDrafts} />
+2 -2
View File
@@ -246,8 +246,8 @@ export const site = {
}, },
reels: { reels: {
title: "Reels", title: "Stimmen",
lead: "Eindrücke und Stimmen in drei kurzen Clips.", lead: "Models erzählen, wie sie ARTISTA erleben.",
// Genau drei Slots. Video und Vorschaubild kommen ueber das // Genau drei Slots. Video und Vorschaubild kommen ueber das
// Medienverzeichnis (reels/reel-1.mp4 und reels/reel-1.jpg usw.). // Medienverzeichnis (reels/reel-1.mp4 und reels/reel-1.jpg usw.).
// Name und Begleittext nur mit Freigabe der gezeigten Person. // Name und Begleittext nur mit Freigabe der gezeigten Person.