Asme
Dark cinematic hero over a looping film whose seam is hand-rolled with 500ms requestAnimationFrame fades: a liquid-glass nav and Login pill, an Instrument Serif headline, a glass email bar and three glass social buttons.
Nội dung prompt
# Asme — Liquid Glass Cinematic Hero
Build a single-page hero section with a full-screen looping background video, liquid glass UI elements, and a dark cinematic aesthetic.
Reproduce this exactly — same copy, same classes, same timings.
## Tech stack
- **Vite + React 18 + TypeScript**
- **Tailwind CSS 3** — default config, no extensions
- **lucide-react** for all icons
- No other UI libraries
## Background video
- Full-screen, muted, autoplaying, positioned `absolute` with `object-cover`, covering the whole viewport
- Source:
```
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260328_115001_bcdaa3b4-03de-47e7-ad63-ae3e392c32d4.mp4
```
- Shifted down by 17% (`translate-y-[17%]`) so the top portion of the video is cropped — the interesting content is in the lower portion of the frame
- The outer container is `min-h-screen bg-black overflow-hidden`
The clip shows a figure in a pale hoodie at a laptop among yellow lilies and stacks of books, wrapped in sweeping trails of glittering light, all on a pure black ground.
**Seamless loop with a custom JavaScript fade system (no CSS transitions):**
1. 500ms `requestAnimationFrame`-based fade-in on load and on each loop start
2. 500ms fade-out when **0.55 seconds** remain before the video ends
3. A `fadingOutRef` boolean prevents repeated `timeupdate` events from re-triggering the fade-out
4. On `ended`: opacity is set to 0, then after **100ms** the video resets to `currentTime = 0`, plays, and fades back in
5. Each new fade cancels any running animation frame, so fades never compete
6. Fades resume from the current opacity rather than snapping
## Font
Import **Instrument Serif** (regular and italic):
```css
@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap');
```
The heading uses `fontFamily: "'Instrument Serif', serif"` via inline style.
## Liquid glass (`.liquid-glass`)
```css
.liquid-glass {
background: rgba(255, 255, 255, 0.01);
background-blend-mode: luminosity;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: none;
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.1);
position: relative;
overflow: hidden;
}
.liquid-glass::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1.4px;
background: linear-gradient(180deg,
rgba(255,255,255,0.45) 0%, rgba(255,255,255,0.15) 20%,
rgba(255,255,255,0) 40%, rgba(255,255,255,0) 60%,
rgba(255,255,255,0.15) 80%, rgba(255,255,255,0.45) 100%);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
```
## Layout — one full-screen flex column
**Navigation** — `relative z-20 pl-6 pr-6 py-6`. Inner container `rounded-full px-6 py-3 flex items-center justify-between max-w-5xl mx-auto`.
- Left: a `Globe` icon (size 24) and **Asme** in white, `font-semibold text-lg`, `gap-2`. Beside it (`gap-8`), links **Features**, **Pricing**, **About** — hidden on mobile, shown from `md:` — `text-white/80 hover:text-white transition-colors text-sm font-medium`
- Right (`gap-4`): **Sign Up** as plain white text; **Login** as a `liquid-glass rounded-full px-6 py-2` button
**Hero** — `relative z-10 flex-1 flex flex-col items-center justify-center px-6 py-12 text-center -translate-y-[20%]`:
- Heading **Built for the curious** — `text-5xl md:text-6xl lg:text-7xl text-white mb-8 tracking-tight whitespace-nowrap`, Instrument Serif
- Beneath it a `max-w-xl w-full space-y-4` container:
- Email bar: `liquid-glass rounded-full pl-6 pr-2 py-2 flex items-center gap-3`, holding a transparent email input (placeholder **Enter your email**, `text-white placeholder:text-white/40 text-base`) and a white circular submit button (`bg-white rounded-full p-3 text-black`) with an `ArrowRight` icon (size 20)
- Subtitle: `text-white text-sm leading-relaxed px-4` — *Stay updated with the latest news and insights. Subscribe to our newsletter today and never miss out on exciting updates.*
- **Manifesto** button, centred: `liquid-glass rounded-full px-8 py-3 text-white text-sm font-medium hover:bg-white/5 transition-colors`
**Social footer** — `relative z-10 flex justify-center gap-4 pb-12`: three circular buttons, each `liquid-glass rounded-full p-4 text-white/80 hover:text-white hover:bg-white/5 transition-all`, with lucide-react `Instagram`, `Twitter` and `Globe` icons (size 20), each with an `aria-label`.
## Quality Bar
- **`Instagram` and `Twitter` no longer exist in lucide-react.** lucide removed every brand icon in **1.0.0 (2026-03-20)**. The last 0.x release, `0.577.0`, still ships both; every 1.x does not — checked directly against the installed `1.28.0`, where both names are `undefined`, while `Globe` and `ArrowRight` are fine. The spec pins no version, so a fresh `npm install lucide-react` today resolves to 1.x and the import fails. Either pin `lucide-react@0.577.0` or draw the two marks as inline SVG. And do not reach for `X` as the replacement — in lucide that is the close/dismiss glyph, not the X logo.
- **With 500ms fades, the fade-out is cut off by `ended` on roughly four loops in five.** The trigger can only fire on a `timeupdate` tick, measured in this Chrome at a median of **265ms** (max 275ms, across two separate runs). So the first tick with ≤ 0.55s remaining lands anywhere between 0.550s and ~0.285s before the end — and a 500ms fade can only finish if it starts at least 0.500s out. That window is 50ms of a 265ms tick: the fade completes in **~19% of loops**. In the rest, `ended` arrives mid-fade and hard-sets opacity to 0 — a visible snap from as high as **0.43–0.45 opacity**, with the worst case only 55% of the way down. That is exactly the snapping the spec's last rule says fades must avoid. The same algorithm with a 250ms fade completes 100% of the time; the 0.55s lead was carried over from a shorter fade and never re-derived. For a 500ms fade the lead has to be **0.775s** (the fade plus one tick) — or drive the fade-out from the rAF loop, comparing `currentTime` every frame instead of waiting for `timeupdate`.
- **`translate-y-[17%]` crops the bottom of the video, not the top.** Moving the element down pushes its lower edge past the viewport and exposes the container behind its upper edge. Measured at 1440×900: the video box starts at **y = 153**, the frame is cropped **17.0% off the bottom and 0.0% off the top**, and the top 153px of the viewport is the container's black. It only looks seamless because this particular clip is pure black across its top third. The stated goal — cut the top, keep the lower content — needs the opposite: `object-position: bottom`, or a negative translate.
- **That shift parks the subject directly behind the social buttons.** The pale hoodie and laptop land exactly where the footer sits, and the buttons are near-transparent glass with `white/80` icons. Measured over all 241 frames: **93.76% of the icon pixels fall below the 3:1 non-text threshold, in every single frame** (worst 1.09:1). They are effectively invisible. By contrast, everything placed in the black band is excellent — the headline measures **20.43:1** at its worst and the nav links **13.08:1**.
- **`placeholder:text-white/40` cannot pass on any background.** White at 40% over pure black composites to `rgb(102,102,102)`, which is **3.66:1** — and pure black is the best case it will ever get. Measured on the actual footage: worst **3.34:1**, with **100% of placeholder pixels below 4.5:1 in all 241 frames**. Placeholder text is still text; use at least `white/60`.
- **The light trails sweep behind the body copy.** The 14px subtitle measures a worst case of **1.03:1** (failing in 69 of 241 frames) and the **Manifesto** label **1.20:1** (110 of 241). They are fine most of the time and unreadable for seconds at a time — a small scrim behind that block would hold them.
- **The navbar is not glass, and its `rounded-full` draws nothing.** Its inner container has no background, no backdrop filter, no border and no shadow — measured, every one of those is empty — so the rounding has nothing to round. For a design described as "liquid glass UI elements", the largest element on screen is plain text on the video. Add `liquid-glass` to it, or drop the pill.
- **Two lines of the liquid-glass CSS do nothing, and one missing line breaks it outside WebKit.** `background-blend-mode: luminosity` blends a box's own background layers together, and there is only one, so it is inert. The `::before` declares `-webkit-mask` and then finishes with the standard `mask-composite: exclude` — but never declares a standard `mask`, so engines that implement only the unprefixed pair have no mask to composite and paint the 1.4px gradient edge as a solid slab over the whole pill. Declare `mask` alongside `-webkit-mask`. (`rgba(255,255,255,0.01)` is also effectively invisible: the "glass" is entirely the 4px blur and that gradient edge.)
- **The italic is downloaded and never drawn.** The `@import` pulls Instrument Serif in both styles, but the only element that uses the face is the heading, which is upright. Google's two latin slices are 15,040 + 15,684 = **30,724 bytes**; the regular alone, subset to the heading's 13 distinct characters, is **2,136 bytes (7.0%)**. A CSS `@import` also can't start fetching until its parent stylesheet is parsed, serialising the font behind the CSS on a page whose headline is the first thing anyone reads.
- **20 MB for ten seconds of background.** The source is 1924×1076 H.264 at **~16 Mbps** — 20,064,148 bytes for a 10.04s loop, all of it on the critical path because the whole page is this video. The same clip at 1280×716 is **1,697,624 bytes (8.5%)**. There is no `poster`, and the element deliberately starts at opacity 0, so first paint is solid black until the clip decodes and the 500ms fade-in runs.
- **`playsInline` is missing.** The spec says muted and autoplaying but not inline; iOS Safari will not autoplay a video inline without `playsinline`, so on iPhones the background never starts — and since the fade-in only runs after a successful play, it stays at opacity 0.
- **The submit button has no accessible name.** The spec gives each social icon an `aria-label` but not the icon-only `ArrowRight` button, which is the one control that actually does something. The email input has no label either (a placeholder is not one), and there is no `<form>` wrapping the two.
- **`fadingOutRef` is only reset inside the restart timer.** Anything that skips `ended` — a seek, a decode error, a source swap — leaves it stuck `true`, and no fade-out ever fires again for the life of the component.
- **Every control has a hover state and none has a focus state.** Twelve focusable controls sit over moving footage with no `:focus-visible` rule anywhere in the spec.
- **There is no `prefers-reduced-motion` branch.** A full-screen video looping forever with a hand-rolled cross-fade. Hold one frame — at opacity 1, not the 0 the element starts from.
Có thể bạn cũng thích
Heritage Grove Footer
Full-viewport footer laid straight onto a hand-drawn ink landscape loop — five columns on the paper sky, and below 1100px the clip leaves the background and becomes a flow item under the copy.
Figurine Carousel Hero
Full-viewport figurine carousel where background colour, scale, blur and position all crossfade together on one 650ms curve.
Augment Reveal Hero
Cyberpunk hero where a cursor spotlight reveals the augmented version of the portrait, with stats drawn on fading circular arcs.
RIVR
DeFi dashboard hero as an inset rounded video card: a centred glass nav, a Sparkles badge over an 80px slate headline, a frosted stats card and a faux-cutout documentation corner built from two inverse-radius SVGs.