feat(ui): Arc bileşenleri, landing bölümleri ve bildirim yığını
Sonner/vaul yerine kendi bildirim yığını ve alt sayfa; çip grubu, kart destesi, kaydırma eylemi, yazı dönüşümü, bölme tutamacı; landing'e program duvarı, süzülen haplar ve veri akışı; yaylar tek dosyada (lib/hareket.ts). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -470,6 +470,111 @@ export function PixelSira({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Piksel sahne (DESIGN.md §4.31): hero'daki dot-matrix Türkiye'nin dilini
|
||||
* küçük resimlere taşır. Harita gibi satır satır bir ızgaradan çizilir:
|
||||
* `#` doku pikseli, `-` silik piksel, `o` turuncu vurgu (hero'daki şehirler
|
||||
* gibi nabız atar), başka her karakter boş. Doku pikselleri imleç ısı
|
||||
* fırçasına katılır. Resim kartın söylediğini gösterir (süs değil); anlamı
|
||||
* yanındaki başlık taşır, bu yüzden aria-hidden.
|
||||
*/
|
||||
export function PixelSahne({
|
||||
izgara,
|
||||
ton = "doku",
|
||||
yaricap = 26,
|
||||
className,
|
||||
}: {
|
||||
izgara: readonly string[];
|
||||
/** "doku": hero haritasının silik mavisi. "dolgun": aynı doku koyu
|
||||
* tonda (0,64–1) — rakam gibi okunması gereken şekiller için. */
|
||||
ton?: "doku" | "dolgun";
|
||||
/** Isı fırçasının yarıçapı (viewBox birimi; hücre 10) */
|
||||
yaricap?: number;
|
||||
className?: string;
|
||||
}) {
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
usePixelHeat(svgRef, { radius: yaricap });
|
||||
const cols = Math.max(...izgara.map((satir) => satir.length));
|
||||
|
||||
const pixels = useMemo(() => {
|
||||
const opaklik = (x: number, y: number) =>
|
||||
ton === "dolgun" ? 0.4 + opacityFor(x, y) * 2 : opacityFor(x, y);
|
||||
const sonuc: ReactNode[] = [];
|
||||
let vurgu = 0;
|
||||
izgara.forEach((satir, y) => {
|
||||
for (let x = 0; x < satir.length; x++) {
|
||||
const c = satir[x];
|
||||
if (c === "o") {
|
||||
sonuc.push(
|
||||
pixelRect(x, y, {
|
||||
key: `${x}-${y}`,
|
||||
accent: true,
|
||||
accentDelay: (vurgu++ % 8) * 0.45,
|
||||
}),
|
||||
);
|
||||
} else if (c === "#" || c === "-") {
|
||||
const op =
|
||||
Math.round(opaklik(x, y) * (c === "-" ? 0.5 : 1) * 100) / 100;
|
||||
sonuc.push(
|
||||
<rect
|
||||
key={`${x}-${y}`}
|
||||
x={x * CELL + INSET}
|
||||
y={y * CELL + INSET}
|
||||
width={DOT}
|
||||
height={DOT}
|
||||
rx={2}
|
||||
data-px=""
|
||||
data-cx={x * CELL + CELL / 2}
|
||||
data-cy={y * CELL + CELL / 2}
|
||||
data-op={op}
|
||||
fill="currentColor"
|
||||
fillOpacity={op}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
return sonuc;
|
||||
}, [izgara, ton]);
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={svgRef}
|
||||
viewBox={`0 0 ${cols * CELL} ${izgara.length * CELL}`}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className={cn("h-auto w-full text-primary", className)}
|
||||
>
|
||||
{pixels}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// 5×7 dot-matrix rakamlar. Yeni bir rakam gerekirse ızgarası buraya eklenir.
|
||||
const RAKAMLAR = {
|
||||
1: ["..#..", ".##..", "..#..", "..#..", "..#..", "..#..", ".###."],
|
||||
2: [".###.", "#...#", "....#", "...#.", "..#..", ".#...", "#####"],
|
||||
3: [".###.", "#...#", "....#", "..##.", "....#", "#...#", ".###."],
|
||||
} as const;
|
||||
|
||||
/** Hero'nun piksel diliyle yazılmış iri rakam (ana sayfa adım numaraları). */
|
||||
export function PixelRakam({
|
||||
rakam,
|
||||
className,
|
||||
}: {
|
||||
rakam: keyof typeof RAKAMLAR;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<PixelSahne
|
||||
izgara={RAKAMLAR[rakam]}
|
||||
ton="dolgun"
|
||||
yaricap={22}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/** Bölüm başlıklarının üstünde duran piksel imzalı eyebrow rozeti. */
|
||||
export function SectionEyebrow({
|
||||
children,
|
||||
|
||||
Reference in New Issue
Block a user