merge(icerik/sezon-celiskisi): [Bilal] content(bant): sezon-dışı bant metni — B alternatifi
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,20 @@
|
|||||||
import { startTransition, useEffect, useState } from "react";
|
import { startTransition, useEffect, useState } from "react";
|
||||||
import NumberFlow, { NumberFlowGroup } from "@number-flow/react";
|
import NumberFlow, { NumberFlowGroup } from "@number-flow/react";
|
||||||
import { CalendarDays, Clock3 } from "lucide-react";
|
import { CalendarDays, Clock3 } from "lucide-react";
|
||||||
|
import {
|
||||||
const TERCIH_BASLANGICI = new Date("2026-07-29T00:00:00+03:00").getTime();
|
BANT_ARIA,
|
||||||
const TERCIH_BITISI = new Date("2026-08-10T23:59:59+03:00").getTime();
|
BANT_METNI,
|
||||||
|
SEZON_TAKVIMI,
|
||||||
|
sezonAsamasi,
|
||||||
|
sezonHedefi,
|
||||||
|
type SezonAraligi,
|
||||||
|
type SezonAsamasi,
|
||||||
|
} from "@/lib/sezon";
|
||||||
|
import { trTarih } from "@/lib/tarih";
|
||||||
|
import { TERCIH_YILI, VERI_ARALIGI } from "@/lib/veri-yillari";
|
||||||
|
|
||||||
type GeriSayim = {
|
type GeriSayim = {
|
||||||
asama: "baslamadi" | "devam-ediyor" | "sona-erdi";
|
asama: SezonAsamasi;
|
||||||
gun: number;
|
gun: number;
|
||||||
saat: number;
|
saat: number;
|
||||||
dakika: number;
|
dakika: number;
|
||||||
@@ -16,16 +24,9 @@ type GeriSayim = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function geriSayimiHesapla(simdi: number): GeriSayim {
|
function geriSayimiHesapla(simdi: number): GeriSayim {
|
||||||
const asama =
|
const asama = sezonAsamasi(simdi);
|
||||||
simdi < TERCIH_BASLANGICI
|
const hedef = sezonHedefi(asama);
|
||||||
? "baslamadi"
|
const kalan = hedef === null ? 0 : Math.max(0, hedef - simdi);
|
||||||
: simdi <= TERCIH_BITISI
|
|
||||||
? "devam-ediyor"
|
|
||||||
: "sona-erdi";
|
|
||||||
|
|
||||||
const hedef =
|
|
||||||
asama === "baslamadi" ? TERCIH_BASLANGICI : TERCIH_BITISI;
|
|
||||||
const kalan = Math.max(0, hedef - simdi);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
asama,
|
asama,
|
||||||
@@ -36,6 +37,27 @@ function geriSayimiHesapla(simdi: number): GeriSayim {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "29 Temmuz – 10 Ağustos 2026": aralık aynı yıl içindeyse yıl yalnızca sonda
|
||||||
|
* yazılır. `trTarih` bilinçli olarak `Date`/`toLocale*` kullanmaz; sunucu ile
|
||||||
|
* tarayıcı saat dilimi farklı olsa da aynı metni üretir.
|
||||||
|
*/
|
||||||
|
function araligiBicimlendir(takvim: SezonAraligi) {
|
||||||
|
const baslangicIso = takvim.baslangic.slice(0, 10);
|
||||||
|
const bitisIso = takvim.bitis.slice(0, 10);
|
||||||
|
const yil = bitisIso.slice(0, 4);
|
||||||
|
const baslangic = trTarih(baslangicIso);
|
||||||
|
|
||||||
|
return {
|
||||||
|
baslangicIso,
|
||||||
|
bitisIso,
|
||||||
|
baslangicMetni: baslangicIso.startsWith(yil)
|
||||||
|
? baslangic.replace(` ${yil}`, "")
|
||||||
|
: baslangic,
|
||||||
|
bitisMetni: trTarih(bitisIso),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const BIRIMLER = [
|
const BIRIMLER = [
|
||||||
{ anahtar: "gun", etiket: "gün" },
|
{ anahtar: "gun", etiket: "gün" },
|
||||||
{ anahtar: "saat", etiket: "sa" },
|
{ anahtar: "saat", etiket: "sa" },
|
||||||
@@ -57,36 +79,50 @@ export function SiteTopBanner() {
|
|||||||
return () => window.clearInterval(zamanlayici);
|
return () => window.clearInterval(zamanlayici);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const asamaMetni =
|
// Takvim açıklanmamışsa aşama zamandan bağımsızdır; mount öncesinde de
|
||||||
geriSayim?.asama === "baslamadi"
|
// bilinir, böylece boş sayaç bir an görünüp kaybolmaz. Takvim varsa sunucu
|
||||||
? "Başlamasına"
|
// çıktısı eskisi gibi "devam-ediyor" varsayar, ilk tikte gerçeğine döner.
|
||||||
: geriSayim?.asama === "sona-erdi"
|
const takvim = SEZON_TAKVIMI[TERCIH_YILI] ?? null;
|
||||||
? "Tercih dönemi sona erdi"
|
const asama: SezonAsamasi =
|
||||||
: "Sona ermesine";
|
geriSayim?.asama ?? (takvim ? "devam-ediyor" : "takvim-belirsiz");
|
||||||
|
const hedef = sezonHedefi(asama);
|
||||||
|
const aralik = takvim ? araligiBicimlendir(takvim) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
aria-label="2026 YKS tercih dönemi"
|
aria-label={BANT_ARIA}
|
||||||
className="relative z-50 border-b border-primary-foreground/15 bg-primary text-primary-foreground print:hidden"
|
className="relative z-50 border-b border-primary-foreground/15 bg-primary text-primary-foreground print:hidden"
|
||||||
>
|
>
|
||||||
<div className="mx-auto flex min-h-12 max-w-6xl flex-wrap items-center justify-center gap-x-5 gap-y-1.5 px-4 py-2 sm:justify-between">
|
<div className="mx-auto flex min-h-12 max-w-6xl flex-wrap items-center justify-center gap-x-5 gap-y-1.5 px-4 py-2 sm:justify-between">
|
||||||
<div className="flex min-w-0 items-center gap-2.5">
|
<div className="flex min-w-0 items-center gap-2.5">
|
||||||
<CalendarDays className="size-4 shrink-0 opacity-80" aria-hidden />
|
<CalendarDays className="size-4 shrink-0 opacity-80" aria-hidden />
|
||||||
<p className="truncate text-sm font-semibold tracking-[-0.01em]">
|
<p className="truncate text-sm font-semibold tracking-[-0.01em]">
|
||||||
<span className="hidden sm:inline">2026 YKS tercih dönemi · </span>
|
<span className="hidden sm:inline">{BANT_ARIA} · </span>
|
||||||
<time dateTime="2026-07-29">29 Temmuz</time>
|
{aralik ? (
|
||||||
<span aria-hidden> – </span>
|
<>
|
||||||
<time dateTime="2026-08-10">10 Ağustos 2026</time>
|
<time dateTime={aralik.baslangicIso}>
|
||||||
|
{aralik.baslangicMetni}
|
||||||
|
</time>
|
||||||
|
<span aria-hidden> – </span>
|
||||||
|
<time dateTime={aralik.bitisIso}>{aralik.bitisMetni}</time>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
// Takvim yok: uydurma tarih yerine elimizdeki veriyi söyle.
|
||||||
|
<span>{VERI_ARALIGI} verisi</span>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2.5" role="timer">
|
<div
|
||||||
|
className="flex items-center gap-2.5"
|
||||||
|
role={hedef !== null ? "timer" : undefined}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-1.5 text-xs font-medium opacity-85">
|
<div className="flex items-center gap-1.5 text-xs font-medium opacity-85">
|
||||||
<Clock3 className="size-3.5" aria-hidden />
|
<Clock3 className="size-3.5" aria-hidden />
|
||||||
<span>{asamaMetni}</span>
|
<span>{BANT_METNI[asama]}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{geriSayim?.asama !== "sona-erdi" ? (
|
{hedef !== null ? (
|
||||||
<dl
|
<dl
|
||||||
className="flex items-baseline gap-1 font-mono text-xs tabular-nums"
|
className="flex items-baseline gap-1 font-mono text-xs tabular-nums"
|
||||||
aria-label={
|
aria-label={
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export function HeroFocusButton() {
|
|||||||
className="mt-8 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
className="mt-8 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||||
>
|
>
|
||||||
Ücretsiz dene
|
Ücretsiz dene
|
||||||
|
<ArrowRight className="size-4" aria-hidden />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -41,8 +42,7 @@ export function KapanisCta() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className="mt-4 max-w-xl text-lg leading-relaxed text-white/90">
|
<p className="mt-4 max-w-xl text-lg leading-relaxed text-white/90">
|
||||||
Sıralaman kayıtlı — yapay zekâ danışmanın veriyle konuşsun. Tercih
|
Sıralaman kayıtlı — yapay zekâ danışmanın veriyle konuşsun.
|
||||||
dönemi bitmeden yerini al.
|
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
@@ -61,8 +61,7 @@ export function KapanisCta() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className="mt-4 max-w-xl text-lg leading-relaxed text-white/90">
|
<p className="mt-4 max-w-xl text-lg leading-relaxed text-white/90">
|
||||||
Sıralamanı gir, yapay zekâ danışmanın veriyle konuşsun. Tercih dönemi
|
Sıralamanı gir, yapay zekâ danışmanın veriyle konuşsun.
|
||||||
bitmeden yerini al.
|
|
||||||
</p>
|
</p>
|
||||||
<HeroFocusButton />
|
<HeroFocusButton />
|
||||||
</>
|
</>
|
||||||
|
|||||||
86
src/lib/sezon.ts
Normal file
86
src/lib/sezon.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Tercih sezonu takvimi — TEK KAYNAK. 2027 takvimi açıklanınca buraya yazılır;
|
||||||
|
* başka hiçbir yer değişmez. Saf: yalnız `veri-yillari` sabitlerini okur, yan
|
||||||
|
* etkisi yok — client, server ve betikler aynı dosyayı kullanabilir.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BASLIK_YILI, TERCIH_YILI } from "@/lib/veri-yillari";
|
||||||
|
|
||||||
|
/** Bir sezonun ÖSYM takvimi. ISO 8601, Türkiye saati (+03:00) ofsetiyle. */
|
||||||
|
export type SezonAraligi = {
|
||||||
|
readonly baslangic: string;
|
||||||
|
readonly bitis: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yıl → tercih dönemi aralığı. `null` = takvim henüz açıklanmadı; tarih
|
||||||
|
* UYDURULMAZ, sezon "takvim-belirsiz" davranışına düşer.
|
||||||
|
*/
|
||||||
|
export const SEZON_TAKVIMI: Readonly<Record<number, SezonAraligi | null>> = {
|
||||||
|
2026: {
|
||||||
|
baslangic: "2026-07-29T00:00:00+03:00",
|
||||||
|
bitis: "2026-08-10T23:59:59+03:00",
|
||||||
|
},
|
||||||
|
// ÖSYM 2027 tercih takvimini açıklamadı (22 Eyl 2026 itibarıyla).
|
||||||
|
2027: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SezonAsamasi =
|
||||||
|
| "takvim-belirsiz"
|
||||||
|
| "baslamadi"
|
||||||
|
| "devam-ediyor"
|
||||||
|
| "sona-erdi";
|
||||||
|
|
||||||
|
/** Verilen anda sezonun hangi aşamada olduğu. */
|
||||||
|
export function sezonAsamasi(
|
||||||
|
simdi: number,
|
||||||
|
yil: number = TERCIH_YILI,
|
||||||
|
): SezonAsamasi {
|
||||||
|
const takvim = SEZON_TAKVIMI[yil] ?? null;
|
||||||
|
|
||||||
|
if (!takvim) {
|
||||||
|
return "takvim-belirsiz";
|
||||||
|
}
|
||||||
|
|
||||||
|
const baslangic = new Date(takvim.baslangic).getTime();
|
||||||
|
const bitis = new Date(takvim.bitis).getTime();
|
||||||
|
|
||||||
|
return simdi < baslangic
|
||||||
|
? "baslamadi"
|
||||||
|
: simdi <= bitis
|
||||||
|
? "devam-ediyor"
|
||||||
|
: "sona-erdi";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geri sayımın hedef zaman damgası: dönem başlamadıysa başlangıç, sürüyorsa
|
||||||
|
* bitiş. `null` dönen aşamalarda (takvim belirsiz ya da dönem kapandı) sayılacak
|
||||||
|
* bir şey yoktur — çağıran geri sayımı hiç render etmemelidir.
|
||||||
|
*/
|
||||||
|
export function sezonHedefi(
|
||||||
|
asama: SezonAsamasi,
|
||||||
|
yil: number = TERCIH_YILI,
|
||||||
|
): number | null {
|
||||||
|
const takvim = SEZON_TAKVIMI[yil] ?? null;
|
||||||
|
|
||||||
|
if (!takvim) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asama === "baslamadi") {
|
||||||
|
return new Date(takvim.baslangic).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
return asama === "devam-ediyor" ? new Date(takvim.bitis).getTime() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Üst bandın saat ikonunun yanındaki metin — aşama başına tek kaynak. */
|
||||||
|
export const BANT_METNI: Record<SezonAsamasi, string> = {
|
||||||
|
"takvim-belirsiz": `${BASLIK_YILI} yerleştirme verileri yüklendi — ${TERCIH_YILI} tercih takvimi açıklanınca geri sayım burada başlar`,
|
||||||
|
baslamadi: "Başlamasına",
|
||||||
|
"devam-ediyor": "Sona ermesine",
|
||||||
|
"sona-erdi": `${TERCIH_YILI} tercihleri tamamlandı`,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Üst bandın `aria-label`'ı. */
|
||||||
|
export const BANT_ARIA = `${TERCIH_YILI} YKS tercih dönemi`;
|
||||||
Reference in New Issue
Block a user