Files
poyraz-portfolio/components/media-kit-content.tsx
T

559 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { Icon } from "@iconify/react";
import Image from "next/image";
import {
Badge,
Button,
ButtonIcon,
ButtonLabel,
Card,
Typography,
} from "poyraz-ui/atoms";
import { SPONSORS } from "@/data/sponsors";
import {
MEDIA_KIT_AGES,
MEDIA_KIT_DEVICES,
MEDIA_KIT_GENDER,
MEDIA_KIT_LOCATIONS,
MEDIA_KIT_METRICS,
MEDIA_KIT_PERIOD,
MEDIA_KIT_TOPICS,
type MediaKitAudienceRow,
type MediaKitBreakdownRow,
type MediaKitLocale,
} from "@/data/media-kit";
import type { YouTubeChannelStats } from "@/lib/youtube-channel-stats";
const COPY = {
tr: {
eyebrow: "Medya kiti · 2026",
availability: "İş birliklerine açık",
title: "Teknolojiyi üretiyor, deneyimliyor ve anlaşılır şekilde paylaşıyorum.",
intro:
"Yazılım, teknoloji ve tasarımı gerçek projeler, dürüst deneyimler ve uygulanabilir anlatımlarla buluşturuyorum. Markalarla çalışırken ürünün doğal kullanımını ve izleyici güvenini merkeze alıyorum.",
emailCta: "İletişime geç",
youtubeCta: "YouTube kanalı",
topicsLabel: "İçerik alanları",
statsTitle: "Kanal görünümü",
liveData: "Canlı YouTube verisi",
liveDescription:
"Görüntüleme ve abone toplamı saatlik yenilenir; diğer değerler YouTube Studio raporundan gelir.",
snapshotDescription:
"Ömür boyu performans ve güncel kitle kırılımları YouTube Studio raporundan alınmıştır.",
publicTotal: "Herkese açık kanal toplamı",
audienceTitle: "Kitle profili",
audienceDescription:
"İzleyicilerin %69,1i 1834 yaş aralığında ve kitlenin %89,2si Türkiyeden izliyor.",
ageTitle: "Yaş dağılımı",
genderTitle: "Cinsiyet dağılımı",
deviceTitle: "Cihaz türü",
locationsTitle: "En çok izleyen yerler",
activeTitle: "İzleyicilerin aktif olduğu saatler",
activeHours: "16:0000:00",
activeDescription:
"Hafta boyunca en yoğun zaman aralığı. Hafta sonu hareketlilik öğleden sonra daha erken başlıyor.",
activePeriod: "Yerel saat · GMT +03:00 · Son 28 gün",
male: "Erkek",
female: "Kadın",
partnersTitle: "Birlikte çalıştığım markalar",
partnersDescription:
"Ürünü gerçekten deneyimlemeye ve içeriğin doğal akışını korumaya dayalı seçili iş birlikleri.",
formatsTitle: "İş birliği formatları",
formats: [
"Sponsorlu YouTube videosu veya doğal ürün entegrasyonu",
"Teknik ürün incelemesi ve kullanım senaryosu",
"Kısa video ve çoklu platform içerik paketi",
"Uzun dönem marka elçiliği ve içerik serisi",
],
ctaTitle: "Teknoloji kitlesine güvenilir bir hikâyeyle ulaşın.",
ctaDescription:
"Ürününüzü anlayan, deneyimleyen ve izleyicisine açık bir dille anlatan bir iş birliği kurgulayalım.",
dataNote:
"Performans, demografi, cihaz ve ülke kırılımları ömür boyudur. Aktif saatler son 28 günü gösterir.",
},
en: {
eyebrow: "Media kit · 2026",
availability: "Open to partnerships",
title: "I build, experience, and explain technology in a clear way.",
intro:
"I bring software, technology, and design together through real projects, honest experiences, and practical storytelling. Brand work is grounded in natural product use and audience trust.",
emailCta: "Get in touch",
youtubeCta: "YouTube channel",
topicsLabel: "Content topics",
statsTitle: "Channel overview",
liveData: "Live YouTube data",
liveDescription:
"View and subscriber totals refresh hourly; other figures come from YouTube Studio reports.",
snapshotDescription:
"Lifetime performance and current audience breakdowns come from YouTube Studio reports.",
publicTotal: "Public channel total",
audienceTitle: "Audience profile",
audienceDescription:
"69.1% of viewers are between 18 and 34, and 89.2% of the audience watches from Türkiye.",
ageTitle: "Age distribution",
genderTitle: "Gender distribution",
deviceTitle: "Device type",
locationsTitle: "Top geographies",
activeTitle: "When viewers are on YouTube",
activeHours: "4:00 PM12:00 AM",
activeDescription:
"The busiest window throughout the week. Weekend activity starts earlier in the afternoon.",
activePeriod: "Local time · GMT +03:00 · Last 28 days",
male: "Male",
female: "Female",
partnersTitle: "Brands I have worked with",
partnersDescription:
"Selected partnerships based on genuine product experience and preserving the natural flow of the content.",
formatsTitle: "Partnership formats",
formats: [
"Sponsored YouTube video or native product integration",
"Technical product review and use-case content",
"Short-form video and multi-platform content package",
"Long-term brand ambassadorship and content series",
],
ctaTitle: "Reach a technology audience through a credible story.",
ctaDescription:
"Lets shape a partnership that understands, experiences, and explains your product clearly.",
dataNote:
"Performance, demographic, device, and geography breakdowns are lifetime figures. Active hours cover the last 28 days.",
},
} as const;
function formatCompactNumber(value: number, locale: MediaKitLocale) {
return new Intl.NumberFormat(locale === "tr" ? "tr-TR" : "en-US", {
notation: "compact",
maximumFractionDigits: 1,
}).format(value);
}
function AudienceBar({
item,
locale,
max = 100,
label,
}: {
item: MediaKitAudienceRow;
locale: MediaKitLocale;
max?: number;
label?: string;
}) {
const width = Math.max((item.value / max) * 100, item.value > 0 ? 2 : 0);
const formattedValue = item.value.toLocaleString(
locale === "tr" ? "tr-TR" : "en-US",
{ maximumFractionDigits: 1 },
);
return (
<div className="grid grid-cols-[3.5rem_1fr_3.25rem] items-center gap-3">
<Typography variant="small" className="text-xs font-medium">
{label ?? item.label}
</Typography>
<div
className="h-1.5 overflow-hidden rounded-full bg-muted"
role="img"
aria-label={`${label ?? item.label}: %${formattedValue}`}
>
<div
className="h-full rounded-full bg-primary"
style={{ width: `${width}%` }}
/>
</div>
<Typography
variant="caption"
className="text-right tabular-nums text-muted-foreground"
>
%{formattedValue}
</Typography>
</div>
);
}
function BreakdownBar({
item,
locale,
max = 100,
}: {
item: MediaKitBreakdownRow;
locale: MediaKitLocale;
max?: number;
}) {
const width = Math.max((item.value / max) * 100, item.value > 0 ? 2 : 0);
const formattedValue = item.value.toLocaleString(
locale === "tr" ? "tr-TR" : "en-US",
{ maximumFractionDigits: 1 },
);
return (
<div>
<div className="mb-2 flex items-center justify-between gap-3">
<Typography variant="small" className="text-xs font-medium">
{item.label[locale]}
</Typography>
<Typography
variant="caption"
className="tabular-nums text-muted-foreground"
>
%{formattedValue}
</Typography>
</div>
<div
className="h-1.5 overflow-hidden rounded-full bg-muted"
role="img"
aria-label={`${item.label[locale]}: %${formattedValue}`}
>
<div
className="h-full rounded-full bg-primary"
style={{ width: `${width}%` }}
/>
</div>
</div>
);
}
function MetricCard({
label,
value,
detail,
icon,
}: {
label: string;
value: string;
detail: string;
icon: string;
}) {
return (
<Card className="rounded-sm border-border p-4">
<div className="flex items-start justify-between gap-3">
<div>
<Typography variant="caption" className="text-muted-foreground">
{label}
</Typography>
<Typography
variant="h3"
className="mt-1 font-secondary tracking-[-0.035em] tabular-nums"
>
{value}
</Typography>
</div>
<span className="flex size-8 shrink-0 items-center justify-center rounded-sm bg-primary/10 text-primary">
<Icon icon={icon} width={17} height={17} />
</span>
</div>
<Typography variant="caption" className="mt-3 block text-muted-foreground">
{detail}
</Typography>
</Card>
);
}
export function MediaKitContent({
locale,
youtubeStats,
}: {
locale: MediaKitLocale;
youtubeStats: YouTubeChannelStats | null;
}) {
const copy = COPY[locale];
const metrics = MEDIA_KIT_METRICS.map((metric) => {
let value = metric.value[locale];
let detail = metric.detail[locale];
if (youtubeStats && metric.id === "views") {
value = formatCompactNumber(youtubeStats.views, locale);
detail = copy.publicTotal;
}
if (youtubeStats && metric.id === "subscribers") {
value =
youtubeStats.subscribers !== null
? formatCompactNumber(youtubeStats.subscribers, locale)
: value;
detail = copy.publicTotal;
}
return {
id: metric.id,
label: metric.label[locale],
value,
detail,
icon: metric.icon,
};
});
return (
<section className="flex h-full flex-col overflow-x-hidden">
<header className="border-b border-border py-4 sm:py-5">
<div className="flex flex-wrap items-center gap-2">
<Badge variant="secondary" radius="sm">
{copy.eyebrow}
</Badge>
<Badge variant="success" radius="full" size="sm" className="gap-1.5">
<span className="size-1.5 rounded-full bg-current" />
{copy.availability}
</Badge>
</div>
<Typography
variant="h2"
component="h1"
className="mt-4 max-w-2xl font-secondary text-2xl font-semibold leading-tight tracking-[-0.045em]"
>
{copy.title}
</Typography>
<Typography
variant="small"
className="mt-3 max-w-2xl text-xs leading-5 text-muted-foreground sm:text-sm sm:leading-6"
>
{copy.intro}
</Typography>
<div className="mt-4 flex flex-wrap gap-2">
<Button asChild size="sm" radius="sm" effect="swap" swapTarget="both">
<a href="mailto:poyrazavsever@gmail.com">
<ButtonLabel>{copy.emailCta}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</a>
</Button>
<Button
asChild
variant="secondary"
size="sm"
radius="sm"
effect="swap"
swapTarget="both"
>
<a
href="https://youtube.com/@poyrazavsever"
target="_blank"
rel="noreferrer"
>
<ButtonIcon>
<Icon icon="mdi:youtube" width={16} height={16} />
</ButtonIcon>
<ButtonLabel>{copy.youtubeCta}</ButtonLabel>
</a>
</Button>
</div>
</header>
<section className="space-y-3 pt-12 md:pt-14">
<div className="flex flex-wrap items-end justify-between gap-3">
<div>
<Typography variant="h3" component="h2" className="tracking-[-0.035em]">
{copy.statsTitle}
</Typography>
<Typography variant="small" className="mt-1 max-w-xl text-xs leading-5 text-muted-foreground">
{youtubeStats ? copy.liveDescription : copy.snapshotDescription}
</Typography>
</div>
<Badge variant={youtubeStats ? "success" : "secondary"} radius="sm" className="gap-1.5">
{youtubeStats ? (
<span className="size-1.5 rounded-full bg-current" />
) : (
<Icon icon="mdi:calendar-outline" width={13} height={13} />
)}
{youtubeStats ? copy.liveData : MEDIA_KIT_PERIOD[locale]}
</Badge>
</div>
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
{metrics.map((metric) => (
<MetricCard
key={metric.id}
label={metric.label}
value={metric.value}
detail={metric.detail}
icon={metric.icon}
/>
))}
</div>
</section>
<section className="space-y-3 pt-12 md:pt-14">
<div>
<Typography variant="h3" component="h2" className="tracking-[-0.035em]">
{copy.audienceTitle}
</Typography>
<Typography variant="small" className="mt-1 max-w-xl text-xs leading-5 text-muted-foreground">
{copy.audienceDescription}
</Typography>
</div>
<div className="grid gap-2 md:grid-cols-2">
<Card className="rounded-sm border-border p-4">
<Typography variant="large" component="h3" className="text-sm font-semibold">
{copy.ageTitle}
</Typography>
<div className="mt-5 space-y-3">
{MEDIA_KIT_AGES.map((item) => (
<AudienceBar key={item.id} item={item} locale={locale} max={50} />
))}
</div>
</Card>
<Card className="rounded-sm border-border p-4">
<Typography variant="large" component="h3" className="text-sm font-semibold">
{copy.genderTitle}
</Typography>
<div className="mt-5 space-y-4">
{MEDIA_KIT_GENDER.map((item) => (
<AudienceBar
key={item.id}
item={item}
locale={locale}
label={item.id === "male" ? copy.male : copy.female}
/>
))}
</div>
</Card>
<Card className="rounded-sm border-border p-4">
<Typography variant="large" component="h3" className="text-sm font-semibold">
{copy.deviceTitle}
</Typography>
<div className="mt-5 space-y-5">
{MEDIA_KIT_DEVICES.map((item) => (
<BreakdownBar
key={item.id}
item={item}
locale={locale}
max={50}
/>
))}
</div>
</Card>
<Card className="rounded-sm border-border p-4">
<Typography variant="large" component="h3" className="text-sm font-semibold">
{copy.locationsTitle}
</Typography>
<div className="mt-5 space-y-5">
{MEDIA_KIT_LOCATIONS.map((item) => (
<BreakdownBar key={item.id} item={item} locale={locale} />
))}
</div>
</Card>
<Card className="rounded-sm border-border p-4 md:col-span-2">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<Typography variant="large" component="h3" className="text-sm font-semibold">
{copy.activeTitle}
</Typography>
<Typography variant="caption" className="mt-1 block text-muted-foreground">
{copy.activePeriod}
</Typography>
</div>
<span className="flex size-8 shrink-0 items-center justify-center rounded-sm bg-primary/10 text-primary">
<Icon icon="mdi:clock-time-four-outline" width={17} height={17} />
</span>
</div>
<Typography
variant="h3"
className="mt-5 font-secondary tracking-[-0.035em] tabular-nums"
>
{copy.activeHours}
</Typography>
<Typography variant="small" className="mt-2 max-w-xl text-xs leading-5 text-muted-foreground">
{copy.activeDescription}
</Typography>
</Card>
</div>
<Typography variant="caption" className="block text-muted-foreground">
{copy.dataNote}
</Typography>
</section>
<section className="space-y-3 pt-12 md:pt-14">
<div>
<Typography variant="h3" component="h2" className="tracking-[-0.035em]">
{copy.partnersTitle}
</Typography>
<Typography variant="small" className="mt-1 max-w-xl text-xs leading-5 text-muted-foreground">
{copy.partnersDescription}
</Typography>
</div>
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
{SPONSORS.map((sponsor) => {
const card = (
<Card className="flex h-24 items-center justify-center rounded-sm border-border bg-card p-4 transition-colors group-hover:border-primary/40">
<div className="relative h-full w-full rounded-sm bg-white p-2">
<Image
src={sponsor.logo}
alt={sponsor.name}
fill
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 220px"
className="object-contain p-2 grayscale transition duration-300 group-hover:grayscale-0"
/>
</div>
</Card>
);
return sponsor.websiteUrl ? (
<a
key={sponsor.id}
href={sponsor.websiteUrl}
target="_blank"
rel="noreferrer"
className="group"
>
{card}
</a>
) : (
<div key={sponsor.id}>{card}</div>
);
})}
</div>
</section>
<section className="grid gap-3 pt-12 md:grid-cols-[0.9fr_1.1fr] md:pt-14">
<Card className="rounded-sm border-border p-5">
<Typography variant="h3" component="h2" className="tracking-[-0.035em]">
{copy.formatsTitle}
</Typography>
<div className="mt-4 space-y-3">
{copy.formats.map((item) => (
<div key={item} className="flex items-start gap-2.5">
<Icon icon="mdi:check-circle-outline" width={17} height={17} className="mt-0.5 shrink-0 text-primary" />
<Typography variant="small" className="text-xs leading-5 text-muted-foreground">
{item}
</Typography>
</div>
))}
</div>
</Card>
<Card className="rounded-sm border-primary/25 bg-primary/5 p-5">
<Typography variant="h3" component="h2" className="max-w-md tracking-[-0.035em]">
{copy.ctaTitle}
</Typography>
<Typography variant="small" className="mt-2 max-w-lg text-xs leading-5 text-muted-foreground">
{copy.ctaDescription}
</Typography>
<div className="mt-5">
<Button asChild size="sm" radius="sm" effect="swap" swapTarget="both">
<a href="mailto:poyrazavsever@gmail.com">
<ButtonLabel>{copy.emailCta}</ButtonLabel>
<ButtonIcon>
<Icon icon="mdi:arrow-right" width={15} height={15} />
</ButtonIcon>
</a>
</Button>
</div>
</Card>
</section>
<section className="pt-8">
<Typography variant="caption" className="text-muted-foreground">
{copy.topicsLabel}: {MEDIA_KIT_TOPICS.map((topic) => topic.label[locale]).join(" · ")}
</Typography>
</section>
</section>
);
}