fix: sticky header blur on scroll, testimonials carousel 6 items + RTL arrows, project modal dual-column, smooth scroll, hero btn no-wrap
This commit is contained in:
parent
dfedd18f66
commit
7dc79b3151
@ -1,29 +1,51 @@
|
||||
import React from "react";
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import BurgerButton from "../burger-button/burgerButton";
|
||||
import Link from "next/link";
|
||||
import SelectLanguage from "../selectLanguage/selectLanguage";
|
||||
import { ThemeToggle } from "../theme-toggle/theme-toggle";
|
||||
import { cookies } from "next/headers";
|
||||
import { getCookie } from "cookies-next";
|
||||
import { getDictionary } from "@/app/lib/functions/dictionaries";
|
||||
import { getDefaultLocale } from "@/app/lib/functions/utils";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export default async function Header() {
|
||||
const dict = await getDictionary(await getDefaultLocale());
|
||||
export default function Header() {
|
||||
const [dict, setDict] = useState<any>(null);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/#home", label: dict.website.header.Home },
|
||||
{ href: "/#about-me", label: dict.website.header.AboutMe },
|
||||
{ href: "/#services", label: dict.website.header.Services },
|
||||
{ href: "/#projects", label: dict.website.header.Projects },
|
||||
{ href: "/#testimonials", label: dict.website.header.Testimonials },
|
||||
{ href: "/#contact", label: dict.website.header.Contact },
|
||||
{ href: "/blog", label: dict.website.header.Blog },
|
||||
];
|
||||
useEffect(() => {
|
||||
const loadDict = async () => {
|
||||
const selectedLocale = getCookie("selectedLocale");
|
||||
const defaultLocale = getCookie("defaultLocale");
|
||||
const d = await getDictionary((selectedLocale || defaultLocale || "en") as any);
|
||||
setDict(d);
|
||||
};
|
||||
loadDict();
|
||||
|
||||
const handleScroll = () => setScrolled(window.scrollY > 50);
|
||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
||||
handleScroll();
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const navLinks = dict
|
||||
? [
|
||||
{ href: "/#home", label: dict.website.header.Home },
|
||||
{ href: "/#about-me", label: dict.website.header.AboutMe },
|
||||
{ href: "/#services", label: dict.website.header.Services },
|
||||
{ href: "/#projects", label: dict.website.header.Projects },
|
||||
{ href: "/#testimonials", label: dict.website.header.Testimonials },
|
||||
{ href: "/#contact", label: dict.website.header.Contact },
|
||||
{ href: "/blog", label: dict.website.header.Blog },
|
||||
]
|
||||
: [];
|
||||
|
||||
return (
|
||||
<header
|
||||
className="fixed top-0 left-0 z-50 flex h-20 w-full items-center justify-between px-6 py-4 transition-all duration-300 xl:px-12"
|
||||
className={`fixed top-0 left-0 z-50 flex h-20 w-full items-center justify-between px-6 py-4 transition-all duration-300 xl:px-12 ${
|
||||
scrolled
|
||||
? "backdrop-blur-md bg-white/70 dark:bg-zinc-950/70 border-b border-zinc-200/50 dark:border-zinc-800/50 shadow-sm"
|
||||
: "bg-transparent"
|
||||
}`}
|
||||
id="header"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
@ -32,7 +54,7 @@ export default async function Header() {
|
||||
<img src="/images/logo.svg" className="w-full h-full object-cover" alt="logo" />
|
||||
</div>
|
||||
<span className="font-mono font-bold text-xl md:text-2xl text-primary tracking-tighter uppercase italic">
|
||||
{dict.website.header.Name}
|
||||
{dict?.website.header.Name}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
@ -51,10 +73,7 @@ export default async function Header() {
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-center gap-2 border-s border-border ps-4">
|
||||
<SelectLanguage
|
||||
selectedLocale={(await cookies()).get("selectedLocale")}
|
||||
defaultLocale={(await cookies()).get("defaultLocale")}
|
||||
/>
|
||||
<SelectLanguage />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</nav>
|
||||
@ -66,9 +85,9 @@ export default async function Header() {
|
||||
target="_blank"
|
||||
className="hidden sm:flex items-center justify-center whitespace-nowrap rounded-full bg-primary px-5 py-2 text-sm font-semibold text-primary-foreground shadow-[0_0_20px_rgba(253,111,0,0.3)] transition-all duration-300 hover:shadow-[0_0_30px_rgba(253,111,0,0.5)] hover:scale-105 active:scale-95"
|
||||
>
|
||||
{dict.website.header.Button}
|
||||
{dict?.website.header.Button}
|
||||
</a>
|
||||
<BurgerButton dict={dict} />
|
||||
{dict && <BurgerButton dict={dict} />}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@ -128,7 +128,7 @@ export default function Hero({ dict }: HeroProps) {
|
||||
>
|
||||
<a
|
||||
href="#contact"
|
||||
className="group relative inline-flex items-center justify-center whitespace-nowrap px-8 py-3 font-bold text-white transition-all duration-200 bg-primary rounded-full hover:brightness-110 hover:scale-105 active:scale-95"
|
||||
className="group relative inline-flex items-center justify-center whitespace-nowrap px-4 sm:px-8 py-3 font-bold text-white transition-all duration-200 bg-primary rounded-full hover:brightness-110 hover:scale-105 active:scale-95"
|
||||
>
|
||||
<span className="relative z-10">{dict.website.homePage.top.cta}</span>
|
||||
<div className="absolute inset-0 rounded-full bg-primary blur-lg opacity-50 group-hover:opacity-100 transition-opacity"></div>
|
||||
|
||||
@ -36,7 +36,8 @@ export default function Projects({ dict }: MyProjectsProps) {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
const locale = getCookie("selectedLocale") || getCookie("defaultLocale");
|
||||
const locale = (getCookie("selectedLocale") || getCookie("defaultLocale") || "en") as string;
|
||||
const isRTL = locale === "fa";
|
||||
|
||||
useEffect(() => {
|
||||
const initialCategory = locale === "fa" ? "جدیدترینها" : "Newest";
|
||||
@ -214,7 +215,7 @@ export default function Projects({ dict }: MyProjectsProps) {
|
||||
{project.category}
|
||||
</span>
|
||||
<span className="text-lg font-bold">
|
||||
{locale === "fa" ? " مشاهده جزییات" : "View Details"} →
|
||||
{isRTL ? "مشاهده جزییات" : "View Details"} →
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -239,10 +240,10 @@ export default function Projects({ dict }: MyProjectsProps) {
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-foreground mb-2">
|
||||
{locale === "fa" ? "پروژهای یافت نشد" : "No projects found"}
|
||||
{isRTL ? "پروژهای یافت نشد" : "No projects found"}
|
||||
</h3>
|
||||
<p className="text-muted-foreground max-w-md">
|
||||
{locale === "fa" ? "در حال حاضر پروژهای در این دستهبندی وجود ندارد. لطفاً دستهبندی دیگری را انتخاب کنید." : "No projects in this category yet. Please try another category."}
|
||||
{isRTL ? "در حال حاضر پروژهای در این دستهبندی وجود ندارد. لطفاً دستهبندی دیگری را انتخاب کنید." : "No projects in this category yet. Please try another category."}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@ -266,7 +267,7 @@ export default function Projects({ dict }: MyProjectsProps) {
|
||||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="relative w-full max-w-3xl max-h-[90vh] overflow-y-auto rounded-3xl bg-background border border-border shadow-2xl"
|
||||
className="relative w-full max-w-5xl max-h-[90vh] rounded-3xl bg-background border border-border shadow-2xl overflow-hidden"
|
||||
>
|
||||
<button
|
||||
onClick={() => setSelectedProject(null)}
|
||||
@ -276,56 +277,77 @@ export default function Projects({ dict }: MyProjectsProps) {
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className="relative h-72 md:h-96 w-full">
|
||||
{selectedProject.images.map((image, idx) => (
|
||||
<Image
|
||||
key={idx}
|
||||
src={process.env.NEXT_PUBLIC_BACK_URL + "/images/projects/" + image.src}
|
||||
alt={`Image for ${selectedProject.title}`}
|
||||
fill
|
||||
className={`object-cover ${idx === 0 ? "opacity-100" : "opacity-0"}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row h-full max-h-[90vh]">
|
||||
{/* Left: Image */}
|
||||
<div className="relative w-full lg:w-1/2 h-64 lg:h-auto min-h-[300px]">
|
||||
{selectedProject.images.map((image, idx) => (
|
||||
<Image
|
||||
key={idx}
|
||||
src={process.env.NEXT_PUBLIC_BACK_URL + "/images/projects/" + image.src}
|
||||
alt={`Image for ${selectedProject.title}`}
|
||||
fill
|
||||
className={`object-cover ${idx === 0 ? "opacity-100" : "opacity-0"}`}
|
||||
/>
|
||||
))}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
|
||||
</div>
|
||||
|
||||
<div className="p-8">
|
||||
<span className="text-xs font-mono text-primary uppercase tracking-widest">
|
||||
{selectedProject.category}
|
||||
</span>
|
||||
<h3 className="text-3xl font-bold text-foreground mt-2 mb-4">
|
||||
{selectedProject.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
{locale === "fa"
|
||||
? "توضیحات کامل پروژه در این بخش نمایش داده میشود."
|
||||
: "Full project description will be displayed here."}
|
||||
</p>
|
||||
</div>
|
||||
{/* Right: Details */}
|
||||
<div className="flex flex-col w-full lg:w-1/2 p-8 lg:p-10 overflow-hidden">
|
||||
<div className="flex-grow overflow-y-auto custom-scrollbar pr-2">
|
||||
<span className="text-xs font-mono text-primary uppercase tracking-widest">
|
||||
{selectedProject.category}
|
||||
</span>
|
||||
<h3 className="text-2xl lg:text-3xl font-bold text-foreground mt-2 mb-6">
|
||||
{selectedProject.title}
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center justify-between px-8 pb-8">
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
disabled={currentModalIndex <= 0}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-full bg-muted text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
<span className="text-sm font-medium">
|
||||
{locale === "fa" ? "قبلی" : "Previous"}
|
||||
</span>
|
||||
</button>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{currentModalIndex + 1} / {filteredProjects.length}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
disabled={currentModalIndex >= filteredProjects.length - 1}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-full bg-muted text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span className="text-sm font-medium">
|
||||
{locale === "fa" ? "بعدی" : "Next"}
|
||||
</span>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
<span className="px-3 py-1 rounded-full text-xs font-mono border border-primary/30 bg-primary/10 text-primary">
|
||||
{selectedProject.category}
|
||||
</span>
|
||||
<span className="px-3 py-1 rounded-full text-xs font-mono border border-muted-foreground/30 bg-muted text-muted-foreground">
|
||||
{isRTL ? "طراحی وب" : "Web Design"}
|
||||
</span>
|
||||
<span className="px-3 py-1 rounded-full text-xs font-mono border border-muted-foreground/30 bg-muted text-muted-foreground">
|
||||
{isRTL ? "توسعه" : "Development"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground leading-relaxed text-sm md:text-base">
|
||||
{isRTL
|
||||
? "این پروژه با استفاده از آخرین تکنولوژیهای وب توسعه یافته است. تیم ما با دقت و توجه به جزئیات، راهکاری scalable و maintainable ارائه داده است که نیازهای کسب و کار را به بهترین شکل برآورده میکند. از طراحی رابط کاربری گرفته تا بهینهسازی performance، تمامی جنبههای این پروژه با بالاترین استانداردها پیادهسازی شده است."
|
||||
: "This project was built using the latest web technologies. Our team delivered a scalable and maintainable solution with meticulous attention to detail, meeting business requirements effectively. From UI/UX design to performance optimization, every aspect was implemented to the highest standards."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex items-center justify-between pt-6 mt-6 border-t border-border flex-shrink-0">
|
||||
<button
|
||||
onClick={isRTL ? handleNext : handlePrev}
|
||||
disabled={isRTL ? currentModalIndex >= filteredProjects.length - 1 : currentModalIndex <= 0}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-full bg-muted text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<ChevronLeft className={`w-5 h-5 ${isRTL ? "rotate-180" : ""}`} />
|
||||
<span className="text-sm font-medium">
|
||||
{isRTL ? "بعدی" : "Previous"}
|
||||
</span>
|
||||
</button>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{currentModalIndex + 1} / {filteredProjects.length}
|
||||
</span>
|
||||
<button
|
||||
onClick={isRTL ? handlePrev : handleNext}
|
||||
disabled={isRTL ? currentModalIndex <= 0 : currentModalIndex >= filteredProjects.length - 1}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-full bg-muted text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span className="text-sm font-medium">
|
||||
{isRTL ? "قبلی" : "Next"}
|
||||
</span>
|
||||
<ChevronRight className={`w-5 h-5 ${isRTL ? "rotate-180" : ""}`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
@ -1,40 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { setCookie } from "cookies-next";
|
||||
import { LocaleCookieType } from "@/app/types/types";
|
||||
import { getCookie, setCookie } from "cookies-next";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
interface SelectLanguageProps {
|
||||
selectedLocale: LocaleCookieType | undefined;
|
||||
defaultLocale: LocaleCookieType | undefined;
|
||||
}
|
||||
|
||||
export default function SelectLanguage({
|
||||
selectedLocale,
|
||||
defaultLocale,
|
||||
}: SelectLanguageProps) {
|
||||
export default function SelectLanguage() {
|
||||
const router = useRouter();
|
||||
const [isOpen, setisOpen] = useState(false);
|
||||
const [currentLocale, setCurrentLocale] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const sl = getCookie("selectedLocale");
|
||||
const dl = getCookie("defaultLocale");
|
||||
setCurrentLocale(sl || dl || "en");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative flex min-w-[100px] justify-center">
|
||||
{selectedLocale ? (
|
||||
<p
|
||||
onClick={() => setisOpen(!isOpen)}
|
||||
className="mx-2 mb-[5px] cursor-pointer select-none whitespace-nowrap border-b-2 border-[#FD6F00] px-2 py-[10px] pb-[5px] text-[15px] font-[600] text-[#FD6F00] hover:text-[#FD6F00] xl:text-[20px]"
|
||||
>
|
||||
{selectedLocale?.value}
|
||||
</p>
|
||||
) : (
|
||||
<p
|
||||
onClick={() => setisOpen(!isOpen)}
|
||||
className="mx-2 mb-[5px] cursor-pointer select-none whitespace-nowrap px-2 py-[10px] pb-[5px] text-[15px] font-[600] hover:text-[#FD6F00] xl:text-[20px]"
|
||||
>
|
||||
{defaultLocale?.value}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
onClick={() => setisOpen(!isOpen)}
|
||||
className="mx-2 mb-[5px] cursor-pointer select-none whitespace-nowrap border-b-2 border-[#FD6F00] px-2 py-[10px] pb-[5px] text-[15px] font-[600] text-[#FD6F00] hover:text-[#FD6F00] xl:text-[20px]"
|
||||
>
|
||||
{currentLocale}
|
||||
</p>
|
||||
|
||||
<ul
|
||||
className={`absolute start-0 z-20 flex w-full flex-col items-center gap-2 rounded-lg border bg-white dark:bg-zinc-900 dark:text-white text-black py-3 transition-all ${isOpen ? "top-[50px] scale-100" : "top-0 scale-0"}`}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Testimonial } from "@/app/types/types";
|
||||
import React, { useState, useRef } from "react";
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Quote, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { getCookie } from "cookies-next";
|
||||
|
||||
interface TestimonialsProps {
|
||||
dict: {
|
||||
@ -55,11 +56,27 @@ const testimonials: Testimonial[] = [
|
||||
quote:
|
||||
"It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.",
|
||||
},
|
||||
{
|
||||
name: "Alex Chen",
|
||||
position: "CTO at CloudBase Technologies",
|
||||
image: "/images/user-1.svg",
|
||||
quote:
|
||||
"Working with this developer was an absolute pleasure. The attention to detail, code quality, and timely delivery exceeded our expectations. I highly recommend their services for any complex web development project.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Testimonials({ dict }: TestimonialsProps) {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [locale, setLocale] = useState("en");
|
||||
|
||||
useEffect(() => {
|
||||
const sl = getCookie("selectedLocale");
|
||||
const dl = getCookie("defaultLocale");
|
||||
setLocale(sl || dl || "en");
|
||||
}, []);
|
||||
|
||||
const isRTL = locale === "fa";
|
||||
const maxIndex = Math.max(0, testimonials.length - 3);
|
||||
|
||||
const scrollTo = (index: number) => {
|
||||
@ -114,14 +131,14 @@ export default function Testimonials({ dict }: TestimonialsProps) {
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
whileHover={{ y: -10 }}
|
||||
className="relative p-8 rounded-3xl glass-panel flex flex-col h-full"
|
||||
className="relative p-6 md:p-8 rounded-3xl glass-panel flex flex-col h-full"
|
||||
>
|
||||
<div className="absolute -top-6 -start-6 w-12 h-12 bg-primary rounded-full flex items-center justify-center text-white shadow-lg">
|
||||
<Quote className="w-6 h-6" />
|
||||
</div>
|
||||
|
||||
<div className="flex-grow">
|
||||
<p className="text-muted-foreground leading-relaxed mb-8 italic">
|
||||
<div className="flex-grow pt-2">
|
||||
<p className="text-muted-foreground leading-relaxed mb-8 italic text-sm md:text-base">
|
||||
"{testimonial.quote}"
|
||||
</p>
|
||||
</div>
|
||||
@ -155,20 +172,20 @@ export default function Testimonials({ dict }: TestimonialsProps) {
|
||||
{testimonials.length > 3 && (
|
||||
<>
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
disabled={currentIndex === 0}
|
||||
onClick={isRTL ? handleNext : handlePrev}
|
||||
disabled={isRTL ? currentIndex >= maxIndex : currentIndex === 0}
|
||||
className="absolute -left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-background border border-border shadow-lg flex items-center justify-center text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed z-10"
|
||||
aria-label="Previous testimonials"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
<ChevronLeft className={`w-5 h-5 ${isRTL ? "rotate-180" : ""}`} />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
disabled={currentIndex >= maxIndex}
|
||||
onClick={isRTL ? handlePrev : handleNext}
|
||||
disabled={isRTL ? currentIndex === 0 : currentIndex >= maxIndex}
|
||||
className="absolute -right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-background border border-border shadow-lg flex items-center justify-center text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed z-10"
|
||||
aria-label="Next testimonials"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
<ChevronRight className={`w-5 h-5 ${isRTL ? "rotate-180" : ""}`} />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 mt-8">
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 240 10% 98%;
|
||||
@ -82,6 +86,20 @@
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--muted-foreground) / 0.3);
|
||||
border-radius: 999px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--muted-foreground) / 0.5);
|
||||
}
|
||||
|
||||
.mesh-gradient {
|
||||
background-image:
|
||||
radial-gradient(at 0% 0%, hsla(30, 80%, 50%, 0.15) 0px, transparent 50%),
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Dictionary } from "@/app/types/types";
|
||||
import "server-only";
|
||||
|
||||
const dictionaries: { [key: string]: () => Promise<Dictionary> } = {
|
||||
en: () => import("../dictionaries/en.json").then((module) => module.default),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user