Update padding in Parallax component for improved layout consistency
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
bilalgursen
2026-07-30 03:54:31 +03:00
parent dc6d894a37
commit 2c542e7756
22 changed files with 274 additions and 133 deletions

Binary file not shown.

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { getSession } from "@/lib/session"; import { getSession } from "@/lib/session";
import { PixelCorner } from "@/components/pixel-decor";
import { GirisForm } from "./giris-form"; import { GirisForm } from "./giris-form";
import { GirisBaslik } from "./giris-baslik"; import { GirisBaslik } from "./giris-baslik";
@@ -27,24 +28,35 @@ export default async function GirisPage({
return ( return (
<div className="flex min-h-screen flex-col bg-slate-50 text-slate-900"> <div className="flex min-h-screen flex-col bg-slate-50 text-slate-900">
<main className="mx-auto flex w-full max-w-md flex-1 flex-col items-stretch justify-center px-4 py-16"> <main className="mx-auto flex w-full max-w-md flex-1 flex-col items-stretch justify-center px-4 py-16">
<div className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm"> <div className="relative">
<GirisBaslik funnel={funnel} /> <div className="relative rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
<p className="mt-2 text-sm text-slate-600"> <GirisBaslik funnel={funnel} />
{funnel ? ( <p className="mt-2 text-sm text-slate-600">
<> {funnel ? (
Seçimlerin hazır giriş yap,{" "} <>
<span className="font-semibold">5 deneme kredinle</span> listen Seçimlerin hazır giriş yap,{" "}
hemen üretilsin. <span className="font-semibold">5 deneme kredinle</span> listen
</> hemen üretilsin.
) : ( </>
<> ) : (
Kayıt olduğunda{" "} <>
<span className="font-semibold">5 deneme kredisi</span> hesabına Kayıt olduğunda{" "}
tanımlanır AI danışmana ücretsiz soru sorabilirsin. <span className="font-semibold">5 deneme kredisi</span>{" "}
</> hesabına tanımlanır AI danışmana ücretsiz soru sorabilirsin.
)} </>
</p> )}
<GirisForm callbackURL={callbackURL} /> </p>
<GirisForm callbackURL={callbackURL} />
</div>
<div
aria-hidden
className="pointer-events-none absolute inset-0 z-10 text-primary"
>
<PixelCorner className="absolute -right-4 -top-4 size-9 [&_rect]:[fill-opacity:0.65]" />
<PixelCorner className="absolute -bottom-4 -right-4 size-9 rotate-90 [&_rect]:[fill-opacity:0.65]" />
<PixelCorner className="absolute -bottom-4 -left-4 size-9 rotate-180 [&_rect]:[fill-opacity:0.65]" />
<PixelCorner className="absolute -left-4 -top-4 size-9 -rotate-90 [&_rect]:[fill-opacity:0.65]" />
</div>
</div> </div>
<p className="mt-6 text-center text-xs leading-relaxed text-slate-500"> <p className="mt-6 text-center text-xs leading-relaxed text-slate-500">
Giriş yaparak verilerinin tercih önerileri üretmek amacıyla{" "} Giriş yaparak verilerinin tercih önerileri üretmek amacıyla{" "}

View File

@@ -1,7 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { Parallax } from "@/components/parallax"; import { Parallax } from "@/components/parallax";
import { ScrollFlow } from "@/components/scroll-flow";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Gizlilik ve KVKK — KolayTercih", title: "Gizlilik ve KVKK — KolayTercih",
@@ -10,8 +9,7 @@ export const metadata: Metadata = {
// NOT: MVP taslağıdır; yayına almadan önce hukuki gözden geçirme gerekir. // NOT: MVP taslağıdır; yayına almadan önce hukuki gözden geçirme gerekir.
export default function GizlilikPage() { export default function GizlilikPage() {
return ( return (
<ScrollFlow> <main className="mx-auto w-full max-w-2xl px-4 py-16">
<main className="mx-auto w-full max-w-2xl px-4 py-16">
<Parallax fromStart strength={10}> <Parallax fromStart strength={10}>
<h1 className="font-heading text-3xl font-bold"> <h1 className="font-heading text-3xl font-bold">
Gizlilik ve Kişisel Verilerin Korunması Gizlilik ve Kişisel Verilerin Korunması
@@ -73,7 +71,6 @@ export default function GizlilikPage() {
</p> </p>
</section> </section>
</Parallax> </Parallax>
</main> </main>
</ScrollFlow>
); );
} }

View File

@@ -1,7 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Mail } from "lucide-react"; import { Mail } from "lucide-react";
import { Parallax } from "@/components/parallax"; import { Parallax } from "@/components/parallax";
import { ScrollFlow } from "@/components/scroll-flow";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "İletişim — KolayTercih", title: "İletişim — KolayTercih",
@@ -9,8 +8,7 @@ export const metadata: Metadata = {
export default function IletisimPage() { export default function IletisimPage() {
return ( return (
<ScrollFlow> <main className="mx-auto w-full max-w-2xl px-4 py-16">
<main className="mx-auto w-full max-w-2xl px-4 py-16">
<Parallax fromStart strength={10}> <Parallax fromStart strength={10}>
<h1 className="font-heading text-3xl font-bold">İletişim</h1> <h1 className="font-heading text-3xl font-bold">İletişim</h1>
</Parallax> </Parallax>
@@ -30,7 +28,6 @@ export default function IletisimPage() {
hesap adresini eklersen daha hızlı yardımcı olabiliriz. Kart bilgini hesap adresini eklersen daha hızlı yardımcı olabiliriz. Kart bilgini
asla e-postayla paylaşma. asla e-postayla paylaşma.
</p> </p>
</main> </main>
</ScrollFlow>
); );
} }

View File

@@ -1,7 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { Parallax } from "@/components/parallax"; import { Parallax } from "@/components/parallax";
import { ScrollFlow } from "@/components/scroll-flow";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Kullanım ve Satış Koşulları — KolayTercih", title: "Kullanım ve Satış Koşulları — KolayTercih",
@@ -10,8 +9,7 @@ export const metadata: Metadata = {
// NOT: MVP taslağıdır; yayına almadan önce hukuki gözden geçirme gerekir. // NOT: MVP taslağıdır; yayına almadan önce hukuki gözden geçirme gerekir.
export default function KosullarPage() { export default function KosullarPage() {
return ( return (
<ScrollFlow> <main className="mx-auto w-full max-w-2xl px-4 py-16">
<main className="mx-auto w-full max-w-2xl px-4 py-16">
<Parallax fromStart strength={10}> <Parallax fromStart strength={10}>
<h1 className="font-heading text-3xl font-bold"> <h1 className="font-heading text-3xl font-bold">
Kullanım ve Satış Koşulları Kullanım ve Satış Koşulları
@@ -80,7 +78,6 @@ export default function KosullarPage() {
</p> </p>
</section> </section>
</Parallax> </Parallax>
</main> </main>
</ScrollFlow>
); );
} }

View File

@@ -7,6 +7,9 @@ import { ListeCekmecesi } from "@/components/manuel-liste/liste-cekmecesi";
import { HeaderGate } from "@/components/header-gate"; import { HeaderGate } from "@/components/header-gate";
import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41"; import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41";
import { DevPanel } from "@/components/dev/dev-panel"; import { DevPanel } from "@/components/dev/dev-panel";
import { ScrollFlow } from "@/components/scroll-flow";
import { PagePixelLayer } from "@/components/page-pixel-layer";
import { ConsoleAsr } from "@/components/console-asr";
import "./globals.css"; import "./globals.css";
const calSans = Cal_Sans({ const calSans = Cal_Sans({
@@ -54,6 +57,8 @@ export default function RootLayout({
className={`${calSans.variable} ${clarityCity.variable} ${geistMono.variable} ${bricolage.variable} h-full antialiased`} className={`${calSans.variable} ${clarityCity.variable} ${geistMono.variable} ${bricolage.variable} h-full antialiased`}
> >
<body className="min-h-full flex flex-col bg-slate-50"> <body className="min-h-full flex flex-col bg-slate-50">
<ConsoleAsr />
<PagePixelLayer />
{/* Site geneli progressive blur — üst ve alt */} {/* Site geneli progressive blur — üst ve alt */}
<div className="pointer-events-none fixed inset-x-0 top-0 z-40 h-28"> <div className="pointer-events-none fixed inset-x-0 top-0 z-40 h-28">
<ProgressiveBlur <ProgressiveBlur
@@ -74,7 +79,9 @@ export default function RootLayout({
<HeaderGate> <HeaderGate>
<SiteHeader /> <SiteHeader />
</HeaderGate> </HeaderGate>
{children} <ScrollFlow className="flex min-h-0 flex-1 flex-col">
{children}
</ScrollFlow>
{/* Manuel 24'lük liste çekmecesi + "+" uçuş katmanı (navbar'dan açılır) */} {/* Manuel 24'lük liste çekmecesi + "+" uçuş katmanı (navbar'dan açılır) */}
<ListeCekmecesi /> <ListeCekmecesi />
<Toaster position="top-center" /> <Toaster position="top-center" />

View File

@@ -24,6 +24,7 @@ import type { RaporParams } from "@/lib/rapor-havuzu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { RaporListesi } from "@/components/rapor-listesi"; import { RaporListesi } from "@/components/rapor-listesi";
import { ViewportPortal } from "@/components/viewport-portal";
import { raporMaskele, ACIK_SATIR } from "@/lib/rapor-maske"; import { raporMaskele, ACIK_SATIR } from "@/lib/rapor-maske";
import { SohbetClient } from "./sohbet-client"; import { SohbetClient } from "./sohbet-client";
import { RevizyonKutusu } from "./revizyon-kutusu"; import { RevizyonKutusu } from "./revizyon-kutusu";
@@ -322,13 +323,15 @@ export default async function ListemPage({
{/* Mobilde sohbet sağ rayla birlikte en alta düşer; yüzen kısayol {/* Mobilde sohbet sağ rayla birlikte en alta düşer; yüzen kısayol
kullanıcıyı 24 satırı kaydırmadan danışmana ulaştırır. */} kullanıcıyı 24 satırı kaydırmadan danışmana ulaştırır. */}
<a <ViewportPortal>
href="#sohbet" <a
className="fixed bottom-5 right-4 z-40 inline-flex h-11 items-center gap-2 rounded-full bg-primary px-4 text-sm font-semibold text-white shadow-lg transition-transform duration-200 active:scale-[0.97] lg:hidden print:hidden" href="#sohbet"
> className="fixed bottom-5 right-4 z-40 inline-flex h-11 items-center gap-2 rounded-full bg-primary px-4 text-sm font-semibold text-white shadow-lg transition-transform duration-200 active:scale-[0.97] lg:hidden print:hidden"
<MessageCircleQuestion className="size-4" aria-hidden /> >
Danışmana sor <MessageCircleQuestion className="size-4" aria-hidden />
</a> Danışmana sor
</a>
</ViewportPortal>
</main> </main>
); );
} }

View File

@@ -11,9 +11,9 @@ import {
} from "lucide-react"; } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { HeroFocusButton } from "@/components/hero-focus-button";
import { HeroForm } from "@/components/hero-form"; import { HeroForm } from "@/components/hero-form";
import { Parallax } from "@/components/parallax"; import { Parallax } from "@/components/parallax";
import { ScrollFlow } from "@/components/scroll-flow";
import { Reveal } from "@/components/landing/reveal"; import { Reveal } from "@/components/landing/reveal";
import { TurkeyPixelMap } from "@/components/turkey-pixel-map"; import { TurkeyPixelMap } from "@/components/turkey-pixel-map";
import { import {
@@ -24,7 +24,6 @@ import {
} from "@/components/pixel-decor"; } from "@/components/pixel-decor";
import { URUNLER } from "@/lib/credits"; import { URUNLER } from "@/lib/credits";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@@ -166,8 +165,10 @@ function ComparisonCell({ value }: { value: boolean | string }) {
export default function Home() { export default function Home() {
return ( return (
<div className="bg-slate-50 text-slate-900"> <div
<ScrollFlow className="flex min-h-screen flex-col"> id="top"
className="flex min-h-screen flex-col bg-slate-50 text-slate-900"
>
<main className="flex-1"> <main className="flex-1">
{/* Hero */} {/* Hero */}
<section className="relative isolate overflow-hidden"> <section className="relative isolate overflow-hidden">
@@ -193,7 +194,7 @@ export default function Home() {
<Parallax <Parallax
fromStart fromStart
strength={14} strength={14}
className="mx-auto flex min-h-[calc(100svh-5.5rem)] max-w-6xl flex-col items-center justify-center px-4 pb-44 pt-4 text-center" className="mx-auto flex min-h-[calc(100svh-5.5rem)] max-w-6xl flex-col items-center justify-center px-4 py-4 text-center sm:pb-44 sm:pt-4"
> >
<Badge <Badge
variant="secondary" variant="secondary"
@@ -201,7 +202,7 @@ export default function Home() {
> >
2026 YKS tercih dönemi ık 2026 YKS tercih dönemi ık
</Badge> </Badge>
<h1 className="max-w-3xl font-heading text-4xl font-bold tracking-[0.015em] sm:text-6xl"> <h1 className="max-w-3xl font-heading text-7xl font-bold tracking-[0.015em] sm:text-6xl">
Dört yıllık geleceğini <br className="hidden sm:block" /> Dört yıllık geleceğini <br className="hidden sm:block" />
<span className="font-bricolage italic tracking-tighter font-bold text-primary"> <span className="font-bricolage italic tracking-tighter font-bold text-primary">
48 saatlik panikle 48 saatlik panikle
@@ -481,7 +482,7 @@ export default function Home() {
value={faq.question} value={faq.question}
className="last:border-b-0" className="last:border-b-0"
> >
<AccordionTrigger className="cursor-pointer text-left font-heading text-base font-semibold"> <AccordionTrigger className="cursor-pointer text-left font-sans text-base font-semibold">
{faq.question} {faq.question}
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="text-base leading-relaxed text-slate-600"> <AccordionContent className="text-base leading-relaxed text-slate-600">
@@ -522,13 +523,7 @@ export default function Home() {
Sıralamanı gir, yapay zekâ danışmanın veriyle konuşsun. Sıralamanı gir, yapay zekâ danışmanın veriyle konuşsun.
Tercih dönemi bitmeden yerini al. Tercih dönemi bitmeden yerini al.
</p> </p>
<Button <HeroFocusButton />
asChild
size="lg"
className="mt-8 h-12 cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
>
<a href="#hero-form">Ücretsiz dene</a>
</Button>
</div> </div>
</div> </div>
</Reveal> </Reveal>
@@ -571,7 +566,6 @@ export default function Home() {
</nav> </nav>
</div> </div>
</footer> </footer>
</ScrollFlow>
</div> </div>
); );
} }

View File

@@ -14,7 +14,6 @@ import { URUNLER, DENEME_KREDISI } from "@/lib/credits";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Parallax } from "@/components/parallax"; import { Parallax } from "@/components/parallax";
import { ScrollFlow } from "@/components/scroll-flow";
import { SatinAlForm } from "./satin-al-form"; import { SatinAlForm } from "./satin-al-form";
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -53,8 +52,7 @@ export default async function PaketPage({
const odemeHatasi = hata === "token" || hata === "siparis"; const odemeHatasi = hata === "token" || hata === "siparis";
return ( return (
<div className="bg-slate-50 text-slate-900"> <div className="flex min-h-screen flex-col bg-slate-50 text-slate-900">
<ScrollFlow className="flex min-h-screen flex-col">
<main className="mx-auto w-full max-w-3xl flex-1 px-4 py-16"> <main className="mx-auto w-full max-w-3xl flex-1 px-4 py-16">
{odemeHatasi ? ( {odemeHatasi ? (
<div <div
@@ -189,7 +187,6 @@ export default async function PaketPage({
. .
</p> </p>
</main> </main>
</ScrollFlow>
</div> </div>
); );
} }

View File

@@ -8,6 +8,7 @@ import { animate } from "motion";
import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ViewportPortal } from "@/components/viewport-portal";
import type { SihirbazFacetleri } from "@/lib/db"; import type { SihirbazFacetleri } from "@/lib/db";
import { import {
SIHIRBAZ_STORAGE_KEY, SIHIRBAZ_STORAGE_KEY,
@@ -446,54 +447,56 @@ export function SihirbazBolumu({
</div> </div>
{/* Sabit alt çağrı kartı — liste/panel yokken ve kart kapatılmadıysa */} {/* Sabit alt çağrı kartı — liste/panel yokken ve kart kapatılmadıysa */}
{monteEdildi && !panelde && !kartGizle && !acik ? ( <ViewportPortal>
<div className="pointer-events-none fixed inset-x-0 bottom-4 z-40 px-4"> {monteEdildi && !panelde && !kartGizle && !acik ? (
<div className="pointer-events-auto mx-auto flex max-w-xl items-center gap-4 rounded-2xl border border-slate-200 bg-white p-4 shadow-xl"> <div className="pointer-events-none fixed inset-x-0 bottom-4 z-40 px-4">
<div className="flex-1"> <div className="pointer-events-auto mx-auto flex max-w-xl items-center gap-4 rounded-2xl border border-slate-200 bg-white p-4 shadow-xl">
<p className="font-heading text-sm font-bold text-slate-900"> <div className="flex-1">
{girisBekliyor <p className="font-heading text-sm font-bold text-slate-900">
? "Seçimlerin kayıtlı — AI listen hazır bekliyor" {girisBekliyor
: "3 adımda 24 tercihlik listen"} ? "Seçimlerin kayıtlı — AI listen hazır bekliyor"
</p> : "3 adımda 24 tercihlik listen"}
<p className="text-xs text-slate-500"> </p>
{girisBekliyor <p className="text-xs text-slate-500">
? "Ücretsiz giriş yap, 5 deneme kredinle listen otomatik kurulsun." {girisBekliyor
: "Sıralamana uygun bölümleri seç, yapay zekâ listeni kursun."} ? "Ücretsiz giriş yap, 5 deneme kredinle listen otomatik kurulsun."
</p> : "Sıralamana uygun bölümleri seç, yapay zekâ listeni kursun."}
</p>
</div>
{girisBekliyor ? (
<Button
asChild
size="sm"
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
>
<Link href={girisUrl}>Giriş yap</Link>
</Button>
) : (
<Button
size="sm"
onClick={() => acikDegisti(true)}
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
>
Başla
</Button>
)}
<button
type="button"
aria-label="Kapat"
onClick={() => {
setKartGizle(true);
try {
sessionStorage.setItem(KAPATMA_KEY, "1");
} catch {}
}}
className="cursor-pointer rounded-full p-1 text-slate-400 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-600"
>
<X className="size-4" aria-hidden />
</button>
</div> </div>
{girisBekliyor ? (
<Button
asChild
size="sm"
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
>
<Link href={girisUrl}>Giriş yap</Link>
</Button>
) : (
<Button
size="sm"
onClick={() => acikDegisti(true)}
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
>
Başla
</Button>
)}
<button
type="button"
aria-label="Kapat"
onClick={() => {
setKartGizle(true);
try {
sessionStorage.setItem(KAPATMA_KEY, "1");
} catch {}
}}
className="cursor-pointer rounded-full p-1 text-slate-400 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-600"
>
<X className="size-4" aria-hidden />
</button>
</div> </div>
</div> ) : null}
) : null} </ViewportPortal>
{/* Üretim başladığında modalı kaldır; yeni panelin geçişini Motion {/* Üretim başladığında modalı kaldır; yeni panelin geçişini Motion
üstlenir ve odağı panel içeriğine taşır. */} üstlenir ve odağı panel içeriğine taşır. */}

View File

@@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
import { MessageCircleMore, X } from "lucide-react"; import { MessageCircleMore, X } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { SohbetClient } from "@/app/listem/sohbet-client"; import { SohbetClient } from "@/app/listem/sohbet-client";
import { ViewportPortal } from "@/components/viewport-portal";
export function SohbetPopup({ kredi }: { kredi: number }) { export function SohbetPopup({ kredi }: { kredi: number }) {
const [acik, setAcik] = useState(false); const [acik, setAcik] = useState(false);
@@ -23,7 +24,7 @@ export function SohbetPopup({ kredi }: { kredi: number }) {
}, [acik]); }, [acik]);
return ( return (
<> <ViewportPortal>
<AnimatePresence> <AnimatePresence>
{acik ? ( {acik ? (
<motion.div <motion.div
@@ -68,6 +69,6 @@ export function SohbetPopup({ kredi }: { kredi: number }) {
)} )}
{acik ? "Kapat" : "Danışmana sor"} {acik ? "Kapat" : "Danışmana sor"}
</button> </button>
</> </ViewportPortal>
); );
} }

View File

@@ -0,0 +1,33 @@
"use client";
import { useEffect } from "react";
let hasPlayed = false;
const consoleMessage = [
"%cبِسْمِ اللَّهِ الرَّحْمٰنِ الرَّحِيمِ",
"",
"وَالْعَصْرِۙ اِنَّ الْاِنْسَانَ لَف۪ي خُسْرٍۙ",
"اِلَّا الَّذ۪ينَ اٰمَنُوا وَعَمِلُوا الصَّالِحَاتِ",
"وَتَوَاصَوْا بِالْحَقِّ وَتَوَاصَوْا بِالصَّبْرِ",
"",
"Rahmân ve Rahîm olan Allahın adıyla.",
"",
"Asra yemîn olsun ki,",
"İnsan gerçekten ziyândadır.",
"Ancak iman edip sâlih ameller yapanlar, birbirlerine hakkı tavsiye",
"edenler ve birbirlerine sabretmeyi öğütleyenler müstesnâ!",
].join("\n");
export function ConsoleAsr() {
useEffect(() => {
if (hasPlayed) return;
hasPlayed = true;
window.setTimeout(() => {
console.log(consoleMessage, "font-size:16px");
}, 250);
}, []);
return null;
}

View File

@@ -200,7 +200,10 @@ export function DevPanel() {
</button> </button>
</div> </div>
<div className="flex flex-col gap-4 overflow-y-auto p-4 text-sm"> <div
data-lenis-prevent
className="flex flex-col gap-4 overflow-y-auto p-4 text-sm"
>
{/* Oturum */} {/* Oturum */}
<section> <section>
<p className="text-[11px] font-semibold uppercase tracking-wide text-slate-400"> <p className="text-[11px] font-semibold uppercase tracking-wide text-slate-400">

View File

@@ -0,0 +1,26 @@
"use client";
import { Button } from "@/components/ui/button";
export function HeroFocusButton() {
function handleClick() {
const input = document.getElementById("siralama");
if (input instanceof HTMLInputElement) {
input.focus({ preventScroll: true });
}
window.scrollTo({ top: 0, behavior: "smooth" });
}
return (
<Button
type="button"
size="lg"
onClick={handleClick}
className="mt-8 h-12 cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
>
Ücretsiz dene
</Button>
);
}

View File

@@ -132,7 +132,7 @@ export function HeroForm() {
// flex-1 yalnızca yatay dizilimde (sm+): mobilde form flex-col olduğu // flex-1 yalnızca yatay dizilimde (sm+): mobilde form flex-col olduğu
// için flex-1 inputun yüksekliğini eziyordu. Mobilde geniş padding // için flex-1 inputun yüksekliğini eziyordu. Mobilde geniş padding
// + büyük yazı; sm ve üzeri eski görünüm. // + büyük yazı; sm ve üzeri eski görünüm.
className="h-12 bg-background px-5 text-lg sm:flex-1 sm:px-2.5 sm:text-base" className="h-14 bg-background px-6 py-3 text-lg sm:h-12 sm:flex-1 sm:px-2.5 sm:py-1 sm:text-base"
/> />
<Button <Button
type="submit" type="submit"

View File

@@ -206,6 +206,7 @@ function CekmeceSheet() {
layout="position" layout="position"
layoutScroll layoutScroll
as="ol" as="ol"
data-lenis-prevent
className="min-h-0 flex-1 space-y-2 overflow-x-hidden overflow-y-auto px-4 py-4" className="min-h-0 flex-1 space-y-2 overflow-x-hidden overflow-y-auto px-4 py-4"
> >
<AnimatePresence initial={false}> <AnimatePresence initial={false}>

View File

@@ -0,0 +1,46 @@
"use client";
import { usePathname } from "next/navigation";
import { PixelCorner } from "@/components/pixel-decor";
const pixelClass = "size-10 [&_rect]:[fill-opacity:0.55]";
/**
* Ana sayfanın kendi zengin piksel kompozisyonunu tekrar etmeden, iç
* sayfalara ortak bir piksel çerçevesi taşır. Katman içeriklerin üstünde
* kaldığı için sayfaların opak zeminlerinde de görünür; kenarlara
* yerleştiğinden etkileşimleri ve okunabilirliği engellemez.
*/
export function PagePixelLayer() {
const pathname = usePathname();
if (pathname === "/") return null;
return (
<div
aria-hidden
className="pointer-events-none fixed inset-0 z-30 overflow-hidden print:hidden"
>
<PixelCorner
className={`absolute -left-3 top-28 -rotate-90 ${pixelClass}`}
/>
<PixelCorner
className={`absolute -right-3 top-28 ${pixelClass}`}
/>
<PixelCorner
className={`absolute -left-4 top-[46%] -rotate-90 text-orange-500 max-sm:hidden ${pixelClass}`}
/>
<PixelCorner
className={`absolute -right-4 top-[58%] rotate-90 max-sm:hidden ${pixelClass}`}
/>
<PixelCorner
className={`absolute -bottom-3 left-6 rotate-180 ${pixelClass}`}
/>
<PixelCorner
className={`absolute -bottom-3 right-6 rotate-90 text-orange-500 ${pixelClass}`}
/>
</div>
);
}

View File

@@ -35,24 +35,27 @@ export function ScrollFlow({
const reduceMotion = useReducedMotion(); const reduceMotion = useReducedMotion();
const { scrollY } = useScroll(); const { scrollY } = useScroll();
const velocity = useVelocity(scrollY); const velocity = useVelocity(scrollY);
// Ham hız titrek olur; yay ile yumuşatınca bükülme salınıp yerine oturur
const smoothVelocity = useSpring(velocity, { // Önce hızı kesintisiz bir hedef açıya dönüştür, sonra doğrudan görünen
damping: 50, // açıyı yumuşat. Böylece eşik/clamp kaynaklı ani hız değişimleri yaya girmez.
stiffness: 420, const targetSkew = useTransform(velocity, (v) => {
mass: 0.6, const RESPONSE = 1800;
return Math.tanh(v / RESPONSE) * maxSkew;
}); });
// Ölü bölge: yavaş/ufak kaydırmalarda bükülme tamamen 0 kalır — transform const smoothSkew = useSpring(targetSkew, {
// hiç değişmediği için compositor'a kare başı iş çıkmaz. Eşik üstünde stiffness: 240,
// kareyle (ease-in) yükselir, 0,01°'lik adımlara yuvarlanır ki yayın damping: 26,
// mikro-salınımları her karede katmanı kirletmesin. mass: 0.7,
const skewY = useTransform(smoothVelocity, (v) => { restDelta: 0.005,
const DEADZONE = 500; // px/sn — bunun altı "normal gezinme" sayılır restSpeed: 0.01,
const FULL = 2600; // px/sn — bu hızda maxSkew'e ulaşılır });
const mag = Math.abs(v);
if (mag < DEADZONE) return 0; // Yuvarlama görsel basamaklanma üretir. Tam hassasiyetle çiz; yalnızca açı
const t = Math.min(1, (mag - DEADZONE) / (FULL - DEADZONE)); // algılanamayacak kadar küçüldüğünde transform'u kaldırarak dev sayfa
const s = t * t * maxSkew * Math.sign(v); // katmanının GPU belleğinde boşuna tutulmasını önle.
return Math.round(s * 100) / 100; const transform = useTransform(smoothSkew, (v) => {
if (Math.abs(v) < 0.01) return "none";
return `skewY(${v}deg)`;
}); });
if (reduceMotion) { if (reduceMotion) {
@@ -61,12 +64,7 @@ export function ScrollFlow({
return ( return (
<ReactLenis root options={{ lerp: 0.09, anchors: true }}> <ReactLenis root options={{ lerp: 0.09, anchors: true }}>
{/* willChange: bükülme 0'a döndüğünde dev katman GPU'dan düşüp <motion.div className={className} style={{ transform }}>
bir sonraki tekerlekte yeniden rasterize edilmesin */}
<motion.div
className={className}
style={{ skewY, willChange: "transform" }}
>
{children} {children}
</motion.div> </motion.div>
</ReactLenis> </ReactLenis>

View File

@@ -26,6 +26,7 @@ function ChatContainerRoot({
return ( return (
<StickToBottom <StickToBottom
className={cn("flex overflow-y-auto", className)} className={cn("flex overflow-y-auto", className)}
data-lenis-prevent
resize="smooth" resize="smooth"
initial="instant" initial="instant"
role="log" role="log"

View File

@@ -60,6 +60,7 @@ function DialogContent({
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
data-slot="dialog-content" data-slot="dialog-content"
data-lenis-prevent
className={cn( className={cn(
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className className

View File

@@ -68,6 +68,7 @@ function SelectContent({
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
data-slot="select-content" data-slot="select-content"
data-lenis-prevent
data-align-trigger={position === "item-aligned"} data-align-trigger={position === "item-aligned"}
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )} className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
position={position} position={position}

View File

@@ -0,0 +1,23 @@
"use client";
import { type ReactNode, useSyncExternalStore } from "react";
import { createPortal } from "react-dom";
const subscribe = () => () => {};
const getClientSnapshot = () => true;
const getServerSnapshot = () => false;
/**
* Viewport'a sabit kalması gereken öğeleri, transform uygulanan sayfa
* katmanının dışına taşır. Böylece üst katmandaki scroll skew animasyonu
* `position: fixed` koordinat sistemini değiştirmez.
*/
export function ViewportPortal({ children }: { children: ReactNode }) {
const mounted = useSyncExternalStore(
subscribe,
getClientSnapshot,
getServerSnapshot,
);
return mounted ? createPortal(children, document.body) : null;
}