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:
1 parent
84c8f2f32b
commit
a37f12a60f
7 files changed
+50
-44
No files matched your search
+1
-1
@@ -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);
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>}
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
Reference in new issue
Block a user