Merge pull request #6 from poyrazavsever/newPoyrazUı

New poyraz uı
This commit is contained in:
Poyraz
2026-07-16 09:43:36 +03:00
committed by GitHub
20 changed files with 1301 additions and 290 deletions
+4 -4
View File
@@ -1,7 +1,7 @@
import { HomeHero } from "@/components/home-hero";
import { HomeProjectsSection } from "@/components/home-projects-section";
import { HomeVideosSection } from "@/components/home-videos-section";
import { ReferencesSection } from "@/components/references-section";
import { SponsorsSection } from "@/components/sponsors-section";
import { getHomeBlogNews } from "@/data/blog";
export default async function Home({
@@ -10,14 +10,14 @@ export default async function Home({
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const homeNews = await getHomeBlogNews(locale, 3);
const homeNews = await getHomeBlogNews(locale, 6);
return (
<section className="flex h-full flex-col gap-4 overflow-y-auto overflow-x-hidden">
<section className="flex h-full flex-col overflow-y-auto overflow-x-hidden">
<HomeHero news={homeNews} />
<HomeProjectsSection />
<ReferencesSection />
<HomeVideosSection />
<SponsorsSection />
</section>
);
}
+12
View File
@@ -1,6 +1,18 @@
@import "tailwindcss";
@import "poyraz-ui/preset.css";
@source "../node_modules/poyraz-ui/dist";
@theme {
--font-secondary: "Nunito", ui-rounded, "Avenir Next", system-ui, sans-serif;
}
@layer base {
:root {
--poyraz-font-secondary: "Nunito", ui-rounded, "Avenir Next", system-ui, sans-serif;
}
}
@layer utilities {
@keyframes marquee {
0% { transform: translateX(0%); }
+229 -108
View File
@@ -1,133 +1,254 @@
"use client";
import { Icon } from "@iconify/react";
import Image from "next/image";
import { Link } from "@/i18n/routing";
import { Badge, Card, Typography } from "poyraz-ui/atoms";
import { useLocale, useTranslations } from "next-intl";
import { Badge, Button, ButtonIcon, ButtonLabel, Card, Typography } from "poyraz-ui/atoms";
import { Sheet, SheetContent, SheetTitle, SheetTrigger } from "poyraz-ui/molecules";
import { StaggerContainer, StaggerItem } from "@/components/motion-wrapper";
import { Link, useRouter } from "@/i18n/routing";
import { BOOKMARKS } from "@/data/bookmarks";
import { certificates } from "@/data/certificates";
import { EDUCATION } from "@/data/education";
import { EXPERIENCE } from "@/data/experience";
import { useTranslations, useLocale } from "next-intl";
import { getLocalizedValue } from "@/lib/locale";
type TimelineItem = {
id: string;
title: string;
subtitle: string;
period: string;
description?: string;
};
function SectionHeading({
title,
action,
}: {
title: string;
action?: React.ReactNode;
}) {
return (
<div className="flex items-center justify-between gap-4">
<Typography
variant="h3"
component="h2"
className="tracking-[-0.035em]"
>
{title}
</Typography>
{action}
</div>
);
}
function TimelineList({ items }: { items: TimelineItem[] }) {
return (
<div className="relative space-y-4 before:absolute before:top-3 before:bottom-3 before:left-3 before:w-px before:bg-border">
{items.map((item) => (
<div
key={item.id}
className="relative grid grid-cols-[1.5rem_1fr] gap-3"
>
<span className="mt-1 size-6 rounded-full border border-border bg-background p-1">
<span className="block size-full rounded-full bg-primary" />
</span>
<div className="border-b border-border pb-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<Typography variant="large" className="text-base leading-tight">
{item.title}
</Typography>
<Typography
variant="small"
className="mt-0.5 text-muted-foreground"
>
{item.subtitle}
</Typography>
</div>
<Typography
variant="display"
className="shrink-0 text-right font-medium text-primary"
>
{item.period}
</Typography>
</div>
{item.description ? (
<Typography
variant="small"
className="mt-3 leading-relaxed text-muted-foreground"
>
{item.description}
</Typography>
) : null}
</div>
</div>
))}
</div>
);
}
export function AboutContent() {
const t = useTranslations("About");
const locale = useLocale();
const router = useRouter();
const educationItems: TimelineItem[] = EDUCATION.map((item) => ({
id: item.id,
title: getLocalizedValue(item.title, locale),
subtitle: item.institution,
period: getLocalizedValue(item.period, locale),
}));
const experienceItems: TimelineItem[] = EXPERIENCE.map((item) => ({
id: item.id,
title: getLocalizedValue(item.role, locale),
subtitle: item.company,
period: getLocalizedValue(item.period, locale),
}));
return (
<section className="flex h-full flex-col gap-3 overflow-hidden">
<div className="grid gap-3 md:grid-cols-2">
<Card className="rounded-sm border-border p-4">
<Typography variant="p" className="text-sm leading-relaxed text-muted-foreground">
{t("aboutText")}
</Typography>
</Card>
<div>
<StaggerContainer className="grid gap-2">
{EDUCATION.map((item) => (
<StaggerItem key={item.id}>
<Card className="rounded-sm border-border p-3">
<Typography variant="large" className="text-base leading-tight">
{getLocalizedValue(item.title, locale)}
</Typography>
<Typography variant="small" className="mt-0.5 text-muted-foreground">
{item.institution}
</Typography>
<Typography variant="small" className="mt-1 text-red-600">
{getLocalizedValue(item.period, locale)}
</Typography>
</Card>
</StaggerItem>
))}
</StaggerContainer>
</div>
</div>
<div className="grid gap-3 md:grid-cols-2 md:items-start">
<Sheet>
<SheetTrigger asChild>
<button
<section className="flex h-full flex-col gap-12 overflow-y-auto overflow-x-hidden">
<section className="space-y-3">
<SectionHeading
title={t("title")}
action={
<Button
type="button"
className="cursor-pointer text-left"
aria-label="Tüm sertifikaları"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
onClick={() => router.push("/contact")}
>
<Card className="rounded-sm border-border p-4 transition-colors hover:border-zinc-700">
<div className="grid grid-cols-2 gap-2">
{certificates.slice(0, 4).map((item) => (
<div
key={getLocalizedValue(item.name, locale)}
className="relative aspect-4/3 overflow-hidden rounded-sm border border-border"
<ButtonLabel>{t("contactCta")}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</Button>
}
/>
<Typography
variant="p"
className="max-w-3xl text-sm leading-7 text-muted-foreground"
>
{t("aboutText")}
</Typography>
</section>
<section className="space-y-10">
<div className="space-y-3">
<SectionHeading title={t("educationTitle")} />
<TimelineList items={educationItems} />
</div>
<div className="space-y-3">
<SectionHeading title={t("experienceTitle")} />
<TimelineList items={experienceItems} />
</div>
</section>
<section className="space-y-3">
<SectionHeading
title={t("certificatesTitle")}
action={
<Sheet>
<SheetTrigger asChild>
<Button
type="button"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
>
<ButtonLabel>{t("allCertificates")}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-full max-w-xl p-4">
<SheetTitle>{t("allCertificates")}</SheetTitle>
<div className="mt-4 grid max-h-[90dvh] gap-3 overflow-y-auto pr-1">
{certificates.map((item) => (
<Card
key={`${getLocalizedValue(item.name, locale)}-${getLocalizedValue(item.date, locale)}`}
className="rounded-sm border-border p-3"
>
<Image
src={item.image}
alt={getLocalizedValue(item.name, locale)}
fill
sizes="(max-width: 768px) 45vw, 22vw"
className="object-cover"
/>
</div>
<div className="flex gap-3">
<div className="relative h-20 w-28 shrink-0 overflow-hidden rounded-sm border border-border">
<Image
src={item.image}
alt={getLocalizedValue(item.name, locale)}
fill
sizes="112px"
className="object-cover"
/>
</div>
<div className="min-w-0">
<Typography variant="large" className="leading-tight">
{getLocalizedValue(item.name, locale)}
</Typography>
<Typography
variant="small"
className="text-muted-foreground"
>
{item.organization}
</Typography>
<Typography variant="small" className="text-primary">
{getLocalizedValue(item.date, locale)}
</Typography>
</div>
</div>
</Card>
))}
</div>
</SheetContent>
</Sheet>
}
/>
<div className="relative overflow-hidden py-1">
<div className="flex w-max items-stretch gap-3">
{certificates.slice(0, 8).map((item) => (
<Card
key={`${getLocalizedValue(item.name, locale)}-${getLocalizedValue(item.date, locale)}`}
className="w-56 shrink-0 overflow-hidden rounded-sm border-border p-0"
>
<div className="relative aspect-4/3 overflow-hidden border-b border-border">
<Image
src={item.image}
alt={getLocalizedValue(item.name, locale)}
fill
sizes="224px"
className="object-cover"
/>
</div>
<div className="p-3">
<Typography variant="large" className="line-clamp-2 text-sm leading-tight">
{getLocalizedValue(item.name, locale)}
</Typography>
<Typography
variant="small"
className="mt-1 text-muted-foreground"
>
{item.organization}
</Typography>
</div>
</Card>
</button>
</SheetTrigger>
<SheetContent side="right" className="w-full max-w-xl p-4">
<SheetTitle>{t("allCertificates")}</SheetTitle>
<div className="mt-4 grid max-h-[90dvh] gap-3 overflow-y-auto pr-1">
{certificates.map((item) => (
<Card key={`${getLocalizedValue(item.name, locale)}-${getLocalizedValue(item.date, locale)}`} className="rounded-sm border-border p-3">
<div className="flex gap-3">
<div className="relative h-20 w-28 shrink-0 overflow-hidden rounded-sm border border-border">
<Image
src={item.image}
alt={getLocalizedValue(item.name, locale)}
fill
sizes="112px"
className="object-cover"
/>
</div>
<div className="min-w-0">
<Typography variant="large" className="leading-tight">
{getLocalizedValue(item.name, locale)}
</Typography>
<Typography variant="small" className="text-muted-foreground">
{item.organization}
</Typography>
<Typography variant="small" className="text-red-600">
{getLocalizedValue(item.date, locale)}
</Typography>
</div>
</div>
</Card>
))}
</div>
</SheetContent>
</Sheet>
<div className="h-full">
<StaggerContainer className="grid gap-2">
{EXPERIENCE.map((item) => (
<StaggerItem key={item.id}>
<Card className="rounded-sm border-border p-3">
<Typography variant="large" className="text-base leading-tight">
{getLocalizedValue(item.role, locale)}
</Typography>
<Typography variant="small" className="mt-0.5 text-muted-foreground">
{item.company}
</Typography>
<Typography variant="small" className="mt-1 text-red-600">
{getLocalizedValue(item.period, locale)}
</Typography>
</Card>
</StaggerItem>
))}
</StaggerContainer>
</div>
<div
aria-hidden="true"
className="pointer-events-none absolute top-0 right-0 h-full w-24 bg-gradient-to-l from-background via-background/80 to-transparent"
/>
</div>
</div>
</section>
<div className="grid gap-3 md:grid-cols-3">
<Link href="/about/references" className="block">
@@ -190,7 +311,7 @@ export function AboutContent() {
href={item.href}
target="_blank"
rel="noreferrer"
className="mt-2 inline-flex text-sm text-red-600 hover:underline"
className="mt-2 inline-flex text-sm text-primary hover:underline"
>
{item.href.replace("https://", "")}
</Link>
-14
View File
@@ -1,7 +1,6 @@
"use client";
import { Icon } from "@iconify/react";
import Link from "next/link";
import { usePathname } from "@/i18n/routing";
import { AnnouncementBar } from "poyraz-ui/organisms";
import { SiteNavbar } from "@/components/site-navbar";
@@ -32,7 +31,6 @@ export function AppShell({ children }: AppShellProps) {
}
const localizedText = announcement ? getLocalizedValue(announcement.text, locale) : "";
const localizedActionLabel = announcement ? getLocalizedValue(announcement.actionLabel, locale) : "";
return (
<>
@@ -45,18 +43,6 @@ export function AppShell({ children }: AppShellProps) {
variant="branded"
dismissible={false}
icon={<Icon icon="mdi:sparkles" width={16} height={16} />}
action={
localizedActionLabel && announcement.actionHref ? (
<Link
href={announcement.actionHref}
target="_blank"
rel="noreferrer"
className="text-xs font-bold underline"
>
{localizedActionLabel}
</Link>
) : null
}
>
{localizedText}
</AnnouncementBar>
+125 -64
View File
@@ -1,10 +1,18 @@
"use client";
import { Icon } from "@iconify/react";
import Image from "next/image";
import { useRef, useState } from "react";
import { Card, Typography } from "poyraz-ui/atoms";
import { useLocale, useTranslations } from "next-intl";
import { useRouter } from "@/i18n/routing";
import {
Button,
ButtonIcon,
ButtonLabel,
TextEffect,
Typography,
} from "poyraz-ui/atoms";
import { NewsCard } from "poyraz-ui/molecules";
import { useTranslations } from "next-intl";
import { getResumeHref } from "@/lib/links";
type HomeHeroProps = {
news: {
@@ -18,78 +26,131 @@ type HomeHeroProps = {
};
export function HomeHero({ news }: HomeHeroProps) {
const [frame, setFrame] = useState<1 | 2>(1);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const t = useTranslations("Home");
const handleMouseEnter = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setFrame((prev) => (prev === 1 ? 2 : 1));
}, 260);
};
const handleMouseLeave = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setFrame(1);
};
const locale = useLocale();
const router = useRouter();
return (
<section className="grid gap-3 md:h-65 md:grid-cols-2">
<div className="grid gap-2 md:grid-rows-3">
{news.map((item) => (
<div key={item.id}>
<NewsCard
className="rounded-sm border-border md:h-full"
category={item.category}
title={item.title}
date={item.date}
image={item.image}
href={item.href}
/>
<section>
<div className="relative grid min-h-[150px] overflow-hidden border-b border-border py-4 sm:grid-cols-[1fr_180px] sm:py-5">
<div className="flex max-w-2xl flex-col justify-center gap-2">
<Typography
variant="h2"
component="h1"
className="leading-none tracking-[-0.045em] font-secondary font-semibold text-foreground text-2xl"
>
Poyraz{" "}
<TextEffect
effect="hand-drawn"
tone="primary"
className="tracking-[-0.04em]"
>
Avsever
</TextEffect>
</Typography>
<Typography
variant="small"
className="max-w-xl text-xs leading-5 text-muted-foreground sm:text-sm"
>
{t("heroDescription")}
</Typography>
<div className="flex flex-wrap items-center gap-2 pt-1">
<Button
type="button"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
onClick={() => router.push("/contact")}
>
<ButtonLabel>{t("hireMe")}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</Button>
<Button
type="button"
variant="secondary"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
onClick={() => window.open(getResumeHref(locale), "_blank", "noopener,noreferrer")}
>
<ButtonIcon>
<Icon icon="mdi:download" width={15} height={15} />
</ButtonIcon>
<ButtonLabel>{t("downloadCv")}</ButtonLabel>
</Button>
</div>
))}
</div>
<div className="pointer-events-none absolute right-0 bottom-0 hidden h-full w-44 overflow-hidden sm:block">
<Image
src="/images/hero1.png"
alt="Poyraz Avsever"
width={220}
height={220}
sizes="180px"
priority
className="absolute right-0 bottom-0 h-auto w-36 object-contain"
/>
<div
aria-hidden="true"
className="absolute right-0 bottom-0 h-px w-36 bg-border"
/>
</div>
</div>
<Card className="grid grid-cols-[112px_1fr] items-stretch gap-2 rounded-sm border-border sm:grid-cols-[168px_1fr] md:h-full">
<div className="flex flex-col justify-center gap-1 px-3 py-2">
<div className="flex items-center gap-1">
<Typography variant="h2" className="leading-tight">
Poyraz{" "}
<span className="font-secondary text-red-600">Avsever</span>
</Typography>
</div>
<Typography variant="small" className="text-xs leading-relaxed text-muted-foreground sm:text-sm">
{t.rich("role", {
strong: (chunks) => (
<strong className="font-semibold text-foreground">{chunks}</strong>
),
})}
<section className="space-y-3 pt-12 md:pt-14">
<div className="flex items-center justify-between gap-4">
<Typography
variant="h3"
component="h2"
className="tracking-[-0.035em]"
>
{t("readMyPosts")}
</Typography>
<Button
type="button"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
onClick={() => router.push("/blog")}
>
<ButtonLabel>{t("allPosts")}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</Button>
</div>
<div className="relative h-full overflow-hidden">
<div
className="relative h-full w-full"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Image
src={frame === 1 ? "/images/hero1.png" : "/images/hero2.png"}
alt="Poyraz Avsever"
width={240}
height={240}
sizes="(max-width: 768px) 80px, 192px"
priority
className="absolute right-0 bottom-0 h-auto w-20 object-contain md:w-48"
/>
<div className="relative overflow-hidden py-1">
<div className="flex w-max items-stretch gap-3">
{news.map((item) => (
<NewsCard
key={item.id}
className="w-72 shrink-0 rounded-sm border-border"
category={item.category}
title={item.title}
date={item.date}
image={item.image}
href={item.href}
/>
))}
</div>
<div
aria-hidden="true"
className="pointer-events-none absolute top-0 right-0 h-full w-24 bg-gradient-to-l from-background via-background/80 to-transparent"
/>
</div>
</Card>
</section>
</section>
);
}
+69
View File
@@ -0,0 +1,69 @@
"use client";
import { Icon } from "@iconify/react";
import { useLocale, useTranslations } from "next-intl";
import { Button, ButtonIcon, ButtonLabel, Typography } from "poyraz-ui/atoms";
import { ImageCard } from "poyraz-ui/molecules";
import { useRouter } from "@/i18n/routing";
import { WEB_APPS } from "@/data/projects";
import { getLocalizedValue } from "@/lib/locale";
export function HomeProjectsSection() {
const t = useTranslations("Home");
const locale = useLocale();
const router = useRouter();
const projects = WEB_APPS.slice(0, 5);
return (
<section className="space-y-3 pt-12 md:pt-14">
<div className="flex items-center justify-between gap-4">
<Typography
variant="h3"
component="h2"
className="tracking-[-0.035em]"
>
{t("projectsTitle")}
</Typography>
<Button
type="button"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
onClick={() => router.push("/projects")}
>
<ButtonLabel>{t("allProjects")}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</Button>
</div>
<div className="relative overflow-hidden py-1">
<div className="flex w-max items-stretch gap-2">
{projects.map((project) => (
<ImageCard
key={project.id}
image={project.image}
title={project.title}
description={getLocalizedValue(project.description, locale)}
badge={
project.badge
? getLocalizedValue(project.badge, locale)
: undefined
}
href={project.href}
className="aspect-square w-56 shrink-0 rounded-sm border-border md:w-[calc((100vw-8rem)/4)] md:max-w-56"
/>
))}
</div>
<div
aria-hidden="true"
className="pointer-events-none absolute top-0 right-0 h-full w-24 bg-gradient-to-l from-background via-background/80 to-transparent"
/>
</div>
</section>
);
}
+35 -2
View File
@@ -1,4 +1,7 @@
import { Card } from "poyraz-ui/atoms";
"use client";
import { Icon } from "@iconify/react";
import { Button, ButtonIcon, ButtonLabel, Card, Typography } from "poyraz-ui/atoms";
import { YOUTUBE_VIDEO_LINKS } from "@/data/youtube-videos";
import { YoutubeLiteEmbed } from "@/components/youtube-lite-embed";
import { useTranslations } from "next-intl";
@@ -7,7 +10,37 @@ export function HomeVideosSection() {
const t = useTranslations("Content");
return (
<section className="space-y-2">
<section className="space-y-3 pt-12 md:pt-14">
<div className="flex items-center justify-between gap-4">
<Typography
variant="h3"
component="h2"
className="tracking-[-0.035em]"
>
{t("youtubeTitle")}
</Typography>
<Button
type="button"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
onClick={() =>
window.open(
"https://youtube.com/@poyrazavsever",
"_blank",
"noopener,noreferrer",
)
}
>
<ButtonLabel>{t("youtubeChannel")}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:youtube" width={16} height={16} />
</ButtonIcon>
</Button>
</div>
<div className="grid gap-2 md:grid-cols-3">
{YOUTUBE_VIDEO_LINKS.map((link) => (
<Card
+8 -4
View File
@@ -2,6 +2,7 @@
import { useLocale } from "next-intl";
import { useRouter, usePathname } from "@/i18n/routing";
import { Button } from "poyraz-ui/atoms";
export function LanguageSwitcher() {
const locale = useLocale();
@@ -14,13 +15,16 @@ export function LanguageSwitcher() {
};
return (
<button
<Button
type="button"
variant="secondary"
size="icon-sm"
radius="sm"
onClick={toggleLanguage}
className="inline-flex h-8 min-w-[36px] cursor-pointer items-center justify-center rounded-sm border border-border px-2 text-xs font-semibold text-muted-foreground transition-colors hover:text-foreground"
className="min-w-9 cursor-pointer text-xs font-semibold"
aria-label={locale === "tr" ? "Switch to English" : "Türkçe'ye geç"}
title={locale === "tr" ? "Switch to English" : "Türkçe'ye geç"}
>
{locale === "tr" ? "EN" : "TR"}
</button>
</Button>
);
}
+10 -4
View File
@@ -8,12 +8,18 @@ type ReferenceCardsProps = {
className?: string;
cardClassName?: string;
lineClamp?: boolean;
showRating?: boolean;
};
export function ReferenceCards({ className, cardClassName, lineClamp }: ReferenceCardsProps) {
export function ReferenceCards({
className,
cardClassName,
lineClamp,
showRating = true,
}: ReferenceCardsProps) {
const locale = useLocale();
const hoverClassName =
"transition-colors duration-200 group-hover:border-red-600/40 group-hover:bg-muted/30 group-hover:shadow-sm";
"transition-colors duration-200 group-hover:border-red-600/40 group-hover:bg-muted/30";
return (
<div className={className}>
@@ -26,8 +32,8 @@ export function ReferenceCards({ className, cardClassName, lineClamp }: Referenc
author={item.author}
role={getLocalizedValue(item.role, locale)}
avatar={item.avatar}
rating={item.rating}
className={`flex flex-col rounded-sm ${lineClamp ? "[&_p]:line-clamp-3" : ""} ${hoverClassName} ${cardClassName || "w-70 shrink-0"}`}
rating={showRating ? item.rating : undefined}
className={`flex flex-col rounded-sm ${lineClamp ? "[&_blockquote]:line-clamp-4" : ""} ${hoverClassName} ${cardClassName || "w-70 shrink-0"}`}
/>
);
+38 -7
View File
@@ -1,24 +1,55 @@
"use client";
import { Icon } from "@iconify/react";
import { ReferenceCards } from "@/components/reference-cards";
import { useRouter } from "@/i18n/routing";
import { Button, ButtonIcon, ButtonLabel, Typography } from "poyraz-ui/atoms";
import { useTranslations } from "next-intl";
export function ReferencesSection() {
const t = useTranslations("Home");
const router = useRouter();
return (
<section className="space-y-8 pt-0 sm:pt-12">
<div className="relative overflow-hidden rounded-sm py-4">
<section className="space-y-3 pt-12 md:pt-14">
<div className="flex items-center justify-between gap-4">
<Typography
variant="h3"
component="h2"
className="tracking-[-0.035em]"
>
{t("reviewsAndReferences")}
</Typography>
<Button
type="button"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
onClick={() => router.push("/about/references")}
>
<ButtonLabel>{t("allReferences")}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</Button>
</div>
<div className="relative overflow-hidden py-1">
{/* We use two sets of cards for seamless infinite marquee effect */}
<div className="flex items-start w-max gap-4 animate-marquee pause-on-hover">
<ReferenceCards className="flex items-start gap-4 shrink-0" lineClamp={true} cardClassName="w-80" />
<ReferenceCards className="flex items-start gap-4 shrink-0" lineClamp={true} cardClassName="w-80" />
<div className="flex w-max items-start gap-4 animate-marquee pause-on-hover">
<ReferenceCards className="flex items-start gap-4 shrink-0" lineClamp={true} showRating={false} cardClassName="w-80" />
<ReferenceCards className="flex items-start gap-4 shrink-0" lineClamp={true} showRating={false} cardClassName="w-80" />
</div>
<div
aria-hidden="true"
className="pointer-events-none absolute top-0 right-0 h-full w-32 bg-gradient-to-l from-white to-transparent"
className="pointer-events-none absolute top-0 right-0 h-full w-32 bg-gradient-to-l from-background to-transparent"
/>
<div
aria-hidden="true"
className="pointer-events-none absolute top-0 left-0 h-full w-32 bg-gradient-to-r from-white to-transparent"
className="pointer-events-none absolute top-0 left-0 h-full w-32 bg-gradient-to-r from-background to-transparent"
/>
</div>
</section>
+221 -64
View File
@@ -5,11 +5,12 @@ import dynamic from "next/dynamic";
import { Link, usePathname } from "@/i18n/routing";
import { useLocale, useTranslations } from "next-intl";
import { LanguageSwitcher } from "@/components/language-switcher";
import { useState } from "react";
import { useEffect, useState } from "react";
import {
Avatar,
AvatarFallback,
AvatarImage,
Button,
ButtonIcon,
ButtonLabel,
Logo,
Separator,
} from "poyraz-ui/atoms";
import {
@@ -24,7 +25,15 @@ import {
SheetContent,
SheetTitle,
SheetTrigger,
Tabs,
TabsList,
TabsTrigger,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "poyraz-ui/molecules";
import { NavbarTopBar, NavbarTopBarSection } from "poyraz-ui/organisms";
import { useKeyboardShortcutLabel } from "@/lib/use-keyboard-shortcut-label";
import { getResumeHref, NAV_LINKS, SOCIAL_LINKS, TOP_ICON_LINKS } from "@/lib/links";
@@ -33,15 +42,63 @@ const SearchCommand = dynamic(
{ ssr: false },
);
const slowShineClassName = "[--poyraz-motion-duration-deliberate:1100ms]";
function getNavLinkClass(isActive: boolean) {
return [
"inline-flex border-b-2 pb-1 text-sm transition-colors",
"inline-flex border-b-2 pb-1 text-sm transition-colors md:border-0 md:pb-0",
isActive
? "border-red-600 text-foreground"
: "border-transparent text-foreground/55 hover:text-foreground",
].join(" ");
}
type ThemeMode = "light" | "dark";
function applyTheme(theme: ThemeMode) {
document.documentElement.dataset.poyrazTheme = theme;
document.documentElement.style.colorScheme = theme;
localStorage.setItem("poyraz-theme", theme);
}
function getInitialTheme(): ThemeMode {
if (typeof window === "undefined") return "light";
const storedTheme = localStorage.getItem("poyraz-theme");
if (storedTheme === "dark" || storedTheme === "light") return storedTheme;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function ThemeToggle() {
const [theme, setTheme] = useState<ThemeMode>(getInitialTheme);
const nextTheme = theme === "dark" ? "light" : "dark";
const label = theme === "dark" ? "Light mode" : "Dark mode";
useEffect(() => {
applyTheme(theme);
}, [theme]);
return (
<Button
type="button"
variant="secondary"
size="icon-sm"
radius="sm"
effect="shine"
aria-label={label}
className={`cursor-pointer ${slowShineClassName}`}
onClick={() => setTheme(nextTheme)}
>
<Icon
icon={theme === "dark" ? "mdi:white-balance-sunny" : "mdi:moon-waning-crescent"}
width={16}
height={16}
/>
</Button>
);
}
export function SiteNavbar() {
const pathname = usePathname();
const [searchOpen, setSearchOpen] = useState(false);
@@ -54,27 +111,69 @@ export function SiteNavbar() {
return pathname === href || pathname.startsWith(`${href}/`);
};
const activeTab = NAV_LINKS.find((item) => isActiveLink(item.href))?.id;
const languageLabel = locale === "tr" ? "Switch to English" : "Türkçe'ye geç";
return (
<div className="space-y-3">
<div className="flex items-center justify-end gap-2 border-b border-border pb-3">
{TOP_ICON_LINKS.map((item) => {
const href = item.id === "cv" ? getResumeHref(locale) : item.href;
return (
<a
key={item.id}
href={href}
target={item.id === "cv" || item.external ? "_blank" : undefined}
rel={item.id === "cv" || item.external ? "noreferrer" : undefined}
aria-label={t.has(item.id) ? t(item.id) : item.label}
title={t.has(item.id) ? t(item.id) : item.label}
className="inline-flex h-8 w-8 items-center justify-center rounded-sm border border-border text-muted-foreground transition-colors hover:text-foreground"
>
<Icon icon={item.icon} width={16} height={16} />
</a>
);
})}
<LanguageSwitcher />
</div>
<TooltipProvider delayDuration={180}>
<NavbarTopBar
variant="secondary"
className="border-0 bg-transparent p-0 shadow-none"
>
<NavbarTopBarSection align="end" className="gap-2">
{TOP_ICON_LINKS.map((item) => {
const href = item.id === "cv" ? getResumeHref(locale) : item.href;
const label = t.has(item.id) ? t(item.id) : item.label;
return (
<Tooltip key={item.id}>
<TooltipTrigger asChild>
<Button
asChild
variant="secondary"
size="icon-sm"
radius="sm"
effect="shine"
aria-label={label}
className={`cursor-pointer ${slowShineClassName}`}
>
<a
href={href}
target={item.id === "cv" || item.external ? "_blank" : undefined}
rel={item.id === "cv" || item.external ? "noreferrer" : undefined}
>
<Icon icon={item.icon} width={16} height={16} />
</a>
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" surface="soft" radius="sm" size="sm">
{label}
</TooltipContent>
</Tooltip>
);
})}
<Tooltip>
<TooltipTrigger asChild>
<LanguageSwitcher />
</TooltipTrigger>
<TooltipContent side="bottom" surface="soft" radius="sm" size="sm">
{languageLabel}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<ThemeToggle />
</TooltipTrigger>
<TooltipContent side="bottom" surface="soft" radius="sm" size="sm">
{locale === "tr" ? "Tema değiştir" : "Toggle theme"}
</TooltipContent>
</Tooltip>
</NavbarTopBarSection>
</NavbarTopBar>
</TooltipProvider>
<header className="flex items-center justify-between gap-3 border-b border-border pb-4">
<Link
@@ -82,66 +181,98 @@ export function SiteNavbar() {
aria-label="Ana sayfaya git"
className="inline-flex items-center"
>
<Avatar className="h-9 w-9 rounded-sm">
<AvatarImage src="/logo/logo.jpeg" alt="Poyraz Avsever" />
<AvatarFallback className="rounded-sm bg-muted text-xs font-semibold">
PA
</AvatarFallback>
</Avatar>
<Logo
src="/logo/logo.jpeg"
alt="Poyraz Avsever"
width={40}
height={40}
radius="sm"
effect="shine-loop"
interactive
className={slowShineClassName}
/>
</Link>
<div className="hidden items-center gap-3 md:flex">
<nav aria-label="Ana navigasyon">
<ul className="flex items-center gap-3">
<Tabs value={activeTab ?? ""} className="w-auto">
<TabsList
variant="line"
radius="sm"
className="h-9 gap-1 bg-transparent p-0"
aria-label="Ana navigasyon"
>
{NAV_LINKS.map((item, index) => (
<li key={item.id} className="flex items-center gap-3">
<div key={item.id} className="flex items-center gap-1.5">
{index > 0 && (
<Separator
orientation="vertical"
className="h-4 bg-border"
className="h-4 bg-border/70"
decorative
/>
)}
<Link
href={item.href}
className={getNavLinkClass(isActiveLink(item.href))}
<TabsTrigger
value={item.id}
asChild
size="sm"
radius="sm"
className="cursor-pointer px-2.5"
>
{t(item.id)}
</Link>
</li>
<Link href={item.href}>{t(item.id)}</Link>
</TabsTrigger>
</div>
))}
</ul>
</nav>
</TabsList>
</Tabs>
<Separator
orientation="vertical"
className="h-4 bg-border"
className="h-5 bg-border"
decorative
/>
<button
<Button
type="button"
variant="secondary"
radius="sm"
effect="shine"
onClick={() => setSearchOpen(true)}
className="inline-flex h-8 w-44 cursor-pointer items-center justify-between rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground sm:w-52"
className={`h-9 w-44 cursor-pointer justify-between px-3 text-sm sm:w-52 ${slowShineClassName}`}
aria-label={t("search")}
>
<span className="inline-flex items-center gap-2">
<Icon icon="mdi:magnify" width={16} height={16} />
<span>{t("search")}</span>
</span>
<ButtonIcon>
<Icon icon="mdi:magnify" width={16} height={16} />
</ButtonIcon>
<ButtonLabel className="mr-auto">{t("search")}</ButtonLabel>
<span className="text-xs text-muted-foreground">{shortcut}</span>
</button>
</Button>
<DropdownMenu>
<DropdownMenuTrigger className="inline-flex h-8 cursor-pointer items-center gap-2 rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground">
<Icon icon="mdi:share-variant" width={16} height={16} />
<span>{t("social")}</span>
<DropdownMenu interaction="click">
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="secondary"
radius="sm"
effect="shine"
className={`h-9 cursor-pointer ${slowShineClassName}`}
>
<ButtonIcon>
<Icon icon="mdi:share-variant" width={16} height={16} />
</ButtonIcon>
<ButtonLabel>{t("social")}</ButtonLabel>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52 rounded-sm">
<DropdownMenuContent
align="end"
surface="solid"
radius="md"
itemSize="md"
itemRadius="sm"
className="w-56 bg-popover"
>
<DropdownMenuLabel>{t("socialLinks")}</DropdownMenuLabel>
<DropdownMenuSeparator />
{SOCIAL_LINKS.map((item) => (
<DropdownMenuItem key={item.id} asChild>
<Link
<DropdownMenuItem key={item.id} asChild interactiveMotion="shift">
<a
href={item.href}
target="_blank"
rel="noreferrer"
@@ -149,7 +280,7 @@ export function SiteNavbar() {
>
<Icon icon={item.icon} width={16} height={16} />
<span>{item.label}</span>
</Link>
</a>
</DropdownMenuItem>
))}
</DropdownMenuContent>
@@ -157,19 +288,45 @@ export function SiteNavbar() {
</div>
<div className="flex items-center gap-2 md:hidden">
<Button
type="button"
variant="secondary"
size="icon-sm"
radius="sm"
effect="shine"
className={`cursor-pointer ${slowShineClassName}`}
aria-label={t("search")}
onClick={() => setSearchOpen(true)}
>
<Icon icon="mdi:magnify" width={16} height={16} />
</Button>
<Sheet>
<SheetTrigger className="inline-flex h-8 cursor-pointer items-center gap-2 rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground">
<Icon icon="mdi:menu" width={18} height={18} />
<span>{t("menu")}</span>
<SheetTrigger asChild>
<Button
type="button"
variant="secondary"
radius="sm"
effect="shine"
className={`h-9 cursor-pointer ${slowShineClassName}`}
>
<ButtonIcon>
<Icon icon="mdi:menu" width={18} height={18} />
</ButtonIcon>
<ButtonLabel>{t("menu")}</ButtonLabel>
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-72 p-4">
<SheetTitle className="sr-only">{t("mobileMenu")}</SheetTitle>
<div className="flex flex-col gap-4">
<SheetClose asChild>
<button
<Button
type="button"
variant="secondary"
radius="sm"
effect="shine"
onClick={() => setSearchOpen(true)}
className="inline-flex h-9 w-full cursor-pointer items-center justify-between rounded-sm border border-border px-3 text-sm text-muted-foreground transition-colors hover:text-foreground"
className={`w-full cursor-pointer justify-between ${slowShineClassName}`}
aria-label={t("search")}
>
<span className="inline-flex items-center gap-2">
@@ -179,7 +336,7 @@ export function SiteNavbar() {
<span className="text-xs text-muted-foreground/80">
{shortcut}
</span>
</button>
</Button>
</SheetClose>
<Separator className="bg-border" decorative />
+1 -1
View File
@@ -7,7 +7,7 @@ export function SponsorsSection() {
if (!SPONSORS || SPONSORS.length === 0) return null;
return (
<section>
<section className="pt-12 md:pt-14">
<div className="grid grid-cols-2 justify-start gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
{SPONSORS.map((sponsor) => {
const logoContent = (
+2 -2
View File
@@ -82,7 +82,7 @@ Figma introduced features that enable design to transform directly into front-en
[Source link](https://uxpilot.ai/blogs/product-design-trends)
Users utilizing AI agents instead of browsing sites has introduced the concept of "Machine Experience (MX)" into our lives. In design trends, while "Glassmorphism 2.0," which heavily uses glass effects, is repopularizing, dynamic interface modes are becoming standard due to accessibility issues. It's now much more critical to offer a clean HTML/data architecture that AI agents can easily read and index, rather than just designs that look good to the eye.
Users utilizing AI agents instead of browsing sites has introduced the concept of "Machine Experience (MX)" into our lives. In design trends, while "secondarymorphism 2.0," which heavily uses secondary effects, is repopularizing, dynamic interface modes are becoming standard due to accessibility issues. It's now much more critical to offer a clean HTML/data architecture that AI agents can easily read and index, rather than just designs that look good to the eye.
### UX Market Stabilization and "AI Slop Fatigue"
@@ -96,7 +96,7 @@ According to a Nielsen Norman Group report, useless AI assistants forced into ev
[Source link](https://www.macrumors.com/2026/07/10/apple-sues-openai/)
Apple filed a federal lawsuit against OpenAI, alleging trade secret theft. The filing claims that former Apple employees leaked confidential documents and prototypes belonging to hardware projects to OpenAI. It is interesting that while two giants cooperate in software, they end up in court so fiercely in the hardware market like smart glasses. AI competition is starting to revolve not just around language models, but around the hardware those models will run on.
Apple filed a federal lawsuit against OpenAI, alleging trade secret theft. The filing claims that former Apple employees leaked confidential documents and prototypes belonging to hardware projects to OpenAI. It is interesting that while two giants cooperate in software, they end up in court so fiercely in the hardware market like smart secondaryes. AI competition is starting to revolve not just around language models, but around the hardware those models will run on.
### Meta's Operational Massacre of 8,000 People and AI Fragility
+1 -1
View File
@@ -82,7 +82,7 @@ Figma yeni tanıttığı "Code Layers" ve "Generative Plugins" gibi araçlarla t
[Kaynak linki](https://uxpilot.ai/blogs/product-design-trends)
Kullanıcıların sitelerde gezmek yerine yapay zeka aracılarını kullanması, "Makine Deneyimi (MX)" konseptini hayatımıza soktu. Tasarım trendlerinde ise cam efektlerinin yoğun kullanıldığı "Glassmorphism 2.0" tekrar popülerleşirken, erişilebilirlik sorunları nedeniyle dinamik arayüz modları standartlaşıyor. Sadece göze güzel gelen tasarımlar değil, yapay zeka ajanlarının kolayca okuyup indeksleyebileceği temiz bir HTML/veri mimarisi sunmak artık çok daha kritik.
Kullanıcıların sitelerde gezmek yerine yapay zeka aracılarını kullanması, "Makine Deneyimi (MX)" konseptini hayatımıza soktu. Tasarım trendlerinde ise cam efektlerinin yoğun kullanıldığı "secondarymorphism 2.0" tekrar popülerleşirken, erişilebilirlik sorunları nedeniyle dinamik arayüz modları standartlaşıyor. Sadece göze güzel gelen tasarımlar değil, yapay zeka ajanlarının kolayca okuyup indeksleyebileceği temiz bir HTML/veri mimarisi sunmak artık çok daha kritik.
### UX Pazarının Stabilizasyonu ve "Yapay Zeka Yorgunluğu" (AI Slop Fatigue)
+489
View File
@@ -0,0 +1,489 @@
# Poyraz Portfolio — poyraz-ui v3 Migration Plan
Bu planın amacı `poyraz-portfolio` projesini `poyraz-ui@2.x` kullanımından `poyraz-ui@3.0.0` kullanımına, mevcut sayfa düzenini ve portfolio kimliğini bozmadan geçirmek.
Öncelik sırası:
1. Önce paket geçişi ve build/type güvenliği.
2. Sonra mevcut layout davranışını koruma.
3. En son görsel polish ve yeni v3 tasarım yeteneklerini kontrollü ekleme.
> Not: Bu dokümandaki tasarım iyileştirmeleri v3 geçişinden sonra ayrı küçük fazlara bölünebilir. İlk migration hedefi “çalışan ve görsel olarak kırılmamış portfolio”dur.
---
## Mevcut Durum Özeti
- Proje Next.js `16.1.6`, React `19.2.3`, Tailwind CSS v4 kullanıyor.
- `poyraz-ui` şu anda npm paketi olarak `^2.0.1` kullanılıyor.
- `app/globals.css` içinde `@import "poyraz-ui/preset.css";` zaten mevcut.
- Componentler local registry copy olarak değil, doğrudan paket exportları üzerinden kullanılıyor.
- Aktif poyraz-ui importları şu alanlarda:
- `poyraz-ui/atoms`
- `poyraz-ui/molecules`
- `poyraz-ui/organisms`
- `poyraz-ui/themes`
Migration için avantajlı nokta: v3 tarafında kullanılan temel exportların çoğu korunmuş görünüyor. Bu nedenle geçişin büyük kısmı dependency bump + görsel/behavior doğrulama şeklinde ilerleyebilir.
---
## Risk Haritası
### Yüksek Öncelikli Riskler
- [x] `poyraz-ui@3.0.0` Node `>=22` istediği için local/Vercel Node versiyonu doğrulanmalı.
- [x] `pnpm-lock.yaml` v3 dependency graph ile temiz güncellenmeli.
- [x] `poyraz-ui/preset.css` v3 tokenları mevcut layout renklerini beklenmedik şekilde değiştirebilir.
- [x] Card template componentleri (`NewsCard`, `ImageCard`, `ArticleCard`, `TestimonialCard`) v3te daha modern/secondaryy defaultlara sahip olduğu için mevcut compact tasarımda kayma oluşturabilir.
- [x] `CommandPalette`, `Sheet`, `Modal`, `Select`, `DropdownMenu` gibi Radix tabanlı componentlerde overlay, focus ve portal davranışı görsel olarak kontrol edilmeli.
### Orta Öncelikli Riskler
- [x] `Typography` variant ölçüleri v3 tokenlarıyla farklılaşabilir.
- [x] `Badge`, `Card`, `Button`, `Input` default radius/spacing değerleri mevcut `rounded-sm` class overridelarıyla büyük ölçüde korunacak ama sayfa bazlı kontrol edilmeli.
- [x] `AnnouncementBar` v3 animasyon ve spacingi `AppShell` içinde ekstra yükseklik/boşluk yaratabilir.
- [x] Mobile navbar sheet davranışı v3 overlay/close butonu nedeniyle farklı görünebilir.
### Düşük Öncelikli Riskler
- [x] `src/lib/themes.ts` aktif kullanılmıyor; v3 theme exportlarıyla uyumlu kalması yeterli.
- [x] `reactive-switcher` şu an kullanılmıyor; dark mode geçişi v3 migration kapsamına alınmamalı.
- [x] Portfolio tasarım polish istekleri migration sonrasına bırakılmalı.
---
## Faz 0 — Hazırlık ve Baseline
Amaç: Değişiklik yapmadan önce projenin mevcut sağlığını ve migration başlangıç noktasını netleştirmek.
### Görevler
- [x] `git status --short` ile mevcut dirty dosyaları not al.
- [x] Mevcut kullanıcı değişikliklerine dokunma.
- [x] `pnpm-workspace.yaml` içinde önceden yapılmış `allowBuilds` değişikliğini kullanıcı değişikliği olarak koru.
- [x] Paket yöneticisi versiyonunu doğrula.
- [x] Node versiyonunu doğrula.
- [x] `pnpm lint` çalıştır.
- [x] `pnpm build` çalıştır.
- [x] Varsa mevcut lint/build uyarılarını migration öncesi baseline olarak not al.
### Faz 0 Notları
- Node: `v24.16.0`
- pnpm: `11.5.1`
- Baseline `pnpm lint`: başarılı, mevcut 4 warning var.
- Baseline `pnpm build`: başarılı.
- Next baseline warning: `middleware` file convention deprecated, `proxy` öneriliyor.
### Kabul Kriterleri
- [x] Migration öncesi proje durumu belgelenmiş olmalı.
- [x] Mevcut build hatası varsa v3 geçişinden bağımsız olduğu anlaşılmalı.
- [x] Kullanıcı değişiklikleri ezilmemeli.
---
## Faz 1 — Dependency Geçişi
Amaç: `poyraz-ui` paketini v3e almak ve lockfile’ı temiz güncellemek.
### Görevler
- [x] `package.json` içindeki `poyraz-ui` sürümünü `^3.0.0` yap.
- [x] `pnpm install` ile `pnpm-lock.yaml` güncelle.
- [x] `pnpm-lock.yaml` içinde `poyraz-ui@3.0.0` çözümlendiğini doğrula.
- [x] `package.json` içine gerekirse Node engine eklemeyi değerlendir:
- `node >=22`
- Bu değişiklik Vercel deploy ile uyumlu olmalı.
- [x] Vercel tarafında Node 22 kullanılacağından emin olmak için proje ayarı/notu ekle.
### Faz 1 Notları
- `poyraz-ui`: `^2.0.1``^3.0.0`
- `package.json` içine `engines.node: >=22` eklendi.
- `pnpm install` sonrası lockfile `poyraz-ui@3.0.0` olarak çözümlendi.
- pnpm, güvenlik politikası nedeniyle `pnpm-workspace.yaml` içine `minimumReleaseAgeExclude: poyraz-ui@3.0.0` ekledi.
### Kabul Kriterleri
- [x] `package.json` v3 dependency kullanıyor.
- [x] Lockfile v3e güncellendi.
- [x] Paket kurulumu temiz tamamlandı.
- [x] Kullanılmayan geçici paket/alias eklenmedi.
---
## Faz 2 — Import ve API Uyumluluk Kontrolü
Amaç: v3 sonrası TypeScript/Next build seviyesinde kırılan import veya prop kullanımlarını düzeltmek.
### Kontrol Edilecek Import Grupları
- [x] `poyraz-ui/atoms`
- `Avatar`
- `Badge`
- `Button`
- `Card`
- `Input`
- `Separator`
- `Skeleton`
- `Typography`
- [x] `poyraz-ui/molecules`
- `ArticleCard`
- `CommandPalette`
- `DropdownMenu`
- `ImageCard`
- `Modal`
- `NewsCard`
- `Pagination`
- `Select`
- `Sheet`
- `TestimonialCard`
- [x] `poyraz-ui/organisms`
- `AnnouncementBar`
- [x] `poyraz-ui/themes`
- `poyrazLightTheme`
- `poyrazDarkTheme`
- `poyrazThemes`
### Görevler
- [x] `rg "poyraz-ui"` ile tüm kullanım noktalarını tekrar listele.
- [x] `pnpm lint` çalıştır.
- [x] `pnpm build` çalıştır.
- [x] Kırılan import varsa en küçük değişiklikle düzelt.
- [x] Kırılan prop varsa component davranışını koruyarak v3 APIya uyarla.
- [x] Gereksiz refactor yapma.
### Faz 2 Notları
- V3 sonrası import/API kırığı çıkmadı.
- `pnpm lint`: başarılı, baseline ile aynı 4 warning devam ediyor.
- `pnpm build`: başarılı.
- Bu fazda component kullanımını veya layout classlarını değiştirmeye gerek kalmadı.
### Kabul Kriterleri
- [x] Tüm importlar v3 exportlarıyla uyumlu.
- [x] Type/build hatası kalmadı.
- [x] Sayfa düzenini etkileyecek gereksiz class değişikliği yapılmadı.
---
## Faz 3 — Global CSS ve Token Uyumluluğu
Amaç: v3 preset tokenlarının portfolio genel görünümünü bozmadığını doğrulamak.
### Görevler
- [x] `app/globals.css` içindeki import sırasını koru:
- `@import "tailwindcss";`
- `@import "poyraz-ui/preset.css";`
- [x] `poyraz-ui` dist utility classlarının tüketici build/dev tarafında tarandığını garantiye al.
- `@source "../node_modules/poyraz-ui/dist";`
- [x] Custom utilityleri kontrol et:
- `animate-marquee`
- `pause-on-hover`
- [x] `bg-background`, `text-foreground`, `border-border`, `muted-foreground` gibi semantic tokenların v3 karşılığını görsel olarak kontrol et.
- [x] Light mode temel kontrastı kontrol et.
- [x] Dark mode aktif değilse migration kapsamında dark mode ekleme.
### Kabul Kriterleri
- [x] Global CSS minimal kaldı.
- [x] Tailwind v4 compile sorunu yok.
- [x] Semantic tokenlar sayfa genelinde okunabilir.
### Faz 3 Ara Notları
- `ArticleCard` ve `TestimonialCard` içindeki avatar görselleri npm paketinin template classlarına (`size-6`, `size-9`) bağlı.
- Avatar boyutlarının bozulması component kullanımından değil, Tailwind source scan/dev cache tarafında `poyraz-ui/dist` classlarının kaçırılmasından kaynaklanabilir.
- Componentlere hardcoded avatar classı eklemek yerine `app/globals.css` içinde paket dist sourceu açıkça tanımlandı.
- Bu değişiklik sonrası `pnpm lint` ve `pnpm build` başarılı.
- Build CSS çıktısında `.bg-background`, `.text-foreground`, `.border-border`, `.text-muted-foreground`, `.animate-marquee`, `.pause-on-hover:hover`, `.size-6` ve `.size-9` classları doğrulandı.
- Dark mode/theme switcher bu migration fazında eklenmedi; mevcut light mode korunuyor.
---
## Faz 4 — Layout ve Shell Doğrulaması
Amaç: Site iskeletini korumak.
### Kontrol Edilecek Dosyalar
- [x] `app/[locale]/layout.tsx`
- [x] `components/app-shell.tsx`
- [x] `components/site-navbar.tsx`
- [x] `components/search-command.tsx`
- [x] `components/language-switcher.tsx`
### Görevler
- [x] Locale layout render zincirini kontrol et.
- [x] `AppShell` max width ve spacing değerlerinin değişmediğini doğrula.
- [x] `AnnouncementBar` yüksekliği ve border davranışını kontrol et.
- [x] Desktop navbar hizasını kontrol et.
- [x] Mobile navbar sheet aç/kapat davranışını kontrol et.
- [x] Search command palette aç/kapat davranışını kontrol et.
- [x] Cmd/Ctrl + K shortcut davranışını doğrula.
### Kabul Kriterleri
- [x] Navbar layout bozulmadı.
- [x] Mobile menü çalışıyor.
- [x] Search command çalışıyor.
- [x] Announcement bar sayfa içeriğini itip bozmadı.
### Faz 4 Notları
- Locale layout `NextIntlClientProvider` + `AppShell` zinciriyle korunuyor.
- `AppShell` container yapısı `max-w-4xl`, `px-4 sm:px-6`, `py-4` olarak değişmedi.
- `SiteNavbar` desktop link/search/social dropdown hattı v3 componentleriyle build ve runtime smoke testinden geçti.
- `SearchCommand` click ile açılıyor, query yazınca sonuç üretiyor ve dialog render oluyor.
- Mobile viewport smoke testinde `Menü` sheet dialogu açıldı.
- Runtime smoke sonucu:
- `navLogo`: başarılı
- `announcement`: başarılı
- `commandOpen`: başarılı
- `commandItems`: 2 sonuç
- `mobileSheet`: başarılı
---
## Faz 5 — Sayfa Bazlı Görsel Regression Kontrolü
Amaç: Mevcut portfolio düzenini v3 sonrası sayfa sayfa korumak.
### Ana Sayfa
- [x] `HomeHero` kart yerleşimi korunuyor.
- [x] `NewsCard` yükseklikleri düzgün.
- [x] Hero görsel alanı taşmıyor.
- [x] References marquee çalışıyor.
- [x] Sponsor kartları hizalı.
### About
- [x] Eğitim ve deneyim kartları compact kalıyor.
- [x] Sertifika sheet açılıyor.
- [x] Bookmark sheet açılıyor.
- [x] Referans ve gönüllülük link kartları hizalı.
### Blog
- [x] Kategori badgeleri doğru görünüyor.
- [x] Search input focus state rahatsız etmiyor.
- [x] `ArticleCard` grid yüksekliği dengeli.
- [x] Pagination active state düzgün.
- [x] Empty state bozulmadı.
### Blog Detail
- [x] Markdown typography okunabilir.
- [x] Code block görünümü korunuyor.
- [x] Mermaid block loading/error kartları düzgün.
- [x] TOC paneli taşmıyor.
- [x] Progress bar çalışıyor.
### Projects
- [x] `ImageCard` square grid düzenini bozmuyor.
- [x] NPM/GitHub kartları aynı yükseklik mantığında kalıyor.
- [x] Badge ve icon spacingleri düzgün.
### Content
- [x] YouTube cardları düzgün.
- [x] PDF preview kartları düzgün.
- [x] PDF modal açılıyor.
- [x] Prev/Next buttonları çalışıyor.
### Links
- [x] Profile card rounded/secondary görünüm korunuyor.
- [x] `Select` ve `Input` aynı satırda düzgün.
- [x] Link kartları hoverda layout shift yapmıyor.
- [x] Mobile layout taşmıyor.
### Contact / Gallery / Volunteer / References
- [x] Kartlar compact düzeni koruyor.
- [x] Linkler çalışıyor.
- [x] Galeri modal/preview davranışı korunuyor.
### Kabul Kriterleri
- [x] Kritik sayfalarda layout kırığı yok.
- [x] Horizontal overflow yok.
- [x] Component default değişimleri mevcut tasarımı bozmadı.
### Faz 5 Notları
- Production runtime smoke testte şu sayfalar desktop viewportta kontrol edildi:
- `/tr`
- `/tr/about`
- `/tr/blog`
- `/tr/projects`
- `/tr/content`
- `/tr/links`
- `/tr/contact`
- `/tr/gallery`
- `/tr/about/references`
- `/tr/about/volunteer-community`
- Tüm bu sayfalarda horizontal overflow `0` ölçüldü.
- Oversized rounded avatar/image regression bulunmadı.
- Blog empty state `/tr/blog?search=unlikelymigrationquery` ile doğrulandı.
- Blog detail sayfasında progress bar ve TOC render doğrulandı.
- Gallery preview/modal akışı 15 galeri itemı üzerinden doğrulandı.
---
## Faz 6 — Davranış ve Etkileşim Testleri
Amaç: Sadece build almakla kalmayıp interaktif alanları doğrulamak.
### Manuel Test Listesi
- [x] Navbar linkleri locale-aware route ediyor.
- [x] Language switcher routeu bozmadan locale değiştiriyor.
- [x] Social dropdown açılıyor/kapanıyor.
- [x] Mobile sheet açılıyor/kapanıyor.
- [x] Search palette:
- [x] Click ile açılıyor.
- [x] Cmd/Ctrl + K ile açılıyor.
- [x] Arama filtreliyor.
- [x] Internal linke gidiyor.
- [x] External link yeni sekme açıyor.
- [x] About sheetleri açılıyor/kapanıyor.
- [x] Atatürk widget modal açılıyor.
- [x] PDF modal açılıyor.
- [x] PDF modal içinde prev/next çalışıyor.
- [x] Blog pagination linkleri doğru query üretir.
### Kabul Kriterleri
- [x] Temel kullanıcı akışlarında regression yok.
- [x] Focus trap/overlay kilitlenmesi yok.
- [x] Body scroll lock sorun çıkarmıyor.
### Faz 6 Notları
- Navbar `/tr``/tr/about` locale-aware route akışı geçti.
- Language switcher `/tr``/en` akışı geçti.
- Social dropdown açılışı doğrulandı.
- Search palette:
- Click ile açıldı.
- `Ctrl+K` ile açıldı.
- `blog` araması sonuç döndürdü.
- Internal item route etti.
- `GitHub` external item yeni sayfa hedefi olarak `https://github.com/poyrazavsever` açtı.
- About certificate ve bookmark sheetleri açıldı.
- Atatürk widget modalı açıldı.
- Content PDF modalı 27 PDF card button üzerinden doğrulandı; prev/next kontrolleri modalda göründü.
- Blog pagination linkleri `/blog?page=2` ve `/blog?page=3` queryleriyle doğrulandı.
- Mobile sheet açıldığında body scroll lock aktifti.
---
## Faz 7 — Build, Lint ve Release Readiness
Amaç: Deploy edilebilir temiz durum elde etmek.
### Görevler
- [x] `pnpm lint`
- [x] `pnpm build`
- [x] `git diff --check`
- [x] `git status --short`
- [x] Build sırasında Next warningleri not al.
- [x] Vercel Node 22 gereksinimi için deploy notu çıkar.
### Kabul Kriterleri
- [x] Build başarılı.
- [x] Lint başarılı ya da mevcut baseline dışı yeni hata yok.
- [x] Diff sadece migration kapsamındaki dosyalardan oluşuyor.
### Faz 7 Notları
- `pnpm lint`: başarılı. Baseline dışı yeni hata yok; mevcut 4 warning devam ediyor:
- `components/blog-detail-content.tsx`: `@next/next/no-img-element`
- `components/links-content.tsx`: `@next/next/no-img-element`
- `scripts/fetch-medium-all.mjs`: kullanılmayan `mkdirSync`
- `scripts/test-medium-scraper.mjs`: kullanılmayan `response`
- `pnpm build`: başarılı.
- Next build warning: `middleware` file convention deprecated, `proxy` öneriliyor.
- `git diff --check`: temiz.
- `git status --short`: yalnızca migration kapsamındaki 5 dosya değişti:
- `package.json`
- `pnpm-lock.yaml`
- `pnpm-workspace.yaml`
- `app/globals.css`
- `docs/poyraz-ui-v3-migration-plan.md`
- Deploy notu: `poyraz-ui@3.0.0` Node `>=22` istediği için Vercel project Node.js runtime ayarı Node 22+ olmalı. `package.json` içinde `engines.node: >=22` eklendi.
---
## Faz 8 — Commit ve Handoff
Amaç: Migration değişikliklerini okunabilir şekilde teslim etmek.
### Önerilen Commit Gruplaması
1. `docs: plan poyraz-ui v3 migration`
- Sadece bu plan dosyası.
2. `chore: migrate portfolio to poyraz-ui v3`
- `package.json`
- `pnpm-lock.yaml`
- Gerekirse `pnpm-workspace.yaml`
- Gerekirse küçük API uyumluluk düzeltmeleri.
3. Eğer görsel uyumluluk düzeltmesi gerekiyorsa:
- `fix: preserve portfolio layout with poyraz-ui v3`
### Kabul Kriterleri
- [x] Commitler anlamlı ve review edilebilir.
- [x] Kullanıcı değişiklikleri commit kapsamına yanlışlıkla alınmadı.
- [x] Final notta test sonuçları açıkça yazıldı.
### Faz 8 Notları
- Commitler migration kapsamını okunabilir tutacak şekilde şu mantıkla ayrıldı:
1. Plan/checklist dokümantasyonu.
2. `poyraz-ui@3.0.0` dependency ve lockfile geçişi.
3. Tailwind v4 consumer source scan düzeltmesi.
- Commit kapsamı yalnızca migration için değişen dosyalarla sınırlandı.
---
## Migration Sonrasına Bırakılacak Tasarım İşleri
Bu işler v3 geçişinden sonra ayrıca ele alınmalı:
- [ ] Portfolio genelini daha secondaryy/soft v3 tarzına yaklaştırma.
- [ ] Button hover animasyonlarını portfolio CTAlarına uygulama.
- [ ] Typography text effectlerini hero/blog başlıklarında kullanma.
- [ ] Card variantlarını sayfa bazlı standardize etme.
- [ ] Dark mode/theme switcher ekleme.
- [ ] Links sayfasını daha modern micro-landing görünümüne taşıma.
- [ ] Navbar ve command palette görsel polish.
- [ ] Blog kartlarında daha zengin image/metadata layout.
---
## İlk Uygulama Sırası
Migrationa başlarken uygulanacak pratik sıra:
1. Faz 0 baseline.
2. Faz 1 dependency geçişi.
3. Faz 2 import/API kontrolü.
4. Faz 7 build/lint doğrulaması.
5. Faz 4 ve Faz 5 hızlı görsel kontrol.
6. Gerekiyorsa küçük düzeltmeler.
7. Commit.
Bu sıra major geçişi hızlı tamamlamaya, tasarım detaylarını ise yayın sonrası küçük ve güvenli iterasyonlara bırakmaya uygundur.
+16 -1
View File
@@ -15,6 +15,15 @@
},
"Home": {
"role": "I build <strong>full-stack products</strong>, shape experiences with <strong>AI systems</strong>, and explain <strong>technology-design-software</strong> through practical content.",
"heroDescription": "I build modern web products, user-focused interfaces, and production-ready software experiences.",
"hireMe": "Hire him",
"downloadCv": "Download CV",
"readMyPosts": "Have You Read My Posts?",
"allPosts": "All posts",
"projectsTitle": "Projects",
"allProjects": "All projects",
"reviewsAndReferences": "Reviews & References",
"allReferences": "All references",
"sections": {
"recentPosts": "Recent Posts",
"references": "References",
@@ -23,12 +32,17 @@
}
},
"About": {
"title": "About me",
"contactCta": "Contact me",
"educationTitle": "Education",
"experienceTitle": "Experience",
"certificatesTitle": "My certificates",
"aboutText": "Ever since I met software in middle school, I've been constantly learning and improving. My passion for learning grew in high school, where I explored different web development technologies. Near the end of high school, I stepped into the mobile world. Today, I actively build, develop, and most importantly, I am still learning.",
"allCertificates": "All Certificates",
"referencesTitle": "References",
"referencesDesc": "View all references on a separate detail page.",
"volunteerTitle": "Volunteering & Community",
"volunteerDesc": "Inspect timeline and contribution details on a separate page.",
"volunteerDesc": "Explore my community work and timeline.",
"bookmarksTitle": "Bookmarks",
"bookmarksDesc": "View quick access links inside a sheet.",
"timelineLabel": "Timeline:",
@@ -98,6 +112,7 @@
},
"Content": {
"youtubeTitle": "Latest YouTube Videos",
"youtubeChannel": "My YouTube channel",
"youtubeEmbedTitle": "YouTube video player",
"pdfTitle": "LinkedIn PDF Notes",
"pdfPreviewError": "Preview could not be loaded",
+16 -1
View File
@@ -15,6 +15,15 @@
},
"Home": {
"role": "<strong>Full-stack ürünler</strong> geliştiriyor, <strong>yapay zeka sistemleriyle</strong> deneyimler kuruyor ve <strong>teknoloji-tasarım-yazılımı</strong> uygulanabilir içeriklerle anlatıyorum.",
"heroDescription": "Modern web ürünleri, kullanıcı odaklı arayüzler ve üretime hazır yazılım deneyimleri geliştiriyorum.",
"hireMe": "İşe al",
"downloadCv": "CV indir",
"readMyPosts": "Yazılarımı Okudun Mu",
"allPosts": "Tüm yazılar",
"projectsTitle": "Projeler",
"allProjects": "Tüm projeler",
"reviewsAndReferences": "Yorumlar & Referanslar",
"allReferences": "Tüm referanslar",
"sections": {
"recentPosts": "Son Yazılar",
"references": "Referanslar",
@@ -23,12 +32,17 @@
}
},
"About": {
"title": "Hakkımda",
"contactCta": "İletişime geç",
"educationTitle": "Eğitim",
"experienceTitle": "Deneyim",
"certificatesTitle": "Sertifikalarım",
"aboutText": "Ortaokulda yazılımla tanıştığımdan beri sürekli öğrenmeye ve kendimi geliştirmeye çalışıyorum. Lise yıllarında öğrenme hevesim daha da arttı ve web geliştirme tarafında farklı teknolojileri keşfetme fırsatı buldum. Lisenin sonlarına doğru mobil dünyasına da adım attım. Şu an aktif olarak üretiyor, geliştiriyor ve en önemlisi hâlâ öğrenmeye devam ediyorum.",
"allCertificates": "Tüm Sertifikalar",
"referencesTitle": "Referanslar",
"referencesDesc": "Tüm referansları ayrı bir detay sayfasında görüntüle.",
"volunteerTitle": "Gönüllülük ve Topluluk",
"volunteerDesc": "Zaman çizelgesi ve katkı detaylarını ayrı bir detay sayfasında incele.",
"volunteerDesc": "Topluluk katkılarımı ve zaman çizelgemi incele.",
"bookmarksTitle": "Yer İmleri",
"bookmarksDesc": "Hızlı erişim bağlantılarını bir sheet içinde görüntüle.",
"timelineLabel": "Zaman Çizelgesi:",
@@ -98,6 +112,7 @@
},
"Content": {
"youtubeTitle": "Son YouTube Videoları",
"youtubeChannel": "YouTube kanalım",
"youtubeEmbedTitle": "YouTube video oynatıcı",
"pdfTitle": "LinkedIn PDF Notları",
"pdfPreviewError": "Önizleme yüklenemedi",
+4 -1
View File
@@ -2,6 +2,9 @@
"name": "portfolio-new",
"version": "0.1.0",
"private": true,
"engines": {
"node": ">=22"
},
"scripts": {
"dev": "next dev",
"build": "next build",
@@ -23,7 +26,7 @@
"next": "16.1.6",
"next-intl": "4.13.0",
"pdfjs-dist": "^5.5.207",
"poyraz-ui": "^2.0.1",
"poyraz-ui": "^3.0.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-hook-form": "^7.71.2",
+12 -6
View File
@@ -30,8 +30,8 @@ importers:
specifier: ^5.5.207
version: 5.5.207
poyraz-ui:
specifier: ^2.0.1
version: 2.0.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.71.2(react@19.2.3))(react@19.2.3)(tailwindcss@4.2.1)(zod@4.3.6)
specifier: ^3.0.0
version: 3.0.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(mermaid@11.13.0)(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.71.2(react@19.2.3))(react@19.2.3)(tailwindcss@4.2.1)(zod@4.3.6)
react:
specifier: 19.2.3
version: 19.2.3
@@ -1765,6 +1765,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
@@ -4348,11 +4349,13 @@ packages:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
poyraz-ui@2.0.1:
resolution: {integrity: sha512-aTkBXorwdftdmUZAk3RkDxAK/KjP2GtXEf0PUdc+EXnH2bMszaZJsARF3dV8Imv52qLXqtZLBP1kJJQPPr0Pag==}
poyraz-ui@3.0.0:
resolution: {integrity: sha512-1gfbxuBncvjaMa129H2YCNYwwzX1hLdnqmwdoaap3la96quw7wagNVS2Q9Sm066vSQHaHl7YQtNO+Eq5MuDJ0Q==}
engines: {node: '>=22', pnpm: '>=11.5.1'}
hasBin: true
peerDependencies:
'@hookform/resolvers': '>=3'
mermaid: '>=11'
react: '>=18'
react-dom: '>=18'
react-hook-form: '>=7'
@@ -4362,6 +4365,8 @@ packages:
peerDependenciesMeta:
'@hookform/resolvers':
optional: true
mermaid:
optional: true
react-hook-form:
optional: true
reactive-switcher:
@@ -5172,7 +5177,7 @@ packages:
uuid@3.4.0:
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
vaul@1.1.2:
@@ -10107,7 +10112,7 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
poyraz-ui@2.0.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.71.2(react@19.2.3))(react@19.2.3)(tailwindcss@4.2.1)(zod@4.3.6):
poyraz-ui@3.0.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(mermaid@11.13.0)(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.71.2(react@19.2.3))(react@19.2.3)(tailwindcss@4.2.1)(zod@4.3.6):
dependencies:
'@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-avatar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -10136,6 +10141,7 @@ snapshots:
tailwindcss: 4.2.1
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
optionalDependencies:
mermaid: 11.13.0
react-hook-form: 7.71.2(react@19.2.3)
zod: 4.3.6
transitivePeerDependencies:
+9 -6
View File
@@ -10,9 +10,12 @@ ignoredBuiltDependencies:
- unrs-resolver
allowBuilds:
'@parcel/watcher': set this to true or false
'@swc/core': set this to true or false
electron: set this to true or false
puppeteer: set this to true or false
sharp: set this to true or false
unrs-resolver: set this to true or false
'@parcel/watcher': true
'@swc/core': true
electron: true
puppeteer: true
sharp: true
unrs-resolver: true
minimumReleaseAgeExclude:
- poyraz-ui@3.0.0