类型:3D · 分类:Buttons · 收录:2026-09-16 20:35:01 · 源码:会员可见
付费可阅读内容 · 可复制
# Rectangle Buttons
Twenty-two authored rectangle-button and animated CTA treatments collected into one family.
> 来源:ThreeUI Community · https://threeui.com · MIT License(Meng To / Design+Code)
> 组件页:https://threeui.com/component/rectangle-buttons · 预览动画:https://threeui.com/previews/rectangle-buttons.webm
> 分类:Buttons
> 包含变体(22):dark-pill、launch-button、dot-border-button、floating-dots-cta、sliding-text-cta、gradient-beam-cta、gradient-pill-button、generate-button、glassmorphism-cta、spinning-border-button、gradient-cta、lumen-cta、lumen-cta-ghost、trochil-signal、attune-thermal、tideform-outline、understory-arrow-pill、meridian-keycap-primary、meridian-keycap-secondary、halvorsen-arrow-pill、aster-glass-access、aster-glass-arrow
/* ===== src/shaders/rectangle-buttons/RectangleButtons.tsx (tsx) ===== */
import { lazy, Suspense, type ComponentType, type LazyExoticComponent, type ReactNode } from "react";
import {
DarkGlassButton as DarkGlassRectangle,
type SectionCompositionProps,
} from "../section-elements/SectionElements";
import type { LumenCtaProps } from "../lumen-cta/LumenCta";
import type { NeuformIsolatedEffectProps } from "../neuform-isolated/NeuformIsolatedEffects";
export type RectangleButtonVariant =
| "dark-pill"
| "launch-button"
| "dot-border-button"
| "floating-dots-cta"
| "sliding-text-cta"
| "gradient-beam-cta"
| "gradient-pill-button"
| "generate-button"
| "glassmorphism-cta"
| "spinning-border-button"
| "gradient-cta"
| "lumen-cta"
| "lumen-cta-ghost"
| "trochil-signal"
| "attune-thermal"
| "tideform-outline"
| "understory-arrow-pill"
| "meridian-keycap-primary"
| "meridian-keycap-secondary"
| "halvorsen-arrow-pill"
| "aster-glass-access"
| "aster-glass-arrow";
type SelectedPageButtonVariant = Extract<
RectangleButtonVariant,
| "trochil-signal"
| "attune-thermal"
| "tideform-outline"
| "understory-arrow-pill"
| "meridian-keycap-primary"
| "meridian-keycap-secondary"
| "halvorsen-arrow-pill"
| "aster-glass-access"
| "aster-glass-arrow"
>;
type IsolatedRectangleVariant = Exclude<
RectangleButtonVariant,
"dark-pill" | "lumen-cta" | "lumen-cta-ghost" | SelectedPageButtonVariant
>;
export type RectangleButtonsProps = SectionCompositionProps &
NeuformIsolatedEffectProps &
Omit<LumenCtaProps, "variant"> & {
variant?: RectangleButtonVariant;
};
const LumenCta = lazy(() =>
import("../lumen-cta/LumenCta").then((module) => ({ default: module.LumenCta })),
);
const RECTANGLE_VARIANTS = {
"launch-button": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.LaunchButton })),
),
"dot-border-button": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.DotBorderButton })),
),
"floating-dots-cta": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.FloatingDotsCta })),
),
"sliding-text-cta": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.SlidingTextCta })),
),
"gradient-beam-cta": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.GradientBeamCta })),
),
"gradient-pill-button": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.GradientPillButton })),
),
"generate-button": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.GenerateButton })),
),
"glassmorphism-cta": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.GlassmorphismCta })),
),
"spinning-border-button": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.SpinningBorderButton })),
),
"gradient-cta": lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.GradientCta })),
),
} satisfies Record<IsolatedRectangleVariant, LazyExoticComponent<ComponentType<NeuformIsolatedEffectProps>>>;
const SELECTED_PAGE_BUTTON_VARIANTS = new Set<RectangleButtonVariant>([
"trochil-signal",
"attune-thermal",
"tideform-outline",
"understory-arrow-pill",
"meridian-keycap-primary",
"meridian-keycap-secondary",
"halvorsen-arrow-pill",
"aster-glass-access",
"aster-glass-arrow",
]);
function isSelectedPageButtonVariant(variant: RectangleButtonVariant): variant is SelectedPageButtonVariant {
return SELECTED_PAGE_BUTTON_VARIANTS.has(variant);
}
const SELECTED_PAGE_BUTTON_STYLES = `
.threeui-page-button-stage {
--threeui-page-ink: #fff;
position: relative;
display: grid;
width: 100%;
height: 100%;
min-height: 240px;
place-items: center;
overflow: hidden;
isolation: isolate;
background: #050505;
color: var(--threeui-page-ink);
font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.threeui-page-button-stage::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
}
.threeui-page-button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
font: inherit;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
-webkit-font-smoothing: antialiased;
}
.threeui-page-button:focus-visible {
outline: 2px solid var(--threeui-page-ink);
outline-offset: 5px;
}
.threeui-page-button-stage--trochil {
background: #030303;
}
.threeui-page-button-stage--trochil::before {
background: radial-gradient(circle at 71% 49%, rgba(251, 215, 54, .18), transparent 31%);
opacity: .72;
}
.threeui-page-button--trochil {
height: 52px;
padding: 0 30px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, .27);
background: linear-gradient(180deg, rgba(255, 255, 255, .07), rgba(0, 0, 0, .36));
box-shadow: 0 15px 36px rgba(0, 0, 0, .48), inset 0 1px rgba(255, 255, 255, .08);
color: rgba(255, 255, 255, .82);
font-size: 14px;
font-weight: 500;
transition: border-color .28s ease, color .28s ease, transform .28s ease, box-shadow .28s ease;
}
.threeui-page-button--trochil::before {
content: "";
position: absolute;
inset: -1px;
background: linear-gradient(105deg, transparent 17%, rgba(251, 215, 54, .25) 48%, transparent 76%);
transform: translateX(-125%);
transition: transform .65s cubic-bezier(.22, .61, .36, 1);
}
.threeui-page-button--trochil span { position: relative; }
.threeui-page-button--trochil:hover {
border-color: rgba(251, 215, 54, .58);
color: #fff;
transform: translateY(-2px);
box-shadow: 0 18px 42px rgba(0, 0, 0, .58), 0 0 24px rgba(251, 215, 54, .12);
}
.threeui-page-button--trochil:hover::before { transform: translateX(125%); }
.threeui-page-button-stage--attune {
background: #070709;
}
.threeui-page-button-stage--attune::before {
background: radial-gradient(circle at 50% 50%, rgba(255, 122, 20, .12), transparent 32%);
}
.threeui-page-button--attune {
gap: 10px;
height: 52px;
padding: 0 22px;
border-radius: 11px;
background: linear-gradient(180deg, #ffa347 0%, #ff7a14 48%, #f2610a 100%);
box-shadow: inset 0 1px rgba(255, 255, 255, .26), 0 8px 28px rgba(255, 122, 20, .28);
color: #1a0e04;
font-size: 14px;
font-weight: 650;
letter-spacing: .012em;
transition: filter .18s ease, transform .18s ease, box-shadow .18s ease;
}
.threeui-page-button--attune svg { width: 8px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.7; }
.threeui-page-button--attune:hover { filter: brightness(1.09); transform: translateY(-2px); box-shadow: inset 0 1px rgba(255, 255, 255, .34), 0 12px 34px rgba(255, 122, 20, .36); }
.threeui-page-button--attune:active { transform: translateY(1px); }
.threeui-page-button-stage--tideform {
background: #16181a;
font-family: "Roboto Mono", "SFMono-Regular", Consolas, monospace;
}
.threeui-page-button-stage--tideform::before {
background: radial-gradient(ellipse at 76% 44%, rgba(255, 122, 24, .12), transparent 34%);
}
.threeui-page-button--tideform {
gap: 18px;
padding: 17px 24px;
border: 1px solid rgba(255, 255, 255, .22);
background: rgba(255, 255, 255, .055);
box-shadow: inset 0 1px rgba(255, 255, 255, .035);
color: #fff;
font-size: 11px;
font-weight: 700;
letter-spacing: .16em;
text-transform: uppercase;
transition: border-color .25s ease, background-color .25s ease, color .25s ease;
}
.threeui-page-button--tideform svg { width: 21px; height: 9px; fill: none; stroke: currentColor; stroke-width: 1.25; transition: transform .25s cubic-bezier(.2, .8, .2, 1); }
.threeui-page-button--tideform:hover { border-color: #ff7a18; color: #ff7a18; background: rgba(255, 122, 24, .14); }
.threeui-page-button--tideform:hover svg { transform: translateX(4px); }
.threeui-page-button-stage--understory {
--threeui-page-ink: #27222d;
background: #f2efe7;
color: #27222d;
}
.threeui-page-button-stage--understory::before {
background: none;
}
.threeui-page-button--arrow-pill {
gap: 18px;
height: 54px;
padding: 0 11px 0 36px;
border-radius: 999px;
font-size: 17px;
letter-spacing: -.005em;
transition: background .3s ease, transform .3s ease, color .3s ease;
}
.threeui-page-button--arrow-pill .threeui-page-button__disc {
display: grid;
width: 32px;
height: 32px;
flex: none;
place-items: center;
border-radius: 50%;
}
.threeui-page-button--arrow-pill svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 2; transition: transform .3s ease; }
.threeui-page-button--arrow-pill:hover { transform: translateY(-2px); }
.threeui-page-button--arrow-pill:hover svg { transform: translateX(2px); }
.threeui-page-button--understory {
background: #9b78d0;
color: #fff;
box-shadow: 0 14px 30px rgba(82, 53, 112, .18);
}
.threeui-page-button--understory .threeui-page-button__disc { background: #f6f3ed; color: #9b78d0; }
.threeui-page-button--understory:hover { background: #8d69c5; }
.threeui-page-button-stage--halvorsen {
background: #111113;
}
.threeui-page-button-stage--halvorsen::before {
background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, .06), transparent 35%);
}
.threeui-page-button--halvorsen {
background: #f0eee7;
color: #111113;
box-shadow: 0 14px 34px rgba(0, 0, 0, .32);
}
.threeui-page-button--halvorsen .threeui-page-button__disc { background: #111113; color: #f0eee7; }
.threeui-page-button--halvorsen:hover { background: #fff; }
.threeui-page-button-stage--meridian {
background: #04070d;
}
.threeui-page-button-stage--meridian::before {
background: radial-gradient(circle at 50% 44%, rgba(77, 163, 255, .13), transparent 34%);
}
.threeui-page-button--meridian {
height: 62px;
padding: 0 30px;
border-radius: 15px;
flex-shrink: 0;
background: #0c1017;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07);
}
.threeui-page-button--meridian::before {
content: "";
position: absolute;
inset: 3px 4px 9px;
border-radius: 12px;
background: linear-gradient(180deg, #2a3142, #1c2230);
box-shadow: 0 5px 0 #080b11, 0 9px 15px -2px rgba(0, 0, 0, .62), inset 0 1px rgba(255, 255, 255, .11);
transition: transform .13s cubic-bezier(.22, .61, .36, 1), box-shadow .13s cubic-bezier(.22, .61, .36, 1);
}
.threeui-page-button--meridian > span:not(.threeui-page-button__led) {
position: relative;
z-index: 1;
color: #dde5f2;
font-size: 14.5px;
font-weight: 600;
letter-spacing: -.005em;
}
.threeui-page-button--meridian .threeui-page-button__led {
position: absolute;
z-index: 1;
top: 12px;
left: 50%;
width: 3px;
height: 3px;
border-radius: 50%;
background: rgba(255, 255, 255, .16);
transform: translateX(-50%);
transition: background .22s ease, box-shadow .22s ease;
}
.threeui-page-button--meridian:hover::before { transform: translateY(4px); box-shadow: 0 1px 0 #080b11, 0 3px 8px -2px rgba(0, 0, 0, .55), inset 0 1px rgba(255, 255, 255, .11); }
.threeui-page-button--meridian:hover .threeui-page-button__led { background: #4da3ff; box-shadow: 0 0 7px #4da3ff; }
.threeui-page-button--meridian-primary { background: #050f1c; box-shadow: inset 0 0 0 1px rgba(77, 163, 255, .22); }
.threeui-page-button--meridian-primary::before { background: linear-gradient(180deg, #6db6ff, #2e85e8); box-shadow: 0 5px 0 #0b4a86, 0 9px 18px -2px rgba(21, 88, 158, .5), inset 0 1px rgba(255, 255, 255, .5); }
.threeui-page-button--meridian-primary > span:not(.threeui-page-button__led) { color: #04182f; }
.threeui-page-button--meridian-primary .threeui-page-button__led { background: rgba(4, 24, 47, .3); }
.threeui-page-button--meridian-primary:hover::before { box-shadow: 0 1px 0 #0b4a86, 0 3px 10px -2px rgba(21, 88, 158, .45), inset 0 1px rgba(255, 255, 255, .5); }
.threeui-page-button--meridian-primary:hover .threeui-page-button__led { background: #04182f; box-shadow: 0 0 6px rgba(4, 24, 47, .55); }
.threeui-page-button-stage--aster {
background: #101010;
font-family: "Roboto Flex", "Arial Narrow", Inter, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.threeui-page-button-stage--aster::before {
background:
radial-gradient(circle at 64% 42%, rgba(255, 255, 255, .07), transparent 21%),
radial-gradient(circle at 38% 62%, rgba(255, 255, 255, .035), transparent 27%);
}
.threeui-page-button--aster {
height: 52px;
border-radius: 12px;
isolation: isolate;
background: rgba(255, 255, 255, .055);
color: #f1f1f1;
font-size: 17px;
font-variation-settings: "wdth" 100, "opsz" 14;
letter-spacing: -.004em;
box-shadow: 0 12px 34px -22px rgba(255, 255, 255, .35);
backdrop-filter: blur(16px) saturate(1.25);
-webkit-backdrop-filter: blur(16px) saturate(1.25);
transition: transform .5s cubic-bezier(.22, 1, .36, 1), background .4s ease, color .4s ease, box-shadow .5s cubic-bezier(.22, 1, .36, 1);
}
.threeui-page-button--aster::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
padding: 1px;
background: linear-gradient(148deg, rgba(255, 255, 255, .72), rgba(255, 255, 255, .16) 34%, rgba(255, 255, 255, .05) 58%, rgba(255, 255, 255, .34));
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
pointer-events: none;
transition: filter .45s cubic-bezier(.22, 1, .36, 1);
}
.threeui-page-button--aster::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
background: linear-gradient(180deg, rgba(255, 255, 255, .13), transparent 46%);
pointer-events: none;
}
.threeui-page-button--aster > span { position: relative; z-index: 1; }
.threeui-page-button--aster:hover {
transform: translateY(-1px);
background: rgba(255, 255, 255, .15);
color: #fff;
box-shadow: 0 8px 26px -12px rgba(255, 255, 255, .42);
}
.threeui-page-button--aster:hover::before { filter: brightness(1.55); }
.threeui-page-button--aster:active { transform: translateY(0); }
.threeui-page-button--aster-access { padding: 0 31px; }
.threeui-page-button--aster-arrow { gap: 17px; padding: 0 7px 0 22px; }
.threeui-page-button--aster-arrow .threeui-page-button__chip {
display: grid;
width: 34px;
height: 34px;
flex: none;
place-items: center;
border-radius: 9px;
background: rgba(255, 255, 255, .10);
}
.threeui-page-button--aster-arrow .threeui-page-button__chip::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(150deg, rgba(255, 255, 255, .75), rgba(255, 255, 255, .10) 62%, rgba(255, 255, 255, .42));
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
pointer-events: none;
}
.threeui-page-button--aster-arrow svg {
width: 15px;
height: 15px;
fill: none;
stroke: currentColor;
stroke-width: 1.4;
stroke-linecap: round;
stroke-linejoin: round;
transition: transform .5s cubic-bezier(.22, 1, .36, 1);
}
.threeui-page-button--aster-arrow:hover svg { transform: translateX(2px); }
/* ---------------------------------------------------------------- *
The other ground
Eight of these treatments were drawn on their page's dark ground and
Understory on its paper one, so the opposite mode is a re-tone rather
than a wash: the stage takes the other ground, and every value that
ground would swallow — edge, ink, sheen, cast shadow — is restated at
the weight it was authored to read at. Geometry and motion never move.
* ---------------------------------------------------------------- */
.threeui-page-button-stage[data-mode="light"] { --threeui-page-ink: #14161a; }
/* Trochil — the amber signal sheen, now over warm paper */
.threeui-page-button-stage--trochil[data-mode="light"] { background: #f5f3ee; }
.threeui-page-button-stage--trochil[data-mode="light"]::before {
background: radial-gradient(circle at 71% 49%, rgba(196, 142, 6, .22), transparent 31%);
opacity: .9;
}
.threeui-page-button-stage--trochil[data-mode="light"] .threeui-page-button--trochil {
border-color: rgba(20, 19, 14, .22);
background: linear-gradient(180deg, rgba(255, 255, 255, .95), rgba(20, 19, 14, .05));
box-shadow: 0 14px 30px rgba(20, 19, 14, .13), inset 0 1px rgba(255, 255, 255, .9);
color: rgba(20, 19, 14, .78);
}
.threeui-page-button-stage--trochil[data-mode="light"] .threeui-page-button--trochil::before {
background: linear-gradient(105deg, transparent 17%, rgba(196, 142, 6, .3) 48%, transparent 76%);
}
.threeui-page-button-stage--trochil[data-mode="light"] .threeui-page-button--trochil:hover {
border-color: rgba(176, 126, 4, .62);
color: #14130e;
box-shadow: 0 18px 38px rgba(20, 19, 14, .17), 0 0 22px rgba(196, 142, 6, .2);
}
/* Attune — the thermal cap already carries its own ink, so only the ground
and the cast heat change */
.threeui-page-button-stage--attune[data-mode="light"] { background: #faf4ee; }
.threeui-page-button-stage--attune[data-mode="light"]::before {
background: radial-gradient(circle at 50% 50%, rgba(255, 122, 20, .18), transparent 32%);
}
.threeui-page-button-stage--attune[data-mode="light"] .threeui-page-button--attune {
box-shadow: inset 0 1px rgba(255, 255, 255, .5), 0 8px 24px rgba(226, 92, 6, .28);
}
.threeui-page-button-stage--attune[data-mode="light"] .threeui-page-button--attune:hover {
box-shadow: inset 0 1px rgba(255, 255, 255, .58), 0 12px 30px rgba(226, 92, 6, .34);
}
/* Tideform — the outline drops to graphite and the hot state to a deeper
orange, which is where it clears text contrast on paper */
.threeui-page-button-stage--tideform[data-mode="light"] { background: #eeece7; --threeui-page-ink: #16181a; }
.threeui-page-button-stage--tideform[data-mode="light"]::before {
background: radial-gradient(ellipse at 76% 44%, rgba(214, 96, 12, .16), transparent 34%);
}
.threeui-page-button-stage--tideform[data-mode="light"] .threeui-page-button--tideform {
border-color: rgba(22, 24, 26, .26);
background: rgba(22, 24, 26, .035);
box-shadow: inset 0 1px rgba(255, 255, 255, .7);
color: #16181a;
}
.threeui-page-button-stage--tideform[data-mode="light"] .threeui-page-button--tideform:hover {
border-color: #c25a0c;
background: rgba(194, 90, 12, .1);
color: #b4530b;
}
/* Understory runs the other way: the violet capsule is the constant, and
dark mode is the ground it was never given */
.threeui-page-button-stage--understory[data-mode="dark"] {
--threeui-page-ink: #ece7e0;
background: #17141b;
color: #ece7e0;
}
.threeui-page-button-stage--understory[data-mode="dark"]::before {
background: radial-gradient(circle at 50% 50%, rgba(155, 120, 208, .16), transparent 34%);
}
.threeui-page-button-stage--understory[data-mode="dark"] .threeui-page-button--understory {
background: #a482d9;
box-shadow: 0 16px 34px rgba(0, 0, 0, .5);
}
.threeui-page-button-stage--understory[data-mode="dark"] .threeui-page-button--understory:hover { background: #b08fe2; }
/* Halvorsen is a maximum-contrast pill, so on paper it inverts rather than
fades — the capsule takes the ink and the endcap takes the paper */
.threeui-page-button-stage--halvorsen[data-mode="light"] { background: #eeece5; }
.threeui-page-button-stage--halvorsen[data-mode="light"]::before {
background: radial-gradient(circle at 50% 50%, rgba(17, 17, 19, .05), transparent 35%);
}
.threeui-page-button-stage--halvorsen[data-mode="light"] .threeui-page-button--halvorsen {
background: #111113;
box-shadow: 0 14px 30px rgba(17, 17, 19, .22);
color: #f0eee7;
}
.threeui-page-button-stage--halvorsen[data-mode="light"] .threeui-page-button--halvorsen .threeui-page-button__disc {
background: #f0eee7;
color: #111113;
}
.threeui-page-button-stage--halvorsen[data-mode="light"] .threeui-page-button--halvorsen:hover { background: #000; }
/* Meridian — the keycap keeps its travel and its hard base edge; only the
housing, the cap face, and the legend follow the light ground */
.threeui-page-button-stage--meridian[data-mode="light"] { background: #e9eef5; --threeui-page-ink: #04070d; }
.threeui-page-button-stage--meridian[data-mode="light"]::before {
background: radial-gradient(circle at 50% 44%, rgba(46, 133, 232, .2), transparent 34%);
}
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian {
background: #dfe5ee;
box-shadow: inset 0 0 0 1px rgba(4, 7, 13, .09);
}
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian::before {
background: linear-gradient(180deg, #fbfcfe, #e3e8f1);
box-shadow: 0 5px 0 #bcc5d3, 0 9px 15px -2px rgba(4, 7, 13, .2), inset 0 1px #fff;
}
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian > span:not(.threeui-page-button__led) { color: #1d2532; }
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian .threeui-page-button__led { background: rgba(4, 7, 13, .18); }
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian:hover::before { box-shadow: 0 1px 0 #bcc5d3, 0 3px 8px -2px rgba(4, 7, 13, .18), inset 0 1px #fff; }
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian:hover .threeui-page-button__led { background: #2e85e8; box-shadow: 0 0 7px #2e85e8; }
/* the primary cap stays blue on both grounds, so it is restated after the
secondary block rather than inheriting it */
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian-primary {
background: #dbe7f6;
box-shadow: inset 0 0 0 1px rgba(46, 133, 232, .3);
}
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian-primary::before {
background: linear-gradient(180deg, #6db6ff, #2e85e8);
box-shadow: 0 5px 0 #1d6dc4, 0 9px 18px -2px rgba(21, 88, 158, .32), inset 0 1px rgba(255, 255, 255, .5);
}
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian-primary > span:not(.threeui-page-button__led) { color: #04182f; }
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian-primary .threeui-page-button__led { background: rgba(4, 24, 47, .3); }
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian-primary:hover::before { box-shadow: 0 1px 0 #1d6dc4, 0 3px 10px -2px rgba(21, 88, 158, .3), inset 0 1px rgba(255, 255, 255, .5); }
.threeui-page-button-stage--meridian[data-mode="light"] .threeui-page-button--meridian-primary:hover .threeui-page-button__led { background: #04182f; box-shadow: 0 0 6px rgba(4, 24, 47, .55); }
/* Aster — the glass is a white edge lit from one corner, so on paper the
whole build flips to graphite and the hover brightening becomes darkening */
.threeui-page-button-stage--aster[data-mode="light"] { background: #eceae7; --threeui-page-ink: #101010; }
.threeui-page-button-stage--aster[data-mode="light"]::before {
background:
radial-gradient(circle at 64% 42%, rgba(16, 16, 16, .06), transparent 21%),
radial-gradient(circle at 38% 62%, rgba(16, 16, 16, .035), transparent 27%);
}
.threeui-page-button-stage--aster[data-mode="light"] .threeui-page-button--aster {
background: rgba(16, 16, 16, .05);
box-shadow: 0 14px 34px -22px rgba(16, 16, 16, .55);
color: #16181a;
}
.threeui-page-button-stage--aster[data-mode="light"] .threeui-page-button--aster::before {
background: linear-gradient(148deg, rgba(16, 16, 16, .45), rgba(16, 16, 16, .12) 34%, rgba(16, 16, 16, .05) 58%, rgba(16, 16, 16, .26));
}
.threeui-page-button-stage--aster[data-mode="light"] .threeui-page-button--aster::after {
background: linear-gradient(180deg, rgba(255, 255, 255, .8), transparent 46%);
}
.threeui-page-button-stage--aster[data-mode="light"] .threeui-page-button--aster:hover {
background: rgba(16, 16, 16, .1);
box-shadow: 0 8px 26px -12px rgba(16, 16, 16, .3);
color: #000;
}
.threeui-page-button-stage--aster[data-mode="light"] .threeui-page-button--aster:hover::before { filter: brightness(.68); }
.threeui-page-button-stage--aster[data-mode="light"] .threeui-page-button--aster-arrow .threeui-page-button__chip { background: rgba(16, 16, 16, .07); }
.threeui-page-button-stage--aster[data-mode="light"] .threeui-page-button--aster-arrow .threeui-page-button__chip::before {
background: linear-gradient(150deg, rgba(16, 16, 16, .5), rgba(16, 16, 16, .1) 62%, rgba(16, 16, 16, .3));
}
@media (prefers-reduced-motion: reduce) {
.threeui-page-button, .threeui-page-button::before, .threeui-page-button svg { transition-duration: .01ms !important; }
}
`;
type SelectedPageButtonProps = Pick<NeuformIsolatedEffectProps, "mode" | "className" | "style"> & {
variant: SelectedPageButtonVariant;
};
function ArrowIcon({ long = false }: { long?: boolean }) {
return long ? (
<svg viewBox="0 0 21 9" aria-hidden="true"><path d="M0 4.5h18M14.5 1.2 18.3 4.5l-3.8 3.3" /></svg>
) : (
<svg viewBox="0 0 12 12" aria-hidden="true"><path d="M3 1.5 8 6 3 10.5" /></svg>
);
}
function SelectedPageButton({ variant, mode = "dark", className = "", style }: SelectedPageButtonProps) {
let theme = "trochil";
let button: ReactNode = null;
switch (variant) {
case "trochil-signal":
button = <button className="threeui-page-button threeui-page-button--trochil" type="button"><span>Request access</span></button>;
break;
case "attune-thermal":
theme = "attune";
button = <button className="threeui-page-button threeui-page-button--attune" type="button"><span>Start free</span><ArrowIcon /></button>;
break;
case "tideform-outline":
theme = "tideform";
button = <button className="threeui-page-button threeui-page-button--tideform" type="button"><span>See the work</span><ArrowIcon long /></button>;
break;
case "understory-arrow-pill":
theme = "understory";
button = <button className="threeui-page-button threeui-page-button--arrow-pill threeui-page-button--understory" type="button"><span>Begin</span><span className="threeui-page-button__disc"><ArrowIcon /></span></button>;
break;
case "meridian-keycap-primary":
theme = "meridian";
button = <button className="threeui-page-button threeui-page-button--meridian threeui-page-button--meridian-primary" type="button"><span className="threeui-page-button__led" /><span>Start free</span></button>;
break;
case "meridian-keycap-secondary":
theme = "meridian";
button = <button className="threeui-page-button threeui-page-button--meridian" type="button"><span className="threeui-page-button__led" /><span>Book a walkthrough</span></button>;
break;
case "halvorsen-arrow-pill":
theme = "halvorsen";
button = <button className="threeui-page-button threeui-page-button--arrow-pill threeui-page-button--halvorsen" type="button"><span>See the work</span><span className="threeui-page-button__disc"><ArrowIcon /></span></button>;
break;
case "aster-glass-access":
theme = "aster";
button = <button className="threeui-page-button threeui-page-button--aster threeui-page-button--aster-access" type="button"><span>Get access</span></button>;
break;
case "aster-glass-arrow":
theme = "aster";
button = <button className="threeui-page-button threeui-page-button--aster threeui-page-button--aster-arrow" type="button"><span>Start free</span><span className="threeui-page-button__chip"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M2 6h8M6.6 2.6 10 6 6.6 9.4" /></svg></span></button>;
break;
}
return (
<div
className={`threeui-page-button-stage threeui-page-button-stage--${theme}${className ? ` ${className}` : ""}`}
data-mode={mode}
data-variant={variant}
style={style}
>
<style>{SELECTED_PAGE_BUTTON_STYLES}</style>
{button}
</div>
);
}
export function RectangleButtons({ variant = "dark-pill", ...props }: RectangleButtonsProps) {
if (variant === "dark-pill") {
return <DarkGlassRectangle className={props.className} style={props.style} mode={props.mode === "light" ? "light" : "dark"} />;
}
if (variant === "lumen-cta" || variant === "lumen-cta-ghost") {
return (
<Suspense fallback={null}>
<LumenCta {...props} variant={variant === "lumen-cta-ghost" ? "ghost" : "primary"} />
</Suspense>
);
}
if (isSelectedPageButtonVariant(variant)) {
return (
<SelectedPageButton
variant={variant}
mode={props.mode}
className={props.className}
style={props.style}
/>
);
}
const Variant = RECTANGLE_VARIANTS[variant];
return (
<Suspense fallback={null}>
<Variant {...props} />
</Suspense>
);
}
/** @deprecated Use RectangleButtons. */
export const DarkGlassButton = RectangleButtons;
/** @deprecated Use RectangleButtonsProps. */
export type DarkGlassButtonProps = RectangleButtonsProps;
/** @deprecated Use RectangleButtonVariant. */
export type DarkGlassButtonVariant = RectangleButtonVariant;
/* ===== src/shaders/section-elements/SectionElements.tsx (tsx) ===== */
import { useState, type CSSProperties, type FormEvent, type ReactNode } from "react";
import "./section-elements.css";
const testimonialIllustration = new URL("./assets/testimonials-illustration.svg", import.meta.url).href;
const testimonialKeyboard = new URL("./assets/testimonials-keyboard.svg", import.meta.url).href;
const testimonialDots = new URL("./assets/testimonials-dots.svg", import.meta.url).href;
export type SectionCompositionProps = {
className?: string;
style?: CSSProperties;
};
function classNames(base: string, className?: string) {
return className ? `${base} ${className}` : base;
}
function GlassPill({ children }: { children: ReactNode }) {
return (
<span className="section-label">
<span className="section-label__title">{children}</span>
<span className="section-label__circle" aria-hidden="true" />
</span>
);
}
function SectionButton({ children = "Sign up", className = "", type = "button" }: { children?: ReactNode; className?: string; type?: "button" | "submit" }) {
return (
<button className={`section-button ${className}`.trim()} type={type}>
<span className="section-button__title">{children}</span>
<span className="section-button__circle" aria-hidden="true" />
</button>
);
}
/* the only section composition offered on both grounds, so it is the only one
that takes a mode; the rest stay on the dark ground they were authored for */
export function DarkGlassButton({ className, style, mode = "dark" }: SectionCompositionProps & { mode?: "light" | "dark" }) {
return (
<div className={classNames("section-element section-element--glass-button", className)} data-mode={mode} style={style}>
<SectionButton />
</div>
);
}
export function EditorialIntroSection({ className, style }: SectionCompositionProps) {
return (
<section className={classNames("section-element section-element--testimonial-intro", className)} style={style} aria-labelledby="section-testimonial-title">
<div className="editorial-intro__graphic" aria-hidden="true">
<img className="editorial-intro__dots" src={testimonialDots} alt="" width="368" height="368" />
<div className="editorial-intro__device">
<img src={testimonialIllustration} alt="" width="368" height="368" />
<img className="editorial-intro__keyboard" src={testimonialKeyboard} alt="" width="148" height="17" />
</div>
</div>
<div className="editorial-intro__copy">
<GlassPill>Made for momentum</GlassPill>
<h2 id="section-testimonial-title">Ideas, in motion.</h2>
<p>A focused interface keeps every action clear and every handoff moving.</p>
</div>
</section>
);
}
function EnvelopeIcon() {
return (
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
<path d="M20.5 9.55v4.9c0 1.56 0 2.34-.3 2.94a2.75 2.75 0 0 1-1.21 1.21c-.6.3-1.38.3-2.94.3h-8.1c-1.56 0-2.34 0-2.94-.3a2.75 2.75 0 0 1-1.21-1.21c-.3-.6-.3-1.38-.3-2.94v-4.9c0-1.56 0-2.34.3-2.94A2.75 2.75 0 0 1 5.01 5.4c.6-.3 1.38-.3 2.94-.3h8.1c1.56 0 2.34 0 2.94.3a2.75 2.75 0 0 1 1.21 1.21c.3.6.3 1.38.3 2.94Z" />
<path d="m3.55 6 6.61 5.14a3 3 0 0 0 3.68 0L20.45 6" fill="none" />
</svg>
);
}
export function NewsletterFooterSection({ className, style }: SectionCompositionProps) {
const [subscribed, setSubscribed] = useState(false);
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setSubscribed(true);
};
return (
<footer className={classNames("section-element section-element--newsletter-footer", className)} style={style}>
<div className="newsletter-footer__row">
<div className="newsletter-footer__brand">
<span className="newsletter-footer__mark" aria-label="Section marker"><span>01</span></span>
<p>A monthly edit of thoughtful interfaces,<br />practical patterns, and new experiments.</p>
</div>
<form className={`newsletter-footer__form${subscribed ? " is-success" : ""}`} aria-label="Join the monthly design notes" onSubmit={submit}>
<EnvelopeIcon />
<input aria-label="Email address" type="email" placeholder="Email for monthly notes" required />
<SectionButton className="newsletter-footer__button" type="submit">{subscribed ? "You're subscribed" : "Join the list"}</SectionButton>
<span className="newsletter-footer__ring" aria-hidden="true" />
</form>
</div>
<div className="newsletter-footer__wordmark" aria-label="Stay curious">STAY CURIOUS</div>
<div className="newsletter-footer__legal">
<span>© 2026. Built for thoughtful work.</span>
<a href="#privacy">Privacy</a>
<a href="#terms">Terms</a>
<a href="#contact">Contact</a>
</div>
</footer>
);
}
/* ===== src/shaders/section-elements/section-elements.css (css) ===== */
.section-element,
.section-element *,
.section-element *::before,
.section-element *::after {
box-sizing: border-box;
}
.section-element {
position: relative;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
color: rgba(255, 255, 255, 0.95);
background: #0a0a0a;
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
container-type: size;
isolation: isolate;
}
.section-element button,
.section-element input {
font: inherit;
}
.section-button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
height: 52px;
padding: 0 32px;
overflow: hidden;
border: 0;
border-radius: 32px;
color: rgba(255, 255, 255, 0.7);
background: transparent;
font-size: 14px;
font-weight: 600;
line-height: 1.4;
cursor: pointer;
}
.section-button::before,
.section-button::after {
position: absolute;
inset: 0;
border-radius: inherit;
content: "";
}
.section-button::before {
background: rgba(23, 23, 23, 0.7);
box-shadow: inset 2px 4px 16px rgba(255, 255, 255, 0.06);
-webkit-backdrop-filter: blur(50px);
backdrop-filter: blur(50px);
}
.section-button::after {
z-index: 2;
border: 1px solid rgba(255, 255, 255, 0.25);
opacity: 0.25;
pointer-events: none;
-webkit-mask-image: linear-gradient(170deg, #000, transparent 50%);
mask-image: linear-gradient(170deg, #000, transparent 50%);
}
.section-button__title {
position: relative;
z-index: 4;
}
.section-button__circle {
position: absolute;
z-index: 3;
top: calc(50% - 100px);
left: calc(50% - 100px);
width: 200px;
height: 200px;
}
.section-button__circle::before,
.section-button__circle::after {
position: absolute;
inset: 0;
border-radius: 50%;
content: "";
}
.section-button__circle::before {
background: conic-gradient(from 180deg, rgba(255, 255, 255, 0.012) 180deg, rgba(255, 255, 255, 0.05) 360deg);
filter: blur(15px);
animation: section-button-circle 3.6s linear infinite;
transition: opacity 0.5s;
}
.section-button__circle::after {
inset: 30px;
background: rgba(255, 255, 255, 0.12);
opacity: 0;
filter: blur(5px);
transform: scale(0.5);
transition: opacity 0.5s, transform 0.7s;
-webkit-mask-image: radial-gradient(50% 50%, #000, rgba(23, 23, 23, 0));
mask-image: radial-gradient(50% 50%, #000, rgba(23, 23, 23, 0));
}
.section-button:hover .section-button__circle::before {
opacity: 0;
}
.section-button:hover .section-button__circle::after {
opacity: 1;
transform: scale(1);
}
.section-button:active {
transform: scale(0.98);
}
@keyframes section-button-circle {
0% { transform: translate(0) rotate(0); }
25% { transform: translateX(-30px) rotate(90deg); }
50% { transform: translate(0) rotate(180deg); }
75% { transform: translateX(30px) rotate(270deg); }
100% { transform: translate(0) rotate(360deg); }
}
.section-label {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
height: 28px;
padding: 2px 16px 0;
overflow: hidden;
border-radius: 16px;
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
font-weight: 600;
line-height: 1.65;
text-transform: uppercase;
}
.section-label::before,
.section-label::after {
position: absolute;
inset: 0;
border-radius: inherit;
content: "";
}
.section-label::before {
z-index: 3;
inset: 1.5px;
background: #0a0a0a;
}
.section-label::after {
z-index: 2;
border: 1.5px solid rgba(255, 255, 255, 0.05);
opacity: 0.25;
pointer-events: none;
-webkit-mask-image: linear-gradient(170deg, #000, transparent 50%);
mask-image: linear-gradient(170deg, #000, transparent 50%);
}
.section-label__title {
position: relative;
z-index: 4;
opacity: 0.8;
}
.section-label__circle {
position: absolute;
z-index: 1;
top: calc(50% - 100px);
left: calc(50% - 100px);
width: 200px;
height: 200px;
}
.section-label__circle::before {
position: absolute;
inset: 0;
border-radius: 50%;
background: conic-gradient(from 180deg at 50% 51%, rgba(255, 255, 255, 0.012) 330deg, #fff 360deg);
filter: blur(15px);
animation: section-button-circle 3.6s linear infinite;
content: "";
}
.section-element--glass-button {
display: grid;
place-items: center;
}
.section-element--glass-button::before {
position: absolute;
top: 50%;
left: 50%;
width: min(62cqw, 440px);
height: min(50cqh, 240px);
border-radius: 50%;
background: rgba(255, 255, 255, 0.02);
filter: blur(54px);
transform: translate(-50%, -50%);
content: "";
}
.section-element--glass-button .section-button {
width: clamp(144px, 23cqw, 288px);
height: clamp(52px, 8.3cqw, 104px);
border-radius: 999px;
font-size: clamp(14px, 2.25cqw, 28px);
}
/* ---------------------------------------------------------------- *
Dark Glass on paper
The same material read on the other ground, and scoped to this one
composition so every other section keeps the dark stage it was drawn
on. Each authored layer keeps its geometry, mask, and timing; only its
direction flips — the body goes from smoked to frosted, and the two
white highlights (the revolving conic and the hover pool) become the
shade that white cast on charcoal, since white on paper is nothing.
* ---------------------------------------------------------------- */
.section-element--glass-button[data-mode="light"] {
background: #eeedeb;
color: rgba(17, 17, 17, 0.95);
}
.section-element--glass-button[data-mode="light"]::before {
background: rgba(17, 17, 17, 0.06);
}
.section-element--glass-button[data-mode="light"] .section-button {
color: rgba(17, 17, 17, 0.72);
}
.section-element--glass-button[data-mode="light"] .section-button::before {
background: rgba(255, 255, 255, 0.72);
box-shadow: inset 2px 4px 16px rgba(17, 17, 17, 0.05), 0 10px 26px -12px rgba(17, 17, 17, 0.4);
}
/* the hairline is masked to a quarter of its own opacity, which reads on
charcoal but not on paper, so the ink is both darker and less faded */
.section-element--glass-button[data-mode="light"] .section-button::after {
border-color: rgba(17, 17, 17, 0.34);
opacity: 0.45;
}
.section-element--glass-button[data-mode="light"] .section-button__circle::before {
background: conic-gradient(from 180deg, rgba(17, 17, 17, 0.012) 180deg, rgba(17, 17, 17, 0.07) 360deg);
}
.section-element--glass-button[data-mode="light"] .section-button__circle::after {
background: rgba(17, 17, 17, 0.12);
}
.editorial-intro h2 {
margin: 0;
background: linear-gradient(93deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.5));
background-clip: text;
color: transparent;
font-size: clamp(18px, 4.1cqw, 54px);
font-weight: 700;
letter-spacing: -0.035em;
line-height: 1.04;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.section-element--testimonial-intro {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: clamp(20px, 6cqh, 56px) 24px clamp(26px, 7cqh, 64px);
text-align: center;
}
.editorial-intro__graphic {
position: relative;
width: min(58cqh, 38cqw, 420px);
aspect-ratio: 1;
flex: 0 0 auto;
}
.editorial-intro__graphic::before,
.editorial-intro__graphic::after {
position: absolute;
top: 50%;
left: 50%;
width: 1px;
height: 20%;
background: rgba(255, 255, 255, 0.13);
content: "";
transform: translate(-50%, -50%);
}
.editorial-intro__graphic::before { top: -3%; }
.editorial-intro__graphic::after { top: 103%; }
.editorial-intro__dots,
.editorial-intro__device,
.editorial-intro__device > img:first-child {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.editorial-intro__dots {
animation: section-dots-drift 8s ease-in-out infinite alternate;
}
.editorial-intro__device {
animation: section-device-float 6s ease-in-out infinite;
}
.editorial-intro__keyboard {
position: absolute;
top: 59.6%;
left: 50%;
width: 40.2%;
transform: translateX(-50%);
}
.editorial-intro__copy {
position: relative;
z-index: 2;
}
.editorial-intro__copy .section-label {
margin-bottom: clamp(8px, 2cqh, 16px);
}
.editorial-intro__copy h2 {
font-size: clamp(23px, 6.6cqw, 84px);
line-height: 1;
}
.editorial-intro__copy p {
margin: clamp(8px, 3.6cqh, 32px) 0 0;
color: rgba(255, 255, 255, 0.64);
font-size: clamp(10px, 2.5cqw, 32px);
font-weight: 300;
line-height: 1.4;
}
@keyframes section-device-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
@keyframes section-dots-drift {
from { transform: rotate(-0.4deg) scale(0.995); }
to { transform: rotate(0.5deg) scale(1.008); }
}
.section-element--newsletter-footer {
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 0 clamp(24px, 4.5cqw, 90px) clamp(12px, 2cqh, 24px);
}
.newsletter-footer__row {
display: flex;
align-items: center;
gap: 40px;
margin-bottom: clamp(18px, 4.5cqh, 32px);
}
.newsletter-footer__brand {
display: flex;
align-items: center;
min-width: 0;
margin-right: auto;
}
.newsletter-footer__mark {
display: grid;
flex: none;
width: clamp(32px, 4.4cqw, 56px);
height: clamp(32px, 4.4cqw, 56px);
margin-right: clamp(16px, 2.5cqw, 32px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 24%;
place-items: center;
background: linear-gradient(145deg, rgba(255, 255, 255, 0.13), rgba(255, 255, 255, 0.025));
box-shadow: inset 0 1px rgba(255, 255, 255, 0.08), 0 14px 28px rgba(0, 0, 0, 0.28);
}
.newsletter-footer__mark span {
color: rgba(255, 255, 255, 0.82);
font-size: clamp(9px, 1.1cqw, 14px);
font-weight: 600;
letter-spacing: -0.04em;
}
.newsletter-footer__brand p {
margin: 0;
color: rgba(255, 255, 255, 0.64);
font-size: clamp(10px, 1.55cqw, 20px);
font-weight: 300;
line-height: 1.6;
}
.newsletter-footer__form {
position: relative;
flex: 0 1 400px;
height: 60px;
overflow: hidden;
border-radius: 30px;
background: rgba(23, 23, 23, 0.3);
}
.newsletter-footer__form::before {
position: absolute;
inset: 0;
border: 1.5px solid rgba(255, 255, 255, 0.02);
border-radius: inherit;
content: "";
pointer-events: none;
-webkit-mask-image: linear-gradient(175deg, #000, transparent 50%);
mask-image: linear-gradient(175deg, #000, transparent 50%);
}
.newsletter-footer__form > svg {
position: absolute;
z-index: 1;
top: 18px;
left: 22px;
fill: rgba(255, 255, 255, 0.62);
stroke: rgba(255, 255, 255, 0.62);
stroke-width: 1.5;
pointer-events: none;
transition: opacity 0.25s;
}
.newsletter-footer__form input {
width: 100%;
height: 60px;
padding: 0 164px 0 60px;
border: 0;
outline: 0;
border-radius: 30px;
color: rgba(255, 255, 255, 0.95);
background: transparent;
font-size: 14px;
line-height: 1.4;
transition: opacity 0.25s;
}
.newsletter-footer__form input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.newsletter-footer__button {
position: absolute;
z-index: 2;
top: 4px;
right: 4px;
width: 148px;
padding: 0 12px;
transition: width 0.45s cubic-bezier(0.32, 0.72, 0, 1), padding 0.45s cubic-bezier(0.32, 0.72, 0, 1);
}
.newsletter-footer__ring {
position: absolute;
inset: -1px;
border: 1px solid transparent;
border-radius: 999px;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s 0.25s;
}
.newsletter-footer__form.is-success > svg,
.newsletter-footer__form.is-success input {
opacity: 0;
}
.newsletter-footer__form.is-success .newsletter-footer__button {
width: calc(100% - 8px);
padding: 0 20px;
}
.newsletter-footer__form.is-success .newsletter-footer__ring {
border-color: rgba(255, 194, 61, 0.5);
opacity: 1;
}
.newsletter-footer__wordmark {
width: 100%;
color: rgba(248, 248, 248, 0.055);
font-size: clamp(50px, 12.7cqw, 164px);
font-weight: 700;
letter-spacing: -0.075em;
line-height: 0.9;
text-align: center;
white-space: nowrap;
}
.newsletter-footer__legal {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px clamp(12px, 1.8cqw, 24px);
margin-top: clamp(14px, 3.9cqh, 28px);
color: rgba(255, 255, 255, 0.5);
font-size: clamp(8px, 1.1cqw, 14px);
line-height: 1.4;
text-align: center;
}
.newsletter-footer__legal a {
color: rgba(255, 255, 255, 0.64);
text-decoration: none;
transition: color 0.2s;
}
.newsletter-footer__legal a:hover {
color: #fff;
}
@container (max-width: 720px) {
.section-element--newsletter-footer {
padding-inline: 18px;
}
.newsletter-footer__row {
gap: 18px;
}
.newsletter-footer__form {
flex-basis: 42%;
height: 44px;
}
.newsletter-footer__form > svg {
top: 13px;
left: 14px;
width: 18px;
height: 18px;
}
.newsletter-footer__form input {
height: 44px;
padding: 0 92px 0 42px;
font-size: 10px;
}
.newsletter-footer__button {
top: 3px;
right: 3px;
width: 84px;
height: 38px;
padding: 0 8px;
font-size: 10px;
}
}
@container (max-height: 360px) {
.section-element--testimonial-intro {
padding-block: 14px 18px;
}
.editorial-intro__copy p {
margin-top: 8px;
}
.newsletter-footer__row {
margin-bottom: 12px;
}
.newsletter-footer__legal {
margin-top: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.section-element *,
.section-element *::before,
.section-element *::after {
animation: none !important;
transition: none !important;
}
}
/* ===== src/shaders/lumen-cta/LumenCta.tsx (tsx) ===== */
import type { CSSProperties, MouseEventHandler } from "react";
import "./lumen-cta.css";
export type LumenCtaVariant = "primary" | "ghost";
export type LumenCtaMode = "light" | "dark";
export type LumenCtaProps = {
variant?: LumenCtaVariant;
mode?: LumenCtaMode;
label?: string;
ring?: boolean;
hue?: number;
saturation?: number;
brightness?: number;
disabled?: boolean;
type?: "button" | "submit" | "reset";
onClick?: MouseEventHandler<HTMLButtonElement>;
className?: string;
style?: CSSProperties;
};
export const LUMEN_CTA_DEFAULTS = {
variant: "primary",
mode: "dark",
label: "Get your card",
ring: true,
hue: 0,
saturation: 1,
brightness: 1,
} as const satisfies Required<
Pick<LumenCtaProps, "variant" | "mode" | "label" | "ring" | "hue" | "saturation" | "brightness">
>;
function clamp(value: number, minimum: number, maximum: number) {
return Math.min(maximum, Math.max(minimum, value));
}
export function LumenCta({
variant = LUMEN_CTA_DEFAULTS.variant,
mode = LUMEN_CTA_DEFAULTS.mode,
label = LUMEN_CTA_DEFAULTS.label,
ring = LUMEN_CTA_DEFAULTS.ring,
hue = LUMEN_CTA_DEFAULTS.hue,
saturation = LUMEN_CTA_DEFAULTS.saturation,
brightness = LUMEN_CTA_DEFAULTS.brightness,
disabled = false,
type = "button",
onClick,
className = "",
style,
}: LumenCtaProps) {
const safeVariant: LumenCtaVariant = variant === "ghost" ? "ghost" : "primary";
const safeMode: LumenCtaMode = mode === "light" ? "light" : "dark";
return (
<div
className={`lumen-cta lumen-cta--${safeMode}${className ? ` ${className}` : ""}`}
data-variant={safeVariant}
style={
{
"--lumen-cta-hue": `${clamp(hue, -180, 180)}deg`,
"--lumen-cta-saturation": clamp(saturation, 0, 2),
"--lumen-cta-brightness": clamp(brightness, 0.35, 1.65),
...style,
} as CSSProperties
}
>
<button
className={`lumen-cta__button${safeVariant === "ghost" ? " lumen-cta__button--ghost" : ""}`}
type={type}
disabled={disabled}
onClick={onClick}
>
{label}
{ring ? <i className="lumen-cta__ring" aria-hidden="true" /> : null}
</button>
</div>
);
}
/* ===== src/shaders/neuform-isolated/sources/launch-button.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #000000;
color: #ffffff;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><button class="group/btn relative">
<div class="-inset-1 group-hover/btn:opacity-75 transition duration-500 bg-amber-500/40 opacity-40 rounded-xl absolute blur"></div>
<div class="relative bg-gradient-to-b from-amber-200 via-amber-300 to-amber-500 text-amber-950 rounded-xl px-8 py-4 flex items-center gap-3 shadow-[0_0_0_1px_rgba(251,191,36,0.5),0_4px_0_#b45309,0_10px_15px_-3px_rgba(0,0,0,0.5)] active:translate-y-[2px] active:shadow-[0_0_0_1px_rgba(251,191,36,0.5),0_2px_0_#b45309] transition-all duration-150">
<span class="text-lg font-medium tracking-tight">Initialize Launch</span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" data-lucide="zap" class="lucide lucide-zap w-5 h-5 fill-amber-950/20 stroke-[1.5]"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" class=""></path></svg>
</div>
</button></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/dot-border-button.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #000000;
color: #ffffff;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><div style="
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #000; /* optional, just for visibility */
"><a href="#" class="btn-wrapper" style="--dot-size: 8px; --line-weight: 1px; --line-distance: 0.8rem 1rem; --animation-speed: 0.35s; --dot-color: #fffa; --line-color: #fffa; --grid-color: #fff3; position: relative; display: inline-flex; justify-content: center; align-items: center; width: auto; height: auto; padding: var(--line-distance); background-color: rgba(0, 0, 0, 0); user-select: none">
<style>
.btn-wrapper::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
pointer-events: none;
background-color: #0000;
background-image: repeating-linear-gradient(45deg, var(--grid-color) 0 1px, transparent 2px 5px);
opacity: 0;
z-index: -1;
}
.btn-wrapper:has(.btn:hover)::after {
animation: opacity-anim calc(var(--animation-speed) * 4) ease-in-out forwards;
}
@keyframes opacity-anim {
80% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.btn-wrapper .btn {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 0.8rem 1.25rem;
background-color: #fff0;
border: 1px solid var(--grid-color);
color: #fffd;
font-family: "Inter", sans-serif;
letter-spacing: -0.01em;
font-size: 1rem;
font-weight: 600;
text-transform: capitalize;
border-radius: 6px;
cursor: pointer;
transition: transform .2s ease-in-out, letter-spacing .2s ease-in-out;
}
.btn-wrapper .btn:hover {
background-color: #25358b;
color: #fff;
transform: scale(1.05);
letter-spacing: .06em;
}
.btn-wrapper .btn:active {
background-color: #25358b;
transform: scale(.98);
letter-spacing: .02em;
}
.btn-wrapper .btn-svg {
margin-left: .5rem;
height: 24px;
stroke-width: 1;
stroke-linecap: round;
stroke-linejoin: round;
stroke: #fff4;
fill: #fff2;
transition: all .2s ease-in-out;
}
.btn-wrapper .btn:hover .btn-svg {
stroke: #fffa;
fill: #fff3;
}
.btn-wrapper .dot {
position: absolute;
width: var(--dot-size);
aspect-ratio: 1;
border-radius: 2px;
background-color: var(--dot-color);
transition: all .3s ease-in-out;
opacity: 0;
}
.btn-wrapper:has(.btn:hover) .dot.top.left {
top: 50%;
left: 20%;
animation: move-top-left var(--animation-speed) ease-in-out forwards;
}
@keyframes move-top-left {
90% {
opacity: .6;
}
100% {
top: calc(var(--dot-size) * -0.5);
left: calc(var(--dot-size) * -0.5);
opacity: 1;
}
}
.btn-wrapper:has(.btn:hover) .dot.top.right {
top: 50%;
right: 20%;
animation: move-top-right var(--animation-speed) ease-in-out forwards;
animation-delay: calc(var(--animation-speed)*.6);
}
@keyframes move-top-right {
80% {
opacity: .6;
}
100% {
top: calc(var(--dot-size) * -0.5);
right: calc(var(--dot-size) * -0.5);
opacity: 1;
}
}
.btn-wrapper:has(.btn:hover) .dot.bottom.right {
bottom: 50%;
right: 20%;
animation: move-bottom-right var(--animation-speed) ease-in-out forwards;
animation-delay: calc(var(--animation-speed)*1.2);
}
@keyframes move-bottom-right {
80% {
opacity: .6;
}
100% {
bottom: calc(var(--dot-size) * -0.5);
right: calc(var(--dot-size) * -0.5);
opacity: 1;
}
}
.btn-wrapper:has(.btn:hover) .dot.bottom.left {
bottom: 50%;
left: 20%;
animation: move-bottom-left var(--animation-speed) ease-in-out forwards;
animation-delay: calc(var(--animation-speed)*1.8);
}
@keyframes move-bottom-left {
80% {
opacity: .6;
}
100% {
bottom: calc(var(--dot-size) * -0.5);
left: calc(var(--dot-size) * -0.5);
opacity: 1;
}
}
.btn-wrapper .line {
position: absolute;
transition: all .3s ease-in-out;
}
.btn-wrapper .line.horizontal {
height: var(--line-weight);
width: 100%;
background-image: repeating-linear-gradient(90deg, #0000 0 calc(var(--line-weight)*2), var(--line-color) calc(var(--line-weight)*2) calc(var(--line-weight)*4));
}
.btn-wrapper .line.top {
top: calc(var(--line-weight)*-0.5);
transform-origin: top left;
transform: rotate(5deg) scaleX(0);
}
.btn-wrapper:has(.btn:hover) .line.top {
animation: draw-top var(--animation-speed) ease-in-out forwards;
animation-delay: calc(var(--animation-speed)*.8);
}
@keyframes draw-top {
100% {
transform: rotate(0deg) scaleX(1);
}
}
.btn-wrapper .line.bottom {
bottom: calc(var(--line-weight)*-0.5);
transform-origin: bottom right;
transform: rotate(5deg) scaleX(0);
}
.btn-wrapper:has(.btn:hover) .line.bottom {
animation: draw-bottom var(--animation-speed) ease-in-out forwards;
animation-delay: calc(var(--animation-speed)*2);
}
@keyframes draw-bottom {
100% {
transform: rotate(0deg) scaleX(1);
}
}
.btn-wrapper .line.vertical {
width: var(--line-weight);
height: 100%;
background-image: repeating-linear-gradient(0deg, #0000 0 calc(var(--line-weight)*2), var(--line-color) calc(var(--line-weight)*2) calc(var(--line-weight)*4));
}
.btn-wrapper .line.left {
left: calc(var(--line-weight)*-0.5);
transform-origin: bottom left;
transform: rotate(0deg) scaleY(0);
}
.btn-wrapper:has(.btn:hover) .line.left {
animation: draw-left var(--animation-speed) ease-in-out forwards;
animation-delay: calc(var(--animation-speed)*2.4);
}
@keyframes draw-left {
100% {
transform: rotate(0deg) scaleY(1);
}
}
.btn-wrapper .line.right {
right: calc(var(--line-weight)*-0.5);
transform-origin: top right;
transform: rotate(5deg) scaleY(0);
}
.btn-wrapper:has(.btn:hover) .line.right {
animation: draw-right var(--animation-speed) ease-in-out forwards;
animation-delay: calc(var(--animation-speed)*1.4);
}
@keyframes draw-right {
100% {
transform: rotate(0deg) scaleY(1);
}
}
</style>
<div class="line horizontal top"></div>
<div class="line vertical right"></div>
<div class="line horizontal bottom"></div>
<div class="line vertical left"></div>
<div class="dot top left"></div>
<div class="dot top right"></div>
<div class="dot bottom right"></div>
<div class="dot bottom left"></div>
<button class="btn bg-[#ffffff]">
<span class="btn-text">Start Creating</span>
<svg class="btn-svg" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M17.6744 11.4075L15.7691 17.1233C15.7072 17.309 15.5586 17.4529 15.3709 17.5087L3.69348 20.9803C3.22819 21.1186 2.79978 20.676 2.95328 20.2155L6.74467 8.84131C6.79981 8.67588 6.92419 8.54263 7.08543 8.47624L12.472 6.25822C12.696 6.166 12.9535 6.21749 13.1248 6.38876L17.5294 10.7935C17.6901 10.9542 17.7463 11.1919 17.6744 11.4075Z" class=""></path>
<path d="M3.2959 20.6016L9.65986 14.2376" class=""></path>
<path d="M17.7917 11.0557L20.6202 8.22724C21.4012 7.44619 21.4012 6.17986 20.6202 5.39881L18.4989 3.27749C17.7178 2.49645 16.4515 2.49645 15.6704 3.27749L12.842 6.10592" class=""></path>
<path d="M11.7814 12.1163C11.1956 11.5305 10.2458 11.5305 9.66004 12.1163C9.07426 12.7021 9.07426 13.6519 9.66004 14.2376C10.2458 14.8234 11.1956 14.8234 11.7814 14.2376C12.3671 13.6519 12.3671 12.7021 11.7814 12.1163Z" class=""></path>
</svg>
</button>
</a></div></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/floating-dots-cta.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #ffffff;
color: #000000;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><button type="button" class="button">
<div class="points_wrapper">
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
<i class="point"></i>
</div>
<span class="inner">
Sign Up
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
</span>
<style>
.button {
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
overflow: hidden;
transition: all 0.25s ease;
background: radial-gradient(65.28% 65.28% at 50% 100%,
rgba(34, 211, 238, 0.8) 0%,
rgba(34, 211, 238, 0) 100%),
linear-gradient(0deg, #2563eb, #2563eb);
border-radius: 0.75rem;
border: none;
outline: none;
padding: 12px 18px;
min-height: 48px;
min-width: 102px;
}
.button::before,
.button::after {
content: "";
position: absolute;
transition: all 0.5s ease-in-out;
z-index: 0;
}
.button::before {
inset: 1px;
background: linear-gradient(177.95deg,
rgba(255, 255, 255, 0.19) 0%,
rgba(255, 255, 255, 0) 100%);
border-radius: calc(0.75rem - 1px);
}
.button::after {
inset: 2px;
background: radial-gradient(65.28% 65.28% at 50% 100%,
rgba(34, 211, 238, 0.8) 0%,
rgba(34, 211, 238, 0) 100%),
linear-gradient(0deg, #2563eb, #2563eb);
border-radius: calc(0.75rem - 2px);
}
.button:active {
transform: scale(0.95);
}
.points_wrapper {
overflow: hidden;
width: 100%;
height: 100%;
pointer-events: none;
position: absolute;
z-index: 1;
}
.points_wrapper .point {
bottom: -10px;
position: absolute;
animation: floating-points infinite ease-in-out;
pointer-events: none;
width: 2px;
height: 2px;
background-color: #fff;
border-radius: 9999px;
}
@keyframes floating-points {
0% {
transform: translateY(0);
}
85% {
opacity: 0;
}
100% {
transform: translateY(-55px);
opacity: 0;
}
}
.points_wrapper .point:nth-child(1) {
left: 10%;
opacity: 1;
animation-duration: 2.35s;
animation-delay: 0.2s;
}
.points_wrapper .point:nth-child(2) {
left: 30%;
opacity: 0.7;
animation-duration: 2.5s;
animation-delay: 0.5s;
}
.points_wrapper .point:nth-child(3) {
left: 25%;
opacity: 0.8;
animation-duration: 2.2s;
animation-delay: 0.1s;
}
.points_wrapper .point:nth-child(4) {
left: 44%;
opacity: 0.6;
animation-duration: 2.05s;
}
.points_wrapper .point:nth-child(5) {
left: 50%;
opacity: 1;
animation-duration: 1.9s;
}
.points_wrapper .point:nth-child(6) {
left: 75%;
opacity: 0.5;
animation-duration: 1.5s;
animation-delay: 1.5s;
}
.points_wrapper .point:nth-child(7) {
left: 88%;
opacity: 0.9;
animation-duration: 2.2s;
animation-delay: 0.2s;
}
.points_wrapper .point:nth-child(8) {
left: 58%;
opacity: 0.8;
animation-duration: 2.25s;
animation-delay: 0.2s;
}
.points_wrapper .point:nth-child(9) {
left: 98%;
opacity: 0.6;
animation-duration: 2.6s;
animation-delay: 0.1s;
}
.points_wrapper .point:nth-child(10) {
left: 65%;
opacity: 1;
animation-duration: 2.5s;
animation-delay: 0.2s;
}
.inner {
z-index: 2;
gap: 6px;
position: relative;
width: 100%;
color: white;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 500;
line-height: 1.5;
transition: color 0.2s ease-in-out;
}
.inner svg.icon {
width: 18px;
height: 18px;
transition: transform 0.3s ease;
stroke: white;
fill: none;
}
.button:hover svg.icon {
transform: translateX(2px);
}
.button:hover svg.icon path {
animation: dash 0.8s linear forwards;
}
@keyframes dash {
0% {
stroke-dasharray: 0, 20;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 10, 10;
stroke-dashoffset: -5;
}
100% {
stroke-dasharray: 20, 0;
stroke-dashoffset: -10;
}
}
</style>
</button></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/sliding-text-cta.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #000000;
color: #ffffff;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><button class="group relative inline-flex min-w-[120px] cursor-pointer transition-all duration-[1000ms] ease-[cubic-bezier(0.15,0.83,0.66,1)] hover:-translate-y-[3px] hover:text-white shadow-[0_2.8px_2.2px_rgba(0,0,0,0.3),_0_6.7px_5.3px_rgba(0,0,0,0.35),_0_12.5px_10px_rgba(0,0,0,0.4)] overflow-hidden font-semibold text-neutral-400 tracking-tight bg-neutral-800 border-neutral-600 border rounded-full pt-[12px] pr-[20px] pb-[12px] pl-[20px] items-center justify-center">
<!-- Original text (slides down on hover) -->
<span class="relative z-10 font-medium rounded-full transition-all duration-500 ease-out group-hover:transform group-hover:translate-y-8 group-hover:opacity-0 group-hover:blur-md">Download Mac app</span>
<!-- Clone text (slides in from top on hover) -->
<span class="absolute inset-0 z-10 flex items-center justify-center transition-all duration-300 ease-in-out transform -translate-y-8 group-hover:translate-y-0 group-hover:opacity-100 group-hover:blur-none font-medium opacity-0 rounded-full blur-md">Download Mac app</span>
<!-- replaces the old ::before underline -->
<span aria-hidden="true" class="absolute bottom-0 left-1/2 h-[1px] w-[70%] -translate-x-1/2 transition-all duration-[1000ms] ease-[cubic-bezier(0.15,0.83,0.66,1)] group-hover:opacity-80 bg-gradient-to-r from-transparent via-neutral-200 to-transparent rounded-full blur-[2px]"></span>
<!-- bottom light gradient on hover -->
<span aria-hidden="true" class="absolute bottom-0 left-0 right-0 h-[100%] group-hover:opacity-60 transition-all duration-[1000ms] ease-[cubic-bezier(0.15,0.83,0.66,1)] pointer-events-none bg-gradient-to-t from-white/20 via-white/10 to-transparent rounded-full"></span>
</button></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/gradient-beam-cta.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #000000;
color: #ffffff;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><button class="group flex overflow-hidden uppercase transition-all duration-500 hover:scale-[1.02] hover:shadow-[0_0_40px_-10px_rgba(234,88,12,0.5)] focus:outline-none text-sm font-medium text-white tracking-widest font-geist rounded-full pt-5 pr-12 pb-5 pl-12 relative items-center justify-center">
<style>
@keyframes beam-spin { to { transform: rotate(360deg); } }
@keyframes dots-move {
0% { background-position: 0 0; }
100% { background-position: 24px 24px; }
}
</style>
<!-- Full Border Beam (Single Beam) -->
<div class="absolute inset-0 -z-20 rounded-full overflow-hidden p-[1px]">
<div class="absolute inset-[-100%] bg-[conic-gradient(from_0deg,transparent_0_300deg,#ea580c_360deg)]" style="animation: beam-spin 3s linear infinite;"></div>
<div class="absolute inset-[1px] rounded-full bg-black"></div>
</div>
<!-- Inner Background & Effects -->
<div class="-z-10 overflow-hidden bg-zinc-950 rounded-full absolute top-[2px] right-[2px] bottom-[2px] left-[2px]">
<!-- Light Monotone Gradient Background -->
<div class="absolute inset-0 bg-gradient-to-b from-zinc-800/60 to-transparent"></div>
<!-- Animated Dots Pattern -->
<div class="opacity-30 mix-blend-overlay absolute top-0 right-0 bottom-0 left-0" style="background-image: radial-gradient(rgba(255,255,255,0.6) 1px, transparent 1px); background-size: 12px 12px; animation: dots-move 8s linear infinite"></div>
<!-- Orange Glow on Hover -->
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-2/3 h-1/2 bg-orange-500/10 blur-2xl rounded-full pointer-events-none transition-colors duration-500 group-hover:bg-orange-500/30"></div>
</div>
<!-- Content -->
<span class="relative z-10 text-white/90 transition-colors group-hover:text-white">Start Building</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right relative z-10 ml-2 transition-transform duration-300 group-hover:translate-x-1"><path d="M5 12h14" class=""></path><path d="m12 5 7 7-7 7" class=""></path></svg>
</button></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/gradient-pill-button.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #ffffff;
color: #000000;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><button class="hover:bg-slate-50 hover:text-slate- transition-all flex text-sm font-medium text-slate-600 bg-gradient-to-b from-black/10 via-black/20 to-black/10 rounded-full pt-3 pr-6 pb-3 pl-6 shadow-[0px_0px_0px_1px_rgba(0,0,0,0.06),0px_1px_1px_-0.5px_rgba(0,0,0,0.06),0px_3px_3px_-1.5px_rgba(0,0,0,0.06),_0px_6px_6px_-3px_rgba(0,0,0,0.06),0px_12px_12px_-6px_rgba(0,0,0,0.06),0px_24px_24px_-12px_rgba(0,0,0,0.06)] gap-x-2 gap-y-2 items-center" style="box-shadow: 0 18px 35px rgba(31, 41, 55, 0.25), 0 0 0 1px rgba(209, 213, 219, 0.3); color: #e5e7eb; position: relative; --border-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.8), rgba(0, 0, 0, 0.4), rgba(255, 255, 255, 0.8)); --border-radius-before: 9999px">
<span class="text-sm font-medium text-black/60 tracking-tight">
Demo Lesson
</span><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-[16px] h-[16px]" data-lucide="arrow-right" data-icon-set="lucide" data-icon-replaced="true" style="color: rgb(229, 231, 235); width: 16px; height: 16px;"><path d="M5 12h14" class=""></path><path d="m12 5 7 7-7 7"></path></svg>
<style id="border-gradient-shared-style">
[style*="--border-gradient"]::before {
content: "";
position: absolute;
inset: 0;
padding: 1px;
border-radius: var(--border-radius-before, inherit);
-webkit-mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
background: var(--border-gradient);
pointer-events: none;
}</style>
</button></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/generate-button.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #000000;
color: #ffffff;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Tailwind (CDN build is fine for quick paste) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Optional font to match the original -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<style>
/* ---------- Core styles that Tailwind can’t express (pseudo-elements, keyframes, multi-shadows) ---------- */
.btn-wrapper {
position: relative;
display: inline-block;
}
.btn {
--border-radius: 24px;
--padding: 4px;
--transition: 0.4s;
--button-color: #101010;
--highlight-color-hue: 210deg;
/* Change this hue to recolor the glow */
user-select: none;
display: flex;
justify-content: center;
background-color: var(--button-color);
/* Complex layered shadows */
box-shadow:
inset 0px 1px 1px rgba(255, 255, 255, 0.2),
inset 0px 2px 2px rgba(255, 255, 255, 0.15),
inset 0px 4px 4px rgba(255, 255, 255, 0.1),
inset 0px 8px 8px rgba(255, 255, 255, 0.05),
inset 0px 16px 16px rgba(255, 255, 255, 0.05),
0px -1px 1px rgba(0, 0, 0, 0.02),
0px -2px 2px rgba(0, 0, 0, 0.03),
0px -4px 4px rgba(0, 0, 0, 0.05),
0px -8px 8px rgba(0, 0, 0, 0.06),
0px -16px 16px rgba(0, 0, 0, 0.08);
border: solid 1px #ffffff22;
border-radius: var(--border-radius);
cursor: pointer;
transition:
box-shadow var(--transition),
border var(--transition),
background-color var(--transition);
}
.btn::before {
content: "";
position: absolute;
top: calc(0px - var(--padding));
left: calc(0px - var(--padding));
width: calc(100% + var(--padding) * 2);
height: calc(100% + var(--padding) * 2);
border-radius: calc(var(--border-radius) + var(--padding));
pointer-events: none;
background-image: linear-gradient(0deg, #0004, #000a);
z-index: -1;
transition:
box-shadow var(--transition),
filter var(--transition);
box-shadow:
0 -8px 8px -6px #0000 inset,
0 -16px 16px -8px #00000000 inset,
1px 1px 1px #fff2,
2px 2px 2px #fff1,
-1px -1px 1px #0002,
-2px -2px 2px #0001;
}
.btn::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
background-image: linear-gradient(0deg,
#fff,
hsl(var(--highlight-color-hue), 100%, 70%),
hsla(var(--highlight-color-hue), 100%, 70%, 50%),
8%,
transparent);
background-position: 0 0;
opacity: 0;
transition: opacity var(--transition), filter var(--transition);
}
.btn-letter {
position: relative;
display: inline-block;
color: #ffffff55;
animation: letter-anim 2s ease-in-out infinite;
transition: color var(--transition), text-shadow var(--transition), opacity var(--transition);
}
@keyframes letter-anim {
50% {
text-shadow: 0 0 3px #ffffff88;
color: #fff;
}
}
.btn-svg {
flex-grow: 1;
height: 24px;
margin-right: 0.5rem;
fill: #e8e8e8;
animation: flicker 2s linear infinite;
animation-delay: 0.5s;
filter: drop-shadow(0 0 2px #ffffff99);
transition: fill var(--transition), filter var(--transition), opacity var(--transition);
}
@keyframes flicker {
50% {
opacity: 0.3;
}
}
.txt-wrapper {
position: relative;
display: flex;
align-items: center;
min-width: 6.4em;
}
.txt-1,
.txt-2 {
position: absolute;
word-spacing: -1em;
}
.txt-1 {
animation: appear-anim 1s ease-in-out forwards;
}
.txt-2 {
opacity: 0;
}
@keyframes appear-anim {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.btn:focus .txt-1,
.btn:focus-visible .txt-1 {
animation: opacity-anim 0.3s ease-in-out forwards;
animation-delay: 1s;
}
.btn:focus .txt-2,
.btn:focus-visible .txt-2 {
animation: opacity-anim 0.3s ease-in-out reverse forwards;
animation-delay: 1s;
}
@keyframes opacity-anim {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.btn:focus .btn-letter,
.btn:focus-visible .btn-letter {
animation:
focused-letter-anim 1s ease-in-out forwards,
letter-anim 1.2s ease-in-out infinite;
animation-delay: 0s, 1s;
}
@keyframes focused-letter-anim {
0%,
100% {
filter: blur(0px);
}
50% {
transform: scale(2);
filter: blur(10px) brightness(150%) drop-shadow(-36px 12px 12px hsl(var(--highlight-color-hue), 100%, 70%));
}
}
.btn:focus .btn-svg,
.btn:focus-visible .btn-svg {
animation-duration: 1.2s;
animation-delay: 0.2s;
}
.btn:focus::before,
.btn:focus-visible::before {
box-shadow:
0 -8px 12px -6px #fff3 inset,
0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 20%) inset,
1px 1px 1px #fff3,
2px 2px 2px #fff1,
-1px -1px 1px #0002,
-2px -2px 2px #0001;
}
.btn:focus::after,
.btn:focus-visible::after {
opacity: 0.6;
-webkit-mask-image: linear-gradient(0deg, #fff, transparent);
mask-image: linear-gradient(0deg, #fff, transparent);
filter: brightness(100%);
}
/* Staggered delays for each letter */
.btn-letter:nth-child(1),
.btn:focus .btn-letter:nth-child(1),
.btn:focus-visible .btn-letter:nth-child(1) {
animation-delay: 0s;
}
.btn-letter:nth-child(2),
.btn:focus .btn-letter:nth-child(2),
.btn:focus-visible .btn-letter:nth-child(2) {
animation-delay: 0.08s;
}
.btn-letter:nth-child(3),
.btn:focus .btn-letter:nth-child(3),
.btn:focus-visible .btn-letter:nth-child(3) {
animation-delay: 0.16s;
}
.btn-letter:nth-child(4),
.btn:focus .btn-letter:nth-child(4),
.btn:focus-visible .btn-letter:nth-child(4) {
animation-delay: 0.24s;
}
.btn-letter:nth-child(5),
.btn:focus .btn-letter:nth-child(5),
.btn:focus-visible .btn-letter:nth-child(5) {
animation-delay: 0.32s;
}
.btn-letter:nth-child(6),
.btn:focus .btn-letter:nth-child(6),
.btn:focus-visible .btn-letter:nth-child(6) {
animation-delay: 0.40s;
}
.btn-letter:nth-child(7),
.btn:focus .btn-letter:nth-child(7),
.btn:focus-visible .btn-letter:nth-child(7) {
animation-delay: 0.48s;
}
.btn-letter:nth-child(8),
.btn:focus .btn-letter:nth-child(8),
.btn:focus-visible .btn-letter:nth-child(8) {
animation-delay: 0.56s;
}
.btn-letter:nth-child(9),
.btn:focus .btn-letter:nth-child(9),
.btn:focus-visible .btn-letter:nth-child(9) {
animation-delay: 0.64s;
}
.btn-letter:nth-child(10),
.btn:focus .btn-letter:nth-child(10),
.btn:focus-visible .btn-letter:nth-child(10) {
animation-delay: 0.72s;
}
.btn-letter:nth-child(11),
.btn:focus .btn-letter:nth-child(11),
.btn:focus-visible .btn-letter:nth-child(11) {
animation-delay: 0.80s;
}
.btn-letter:nth-child(12),
.btn:focus .btn-letter:nth-child(12),
.btn:focus-visible .btn-letter:nth-child(12) {
animation-delay: 0.88s;
}
.btn-letter:nth-child(13),
.btn:focus .btn-letter:nth-child(13),
.btn:focus-visible .btn-letter:nth-child(13) {
animation-delay: 0.96s;
}
/* Active state */
.btn:active {
border: solid 1px hsla(var(--highlight-color-hue), 100%, 80%, 0.7);
background-color: hsla(var(--highlight-color-hue), 50%, 20%, 0.5);
}
.btn:active::before {
box-shadow:
0 -8px 12px -6px #fffa inset,
0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 0.8) inset,
1px 1px 1px #fff4,
2px 2px 2px #fff2,
-1px -1px 1px #0002,
-2px -2px 2px #0001;
}
.btn:active::after {
opacity: 1;
-webkit-mask-image: linear-gradient(0deg, #fff, transparent);
mask-image: linear-gradient(0deg, #fff, transparent);
filter: brightness(200%);
}
.btn:active .btn-letter {
text-shadow: 0 0 1px hsla(var(--highlight-color-hue), 100%, 90%, 0.9);
animation: none;
}
/* Hover state */
.btn:hover {
border: solid 1px hsla(var(--highlight-color-hue), 100%, 80%, 0.4);
}
.btn:hover::before {
box-shadow:
0 -8px 8px -6px #fffa inset,
0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 0.3) inset,
1px 1px 1px #fff2,
2px 2px 2px #fff1,
-1px -1px 1px #0002,
-2px -2px 2px #0001;
}
.btn:hover::after {
opacity: 1;
-webkit-mask-image: linear-gradient(0deg, #fff, transparent);
mask-image: linear-gradient(0deg, #fff, transparent);
}
.btn:hover .btn-svg {
fill: #fff;
filter:
drop-shadow(0 0 3px hsl(var(--highlight-color-hue), 100%, 70%)) drop-shadow(0 -4px 6px #0009);
animation: none;
}
</style>
</head>
<!-- Button -->
<body><div class="btn-wrapper">
<button class="btn px-3 py-2 md:px-4 md:py-2 focus:outline-none" type="button" aria-label="Generate" title="Generate">
<svg class="btn-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z">
</path>
</svg>
<div class="txt-wrapper">
<div class="txt-1">
<span class="btn-letter">G</span>
<span class="btn-letter">e</span>
<span class="btn-letter">n</span>
<span class="btn-letter">e</span>
<span class="btn-letter">r</span>
<span class="btn-letter">a</span>
<span class="btn-letter">t</span>
<span class="btn-letter">e</span>
</div>
<div class="txt-2">
<span class="btn-letter">G</span>
<span class="btn-letter">e</span>
<span class="btn-letter">n</span>
<span class="btn-letter">e</span>
<span class="btn-letter">r</span>
<span class="btn-letter">a</span>
<span class="btn-letter">t</span>
<span class="btn-letter">i</span>
<span class="btn-letter">n</span>
<span class="btn-letter">g</span>
</div>
</div>
</button>
</div>
</body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/glassmorphism-cta.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #000000;
color: #ffffff;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><a href="#" class="group isolate inline-flex cursor-pointer overflow-hidden transition-all duration-300 hover:scale-105 hover:shadow-[0_0_40px_8px_rgba(129,140,248,0.35)] rounded-full relative shadow-[0_8px_40px_rgba(129,140,248,0.25)]" style="--spread: 90deg; --shimmer-color: rgba(255,255,255,0.6); --radius: 9999px; --speed: 4s; --cut: 1px; --bg: rgba(255, 255, 255, 0.05);" data-element-locator="html > body:nth-of-type(1) > section:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1)">
<div class="absolute inset-0">
<div class="absolute inset-[-200%] w-[400%] h-[400%] [animation:rotate-gradient_var(--speed)_linear_infinite]">
<div class="absolute inset-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))]">
</div>
</div>
</div>
<div class="absolute rounded-full [background:var(--bg)] [inset:var(--cut)] backdrop-blur"></div>
<div class="z-10 flex gap-3 sm:w-auto overflow-hidden text-base font-medium text-white w-full pt-3 pr-4 pb-3 pl-4 relative gap-x-3 gap-y-3 items-center" style="border-radius: 9999px;" data-element-locator="html > body:nth-of-type(1) > section:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > div:nth-of-type(3)">
<div style="position: absolute; content: ' '; display: block; width: 200%; height: 200%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2), transparent); animation: borderBeamRotation 4s infinite linear; top: 50%; left: 50%; transform: translate(-50%, -50%);" class=""></div>
<div style="position: absolute; inset: 1px; background: rgba(10, 11, 20, 0.8); border-radius: 9999px; backdrop-filter: blur(8px);" class="" data-element-locator="html > body:nth-of-type(1) > section:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > div:nth-of-type(3) > div:nth-of-type(2)">
</div>
<img src="https://hoirqrkdgbmvpwutwuwj.supabase.co/storage/v1/object/public/assets/assets/3f6038cb-af1c-4483-97bc-dd58d89c36ef_320w.jpg" alt="Advisor headshot" class="ring-2 ring-white/10 z-10 w-8 h-8 object-cover rounded-full relative">
<span class="whitespace-nowrap relative z-10 font-sans">
Generate My Site
</span>
<span class="inline-flex items-center justify-center z-10 bg-white/10 w-7 h-7 rounded-full ml-1 relative" data-element-locator="html > body:nth-of-type(1) > section:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > div:nth-of-type(3) > span:nth-of-type(2)">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-lucide="wand-sparkles" class="lucide lucide-wand-sparkles lucide-arrow-right w-[24px] h-[16px]" data-icon-replaced="true" style="width: 24px; height: 16px; color: rgb(255, 255, 255);"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72" class=""></path><path d="m14 7 3 3" class=""></path><path d="M5 6v4" class=""></path><path d="M19 14v4" class=""></path><path d="M10 2v2" class=""></path><path d="M7 8H3" class=""></path><path d="M21 16h-4" class=""></path><path d="M11 3H9" class=""></path></svg>
</span>
<style>
@keyframes borderBeamRotation {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
</style>
</div>
</a></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/spinning-border-button.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #000000;
color: #ffffff;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><button class="group inline-flex overflow-hidden transition-all duration-300 hover:-translate-y-0.5 hover:shadow-[0_0_25px_rgba(255,255,255,0.1)] rounded-full pt-[1px] pr-[1px] pb-[1px] pl-[1px] relative items-center justify-center">
<!-- Spinning Border Beam (Visible on Hover) -->
<span class="absolute inset-[-100%] animate-[spin_3s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,transparent_0%,transparent_75%,#ffffff_100%)] opacity-0 transition-opacity duration-300 group-hover:opacity-100"></span>
<!-- Default Static Border -->
<span class="absolute inset-0 rounded-full bg-zinc-800 transition-opacity duration-300 group-hover:opacity-0"></span>
<!-- 3D Button Surface & Content -->
<span class="flex items-center justify-center gap-2 uppercase transition-colors duration-300 group-hover:text-white text-xs font-medium text-zinc-400 tracking-widest bg-gradient-to-b from-zinc-800 to-zinc-950 w-full h-full rounded-full pt-2.5 pr-6 pb-2.5 pl-6 relative shadow-[inset_0_1px_0_rgba(255,255,255,0.3)]">
<span class="relative z-10">Request Demo</span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="relative z-10 transition-transform duration-300 group-hover:translate-x-0.5">
<path d="M5 12h14" class=""></path>
<path d="m12 5 7 7-7 7" class=""></path>
</svg>
</span>
</button></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/neuform-isolated/sources/gradient-cta.html (html) ===== */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
height: 100%;
overflow: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #ffffff;
color: #000000;
}
.component-wrapper {
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
overflow: auto;
}
</style>
</head>
<body>
<div class="component-wrapper">
<html><head></head><body><button class="group shadow-orange-500/30 hover:shadow-orange-500/50 transition-all duration-300 overflow-hidden font-medium text-orange-900 bg-gradient-to-r from-[#FFEBB1] to-[#FFC438] rounded-xl pt-4 pr-8 pb-4 pl-8 relative shadow-lg" style="box-shadow:0 15px 33px -12px rgba(255,162,42,0.9), inset 0 4px 6.3px rgba(252,220,134,1), inset 0 -5px 6.3px rgba(255,162,38,1); border-radius:9999px">
<div class="group-hover:translate-y-0 transition-transform duration-300 bg-white/20 absolute top-0 right-0 bottom-0 left-0 translate-y-full"></div>
<span class="relative flex items-center gap-2">
Start Free Pilot
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-lucide="send" class="lucide lucide-send w-4 h-4"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z" class=""></path><path d="m21.854 2.147-10.94 10.939" class=""></path></svg>
</span>
</button></body></html>
</div>
<script>
(function() {
function checkAndCenter() {
// Use double requestAnimationFrame to ensure DOM is fully painted and layout is stable
requestAnimationFrame(function() {
requestAnimationFrame(function() {
const wrapper = document.querySelector('.component-wrapper');
if (!wrapper) return;
// Get all child elements
const children = Array.from(wrapper.children);
if (children.length === 0) return;
// Temporarily override wrapper styles to shrink-wrap for measurement
// Set inline styles to shrink-wrap (inline styles override CSS)
wrapper.style.display = 'inline-block';
wrapper.style.width = 'auto';
wrapper.style.height = 'auto';
wrapper.style.overflow = 'visible';
wrapper.style.boxSizing = 'content-box';
// Force multiple reflows to ensure styles are fully applied
void wrapper.offsetHeight;
void wrapper.offsetWidth;
void wrapper.offsetHeight;
// Measure the wrapper's content size
const contentWidth = wrapper.offsetWidth;
const contentHeight = wrapper.offsetHeight;
// Restore original styles by removing inline styles we added
wrapper.style.removeProperty('display');
wrapper.style.removeProperty('width');
wrapper.style.removeProperty('height');
wrapper.style.removeProperty('overflow');
wrapper.style.removeProperty('box-sizing');
// If content is less than 500x500, center it
if (contentWidth > 0 && contentHeight > 0 && contentWidth < 500 && contentHeight < 500) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
}
});
});
}
// Check immediately and after delays to ensure content is rendered and styled
function runChecks() {
checkAndCenter();
setTimeout(checkAndCenter, 50);
setTimeout(checkAndCenter, 150);
setTimeout(checkAndCenter, 300);
setTimeout(checkAndCenter, 500);
setTimeout(checkAndCenter, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runChecks);
} else {
runChecks();
}
// Also check when images or other resources load
window.addEventListener('load', () => {
setTimeout(checkAndCenter, 100);
setTimeout(checkAndCenter, 300);
});
// Use ResizeObserver to detect size changes
if (window.ResizeObserver) {
setTimeout(() => {
const wrapper = document.querySelector('.component-wrapper');
if (wrapper) {
const observer = new ResizeObserver(() => {
setTimeout(checkAndCenter, 50);
});
observer.observe(wrapper);
}
}, 500);
}
})();
</script>
</body>
</html>
/* ===== src/shaders/lumen-cta/lumen-cta.css (css) ===== */
.lumen-cta,
.lumen-cta *,
.lumen-cta *::before,
.lumen-cta *::after {
box-sizing: border-box;
}
.lumen-cta {
--lumen-cta-hue: 0deg;
--lumen-cta-saturation: 1;
--lumen-cta-brightness: 1;
position: relative;
display: grid;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
place-items: center;
overflow: hidden;
isolation: isolate;
background: #0b0821;
}
.lumen-cta--light {
background: #f3f0fb;
}
.lumen-cta__button {
/* Measurements are the authored ones from lumen.html: .btn / .btn-primary. */
height: 45px;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 0 30px;
border-radius: 999px;
border: 0;
outline: 1px solid rgba(255, 255, 255, 0.1);
text-decoration: none;
font-family: "Mulish", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 15.7px;
font-weight: 600;
line-height: 1;
color: #fff;
white-space: nowrap;
position: relative;
cursor: pointer;
appearance: none;
background: linear-gradient(
90deg,
#050014 0%,
#0d0029 20%,
#1e0066 40%,
#2f00ab 60%,
#5227c0 75%,
#9470d9 100%
);
box-shadow:
0 4px 22px rgba(70, 20, 190, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.14);
filter: hue-rotate(var(--lumen-cta-hue)) saturate(var(--lumen-cta-saturation))
brightness(var(--lumen-cta-brightness));
transition: filter 0.25s ease;
}
.lumen-cta__button:hover:not(:disabled) {
filter: hue-rotate(var(--lumen-cta-hue)) saturate(var(--lumen-cta-saturation))
brightness(calc(var(--lumen-cta-brightness) * 1.12));
}
.lumen-cta__button:focus-visible {
outline: 2px solid rgba(255, 255, 255, 0.85);
outline-offset: 3px;
}
.lumen-cta__button:disabled {
cursor: default;
opacity: 0.55;
}
/* The authored sibling: same pill, glass instead of gradient (.btn-ghost / .navcta). */
.lumen-cta__button--ghost {
padding: 0 33px;
background: rgba(255, 255, 255, 0.018);
outline-color: rgba(255, 255, 255, 0.2);
box-shadow: none;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
transition: background 0.25s ease, outline-color 0.25s ease;
}
.lumen-cta__button--ghost:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.09);
filter: hue-rotate(var(--lumen-cta-hue)) saturate(var(--lumen-cta-saturation))
brightness(var(--lumen-cta-brightness));
}
.lumen-cta--light .lumen-cta__button--ghost {
color: #1a1033;
background: rgba(12, 6, 34, 0.03);
outline-color: rgba(12, 6, 34, 0.16);
}
.lumen-cta--light .lumen-cta__button--ghost:hover:not(:disabled) {
background: rgba(12, 6, 34, 0.07);
}
.lumen-cta__ring {
width: 7px;
height: 7px;
border-radius: 50%;
border: 1.3px solid rgba(255, 255, 255, 0.9);
flex: 0 0 auto;
}
.lumen-cta--light .lumen-cta__button--ghost .lumen-cta__ring {
border-color: rgba(26, 16, 51, 0.75);
}
@media (prefers-reduced-motion: reduce) {
.lumen-cta__button {
transition: none;
}
}
/* ===== src/shaders/lumen-cta/sources/lumen.html (html) ===== */
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nocturne — Money that never sleeps</title>
<meta name="description" content="Nocturne — a debit card for life after dark, rendered in three.js on a still midnight sea.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 23 29'%3E%3Cpath d='M9.09 2.3A13.91 13.91 0 0 0 23 16.21' stroke='%23fff' stroke-width='4.6' fill='none'/%3E%3Cpath d='M2.3 12.79A13.91 13.91 0 0 0 16.21 26.7' stroke='%23fff' stroke-width='4.6' fill='none'/%3E%3C/svg%3E">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Mulish:wght@300;400;600&display=swap" rel="stylesheet">
<style>
:root{
--ink:#ffffff;
--ink-dim:#8ea6cc;
--ink-dimmer:#7d94b8;
--pad:32px;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;height:100%;background:#050b1a;overflow:hidden}
body{
font-family:'Mulish',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
font-weight:300;
color:var(--ink);
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
#gl{position:fixed;inset:0;width:100%;height:100%;display:block;z-index:0}
.stage{position:fixed;inset:0;z-index:2;pointer-events:none}
.stage > *{pointer-events:auto}
/* ---------------- nav ---------------- */
nav{
position:absolute;left:0;right:0;top:0;height:88px;
display:flex;align-items:center;
padding:0 var(--pad);
}
.brand{display:flex;align-items:center;gap:13px;text-decoration:none;color:var(--ink)}
.brand svg{display:block;width:23px;height:29px;overflow:visible}
.brand span{font-size:16px;font-weight:400;letter-spacing:-.005em;line-height:1}
.navpill{
position:absolute;left:50%;top:22px;transform:translateX(-50%);
height:45px;display:flex;align-items:center;gap:0;
padding:0 5px;border-radius:999px;
border:1px solid rgba(255,255,255,.20);
background:rgba(255,255,255,.05);
backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
}
.navpill a{
color:#fff;text-decoration:none;font-size:15.7px;font-weight:600;
padding:0 16.3px;line-height:1;white-space:nowrap;transition:opacity .25s ease;opacity:.94;
}
.navpill a:hover{opacity:1}
.navcta{
position:absolute;right:var(--pad);top:19px;
height:47px;display:inline-flex;align-items:center;gap:9px;
padding:0 21px;border-radius:999px;
border:1px solid rgba(255,255,255,.24);
background:rgba(255,255,255,.05);
backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
color:#fff;text-decoration:none;font-size:15.7px;font-weight:600;white-space:nowrap;
transition:background .25s ease,border-color .25s ease;
}
.navcta:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.32)}
.ring{width:7px;height:7px;border-radius:50%;border:1.3px solid rgba(255,255,255,.9);flex:0 0 auto}
/* ---------------- hero copy ---------------- */
h1{
position:absolute;left:var(--pad);top:114px;margin:0;
font-size:58.4px;line-height:60px;font-weight:300;letter-spacing:0;
white-space:nowrap;
}
h1 .dim{color:var(--ink-dim);display:block}
.lede{
position:absolute;left:var(--pad);top:484px;margin:0;width:360px;
font-size:15.7px;line-height:22px;font-weight:600;color:#fff;letter-spacing:0;
}
.facts{
position:absolute;right:var(--pad);top:484px;margin:0;text-align:right;
font-size:15.7px;line-height:22px;font-weight:600;color:#fff;
}
.bigtag{
position:absolute;right:30px;bottom:41px;margin:0;text-align:right;
font-size:58.5px;line-height:60px;font-weight:300;letter-spacing:0;
color:#fff;white-space:nowrap;
}
.bigtag .dim{color:var(--ink-dimmer);display:block}
.actions{position:absolute;left:var(--pad);bottom:45px;display:flex;gap:16px;align-items:center}
.btn{
height:45px;display:inline-flex;align-items:center;gap:10px;
padding:0 30px;border-radius:999px;text-decoration:none;
font-size:15.7px;font-weight:600;color:#fff;white-space:nowrap;position:relative;
}
.btn-ghost{padding:0 33px}
.btn-primary{
background:linear-gradient(90deg,#050014 0%,#0d0029 20%,#1e0066 40%,#2f00ab 60%,#5227c0 75%,#9470d9 100%);
box-shadow:0 4px 22px rgba(70,20,190,.30), inset 0 1px 0 rgba(255,255,255,.14);
border:1px solid rgba(255,255,255,.10);
}
.btn-primary:hover{filter:brightness(1.12)}
.btn-ghost{
border:1px solid rgba(255,255,255,.20);
background:rgba(255,255,255,.018);
backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
}
.btn-ghost:hover{background:rgba(255,255,255,.09)}
@media (max-width:1240px){ .navpill{display:none} }
@media (max-width:1180px){
h1{font-size:46px;line-height:50px;top:100px}
.bigtag{font-size:40px;line-height:45px;bottom:132px}
.lede{top:auto;bottom:214px;width:min(330px,44vw)}
.facts{top:auto;bottom:214px}
.actions{bottom:52px}
}
@media (max-width:820px){
h1{font-size:38px;line-height:42px;top:92px}
.bigtag{font-size:32px;line-height:36px;bottom:150px}
.lede{bottom:236px;width:min(290px,52vw);font-size:14.5px;line-height:20px}
.facts{bottom:236px;font-size:14.5px;line-height:20px}
}
@media (max-width:620px){
:root{--pad:20px}
h1{font-size:31px;line-height:35px;top:80px}
.bigtag{font-size:25px;line-height:29px;bottom:160px;left:var(--pad);right:auto;text-align:left}
.lede{bottom:230px;width:min(320px,88vw);font-size:14px;line-height:20px}
.facts{display:none}
.navcta{display:none}
.actions{bottom:44px;gap:12px}
.btn{height:43px;padding:0 22px;font-size:15px}
.btn-ghost{padding:0 22px}
}
@media (max-height:560px){
h1{font-size:32px;line-height:36px;top:74px}
.bigtag{font-size:26px;line-height:30px;bottom:96px}
.lede,.facts{bottom:150px;font-size:13.5px;line-height:19px}
.actions{bottom:34px}
}
</style>
</head>
<body>
<canvas id="gl"></canvas>
<div class="stage">
<nav>
<a class="brand" href="#">
<svg viewBox="0 0 23 29" fill="none" aria-hidden="true">
<path d="M9.09 2.30 A13.91 13.91 0 0 0 23 16.21" stroke="#fff" stroke-width="4.6"/>
<path d="M2.30 12.79 A13.91 13.91 0 0 0 16.21 26.70" stroke="#fff" stroke-width="4.6"/>
</svg>
<span>Nocturne</span>
</a>
<div class="navpill">
<a href="#">Accounts</a>
<a href="#">How it works</a>
<a href="#">Rates</a>
<a href="#">Support</a>
</div>
<a class="navcta" href="#">Open an account <i class="ring"></i></a>
</nav>
<h1>Money that never<span class="dim">sleeps</span></h1>
<p class="lede">Send, save, and settle around the clock —<br>one account that simply never closes.</p>
<p class="facts">No monthly fees<br>Live in 90 seconds</p>
<p class="bigtag"><span class="dim">A debit card for</span>life after dark</p>
<div class="actions">
<a class="btn btn-primary" href="#">Open an account <i class="ring"></i></a>
<a class="btn btn-ghost" href="#">See how it works</a>
</div>
</div>
<script src="https://unpkg.com/three@0.149.0/build/three.min.js"></script>
<script>
(function(){
'use strict';
// ───────────────────────────── design constants ─────────────────────────────
// Solved from the reference: 1440x1026, horizon at y=572, world unit = card width.
var DES_W=1440, DES_H=1026;
var FOVY=35; // vertical field of view (deg)
var CAM_PITCH= 2.0768*Math.PI/180; // pitched up -> horizon lands at 55.75% of the frame
var CAM_H=0.5556; // camera height above the water plane
var CARD_W=1.0, CARD_H=1.4603, CARD_T=0.030, CARD_R=0.062;
var CARD_X=-0.0008, CARD_Y=0.7140, CARD_Z=-4.3043; // Y measured from the water plane
var ARCH_R=4.7627, ARCH_TUBE=0.045;
var ARCH_X=-0.0957, ARCH_Y=0.4587, ARCH_Z=-21.02;
var OMEGA=2*Math.PI/1.5; // master clock: 1.5 s cycle
var GYRO_A=8.812*Math.PI/180, GYRO_PH=134.43*Math.PI/180;
var GYRO_CX=-1.991*Math.PI/180, GYRO_CY=0.898*Math.PI/180;
var Q=new URLSearchParams(location.search);
var FIXED_T=Q.has('t')?parseFloat(Q.get('t')):null;
var REDUCED=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if(REDUCED&&FIXED_T===null) FIXED_T=0.62; // a settled, well-composed pose
if(Q.get('ui')==='0'){var st=document.querySelector('.stage'); if(st) st.style.visibility='hidden';}
// ───────────────────────────── renderer ─────────────────────────────
var canvas=document.getElementById('gl');
var renderer=new THREE.WebGLRenderer({canvas:canvas,antialias:true,alpha:false,powerPreference:'high-performance'});
renderer.setClearColor(0x000000,1);
var scene=new THREE.Scene();
var camera=new THREE.PerspectiveCamera(FOVY,1,0.05,4000);
camera.position.set(0,CAM_H,0);
camera.rotation.order='YXZ';
camera.rotation.set(CAM_PITCH,0,0);
var DPR=1;
var reflRT=null, sceneRT=null;
// ───────────────────────────── shared GLSL ─────────────────────────────
var COMMON=[
'precision highp float;',
'float hash11(float p){ p=fract(p*0.1031); p*=p+33.33; p*=p+p; return fract(p); }',
'float hash13(vec3 p){ p=fract(p*0.1031); p+=dot(p,p.yzx+33.33); return fract((p.x+p.y)*p.z); }',
'float vnoise(vec3 x){',
' vec3 i=floor(x), f=fract(x); f=f*f*(3.0-2.0*f);',
' float n000=hash13(i+vec3(0,0,0)), n100=hash13(i+vec3(1,0,0));',
' float n010=hash13(i+vec3(0,1,0)), n110=hash13(i+vec3(1,1,0));',
' float n001=hash13(i+vec3(0,0,1)), n101=hash13(i+vec3(1,0,1));',
' float n011=hash13(i+vec3(0,1,1)), n111=hash13(i+vec3(1,1,1));',
' return mix(mix(mix(n000,n100,f.x),mix(n010,n110,f.x),f.y),',
' mix(mix(n001,n101,f.x),mix(n011,n111,f.x),f.y),f.z);',
'}',
'float fbm(vec3 p){',
' float a=0.5,s=0.0; mat3 m=mat3(0.00,0.80,0.60,-0.80,0.36,-0.48,-0.60,-0.48,0.64);',
' for(int i=0;i<5;i++){ s+=a*vnoise(p); p=m*p*2.02; a*=0.5; }',
' return s;',
'}',
'vec3 s2l(vec3 c){ return pow(c,vec3(2.2)); }',
'vec3 l2s(vec3 c){ return pow(max(c,0.0),vec3(1.0/2.2)); }',
''].join('n');
var SKY=[
'uniform vec3 uGlowCol, uNebCol, uGlowDir; uniform vec3 uSky[7];',
'uniform float uGlowSig, uGlowI, uNebI, uStarI, uTime;',
'vec3 skyColor(vec3 rd, float stars){',
' float y=rd.y;',
' // vertical ramp measured off the reference sky (linear light, 0.05 rad steps)',
' float e=clamp(y,0.0,0.32)/0.0533333;',
' float ei=floor(e); float ef=e-ei; int ek=int(ei);',
' vec3 sa=uSky[6], sb=uSky[6];',
' for(int q=0;q<6;q++){ if(q==ek){ sa=uSky[q]; sb=uSky[q+1]; } }',
' vec3 col=mix(sa,sb,ef);',
' // soft nebula banks',
' vec3 q=rd*vec3(3.1,6.4,3.1);',
' float n=fbm(q*1.25+vec3(11.3,4.1,7.7));',
' float neb=smoothstep(0.470,0.700,n)*(0.20+0.80*clamp(y*3.6,0.0,1.0));',
' col+=uNebCol*neb*uNebI;',
' // horizon glow',
' float ca=dot(normalize(rd),uGlowDir);',
' float ang=acos(clamp(ca,-1.0,1.0));',
' float g=exp(-pow(ang/uGlowSig,2.0))+0.0026*exp(-pow(ang/(uGlowSig*3.2),2.0));',
' col+=uGlowCol*g*uGlowI;',
' // stars',
' if(stars>0.0){',
' vec3 sp=rd*340.0; vec3 ci=floor(sp);',
' float h=hash13(ci);',
' if(h>0.9055){',
' vec3 cf=fract(sp)-0.5;',
' vec3 jitter=vec3(hash11(h*13.1),hash11(h*27.7),hash11(h*41.3))-0.5;',
' float d=length(cf-jitter*0.6);',
' float b=smoothstep(0.21,0.0,d)*(0.30+0.70*hash11(h*7.3));',
' float tw=0.75+0.25*sin(uTime*2.1+h*90.0);',
' col+=vec3(0.95,0.93,1.0)*b*tw*uStarI*stars*smoothstep(-0.02,0.25,y);',
' }',
' }',
' return col;',
'}',
''].join('n');
var skyUniforms={
uSky:{value:[]},
uGlowCol:{value:new THREE.Color()},
uNebCol:{value:new THREE.Color()},
uGlowDir:{value:new THREE.Vector3(0,-0.004,-1).normalize()},
uGlowSig:{value:0.163},
uGlowI:{value:1.0},
uNebI:{value:1.0},
uStarI:{value:1.0},
uTime:{value:0}
};
function sRGB(hex){ var c=new THREE.Color(hex); return new THREE.Color(Math.pow(c.r,2.2),Math.pow(c.g,2.2),Math.pow(c.b,2.2)); }
// elevation ramp, linear RGB, sampled every 0.0533 of rd.y (0 -> 0.32)
// elevation ramp, linear RGB, every 0.0533 of rd.y (0 -> 0.32): midnight blue
var SKY_RAMP=[[0.0593,0.0798,0.3176],[0.0503,0.0745,0.2981],[0.0327,0.0497,0.2421],
[0.0214,0.0350,0.1893],[0.0131,0.0253,0.1481],[0.0062,0.0142,0.1074],
[0.0026,0.0085,0.0798]];
var CFG={
glow:'#a6d2ff', neb:'#a9bcd8',
glowSig:0.136, glowI:2.20, nebI:0.08, starI:0.70
};
function applySky(){
skyUniforms.uSky.value=SKY_RAMP.map(function(c){return new THREE.Color(c[0],c[1],c[2]);});
skyUniforms.uGlowCol.value.copy(sRGB(CFG.glow));
skyUniforms.uNebCol.value.setRGB(0.44,0.56,1.0);
skyUniforms.uGlowSig.value=CFG.glowSig;
skyUniforms.uGlowI.value=CFG.glowI;
skyUniforms.uNebI.value=CFG.nebI;
skyUniforms.uStarI.value=CFG.starI;
}
applySky();
// ───────────────────────────── sky backdrop ─────────────────────────────
var camU={ uRight:{value:new THREE.Vector3()}, uUp:{value:new THREE.Vector3()},
uFwd:{value:new THREE.Vector3()}, uTanHalf:{value:0}, uAspect:{value:1} };
var skyMat=new THREE.ShaderMaterial({
uniforms:Object.assign({},skyUniforms,camU),
depthTest:false, depthWrite:false,
vertexShader:'varying vec2 vUv; void main(){ vUv=uv; gl_Position=vec4(position.xy,1.0,1.0); }',
fragmentShader:COMMON+SKY+[
'varying vec2 vUv;',
'uniform vec3 uRight,uUp,uFwd; uniform float uTanHalf,uAspect;',
'void main(){',
' vec2 p=vUv*2.0-1.0;',
' vec3 rd=normalize(uFwd + uRight*(p.x*uTanHalf*uAspect) + uUp*(p.y*uTanHalf));',
' vec3 c=skyColor(rd,1.0);',
' gl_FragColor=vec4(l2s(c),1.0);',
'}'].join('n')
});
var skyMesh=new THREE.Mesh(new THREE.PlaneGeometry(2,2),skyMat);
skyMesh.frustumCulled=false; skyMesh.renderOrder=-10;
scene.add(skyMesh);
// ───────────────────────────── card ─────────────────────────────
function roundedRectShape(w,h,r){
var s=new THREE.Shape(), x=-w/2, y=-h/2;
s.moveTo(x+r,y);
s.lineTo(x+w-r,y); s.quadraticCurveTo(x+w,y,x+w,y+r);
s.lineTo(x+w,y+h-r); s.quadraticCurveTo(x+w,y+h,x+w-r,y+h);
s.lineTo(x+r,y+h); s.quadraticCurveTo(x,y+h,x,y+h-r);
s.lineTo(x,y+r); s.quadraticCurveTo(x,y,x+r,y);
return s;
}
var cardGeo=new THREE.ExtrudeGeometry(roundedRectShape(CARD_W,CARD_H,CARD_R),
{depth:CARD_T,bevelEnabled:true,bevelThickness:0.006,bevelSize:0.006,bevelSegments:3,curveSegments:22});
cardGeo.translate(0,0,-CARD_T/2);
cardGeo.computeVertexNormals();
(function(){ // planar UVs from x,y so front + rim share the artwork frame
var pos=cardGeo.attributes.position, uvs=new Float32Array(pos.count*2);
for(var i=0;i<pos.count;i++){
uvs[i*2] =(pos.getX(i)+CARD_W/2)/CARD_W;
uvs[i*2+1]=1.0-(pos.getY(i)+CARD_H/2)/CARD_H;
}
cardGeo.setAttribute('uv',new THREE.BufferAttribute(uvs,2));
})();
// card artwork (chip / wordmark / brand arcs) drawn to a canvas
function cardMarkTexture(){
// the two brand arcs: identical 90 deg quarter circles, offset diagonally
var W=512, H=Math.round(512*CARD_H/CARD_W);
var c=document.createElement('canvas'); c.width=W; c.height=H;
var g=c.getContext('2d');
var R=W*0.1493, sw=W*0.0494;
g.strokeStyle='#ffffff'; g.lineWidth=sw; g.lineCap='butt';
[[0.6220,0.3973],[0.5491,0.5011]].forEach(function(p){
g.beginPath(); g.arc(W*p[0], H*p[1], R, Math.PI*0.5, Math.PI); g.stroke();
});
var t=new THREE.CanvasTexture(c); t.flipY=false; t.anisotropy=8; t.needsUpdate=true;
return t;
}
function cardArtTexture(){
var W=1024, H=Math.round(1024*CARD_H/CARD_W);
var c=document.createElement('canvas'); c.width=W; c.height=H;
var g=c.getContext('2d');
g.clearRect(0,0,W,H);
// ---- EMV chip: 5 fingers, solid centre band, 5 fingers ----
var cx0=W*0.766, cy0=H*0.0795, cw=W*0.118, ch=H*0.1082;
var rr=cw*0.135;
g.fillStyle='rgba(238,236,244,0.98)';
g.beginPath();
g.moveTo(cx0+rr,cy0); g.lineTo(cx0+cw-rr,cy0); g.quadraticCurveTo(cx0+cw,cy0,cx0+cw,cy0+rr);
g.lineTo(cx0+cw,cy0+ch-rr); g.quadraticCurveTo(cx0+cw,cy0+ch,cx0+cw-rr,cy0+ch);
g.lineTo(cx0+rr,cy0+ch); g.quadraticCurveTo(cx0,cy0+ch,cx0,cy0+ch-rr);
g.lineTo(cx0,cy0+rr); g.quadraticCurveTo(cx0,cy0,cx0+rr,cy0); g.closePath(); g.fill();
g.globalCompositeOperation='destination-out';
g.fillStyle='#000';
[[0.145,0.030],[0.381,0.030],[0.619,0.030],[0.855,0.030]].forEach(function(v){
g.fillRect(cx0+cw*(v[0]-v[1]/2), cy0, cw*v[1], ch*0.297);
g.fillRect(cx0+cw*(v[0]-v[1]/2), cy0+ch*0.684, cw*v[1], ch*0.316);
});
g.fillRect(cx0, cy0+ch*0.278, cw, ch*0.040);
g.fillRect(cx0, cy0+ch*0.671, cw, ch*0.027);
g.globalCompositeOperation='source-over';
// ---- wordmark ----
g.fillStyle='rgba(255,255,255,0.99)';
var fs=H*0.0579; // cap 0.0411 of the card height / Mulish cap ratio 0.71
g.font='600 '+fs+'px Mulish, system-ui, sans-serif';
g.textBaseline='alphabetic';
if('letterSpacing' in g) g.letterSpacing=(fs*0.055).toFixed(2)+'px';
var m=g.measureText('Nocturne');
g.fillText('Nocturne', W*0.120+(m.actualBoundingBoxLeft||0), H*0.9466);
if('letterSpacing' in g) g.letterSpacing='0px';
var t=new THREE.CanvasTexture(c); t.flipY=false; t.anisotropy=8; t.needsUpdate=true;
return t;
}
var PASS={uEncode:{value:1}, uReflFade:{value:15.0}, uArchFade:{value:1.5}};
var cardUniforms=Object.assign({
uArt:{value:null},
uMark:{value:null},
uCurv:{value:new THREE.Vector2(1.015,0.245)},
uLw:{value:0.075},
uLl:{value:1.60},
uLoff:{value:0.1116},
uPd:{value:new THREE.Vector3(-0.4457,0.8951,0)},
uLaxis:{value:new THREE.Vector3(0.8951,0.4457,0)},
uSpecCol:{value:new THREE.Vector3(0.70,0.86,1.00)},
uBandI:{value:0.215},
uCam:{value:new THREE.Vector3()},
uEncode:PASS.uEncode,
uReflFade:PASS.uReflFade
},skyUniforms);
var cardMat=new THREE.ShaderMaterial({
uniforms:cardUniforms, side:THREE.DoubleSide,
vertexShader:[
'varying vec2 vUv; varying vec3 vN; varying vec3 vW; varying vec3 vT; varying vec3 vB;',
'void main(){ vUv=uv; mat3 m=mat3(modelMatrix);',
' vN=normalize(m*normal); vT=normalize(m*vec3(1.0,0.0,0.0)); vB=normalize(m*vec3(0.0,1.0,0.0));',
' vec4 w=modelMatrix*vec4(position,1.0); vW=w.xyz;',
' gl_Position=projectionMatrix*viewMatrix*w; }'].join('n'),
fragmentShader:COMMON+SKY+[
'varying vec2 vUv; varying vec3 vN; varying vec3 vW; varying vec3 vT; varying vec3 vB;',
'uniform float uReflFade;',
'uniform sampler2D uArt,uMark; uniform float uBandI,uEncode;',
'uniform vec3 uCam,uLaxis,uPd,uSpecCol; uniform vec2 uCurv; uniform float uLw,uLl,uLoff;',
// base gradient of the card artwork, fitted from the reference (band removed)
'vec3 cardBase(vec2 uv){',
' float u=clamp(uv.x,0.03,0.97), v=clamp(uv.y,0.02,0.98);',
' float U2=u*u,U3=U2*u,U4=U3*u,U5=U4*u;',
' float V2=v*v,V3=V2*v,V4=V3*v,V5=V4*v;',
' float r=+0.130046-1.902770*v+9.104567*V2-18.981270*V3+19.107280*V4-7.331406*V5-0.296894*u+3.157422*u*v-8.564896*u*V2+11.468227*u*V3-5.586505*u*V4+1.612817*U2-4.463720*U2*v+3.932750*U2*V2-0.471929*U2*V3-2.368938*U3+4.350466*U3*v-2.251935*U3*V2+1.230187*U4-1.203454*U4*v-0.160995*U5;',
' float g=+0.105626-1.672649*v+9.203426*V2-21.063687*V3+21.788257*V4-8.297541*V5-0.313634*u+3.297877*u*v-11.158597*u*V2+16.146581*u*V3-8.109575*u*V4+1.229731*U2-3.564870*U2*v+3.650588*U2*V2+0.135369*U2*V3-2.066140*U3+2.916709*U3*v-2.302265*U3*V2+1.591828*U4-0.514380*U4*v-0.511339*U5;',
' float b=+0.181705-2.583078*v+12.773667*V2-28.929175*V3+32.557368*V4-13.797728*V5-0.455706*u+1.348018*u*v+2.662210*u*V2-2.091564*u*V3-0.868155*u*V4+3.226593*U2-6.385232*U2*v-3.199963*U2*V2+5.280860*U2*V3-2.188520*U3+9.604219*U3*v-4.182629*U3*V2-1.492419*U4-2.254428*U4*v+1.270163*U5;',
' vec3 c=s2l(clamp(vec3(r,g,b),0.0,1.0));',
' // the gradient was fitted off a violet card; rotate it into midnight blue',
' return vec3(c.r*0.30+c.b*0.02, c.g*0.75+c.b*0.10, c.b*1.02+c.r*0.05);',
'}',
'void main(){',
' vec2 uv=vUv;',
' vec3 col=cardBase(uv);',
' // strip light reflected off a shallow virtual bulge -> a band that slides *and* turns',
' vec3 V=normalize(uCam-vW);',
' vec2 p=vec2(uv.x-0.5,(0.5-uv.y)*1.4603);',
' vec3 Nb=normalize(vN - (vT*(p.x*uCurv.x) + vB*(p.y*uCurv.y)));',
' vec3 Rr=reflect(-V,Nb);',
' float dp=dot(Rr,uPd)-uLoff;',
' float dax=dot(Rr,uLaxis);',
' float band=exp(-pow(dp/uLw,2.0))*exp(-pow(dax/uLl,2.0));',
' col+=uSpecCol*band*uBandI;',
' float fr=pow(1.0-clamp(dot(normalize(vN),V),0.0,1.0),3.0);',
' col+=vec3(0.34,0.52,0.78)*fr*0.05;',
' // embossed brand arcs (measured as a ~1.75x lift of the base ramp)',
' float mk=texture2D(uMark,uv).a;',
' col*=mix(vec3(1.0),vec3(1.78,1.78,1.52),mk);',
' // artwork',
' vec4 art=texture2D(uArt,uv);',
' col=mix(col,s2l(art.rgb),art.a);',
' if(uEncode<0.5){ float fd=exp(-max(vW.y,0.0)*uReflFade); gl_FragColor=vec4(col*fd,fd); return; }',
' gl_FragColor=vec4(l2s(col),1.0);',
'}'].join('n')
});
var card=new THREE.Mesh(cardGeo,cardMat);
card.position.set(CARD_X,CARD_Y,CARD_Z);
card.rotation.order='XYZ';
scene.add(card);
// ───────────────────────────── arch ─────────────────────────────
var archGeo=new THREE.TorusGeometry(ARCH_R,ARCH_TUBE,40,1600);
var archMat=new THREE.ShaderMaterial({
uniforms:Object.assign({uCam:{value:new THREE.Vector3()},uBody:{value:sRGB('#22375f')},uSpec:{value:1.0},uWaterCol:{value:new THREE.Color(0.005,0.012,0.050)},uArchSig:{value:14.0},uArchI:{value:1.9},uEncode:PASS.uEncode,uReflFade:PASS.uArchFade},skyUniforms),
side:THREE.DoubleSide,
vertexShader:['varying vec3 vN; varying vec3 vW;',
'void main(){ vN=normalize(mat3(modelMatrix)*normal); vec4 w=modelMatrix*vec4(position,1.0); vW=w.xyz;',
' gl_Position=projectionMatrix*viewMatrix*w; }'].join('n'),
fragmentShader:COMMON+SKY+[
'varying vec3 vN; varying vec3 vW; uniform vec3 uCam,uBody,uWaterCol; uniform float uSpec,uEncode,uReflFade,uArchSig,uArchI;',
'void main(){',
' vec3 N=normalize(vN); vec3 V=normalize(uCam-vW);',
' vec3 R=reflect(-V,N);',
' // rays that go below the horizon see dark water, not the bright horizon band',
' vec3 env=skyColor(vec3(R.x,max(R.y,0.0),R.z),0.0);',
' env=mix(env,uWaterCol,clamp(-R.y*5.0,0.0,1.0));',
' float fr=0.03+0.97*pow(1.0-clamp(dot(N,V),0.0,1.0),3.0);',
' vec3 col=uBody*0.92+env*fr*uSpec;',
' // glossy key highlight, held at a constant offset inside the tube cross-section',
' float lobe=pow(clamp(-N.y,0.0,1.0),uArchSig);',
' col+=vec3(0.82,0.92,1.00)*lobe*uArchI;',
' if(uEncode<0.5){ float fd=exp(-max(vW.y,0.0)*uReflFade); gl_FragColor=vec4(col*fd,fd); return; }',
' gl_FragColor=vec4(l2s(col),1.0);',
'}'].join('n')
});
var arch=new THREE.Mesh(archGeo,archMat);
arch.position.set(ARCH_X,ARCH_Y,ARCH_Z);
scene.add(arch);
// ───────────────────────────── water ─────────────────────────────
var waterUniforms=Object.assign({
uRefl:{value:null},
uRes:{value:new THREE.Vector2(1,1)},
uCam:{value:new THREE.Vector3()},
uCenter:{value:new THREE.Vector3(CARD_X,0,CARD_Z)},
uDeep:{value:new THREE.Color(0.0035,0.0085,0.0520)},
uDistort:{value:0.60},
uDecay:{value:0.35},
uSmear:{value:0.006},
uFpow:{value:20.0},
uF0:{value:0.140},
uK:{value:5.0},
uSwell:{value:0.06},
uSunI:{value:3.5},
uSunSig:{value:0.09},
uSunCol:{value:new THREE.Vector3(0.62,0.82,1.00)},
uAmp:{value:0.20},
uFade:{value:0.010}
},skyUniforms);
var waterMat=new THREE.ShaderMaterial({
uniforms:waterUniforms,
vertexShader:['varying vec3 vW;',
'void main(){ vec4 w=modelMatrix*vec4(position,1.0); vW=w.xyz; gl_Position=projectionMatrix*viewMatrix*w; }'].join('n'),
fragmentShader:COMMON+SKY+[
'varying vec3 vW;',
'uniform sampler2D uRefl; uniform vec2 uRes; uniform vec3 uCam,uCenter,uDeep;',
'uniform float uDistort,uAmp,uFade,uDecay,uFpow,uF0,uSunI,uSunSig,uK,uSwell,uSmear;',
'uniform vec3 uSunCol;',
'void main(){',
' vec3 P=vW;',
' vec3 toCam=uCam-P; float dist=length(toCam); vec3 V=toCam/dist;',
' vec2 d2=P.xz-uCenter.xz; float r=length(d2);',
' vec2 dir = r>1e-4 ? d2/r : vec2(1.0,0.0);',
' // break the rings out of perfect circles',
' float ang=atan(d2.y,d2.x);',
' float wob=0.30*sin(ang*2.0+0.7)+0.20*sin(ang*3.0-1.9)+0.11*sin(ang*5.0+2.4);',
' r=r*(1.0+wob*0.11)+wob*0.16;',
' float fade=1.0/(1.0+dist*dist*uFade);',
' float t=uTime;',
' vec2 grad=vec2(0.0);',
' // concentric ripples radiating from the card',
' float A=0.050*uAmp;',
' float k=uK;',
' float dec=1.0/(1.0+r*r*uDecay*uDecay);',
' grad+=dir*(A*dec*k*cos(k*r-OMEGA_*t));',
' grad+=dir*(A*0.24*dec*(k*2.3)*cos(k*2.3*r-OMEGA_*t*2.0+1.7));',
' grad+=dir*(A*0.055*dec*(k*4.6)*cos(k*4.6*r-OMEGA_*t*3.0+4.1));',
' float cap=exp(-max(dist-1.8,0.0)*1.9);', // capillary detail dies fast or it moires
' grad+=dir*(A*0.05*dec*cap*(k*13.0)*cos(k*13.0*r-OMEGA_*t*5.0+1.3));',
' // broad swell so the sheet is never perfectly flat',
' vec3 q=vec3(P.x*0.42,t*0.30,P.z*0.42);',
' float n1=fbm(q), n2=fbm(q+vec3(3.7,0.0,1.9));',
' grad+=vec2(n1-0.5,n2-0.5)*uSwell*(0.35+0.65*dec);',
' grad*=fade;',
' vec3 N=normalize(vec3(-grad.x,1.0,-grad.y));',
' vec3 R=reflect(-V,N); R.y=abs(R.y);',
' vec3 skyR=skyColor(R,0.35);',
' vec2 uv=gl_FragCoord.xy/uRes;',
' vec2 off=vec2(N.x,N.z)*uDistort/(1.0+dist*0.22);',
' // rippled water smears a near reflection vertically -- 5 taps along the distortion',
' float sm=uSmear*(0.25+length(off)*6.0);',
' vec4 objR=vec4(0.0);',
' for(int q=-2;q<=2;q++){',
' float o=float(q)*sm;',
' objR+=texture2D(uRefl,clamp(uv+off+vec2(o*0.5,o),vec2(0.002),vec2(0.998)));',
' objR+=texture2D(uRefl,clamp(uv+off+vec2(o*1.7,-o*0.4),vec2(0.002),vec2(0.998)));',
' }',
' objR*=0.1;',
' vec3 refl=mix(skyR,objR.rgb,objR.a);',
' float ct=clamp(dot(V,N),0.0,1.0);',
' float F=uF0+(1.0-uF0)*pow(1.0-ct,uFpow);',
' vec3 col=mix(uDeep,refl,F);',
' // sun glitter: the horizon light is far brighter than the displayed sky',
' float sang=acos(clamp(dot(R,uGlowDir),-1.0,1.0));',
' col+=uSunCol*(exp(-pow(sang/uSunSig,2.0))*uSunI*(0.25+0.75*F));',
' gl_FragColor=vec4(l2s(col),1.0);',
'}'].join('n').replace(/OMEGA_/g,OMEGA.toFixed(6))
});
var water=new THREE.Mesh(new THREE.PlaneGeometry(4000,4000,1,1),waterMat);
water.rotation.x=-Math.PI/2;
water.position.set(0,0,-1200);
water.frustumCulled=false;
scene.add(water);
// ───────────────────────────── reflection camera ─────────────────────────────
var reflCam=new THREE.PerspectiveCamera(FOVY,1,0.05,4000);
reflCam.matrixAutoUpdate=false;
var MIRROR=new THREE.Matrix4().makeScale(1,-1,1);
// ───────────────────────────── resize ─────────────────────────────
var QUALITY=1.0; // adaptive pixel-ratio governor
function resize(){
var w=canvas.clientWidth||window.innerWidth, h=canvas.clientHeight||window.innerHeight;
var cap=(w*h>2200000)?1.6:2.0; // keep very large canvases affordable
DPR=Math.min(window.devicePixelRatio||1, cap)*QUALITY;
renderer.setPixelRatio(DPR);
renderer.setSize(w,h,false);
var aspect=w/h, designA=DES_W/DES_H;
camera.aspect=aspect;
// narrower than the design -> widen the frustum so the scene still fits, but cap it
// so a portrait phone does not end up with a fish-eye view of a tiny card
var adapted = aspect<designA ? 2*Math.atan(Math.tan(FOVY*Math.PI/360)*designA/aspect)*180/Math.PI : FOVY;
camera.fov = Math.min(adapted, 52);
camera.updateProjectionMatrix();
reflCam.aspect=camera.aspect; reflCam.fov=camera.fov; reflCam.updateProjectionMatrix();
var rs=Math.min(0.34, 620/Math.max(w*DPR,1)); // deliberately soft: ripples destroy detail anyway
var rw=Math.max(2,Math.round(w*DPR*rs)), rh=Math.max(2,Math.round(h*DPR*rs));
if(reflRT) reflRT.dispose();
reflRT=new THREE.WebGLRenderTarget(rw,rh,{minFilter:THREE.LinearFilter,magFilter:THREE.LinearFilter,format:THREE.RGBAFormat,type:THREE.HalfFloatType});
waterUniforms.uRefl.value=reflRT.texture;
waterUniforms.uRes.value.set(w*DPR,h*DPR);
camU.uTanHalf.value=Math.tan(camera.fov*Math.PI/360);
camU.uAspect.value=camera.aspect;
}
window.addEventListener('resize',resize);
// ───────────────────────────── loop ─────────────────────────────
cardUniforms.uArt.value=cardArtTexture();
cardUniforms.uMark.value=cardMarkTexture();
if(document.fonts&&document.fonts.ready){document.fonts.ready.then(function(){cardUniforms.uArt.value=cardArtTexture();});}
var _r=new THREE.Vector3(),_u=new THREE.Vector3(),_f=new THREE.Vector3();
function setCamBasis(cam){
cam.updateMatrixWorld();
var m=cam.matrixWorld.elements;
_r.set(m[0],m[1],m[2]); _u.set(m[4],m[5],m[6]); _f.set(-m[8],-m[9],-m[10]);
camU.uRight.value.copy(_r); camU.uUp.value.copy(_u); camU.uFwd.value.copy(_f);
camU.uTanHalf.value=Math.tan(cam.fov*Math.PI/360);
camU.uAspect.value=cam.aspect;
}
var clock=new THREE.Clock();
var T=0, slow=0, fast=0, visible=true;
document.addEventListener('visibilitychange',function(){
visible=!document.hidden;
if(visible){ clock.getDelta(); requestAnimationFrame(frame); }
});
function frame(){
if(!visible) return;
var dt=clock.getDelta();
var t = FIXED_T!==null ? FIXED_T : (T+=Math.min(dt,0.05));
// governor: two seconds of >26 ms frames drops the pixel ratio one notch
if(FIXED_T===null&&dt>0){
if(dt>0.026){ slow++; fast=0; } else { fast++; if(fast>240){ slow=0; fast=0; } }
if(slow>90&&QUALITY>0.55){ QUALITY=Math.max(0.55,QUALITY-0.25); slow=0; resize(); }
}
skyUniforms.uTime.value=t;
// card gyration
var th=GYRO_PH-OMEGA*t;
// the pose was solved in a +Z-forward frame; three.js looks down -Z, so both angles flip
card.rotation.x=-(GYRO_CX+GYRO_A*Math.cos(th));
card.rotation.y=-(GYRO_CY+GYRO_A*Math.sin(th));
card.position.y=CARD_Y+0.0059*Math.sin(OMEGA*t+2.44);
card.position.x=CARD_X+0.0035*Math.sin(OMEGA*t+0.51);
// specular band sweep (measured: 1.5 s sinusoid across the card face)
camera.updateMatrixWorld();
cardUniforms.uCam.value.setFromMatrixPosition(camera.matrixWorld);
archMat.uniforms.uCam.value.copy(cardUniforms.uCam.value);
waterUniforms.uCam.value.copy(cardUniforms.uCam.value);
// ---- reflection pass ----
reflCam.matrixWorld.multiplyMatrices(MIRROR,camera.matrixWorld);
reflCam.matrixWorldInverse.copy(reflCam.matrixWorld).invert();
reflCam.projectionMatrix.copy(camera.projectionMatrix);
reflCam.projectionMatrixInverse.copy(camera.projectionMatrixInverse);
skyMesh.visible=false; water.visible=false; PASS.uEncode.value=0;
renderer.setRenderTarget(reflRT);
renderer.setClearColor(0x000000,0);
renderer.clear(true,true,false);
renderer.render(scene,reflCam);
skyMesh.visible=true; water.visible=true; PASS.uEncode.value=1;
// ---- main pass ----
setCamBasis(camera);
renderer.setRenderTarget(null);
renderer.setClearColor(0x000000,1);
renderer.render(scene,camera);
if(visible) requestAnimationFrame(frame);
}
resize();
requestAnimationFrame(frame);
window.__probe=function(){
var w=renderer.domElement.clientWidth, h=renderer.domElement.clientHeight;
camera.updateMatrixWorld(); card.updateMatrixWorld();
function px(v){ var p=v.clone().project(camera); return [(p.x*0.5+0.5)*w, (0.5-p.y*0.5)*h]; }
var hw=CARD_W/2, hh=CARD_H/2, out={};
out.cardTL=px(card.localToWorld(new THREE.Vector3(-hw, hh,0)));
out.cardTR=px(card.localToWorld(new THREE.Vector3( hw, hh,0)));
out.cardBR=px(card.localToWorld(new THREE.Vector3( hw,-hh,0)));
out.cardBL=px(card.localToWorld(new THREE.Vector3(-hw,-hh,0)));
out.horizon=px(new THREE.Vector3(0,0,-1e6))[1];
out.archTop=px(new THREE.Vector3(ARCH_X,ARCH_Y+ARCH_R,ARCH_Z));
out.archL=px(new THREE.Vector3(ARCH_X-ARCH_R,ARCH_Y,ARCH_Z));
out.archR=px(new THREE.Vector3(ARCH_X+ARCH_R,ARCH_Y,ARCH_Z));
out.rot=[card.rotation.x*180/Math.PI, card.rotation.y*180/Math.PI];
out.fov=camera.fov; out.size=[w,h];
return out;
};
window.__lumen={THREE:THREE,scene:scene,camera:camera,card:card,arch:arch,water:water,CFG:CFG,applySky:applySky,
uCard:cardUniforms,uWater:waterUniforms,uSky:skyUniforms,
setT:function(v){FIXED_T=v;}};
})();
</script>
</body>
</html>
