"use client";
import * as React from "react";
import { GlassPanel, type Painter } from "@/components/ui/refractive-glass-panel";
/* Демо: живой полосатый фон и стеклянная карточка поверх. Полосы выбраны
не для красоты — на ровной заливке преломление не видно, а вертикальные
линии показывают его сразу: у кромки они сжимаются в узкую полосу. */
const BASE = 100; /* опорная ширина шейпа: её и масштабируем через scaleX */
/* тень под белым текстом: на горячем фоне без неё буквы разъедает светом */
const INK = "0 1px 3px rgba(0,0,0,.55), 0 0 1px rgba(0,0,0,.4)";
const GLOW: Array<[number, string]> = [
[0, "#ff8f4e"],
[0.2, "#f4573c"],
[0.36, "#d13340"],
[0.52, "#8d1a25"],
[0.68, "#350810"],
[0.85, "#0d0305"],
[1, "#050305"],
];
/* Фон собран отдельными слоями, а не фоном контейнера, поэтому автоматика
его не увидит: растрировать чужую вёрстку она не умеет. Отдаём фон стеклу
сами — кладём в paintRef функцию, которая рисует ту же картину по той же
раскладке, и полосы под стеклом продолжают наружные кадр в кадр. */
function StripedBackdrop({
count = 34,
paintRef,
}: {
count?: number;
paintRef: React.MutableRefObject<Painter | null>;
}) {
const wrapRef = React.useRef<HTMLDivElement | null>(null);
const barsRef = React.useRef<Array<HTMLSpanElement | null>>([]);
React.useEffect(() => {
const wrap = wrapRef.current;
if (!wrap) return;
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* сегменты чередуются: полоса — просвет — полоса — ... */
const seg = Array.from({ length: count * 2 }, () => ({
base: 0.6 + Math.random(),
amp: 0.25 + Math.random() * 0.45,
speed: (2 * Math.PI) / (16 + Math.random() * 18), // период 16–34 с
phase: Math.random() * Math.PI * 2,
}));
let width = wrap.clientWidth;
const ro = new ResizeObserver(() => {
width = wrap.clientWidth;
});
ro.observe(wrap);
/* пары «начало — конец» каждой полосы: по ним рисует и стекло */
const edges: number[] = [];
paintRef.current = (ctx, sw, sh) => {
ctx.save();
ctx.translate(sw * 0.5, sh * 0.1);
ctx.scale(1, (sh * 0.56) / (sw * 0.4));
const g = ctx.createRadialGradient(0, 0, 0, 0, 0, sw * 0.4);
GLOW.forEach(([at, color]) => g.addColorStop(at, color));
ctx.fillStyle = g;
ctx.fillRect(-sw, -sh * 2, sw * 2, sh * 4);
ctx.restore();
ctx.fillStyle = "rgba(0,0,0,.92)";
for (let n = 0; n < edges.length; n += 2) {
if (edges[n] === undefined) continue;
ctx.fillRect((edges[n] / width) * sw, 0, ((edges[n + 1] - edges[n]) / width) * sw, sh);
}
};
let raf = 0;
const frame = (now: number) => {
const t = now / 1000;
const w: number[] = [];
let total = 0;
for (const s of seg) {
const v = s.base * (1 + s.amp * Math.sin(t * s.speed + s.phase));
w.push(v);
total += v;
}
/* сумма сегментов каждый кадр нормируется в ширину контейнера:
соседи поджимаются ровно на столько, на сколько вырос сосед */
const k = width / total;
let x = 0;
w.forEach((v, n) => {
const px = v * k;
if ((n & 1) === 0) {
const bar = barsRef.current[n >> 1];
if (bar) bar.style.transform = `translateX(${x}px) scaleX(${px / BASE})`;
edges[n] = x;
edges[n + 1] = x + px;
}
x += px; /* нечётный сегмент — просвет, шейпа под ним нет */
});
if (!reduce) raf = requestAnimationFrame(frame);
};
raf = requestAnimationFrame(frame);
return () => {
cancelAnimationFrame(raf);
ro.disconnect();
paintRef.current = null;
};
}, [count, paintRef]);
return (
<>
<div
aria-hidden="true"
className="absolute inset-0"
style={{
background:
"radial-gradient(40% 56% at 50% 10%, #ff8f4e 0%, #f4573c 20%, #d13340 36%, #8d1a25 52%, #350810 68%, #0d0305 85%, #050305 100%)",
}}
/>
<div
ref={wrapRef}
aria-hidden="true"
className="absolute inset-0"
style={{ mixBlendMode: "multiply", opacity: 0.95, contain: "strict" }}
>
{Array.from({ length: count }, (_, i) => (
<span
key={i}
ref={(el) => {
barsRef.current[i] = el;
}}
className="absolute bottom-0 left-0 top-0"
style={{
width: BASE,
background: "rgba(0,0,0,.92)",
transformOrigin: "0 0",
transform: "translateX(-9999px)", // до первого кадра шейп за экраном
backfaceVisibility: "hidden",
}}
/>
))}
</div>
</>
);
}
export default function GlassPanelDemo() {
const paintRef = React.useRef<Painter | null>(null);
const sceneRef = React.useRef<HTMLDivElement | null>(null);
/* стабильная ссылка: иначе эффект стекла пересобирался бы каждый рендер */
const paint = React.useCallback<Painter>((ctx, w, h) => {
paintRef.current?.(ctx, w, h);
}, []);
return (
<div
ref={sceneRef}
className="relative flex min-h-[560px] w-full items-center justify-center overflow-hidden bg-[#050305] p-8"
>
<StripedBackdrop paintRef={paintRef} />
<GlassPanel
scene={sceneRef}
paint={paint}
/* фиксируем подсветку: автозамер целится в среднюю яркость, а здесь
под плашкой горячее пятно, и белый текст на нём разъедает светом */
brightness={1.25}
className="w-full max-w-[360px] rounded-[28px] p-[26px] text-center shadow-[0_24px_48px_rgba(0,0,0,.34)]"
style={{ borderRadius: 28 }}
>
<p className="mb-2.5 text-[11.5px] uppercase tracking-[0.2em] text-white" style={{ textShadow: INK }}>
Round-the-clock support
</p>
<p
className="mx-auto mb-4 max-w-[15.5rem] text-[13px] leading-relaxed text-white/90"
style={{ textShadow: INK }}
>
A dedicated manager and a night shift — the account never idles.
</p>
<ul
className="mb-5 flex flex-wrap items-center justify-center gap-x-3.5 gap-y-1 text-[11px] text-white/75"
style={{ textShadow: INK }}
>
<li>Production</li>
<li>Traffic</li>
<li>Sales</li>
</ul>
<div className="inline-flex items-center rounded-full bg-white/[.13] p-[5px] shadow-[inset_0_0_0_1px_rgba(255,255,255,.2)]">
<span className="px-3 text-[11px] text-white/90">no commission</span>
<button
type="button"
className="rounded-full bg-[#150c0f] px-[18px] py-2.5 text-[11px] uppercase tracking-[0.12em] text-white transition hover:bg-gradient-to-r hover:from-[#ea364a] hover:to-[#f2894f]"
>
Get started
</button>
</div>
</GlassPanel>
</div>
);
}