feat(layout): add animated promotional side rails
This commit is contained in:
+24
-2
@@ -9,6 +9,7 @@ import { AppShell } from "@/components/app-shell";
|
||||
import { GoogleAnalytics } from "@/components/google-analytics";
|
||||
import { PoyrazBottomRightFollower } from "@/components/poyraz-bottom-right-follower";
|
||||
import { listAnimationSources } from "@/data/animation-sources";
|
||||
import { getHomeBlogNews, getLatestAgendaArticle } from "@/data/blog";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -116,9 +117,12 @@ export default async function LocaleLayout({
|
||||
}
|
||||
|
||||
// Provide messages for NextIntlClientProvider
|
||||
const [messages, animationSources] = await Promise.all([
|
||||
const [messages, animationSources, latestAgendaArticle, latestPosts] =
|
||||
await Promise.all([
|
||||
getMessages(),
|
||||
listAnimationSources(locale),
|
||||
getLatestAgendaArticle(locale),
|
||||
getHomeBlogNews(locale, 1),
|
||||
]);
|
||||
const animationSourceSearchItems = animationSources.map((source) => ({
|
||||
slug: source.slug,
|
||||
@@ -133,7 +137,25 @@ export default async function LocaleLayout({
|
||||
<body className="min-h-dvh bg-background text-foreground antialiased">
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<GoogleAnalytics />
|
||||
<AppShell animationSources={animationSourceSearchItems}>
|
||||
<AppShell
|
||||
animationSources={animationSourceSearchItems}
|
||||
latestAgenda={
|
||||
latestAgendaArticle
|
||||
? {
|
||||
title: latestAgendaArticle.title,
|
||||
href: latestAgendaArticle.href,
|
||||
}
|
||||
: null
|
||||
}
|
||||
latestPost={
|
||||
latestPosts[0]
|
||||
? {
|
||||
title: latestPosts[0].title,
|
||||
href: latestPosts[0].href,
|
||||
}
|
||||
: null
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</AppShell>
|
||||
<PoyrazBottomRightFollower />
|
||||
|
||||
+24
-13
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { usePathname } from "@/i18n/routing";
|
||||
import { AnnouncementBar } from "poyraz-ui/organisms";
|
||||
import { SiteNavbar } from "@/components/site-navbar";
|
||||
import { NekoFollower } from "@/components/neko-follower";
|
||||
@@ -10,6 +9,11 @@ import { useLocale } from "next-intl";
|
||||
import { getLocalizedValue } from "@/lib/locale";
|
||||
import dynamic from "next/dynamic";
|
||||
import type { AnimationSourceSearchItem } from "@/lib/command-palette-links";
|
||||
import {
|
||||
LayoutLeftPromoRail,
|
||||
LayoutRightPromoRail,
|
||||
type LayoutContentPromo,
|
||||
} from "@/components/layout-promo-rails";
|
||||
|
||||
const AtaturkWidgetModal = dynamic(
|
||||
() => import("@/components/ataturk-widget-modal").then((mod) => mod.AtaturkWidgetModal),
|
||||
@@ -19,6 +23,8 @@ const AtaturkWidgetModal = dynamic(
|
||||
type AppShellProps = {
|
||||
children: React.ReactNode;
|
||||
animationSources: AnimationSourceSearchItem[];
|
||||
latestAgenda: LayoutContentPromo | null;
|
||||
latestPost: LayoutContentPromo | null;
|
||||
};
|
||||
|
||||
export type ThemeMode = "light" | "dark";
|
||||
@@ -32,15 +38,15 @@ function getInitialTheme(): ThemeMode {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
export function AppShell({ children, animationSources }: AppShellProps) {
|
||||
const pathname = usePathname();
|
||||
export function AppShell({
|
||||
children,
|
||||
animationSources,
|
||||
latestAgenda,
|
||||
latestPost,
|
||||
}: AppShellProps) {
|
||||
const locale = useLocale();
|
||||
const announcement = ANNOUNCEMENT_ITEMS[0];
|
||||
const [theme, setTheme] = useState<ThemeMode>(getInitialTheme);
|
||||
const isStandaloneLinksPage =
|
||||
pathname === "/links" || pathname.startsWith("/links/");
|
||||
const isStandaloneMediaKitPage =
|
||||
pathname === "/media-kit" || pathname.startsWith("/media-kit/");
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.dataset.poyrazTheme = theme;
|
||||
@@ -48,17 +54,19 @@ export function AppShell({ children, animationSources }: AppShellProps) {
|
||||
localStorage.setItem("poyraz-theme", theme);
|
||||
}, [theme]);
|
||||
|
||||
if (isStandaloneLinksPage || isStandaloneMediaKitPage) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const localizedText = announcement ? getLocalizedValue(announcement.text, locale) : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<AtaturkWidgetModal theme={theme} />
|
||||
{ENABLE_NEKO_FOLLOWER ? <NekoFollower /> : null}
|
||||
<div className="mx-auto flex w-full max-w-4xl flex-col px-4 py-4 ">
|
||||
<div className="mx-auto grid w-full max-w-[1800px] grid-cols-1 gap-4 px-4 min-[1420px]:grid-cols-[220px_minmax(0,896px)_220px] min-[1420px]:justify-between">
|
||||
<LayoutLeftPromoRail
|
||||
latestAgenda={latestAgenda}
|
||||
latestPost={latestPost}
|
||||
/>
|
||||
<div className="min-w-0 w-full max-w-4xl justify-self-center min-[1420px]:max-w-none">
|
||||
<div className="pt-4">
|
||||
<SiteNavbar
|
||||
theme={theme}
|
||||
onThemeChange={setTheme}
|
||||
@@ -73,7 +81,10 @@ export function AppShell({ children, animationSources }: AppShellProps) {
|
||||
{localizedText}
|
||||
</AnnouncementBar>
|
||||
) : null}
|
||||
<main className="flex-1 py-4">{children}</main>
|
||||
</div>
|
||||
<main className="min-w-0 py-4">{children}</main>
|
||||
</div>
|
||||
<LayoutRightPromoRail />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
"use client";
|
||||
|
||||
import { useState, type KeyboardEvent } from "react";
|
||||
import Image from "next/image";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
ButtonIcon,
|
||||
ButtonLabel,
|
||||
Card,
|
||||
Typography,
|
||||
} from "poyraz-ui/atoms";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import {
|
||||
LEFT_LAYOUT_PROMO_SLIDES,
|
||||
RIGHT_LAYOUT_PROMO_SLIDES,
|
||||
type LayoutPromoCardDefinition,
|
||||
type LayoutPromoSlide,
|
||||
} from "@/data/layout-promos";
|
||||
import { SPONSORS } from "@/data/sponsors";
|
||||
import { getLocalizedValue } from "@/lib/locale";
|
||||
|
||||
export type LayoutContentPromo = {
|
||||
title: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
type PromoRailSide = "left" | "right";
|
||||
|
||||
function RailButton({
|
||||
href,
|
||||
label,
|
||||
icon = "mdi:arrow-right",
|
||||
variant = "outline",
|
||||
external = false,
|
||||
}: {
|
||||
href: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
variant?: "default" | "outline" | "secondary";
|
||||
external?: boolean;
|
||||
}) {
|
||||
const content = (
|
||||
<>
|
||||
<ButtonLabel>{label}</ButtonLabel>
|
||||
<ButtonIcon>
|
||||
<Icon icon={icon} width={14} height={14} />
|
||||
</ButtonIcon>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Button
|
||||
asChild
|
||||
variant={variant}
|
||||
size="xs"
|
||||
radius="sm"
|
||||
effect="swap"
|
||||
swapTarget="both"
|
||||
className="w-full justify-between"
|
||||
>
|
||||
{external ? (
|
||||
<a href={href} target="_blank" rel="noreferrer">
|
||||
{content}
|
||||
</a>
|
||||
) : (
|
||||
<Link href={href}>{content}</Link>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SponsorLogos() {
|
||||
return (
|
||||
<div className="mt-3 grid grid-cols-2 gap-1.5">
|
||||
{SPONSORS.map((sponsor, index) => (
|
||||
<a
|
||||
key={sponsor.id}
|
||||
href={sponsor.websiteUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={sponsor.name}
|
||||
className={`relative flex h-10 items-center justify-center rounded-sm border border-border bg-white p-1.5 transition-colors hover:border-primary/40 ${
|
||||
index === SPONSORS.length - 1 && SPONSORS.length % 2 === 1
|
||||
? "col-span-2"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={sponsor.logo}
|
||||
alt=""
|
||||
fill
|
||||
sizes="100px"
|
||||
className="object-contain p-1.5 grayscale transition duration-300 hover:grayscale-0"
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PromoIcon({
|
||||
card,
|
||||
className,
|
||||
}: {
|
||||
card: LayoutPromoCardDefinition;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Icon
|
||||
icon={card.icon}
|
||||
width={17}
|
||||
height={17}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PromoCard({
|
||||
card,
|
||||
latestAgenda,
|
||||
latestPost,
|
||||
}: {
|
||||
card: LayoutPromoCardDefinition;
|
||||
latestAgenda: LayoutContentPromo | null;
|
||||
latestPost: LayoutContentPromo | null;
|
||||
}) {
|
||||
const t = useTranslations("LayoutPromos");
|
||||
const locale = useLocale();
|
||||
const liveContent =
|
||||
card.contentSource === "latestAgenda"
|
||||
? latestAgenda
|
||||
: card.contentSource === "latestPost"
|
||||
? latestPost
|
||||
: null;
|
||||
const href = liveContent?.href ?? getLocalizedValue(card.href, locale);
|
||||
const description = liveContent?.title ?? t(card.descriptionKey);
|
||||
const external = card.external ?? false;
|
||||
const iconSurface = card.iconSurface ?? "accent";
|
||||
const cardClassName =
|
||||
card.surface === "primary"
|
||||
? "rounded-sm border-primary/25 bg-primary/5 p-3"
|
||||
: "rounded-sm border-border p-3";
|
||||
const iconClassName =
|
||||
iconSurface === "primary"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: iconSurface === "foreground"
|
||||
? "bg-foreground text-background"
|
||||
: "bg-accent text-foreground";
|
||||
|
||||
return (
|
||||
<Card className={cardClassName}>
|
||||
{card.eyebrowKey ? (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Badge variant="secondary" radius="sm">
|
||||
{t(card.eyebrowKey)}
|
||||
</Badge>
|
||||
<PromoIcon card={card} className="text-primary" />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`flex size-8 items-center justify-center rounded-sm ${iconClassName}`}
|
||||
>
|
||||
<PromoIcon card={card} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
variant="large"
|
||||
component="h2"
|
||||
className="mt-3 text-base leading-5 tracking-[-0.025em]"
|
||||
>
|
||||
{t(card.titleKey)}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="small"
|
||||
className={`mt-2 text-xs leading-5 text-muted-foreground ${
|
||||
liveContent ? "line-clamp-3" : ""
|
||||
}`}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
|
||||
{card.kind === "sponsors" ? <SponsorLogos /> : null}
|
||||
|
||||
<div className="mt-3">
|
||||
<RailButton
|
||||
href={href}
|
||||
label={t(card.ctaKey)}
|
||||
icon={external ? "mdi:arrow-top-right" : "mdi:arrow-right"}
|
||||
variant={card.buttonVariant}
|
||||
external={external}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function PromoRail({
|
||||
side,
|
||||
slides,
|
||||
latestAgenda = null,
|
||||
latestPost = null,
|
||||
}: {
|
||||
side: PromoRailSide;
|
||||
slides: readonly LayoutPromoSlide[];
|
||||
latestAgenda?: LayoutContentPromo | null;
|
||||
latestPost?: LayoutContentPromo | null;
|
||||
}) {
|
||||
const t = useTranslations("LayoutPromos");
|
||||
const reduceMotion = useReducedMotion();
|
||||
const [activeSlide, setActiveSlide] = useState(0);
|
||||
const direction = side === "left" ? -1 : 1;
|
||||
const activeCards = slides[activeSlide] ?? slides[0];
|
||||
const railId = `${side}-promo-rail`;
|
||||
|
||||
const selectAdjacentSlide = (step: number) => {
|
||||
setActiveSlide((current) => (current + step + slides.length) % slides.length);
|
||||
};
|
||||
|
||||
const handleNavigationKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
||||
|
||||
event.preventDefault();
|
||||
selectAdjacentSlide(event.key === "ArrowRight" ? 1 : -1);
|
||||
};
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label={t(side === "left" ? "leftRailLabel" : "rightRailLabel")}
|
||||
className="relative z-50 hidden min-[1420px]:block"
|
||||
>
|
||||
<div className="sticky top-4 py-4">
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label={t("slideNavigationLabel")}
|
||||
onKeyDown={handleNavigationKeyDown}
|
||||
className="mb-3 flex h-5 items-center justify-center gap-2"
|
||||
>
|
||||
{slides.map((_, index) => {
|
||||
const selected = index === activeSlide;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={`${railId}-dot-${index}`}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={selected}
|
||||
aria-controls={`${railId}-panel-${index}`}
|
||||
aria-label={t("slideCta", { slide: index + 1 })}
|
||||
onClick={() => setActiveSlide(index)}
|
||||
className="group flex size-5 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
<span
|
||||
className={`block rounded-full transition-[width,background-color,transform] duration-300 group-hover:scale-110 ${
|
||||
selected
|
||||
? "h-2 w-5 bg-primary"
|
||||
: "size-2 bg-border group-hover:bg-muted-foreground/60"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden">
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={`${railId}-slide-${activeSlide}`}
|
||||
id={`${railId}-panel-${activeSlide}`}
|
||||
role="tabpanel"
|
||||
aria-label={t("slideStatus", {
|
||||
current: activeSlide + 1,
|
||||
total: slides.length,
|
||||
})}
|
||||
initial={
|
||||
reduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: 0, x: -direction * 26 }
|
||||
}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={
|
||||
reduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: 0, x: direction * 26 }
|
||||
}
|
||||
transition={{
|
||||
duration: reduceMotion ? 0.12 : 0.38,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
className="space-y-3"
|
||||
>
|
||||
{activeCards.map((card, index) => (
|
||||
<motion.div
|
||||
key={card.id}
|
||||
initial={reduceMotion ? false : { opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: reduceMotion ? 0 : 0.28,
|
||||
delay: reduceMotion ? 0 : index * 0.045,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
whileHover={reduceMotion ? undefined : { y: -3 }}
|
||||
>
|
||||
<PromoCard
|
||||
card={card}
|
||||
latestAgenda={latestAgenda}
|
||||
latestPost={latestPost}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
export function LayoutLeftPromoRail({
|
||||
latestAgenda,
|
||||
latestPost,
|
||||
}: {
|
||||
latestAgenda: LayoutContentPromo | null;
|
||||
latestPost: LayoutContentPromo | null;
|
||||
}) {
|
||||
return (
|
||||
<PromoRail
|
||||
side="left"
|
||||
slides={LEFT_LAYOUT_PROMO_SLIDES}
|
||||
latestAgenda={latestAgenda}
|
||||
latestPost={latestPost}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function LayoutRightPromoRail() {
|
||||
return <PromoRail side="right" slides={RIGHT_LAYOUT_PROMO_SLIDES} />;
|
||||
}
|
||||
@@ -97,6 +97,11 @@ export async function getAllAgendaArticles(locale?: string): Promise<BlogArticle
|
||||
return articles.filter((article) => isNewsletterCategory(article.category));
|
||||
}
|
||||
|
||||
export async function getLatestAgendaArticle(locale?: string) {
|
||||
const articles = await getAllAgendaArticles(locale);
|
||||
return articles[0] ?? null;
|
||||
}
|
||||
|
||||
export async function getHomeBlogNews(locale?: string, limit = 3) {
|
||||
const articles = await getAllBlogArticles(locale);
|
||||
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
import type { Localized } from "@/lib/locale";
|
||||
|
||||
export type LayoutPromoCopyKey =
|
||||
| "weeklyEyebrow"
|
||||
| "weeklyTitle"
|
||||
| "weeklyFallback"
|
||||
| "weeklyCta"
|
||||
| "projectsTitle"
|
||||
| "projectsDescription"
|
||||
| "projectsCta"
|
||||
| "latestPostEyebrow"
|
||||
| "latestPostTitle"
|
||||
| "latestPostFallback"
|
||||
| "latestPostCta"
|
||||
| "anatomyTitle"
|
||||
| "anatomyDescription"
|
||||
| "anatomyCta"
|
||||
| "youtubeEyebrow"
|
||||
| "youtubeTitle"
|
||||
| "youtubeDescription"
|
||||
| "youtubeCta"
|
||||
| "designSystemEyebrow"
|
||||
| "designSystemTitle"
|
||||
| "designSystemDescription"
|
||||
| "designSystemCta"
|
||||
| "communityEyebrow"
|
||||
| "communityTitle"
|
||||
| "communityDescription"
|
||||
| "communityCta"
|
||||
| "referencesEyebrow"
|
||||
| "referencesTitle"
|
||||
| "referencesDescription"
|
||||
| "referencesCta"
|
||||
| "sponsorsEyebrow"
|
||||
| "sponsorsTitle"
|
||||
| "sponsorsDescription"
|
||||
| "sponsorsCta"
|
||||
| "contactTitle"
|
||||
| "contactDescription"
|
||||
| "contactCta"
|
||||
| "linkedinEyebrow"
|
||||
| "linkedinTitle"
|
||||
| "linkedinDescription"
|
||||
| "linkedinCta"
|
||||
| "instagramEyebrow"
|
||||
| "instagramTitle"
|
||||
| "instagramDescription"
|
||||
| "instagramCta";
|
||||
|
||||
export type LayoutPromoCardDefinition = {
|
||||
id: string;
|
||||
kind?: "standard" | "sponsors";
|
||||
eyebrowKey?: LayoutPromoCopyKey;
|
||||
titleKey: LayoutPromoCopyKey;
|
||||
descriptionKey: LayoutPromoCopyKey;
|
||||
ctaKey: LayoutPromoCopyKey;
|
||||
href: string | Localized;
|
||||
icon: string;
|
||||
iconSurface?: "accent" | "foreground" | "primary";
|
||||
surface?: "default" | "primary";
|
||||
buttonVariant?: "default" | "outline" | "secondary";
|
||||
external?: boolean;
|
||||
contentSource?: "latestAgenda" | "latestPost";
|
||||
};
|
||||
|
||||
export type LayoutPromoSlide = readonly LayoutPromoCardDefinition[];
|
||||
|
||||
export const LEFT_LAYOUT_PROMO_SLIDES: readonly LayoutPromoSlide[] = [
|
||||
[
|
||||
{
|
||||
id: "weekly-agenda",
|
||||
eyebrowKey: "weeklyEyebrow",
|
||||
titleKey: "weeklyTitle",
|
||||
descriptionKey: "weeklyFallback",
|
||||
ctaKey: "weeklyCta",
|
||||
href: "/agenda",
|
||||
icon: "mdi:newspaper-variant-outline",
|
||||
surface: "primary",
|
||||
buttonVariant: "default",
|
||||
contentSource: "latestAgenda",
|
||||
},
|
||||
{
|
||||
id: "projects",
|
||||
titleKey: "projectsTitle",
|
||||
descriptionKey: "projectsDescription",
|
||||
ctaKey: "projectsCta",
|
||||
href: "/projects",
|
||||
icon: "mdi:layers-triple-outline",
|
||||
},
|
||||
{
|
||||
id: "latest-post",
|
||||
eyebrowKey: "latestPostEyebrow",
|
||||
titleKey: "latestPostTitle",
|
||||
descriptionKey: "latestPostFallback",
|
||||
ctaKey: "latestPostCta",
|
||||
href: "/blog",
|
||||
icon: "mdi:post-outline",
|
||||
contentSource: "latestPost",
|
||||
},
|
||||
{
|
||||
id: "javascript-anatomy",
|
||||
titleKey: "anatomyTitle",
|
||||
descriptionKey: "anatomyDescription",
|
||||
ctaKey: "anatomyCta",
|
||||
href: "/content",
|
||||
icon: "ri:twitter-x-fill",
|
||||
iconSurface: "foreground",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
id: "youtube",
|
||||
eyebrowKey: "youtubeEyebrow",
|
||||
titleKey: "youtubeTitle",
|
||||
descriptionKey: "youtubeDescription",
|
||||
ctaKey: "youtubeCta",
|
||||
href: "https://youtube.com/@poyrazavsever",
|
||||
icon: "mdi:youtube",
|
||||
iconSurface: "primary",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
id: "poyraz-ui",
|
||||
eyebrowKey: "designSystemEyebrow",
|
||||
titleKey: "designSystemTitle",
|
||||
descriptionKey: "designSystemDescription",
|
||||
ctaKey: "designSystemCta",
|
||||
href: "https://ui.poyrazavsever.com",
|
||||
icon: "mdi:palette-swatch-outline",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
id: "community",
|
||||
eyebrowKey: "communityEyebrow",
|
||||
titleKey: "communityTitle",
|
||||
descriptionKey: "communityDescription",
|
||||
ctaKey: "communityCta",
|
||||
href: "/about/volunteer-community",
|
||||
icon: "mdi:account-group-outline",
|
||||
},
|
||||
{
|
||||
id: "references",
|
||||
eyebrowKey: "referencesEyebrow",
|
||||
titleKey: "referencesTitle",
|
||||
descriptionKey: "referencesDescription",
|
||||
ctaKey: "referencesCta",
|
||||
href: "/about/references",
|
||||
icon: "mdi:comment-quote-outline",
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export const RIGHT_LAYOUT_PROMO_SLIDES: readonly LayoutPromoSlide[] = [
|
||||
[
|
||||
{
|
||||
id: "sponsors",
|
||||
kind: "sponsors",
|
||||
eyebrowKey: "sponsorsEyebrow",
|
||||
titleKey: "sponsorsTitle",
|
||||
descriptionKey: "sponsorsDescription",
|
||||
ctaKey: "sponsorsCta",
|
||||
href: "/media-kit",
|
||||
icon: "mdi:handshake-outline",
|
||||
buttonVariant: "default",
|
||||
},
|
||||
{
|
||||
id: "contact",
|
||||
titleKey: "contactTitle",
|
||||
descriptionKey: "contactDescription",
|
||||
ctaKey: "contactCta",
|
||||
href: "/contact",
|
||||
icon: "mdi:message-text-outline",
|
||||
iconSurface: "primary",
|
||||
surface: "primary",
|
||||
buttonVariant: "default",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
id: "linkedin",
|
||||
eyebrowKey: "linkedinEyebrow",
|
||||
titleKey: "linkedinTitle",
|
||||
descriptionKey: "linkedinDescription",
|
||||
ctaKey: "linkedinCta",
|
||||
href: "https://www.linkedin.com/in/poyrazavsever/",
|
||||
icon: "mdi:linkedin",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
id: "instagram",
|
||||
eyebrowKey: "instagramEyebrow",
|
||||
titleKey: "instagramTitle",
|
||||
descriptionKey: "instagramDescription",
|
||||
ctaKey: "instagramCta",
|
||||
href: "https://instagram.com/poyraz_avsever",
|
||||
icon: "mdi:instagram",
|
||||
surface: "primary",
|
||||
buttonVariant: "default",
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
];
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Nav": {
|
||||
"home": "Home",
|
||||
"about": "About",
|
||||
"blog": "Blog",
|
||||
"agenda": "Weekly Agenda",
|
||||
@@ -12,6 +13,10 @@
|
||||
"socialLinks": "Social Links",
|
||||
"others": "Others",
|
||||
"animationResources": "Animation Resources",
|
||||
"links": "Links",
|
||||
"mediaKit": "Media Kit",
|
||||
"references": "References",
|
||||
"volunteerCommunity": "Volunteering & Community",
|
||||
"backToMenu": "Back to menu",
|
||||
"menu": "Menu",
|
||||
"mobileMenu": "Mobile Menu",
|
||||
@@ -35,6 +40,58 @@
|
||||
"sponsors": "Sponsors"
|
||||
}
|
||||
},
|
||||
"LayoutPromos": {
|
||||
"leftRailLabel": "Featured content",
|
||||
"rightRailLabel": "Partnerships and contact",
|
||||
"slideNavigationLabel": "Promotional slides",
|
||||
"slideCta": "Go to slide {slide}",
|
||||
"slideStatus": "Slide {current} of {total}",
|
||||
"weeklyEyebrow": "This week",
|
||||
"weeklyTitle": "Have you read this week's agenda?",
|
||||
"weeklyFallback": "Weekly developments from software, technology, design, and artificial intelligence.",
|
||||
"weeklyCta": "Read the agenda",
|
||||
"projectsTitle": "Curious how I build my projects?",
|
||||
"projectsDescription": "Explore the decisions, architecture, and outcomes behind selected products through detailed case studies.",
|
||||
"projectsCta": "Explore projects",
|
||||
"latestPostEyebrow": "New post",
|
||||
"latestPostTitle": "What's new on the blog?",
|
||||
"latestPostFallback": "Read my latest notes on software and product development.",
|
||||
"latestPostCta": "Read the post",
|
||||
"anatomyTitle": "Have you watched JavaScript Anatomy?",
|
||||
"anatomyDescription": "Explore my series explaining JavaScript concepts through concise 1-4 minute landscape videos.",
|
||||
"anatomyCta": "Watch the series",
|
||||
"youtubeEyebrow": "YouTube",
|
||||
"youtubeTitle": "Join a community of 8K+ people.",
|
||||
"youtubeDescription": "Watch practical videos about software, artificial intelligence, and product development.",
|
||||
"youtubeCta": "Visit the channel",
|
||||
"designSystemEyebrow": "Poyraz UI",
|
||||
"designSystemTitle": "Explore the design system behind this site.",
|
||||
"designSystemDescription": "See the components, design decisions, and live examples I use across the portfolio.",
|
||||
"designSystemCta": "Open the docs",
|
||||
"communityEyebrow": "Community",
|
||||
"communityTitle": "What do I do beyond writing code?",
|
||||
"communityDescription": "Explore my workshops, hackathons, mentoring, and volunteer community work.",
|
||||
"communityCta": "Community work",
|
||||
"referencesEyebrow": "References",
|
||||
"referencesTitle": "What do the people I work with say?",
|
||||
"referencesDescription": "Read feedback from teammates and people I have built products with.",
|
||||
"referencesCta": "Read references",
|
||||
"sponsorsEyebrow": "Sponsors",
|
||||
"sponsorsTitle": "These brands have sponsored my work so far.",
|
||||
"sponsorsDescription": "Would you like to introduce your brand to my software and technology-focused audience?",
|
||||
"sponsorsCta": "Become a sponsor",
|
||||
"contactTitle": "Have an idea? Let's build it together.",
|
||||
"contactDescription": "Reach out directly for a project, partnership, or content idea.",
|
||||
"contactCta": "Get in touch",
|
||||
"linkedinEyebrow": "LinkedIn",
|
||||
"linkedinTitle": "Follow my professional journey.",
|
||||
"linkedinDescription": "I share my projects, technical experiences, and notes from the industry on LinkedIn.",
|
||||
"linkedinCta": "Visit LinkedIn",
|
||||
"instagramEyebrow": "Instagram",
|
||||
"instagramTitle": "Technology, in a shorter format.",
|
||||
"instagramDescription": "Follow for concise technical videos, moments from events, and a look behind my creative process.",
|
||||
"instagramCta": "Follow on Instagram"
|
||||
},
|
||||
"About": {
|
||||
"title": "About me",
|
||||
"contactCta": "Contact me",
|
||||
@@ -134,12 +191,17 @@
|
||||
"copied": "Copied"
|
||||
},
|
||||
"Links": {
|
||||
"title": "My links",
|
||||
"desc": "Here you can find all my social accounts, portfolio pages, and quick access links in one place. Open and share directly.",
|
||||
"socialLinks": "Social media links",
|
||||
"quickLinks": "Quick access",
|
||||
"quickLinksDesc": "Frequently used projects, resources, and resume",
|
||||
"allLinks": "All Links",
|
||||
"allLinksDesc": "Pages, resources, and social profiles",
|
||||
"selectCategory": "Select category",
|
||||
"allCategories": "All categories",
|
||||
"searchPlaceholder": "Search links... github, medium, /blog",
|
||||
"searchAriaLabel": "Search links",
|
||||
"empty": "No links found matching the selected filter.",
|
||||
"categories": {
|
||||
"navigation": "Pages",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Nav": {
|
||||
"home": "Ana Sayfa",
|
||||
"about": "Hakkımda",
|
||||
"blog": "Blog",
|
||||
"agenda": "Haftalık Gündem",
|
||||
@@ -12,6 +13,10 @@
|
||||
"socialLinks": "Sosyal Bağlantılar",
|
||||
"others": "Diğerleri",
|
||||
"animationResources": "Animasyon Kaynakları",
|
||||
"links": "Bağlantılar",
|
||||
"mediaKit": "Medya Kiti",
|
||||
"references": "Referanslar",
|
||||
"volunteerCommunity": "Gönüllük ve Topluluk",
|
||||
"backToMenu": "Menüye dön",
|
||||
"menu": "Menü",
|
||||
"mobileMenu": "Mobil Menü",
|
||||
@@ -35,6 +40,58 @@
|
||||
"sponsors": "Sponsorlar"
|
||||
}
|
||||
},
|
||||
"LayoutPromos": {
|
||||
"leftRailLabel": "Öne çıkan içerikler",
|
||||
"rightRailLabel": "İş birlikleri ve iletişim",
|
||||
"slideNavigationLabel": "Tanıtım slaytları",
|
||||
"slideCta": "{slide}. slayda git",
|
||||
"slideStatus": "{current} / {total}. slayt",
|
||||
"weeklyEyebrow": "Bu hafta",
|
||||
"weeklyTitle": "Bu haftanın gündemini okudun mu?",
|
||||
"weeklyFallback": "Yazılım, teknoloji, tasarım ve yapay zekâ dünyasından haftalık gelişmeler.",
|
||||
"weeklyCta": "Gündemi oku",
|
||||
"projectsTitle": "Nasıl geliştirdiğimi merak ediyor musun?",
|
||||
"projectsDescription": "Seçili projelerde aldığım kararları, mimariyi ve ortaya çıkan sonuçları vaka çalışmalarıyla incele.",
|
||||
"projectsCta": "Projeleri incele",
|
||||
"latestPostEyebrow": "Yeni yazı",
|
||||
"latestPostTitle": "Blogda yeni ne var?",
|
||||
"latestPostFallback": "Yazılım ve ürün geliştirme üzerine son notlarıma göz at.",
|
||||
"latestPostCta": "Yazıyı oku",
|
||||
"anatomyTitle": "JavaScript Anatomisi'ni izledin mi?",
|
||||
"anatomyDescription": "JavaScript kavramlarını 1-4 dakikalık kısa ve yatay videolarla anlattığım seriyi keşfet.",
|
||||
"anatomyCta": "Seriyi izle",
|
||||
"youtubeEyebrow": "YouTube",
|
||||
"youtubeTitle": "8 B+ kişilik topluluğa katıl.",
|
||||
"youtubeDescription": "Yazılım, yapay zekâ ve ürün geliştirme üzerine uygulamalı videoları izle.",
|
||||
"youtubeCta": "Kanala git",
|
||||
"designSystemEyebrow": "Poyraz UI",
|
||||
"designSystemTitle": "Bu sitenin tasarım sistemini keşfet.",
|
||||
"designSystemDescription": "Kullandığım bileşenleri, tasarım kararlarını ve canlı örnekleri incele.",
|
||||
"designSystemCta": "Dokümantasyonu aç",
|
||||
"communityEyebrow": "Topluluk",
|
||||
"communityTitle": "Kodun dışında neler yapıyorum?",
|
||||
"communityDescription": "Workshop, hackathon, mentörlük ve gönüllülük çalışmalarımı incele.",
|
||||
"communityCta": "Topluluk çalışmaları",
|
||||
"referencesEyebrow": "Referanslar",
|
||||
"referencesTitle": "Birlikte çalıştığım insanlar ne diyor?",
|
||||
"referencesDescription": "Ekip arkadaşlarımdan ve birlikte ürettiğim insanlardan gelen yorumları oku.",
|
||||
"referencesCta": "Referansları oku",
|
||||
"sponsorsEyebrow": "Sponsorlar",
|
||||
"sponsorsTitle": "Bak, bunlar bana şimdiye kadar sponsor oldu.",
|
||||
"sponsorsDescription": "Sen de markanı yazılım ve teknoloji odaklı kitlemle buluşturmak ister misin?",
|
||||
"sponsorsCta": "Sen de sponsor ol",
|
||||
"contactTitle": "Bir fikrin mi var? Birlikte üretelim.",
|
||||
"contactDescription": "Proje, iş birliği veya içerik fikrin için doğrudan iletişime geç.",
|
||||
"contactCta": "İletişime geç",
|
||||
"linkedinEyebrow": "LinkedIn",
|
||||
"linkedinTitle": "Profesyonel yolculuğumu takip et.",
|
||||
"linkedinDescription": "Projelerimi, teknik deneyimlerimi ve sektörden notlarımı LinkedIn'de paylaşıyorum.",
|
||||
"linkedinCta": "LinkedIn'e git",
|
||||
"instagramEyebrow": "Instagram",
|
||||
"instagramTitle": "Teknolojinin daha kısa hâli burada.",
|
||||
"instagramDescription": "Kısa teknik videolar, etkinliklerden anlar ve üretim sürecimin perde arkası için takip et.",
|
||||
"instagramCta": "Instagram'da takip et"
|
||||
},
|
||||
"About": {
|
||||
"title": "Hakkımda",
|
||||
"contactCta": "İletişime geç",
|
||||
@@ -134,12 +191,17 @@
|
||||
"copied": "Kopyalandı"
|
||||
},
|
||||
"Links": {
|
||||
"title": "Bağlantılarım",
|
||||
"desc": "Burada sosyal hesaplarım, portfolyo sayfalarım ve hızlı erişim linklerimin tamamı tek yerde duruyor. Direkt açıp paylaşabilirsin.",
|
||||
"socialLinks": "Sosyal medya bağlantıları",
|
||||
"quickLinks": "Hızlı erişim",
|
||||
"quickLinksDesc": "Sık kullanılan projeler, kaynaklar ve özgeçmiş",
|
||||
"allLinks": "Tüm Linkler",
|
||||
"allLinksDesc": "Sayfalar, kaynaklar ve sosyal profiller",
|
||||
"selectCategory": "Kategori seç",
|
||||
"allCategories": "Tüm kategoriler",
|
||||
"searchPlaceholder": "Link ara... github, medium, /blog",
|
||||
"searchAriaLabel": "Bağlantılarda ara",
|
||||
"empty": "Seçili filtreye uygun link bulunamadı.",
|
||||
"categories": {
|
||||
"navigation": "Sayfalar",
|
||||
|
||||
Reference in New Issue
Block a user