feat: update app.db and BACKLOG.md with new decisions and adjustments
All checks were successful
Deploy / deploy (push) Successful in 7m45s
All checks were successful
Deploy / deploy (push) Successful in 7m45s
- Updated the app.db file to reflect recent changes in the application state. - Modified BACKLOG.md to include new entries regarding the removal of certain wizard steps, adjustments to priority chips, and the introduction of special quota conditions for candidates. - Added details on the decision to link priority chips to data sources and clarified the implications for user experience and data handling.
This commit is contained in:
@@ -4,14 +4,19 @@ import { useEffect, useRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// public/kolay-tercih-mark.svg'nin inline, animasyonlu karşılığı.
|
||||
// İki varyant:
|
||||
// Üç varyant:
|
||||
//
|
||||
// - "rota" (varsayılan): her kare sıradakinin pozisyonuna süzülür (mavi →
|
||||
// şeftalinin yeri, şeftali → turuncunun, turuncu → mavinin), üç adımda
|
||||
// herkes başlangıç formuna döner. Uçuş sırasında kare hafifçe küçülüp
|
||||
// solar, varışta toparlanır. Site header'daki logo hover'da bunu oynatır.
|
||||
// - "zincir": aynı rota, ama kareler aynı anda değil sırayla yürür — bir kare
|
||||
// hedefindeki karenin üzerine konarken alttaki yola çıkar, üçüncü kare boş
|
||||
// kalan yere oturur ve zincir baştan başlar. Danışman avatarı yalnız
|
||||
// danışman veriye bakarken (araç çalışırken) bunu oynatır.
|
||||
// - "nabiz": kareler yerlerinden ayrılmaz; 120° faz farkıyla dalga gibi
|
||||
// küçülüp büyür (danışman panel başlığının sakin, sürekli hâli).
|
||||
// küçülüp büyür (danışman panel başlığının sakin, sürekli hâli; danışman
|
||||
// avatarı da cevap beklenirken böyle atar).
|
||||
//
|
||||
// Performans notu: kareler SVG <rect> değil HTML <span>, animasyon da Web
|
||||
// Animations API ile yalnızca `translate` / `scale` / `opacity` üzerinden
|
||||
@@ -49,6 +54,26 @@ const ROTA_NABIZ_ZAMANLARI = [
|
||||
const ROTA_NABIZ_SCALE = [1, 0.84, 1, 1, 0.84, 1, 1, 0.84, 1, 1];
|
||||
const ROTA_NABIZ_OPACITY = [1, 0.6, 1, 1, 0.6, 1, 1, 0.6, 1, 1];
|
||||
|
||||
// Zincir: iki kalkış arası 450 ms, tek yolculuk 600 ms. Sıradaki kare, gelen
|
||||
// kare yolun ~%94'ündeyken (üzerine konmuşken) kalkar; son 150 ms'de biri
|
||||
// inerken diğeri hızlanır, akış hiç durmaz. Her kare turda 3 yolculuk yapar.
|
||||
const ZINCIR_ADIM = 450;
|
||||
const ZINCIR_YOL = 600;
|
||||
const ZINCIR_SURE = ZINCIR_ADIM * 9;
|
||||
const ZINCIR_YOL_PAYI = ZINCIR_YOL / ZINCIR_SURE;
|
||||
const ZINCIR_KONUM_ZAMANLARI = [
|
||||
...[0, 1, 2].flatMap((etap) => [etap / 3, etap / 3 + ZINCIR_YOL_PAYI]),
|
||||
1,
|
||||
];
|
||||
const ZINCIR_NABIZ_ZAMANLARI = [
|
||||
...[0, 1, 2].flatMap((etap) => [
|
||||
etap / 3,
|
||||
etap / 3 + ZINCIR_YOL_PAYI / 2,
|
||||
etap / 3 + ZINCIR_YOL_PAYI,
|
||||
]),
|
||||
1,
|
||||
];
|
||||
|
||||
// Yerinde nabız: kesintisiz tam-sinüs (dinlenme yok), delay'ler döngüyü üçe
|
||||
// böler — biri büyürken sıradaki küçülür, hiçbir kare beklemez.
|
||||
const NABIZ_SURE = 1800;
|
||||
@@ -60,6 +85,10 @@ const YOL = "cubic-bezier(0.65, 0, 0.35, 1)";
|
||||
// Hover bitince yerine oturma (easeOutQuint): hızlı toparlanır, sarsmadan durur.
|
||||
const DONUS = "cubic-bezier(0.22, 1, 0.36, 1)";
|
||||
const DONUS_SURE = 500;
|
||||
// `oynat` kapanınca yerine oturma: hover'daki kadar aceleci değil — duran kare
|
||||
// yumuşak kalkar, uzun ve yumuşak iner.
|
||||
const YERLES = "cubic-bezier(0.4, 0, 0.2, 1)";
|
||||
const YERLES_SURE = 600;
|
||||
|
||||
/** Karenin `translate` değeri: yüzdeler karenin kendi boyutuna göredir, bu
|
||||
* yüzden işaret hangi boyutta çizilirse çizilsin aynı oran korunur. */
|
||||
@@ -76,10 +105,12 @@ function rota(baslangic: number, boyut: number): string[] {
|
||||
return [p0, p1, p1, p2, p2, p0, p0];
|
||||
}
|
||||
|
||||
type Varyant = "rota" | "zincir" | "nabiz";
|
||||
|
||||
function donguyuBaslat(
|
||||
el: HTMLElement,
|
||||
i: number,
|
||||
varyant: "rota" | "nabiz",
|
||||
varyant: Varyant,
|
||||
): Animation[] {
|
||||
const k = KARELER[i];
|
||||
if (varyant === "nabiz") {
|
||||
@@ -94,9 +125,10 @@ function donguyuBaslat(
|
||||
),
|
||||
];
|
||||
}
|
||||
const zincir = varyant === "zincir";
|
||||
const secenek = {
|
||||
duration: ROTA_SURE,
|
||||
delay: i * ROTA_GECIKME_ADIMI,
|
||||
duration: zincir ? ZINCIR_SURE : ROTA_SURE,
|
||||
delay: i * (zincir ? ZINCIR_ADIM : ROTA_GECIKME_ADIMI),
|
||||
iterations: Infinity,
|
||||
};
|
||||
// Konum ve nabız ayrı animasyonlar: konum etabı tek parça eğriyle akar,
|
||||
@@ -105,7 +137,7 @@ function donguyuBaslat(
|
||||
el.animate(
|
||||
{
|
||||
translate: rota(k.baslangic, k.boyut),
|
||||
offset: KONUM_ZAMANLARI,
|
||||
offset: zincir ? ZINCIR_KONUM_ZAMANLARI : KONUM_ZAMANLARI,
|
||||
easing: YOL,
|
||||
},
|
||||
secenek,
|
||||
@@ -113,8 +145,10 @@ function donguyuBaslat(
|
||||
el.animate(
|
||||
{
|
||||
scale: ROTA_NABIZ_SCALE.map(String),
|
||||
opacity: ROTA_NABIZ_OPACITY,
|
||||
offset: ROTA_NABIZ_ZAMANLARI,
|
||||
// Zincirde kare solmaz: başka bir karenin üzerine konar, yarı saydam
|
||||
// olursa alttaki renk içinden görünür.
|
||||
...(zincir ? {} : { opacity: ROTA_NABIZ_OPACITY }),
|
||||
offset: zincir ? ZINCIR_NABIZ_ZAMANLARI : ROTA_NABIZ_ZAMANLARI,
|
||||
easing: SINE,
|
||||
},
|
||||
secenek,
|
||||
@@ -126,22 +160,35 @@ function donguyuBaslat(
|
||||
* Animasyonlu KolayTercih işareti. Varsayılanda koreografi sürekli döner;
|
||||
* `sadeceHover` ile yalnızca işaretin üzerine gelindiğinde oynar (ör. site
|
||||
* header'daki logo linki) ve hover bitince kareler yumuşakça yerine oturur.
|
||||
* `oynat` ile döngü dışarıdan açılıp kapatılır, `varyant` da çalışırken
|
||||
* değişebilir (ör. danışman avatarı: cevap beklenirken nabız, veriye bakarken
|
||||
* zincir); her geçişte kareler önce yumuşakça yerine oturur, yeni döngü
|
||||
* yerinden başlar.
|
||||
*/
|
||||
export function AnimasyonluMark({
|
||||
className,
|
||||
sadeceHover = false,
|
||||
oynat = true,
|
||||
varyant = "rota",
|
||||
renk = "marka",
|
||||
}: {
|
||||
className?: string;
|
||||
sadeceHover?: boolean;
|
||||
varyant?: "rota" | "nabiz";
|
||||
/** `sadeceHover` yokken döngü açık mı. `false` iken işaret durağandır. */
|
||||
oynat?: boolean;
|
||||
varyant?: Varyant;
|
||||
/** "marka": logodaki üç renk; "turuncu": tüm kareler sitenin CTA turuncusu
|
||||
* (orange-500) — ör. danışman panel başlığı. */
|
||||
renk?: "marka" | "turuncu";
|
||||
}) {
|
||||
const kokRef = useRef<HTMLSpanElement>(null);
|
||||
const kontrolRef = useRef<{
|
||||
ayarla: (hedef: Varyant | null, cevik?: boolean) => void;
|
||||
} | null>(null);
|
||||
|
||||
// Animasyonların sahibi: bileşen yaşadıkça bir kez kurulur. Prop değişimi
|
||||
// bunu söküp kurmaz (kareler sıçrardı); aşağıdaki efekt `ayarla` ile hedefi
|
||||
// bildirir.
|
||||
useEffect(() => {
|
||||
const kok = kokRef.current;
|
||||
if (!kok || typeof kok.animate !== "function") return;
|
||||
@@ -150,32 +197,25 @@ export function AnimasyonluMark({
|
||||
|
||||
let dongu: Animation[] = [];
|
||||
let donus: Animation[] = [];
|
||||
let istekli = false;
|
||||
/** Şu an dönen döngü ve istenen döngü (`null`: durağan). */
|
||||
let aktif: Varyant | null = null;
|
||||
let hedef: Varyant | null = null;
|
||||
// Döngü bir mikro görev sonra başlar; o arada efekt sökülmüşse (React
|
||||
// geliştirme modunda her efekti kur-sök-kur yapar) başlamamalı — yoksa
|
||||
// kimsenin durduramadığı sahipsiz bir döngü kalır.
|
||||
let sokuldu = false;
|
||||
|
||||
const baslat = () => {
|
||||
if (istekli) return;
|
||||
istekli = true;
|
||||
if (azHareket.matches) return;
|
||||
// Kareler hâlâ yerine dönüyorsa (hızlı çık-gir) dönüşün bitmesi
|
||||
// beklenir; döngü hep yerinden başlar, sıçrama olmaz.
|
||||
Promise.all(donus.map((a) => a.finished))
|
||||
.then(() => {
|
||||
if (!istekli || dongu.length > 0) return;
|
||||
dongu = kareler.flatMap((el, i) => donguyuBaslat(el, i, varyant));
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const durdur = () => {
|
||||
istekli = false;
|
||||
if (dongu.length === 0) return;
|
||||
// Kareler o an neredeyse oradan yerlerine süzülür.
|
||||
/** Döngüyü keser; kareler o an neredeyse oradan yerlerine süzülür. */
|
||||
const yerles = (cevik: boolean) => {
|
||||
const anlik = kareler.map((el) => {
|
||||
const cs = getComputedStyle(el);
|
||||
return { translate: cs.translate, scale: cs.scale, opacity: cs.opacity };
|
||||
});
|
||||
// Nabızda kareler zaten yerindedir, yalnız boyut toparlanır: kısa sürer.
|
||||
const sure = aktif === "nabiz" ? YERLES_SURE / 2 : YERLES_SURE;
|
||||
dongu.forEach((a) => a.cancel());
|
||||
dongu = [];
|
||||
aktif = null;
|
||||
donus = kareler.map((el, i) =>
|
||||
el.animate(
|
||||
[
|
||||
@@ -186,38 +226,73 @@ export function AnimasyonluMark({
|
||||
opacity: 1,
|
||||
},
|
||||
],
|
||||
{ duration: DONUS_SURE, easing: DONUS },
|
||||
cevik
|
||||
? { duration: DONUS_SURE, easing: DONUS }
|
||||
: { duration: sure, easing: YERLES },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
if (!sadeceHover) {
|
||||
baslat();
|
||||
return () => {
|
||||
dongu.forEach((a) => a.cancel());
|
||||
};
|
||||
}
|
||||
const ayarla = (yeni: Varyant | null, cevik = false) => {
|
||||
if (yeni === hedef) return;
|
||||
hedef = yeni;
|
||||
if (aktif !== null) yerles(cevik);
|
||||
if (yeni === null || azHareket.matches) return;
|
||||
// Kareler hâlâ yerine dönüyorsa dönüşün bitmesi beklenir; döngü hep
|
||||
// yerinden başlar, sıçrama olmaz.
|
||||
Promise.all(donus.map((a) => a.finished))
|
||||
.then(() => {
|
||||
if (sokuldu || hedef !== yeni || aktif !== null) return;
|
||||
dongu = kareler.flatMap((el, i) => donguyuBaslat(el, i, yeni));
|
||||
aktif = yeni;
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const kontrol = { ayarla };
|
||||
kontrolRef.current = kontrol;
|
||||
return () => {
|
||||
sokuldu = true;
|
||||
if (kontrolRef.current === kontrol) kontrolRef.current = null;
|
||||
[...dongu, ...donus].forEach((a) => a.cancel());
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Üstteki efektten sonra koşar (aynı bileşende efektler sırayla çalışır),
|
||||
// o yüzden kontrolRef burada doludur.
|
||||
useEffect(() => {
|
||||
const kok = kokRef.current;
|
||||
if (!kok) return;
|
||||
if (!sadeceHover) {
|
||||
kontrolRef.current?.ayarla(oynat ? varyant : null);
|
||||
return;
|
||||
}
|
||||
kontrolRef.current?.ayarla(null, true);
|
||||
// Hover hedefi işaretin kendisi değil, en yakın `group` ebeveyni (site
|
||||
// header'da logo + "KolayTercih" yazısını saran Link): yazının üzerine
|
||||
// gelindiğinde de animasyon başlar. `group`'suz kullanımda köke düşer.
|
||||
const hedef = kok.closest(".group") ?? kok;
|
||||
hedef.addEventListener("mouseenter", baslat);
|
||||
hedef.addEventListener("mouseleave", durdur);
|
||||
const hoverHedefi = kok.closest(".group") ?? kok;
|
||||
const gir = () => kontrolRef.current?.ayarla(varyant, true);
|
||||
const cik = () => kontrolRef.current?.ayarla(null, true);
|
||||
hoverHedefi.addEventListener("mouseenter", gir);
|
||||
hoverHedefi.addEventListener("mouseleave", cik);
|
||||
return () => {
|
||||
hedef.removeEventListener("mouseenter", baslat);
|
||||
hedef.removeEventListener("mouseleave", durdur);
|
||||
[...dongu, ...donus].forEach((a) => a.cancel());
|
||||
hoverHedefi.removeEventListener("mouseenter", gir);
|
||||
hoverHedefi.removeEventListener("mouseleave", cik);
|
||||
};
|
||||
}, [sadeceHover, varyant]);
|
||||
}, [oynat, sadeceHover, varyant]);
|
||||
|
||||
return (
|
||||
<span
|
||||
ref={kokRef}
|
||||
aria-hidden="true"
|
||||
className={cn("relative block shrink-0", className)}
|
||||
className={cn(
|
||||
"relative block shrink-0",
|
||||
varyant !== "rota" && "isolate",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{KARELER.map((k) => (
|
||||
{KARELER.map((k, i) => (
|
||||
<span
|
||||
key={k.renk}
|
||||
className={cn(
|
||||
@@ -230,6 +305,11 @@ export function AnimasyonluMark({
|
||||
borderRadius: `${(13 / k.boyut) * 100}%`,
|
||||
backgroundColor: renk === "marka" ? k.renk : undefined,
|
||||
translate: yer(k.baslangic, k.boyut),
|
||||
// Zincirde yürüyen kare hep hedefindekinin üstüne konar: sırası
|
||||
// önce gelen üstte durur (mavi > şeftali > turuncu). Avatar nabız
|
||||
// ile zincir arasında gidip geldiği için sıra nabızda da aynıdır
|
||||
// (orada kareler üst üste gelmez); header'daki rota'ya dokunulmaz.
|
||||
zIndex: varyant !== "rota" ? KARELER.length - i : undefined,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user