One fileNo dependenciesReduced-motion safeMake the claim felt, not just read.
A photo anchors it. A few floating labels point at what matters, live, instead of waiting in a caption underneath.
Label Beacon
Everywhere else in this set, the field itself supplies the energy a badge would. Here a photograph does that work instead — a few labels float along its edges, reading like a live readout rather than decoration, the way a product shot points at what it does mid-frame instead of underneath it in a caption. The labels carry the same three facts `split-beacon`'s glass ledger proves, just staged differently.
Built to prove the anatomy holds outside pure abstraction too: swap the field, keep the photo and its labels, and the composition still reads.
Install
No installation needed — self-contained, paste-in code.
Usage
Drop it straight into a page.
import Image from "next/image";
import { GradientBackground } from "./GradientBackground";
const TAG =
"absolute inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-3 py-1.5 text-xs font-medium text-white backdrop-blur-md before:h-1.5 before:w-1.5 before:rounded-full before:bg-[#a78bfa]";
export default function Example() {
return (
<header className="relative isolate min-h-[40rem] overflow-hidden bg-black flex flex-col items-center justify-start pt-16 text-center [--portrait-w:min(24rem,60vw)]">
<div className="absolute inset-0 -z-10">
<GradientBackground />
</div>
<div className="absolute bottom-0 left-1/2 w-[var(--portrait-w)] -translate-x-1/2">
{/* Swap for your own portrait or product shot. */}
<Image
src="/your-photo.png"
alt=""
width={628}
height={713}
className="block h-auto w-full [mask-image:radial-gradient(65%_60%_at_50%_40%,#000_40%,transparent_85%)]"
/>
<span className={`${TAG} top-[4%] left-[2%]`}>One file</span>
<span className={`${TAG} top-[46%] right-[4%]`}>No dependencies</span>
<span className={`${TAG} bottom-[6%] left-[6%]`}>Reduced-motion safe</span>
</div>
<div className="mx-auto max-w-xl px-8 pb-[26rem] text-white">
<h1 className="text-5xl font-medium tracking-tight">
Make the claim{" "}
<span className="rounded-lg bg-[#7c3aed] px-[0.42em] py-[0.05em]">
felt
</span>
, not just{" "}
<span className="rounded-lg border border-white/20 bg-white/10 px-[0.42em] py-[0.05em] backdrop-blur-sm">
read
</span>
.
</h1>
<p className="mt-4 opacity-80">
A photo anchors it. A few floating labels point at what matters,
live, instead of waiting in a caption underneath.
</p>
<div className="mt-8">
<a href="#" className="rounded-xl bg-[#7c3aed] px-5 py-3 text-white">
See it work
</a>
</div>
</div>
</header>
);
}Component
The real source, exactly as it ships — multiple files, kept together.
import Link from 'next/link';
import Image from 'next/image';
import type { CategoryDef, CreationCard } from '@/lib/iris';
import { GradientBackground } from '../visuals/backgrounds';
const TAG =
"absolute inline-flex items-center gap-[0.35rem] py-[0.4rem] px-3 rounded-full text-[length:0.75rem] font-medium text-[color:var(--field-ink)] bg-[var(--field-glass)] border border-[var(--field-glass-border)] [backdrop-filter:blur(10px)_saturate(1.2)] [-webkit-backdrop-filter:blur(10px)_saturate(1.2)] [text-shadow:0_1px_8px_oklch(0.1_0.02_240_/_0.5)] whitespace-nowrap before:content-[''] before:w-[0.4rem] before:h-[0.4rem] before:rounded-full before:bg-[color:var(--iris-accent-bright)] before:flex-none [@media(prefers-reduced-transparency:reduce)]:[backdrop-filter:none] [@media(prefers-reduced-transparency:reduce)]:[-webkit-backdrop-filter:none] [@media(prefers-reduced-transparency:reduce)]:bg-[var(--field-glass-opaque)]";
const WORD_BASE = "inline py-[0.05em] px-[0.42em] rounded-[0.5em] whitespace-nowrap";
/**
* The real `label-beacon` section, full scale. Everywhere else in this set
* the field itself supplies the energy a badge would; here a photograph
* does that work instead, and a few floating labels ride its edges rather
* than sitting underneath in a caption — the same three facts
* `split-beacon`'s glass ledger proves, just carried as live-looking tags
* next to a face instead of a panel of text.
*
* `GradientBackground`'s violet/blue/cyan/mint field is real, composed
* underneath rather than a stand-in — one calm field to read the photo
* against, not several bands competing with it — built to prove the
* anatomy holds outside pure abstraction too: swap the field, keep the
* photo and its labels.
*/
export function LabelBeacon({
category,
creation,
}: {
category: CategoryDef;
creation: CreationCard;
}) {
return (
<header className="iris-hero iris-hero--field iris-section-hero--label relative isolate [overflow:clip] mt-[calc(-3.5rem_-_1px)] min-h-[min(82vh,760px)] flex flex-col justify-start [padding-block:calc(clamp(3rem,8vh,6rem)+3.5rem)_clamp(3rem,8vh,6rem)] [--portrait-w:min(24rem,60vw)] [--portrait-h:calc(var(--portrait-w)*713/628)]">
<div className="absolute inset-0 z-[-1] overflow-hidden bg-[color:var(--iris-bg)]" aria-hidden="true">
<div className="absolute inset-0 motion-reduce:[animation:none] data-[enter=right]:[animation:iris-field-enter-right_560ms_cubic-bezier(0.16,1,0.3,1)_both] data-[enter=left]:[animation:iris-field-enter-left_560ms_cubic-bezier(0.16,1,0.3,1)_both]">
<GradientBackground />
</div>
</div>
{/* Pinned to the hero's own bottom edge, out of the text's flow,
so scaling it up never pushes the headline around — it just
grows toward the top from feet planted at the hero's floor.
Sits before `.iris-hero__inner` in the DOM so the text stacks
above it by default if the two ever overlap. */}
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[var(--portrait-w,min(24rem,60vw))] z-0">
<Image
src="/hero_char.png"
alt="Studio portrait used to demonstrate the layout"
width={628}
height={713}
sizes="(min-width: 48rem) 24rem, 60vw"
className="block w-full h-auto [mask-image:radial-gradient(65%_60%_at_50%_40%,#000_40%,transparent_85%)] [-webkit-mask-image:radial-gradient(65%_60%_at_50%_40%,#000_40%,transparent_85%)] [filter:drop-shadow(0_1.5rem_2.5rem_oklch(0.05_0.01_280_/_0.45))]"
priority
/>
<span className={`${TAG} top-[4%] left-[2%]`}>
One file
</span>
<span className={`${TAG} top-[46%] right-[4%]`}>
No dependencies
</span>
<span className={`${TAG} bottom-[6%] left-[6%]`}>
Reduced-motion safe
</span>
</div>
<div className="iris-shell flex flex-col items-center relative text-center pb-[calc(var(--portrait-h)+2.5rem)]">
<p className="flex flex-wrap items-center gap-[0.4rem] text-[length:0.8125rem] text-[var(--field-ink-dim)] [text-shadow:0_1px_10px_oklch(0.12_0.02_264_/_0.55)] [&_a]:text-inherit [&_a:hover]:text-[color:var(--field-ink)]">
<Link href="/iris">Iris</Link>
<span aria-hidden="true">/</span>
<Link href={`/iris/${category.slug}`}>
{category.label}
</Link>
<span aria-hidden="true">/</span>
<span>{creation.title}</span>
</p>
<h1 className="font-[family-name:var(--font-iris-display)] font-normal text-[length:clamp(2.5rem,1.5rem+4.4vw,5rem)] leading-[1.02] tracking-[-0.02em] max-w-[18ch] text-balance text-[color:var(--field-ink)] [text-shadow:0_1px_12px_oklch(0.12_0.02_264_/_0.55)]">
Make the claim{' '}
<span className={`${WORD_BASE} bg-[color:var(--iris-accent)] text-[color:var(--iris-accent-ink)]`}>
felt
</span>
, not just{' '}
<span className={`${WORD_BASE} bg-[var(--field-glass)] text-[color:var(--field-ink)] border border-[var(--field-glass-border)] [backdrop-filter:blur(8px)] [-webkit-backdrop-filter:blur(8px)] [@media(prefers-reduced-transparency:reduce)]:[backdrop-filter:none] [@media(prefers-reduced-transparency:reduce)]:[-webkit-backdrop-filter:none] [@media(prefers-reduced-transparency:reduce)]:bg-[var(--field-glass-opaque)]`}>
read
</span>
.
</h1>
<p className="mt-4 max-w-[38ch] text-black [text-shadow:none]">
A photo anchors it. A few floating labels point at what
matters, live, instead of waiting in a caption underneath.
</p>
<div className="mt-9 flex flex-wrap justify-center gap-3">
<Link href={`/iris/${category.slug}/${creation.slug}#detail`} className="inline-flex items-center justify-center gap-[0.4rem] py-[0.8rem] px-5 rounded-[var(--iris-radius-sm)] border border-transparent text-[length:0.9375rem] font-medium leading-none whitespace-nowrap cursor-pointer transition-[background-color,border-color,color] duration-[120ms] ease-[ease] disabled:opacity-35 disabled:cursor-default disabled:pointer-events-none bg-[color:var(--iris-accent)] text-[color:var(--iris-accent-ink)] hover:bg-[color:var(--iris-accent-hover)]">
See it work
</Link>
</div>
</div>
</header>
);
}