스펙 문서형 프롬프트로 만든 시네마틱 히어로 'Aethera'

Aethera — Cinematic Hero Section with Looping Video Background

pulkitxm2026-06-16frontendClaude Code

루핑 비디오 배경과 에디토리얼 타이포의 히어로 섹션을 기술 스펙 문서 형식(스택 버전 고정, 파일별 요구사항, 접근성 요건)으로 지시한 claude-directory 프로젝트. 프롬프트를 '요청문'이 아니라 '스펙 문서'로 쓰는 접근을 보여준다.

prompt · en
# Aethera — Cinematic Hero Section with Looping Video Background
 
## Overview
 
Build a fullscreen, single-page hero section for a fictional studio brand called "Aethera". The page pairs a monochrome editorial layout (white background, black ink, gray accents) with a looping background video that custom-fades in and out at each playthrough, an editorial serif headline with italicized emphasis, a glassmorphic navigation bar, and a staggered "fade-rise" entrance animation.
 
 
- **Framework:** React `^18.3.1` with `react-dom` `^18.3.1`
- **Build tool:** Vite `^6.3.5` with `@vitejs/plugin-react` `^4.4.1`
- **Language:** TypeScript `^5.8.3`
- **Styling:** Tailwind CSS `^4.1.7` via the `@tailwindcss/vite` `^4.1.7` plugin (CSS-first config using the `@theme` directive — no `tailwind.config.js`)
- **Fonts:** Instrument Serif (display) and Inter (body), loaded from Google Fonts
- **Testing/tooling:** Playwright `^1.60.0` (dev dependency)
- **Notable techniques:** custom `requestAnimationFrame`-driven video fade loop, CSS keyframe entrance choreography, , glassmorphic backdrop blur on the navbar
 
## Global Setup
 
### Entry HTML (`index.html`)
 
- `<html lang="en">`, `<meta charset="UTF-8" />`, `<meta name="viewport" content="width=device-width, initial-scale=1.0" />`.
- Meta description: `Aethera — Beyond silence, we build the eternal. Digital havens for deep work and pure flows.`
- Preconnect to Google Fonts: `https://fonts.googleapis.com` and `https://fonts.gstatic.com` (the latter with `crossorigin`).
- Inline SVG favicon (`data:image/svg+xml,...`): a `32×32` rounded rect (`rx='16'`) filled `#000000` with a centered white (`#ffffff`) letter "A" in `Georgia,serif` at `font-size='18'`.
- Page `<title>`: `Aethera® — Beyond silence, we build the eternal.`
- Body contains `<div id="root"></div>` and loads `<script type="module" src="/src/main.tsx"></script>`.
 
### React entry (`src/main.tsx`)
 
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";
 
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);
```
 
### Stylesheet imports (`src/index.css`)
 
Import order matters — fonts first, then Tailwind, then theme:
 
```css
@import "./styles/fonts.css";
@import "tailwindcss";
@import "./styles/theme.css";
```
 
## Fonts (`src/styles/fonts.css`)
 
- **Display text** (headings, logo): Instrument Serif (roman + italic, used for headline emphasis).
- **Body text** (navigation, descriptions, buttons): Inter.
- Both fonts are imported via a single Google Fonts `@import`:
 
```css
@import url("https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&display=swap");
```
 
## Color Palette & Theme Tokens (`src/styles/theme.css`)
 
Define design tokens inside Tailwind's `@theme` block so they are available as utilities (e.g. `bg-background`) and CSS variables:
 
```css
@theme {
--color-background: #ffffff;
--color-ink: #000000;
--color-muted: #6f6f6f;
 
--font-display: "Instrument Serif", "Georgia", "Times New Roman", serif;
--font-body:
"Inter", -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}
```
 
Color usage summary:
 
- **Background:** white (`#ffffff`)
- **Headlines / logo / buttons:** black (`#000000`)
- **Descriptions / inactive menu items / italic emphasis words:** gray (`#6f6f6f`)
- **Button text:** white (`#ffffff` / `#FFFFFF`)
 
### Base styles
 
Also in `theme.css`:
 
```css
::selection {
background: #000000;
color: #ffffff;
}
 
html {
scroll-behavior: smooth;
}
 
body {
margin: 0;
background-color: var(--color-background);
color: var(--color-ink);
font-family: var(--font-body);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
```
 
## Layout Structure (`src/App.tsx`)
 
A single root container holds three stacked layers:
 
```tsx
import VideoBackground from "./components/VideoBackground";
import Navbar from "./components/Navbar";
import Hero from "./components/Hero";
 
export default function App() {
return (
<div className="relative min-h-screen w-full overflow-hidden bg-background">
<VideoBackground />
<Navbar />
<Hero />
</div>
);
}
```
 
- **Container:** `relative min-h-screen w-full overflow-hidden bg-background`.
- **Background video layer** sits at `z-0`.
- **Gradient overlay** is rendered over the video.
- **Navigation bar** and **hero section** both sit at `z-10`.
- All elements should be responsive and maintain the glassmorphic aesthetic with the specified padding, positioning, and smooth animations.
 
## Video Background (`src/components/VideoBackground.tsx`)
 
### Source
 
- The video asset is served locally from `/assets/hf_20260328_083109_283f3553-e28f-428b-a723-d639c617eb2b.mp4` (a `VIDEO_SRC` constant). The original asset was sourced from `https://d8j0ntlcm91z4.cloudfront.net/USER_38XZZBOKVIGWJOTTWIXH07LWA1P/HF_20260328_083109_283F3553-E28F-428B-A723-D639C617EB2B.MP4` and has been vendored into the project's `public/assets/` directory (the local filename is lowercased). Note: the CloudFront URL appeared fully uppercased in the original brief; only its scheme+host casing has been normalized here (the case-sensitive path segment and `.MP4` extension are preserved as-is) — the local path is the ground-truth source.
 
### Positioning
 
- The `<video>` element uses `position: absolute` with inline style `inset: "auto 0 0 0"` and `top: "300px"`, plus `opacity: 0` (driven by JS) and `willChange: "opacity"`.
- Classes on the video: `absolute w-full object-cover`.
- Wrapper: `<div aria-hidden="true" className="absolute inset-0 z-0">`.
- Video attributes: `muted`, `playsInline`, `autoPlay`, `preload="auto"`.
 
### Custom fade-in / fade-out loop logic
 
Implemented with React `useEffect` and `useRef`:
 
- A `requestAnimationFrame` loop continuously monitors `currentTime` and `duration`.
- **Fade in** over `0.5s` at the start (opacity `0 → 1`).
- **Fade out** over `0.5s` before the end (opacity `1 → 0`).
- On the `ended` event: set opacity to `0`, wait `100ms`, reset `currentTime = 0`, then call `play()` again.
- This creates a seamless manual loop with smooth fade transitions.
- Constants: `FADE_SECONDS = 0.5` (fade window applied at both ends), `RESTART_DELAY_MS = 100` (pause between loops before restart).
 
Reference implementation:
 
```tsx
import { useEffect, useRef } from "react";
 
const VIDEO_SRC =
"/assets/hf_20260328_083109_283f3553-e28f-428b-a723-d639c617eb2b.mp4";
 
/** Fade window, in seconds, applied at both ends of each playthrough. */
const FADE_SECONDS = 0.5;
 
/** Pause between loops before the video restarts, in milliseconds. */
const RESTART_DELAY_MS = 100;
 
export default function VideoBackground() {
const videoRef = useRef<HTMLVideoElement>(null);
 
useEffect(() => {
const video = videoRef.current;
if (!video) return;
 
let frameId = 0;
let restartTimer: number | undefined;
 
const updateOpacity = () => {
const { currentTime, duration } = video;
 
if (Number.isFinite(duration) && duration > 0) {
const fadeIn = currentTime / FADE_SECONDS;
const fadeOut = (duration - currentTime) / FADE_SECONDS;
const opacity = Math.min(Math.max(Math.min(fadeIn, fadeOut), 0), 1);
video.style.opacity = opacity.toFixed(3);
}
 
frameId = requestAnimationFrame(updateOpacity);
};
 
const handleEnded = () => {
video.style.opacity = "0";
restartTimer = window.setTimeout(() => {
video.currentTime = 0;
video.play().catch(() => {
/* autoplay interrupted — the rAF loop keeps the layer hidden */
});
}, RESTART_DELAY_MS);
};
 
video.addEventListener("ended", handleEnded);
frameId = requestAnimationFrame(updateOpacity);
 
video.play().catch(() => {
/* Autoplay can be deferred by the browser; muted playback retries
automatically once the media is allowed to start. */
});
 
return () => {
cancelAnimationFrame(frameId);
window.clearTimeout(restartTimer);
video.removeEventListener("ended", handleEnded);
};
}, []);
 
return (
<div aria-hidden="true" className="absolute inset-0 z-0">
<video
ref={videoRef}
src={VIDEO_SRC}
muted
playsInline
autoPlay
preload="auto"
className="absolute w-full object-cover"
style={{
inset: "auto 0 0 0",
top: "300px",
opacity: 0,
willChange: "opacity",
}}
/>
{/* Gradient veil: dissolves the video into the page at both edges. */}
<div className="absolute inset-0 bg-gradient-to-b from-background via-transparent to-background" />
</div>
);
}
```
 
### Gradient overlay
 
A sibling `<div>` over the video: `absolute inset-0 bg-gradient-to-b from-background via-transparent to-background` — dissolves the video into the page at the top and bottom edges.
 
## Navigation Bar (`src/components/Navbar.tsx`)
 
- **Header wrapper:** `<header className="relative z-10 bg-white/70 backdrop-blur-md">` — the glassmorphic blur effect.
- **Nav element:** `aria-label="Primary"`, classes `mx-auto flex max-w-7xl items-center justify-between px-8 py-6`.
- **Logo:** `Aethera®` where the registered-trademark symbol (`&reg;`) is rendered as a superscript via `<sup className="text-[0.45em]">`. Logo `<a href="#">` styling: `font-display text-3xl tracking-tight text-[#000000]`.
- **Menu items** (rendered from a `MENU_ITEMS` array, hidden on small screens via `hidden items-center gap-8 md:flex`):
- `Home` — `href="#"`, active, color `text-[#000000]`
- `Studio` — `href="#studio"`
- `About` — `href="#about"`
- `Journal` — `href="#journal"`
- `Reach Us` — `href="#reach-us"`
- Inactive items: `text-[#6F6F6F] hover:text-[#000000]`. All items: `text-sm transition-colors`. The active item also sets `aria-current="page"`.
- **CTA button:** label `Begin Journey`, classes `rounded-full bg-[#000000] px-6 py-2.5 text-sm text-white transition-transform duration-300 ease-out hover:scale-103`.
 
```tsx
const MENU_ITEMS = [
{ label: "Home", href: "#", active: true },
{ label: "Studio", href: "#studio", active: false },
{ label: "About", href: "#about", active: false },
{ label: "Journal", href: "#journal", active: false },
{ label: "Reach Us", href: "#reach-us", active: false },
];
 
export default function Navbar() {
return (
<header className="relative z-10 bg-white/70 backdrop-blur-md">
<nav
aria-label="Primary"
className="mx-auto flex max-w-7xl items-center justify-between px-8 py-6"
>
<a
href="#"
className="font-display text-3xl tracking-tight text-[#000000]"
>
Aethera<sup className="text-[0.45em]">&reg;</sup>
</a>
 
<ul className="hidden items-center gap-8 md:flex">
{MENU_ITEMS.map(({ label, href, active }) => (
<li key={label}>
<a
href={href}
aria-current={active ? "page" : undefined}
className={`text-sm transition-colors ${
active
? "text-[#000000]"
: "text-[#6F6F6F] hover:text-[#000000]"
}`}
>
{label}
</a>
</li>
))}
</ul>
 
<button
type="button"
className="rounded-full bg-[#000000] px-6 py-2.5 text-sm text-white transition-transform duration-300 ease-out hover:scale-103"
>
Begin Journey
</button>
</nav>
</header>
);
}
```
 
## Hero Section (`src/components/Hero.tsx`)
 
### Positioning & layout
 
- **Section:** `relative z-10 flex flex-col items-center justify-center px-6 pb-40 text-center` with inline `style={{ paddingTop: "calc(8rem - 75px)" }}`.
 
### Headline
 
- **Text:** "Beyond *silence,* we build *the eternal.*" — the words `silence,` and `the eternal.` are wrapped in `<em className="italic text-[#6F6F6F]">` for italic emphasis; the rest is `#000000`.
- **Classes:** `animate-fade-rise max-w-7xl font-display text-5xl font-normal text-[#000000] sm:text-7xl md:text-8xl`.
- **Inline style:** `lineHeight: 0.95`, `letterSpacing: "-2.46px"`.
- **Font:** Instrument Serif (`font-display`).
- **Animation:** `animate-fade-rise`.
 
### Description
 
- **Text:** `Building platforms for brilliant minds, fearless makers, and thoughtful souls. Through the noise, we craft digital havens for deep work and pure flows.`
- **Classes:** `animate-fade-rise-delay mt-8 max-w-2xl text-base leading-relaxed text-[#6F6F6F] sm:text-lg`.
- **Color:** `#6F6F6F`.
- **Animation:** `animate-fade-rise-delay`.
 
### Hero CTA button
 
- **Text:** `Begin Journey`.
- **Classes:** `animate-fade-rise-delay-2 mt-12 rounded-full bg-[#000000] px-14 py-5 text-base text-[#FFFFFF] transition-transform duration-300 ease-out hover:scale-103`.
- **Colors:** black background (`#000000`), white text (`#FFFFFF`).
- **Hover:** scale `1.03` (`hover:scale-103`).
- **Animation:** `animate-fade-rise-delay-2`.
 
```tsx
export default function Hero() {
return (
<section
className="relative z-10 flex flex-col items-center justify-center px-6 pb-40 text-center"
style={{ paddingTop: "calc(8rem - 75px)" }}
>
<h1
className="animate-fade-rise max-w-7xl font-display text-5xl font-normal text-[#000000] sm:text-7xl md:text-8xl"
style={{ lineHeight: 0.95, letterSpacing: "-2.46px" }}
>
Beyond <em className="italic text-[#6F6F6F]">silence,</em> we build{" "}
<em className="italic text-[#6F6F6F]">the eternal.</em>
</h1>
 
<p className="animate-fade-rise-delay mt-8 max-w-2xl text-base leading-relaxed text-[#6F6F6F] sm:text-lg">
Building platforms for brilliant minds, fearless makers, and thoughtful
souls. Through the noise, we craft digital havens for deep work and
pure flows.
</p>
 
<button
type="button"
className="animate-fade-rise-delay-2 mt-12 rounded-full bg-[#000000] px-14 py-5 text-base text-[#FFFFFF] transition-transform duration-300 ease-out hover:scale-103"
>
Begin Journey
</button>
</section>
);
}
```
 
## Animations (`src/styles/theme.css`)
 
The entrance choreography uses a single `fade-rise` keyframe with three staggered helper classes:
 
- **`fade-rise`:** opacity `0 → 1`, `translateY(20px) → translateY(0)`, duration `0.8s`, `ease-out`.
- **`.animate-fade-rise`:** `fade-rise 0.8s ease-out both`.
- **`.animate-fade-rise-delay`:** same as fade-rise but with a `0.2s` delay.
- **`.animate-fade-rise-delay-2`:** same as fade-rise but with a `0.4s` delay.
- **Reduced motion:** under `@media (prefers-reduced-motion: reduce)`, all three classes settle instantly (`animation-duration: 0.01ms; animation-delay: 0ms;`) — no rise.
 
```css
/* ---- Entrance choreography ------------------------------------- */
 
@keyframes fade-rise {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
 
.animate-fade-rise {
animation: fade-rise 0.8s ease-out both;
}
 
.animate-fade-rise-delay {
animation: fade-rise 0.8s ease-out 0.2s both;
}
 
.animate-fade-rise-delay-2 {
animation: fade-rise 0.8s ease-out 0.4s both;
}
 
/* Respect reduced-motion preferences: settle instantly, no rise. */
@media (prefers-reduced-motion: reduce) {
.animate-fade-rise,
.animate-fade-rise-delay,
.animate-fade-rise-delay-2 {
animation-duration: 0.01ms;
animation-delay: 0ms;
}
}
```
 
## File Structure
 
```
aethera-cinematic-hero/
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── vite.config.ts
├── public/
│ └── assets/
│ └── hf_20260328_083109_283f3553-e28f-428b-a723-d639c617eb2b.mp4
└── src/
├── main.tsx
├── App.tsx
├── index.css
├── vite-env.d.ts
├── components/
│ ├── VideoBackground.tsx
│ ├── Navbar.tsx
│ └── Hero.tsx
└── styles/
├── fonts.css
└── theme.css
```
 
## Scripts (`package.json`)
 
- `dev`: `vite`
- `build`: `tsc -b && vite build`
- `preview`: `vite preview`
 

이 프롬프트가 잘한 것

Overview→Tech Stack→Global Setup 순의 섹션 구조.

  • Overview→Tech Stack→Global Setup 순의 섹션 구조 — 프롬프트 자체를 스펙 문서처럼 조직해 모델이 참조하기 쉽게 했다.

    구조화 가이드
  • 접근성 요건을 기능 목록에 명시 — 빠뜨리기 쉬운 비기능 요구를 처음부터 스코프에 넣었다.

    지시 명확성 가이드

사용 세팅

modelclaude-fable-5
notes저장소 README에 '모든 프로젝트는 Claude Fable 5로 생성'이라 명시. 저장소에 .claude/agents 구성 존재
Sidi Bou Said — A Procedural 3D Walk Above the Gulf of Tunis 데모 포스터 이미지GitHub
frontendClaude Codeprompt

프롬프트 한 장으로 만든 튀니지 '시디 부 사이드' 3D 산책 씬

pulkitxm · 2026-06-16

# Sidi Bou Said — A Procedural 3D Walk Above the Gulf of Tunis
GitHub
frontendClaude Code

Fable 5로 생성한 웹 UI 실험 189개 오픈소스 갤러리

pulkitxm · 2026-06-10

Amber Foundry — Warm-Intelligence Operations Landing Page 데모 포스터 이미지GitHub
frontendClaude Codeprompt

'네온 온 블랙의 정반대' — 반례로 톤을 지시한 랜딩페이지

pulkitxm · 2026-06-20

# AMBER FOUNDRY — WARM-INTELLIGENCE OPERATIONS LANDING PAGE