diff --git a/app/globals.css b/app/globals.css
index b600302..42648a9 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -368,34 +368,32 @@ html[lang="ar"] {
.hero-title-accent {
background-image: linear-gradient(
- 120deg,
- hsl(var(--hero-ink)) 0%,
- hsl(var(--hero-ink)) 24%,
- hsl(var(--primary) / 0.58) 52%,
- hsl(var(--hero-right) / 0.34) 78%,
- hsl(var(--hero-ink)) 100%
+ 135deg,
+ hsl(var(--primary)) 0%,
+ color-mix(in srgb, hsl(var(--primary)) 82%, hsl(var(--foreground))) 100%
);
- background-size: 220% 220%;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
- animation: hero-title-shift 8s ease-in-out infinite alternate;
}
.hero-title-accent-soft {
+ color: hsl(var(--hero-ink));
+ }
+
+ .dark .hero-title-accent {
background-image: linear-gradient(
- 120deg,
- hsl(var(--hero-ink)) 0%,
- hsl(var(--hero-ink)) 34%,
- hsl(var(--hero-left) / 0.26) 62%,
- hsl(var(--primary) / 0.24) 82%,
- hsl(var(--hero-ink)) 100%
+ 135deg,
+ hsl(var(--primary)) 0%,
+ color-mix(in srgb, hsl(var(--primary)) 74%, hsl(var(--foreground))) 100%
);
- background-size: 220% 220%;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
- animation: hero-title-shift 10s ease-in-out infinite alternate;
+ }
+
+ .dark .hero-title-accent-soft {
+ color: hsl(var(--hero-ink));
}
.hero-scroll-link {
diff --git a/components/layout/home-hero.tsx b/components/layout/home-hero.tsx
index a0d65c2..ac76b46 100644
--- a/components/layout/home-hero.tsx
+++ b/components/layout/home-hero.tsx
@@ -1,7 +1,8 @@
+import { motion, useReducedMotion } from "framer-motion";
+
import {
HeroContentMotion,
HeroMotionItem,
- HeroScrollLink,
HeroShell,
HeroTitle,
} from "@/components/layout/site-hero";
@@ -19,36 +20,83 @@ export function HomeHero({
title,
description,
}: HomeHeroProps) {
+ const reducedMotion = useReducedMotion();
const titleLines = title.split("\n").filter(Boolean);
const isArabic = locale === "ar";
- const lines = titleLines.map((line, index) => ({
- text: line,
- tone: isArabic
- ? index === 0
- ? "accent"
- : "default"
- : index === titleLines.length - 1
- ? "accent"
- : index === 0
- ? "soft"
- : "default",
- align: isArabic
- ? "center"
- : index === 1
- ? "end"
- : "start",
- })) as {
- text: string;
- tone: "default" | "accent" | "soft";
- align: "start" | "center" | "end";
- }[];
+ const lines = isArabic
+ ? [
+ {
+ text: titleLines[0] ?? "",
+ align: "center" as const,
+ segments: [
+ { text: "مطوّر ", tone: "accent" as const },
+ { text: "واجهات", tone: "soft" as const },
+ { text: " ويب", tone: "accent" as const },
+ ],
+ },
+ {
+ text: titleLines[1] ?? "",
+ align: "center" as const,
+ segments: [
+ { text: "ومصمّم ", tone: "soft" as const },
+ { text: "جرافيك", tone: "accent" as const },
+ ],
+ },
+ ]
+ : [
+ {
+ text: titleLines[0] ?? "",
+ tone: "soft" as const,
+ align: "start" as const,
+ },
+ {
+ text: titleLines[1] ?? "",
+ tone: "accent" as const,
+ align: "end" as const,
+ },
+ {
+ text: titleLines[2] ?? "",
+ segments: [
+ { text: "& ", tone: "accent" as const },
+ { text: "designer", tone: "soft" as const },
+ ],
+ align: "start" as const,
+ },
+ ];
return (
-
- {kicker}
+
+ {kicker}
+
+ 👋
+
@@ -57,13 +105,6 @@ export function HomeHero({
{description}
-
-
-
);
diff --git a/components/layout/site-header.tsx b/components/layout/site-header.tsx
index c7c0650..ff3d8ac 100644
--- a/components/layout/site-header.tsx
+++ b/components/layout/site-header.tsx
@@ -128,6 +128,7 @@ export function SiteHeader({
}: SiteHeaderProps) {
const [isOpen, setIsOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
+ const [logoReplayKey, setLogoReplayKey] = useState(0);
const pathname = usePathname();
const locale = useLocale();
const t = useTranslations("navigation");
@@ -137,7 +138,7 @@ export function SiteHeader({
? "مــــــطـــــــــــــور ويــــــــــب | فــــــــــــريــــــلانــــــســــــر"
: "Webdesigner Bremen | Freelancer";
const desktopControlRailClassName =
- "inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1 shadow-panel backdrop-blur-chrome";
+ "inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1 backdrop-blur-chrome";
const desktopControlButtonClassName =
"h-9 w-9 rounded-pill border border-transparent p-0 text-foreground/80 hover:bg-accent hover:text-foreground";
const mobileControlButtonClassName =
@@ -158,7 +159,7 @@ export function SiteHeader({
return (
-
+
setLogoReplayKey((value) => value + 1)}
>
-
-
-
+
@@ -298,7 +294,7 @@ export function SiteHeader({
onClick={() => setIsOpen((open) => !open)}
variant="ghost"
size="icon"
- className="col-start-3 ml-auto h-11 w-11 justify-self-end rounded-pill border border-border/70 bg-background/80 text-foreground shadow-panel backdrop-blur-chrome hover:bg-accent lg:hidden"
+ className="col-start-3 ml-auto h-11 w-11 justify-self-end rounded-pill border border-border/70 bg-background/80 text-foreground backdrop-blur-chrome hover:bg-accent lg:hidden"
aria-label={isOpen ? t("closeMenu") : t("openMenu")}
>
-
+
= {
};
const lineAlignClasses: Record = {
- start: "self-start text-start",
+ start: "self-center text-center lg:self-start lg:text-start",
center: "self-center text-center",
- end: "self-end text-end",
+ end: "self-center text-center lg:self-end lg:text-end",
};
export function HeroShell({
@@ -97,7 +103,7 @@ export function HeroShell({
@@ -183,9 +189,9 @@ export function HeroTitle({
"text-balance font-semibold text-[hsl(var(--hero-ink))]",
variant === "home"
? isArabic
- ? "mt-6 flex w-full max-w-[16rem] flex-col gap-y-1 text-[clamp(3rem,11vw,5.9rem)] leading-[1.06] tracking-[-0.03em] md:max-w-[22rem] lg:max-w-[32rem]"
- : "mt-6 flex w-full max-w-[16rem] flex-col gap-y-1 text-[clamp(3.1rem,11vw,6.3rem)] leading-[0.94] tracking-[-0.06em] md:max-w-[22rem] lg:max-w-[32rem]"
- : "mt-4 flex w-full max-w-[16rem] flex-col gap-y-1 text-[clamp(2.8rem,10vw,4.9rem)] leading-[0.96] tracking-[-0.06em] md:max-w-[22rem] lg:max-w-[32rem]",
+ ? "mt-6 flex w-full max-w-[20rem] flex-col gap-y-1 text-[clamp(3rem,11vw,5.9rem)] leading-[1.06] tracking-[-0.03em] sm:max-w-[24rem] md:max-w-[30rem] lg:max-w-[32rem]"
+ : "mt-6 flex w-full max-w-[20rem] flex-col gap-y-1 text-[clamp(3.1rem,11vw,6.3rem)] leading-[0.94] tracking-[-0.06em] sm:max-w-[24rem] md:max-w-[30rem] lg:max-w-[32rem]"
+ : "mt-4 flex w-full max-w-[20rem] flex-col gap-y-1 text-[clamp(2.8rem,10vw,4.9rem)] leading-[0.96] tracking-[-0.06em] sm:max-w-[24rem] md:max-w-[28rem] lg:max-w-[32rem]",
className,
)}
>
@@ -194,11 +200,23 @@ export function HeroTitle({
key={`${index}-${line.text}`}
className={cn(
"hero-title-line",
- toneClasses[line.tone ?? "default"],
lineAlignClasses[line.align ?? "center"],
)}
>
- {line.text}
+ {line.segments?.length
+ ? line.segments.map((segment, segmentIndex) => (
+
+ {segment.text}
+
+ ))
+ : (
+
+ {line.text}
+
+ )}
))}
diff --git a/components/layout/site-logo.tsx b/components/layout/site-logo.tsx
index ff72bbc..7ef571b 100644
--- a/components/layout/site-logo.tsx
+++ b/components/layout/site-logo.tsx
@@ -1,6 +1,6 @@
"use client";
-import { motion } from "framer-motion";
+import { motion, useAnimationControls, useReducedMotion } from "framer-motion";
import Image from "next/image";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
@@ -13,6 +13,7 @@ type SiteLogoProps = {
alt: string;
priority?: boolean;
className?: string;
+ replayKey?: number;
};
export function SiteLogo({
@@ -21,14 +22,46 @@ export function SiteLogo({
alt,
priority = false,
className,
+ replayKey = 0,
}: SiteLogoProps) {
const { theme, resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
+ const controls = useAnimationControls();
+ const reducedMotion = useReducedMotion();
useEffect(() => {
setMounted(true);
}, []);
+ useEffect(() => {
+ if (reducedMotion) {
+ return;
+ }
+
+ controls.set({
+ opacity: 0,
+ x: -42,
+ scale: 0.88,
+ rotate: -360,
+ filter: "blur(8px)",
+ });
+
+ void controls.start({
+ opacity: 1,
+ x: 0,
+ scale: 1,
+ rotate: 0,
+ filter: "blur(0px)",
+ transition: {
+ x: { duration: 0.78, ease: [0.22, 1, 0.36, 1] },
+ opacity: { duration: 0.46 },
+ rotate: { duration: 0.56, ease: "easeOut" },
+ scale: { duration: 0.72, ease: [0.22, 1, 0.36, 1] },
+ filter: { duration: 0.42, ease: [0.22, 1, 0.36, 1] },
+ },
+ });
+ }, [controls, reducedMotion, replayKey]);
+
const activeTheme = theme === "system" ? resolvedTheme : theme;
const isDark = mounted && activeTheme === "dark";
const fallbackSrc = isDark ? "/logos/dark-primary.svg" : "/logos/light-primary.svg";
@@ -37,11 +70,10 @@ export function SiteLogo({
return (