Piksel harita ve logo animasyonu iyileştirmeleri
All checks were successful
Deploy / deploy (push) Successful in 7m52s
All checks were successful
Deploy / deploy (push) Successful in 7m52s
Şehir/Türkiye piksel haritası görünüme girince oynar; animasyonlu-mark, console-asr, use-pixel-heat ve cta-sira-form güncellendi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -195,40 +195,61 @@ function PikselSvg({
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
usePixelHeat(svgRef, { radius: ISI_YARICAPI });
|
||||
|
||||
// Yüklenince gecikmeyle odağa yakınlaş. viewBox her karede yazılır; ısı
|
||||
// fırçası viewBox'ı canlı okuduğu için imleç hep doğru kareyi ısıtır.
|
||||
// Harita görünüme girince (yüklenince değil) gecikmeyle odağa yakınlaş;
|
||||
// il karelerinin sırayla belirmesi de o ana kadar bekler (globals.css:
|
||||
// data-gorundu yokken kt-piksel-belir duraklatılır). Ekranın altında kalan
|
||||
// harita, kullanıcı oraya inmeden animasyonunu harcamasın. viewBox her
|
||||
// karede yazılır; ısı fırçası viewBox'ı canlı okuduğu için imleç hep doğru
|
||||
// kareyi ısıtır.
|
||||
const { sinir, zoom } = harita;
|
||||
const hizaLg = odakHiza?.lg ?? ORTA;
|
||||
const hizaDar = odakHiza?.dar ?? ORTA;
|
||||
useEffect(() => {
|
||||
const svg = svgRef.current;
|
||||
if (!svg || !sinir) return;
|
||||
const rect = svg.getBoundingClientRect();
|
||||
const oran = rect.width && rect.height ? rect.width / rect.height : VIEW_W / VIEW_H;
|
||||
const genis = window.matchMedia("(min-width: 1024px)").matches;
|
||||
const odak = odakKutusu(sinir, oran, zoom, genis ? hizaLg : hizaDar);
|
||||
if (!svg) return;
|
||||
const yaz = (k: Kutu) => svg.setAttribute("viewBox", kutuMetni(k));
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||||
yaz(odak);
|
||||
return () => yaz(TAM_KUTU);
|
||||
}
|
||||
const bas = TAM_KUTU;
|
||||
const kontrol = animate(0, 1, {
|
||||
type: "spring",
|
||||
bounce: 0,
|
||||
delay: ODAK_GECIKME,
|
||||
duration: ODAK_SURE,
|
||||
onUpdate: (t) =>
|
||||
yaz([
|
||||
bas[0] + (odak[0] - bas[0]) * t,
|
||||
bas[1] + (odak[1] - bas[1]) * t,
|
||||
bas[2] + (odak[2] - bas[2]) * t,
|
||||
bas[3] + (odak[3] - bas[3]) * t,
|
||||
]),
|
||||
});
|
||||
let kontrol: { stop: () => void } | null = null;
|
||||
const basla = () => {
|
||||
svg.setAttribute("data-gorundu", "");
|
||||
if (!sinir) return;
|
||||
const rect = svg.getBoundingClientRect();
|
||||
const oran = rect.width && rect.height ? rect.width / rect.height : VIEW_W / VIEW_H;
|
||||
const genis = window.matchMedia("(min-width: 1024px)").matches;
|
||||
const odak = odakKutusu(sinir, oran, zoom, genis ? hizaLg : hizaDar);
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||||
yaz(odak);
|
||||
return;
|
||||
}
|
||||
const bas = TAM_KUTU;
|
||||
kontrol = animate(0, 1, {
|
||||
type: "spring",
|
||||
bounce: 0,
|
||||
delay: ODAK_GECIKME,
|
||||
duration: ODAK_SURE,
|
||||
onUpdate: (t) =>
|
||||
yaz([
|
||||
bas[0] + (odak[0] - bas[0]) * t,
|
||||
bas[1] + (odak[1] - bas[1]) * t,
|
||||
bas[2] + (odak[2] - bas[2]) * t,
|
||||
bas[3] + (odak[3] - bas[3]) * t,
|
||||
]),
|
||||
});
|
||||
};
|
||||
// Haritanın dörtte biri, ekranın alt %15'lik şeridinin üstüne çıkınca
|
||||
const gozlemci = new IntersectionObserver(
|
||||
(girdiler) => {
|
||||
if (!girdiler.some((g) => g.isIntersecting)) return;
|
||||
gozlemci.disconnect();
|
||||
basla();
|
||||
},
|
||||
{ threshold: 0.25, rootMargin: "0px 0px -15% 0px" },
|
||||
);
|
||||
gozlemci.observe(svg);
|
||||
return () => {
|
||||
kontrol.stop();
|
||||
yaz(TAM_KUTU);
|
||||
gozlemci.disconnect();
|
||||
kontrol?.stop();
|
||||
svg.removeAttribute("data-gorundu");
|
||||
if (sinir) yaz(TAM_KUTU);
|
||||
};
|
||||
}, [sinir, zoom, hizaLg, hizaDar]);
|
||||
|
||||
@@ -237,7 +258,7 @@ function PikselSvg({
|
||||
ref={svgRef}
|
||||
viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}
|
||||
preserveAspectRatio={doldur ? "xMidYMid slice" : "xMidYMid meet"}
|
||||
className={cn(doldur ? "h-full w-full" : "h-auto w-full", "text-primary", className)}
|
||||
className={cn(doldur ? "h-full w-full" : "h-auto w-full", "kt-piksel-harita text-primary", className)}
|
||||
role="img"
|
||||
aria-label={etiket}
|
||||
// Statik, deterministik markup — her render'da yeniden kurulmaz
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
* imleç uzaklaşınca yavaşça kendi mavi tonuna soğur (asimetrik zamanlama).
|
||||
* Fırça mantığı usePixelHeat hook'unda ortaklaştırıldı; DOM'a
|
||||
* querySelectorAll ile bağlandığı için statik markup'la sorunsuz çalışır.
|
||||
*
|
||||
* İmleç gelmese de rastgele pikseller tek tük, seyrek aralıklarla turuncuya
|
||||
* ısınıp söner (usePixelHeat `kirpisma`); "hareketi azalt"ta kapalıdır.
|
||||
*/
|
||||
|
||||
import { useRef } from "react";
|
||||
@@ -139,7 +142,7 @@ const MAP_MARKUP = (() => {
|
||||
);
|
||||
}
|
||||
}
|
||||
// Şehir pikselleri: turuncu, hafif nabız; hover fırçasına katılmazlar
|
||||
// Şehir pikselleri: turuncu, hafif nabız; hover fırçasına katılmazlar.
|
||||
CITIES.forEach((city, i) => {
|
||||
parts.push(
|
||||
`<rect ${rectAttrs(city.x, city.y)} class="animate-pulse fill-orange-500/70 motion-reduce:animate-none" style="animation-duration:3.6s;animation-delay:${i * 0.45}s"/>`,
|
||||
@@ -150,7 +153,7 @@ const MAP_MARKUP = (() => {
|
||||
|
||||
export function TurkeyPixelMap({ className }: { className?: string }) {
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
usePixelHeat(svgRef);
|
||||
usePixelHeat(svgRef, { kirpisma: true });
|
||||
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -442,15 +442,21 @@ export function CtaSiraForm({
|
||||
href={sonucHref()}
|
||||
aria-label="Sıralamana uygun programları gör"
|
||||
title="Sıralamana uygun programları gör"
|
||||
// Gölge yok (§6: derinlik gölgeyle verilmez), hover'da renk
|
||||
// değişmez (iri dolu yüzeyde orange-600 kırmızımsı okunuyor —
|
||||
// Bilal, 24 Eyl 2026); tepki: ok kayar, basınca küçülür.
|
||||
className="group flex size-12 shrink-0 items-center justify-center rounded-full bg-orange-500 text-white transition-transform duration-200 after:absolute after:inset-0 hover:scale-[1.04] active:scale-[0.95] sm:size-14 lg:order-3"
|
||||
// Gerilmiş link (after:inset-0) bandın tamamını kaplar. Ölçek
|
||||
// linkin KENDİSİNE verilmez: transform, ::after için kapsayan
|
||||
// blok olur, alan 48 px'lik daireye çöker, imleç linkten düşer
|
||||
// ve hover açılıp kapanarak titrer. Büyüyen içteki dairedir.
|
||||
className="group flex shrink-0 lg:order-3 after:absolute after:inset-0"
|
||||
>
|
||||
<ArrowRight
|
||||
className="size-6 transition-transform duration-200 group-hover:translate-x-0.5 sm:size-7"
|
||||
aria-hidden
|
||||
/>
|
||||
{/* Gölge yok (§6: derinlik gölgeyle verilmez), hover'da renk
|
||||
değişmez (iri dolu yüzeyde orange-600 kırmızımsı okunuyor —
|
||||
Bilal, 24 Eyl 2026); tepki: ok kayar, basınca küçülür. */}
|
||||
<span className="flex size-12 items-center justify-center rounded-full bg-orange-500 text-white transition-transform duration-200 group-hover:scale-[1.04] group-active:scale-[0.95] sm:size-14">
|
||||
<ArrowRight
|
||||
className="size-6 transition-transform duration-200 group-hover:translate-x-0.5 sm:size-7"
|
||||
aria-hidden
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user