Compare commits

...

10 Commits

Author SHA1 Message Date
nabijonovdavronbek619@gmail.com
49f5c0979a for gitea 2026-01-28 17:03:27 +05:00
nabijonovdavronbek619@gmail.com
671344669c footer navbar linkes change with work asyn lamguage3 2026-01-28 15:30:25 +05:00
nabijonovdavronbek619@gmail.com
b9792e7eb5 footer navbar linkes change with work asyn lamguage2 2026-01-28 15:24:39 +05:00
nabijonovdavronbek619@gmail.com
638e5518e4 footer navbar linkes change with work asyn lamguage 2026-01-28 13:45:59 +05:00
nabijonovdavronbek619@gmail.com
ce8d14c9b2 change blog sectio from home page 2026-01-28 12:25:06 +05:00
nabijonovdavronbek619@gmail.com
29e06be337 text font fixed 2026-01-28 12:10:55 +05:00
nabijonovdavronbek619@gmail.com
196f99d8dd font-unbounded and font-almarai 2026-01-28 11:07:31 +05:00
nabijonovdavronbek619@gmail.com
ac7cd51600 translation 2026-01-27 20:44:21 +05:00
nabijonovdavronbek619@gmail.com
21cb013cd8 page loading updated 2026-01-27 17:46:10 +05:00
nabijonovdavronbek619@gmail.com
de2554a2e7 language Switcher added 2026-01-27 17:36:09 +05:00
49 changed files with 1659 additions and 656 deletions

View File

@@ -0,0 +1,25 @@
import {
AboutUs,
Banner,
Blog,
Line,
OurService,
Statistics,
Testimonial,
Video,
} from "@/components/pages/home";
export default function Home() {
return (
<main className="bg-slate-950">
<Banner />
<Statistics />
<AboutUs />
<Line />
<Blog />
<Video />
<OurService />
{/* <Testimonial /> */}
</main>
);
}

6
app/[locale]/page.tsx Normal file
View File

@@ -0,0 +1,6 @@
import { redirect } from 'next/navigation'
import React from 'react'
export default function Page() {
return redirect('/home')
}

View File

@@ -1,9 +1,60 @@
@import 'tailwindcss'; @import "tailwindcss";
@import 'tw-animate-css'; @import "tw-animate-css";
@custom-variant dark (&:is(.dark *)); @layer {
@import url("https://fonts.googleapis.com/css2?family=Almarai:wght@300;400;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Unbounded:wght@200..900&display=swap");
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--font-roboto: "Roboto", sans-serif;
--font-almarai: "Almarai", sans-serif;
--font-unbounded:"Unbounded",sans-serif;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
}
:root { :root {
--radius: 0.625rem;
--background: oklch(1 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.145 0 0); --foreground: oklch(0.145 0 0);
--card: oklch(1 0 0); --card: oklch(1 0 0);
@@ -19,7 +70,6 @@
--accent: oklch(0.97 0 0); --accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0); --accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0); --border: oklch(0.922 0 0);
--input: oklch(0.922 0 0); --input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0); --ring: oklch(0.708 0 0);
@@ -28,7 +78,6 @@
--chart-3: oklch(0.398 0.07 227.392); --chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429); --chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08); --chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0); --sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0); --sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0); --sidebar-primary: oklch(0.205 0 0);
@@ -42,11 +91,11 @@
.dark { .dark {
--background: oklch(0.145 0 0); --background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0); --card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0); --popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0); --popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0); --primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0); --primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0); --secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.985 0 0);
@@ -54,11 +103,10 @@
--muted-foreground: oklch(0.708 0 0); --muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0); --accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723); --destructive: oklch(0.704 0.191 22.216);
--destructive-foreground: oklch(0.637 0.237 25.331); --border: oklch(1 0 0 / 10%);
--border: oklch(0.269 0 0); --input: oklch(1 0 0 / 15%);
--input: oklch(0.269 0 0); --ring: oklch(0.556 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376); --chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48); --chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08); --chart-3: oklch(0.769 0.188 70.08);
@@ -70,49 +118,8 @@
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0); --sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0); --sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0); --sidebar-ring: oklch(0.556 0 0);
}
@theme inline {
--font-sans: 'Geist', 'Geist Fallback';
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
} }
@layer base { @layer base {
@@ -124,26 +131,6 @@
} }
} }
.loi{ body {
color:#a6040400; background: #1e1d1c;
} }
.gradient-text {
background: linear-gradient(
to bottom right,
#ffffff 0%,
#ffffff 50%,
rgba(255, 255, 255, 0.5) 100%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
/*
text gradient tailwindd classlari
bg-linear-to-br from-white via-white to-white/50
text-transparent bg-clip-text
*/

View File

@@ -4,9 +4,18 @@ import { Geist, Geist_Mono } from "next/font/google";
import { Analytics } from "@vercel/analytics/next"; import { Analytics } from "@vercel/analytics/next";
import "./globals.css"; import "./globals.css";
import { Footer, Navbar } from "@/components/layout"; import { Footer, Navbar } from "@/components/layout";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
const _geist = Geist({ subsets: ["latin"] }); const geistSans = Geist({
const _geistMono = Geist_Mono({ subsets: ["latin"] }); variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "FireForce - Emergency Services", title: "FireForce - Emergency Services",
@@ -32,18 +41,26 @@ export const metadata: Metadata = {
}, },
}; };
export default function RootLayout({ export default async function RootLayout({
children, children,
params,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
params: any;
}>) { }>) {
const { locale } = await params;
const messages: any = await getMessages();
return ( return (
<html lang="en"> <html lang={locale} suppressHydrationWarning>
<body className={`font-sans antialiased`}> <body
<Navbar /> className={`${geistSans.variable} ${geistMono.variable} antialiased`}
{children} >
<Footer /> <NextIntlClientProvider messages={messages} locale={locale}>
<Analytics /> <Navbar />
{children}
<Footer />
<Analytics />
</NextIntlClientProvider>
</body> </body>
</html> </html>
); );

View File

@@ -1,25 +1,5 @@
import { import { redirect } from "next/navigation";
AboutUs,
Banner,
Blog,
Line,
OurService,
Statistics,
Testimonial,
Video,
} from "@/components/pages/home";
export default function Home() { export default function Home() {
return ( return redirect('/uz/home')
<main className="bg-slate-950">
<Banner />
<Statistics />
<AboutUs />
<Video />
<OurService />
<Testimonial />
<Line />
<Blog />
</main>
);
} }

23
components/Counter.tsx Normal file
View File

@@ -0,0 +1,23 @@
"use client";
import { useEffect, useState } from "react";
import { animate, motion, useMotionValue, useTransform } from "framer-motion";
export function Counter({ countNum }: { countNum: number }) {
const count = useMotionValue(0);
const rounded = useTransform(count, (latest:any) => Math.round(latest));
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
const controls = animate(count, countNum, {
duration: 2,
ease: "easeOut",
onUpdate: (latest:any) => {
setDisplayValue(Math.round(latest));
},
});
return () => controls.stop();
}, [countNum]); // countNum dependency qo'shildi
return <motion.span>{displayValue}</motion.span>;
}

View File

@@ -0,0 +1,120 @@
"use client";
import { useState, useTransition } from "react";
import { useRouter, usePathname } from "next/navigation";
import { Check, ChevronDown, Globe } from "lucide-react";
import { locales, localeFlags, localeNames, type Locale } from "@/i18n/config";
import { useLocale } from "next-intl";
export default function LanguageSelectRadix() {
const router = useRouter();
const pathname = usePathname();
const currentLocale = useLocale() as Locale;
const [isPending, startTransition] = useTransition();
const [isOpen, setIsOpen] = useState(false);
const changeLanguage = (newLocale: Locale) => {
if (newLocale === currentLocale) {
setIsOpen(false);
return;
}
startTransition(() => {
// ✅ 1. Set cookie (middleware will sync this)
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`;
// ✅ 2. Build new path
const segments = pathname.split("/").filter(Boolean);
// Remove current locale if exists
const pathWithoutLocale =
segments[0] && locales.includes(segments[0] as Locale)
? segments.slice(1)
: segments;
// Add new locale
const newPath = `/${newLocale}${
pathWithoutLocale.length ? "/" + pathWithoutLocale.join("/") : ""
}`;
// ✅ 3. Navigate (middleware will handle the rest)
router.push(newPath);
// ✅ 4. Force refresh after navigation completes
setTimeout(() => {
router.refresh();
}, 100); // Small delay ensures navigation completes
});
setIsOpen(false);
};
return (
<div className="relative">
{/* Trigger Button */}
<button
onClick={() => setIsOpen(!isOpen)}
disabled={isPending}
className="inline-flex items-center justify-between gap-2 px-2 py-1 border border-gray-300 hover:border-red-600
rounded-lg text-white text-sm font-medium shadow-sm hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
aria-label="Tilni tanlash"
aria-expanded={isOpen}
>
<div className="flex items-center gap-2">
{isPending ? (
<Globe className="w-4 h-4 animate-spin" />
) : (
<span className="text-lg">{localeFlags[currentLocale]}</span>
)}
<span className="hidden md:inline font-medium">
{localeNames[currentLocale]}
</span>
</div>
<ChevronDown
className={`w-4 h-4 transition-transform duration-200 ${
isOpen ? "rotate-180" : ""
}`}
/>
</button>
{/* Dropdown */}
{isOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 z-40"
onClick={() => setIsOpen(false)}
aria-hidden="true"
/>
{/* Menu */}
<div className="absolute right-0 mt-2 w-48 rounded-lg border border-gray-200 bg-white shadow-lg z-50 animate-in fade-in slide-in-from-top-2 duration-200">
<div className="p-1" role="menu">
{locales.map((lang) => (
<button
key={lang}
onClick={() => changeLanguage(lang)}
disabled={isPending}
role="menuitem"
className={`w-full flex items-center justify-between px-3 py-2 text-sm rounded-md transition-colors ${
currentLocale === lang
? "bg-blue-50 text-blue-700 font-medium"
: "hover:bg-gray-100 text-gray-700"
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
<div className="flex items-center gap-2">
<span className="text-lg">{localeFlags[lang]}</span>
<span>{localeNames[lang]}</span>
</div>
{currentLocale === lang && (
<Check className="w-4 h-4 text-blue-700" />
)}
</button>
))}
</div>
</div>
</>
)}
</div>
);
}

View File

@@ -4,8 +4,13 @@ import React from "react";
import { useState } from "react"; import { useState } from "react";
import { Mail, Phone, MapPin } from "lucide-react"; import { Mail, Phone, MapPin } from "lucide-react";
import { useLocale, useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
export function Footer() { export function Footer() {
const locale = useLocale();
const t = useTranslations();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [subscribed, setSubscribed] = useState(false); const [subscribed, setSubscribed] = useState(false);
@@ -26,16 +31,16 @@ export function Footer() {
"linear-gradient(to top right, #452811 0%, #000000 20%, #000000 40%, #000000 60%, #000000 80%, #000000 100%)", "linear-gradient(to top right, #452811 0%, #000000 20%, #000000 40%, #000000 60%, #000000 80%, #000000 100%)",
}} }}
> >
{/* Newsletter Section */} {/* Newsletter Section for gitea */}
<div className=" absolute w-full -top-40 px-4 py-12 md:py-16"> <div className=" absolute w-full -top-40 px-4 py-12 md:py-16">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<div className="rounded-2xl bg-[#fa1d1d] px-6 py-8 md:flex lg:flex-row flex-col max-lg:gap-5 md:items-center lg:justify-between justify-center md:px-10 md:py-12"> <div className="rounded-2xl bg-[#fa1d1d] px-6 py-8 md:flex lg:flex-row flex-col max-lg:gap-5 md:items-center lg:justify-between justify-center md:px-10 md:py-12">
<div className="mb-8 md:mb-0"> <div className="mb-8 md:mb-0">
<h2 className="text-2xl font-bold text-white md:text-3xl"> <h2 className="font-unbounded text-2xl font-bold text-white md:text-3xl">
SUBSCRIBE OUR NEWSLETTER {t("contactTitle")}
</h2> </h2>
<p className="mt-2 text-gray-100"> <p className="font-almarai mt-2 text-gray-100">
Expect a friendly letter from us once a week. No spam. {t("contactSubTitle")}
</p> </p>
</div> </div>
@@ -45,17 +50,17 @@ export function Footer() {
> >
<input <input
type="email" type="email"
placeholder="Enter your email address" placeholder={t("enterPhone")}
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="flex-1 rounded-full bg-white px-6 py-3 text-gray-800 placeholder-gray-400 focus:outline-none md:w-64" className="font-almarai flex-1 rounded-full bg-white px-6 py-3 text-gray-800 placeholder-gray-400 focus:outline-none md:w-64"
required required
/> />
<button <button
type="submit" type="submit"
className="rounded-full bg-gray-800 px-6 py-3 font-bold text-white transition hover:bg-gray-700" className="font-almarai rounded-full bg-gray-800 px-6 py-3 font-bold text-white transition hover:bg-gray-700"
> >
{subscribed ? "✓ Sent" : "SIGN UP"} {subscribed ? "✓ Sent" : t("send")}
</button> </button>
</form> </form>
</div> </div>
@@ -69,91 +74,71 @@ export function Footer() {
{/* Brand Section */} {/* Brand Section */}
<div> <div>
<div className="mb-4 flex items-center gap-2"> <div className="mb-4 flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-[#fa1d1d]"> <div className=" flex items-center justify-center">
<span className="text-lg font-bold text-white"></span> <Image
src="/images/IGNUM/PNG/1.@6x.png"
alt="logo image"
width={70}
height={70}
className=""
/>
</div> </div>
<span className="text-xl font-bold text-white">FIREFORCE</span>
</div> </div>
<p className="text-sm leading-relaxed text-gray-300"> <p className="font-almarai text-sm leading-relaxed text-gray-300">
They are seen as a beacon of hope, a figure who brings calm {t("footer.description")}
amidst chaos and light in the darkest of moments.
</p> </p>
</div> </div>
{/* Quick Links */} {/* Quick Links */}
<div> <div>
<h3 className="mb-6 text-lg font-bold text-white">QUICK LINK</h3> <h3 className="font-unbounded uppercase mb-6 text-lg font-bold text-white">
{t("footer.quickLinks.title")}
</h3>
<ul className="space-y-3 text-gray-300"> <ul className="space-y-3 text-gray-300">
<li> <li>
<a href="#home" className="transition hover:text-[#fa1d1d]"> <Link
Home href={`/${locale}/home`}
</a> className="font-almarai transition hover:text-[#fa1d1d]"
</li>
<li>
<a href="#about" className="transition hover:text-[#fa1d1d]">
About
</a>
</li>
<li>
<a
href="#services"
className="transition hover:text-[#fa1d1d]"
> >
Services {t("footer.quickLinks.home")}
</a> </Link>
</li> </li>
<li> <li>
<a <Link
href="#pricing" href={`/${locale}/about`}
className="transition hover:text-[#fa1d1d]" className="font-almarai transition hover:text-[#fa1d1d]"
> >
Pricing {t("footer.quickLinks.about")}
</a> </Link>
</li> </li>
<li> <li>
<a href="#blog" className="transition hover:text-[#fa1d1d]"> <Link
Blog href={`/${locale}/services`}
</a> className="font-almarai transition hover:text-[#fa1d1d]"
>
{t("footer.quickLinks.services")}
</Link>
</li>
<li>
<Link
href={`/${locale}/blog`}
className="font-almarai transition hover:text-[#fa1d1d]"
>
{t("footer.quickLinks.products")}
</Link>
</li> </li>
</ul> </ul>
</div> </div>
{/* Support */} {/* Support */}
<div> <div>
<h3 className="mb-6 text-lg font-bold text-white">SUPPORT</h3> <h3 className="font-unbounded mb-6 text-lg font-bold text-white">
<ul className="space-y-3 text-gray-300"> {t("footer.support.title")}
</h3>
<ul className="font-almarai space-y-3 text-gray-300">
<li> <li>
<a href="#help" className="transition hover:text-[#fa1d1d]"> <a href="#help" className="transition hover:text-[#fa1d1d]">
Help Center {t("footer.support.contact")}
</a>
</li>
<li>
<a
href="#disclaimer"
className="transition hover:text-[#fa1d1d]"
>
Disclaimer
</a>
</li>
<li>
<a href="#faq" className="transition hover:text-[#fa1d1d]">
FAQ
</a>
</li>
<li>
<a
href="#support"
className="transition hover:text-[#fa1d1d]"
>
Support
</a>
</li>
<li>
<a
href="#contact"
className="transition hover:text-[#fa1d1d]"
>
Contact
</a> </a>
</li> </li>
</ul> </ul>
@@ -161,12 +146,14 @@ export function Footer() {
{/* Contact */} {/* Contact */}
<div> <div>
<h3 className="mb-6 text-lg font-bold text-white">CONTACT</h3> <h3 className="font-unbounded uppercase mb-6 text-lg font-bold text-white">
<ul className="space-y-4 text-gray-300"> {t("footer.support.contact")}
</h3>
<ul className="font-almarai space-y-4 text-gray-300">
<li className="flex items-start gap-3"> <li className="flex items-start gap-3">
<Phone className="mt-1 h-5 w-5 shrink-0 text-white" /> <Phone className="mt-1 h-5 w-5 shrink-0 text-white" />
<a href="tel:+1234567890" className="hover:text-[#fa1d1d]"> <a href="tel:+998773722121" className="hover:text-[#fa1d1d]">
+123-456-7890 +998-77-372-21-21
</a> </a>
</li> </li>
<li className="flex items-start gap-3"> <li className="flex items-start gap-3">
@@ -191,13 +178,9 @@ export function Footer() {
{/* Copyright Section */} {/* Copyright Section */}
<div className="border-t border-gray-800 px-4 py-8"> <div className="border-t border-gray-800 px-4 py-8">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<div className="flex flex-col justify-between gap-4 text-sm text-gray-400 md:flex-row md:items-center"> <div className="font-almarai flex flex-col justify-between gap-4 text-sm text-gray-400 md:flex-row md:items-center">
<div> <div>
Copyright © 2025 Fireforce. Built with Copyright © 2025 Ignum Company.
<span className="text-white">
{" "}
Guniverse WordPress Blocks Addons
</span>
</div> </div>
<div className="flex gap-6"> <div className="flex gap-6">
<a href="#terms" className="hover:text-white"> <a href="#terms" className="hover:text-white">

View File

@@ -3,8 +3,12 @@ import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { ChevronDown, Phone, Menu, X } from "lucide-react"; import { ChevronDown, Phone, Menu, X } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import LanguageSelectRadix from "../languageSwitcher";
import { useLocale, useTranslations } from "next-intl";
export function Navbar() { export function Navbar() {
const locale = useLocale();
const t = useTranslations();
const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
@@ -34,9 +38,9 @@ export function Navbar() {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-20"> <div className="flex justify-between items-center h-20">
{/* Logo */} {/* Logo */}
<Link href="/" className="hover:cursor-pointer"> <Link href={`/${locale}/home`} className="hover:cursor-pointer">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="p-2 rounded-xl flex items-center justify-center"> <div className=" flex items-center justify-center">
<Image <Image
src="/images/IGNUM/PNG/1.@6x.png" src="/images/IGNUM/PNG/1.@6x.png"
alt="logo image" alt="logo image"
@@ -51,25 +55,25 @@ export function Navbar() {
{/* Desktop Navigation Menu */} {/* Desktop Navigation Menu */}
<div className="hidden h-full lg:flex items-center gap-8"> <div className="hidden h-full lg:flex items-center gap-8">
<Link <Link
href="/" href={`/${locale}/home`}
className="text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition" className="font-unbounded uppercase text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition"
> >
HOME {t("navbar.home")}
</Link> </Link>
<Link <Link
href="/about" href={`/${locale}/about`}
className="text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition" className="font-unbounded uppercase text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition"
> >
ABOUT {t("navbar.about")}
</Link> </Link>
{/* Pages Dropdown */} {/* Pages Dropdown */}
<div className="relative group h-full"> <div className="relative group h-full">
<button <button
className="text-white text-sm h-full font-semibold hover:text-red-500 className="font-unbounded uppercase text-white text-sm h-full font-semibold hover:text-red-500
transition-colors flex items-center gap-1" transition-colors flex items-center gap-1"
> >
PAGES {t("navbar.pages")}
<ChevronDown <ChevronDown
size={16} size={16}
className="transition-transform group-hover:rotate-180" className="transition-transform group-hover:rotate-180"
@@ -85,22 +89,22 @@ export function Navbar() {
pointer-events-none group-hover:pointer-events-auto overflow-hidden" pointer-events-none group-hover:pointer-events-auto overflow-hidden"
> >
<Link <Link
href="/faq" href={`/${locale}/faq`}
className="block px-4 py-2 text-black text-sm hover:bg-red-600 className="font-unbounded uppercase block px-4 py-2 text-black text-sm hover:bg-red-600
hover:text-white transition-colors" hover:text-white transition-colors"
> >
FAQ {t("navbar.faq")}
</Link> </Link>
<Link <Link
href="/services" href={`/${locale}/services`}
className="block px-4 py-2 text-black text-sm hover:bg-red-600 className="font-unbounded uppercase block px-4 py-2 text-black text-sm hover:bg-red-600
hover:text-white transition-colors" hover:text-white transition-colors"
> >
Services {t("navbar.services")}
</Link> </Link>
{/* <Link {/* <Link
href="/blog" href={`/${locale}/blog`}
className="block px-4 py-2 text-black text-sm hover:bg-red-600 className="font-unbounded uppercase block px-4 py-2 text-black text-sm hover:bg-red-600
hover:text-white transition-colors rounded-b-md" hover:text-white transition-colors rounded-b-md"
> >
Blog Blog
@@ -109,39 +113,46 @@ export function Navbar() {
</div> </div>
<Link <Link
href="/products" href={`/${locale}/products`}
className="text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition" className="font-unbounded uppercase text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition"
> >
PRODUCTS {t("navbar.products")}
</Link> </Link>
<Link <Link
href="/contact" href={`/${locale}/contact`}
className="text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition" className="font-unbounded uppercase text-white text-sm h-full flex items-center font-semibold hover:cursor-pointer hover:text-red-500 transition"
> >
CONTACT {t("navbar.contact")}
</Link> </Link>
</div> </div>
{/* Emergency Call Button - Hidden on mobile */} <div className="flex items-center gap-5">
<div className="hidden lg:flex items-center gap-3 bg-red-600 px-4 py-2 rounded-full"> {/* Language Switcher */}
<Phone size={20} className="text-white" /> <LanguageSelectRadix />
<div>
<div className="text-white text-xs font-semibold"> {/* Emergency Call Button - Hidden on mobile */}
Emergency Call! <div className="hidden lg:flex items-center hover:cursor-pointer gap-3 px-4 py-2 rounded-full">
</div> <span
<div className="text-white text-sm font-bold"> className="shrink-0 w-10 h-10 bg-red-600 rounded-full flex items-center justify-center
+123-456-7890 shadow-[0_0_0px_4px_rgba(239,68,68,0.1)]"
>
<Phone size={20} className="text-white" />
</span>
<div>
<div className="text-white text-sm font-bold">
<div>+998-98-099-21-21</div> <div>+998-77-372-21-21</div>
</div>
</div> </div>
</div> </div>
</div>
{/* Mobile Menu Button */} {/* Mobile Menu Button */}
<button <button
onClick={() => setIsMobileMenuOpen(true)} onClick={() => setIsMobileMenuOpen(true)}
className="lg:hidden text-white p-2 hover:bg-red-600 rounded-md transition" className="lg:hidden text-white p-2 hover:bg-red-600 rounded-md transition"
> >
<Menu size={24} /> <Menu size={24} />
</button> </button>
</div>
</div> </div>
</div> </div>
</nav> </nav>
@@ -164,14 +175,20 @@ export function Navbar() {
> >
{/* Mobile Menu Header */} {/* Mobile Menu Header */}
<div className="flex justify-between items-center p-6 border-b border-gray-700"> <div className="flex justify-between items-center p-6 border-b border-gray-700">
<div className="flex items-center gap-2"> {/* Logo */}
<div className="w-8 h-8 bg-red-600 rounded-full flex items-center justify-center"> <Link href={`/${locale}/home`} className="hover:cursor-pointer">
<div className="w-6 h-6 bg-white rounded-full"></div> <div className="font-unbounded uppercase flex flex-col items-center text-2xl gap-2 text-white font-bold">
<div className=" flex items-center justify-center">
<Image
src="/images/IGNUM/PNG/1.@6x.png"
alt="logo image"
width={50}
height={50}
className=""
/>
</div>
</div> </div>
<span className="text-white font-bold text-lg tracking-wider"> </Link>
FIREFORCE
</span>
</div>
<button <button
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
className="text-white p-2 hover:bg-red-600 rounded-md transition" className="text-white p-2 hover:bg-red-600 rounded-md transition"
@@ -183,27 +200,27 @@ export function Navbar() {
{/* Mobile Menu Links */} {/* Mobile Menu Links */}
<div className="flex flex-col p-6 gap-4"> <div className="flex flex-col p-6 gap-4">
<Link <Link
href="/" href={`/${locale}/home`}
className="text-white text-base font-semibold hover:text-red-500 transition py-2" className="font-unbounded uppercase text-white text-base font-semibold hover:text-red-500 transition py-2"
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
HOME {t("navbar.home")}
</Link> </Link>
<Link <Link
href="/about" href={`/${locale}/about`}
className="text-white text-base font-semibold hover:text-red-500 transition py-2" className="font-unbounded uppercase text-white text-base font-semibold hover:text-red-500 transition py-2"
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
ABOUT {t("navbar.about")}
</Link> </Link>
{/* Mobile Pages Dropdown */} {/* Mobile Pages Dropdown */}
<div> <div>
<button <button
onClick={() => setIsDropdownOpen(!isDropdownOpen)} onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className="text-white text-base font-semibold hover:text-red-500 transition flex items-center gap-1 py-2 w-full" className="font-unbounded uppercase text-white text-base font-semibold hover:text-red-500 transition flex items-center gap-1 py-2 w-full"
> >
PAGES {t("navbar.pages")}
<ChevronDown <ChevronDown
size={16} size={16}
className={`transition-transform ${isDropdownOpen ? "rotate-180" : ""}`} className={`transition-transform ${isDropdownOpen ? "rotate-180" : ""}`}
@@ -212,36 +229,36 @@ export function Navbar() {
{isDropdownOpen && ( {isDropdownOpen && (
<div className="ml-4 mt-2 flex flex-col gap-2"> <div className="ml-4 mt-2 flex flex-col gap-2">
<Link <Link
href="/faq" href={`/${locale}/faq`}
className="text-white/80 text-sm hover:text-red-500 transition py-2" className="font-unbounded uppercase text-white/80 text-sm hover:text-red-500 transition py-2"
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
FAQ {t("navbar.faq")}
</Link> </Link>
<Link <Link
href="/services" href={`/${locale}/services`}
className="text-white/80 text-sm hover:text-red-500 transition py-2" className="font-unbounded uppercase text-white/80 text-sm hover:text-red-500 transition py-2"
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
Services {t("navbar.services")}
</Link> </Link>
</div> </div>
)} )}
</div> </div>
<Link <Link
href="/products" href={`/${locale}/products`}
className="text-white text-base font-semibold hover:text-red-500 transition py-2" className="font-unbounded uppercase text-white text-base font-semibold hover:text-red-500 transition py-2"
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
PRODUCTS {t("navbar.products")}
</Link> </Link>
<Link <Link
href="/contact" href={`/${locale}/contact`}
className="text-white text-base font-semibold hover:text-red-500 transition py-2" className="font-unbounded uppercase text-white text-base font-semibold hover:text-red-500 transition py-2"
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
CONTACT {t("navbar.contact")}
</Link> </Link>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function AboutBanner() { export function AboutBanner() {
const t = useTranslations();
return ( return (
<section className="relative w-full lg:h-[60vh] h-screen min-h-100 overflow-hidden pt-10"> <section className="relative w-full lg:h-[60vh] h-screen min-h-100 overflow-hidden pt-10">
{/* Background Image */} {/* Background Image */}
@@ -24,17 +26,21 @@ export function AboutBanner() {
<div className="max-w-250 w-full mx-auto px-4"> <div className="max-w-250 w-full mx-auto px-4">
<div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30"> <div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30">
<div className="spacw-y-4 "> <div className="spacw-y-4 ">
<DotAnimatsiya /> <div className="flex items-center gap-3">
<DotAnimatsiya />
<span className="font-almarai text-sm text-white font-semibold tracking-wide">
{t("about.banner.title")}
</span>
</div>
<p <p
className=" bg-linear-to-br from-white via-white to-black className="font-unbounded uppercase bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty" text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty"
> >
Fire Fighter <br /> At The Ready {t("about.banner.subtitle")}
</p> </p>
</div> </div>
<div className="lg:w-[40%] text-gray-300 mt-20"> <div className="font-almarai lg:w-[40%] text-gray-300 mt-20">
It emphasizes that these firefighters are there not just as public {t("about.banner.description")}
servants but as a vital part of the community.
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,8 @@
import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
export function Story() { export function Story() {
const t = useTranslations();
return ( return (
<div className="bg-[#1e1d1c] pb-0 relative z-10 max-[350px]:pb-30 "> <div className="bg-[#1e1d1c] pb-0 relative z-10 max-[350px]:pb-30 ">
<div className="max-w-260 mx-auto px-4"> <div className="max-w-260 mx-auto px-4">
@@ -25,22 +27,14 @@ export function Story() {
{/* Content */} {/* Content */}
<div className="relative z-20 p-8 lg:p-12 max-w-130"> <div className="relative z-20 p-8 lg:p-12 max-w-130">
<h2 className="text-white text-4xl lg:text-5xl font-bold mb-6"> <h2 className="uppercase font-unbounded text-white text-4xl lg:text-5xl font-bold mb-6">
OUR STORY {t("about.story.title")}
</h2> </h2>
<p className="text-gray-300 mb-4"> <p className="font-almarai text-gray-300 mb-4">
Our story is one of unwavering dedication, selflessness, and a{" "} {t("about.story.description")}
<span className="text-white font-semibold">
deep commitment to the safety
</span>
and well-being of our communities.
</p> </p>
<p className="text-gray-400 mb-6"> <button className="font-almarai text-white flex items-center gap-2 hover:text-red-500 transition">
Aliquam lorem ante dapibus in viverra quis a tellus phasellus {t("about.story.readMore")}
viverra nulla ut metus varius laoreet quisque rutrum.
</p>
<button className="text-white flex items-center gap-2 hover:text-red-500 transition">
READ MORE
<svg <svg
className="w-4 h-4" className="w-4 h-4"
fill="none" fill="none"
@@ -74,14 +68,15 @@ export function Story() {
/> />
</span> </span>
<div className="space-y-1 text-white"> <div className="space-y-1 text-white">
<h2 className="text-xl font-semibold">FIREFORCE VISION</h2> <h2 className="font-unbounded text-xl font-semibold">
<p> {t("about.story.vision.title")}
Phasellus viverra nulla ut metus varius leo imperdiet laoreet. </h2>
Quisque rutrum aenean augue vulputate eleifend. <p className="font-almarai">
{t("about.story.vision.description")}
</p> </p>
</div> </div>
</div> </div>
<div className="flex sm:flex-row flex-col sm:items-center items-start md:rounded-t-xl max-md:rounded-xl max-w-md w-full sm:p-7 p-2 bg-black sm:gap-5 gap-2"> <div className="flex sm:flex-row flex-col sm:items-center items-start md:rounded-t-xl max-md:rounded-xl max-w-md w-full sm:p-4 p-2 bg-black sm:gap-5 gap-2">
<span className="sm:rounded-xl rounded-lg bg-[#1e1d1c] sm:p-3 p-1 max-sm:w-15 max-sm:h-15"> <span className="sm:rounded-xl rounded-lg bg-[#1e1d1c] sm:p-3 p-1 max-sm:w-15 max-sm:h-15">
<Image <Image
src="/images/about/fireforce-mission.png" src="/images/about/fireforce-mission.png"
@@ -92,10 +87,11 @@ export function Story() {
/> />
</span> </span>
<div className="space-y-1 text-white"> <div className="space-y-1 text-white">
<h2 className="text-xl font-semibold">FIREFORCE VISION</h2> <h2 className="font-unbounded text-xl font-semibold">
<p> {t("about.story.mission.title")}
Phasellus viverra nulla ut metus varius leo imperdiet laoreet. </h2>
Quisque rutrum aenean augue vulputate eleifend. <p className="font-almarai">
{t("about.story.mission.description")}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -3,13 +3,15 @@
import Image from "next/image"; import Image from "next/image";
import { Check } from "lucide-react"; import { Check } from "lucide-react";
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function WhyChooseUs() { export function WhyChooseUs() {
const t = useTranslations();
const features = [ const features = [
{ title: "Fast Response Team" }, { title: t("about.whyChoose.features.fastResponse") },
{ title: "Experienced Firefighter" }, { title: t("about.whyChoose.features.ready24") },
{ title: "Ready 24 Hours" }, { title: t("about.whyChoose.features.experienced") },
{ title: "Fast Response Team" }, { title: t("about.whyChoose.features.quality") },
]; ];
return ( return (
@@ -21,24 +23,21 @@ export function WhyChooseUs() {
<div> <div>
<div className="mb-4 flex items-center gap-2"> <div className="mb-4 flex items-center gap-2">
<DotAnimatsiya /> <DotAnimatsiya />
<span className="text-sm font-semibold tracking-wider text-white"> <span className="font-almarai text-sm font-semibold tracking-wider text-white">
WHY CHOOSE US {t("about.whyChoose.title")}
</span> </span>
</div> </div>
<h2 <h2
className="bg-linear-to-br from-white via-white to-black className="font-unbounded uppercase bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text text-4xl font-bold sm:text-5xl lg:text-6xl" text-transparent bg-clip-text text-3xl font-bold sm:text-4xl lg:text-5xl"
> >
WE ARE BEST {t("about.whyChoose.subtitle")}
<br />
FIREFIGHTER
</h2> </h2>
</div> </div>
{/* Description */} {/* Description */}
<p className="text-base leading-relaxed text-gray-400 sm:text-lg"> <p className="font-almarai text-base leading-relaxed text-gray-400 sm:text-lg">
Aliquam lorem ante dapibus in viverra quis a tellus phasellus {t("about.whyChoose.description")}
viverra nulla ut metus varius laoreet quisque rutrum.
</p> </p>
{/* Features Grid */} {/* Features Grid */}
@@ -48,7 +47,7 @@ export function WhyChooseUs() {
<div className="shrink-0"> <div className="shrink-0">
<Check className="h-5 w-5 text-red-600 sm:h-6 sm:w-6" /> <Check className="h-5 w-5 text-red-600 sm:h-6 sm:w-6" />
</div> </div>
<span className="text-sm font-medium text-white sm:text-base"> <span className="font-almarai text-sm font-medium text-white sm:text-base">
{feature.title} {feature.title}
</span> </span>
</div> </div>
@@ -57,8 +56,8 @@ export function WhyChooseUs() {
{/* CTA Button */} {/* CTA Button */}
<div> <div>
<button className="shadow-[0px_0px_2px_8px_#ff01015c] rounded-full bg-red-600 px-6 py-3 font-bold text-white transition-all hover:bg-red-700 sm:px-8 sm:py-4"> <button className="font-almarai shadow-[0px_0px_2px_8px_#ff01015c] rounded-full bg-red-600 px-6 py-3 font-bold text-white transition-all hover:bg-red-700 sm:px-8 sm:py-4">
CONTACT US {t("about.whyChoose.contact")}
</button> </button>
</div> </div>
</div> </div>
@@ -87,11 +86,8 @@ export function WhyChooseUs() {
className="h-24 w-24 sm:h-20 sm:w-20" className="h-24 w-24 sm:h-20 sm:w-20"
/> />
<div> <div>
<p className="text-sm font-bold text-white sm:text-base"> <p className="font-almarai text-sm font-bold text-white sm:text-base">
BEST AWARD {t("about.whyChoose.award")}
</p>
<p className="text-xs text-gray-400 sm:text-sm">
FIREFIGHTER 2025
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,23 +1,25 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
import React from "react"; import React from "react";
export default function ContactHeader() { export default function ContactHeader() {
const t = useTranslations();
return ( return (
<div className="mb-8 text-center"> <div className="mb-8 text-center">
<div className="mb-4 flex items-center justify-center gap-2"> <div className="mb-4 flex items-center justify-center gap-2">
<DotAnimatsiya /> <DotAnimatsiya />
<span className="text-sm font-semibold tracking-wider text-white"> <span className="font-almarai text-sm font-semibold tracking-wider text-white">
CONTACT US {t("contact.banner.title")}
</span> </span>
</div> </div>
<h2 <h2
className="bg-linear-to-br from-white via-white to-black className="uppercase font-unbounded bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text text-4xl font-bold tracking-wide md:text-5xl" text-transparent bg-clip-text text-4xl font-bold tracking-wide md:text-5xl"
> >
GET IN TOUCH {t("contact.banner.subtitle")}
</h2> </h2>
<p className="mt-3 text-sm text-gray-300 italic"> <p className="font-almarai mt-3 text-sm text-gray-300 italic">
We'd love to hear from you. Please fill out this form. {t("contact.banner.description")}
</p> </p>
</div> </div>
); );

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { Check } from "lucide-react"; import { Check } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react"; import { useState } from "react";
interface FormData { interface FormData {
@@ -22,6 +23,7 @@ interface FormErrors {
} }
export default function Form() { export default function Form() {
const t = useTranslations();
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<FormData>({
firstName: "", firstName: "",
lastName: "", lastName: "",
@@ -124,30 +126,31 @@ export default function Form() {
<input <input
type="text" type="text"
name="firstName" name="firstName"
placeholder="First Name" placeholder={t("contact.form.placeholders.firstName")}
value={formData.firstName} value={formData.firstName}
onChange={handleChange} onChange={handleChange}
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${ className={`font-almarai w-full rounded-3xl border bg-white px-4 py-3 text-sm
text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
errors.firstName ? "border-red-500" : "border-transparent" errors.firstName ? "border-red-500" : "border-transparent"
}`} }`}
/> />
{errors.firstName && ( {errors.firstName && (
<p className="mt-1 text-xs text-red-500">{errors.firstName}</p> <p className="font-almarai mt-1 text-xs text-red-500">{errors.firstName}</p>
)} )}
</div> </div>
<div> <div>
<input <input
type="text" type="text"
name="lastName" name="lastName"
placeholder="Last Name" placeholder={t("contact.form.placeholders.lastName")}
value={formData.lastName} value={formData.lastName}
onChange={handleChange} onChange={handleChange}
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${ className={`font-almarai w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
errors.lastName ? "border-red-500" : "border-transparent" errors.lastName ? "border-red-500" : "border-transparent"
}`} }`}
/> />
{errors.lastName && ( {errors.lastName && (
<p className="mt-1 text-xs text-red-500">{errors.lastName}</p> <p className="font-almarai mt-1 text-xs text-red-500">{errors.lastName}</p>
)} )}
</div> </div>
</div> </div>
@@ -158,30 +161,30 @@ export default function Form() {
<input <input
type="email" type="email"
name="email" name="email"
placeholder="Your Email Address" placeholder={t("contact.form.placeholders.email")}
value={formData.email} value={formData.email}
onChange={handleChange} onChange={handleChange}
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${ className={`font-almarai w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
errors.email ? "border-red-500" : "border-transparent" errors.email ? "border-red-500" : "border-transparent"
}`} }`}
/> />
{errors.email && ( {errors.email && (
<p className="mt-1 text-xs text-red-500">{errors.email}</p> <p className="font-almarai mt-1 text-xs text-red-500">{errors.email}</p>
)} )}
</div> </div>
<div> <div>
<input <input
type="text" type="text"
name="subject" name="subject"
placeholder="Subject" placeholder={t("contact.form.placeholders.subject")}
value={formData.subject} value={formData.subject}
onChange={handleChange} onChange={handleChange}
className={`w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${ className={`font-almarai w-full rounded-3xl border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
errors.subject ? "border-red-500" : "border-transparent" errors.subject ? "border-red-500" : "border-transparent"
}`} }`}
/> />
{errors.subject && ( {errors.subject && (
<p className="mt-1 text-xs text-red-500">{errors.subject}</p> <p className="font-almarai mt-1 text-xs text-red-500">{errors.subject}</p>
)} )}
</div> </div>
</div> </div>
@@ -190,16 +193,16 @@ export default function Form() {
<div> <div>
<textarea <textarea
name="message" name="message"
placeholder="Leave us a message" placeholder={t("contact.form.placeholders.message")}
rows={8} rows={8}
value={formData.message} value={formData.message}
onChange={handleChange} onChange={handleChange}
className={`w-full resize-none rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${ className={`font-almarai w-full resize-none rounded-md border bg-white px-4 py-3 text-sm text-gray-700 placeholder-gray-400 outline-none transition focus:ring-2 focus:ring-red-500 ${
errors.message ? "border-red-500" : "border-transparent" errors.message ? "border-red-500" : "border-transparent"
}`} }`}
/> />
{errors.message && ( {errors.message && (
<p className="mt-1 text-xs text-red-500">{errors.message}</p> <p className="font-almarai mt-1 text-xs text-red-500">{errors.message}</p>
)} )}
</div> </div>
@@ -220,30 +223,30 @@ export default function Form() {
<Check className="h-3 w-3 text-white" strokeWidth={3} /> <Check className="h-3 w-3 text-white" strokeWidth={3} />
)} )}
</button> </button>
<span className="text-sm text-gray-300"> <span className="font-almarai text-sm text-gray-300">
You agree to our friendly privacy policy {t("contact.form.privacy")}
</span> </span>
</div> </div>
<button <button
type="submit" type="submit"
disabled={isSubmitting} disabled={isSubmitting}
className="shadow-[0px_0px_2px_8px_#ff01015c] rounded-full bg-red-600 px-8 py-3 text-sm font-semibold uppercase tracking-wider text-white transition hover:cursor-pointer hover:scale-90 disabled:cursor-not-allowed disabled:opacity-50" className="font-almarai shadow-[0px_0px_2px_8px_#ff01015c] rounded-full bg-red-600 px-8 py-3 text-sm font-semibold uppercase tracking-wider text-white transition hover:cursor-pointer hover:scale-90 disabled:cursor-not-allowed disabled:opacity-50"
> >
{isSubmitting ? "Sending..." : "Send Message"} {isSubmitting ? "Sending..." : t("contact.form.send")}
</button> </button>
</div> </div>
{errors.agreeToPolicy && ( {errors.agreeToPolicy && (
<p className="text-xs text-red-500">{errors.agreeToPolicy}</p> <p className="font-almarai text-xs text-red-500">{errors.agreeToPolicy}</p>
)} )}
{/* Status Messages */} {/* Status Messages */}
{submitStatus === "success" && ( {submitStatus === "success" && (
<p className="text-center text-sm text-green-400"> <p className="font-almarai text-center text-sm text-green-400">
Message sent successfully! Message sent successfully!
</p> </p>
)} )}
{submitStatus === "error" && ( {submitStatus === "error" && (
<p className="text-center text-sm text-red-400"> <p className="font-almarai text-center text-sm text-red-400">
Failed to send message. Please try again. Failed to send message. Please try again.
</p> </p>
)} )}

View File

@@ -2,23 +2,25 @@ import Image from "next/image";
import { Mail, MapPin, Phone, Check } from "lucide-react"; import { Mail, MapPin, Phone, Check } from "lucide-react";
import ContactHeader from "./contactHeader"; import ContactHeader from "./contactHeader";
import Form from "./form"; import Form from "./form";
import { useTranslations } from "next-intl";
export function Contact() { export function Contact() {
const t = useTranslations();
const contactInfo = [ const contactInfo = [
{ {
icon: Mail, icon: Mail,
title: "EMAIL", title:t("contact.form.email"),
detail: "support@fireforce", detail: t("contact.form.emailAddress"),
}, },
{ {
icon: MapPin, icon: MapPin,
title: "OUR LOCATION", title: t("contact.form.location"),
detail: "Jl. Dr. Ir. Soekarno No. 99x Tabanan - Bali", detail: t("contact.form.address"),
}, },
{ {
icon: Phone, icon: Phone,
title: "PHONE", title: t("contact.form.phone"),
detail: "+123-456-7890", detail: "+998-77-372-21-21",
}, },
]; ];
@@ -38,7 +40,7 @@ export function Contact() {
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
background: background:
"radial-gradient(ellipse at bottom center, #d2610ab0 0%, #1e1d1ce3 70%)", "radial-gradient(ellipse at bottom center, #d2610a 0%, #1e1d1ce9 70% , #1e1d1ce9 70%)",
}} }}
/> />
</div> </div>
@@ -59,10 +61,10 @@ export function Contact() {
<div className="mb-3 flex h-14 w-14 items-center justify-center rounded-xl bg-[#2c2b2a]"> <div className="mb-3 flex h-14 w-14 items-center justify-center rounded-xl bg-[#2c2b2a]">
<info.icon className="h-6 w-6 text-red-600" /> <info.icon className="h-6 w-6 text-red-600" />
</div> </div>
<h3 className="text-sm font-bold tracking-wider text-white"> <h3 className="font-almarai text-sm font-bold tracking-wider text-white">
{info.title} {info.title}
</h3> </h3>
<p className="mt-1 text-sm text-gray-400">{info.detail}</p> <p className="font-almarai mt-1 text-sm text-gray-400">{info.detail}</p>
</div> </div>
))} ))}
</div> </div>

View File

@@ -1,44 +1,73 @@
import Link from "next/link"; import Link from "next/link";
import FAQAccordion from "./faqAccardion"; import FAQAccordion from "./faqAccardion";
import { faqItems } from "@/lib/demoData"; import { faqItems } from "@/lib/demoData";
import { useTranslations } from "next-intl";
export function Togle() { export function Togle() {
const t = useTranslations();
const faqItems = [
{
id: "faq-1",
question: t("faq.question1.question"),
answer: t("faq.question1.answer"),
},
{
id: "faq-2",
question: t("faq.question2.question"),
answer: t("faq.question2.answer"),
},
{
id: "faq-3",
question: t("faq.question3.question"),
answer: t("faq.question3.answer"),
},
{
id: "faq-4",
question: t("faq.question4.question"),
answer: t("faq.question4.answer"),
},
{
id: "faq-5",
question: t("faq.question5.question"),
answer: t("faq.question5.answer"),
},
];
return ( return (
<div className="min-h-screen bg-[#1e1d1c]"> <div className="min-h-screen bg-[#1e1d1c]">
<main className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8"> <main className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8">
{/* Header Section */} {/* Header Section */}
<div className="mb-16 flex items-start justify-between gap-8 lg:gap-12"> <div className="mb-16 flex items-start justify-between gap-4 lg:gap-12">
<div className="flex items-center md:col-span-1"> <div className="flex items-center md:col-span-1">
<h1 <h1
className="bg-linear-to-br from-white via-white/50 to-black className="font-unbounded uppercase bg-linear-to-br from-white via-white/50 to-black
text-transparent bg-clip-text text-3xl font-bold uppercase leading-tight sm:text-4xl md:text-5xl lg:text-6xl" text-transparent bg-clip-text text-3xl font-bold leading-tight sm:text-4xl md:text-5xl lg:text-6xl"
> >
About <br /> Work {t("faq.banner.topic")}
</h1> </h1>
</div> </div>
{/* FAQ Section */} {/* FAQ Section */}
<div className="md:col-span-2"> <div className="max-w-250 w-full">
<FAQAccordion items={faqItems} /> <FAQAccordion items={faqItems} />
</div> </div>
</div> </div>
{/* ASK QUESTION */} {/* ASK QUESTION */}
<div className="space-y-8 w-full mt-10 "> <div className="space-y-8 w-full mt-10 ">
<h1 <h1
className="text-center bg-linear-to-br from-white via-white/50 to-black className="font-unbounded uppercase text-center bg-linear-to-br from-white via-white/50 to-black
text-transparent bg-clip-text text-3xl font-bold uppercase leading-tight sm:text-4xl md:text-5xl lg:text-6xl" text-transparent bg-clip-text text-3xl font-bold leading-tight sm:text-4xl md:text-5xl lg:text-6xl"
> >
Still Have Question? {t("faq.ask.question")}
</h1> </h1>
<p className="text-center text-white/80 text-lg"> <p className="font-almarai text-center text-white/80 text-lg">
Nullam dictum felis eu pede mollis pretium integer tincidunt. {t("faq.ask.subtitle")}
</p> </p>
<div className="w-full flex items-center justify-center"> <div className="w-full flex items-center justify-center">
<Link <Link
href={"/contact"} href={"/contact"}
className="mx-auto shadow-[0px_0px_2px_8px_#ff01015c] bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-8 rounded-full transition duration-300 transform hover:scale-105 w-fit" className="font-almarai mx-auto shadow-[0px_0px_2px_8px_#ff01015c] bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-8 rounded-full transition duration-300 transform hover:scale-105 w-fit"
> >
ASK A QUESTION {t("faq.ask.btn")}
</Link> </Link>
</div> </div>
</div> </div>

View File

@@ -19,7 +19,7 @@ export default function FAQAccordion({ items }: FAQAccordionProps) {
{items.map((item, index) => ( {items.map((item, index) => (
<Accordion.Item key={item.id} value={item.id} className="border-b border-slate-700 py-6"> <Accordion.Item key={item.id} value={item.id} className="border-b border-slate-700 py-6">
<Accordion.Trigger className="group flex w-full items-center justify-between text-left"> <Accordion.Trigger className="group flex w-full items-center justify-between text-left">
<h3 className="text-lg font-bold uppercase tracking-wide text-white transition-colors duration-300 group-hover:cursor-pointer md:text-xl"> <h3 className="font-almarai text-lg font-bold uppercase tracking-wide text-white transition-colors duration-300 group-hover:cursor-pointer md:text-xl">
{item.question} {item.question}
</h3> </h3>
<div className="ml-4 shrink-0"> <div className="ml-4 shrink-0">
@@ -31,7 +31,7 @@ export default function FAQAccordion({ items }: FAQAccordionProps) {
</Accordion.Trigger> </Accordion.Trigger>
<Accordion.Content className="overflow-hidden pt-4 text-gray-400 animate-in fade-in slide-in-from-top-2 duration-300 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:slide-out-to-top-2"> <Accordion.Content className="overflow-hidden pt-4 text-gray-400 animate-in fade-in slide-in-from-top-2 duration-300 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:slide-out-to-top-2">
<p className="leading-relaxed text-sm md:text-base">{item.answer}</p> <p className="font-almarai leading-relaxed text-sm md:text-base">{item.answer}</p>
</Accordion.Content> </Accordion.Content>
</Accordion.Item> </Accordion.Item>
))} ))}

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function FaqBanner() { export function FaqBanner() {
const t = useTranslations();
return ( return (
<section className="relative w-full h-[55vh] min-h-100 overflow-hidden pt-10"> <section className="relative w-full h-[55vh] min-h-100 overflow-hidden pt-10">
{/* Background Image */} {/* Background Image */}
@@ -24,12 +26,17 @@ export function FaqBanner() {
<div className="max-w-250 w-full mx-auto px-4"> <div className="max-w-250 w-full mx-auto px-4">
<div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30"> <div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30">
<div className="spacw-y-4 "> <div className="spacw-y-4 ">
<DotAnimatsiya /> <div className="flex items-center gap-3">
<DotAnimatsiya />
<span className="font-almarai text-sm text-white font-semibold tracking-wide">
{t("faq.banner.title")}
</span>
</div>
<p <p
className=" bg-linear-to-br from-white via-white to-black className="font-unbounded uppercase bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty" text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty"
> >
GENERAL <br /> QUESTIONS {t("faq.banner.subtitle")}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
import Image from "next/image"; import Image from "next/image";
import { Flame, Building2, Ambulance } from "lucide-react"; import { Flame, Building2, Ambulance } from "lucide-react";
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
interface ServiceItem { interface ServiceItem {
icon: React.ReactNode; icon: React.ReactNode;
@@ -13,28 +14,25 @@ interface ServiceItem {
description: string; description: string;
} }
const services: ServiceItem[] = [
{
icon: <Flame width={40} height={40} className="text-red-500" />,
title: "FIRE PREVENTION",
description:
"Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum aenean imperdiet augue",
},
{
icon: <Building2 width={40} height={40} className="text-red-500" />,
title: "ACTIVE ACCIDENTS",
description:
"Nullam dictum felis eu pede mollis pretium. Integer tincidunt cras dapibus vivamus consequat vitae.",
},
{
icon: <Ambulance width={40} height={40} className="text-red-500" />,
title: "AMBULANCE SERVICE",
description:
"Donec pede justo fringilla vel aliquet nec vulputate eget arcu enim justo rhoncus ut venenatis.",
},
];
export function AboutUs() { export function AboutUs() {
const t = useTranslations();
const services: ServiceItem[] = [
{
icon: <Flame width={40} height={40} className="text-red-500" />,
title: t("home.about.prevention.title"),
description: t("home.about.prevention.description"),
},
{
icon: <Building2 width={40} height={40} className="text-red-500" />,
title: t("home.about.accidents.title"),
description: t("home.about.accidents.description"),
},
{
icon: <Ambulance width={40} height={40} className="text-red-500" />,
title: t("home.about.service.title"),
description: t("home.about.service.description"),
},
];
return ( return (
<section className="bg-[#1e1d1c] py-16 px-4 sm:py-20 sm:px-6 lg:px-8"> <section className="bg-[#1e1d1c] py-16 px-4 sm:py-20 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
@@ -45,17 +43,15 @@ export function AboutUs() {
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<DotAnimatsiya /> <DotAnimatsiya />
<span className="text-white font-bold text-sm tracking-wide"> <span className="font-almarai text-white font-bold text-sm tracking-wide">
ABOUT US {t("home.about.title")}
</span> </span>
</div> </div>
<h2 <h2
className="text-4xl bg-linear-to-br from-white via-white to-black className="font-unbounded uppercase text-4xl bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text sm:text-5xl lg:text-6xl font-bold leading-tight" text-transparent bg-clip-text sm:text-5xl lg:text-6xl font-bold leading-tight"
> >
FIREFIGHTERS {t("home.about.subtitle")}
<br />
AT THE READY
</h2> </h2>
</div> </div>
@@ -67,10 +63,10 @@ export function AboutUs() {
{service.icon} {service.icon}
</div> </div>
<div> <div>
<h3 className="text-white font-bold text-base sm:text-lg mb-2"> <h3 className="uppercase font-unbounded text-white font-bold text-base sm:text-lg mb-2">
{service.title} {service.title}
</h3> </h3>
<p className="text-gray-400 text-sm sm:text-base leading-relaxed"> <p className="font-almarai text-gray-400 text-sm sm:text-base leading-relaxed">
{service.description} {service.description}
</p> </p>
</div> </div>
@@ -80,8 +76,8 @@ export function AboutUs() {
{/* Button */} {/* Button */}
<div> <div>
<Button className="bg-red-600 hover:bg-red-700 text-white font-bold px-8 py-3 rounded-full transition-colors duration-300 shadow-[0px_0px_2px_8px_#ff01015c]"> <Button className="font-almarai bg-red-600 hover:bg-red-700 text-white font-bold px-8 py-3 rounded-full transition-colors duration-300 shadow-[0px_0px_2px_8px_#ff01015c]">
ABOUT US {t("home.about.title")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -112,10 +108,7 @@ export function AboutUs() {
/> />
<div className="text-center"> <div className="text-center">
<p className="text-white font-bold text-sm sm:text-base"> <p className="text-white font-bold text-sm sm:text-base">
BEST AWARD {t("home.about.award")}
</p>
<p className="text-white font-bold text-sm sm:text-base">
FIREFIGHTER 2025
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,8 @@
import { useTranslations } from "next-intl";
import DotAnimatsiya from "../../dot/DotAnimatsiya"; import DotAnimatsiya from "../../dot/DotAnimatsiya";
export function Banner() { export function Banner() {
const t = useTranslations();
return ( return (
<section className="relative w-full lg:h-[86vh] h-screen min-h-150 overflow-hidden pt-20"> <section className="relative w-full lg:h-[86vh] h-screen min-h-150 overflow-hidden pt-20">
{/* Background Image */} {/* Background Image */}
@@ -17,78 +19,77 @@ export function Banner() {
<div <div
className="absolute inset-0 z-10" className="absolute inset-0 z-10"
style={{ style={{
background: `linear-gradient(to top right, #d2610ab0 0%, #1e1d1ce3 20%, #1e1d1ce3 100%)`, background: `linear-gradient(to top right, #c75c08 0%, #1e1d1ce3 28%, #1e1d1ce3 100%)`,
}} }}
/> />
{/* Content Container */} {/* Content Container */}
<div className="relative z-20 h-full flex items-end lg:mt-0 md:mt-[15vh] sm:mt-[5vh] mt-0"> <div className="relative z-20 h-full flex items-center lg:mt-0 sm:mt-[10vh] mt-[5vh]">
<div className="max-w-375 mx-auto px-4 sm:px-6 lg:px-8 w-full"> <div className="max-w-400 mx-auto px-4 sm:px-6 lg:px-8 w-full">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center h-full"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center h-full">
{/* Right side - Text Content */} {/* Right side - Text Content */}
<div className="lg:hidden inline-block space-y-6 text-white"> <div className="lg:hidden inline-block space-y-6 text-white">
{/* Badge */} {/* Badge */}
<div className="flex items-center gap-2 w-fit"> <div className="flex items-center gap-2 w-fit">
<DotAnimatsiya /> <DotAnimatsiya />
<span className="text-sm font-semibold tracking-wide"> <span className="text-sm font-semibold tracking-wide font-almarai">
WELCOME TO FIREFORCE {t("home.banner.title1")}
</span> </span>
</div> </div>
{/* Main Heading */} {/* Main Heading */}
<h1 <h1
className="bg-linear-to-br from-white via-white to-black className="bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty" text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty font-unbounded"
> >
THE FIRE GUARDIAN {t("home.banner.title2")}
</h1> </h1>
{/* Description */} {/* Description */}
<p className="text-base sm:text-lg text-gray-300 leading-relaxed max-w-md"> <p className="text-base sm:text-lg text-gray-300 leading-relaxed max-w-md">
They are seen as a beacon of hope, a figure who brings calm {t("home.banner.description")}
amidst chaos and light in the darkest of moments.
</p> </p>
{/* CTA Button */} {/* CTA Button */}
<button className="shadow-[0px_0px_2px_8px_#ff01015c] bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-8 rounded-full transition duration-300 transform hover:scale-105 w-fit"> <button className="shadow-[0px_0px_2px_8px_#ff01015c] bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-8 rounded-full transition duration-300 transform hover:scale-105 w-fit">
GET STARTED {t("home.banner.cta")}
</button> </button>
</div> </div>
{/* Left side - Firefighters Image */} {/* Left side - Firefighters Image */}
<div className="flex items-end justify-center"> <div className="flex items-end justify-center ">
<img <img
src="/images/home/bannerHuman.png" src="/images/homeBanner.png"
alt="Firefighters" alt="Firefighters"
loading="lazy" loading="lazy"
className="w-full max-lg:w-[80vw] h-auto object-cover drop-shadow-2xl" className="lg:w-150 w-100 lg:h-150 max-[300px]:w-[80vw] object-cover object-right rounded-xl drop-shadow-2xl"
/> />
</div> </div>
{/* Right side - Text Content */} {/* Right side - Text Content */}
<div className="lg:inline-block hidden space-y-6 text-white mb-20"> <div className="lg:inline-block hidden space-y-6 mb-20">
{/* Badge */} {/* Badge */}
<div className="flex items-center gap-2 w-fit"> <div className="flex items-center gap-2 w-fit">
<DotAnimatsiya /> <DotAnimatsiya />
<span className="text-sm font-semibold tracking-wide"> <span className="text-sm font-semibold text-white tracking-wide font-almarai">
WELCOME TO FIREFORCE {t("home.banner.title1")}
</span> </span>
</div> </div>
{/* Main Heading */} {/* Main Heading */}
<h1 className="gradient-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty"> <h1 className="font-unbounded uppercase text-4xl bg-linear-to-br from-white via-white to-black
THE FIRE GUARDIAN text-transparent bg-clip-text sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty">
{t("home.banner.title2")}
</h1> </h1>
{/* Description */} {/* Description */}
<p className="text-base sm:text-lg text-gray-300 leading-relaxed max-w-md"> <p className="font-almarai text-base sm:text-lg text-gray-300 leading-relaxed max-w-md">
They are seen as a beacon of hope, a figure who brings calm {t("home.banner.description")}
amidst chaos and light in the darkest of moments.
</p> </p>
{/* CTA Button */} {/* CTA Button */}
<button className="shadow-[0px_0px_2px_8px_#ff01015c] bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-8 rounded-full transition duration-300 transform hover:scale-105 w-fit"> <button className="font-almarai shadow-[0px_0px_2px_8px_#ff01015c] bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-8 rounded-full transition duration-300 transform hover:scale-105 w-fit">
GET STARTED {t("home.banner.cta")}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,35 +1,38 @@
import Image from "next/image"; import Image from "next/image";
import { ChevronRight } from "lucide-react"; import { ChevronRight } from "lucide-react";
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
const blogPosts = [ import ProductCard from "../products/productCard";
{
id: 1,
image: "/images/img14.webp",
category: "Tips & Trick",
title: "BEHIND THE HELMET: LIFE AS A FIREFIGHTER",
author: "John Doe",
date: "July 24, 2025",
},
{
id: 2,
image: "/images/img15.webp",
category: "Insight",
title: "FIREFIGHTING EQUIPMENT: TOOLS OF THE TRADE",
author: "John Doe",
date: "July 24, 2025",
},
{
id: 3,
image: "/images/img16.webp",
category: "News",
title: "FIREFIGHTER TRAINING TAKES TO BECOME A HERO",
author: "John Doe",
date: "July 24, 2025",
},
];
export function Blog() { export function Blog() {
const t = useTranslations();
const blogPosts = [
{
id: 1,
image: "/images/img14.webp",
category: "Tips & Trick",
title: t("home.blog.articles.article1"),
author: "John Doe",
date: "July 24, 2025",
},
{
id: 2,
image: "/images/img15.webp",
category: "Insight",
title: t("home.blog.articles.article2"),
author: "John Doe",
date: "July 24, 2025",
},
{
id: 3,
image: "/images/img16.webp",
category: "News",
title: t("home.blog.articles.article3"),
author: "John Doe",
date: "July 24, 2025",
},
];
return ( return (
<section className="bg-[#1f1f1f] py-45"> <section className="bg-[#1f1f1f] py-45">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
@@ -37,23 +40,23 @@ export function Blog() {
<div className="mb-12 text-center"> <div className="mb-12 text-center">
<div className="mb-4 flex items-center justify-center gap-2"> <div className="mb-4 flex items-center justify-center gap-2">
<DotAnimatsiya /> <DotAnimatsiya />
<span className="text-sm font-semibold tracking-wider text-white uppercase"> <span className="font-almarai text-sm font-semibold tracking-wider text-white uppercase">
Blog & Articles {t("products.banner.title")}
</span> </span>
</div> </div>
<h2 <h2
className="bg-linear-to-br from-white via-white to-black className="font-unbounded bg-linear-to-br from-white py-2 via-white to-black
text-transparent bg-clip-text text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl" text-transparent bg-clip-text text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl"
> >
LATEST BLOG & NEWS {t("products.ourproducts")}
</h2> </h2>
</div> </div>
{/* Blog Cards Grid */} {/* Blog Cards Grid */}
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3 max-sm:place-items-center"> <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3 max-sm:place-items-center">
{blogPosts.map((post) => ( {/* {blogPosts.map((post) => (
<article key={post.id} className="group"> <article key={post.id} className="group">
{/* Image Container */}
<div className="relative mb-6 aspect-4/2 md:aspect-4/3 overflow-hidden rounded-lg"> <div className="relative mb-6 aspect-4/2 md:aspect-4/3 overflow-hidden rounded-lg">
<Image <Image
src={post.image || "/placeholder.svg"} src={post.image || "/placeholder.svg"}
@@ -61,20 +64,19 @@ export function Blog() {
fill fill
className="object-cover transition-transform duration-300 group-hover:scale-105" className="object-cover transition-transform duration-300 group-hover:scale-105"
/> />
{/* Category Badge */}
<div className="absolute bottom-4 left-4"> <div className="absolute bottom-4 left-4">
<span className="rounded bg-red-600 px-4 py-2 text-sm font-medium text-white"> <span className="font-almarai rounded bg-red-600 px-4 py-2 text-sm font-medium text-white">
{post.category} {post.category}
</span> </span>
</div> </div>
</div> </div>
{/* Content */}
<div> <div>
<h3 className="mb-3 text-lg font-bold leading-tight tracking-wide text-white uppercase md:text-xl"> <h3 className="font-unbounded uppercase mb-3 text-lg font-bold leading-tight tracking-wide text-white md:text-xl">
{post.title} {post.title}
</h3> </h3>
<p className="mb-4 text-sm text-gray-400"> <p className="font-almarai mb-4 text-sm text-gray-400">
<span className="text-gray-500">by </span> <span className="text-gray-500">by </span>
<span className="text-white">{post.author}</span> <span className="text-white">{post.author}</span>
<span className="mx-2 text-gray-500">•</span> <span className="mx-2 text-gray-500">•</span>
@@ -82,14 +84,26 @@ export function Blog() {
</p> </p>
<a <a
href="#" href="#"
className="inline-flex items-center gap-1 text-sm font-semibold tracking-wider text-red-600 uppercase transition-colors hover:text-red-500" className="font-almarai inline-flex items-center gap-1 text-sm font-semibold tracking-wider text-red-600 uppercase transition-colors hover:text-red-500"
> >
Read More {t("home.blog.readMore")}
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />
</a> </a>
</div> </div>
</article> </article>
))} ))} */}
{Array(3)
.fill(null)
.map((_, index) => (
<ProductCard
key={index}
title="Elektr yong'in detektori-Ypres ver.2"
name="P-0834404"
image="/images/products/products.webp"
slug="P_0834404"
status="full"
/>
))}
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,13 +1,15 @@
import { Phone } from "lucide-react"; import { Phone } from "lucide-react";
import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
export function Line() { export function Line() {
const t = useTranslations();
return ( return (
<div className="bg-black py-10 px-4"> <div className="bg-black py-10 px-4">
<div className="max-w-280 w-full mx-auto relative py-10 flex items-center justify-between "> <div className="max-w-280 w-full mx-auto relative py-10 flex items-center justify-between ">
<div className="text-white flex flex-col items-start justify-start gap-5 "> <div className="text-white flex flex-col items-start justify-start gap-5 ">
<h2 className="lg:text-5xl sm:text-3xl text-2xl max-w-[80%] w-full font-semibold"> <h2 className="font-almarai lg:text-5xl sm:text-3xl text-2xl max-w-[80%] w-full font-semibold">
Ready for Action 24/7: Contact Us at {t("home.contactLine.text")}
</h2> </h2>
<p className="flex items-center justify-center gap-4 font-semibold sm:text-xl text-lg"> <p className="flex items-center justify-center gap-4 font-semibold sm:text-xl text-lg">
<span <span

View File

@@ -1,63 +1,64 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { ChevronRight } from "lucide-react"; import { ChevronRight } from "lucide-react";
import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
export function OurService() { export function OurService() {
const t = useTranslations();
return ( return (
<div className="bg-[#1e1d1c] py-10 md:py-16 lg:py-20"> <div className="bg-[#1e1d1c] py-10 md:py-16 lg:py-20 mb-30">
<div className="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */} {/* Header */}
<div className="space-y-4 md:space-y-6"> <div className="space-y-4 md:space-y-6">
<div className="flex items-center justify-center gap-2 text-base sm:text-lg md:text-xl text-white font-bold"> <div className="font-almarai flex items-center justify-center gap-2 text-base sm:text-lg md:text-xl text-white font-bold">
<DotAnimatsiya /> <DotAnimatsiya />
OUR SERVICES {t("home.services.title")}
</div> </div>
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl tracking-wider lg:tracking-[5px] font-bold bg-linear-to-br from-white via-white to-gray-400 text-transparent bg-clip-text text-center w-full"> <h1 className="uppercase font-unbounded text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl tracking-wider lg:tracking-[5px] font-bold bg-linear-to-br from-white via-white to-gray-400 text-transparent bg-clip-text text-center w-full">
FIREFIGHTER RESPONSIBILITIES {t("home.services.subtitle")}
</h1> </h1>
<p className="text-center text-sm sm:text-base md:text-lg text-gray-400 max-w-3xl mx-auto px-4"> <p className="font-almarai text-center text-sm sm:text-base md:text-lg text-gray-400 max-w-4xl mx-auto px-4">
Aliquam lorem ante dapibus in viverra quis feugiat a tellus {t("home.services.description")}
phasellus viverra nulla ut metus varius laoreet quisque rutrum.
</p> </p>
</div> </div>
{/* cards */} {/* cards */}
<div className="max-w-250 w-full mx-auto flex sm:flex-row flex-col items-center gap-5 my-10"> <div className="max-w-250 w-full mx-auto flex sm:flex-row flex-col items-center gap-5 my-10">
<div className="relative space-y-4 py-6 px-8 rounded-xl sm:w-[55%] w-full bg-[linear-gradient(to_bottom_right,#000000,#000000,#000000,#d2610a)]"> <div className="relative space-y-4 py-6 px-8 rounded-xl sm:w-[55%] w-full bg-[linear-gradient(to_bottom_right,#000000,#000000,#000000,#d2610a)]">
<p className="font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text"> <p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
Operation Force {t("home.services.services.operation.title")}
</p> </p>
<p className="text-gray-400 max-w-80 w-full"> <p className="font-almarai text-gray-400 max-w-80 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus. {t("home.services.services.operation.description")}
</p> </p>
<button className="text-[#dc2626] font-semibold flex items-center gap-2 text-sm"> <button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} /> {t("home.services.learnmore")} <ChevronRight size={20} />
</button> </button>
<Image <Image
src="/images/home/gruop.png" src="/images/home/gruop.png"
alt="images" alt="images"
width={200} width={200}
height={100} height={100}
className="object-contain sm:absolute bottom-0 right-2 z-50" className="object-contain sm:absolute bottom-0 right-2 z-10"
/> />
</div> </div>
<div className="relative overflow-hidden space-y-4 py-6 px-8 rounded-xl sm:w-[45%] w-full bg-[linear-gradient(to_bottom_right,#000000,#000000,#000000,#d2610a)]"> <div className="relative overflow-hidden space-y-4 py-6 px-8 rounded-xl sm:w-[45%] w-full bg-[linear-gradient(to_bottom_right,#000000,#000000,#000000,#d2610a)]">
<p className="font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text"> <p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
Operation Force {t("home.services.services.suppression.title")}
</p> </p>
<p className="text-gray-400 max-w-70 w-full"> <p className="font-almarai text-gray-400 max-w-70 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus. {t("home.services.services.suppression.description")}
</p> </p>
<button className="text-[#dc2626] font-semibold flex items-center gap-2 text-sm"> <button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} /> {t("home.services.learnmore")} <ChevronRight size={20} />
</button> </button>
<Image <Image
src="/images/home/redShlang.png" src="/images/home/redShlang.png"
alt="images" alt="images"
width={200} width={200}
height={100} height={100}
className="object-contain sm:absolute -bottom-4 -right-4 z-50" className="object-contain sm:absolute -bottom-4 -right-4 z-10"
/> />
</div> </div>
</div> </div>
@@ -72,45 +73,45 @@ export function OurService() {
className="object-contain mt-5" className="object-contain mt-5"
/> />
<div className="space-y-4 py-6 px-8"> <div className="space-y-4 py-6 px-8">
<p className="font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text"> <p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
Operation Force {t("home.services.services.safety.title")}
</p> </p>
<p className="text-gray-400 max-w-80 w-full"> <p className="font-almarai text-gray-400 max-w-80 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus. {t("home.services.services.safety.description")}
</p> </p>
<button className="text-[#dc2626] font-semibold flex items-center gap-2 text-sm"> <button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} /> {t("home.services.learnmore")} <ChevronRight size={20} />
</button> </button>
</div> </div>
</div> </div>
<div className="sm:w-[60%] w-full"> <div className="sm:w-[60%] w-full">
<div className="relative overflow-hidden space-y-4 py-6 px-8 rounded-xl w-full bg-[linear-gradient(to_bottom_right,#000000,#000000,#000000,#d2610a)]"> <div className="relative overflow-hidden space-y-4 py-6 px-8 rounded-xl w-full bg-[linear-gradient(to_bottom_right,#000000,#000000,#000000,#d2610a)]">
<p className="font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text"> <p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
Operation Force {t("home.services.services.monitoring.title")}
</p> </p>
<p className="text-gray-400 max-w-70 w-full"> <p className="font-almarai text-gray-400 max-w-70 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus. {t("home.services.services.monitoring.description")}
</p> </p>
<button className="sm:mt-38 mt-0 text-[#dc2626] font-semibold flex items-center gap-2 text-sm"> <button className="font-almarai sm:mt-38 mt-0 text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} /> {t("home.services.learnmore")} <ChevronRight size={20} />
</button> </button>
<Image <Image
src="/images/home/balon.png" src="/images/home/balon.png"
alt="images" alt="images"
width={200} width={200}
height={100} height={100}
className="object-contain sm:absolute -bottom-20 -right-4 max-sm:-mb-20 z-50" className="object-contain sm:absolute -bottom-20 -right-4 max-sm:-mb-20 z-10"
/> />
</div> </div>
<div className="py-8 px-8 rounded-xl mt-5 w-full p-5 bg-[linear-gradient(to_top_right,#000000,#000000,#d2610a)] flex sm:flex-row flex-col gap-5 items-center justify-between"> <div className="py-8 px-8 rounded-xl mt-5 w-full p-5 bg-[linear-gradient(to_top_right,#000000,#000000,#d2610a)] flex sm:flex-row flex-col gap-5 items-center justify-between">
<h2 className="sm:text-3xl text-xl font-semibold font-armanai text-white"> <h2 className="font-unbounded sm:text-3xl text-xl font-semibold font-armanai text-white">
View more service {t("home.services.viewMoreServices")}
</h2> </h2>
<Link <Link
href="/services" href="/services"
className="shadow-[0px_0px_2px_6px_#a60404ad] bg-red-600 hover:bg-red-700 text-white font-bold sm:py-3 sm:px-8 px-8 py-2 rounded-full transition duration-300 transform hover:scale-105 w-fit" className="font-almarai shadow-[0px_0px_2px_6px_#a60404ad] bg-red-600 hover:bg-red-700 text-white font-bold sm:py-3 sm:px-8 px-8 py-2 rounded-full transition duration-300 transform hover:scale-105 w-fit"
> >
GET STARTED {t("home.services.viewMore")}
</Link> </Link>
</div> </div>
</div> </div>

View File

@@ -1,24 +1,28 @@
import { Counter } from "@/components/Counter";
import { useTranslations } from "next-intl";
export function Statistics() { export function Statistics() {
const t = useTranslations();
const stats = [ const stats = [
{ {
number: '25', number: "25",
symbol: '+', symbol: "+",
label: 'Years Experience', label: t("home.statistics.experience"),
}, },
{ {
number: '450', number: "450",
symbol: '+', symbol: "+",
label: 'Families Saved', label: t("home.statistics.projectsCompleted"),
}, },
{ {
number: '99', number: "99",
symbol: '+', symbol: "+",
label: 'Trained Staff', label: t("home.statistics.trainedSpecialists"),
}, },
{ {
number: '93', number: "93",
symbol: '%', symbol: "%",
label: 'Trusted Clients', label: t("home.statistics.trustedClients"),
}, },
]; ];
@@ -27,17 +31,22 @@ export function Statistics() {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 lg:gap-12"> <div className="grid grid-cols-2 md:grid-cols-4 gap-8 lg:gap-12">
{stats.map((stat, index) => ( {stats.map((stat, index) => (
<div key={index} className="flex flex-col items-center justify-center py-10 sm:py-20 lg:py-15 border-b-red-600 border-b"> <div
key={index}
className="flex flex-col items-center justify-center py-10 sm:py-20 lg:py-15 border-b-red-600 border-b"
>
{/* Number and Symbol */} {/* Number and Symbol */}
<div className="flex items-baseline gap-2"> <div className="flex items-baseline gap-2 font-almarai">
<span className="text-4xl sm:text-5xl lg:text-6xl font-bold text-white"> <span className="text-4xl sm:text-5xl lg:text-6xl font-bold text-white">
{stat.number} <Counter countNum={Number(stat.number)} />
</span>
<span className="text-4xl sm:text-5xl lg:text-6xl font-bold text-red-600">
{stat.symbol}
</span> </span>
<span className="text-4xl sm:text-5xl lg:text-6xl font-bold text-red-600">{stat.symbol}</span>
</div> </div>
{/* Label */} {/* Label */}
<p className="text-sm sm:text-base text-gray-300 mt-4 text-center font-medium"> <p className="font-almarai text-sm sm:text-base text-gray-300 mt-4 text-center font-medium">
{stat.label} {stat.label}
</p> </p>
</div> </div>

View File

@@ -5,36 +5,7 @@ import { Autoplay } from "swiper/modules";
import Image from "next/image"; import Image from "next/image";
import "swiper/css"; import "swiper/css";
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
const testimonials = [
{
id: 1,
quote:
"I've witnessed Fireforce in action multiple times, and they never cease to amaze me with their dedication and professionalism. They are the backbone of our safety and security. Thank you, Fireforce!",
name: "JOHN SMITH",
role: "Manager",
avatar: "/images/home/avatar.jpg",
rating: 5,
},
{
id: 2,
quote:
"The team's response time and expertise saved our property from a devastating fire. Their bravery and commitment to protecting our community is truly remarkable. Highly recommend their services!",
name: "SARAH JOHNSON",
role: "Business Owner",
avatar: "/images/home/avatar.jpg",
rating: 5,
},
{
id: 3,
quote:
"Working alongside Fireforce has been an honor. Their training programs and emergency protocols are second to none. They set the standard for fire safety excellence in our region.",
name: "MICHAEL DAVIS",
role: "Safety Director",
avatar: "/images/home/avatar.jpg",
rating: 5,
},
];
function StarRating({ rating }: { rating: number }) { function StarRating({ rating }: { rating: number }) {
return ( return (
@@ -53,6 +24,33 @@ function StarRating({ rating }: { rating: number }) {
} }
export function Testimonial() { export function Testimonial() {
const t = useTranslations();
const testimonials = [
{
id: 1,
quote:t("home.testimonials.clients.john.text"),
name: t("home.testimonials.clients.john.name"),
role: t("home.testimonials.clients.john.position"),
avatar: "/images/home/avatar.jpg",
rating: 5,
},
{
id: 2,
quote:t("home.testimonials.clients.sarah.text"),
name: t("home.testimonials.clients.sarah.name"),
role: t("home.testimonials.clients.sarah.position"),
avatar: "/images/home/avatar.jpg",
rating: 5,
},
{
id: 3,
quote:t("home.testimonials.clients.michael.text"),
name: t("home.testimonials.clients.michael.name"),
role: t("home.testimonials.clients.michael.position"),
avatar: "/images/home/avatar.jpg",
rating: 5,
},
];
return ( return (
<section className="w-full bg-[#1a1a1a]"> <section className="w-full bg-[#1a1a1a]">
<div className="flex flex-col lg:flex-row min-h-100 lg:min-h-125"> <div className="flex flex-col lg:flex-row min-h-100 lg:min-h-125">
@@ -87,8 +85,8 @@ export function Testimonial() {
<div className="w-full max-w-xl mx-auto mb-5"> <div className="w-full max-w-xl mx-auto mb-5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<DotAnimatsiya /> <DotAnimatsiya />
<span className="text-white font-semibold text-sm tracking-wider"> <span className="font-unbounded text-white font-semibold text-sm tracking-wider">
TESTIMONIALS {t("home.testimonials.title")}
</span> </span>
</div> </div>
</div> </div>
@@ -108,7 +106,7 @@ export function Testimonial() {
<SwiperSlide key={testimonial.id}> <SwiperSlide key={testimonial.id}>
<div className="space-y-6"> <div className="space-y-6">
{/* Quote */} {/* Quote */}
<p className="text-white text-base sm:text-lg lg:text-xl leading-relaxed"> <p className="font-almarai text-white text-base sm:text-lg lg:text-xl leading-relaxed">
"{testimonial.quote}" "{testimonial.quote}"
</p> </p>
@@ -123,10 +121,10 @@ export function Testimonial() {
/> />
</div> </div>
<div> <div>
<h4 className="text-white font-bold text-sm sm:text-base"> <h4 className="font-unbounded text-white font-bold text-sm sm:text-base">
{testimonial.name} {testimonial.name}
</h4> </h4>
<p className="text-red-600 text-xs sm:text-sm"> <p className="font-almarai text-red-600 text-xs sm:text-sm">
{testimonial.role} {testimonial.role}
</p> </p>
</div> </div>

View File

@@ -1,15 +1,27 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import HomeMarquee from "@/components/HomeMarquee"; import HomeMarquee from "@/components/HomeMarquee";
import { Play } from "lucide-react"; import { Play } from "lucide-react";
import { useTranslations } from "next-intl";
export function Video() { export function Video() {
const t = useTranslations();
return ( return (
<> <>
<div <div
className="bg-[#1e1d1c] bg-fixed max-sm:bg-center" className="bg-[#1e1d1c] bg-fixed max-sm:bg-center"
style={{ backgroundImage: "url(/images/img7.jpg)" }} style={{ backgroundImage: "url(/images/img7.jpg)" }}
> >
<div className="flex items-center justify-center bg-linear-to-t from-[#1e1d1c] to-[#1e1d1c02] h-80 w-full relative"> <div className="flex flex-col items-center justify-center bg-linear-to-t from-[#1e1d1c] to-[#1e1d1c02] h-80 w-full relative">
<div className="flex items-center gap-2 w-fit text-white ">
<DotAnimatsiya />
<span className="text-sm font-semibold tracking-wide font-almarai">
{t("rasmlar")}
</span>
</div>
<h1 className="font-unbounded uppercase text-4xl bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty">
{t("fotogalereya")}
</h1>
</div> </div>
</div> </div>
<div> <div>

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function ProductBanner() { export function ProductBanner() {
const t = useTranslations();
return ( return (
<section className="relative w-full h-[60vh] min-h-100 overflow-hidden pt-10"> <section className="relative w-full h-[60vh] min-h-100 overflow-hidden pt-10">
{/* Background Image */} {/* Background Image */}
@@ -24,17 +26,21 @@ export function ProductBanner() {
<div className="max-w-250 w-full mx-auto px-4"> <div className="max-w-250 w-full mx-auto px-4">
<div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30"> <div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30">
<div className="spacw-y-4 "> <div className="spacw-y-4 ">
<DotAnimatsiya /> <div className="flex items-center gap-2 w-fit">
<DotAnimatsiya />
<span className="font-almarai text-white text-sm font-semibold tracking-wide">
{t("products.banner.title")}
</span>
</div>
<p <p
className=" bg-linear-to-br from-white via-white to-black className="font-unbounded uppercase bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty" text-transparent bg-clip-text text-3xl sm:text-4xl lg:text-5xl font-bold leading-tight text-pretty"
> >
Ignum technology <br /> At The Ready {t("products.banner.subtitle")}
</p> </p>
</div> </div>
<div className="lg:w-[40%] text-gray-300 lg:mt-20 md:mt-10 sm:mt-5 "> <div className="font-almarai lg:w-[40%] text-gray-300 lg:mt-20 md:mt-10 sm:mt-5 ">
It emphasizes that these firefighters are there not just as public {t("products.banner.description")}
servants but as a vital part of the community.
</div> </div>
</div> </div>
</div> </div>

View File

@@ -36,7 +36,7 @@ export default function ProductCard({
<Link href={`/products/${slug}`}> <Link href={`/products/${slug}`}>
<article className="group transition-all duration-300 hover:cursor-pointer max-sm:max-w-100 max-sm:mx-auto max-sm:w-full "> <article className="group transition-all duration-300 hover:cursor-pointer max-sm:max-w-100 max-sm:mx-auto max-sm:w-full ">
{/* Image Container */} {/* Image Container */}
<div className="relative rounded-2xl h-45 sm:h-55 md:h-65 lg:w-70 w-[90%] mx-auto overflow-hidden bg-white"> <div className="relative rounded-2xl h-45 sm:h-55 md:h-65 lg:w-[95%] w-[90%] mx-auto overflow-hidden bg-white">
<Image <Image
src={image || "/placeholder.svg"} src={image || "/placeholder.svg"}
alt={title} alt={title}

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function ServiceBanner() { export function ServiceBanner() {
const t = useTranslations();
return ( return (
<section className="relative w-full h-[60vh] min-h-100 overflow-hidden pt-10"> <section className="relative w-full h-[60vh] min-h-100 overflow-hidden pt-10">
{/* Background Image */} {/* Background Image */}
@@ -24,17 +26,21 @@ export function ServiceBanner() {
<div className="max-w-250 w-full mx-auto px-4"> <div className="max-w-250 w-full mx-auto px-4">
<div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30"> <div className="relative z-20 h-full flex max-lg:flex-col items-start justify-between gap-5 pt-30">
<div className="spacw-y-4 "> <div className="spacw-y-4 ">
<DotAnimatsiya /> <div className="flex items-center gap-3">
<DotAnimatsiya />
<span className="font-almarai text-sm text-white font-semibold tracking-wide">
{t("services.banner.title")}
</span>
</div>
<p <p
className=" bg-linear-to-br from-white via-white to-black className="font-unbounded uppercase bg-linear-to-br from-white via-white to-black
text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty" text-transparent bg-clip-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty"
> >
GENERAL <br /> QUESTIONS {t("services.banner.subtitle")}
</p> </p>
</div> </div>
<div className="lg:w-[40%] text-gray-300 mt-10 text-lg"> <div className="font-almarai lg:w-[40%] text-gray-300 mt-10 text-lg">
Always Ready, Always On Time: Rescuing Lives represents an {t("services.banner.description")}
unwavering commitment to emergency.
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,8 +1,36 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya"; import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import FAQAccordion from "../faq/faqAccardion"; import FAQAccordion from "../faq/faqAccardion";
import { faqItems } from "@/lib/demoData"; import { useTranslations } from "next-intl";
export function ServiceFaq() { export function ServiceFaq() {
const t = useTranslations();
const faqItems = [
{
id: "faq-1",
question: t("faq.question1.question"),
answer: t("faq.question1.answer"),
},
{
id: "faq-2",
question: t("faq.question2.question"),
answer: t("faq.question2.answer"),
},
{
id: "faq-3",
question: t("faq.question3.question"),
answer: t("faq.question3.answer"),
},
{
id: "faq-4",
question: t("faq.question4.question"),
answer: t("faq.question4.answer"),
},
{
id: "faq-5",
question: t("faq.question5.question"),
answer: t("faq.question5.answer"),
},
];
return ( return (
<div className="bg-[#1e1d1c] py-20 pb-50 space-y-8"> <div className="bg-[#1e1d1c] py-20 pb-50 space-y-8">
{/* header */} {/* header */}

16
i18n/config.ts Normal file
View File

@@ -0,0 +1,16 @@
export const locales = ["uz", "ru", "en"] as const
export type Locale = (typeof locales)[number]
export const defaultLocale: Locale = "uz"
export const localeNames: Record<Locale, string> = {
uz: "O'zbekcha",
ru: "Русский",
en: "English",
}
export const localeFlags: Record<Locale, string> = {
uz: "🇺🇿",
ru: "🇷🇺",
en: "🇬🇧",
}

25
i18n/request.ts Normal file
View File

@@ -0,0 +1,25 @@
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { headers } from 'next/headers';
const LOCALES = ["uz", "ru", "en"] as const;
const DEFAULT_LOCALE = "uz";
type Locale = typeof LOCALES[number];
export default getRequestConfig(async () => {
const headersList = await headers();
// ✅ Middleware allaqachon locale'ni set qilgan
const locale = headersList.get('x-locale') || DEFAULT_LOCALE;
// Validate
const validLocale = LOCALES.includes(locale as Locale)
? locale
: DEFAULT_LOCALE;
return {
locale: validLocale,
messages: (await import(`../messages/${validLocale}.json`)).default,
};
});

View File

@@ -1,3 +1,4 @@
export const DATA = [ export const DATA = [
{ {
name: "P-0834405", name: "P-0834405",

23
lib/i18n-utils.ts Normal file
View File

@@ -0,0 +1,23 @@
"use client"
import { Locale } from "@/i18n/config"
import { useRouter } from "next/navigation"
export function useChangeLocale() {
const router = useRouter()
const changeLocale = (locale: Locale) => {
document.cookie = `NEXT_LOCALE=${locale}; path=/; max-age=31536000`
router.refresh()
}
return changeLocale
}
export function getLocaleFromCookie(): Locale {
if (typeof document === "undefined") return "uz"
const cookie = document.cookie.split("; ").find((row) => row.startsWith("NEXT_LOCALE="))
return (cookie?.split("=")[1] as Locale) || "uz"
}

View File

@@ -2,7 +2,7 @@
"home": { "home": {
"banner": { "banner": {
"title1": "Welcome to Ignum", "title1": "Welcome to Ignum",
"title2": "Fire Protection Guardian", "title2": "FIRE PROTECTION GUARDIAN",
"description": "We are seen as a beacon of hope, a figure that brings calm amidst chaos and light in the darkest of moments.", "description": "We are seen as a beacon of hope, a figure that brings calm amidst chaos and light in the darkest of moments.",
"cta": "Get Started" "cta": "Get Started"
}, },
@@ -150,7 +150,8 @@
"title": "Products", "title": "Products",
"subtitle": "Ignum Technology Ready", "subtitle": "Ignum Technology Ready",
"description": "We not only supply equipment but become a successful partner for every client." "description": "We not only supply equipment but become a successful partner for every client."
} },
"ourproducts": "Our Products"
}, },
"faq": { "faq": {
"banner": { "banner": {
@@ -158,28 +159,31 @@
"subtitle": "General Questions", "subtitle": "General Questions",
"topic": "ABOUT WORK" "topic": "ABOUT WORK"
}, },
"questions": [ "question1": {
{ "question": "How to choose a fire alarm system?",
"question": "How to choose a fire alarm system?", "answer": "Depending on the area, type and features of the facility, our specialists will offer you the most optimal solution. You can get a free consultation."
"answer": "Depending on the area, type and features of the facility, our specialists will offer you the most optimal solution. You can get a free consultation." },
}, "question2": {
{ "question": "How long does equipment installation take?",
"question": "How long does equipment installation take?", "answer": "Installation time depends on the project scope. Usually, 1-3 days for small facilities, 1-2 weeks for large facilities."
"answer": "Installation time depends on the project scope. Usually, 1-3 days for small facilities, 1-2 weeks for large facilities." },
}, "question3": {
{ "question": "How is maintenance carried out?",
"question": "How is maintenance carried out?", "answer": "We offer regular maintenance and 24/7 emergency service. Preventive inspections are conducted every six months."
"answer": "We offer regular maintenance and 24/7 emergency service. Preventive inspections are conducted every six months." },
}, "question4": {
{ "question": "What is the warranty period?",
"question": "What is the warranty period?", "answer": "All our equipment comes with a 2-year warranty and 10-year maintenance guarantee."
"answer": "All our equipment comes with a 2-year warranty and 10-year maintenance guarantee." },
}, "question5": {
{ "question": "For which facilities are they suitable?",
"question": "For which facilities are they suitable?", "answer": "We have suitable solutions for office buildings, warehouses, industrial plants, shopping malls, residential buildings and all types of public places."
"answer": "We have suitable solutions for office buildings, warehouses, industrial plants, shopping malls, residential buildings and all types of public places." },
} "ask": {
] "question": "Do you still have questions?",
"subtitle": "Contact us and our specialists will answer you",
"btn": "Ask Question"
}
}, },
"services": { "services": {
"banner": { "banner": {
@@ -213,5 +217,11 @@
"contact": "Contact", "contact": "Contact",
"help": "Help" "help": "Help"
} }
} },
} "rasmlar": "Images",
"fotogalereya": "Photo Gallery",
"contactTitle": "Send us your phone number",
"contactSubTitle": "Our staff will contact you",
"enterPhone": "Enter your phone number",
"send": "Send"
}

View File

@@ -150,7 +150,8 @@
"title": "Продукты", "title": "Продукты",
"subtitle": "Технология Ignum Готова", "subtitle": "Технология Ignum Готова",
"description": "Мы не просто поставляем оборудование, мы становимся успешным партнером для каждого клиента." "description": "Мы не просто поставляем оборудование, мы становимся успешным партнером для каждого клиента."
} },
"ourproducts": "Наши продукты"
}, },
"faq": { "faq": {
"banner": { "banner": {
@@ -158,28 +159,31 @@
"subtitle": "Общие Вопросы", "subtitle": "Общие Вопросы",
"topic": "О РАБОТЕ" "topic": "О РАБОТЕ"
}, },
"questions": [ "question1": {
{ "question": "Как выбрать систему пожарной сигнализации?",
"question": "Как выбрать систему пожарной сигнализации?", "answer": "В зависимости от площади, типа и особенностей объекта наши специалисты предложат вам наиболее оптимальное решение. Вы можете получить бесплатную консультацию."
"answer": "В зависимости от площади, типа и особенностей объекта наши специалисты предложат вам наиболее оптимальное решение. Вы можете получить бесплатную консультацию." },
}, "question2": {
{ "question": "Сколько времени занимает установка оборудования?",
"question": "Сколько времени занимает установка оборудования?", "answer": "Время установки зависит от объема проекта. Обычно для небольших объектов требуется 1-3 дня, для крупных объектов - 1-2 недели."
"answer": "Время установки зависит от объема проекта. Обычно для небольших объектов требуется 1-3 дня, для крупных объектов - 1-2 недели." },
}, "question3": {
{ "question": "Как проводится техническое обслуживание?",
"question": "Как проводится техническое обслуживание?", "answer": "Мы предлагаем регулярное техническое обслуживание и экстренную службу 24/7. Профилактические проверки проводятся каждые шесть месяцев."
"answer": "Мы предлагаем регулярное техническое обслуживание и экстренную службу 24/7. Профилактические проверки проводятся каждые шесть месяцев." },
}, "question4": {
{ "question": "Какой срок гарантии?",
"question": "Какой срок гарантии?", "answer": "На все наше оборудование предоставляется 2-летняя гарантия и 10-летняя гарантия на техническое обслуживание."
"answer": "На все наше оборудование предоставляется 2-летняя гарантия и 10-летняя гарантия на техническое обслуживание." },
}, "question5": {
{ "question": "Для каких объектов подходят?",
"question": "Для каких объектов подходят?", "answer": "У нас есть подходящие решения для офисных зданий, складов, промышленных предприятий, торговых центров, жилых зданий и всех видов общественных мест."
"answer": "У нас есть подходящие решения для офисных зданий, складов, промышленных предприятий, торговых центров, жилых зданий и всех видов общественных мест." },
} "ask": {
] "question": "Остались вопросы?",
"subtitle": "Свяжитесь с нами, и наши специалисты ответят вам",
"btn": "Задать вопрос"
}
}, },
"services": { "services": {
"banner": { "banner": {
@@ -213,5 +217,11 @@
"contact": "Контакты", "contact": "Контакты",
"help": "Помощь" "help": "Помощь"
} }
} },
} "rasmlar": "Изображения",
"fotogalereya": "Фотогалерея",
"contactTitle": "Отправьте нам свой номер",
"contactSubTitle": "Наши сотрудники свяжутся с вами",
"enterPhone": "Введите ваш номер телефона",
"send": "Отправить"
}

View File

@@ -2,7 +2,7 @@
"home": { "home": {
"banner": { "banner": {
"title1": "Ignum-ga xush kelibsiz", "title1": "Ignum-ga xush kelibsiz",
"title2": "Yong'inga Qarshi Himoya", "title2": "YONG'INGA QARSHI HIMOYA",
"description": "Biz umid nuri, tartibsizlik davrida tinchlik va eng qiyin vaziyatlarda ishonchli himoya manbai sifatida ko'rilamiz.", "description": "Biz umid nuri, tartibsizlik davrida tinchlik va eng qiyin vaziyatlarda ishonchli himoya manbai sifatida ko'rilamiz.",
"cta": "Boshlash" "cta": "Boshlash"
}, },
@@ -14,17 +14,17 @@
}, },
"about": { "about": {
"title": "Biz haqimizda", "title": "Biz haqimizda",
"subtitle": "Yong'inga Qarshi Tizimlar Tayyor", "subtitle": "YONG'INGA QARSHI TIZIMLAR TAYYOR",
"prevention": { "prevention": {
"title": "Yong'inni Oldini Olish Tizimlari", "title": "YONG'INDI OLDINI OLISH TIZIMLARI",
"description": "Biz eng ilg'or yong'in oldini olish texnologiyalarini taklif etamiz. Har bir detal xavfsizlik va ishonchlilikni ta'minlash uchun ishlab chiqilgan." "description": "Biz eng ilg'or yong'in oldini olish texnologiyalarini taklif etamiz. Har bir detal xavfsizlik va ishonchlilikni ta'minlash uchun ishlab chiqilgan."
}, },
"accidents": { "accidents": {
"title": "Favqulodda Holatlar", "title": "FAVQULODDA HOLATLAR",
"description": "Har qanday vaziyatga tezkor va samarali javob berish uchun mo'ljallangan avtomatik o'chirish tizimlari. Xavfsizlik - bizning birinchi ustuvorligimiz." "description": "Har qanday vaziyatga tezkor va samarali javob berish uchun mo'ljallangan avtomatik o'chirish tizimlari. Xavfsizlik - bizning birinchi ustuvorligimiz."
}, },
"service": { "service": {
"title": "Texnik Xizmat", "title": "TEXNIK XIZMAT",
"description": "Muntazam texnik xizmat ko'rsatish va ta'mirlash xizmatlari. Tizimlaringiz har doim ish holatida bo'lishini kafolatlaymiz." "description": "Muntazam texnik xizmat ko'rsatish va ta'mirlash xizmatlari. Tizimlaringiz har doim ish holatida bo'lishini kafolatlaymiz."
}, },
"award": "Eng Yaxshi Yong'in Himoyasi 2025" "award": "Eng Yaxshi Yong'in Himoyasi 2025"
@@ -53,7 +53,7 @@
} }
}, },
"viewMoreServices": "Ko'proq Xizmatlar", "viewMoreServices": "Ko'proq Xizmatlar",
"viewMore": "Ko'proq" "viewMore": "Ko'proq ko'rish"
}, },
"testimonials": { "testimonials": {
"title": "Mijozlar Fikrlari", "title": "Mijozlar Fikrlari",
@@ -80,7 +80,7 @@
}, },
"blog": { "blog": {
"title": "Blog & Maqolalar", "title": "Blog & Maqolalar",
"subtitle": "SO'NGI BLOG & YANGILIKLAR", "subtitle": "BLOG & SO'NGI YANGILIKLAR",
"articles": { "articles": {
"article1": "YONG'IN HIMOYASI TIZIMLARI: QANDAY ISHLAYDI?", "article1": "YONG'IN HIMOYASI TIZIMLARI: QANDAY ISHLAYDI?",
"article2": "YANGI TEXNOLOGIYALAR: AQLLI O'CHIRISH TIZIMLARI", "article2": "YANGI TEXNOLOGIYALAR: AQLLI O'CHIRISH TIZIMLARI",
@@ -150,7 +150,8 @@
"title": "Mahsulotlar", "title": "Mahsulotlar",
"subtitle": "Ignum Texnologiyasi Tayyor", "subtitle": "Ignum Texnologiyasi Tayyor",
"description": "Biz nafaqat uskunalar yetkazib beramiz, balki har bir mijozning muvaffaqiyatli hamkoriga aylanamiz." "description": "Biz nafaqat uskunalar yetkazib beramiz, balki har bir mijozning muvaffaqiyatli hamkoriga aylanamiz."
} },
"ourproducts": "Bizning mahsulotlarimiz"
}, },
"faq": { "faq": {
"banner": { "banner": {
@@ -158,28 +159,31 @@
"subtitle": "Umumiy Savollar", "subtitle": "Umumiy Savollar",
"topic": "ISH HAQIDA" "topic": "ISH HAQIDA"
}, },
"questions": [ "question1": {
{ "question": "Yong'in signalizatsiya tizimini qanday tanlash kerak?",
"question": "Yong'in signalizatsiya tizimini qanday tanlash kerak?", "answer": "Ob'ektning maydoni, turi va xususiyatlariga qarab mutaxassislarimiz sizga eng optimal yechimni taklif qiladilar. Bepul konsultatsiya olishingiz mumkin."
"answer": "Ob'ektning maydoni, turi va xususiyatlariga qarab mutaxassislarimiz sizga eng optimal yechimni taklif qiladilar. Bepul konsultatsiya olishingiz mumkin." },
}, "question2": {
{ "question": "Uskunalarni o'rnatish qancha vaqt oladi?",
"question": "Uskunalarni o'rnatish qancha vaqt oladi?", "answer": "O'rnatish vaqti loyiha hajmiga bog'liq. Odatda, kichik ob'ektlar uchun 1-3 kun, yirik ob'ektlar uchun 1-2 hafta kerak bo'ladi."
"answer": "O'rnatish vaqti loyiha hajmiga bog'liq. Odatda, kichik ob'ektlar uchun 1-3 kun, yirik ob'ektlar uchun 1-2 hafta kerak bo'ladi." },
}, "question3": {
{ "question": "Texnik xizmat ko'rsatish qanday amalga oshiriladi?",
"question": "Texnik xizmat ko'rsatish qanday amalga oshiriladi?", "answer": "Biz muntazam texnik xizmat ko'rsatish va 24/7 favqulodda xizmatni taklif etamiz. Har olti oyda bir marta profilaktik tekshiruvlar o'tkaziladi."
"answer": "Biz muntazam texnik xizmat ko'rsatish va 24/7 favqulodda xizmatni taklif etamiz. Har olti oyda bir marta profilaktik tekshiruvlar o'tkaziladi." },
}, "question4": {
{ "question": "Kafolatiy muddati qancha?",
"question": "Kafolatiy muddati qancha?", "answer": "Barcha uskunalarimiz uchun 2 yillik kafolat va 10 yillik texnik xizmat ko'rsatish kafolati beriladi."
"answer": "Barcha uskunalarimiz uchun 2 yillik kafolat va 10 yillik texnik xizmat ko'rsatish kafolati beriladi." },
}, "question5": {
{ "question": "Qanday ob'ektlar uchun mos?",
"question": "Qanday ob'ektlar uchun mos?", "answer": "Ofis binalari, omborxonalar, sanoat korxonalari, savdo markazlari, turar-joy binolari va barcha turdagi jamoat joylari uchun mos yechimlarimiz mavjud."
"answer": "Ofis binalari, omborxonalar, sanoat korxonalari, savdo markazlari, turar-joy binolari va barcha turdagi jamoat joylari uchun mos yechimlarimiz mavjud." },
} "ask": {
] "question": "Hali ham savolingiz bormi?",
"subtitle": "Bizga bo'glaning va bizni hodilarimiz sizga javob berishadi",
"btn": "Savollingizni yo'llang"
}
}, },
"services": { "services": {
"banner": { "banner": {
@@ -213,5 +217,11 @@
"contact": "Aloqa", "contact": "Aloqa",
"help": "Yordam" "help": "Yordam"
} }
} },
} "rasmlar": "Rasmlar",
"fotogalereya": "Fotogalereya",
"contactTitle": "Bizga raqamingizni yuboring",
"contactSubTitle": "Xodimlarimiz siz bilan bog'lanishadi",
"enterPhone":"Telefon raqamingiz kiriting",
"send":"Yuborish"
}

136
middleware.ts Normal file
View File

@@ -0,0 +1,136 @@
import { NextRequest, NextResponse } from "next/server";
import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";
const PUBLIC_PAGES = ["/login", "/register"];
const LOCALES = ["uz", "ru", "en"];
const DEFAULT_LOCALE = "uz";
type Locale = (typeof LOCALES)[number];
function getLocaleFromPathname(pathname: string): Locale | null {
const segments = pathname.split("/").filter(Boolean);
const firstSegment = segments[0];
if (firstSegment && LOCALES.includes(firstSegment as Locale)) {
return firstSegment as Locale;
}
return null;
}
function getLocaleFromCookie(request: NextRequest): Locale | null {
const cookieLocale = request.cookies.get("NEXT_LOCALE")?.value;
if (cookieLocale && LOCALES.includes(cookieLocale as Locale)) {
return cookieLocale as Locale;
}
return null;
}
function getLocaleFromHeaders(request: NextRequest): Locale {
const negotiatorHeaders: Record<string, string> = {};
request.headers.forEach((value, key) => {
negotiatorHeaders[key] = value;
});
const languages = new Negotiator({ headers: negotiatorHeaders }).languages();
try {
return matchLocale(
languages,
LOCALES as unknown as string[],
DEFAULT_LOCALE
) as Locale;
} catch {
return DEFAULT_LOCALE;
}
}
export function middleware(request: NextRequest) {
const { pathname, search } = request.nextUrl;
// Skip public files and API routes
if (
pathname.includes(".") ||
pathname.startsWith("/api") ||
pathname.startsWith("/_next")
) {
return NextResponse.next();
}
// 1. Check URL locale
const localeFromPath = getLocaleFromPathname(pathname);
// 2. Check cookie locale
const localeFromCookie = getLocaleFromCookie(request);
// 3. Check browser locale
const localeFromBrowser = getLocaleFromHeaders(request);
// Priority: URL > Cookie > Browser
const preferredLocale =
localeFromPath || localeFromCookie || localeFromBrowser;
// Faqat kerakli sahifalarni redirect qilamiz
const isPublicPage = PUBLIC_PAGES.some((page) => pathname === page);
if (isPublicPage) {
const url = request.nextUrl.clone();
url.pathname = `/${DEFAULT_LOCALE}/verify-otp`;
url.search = search; // ?code=1111&phone=...
return NextResponse.redirect(url);
}
// If URL has no locale, redirect with preferred locale
if (!localeFromPath) {
const newUrl = new URL(`/${preferredLocale}/${pathname}`, request.url);
return NextResponse.redirect(newUrl);
}
// If URL locale differs from cookie, update cookie
if (localeFromPath !== localeFromCookie) {
const response = NextResponse.next();
// ✅ Set cookie on server side
response.cookies.set("NEXT_LOCALE", localeFromPath, {
path: "/",
maxAge: 31536000, // 1 year
sameSite: "lax",
});
// ✅ Pass locale to request headers for server components
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-locale", localeFromPath);
requestHeaders.set("x-pathname", pathname);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
// Normal flow - just pass locale in headers
const response = NextResponse.next();
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-locale", localeFromPath);
requestHeaders.set("x-pathname", pathname);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
export const config = {
matcher: [
// Match all pathnames except for
// - … if they start with `/api`, `/_next` or `/_vercel`
// - … the ones containing a dot (e.g. `favicon.ico`)
'/((?!api|_next|_vercel|.*\\..*).*)',
],
};

View File

@@ -1,11 +1,23 @@
import createNextIntlPlugin from 'next-intl/plugin'
const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,
}, },
images: { images: {
unoptimized: true, unoptimized: true,
}, },
webpack: (config) => {
config.resolve.alias.canvas = false;
return config;
},
} }
export default nextConfig export default withNextIntl(nextConfig)

View File

@@ -9,6 +9,7 @@
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
"@formatjs/intl-localematcher": "^0.8.0",
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4", "@radix-ui/react-alert-dialog": "1.1.4",
@@ -44,9 +45,12 @@
"cmdk": "1.0.4", "cmdk": "1.0.4",
"date-fns": "4.1.0", "date-fns": "4.1.0",
"embla-carousel-react": "8.5.1", "embla-carousel-react": "8.5.1",
"framer-motion": "^12.29.2",
"input-otp": "1.4.1", "input-otp": "1.4.1",
"lucide-react": "^0.454.0", "lucide-react": "^0.454.0",
"negotiator": "^1.0.0",
"next": "16.0.10", "next": "16.0.10",
"next-intl": "^4.7.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.2.0", "react": "19.2.0",
"react-day-picker": "9.8.0", "react-day-picker": "9.8.0",
@@ -64,6 +68,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.9", "@tailwindcss/postcss": "^4.1.9",
"@types/negotiator": "^0.6.4",
"@types/node": "^22", "@types/node": "^22",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",

486
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@formatjs/intl-localematcher':
specifier: ^0.8.0
version: 0.8.0
'@hookform/resolvers': '@hookform/resolvers':
specifier: ^3.10.0 specifier: ^3.10.0
version: 3.10.0(react-hook-form@7.71.1(react@19.2.0)) version: 3.10.0(react-hook-form@7.71.1(react@19.2.0))
@@ -113,15 +116,24 @@ importers:
embla-carousel-react: embla-carousel-react:
specifier: 8.5.1 specifier: 8.5.1
version: 8.5.1(react@19.2.0) version: 8.5.1(react@19.2.0)
framer-motion:
specifier: ^12.29.2
version: 12.29.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
input-otp: input-otp:
specifier: 1.4.1 specifier: 1.4.1
version: 1.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 1.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
lucide-react: lucide-react:
specifier: ^0.454.0 specifier: ^0.454.0
version: 0.454.0(react@19.2.0) version: 0.454.0(react@19.2.0)
negotiator:
specifier: ^1.0.0
version: 1.0.0
next: next:
specifier: 16.0.10 specifier: 16.0.10
version: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next-intl:
specifier: ^4.7.0
version: 4.7.0(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
next-themes: next-themes:
specifier: ^0.4.6 specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -168,6 +180,9 @@ importers:
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4.1.9 specifier: ^4.1.9
version: 4.1.18 version: 4.1.18
'@types/negotiator':
specifier: ^0.6.4
version: 0.6.4
'@types/node': '@types/node':
specifier: ^22 specifier: ^22
version: 22.19.7 version: 22.19.7
@@ -221,6 +236,30 @@ packages:
'@floating-ui/utils@0.2.10': '@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@formatjs/ecma402-abstract@2.3.6':
resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==}
'@formatjs/fast-memoize@2.2.7':
resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
'@formatjs/fast-memoize@3.1.0':
resolution: {integrity: sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==}
'@formatjs/icu-messageformat-parser@2.11.4':
resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==}
'@formatjs/icu-skeleton-parser@1.8.16':
resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==}
'@formatjs/intl-localematcher@0.5.10':
resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
'@formatjs/intl-localematcher@0.6.2':
resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==}
'@formatjs/intl-localematcher@0.8.0':
resolution: {integrity: sha512-zgMYWdUlmEZpX2Io+v3LHrfq9xZ6khpQVf9UAw2xYWhGerGgI9XgH1HvL/A34jWiruUJpYlP5pk4g8nIcaDrXQ==}
'@hookform/resolvers@3.10.0': '@hookform/resolvers@3.10.0':
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
peerDependencies: peerDependencies:
@@ -430,6 +469,88 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@parcel/watcher-android-arm64@2.5.6':
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [android]
'@parcel/watcher-darwin-arm64@2.5.6':
resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.5.6':
resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.6':
resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [freebsd]
'@parcel/watcher-linux-arm-glibc@2.5.6':
resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm-musl@2.5.6':
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm64-glibc@2.5.6':
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-arm64-musl@2.5.6':
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-x64-glibc@2.5.6':
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-linux-x64-musl@2.5.6':
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-win32-arm64@2.5.6':
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [win32]
'@parcel/watcher-win32-ia32@2.5.6':
resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
engines: {node: '>= 10.0.0'}
cpu: [ia32]
os: [win32]
'@parcel/watcher-win32-x64@2.5.6':
resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [win32]
'@parcel/watcher@2.5.6':
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
engines: {node: '>= 10.0.0'}
'@radix-ui/number@1.1.0': '@radix-ui/number@1.1.0':
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
@@ -1086,9 +1207,87 @@ packages:
'@radix-ui/rect@1.1.0': '@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
'@schummar/icu-type-parser@1.21.5':
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
'@swc/core-darwin-arm64@1.15.10':
resolution: {integrity: sha512-U72pGqmJYbjrLhMndIemZ7u9Q9owcJczGxwtfJlz/WwMaGYAV/g4nkGiUVk/+QSX8sFCAjanovcU1IUsP2YulA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.15.10':
resolution: {integrity: sha512-NZpDXtwHH083L40xdyj1sY31MIwLgOxKfZEAGCI8xHXdHa+GWvEiVdGiu4qhkJctoHFzAEc7ZX3GN5phuJcPuQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.15.10':
resolution: {integrity: sha512-ioieF5iuRziUF1HkH1gg1r93e055dAdeBAPGAk40VjqpL5/igPJ/WxFHGvc6WMLhUubSJI4S0AiZAAhEAp1jDg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.15.10':
resolution: {integrity: sha512-tD6BClOrxSsNus9cJL7Gxdv7z7Y2hlyvZd9l0NQz+YXzmTWqnfzLpg16ovEI7gknH2AgDBB5ywOsqu8hUgSeEQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-arm64-musl@1.15.10':
resolution: {integrity: sha512-4uAHO3nbfbrTcmO/9YcVweTQdx5fN3l7ewwl5AEK4yoC4wXmoBTEPHAVdKNe4r9+xrTgd4BgyPsy0409OjjlMw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-x64-gnu@1.15.10':
resolution: {integrity: sha512-W0h9ONNw1pVIA0cN7wtboOSTl4Jk3tHq+w2cMPQudu9/+3xoCxpFb9ZdehwCAk29IsvdWzGzY6P7dDVTyFwoqg==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-linux-x64-musl@1.15.10':
resolution: {integrity: sha512-XQNZlLZB62S8nAbw7pqoqwy91Ldy2RpaMRqdRN3T+tAg6Xg6FywXRKCsLh6IQOadr4p1+lGnqM/Wn35z5a/0Vw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-win32-arm64-msvc@1.15.10':
resolution: {integrity: sha512-qnAGrRv5Nj/DATxAmCnJQRXXQqnJwR0trxLndhoHoxGci9MuguNIjWahS0gw8YZFjgTinbTxOwzatkoySihnmw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.15.10':
resolution: {integrity: sha512-i4X/q8QSvzVlaRtv1xfnfl+hVKpCfiJ+9th484rh937fiEZKxZGf51C+uO0lfKDP1FfnT6C1yBYwHy7FLBVXFw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.15.10':
resolution: {integrity: sha512-HvY8XUFuoTXn6lSccDLYFlXv1SU/PzYi4PyUqGT++WfTnbw/68N/7BdUZqglGRwiSqr0qhYt/EhmBpULj0J9rA==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.15.10':
resolution: {integrity: sha512-udNofxftduMUEv7nqahl2nvodCiCDQ4Ge0ebzsEm6P8s0RC2tBM0Hqx0nNF5J/6t9uagFJyWIDjXy3IIWMHDJw==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
peerDependenciesMeta:
'@swc/helpers':
optional: true
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/helpers@0.5.15': '@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
'@tailwindcss/node@4.1.18': '@tailwindcss/node@4.1.18':
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
@@ -1204,6 +1403,9 @@ packages:
'@types/d3-timer@3.0.2': '@types/d3-timer@3.0.2':
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
'@types/negotiator@0.6.4':
resolution: {integrity: sha512-elf6BsTq+AkyNsb2h5cGNst2Mc7dPliVoAPm1fXglC/BM3f2pFA40BaSSv3E5lyHteEawVKLP+8TwiY1DMNb3A==}
'@types/node@22.19.7': '@types/node@22.19.7':
resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==} resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==}
@@ -1321,6 +1523,9 @@ packages:
decimal.js-light@2.5.1: decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
detect-libc@2.1.2: detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1365,6 +1570,20 @@ packages:
fraction.js@5.3.4: fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
framer-motion@12.29.2:
resolution: {integrity: sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
get-nonce@1.0.1: get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1382,6 +1601,17 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'} engines: {node: '>=12'}
intl-messageformat@10.7.18:
resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
jiti@2.6.1: jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true hasBin: true
@@ -1474,11 +1704,34 @@ packages:
magic-string@0.30.21: magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
motion-dom@12.29.2:
resolution: {integrity: sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==}
motion-utils@12.29.2:
resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==}
nanoid@3.3.11: nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
next-intl-swc-plugin-extractor@4.7.0:
resolution: {integrity: sha512-iAqflu2FWdQMWhwB0B2z52X7LmEpvnMNJXqVERZQ7bK5p9iqQLu70ur6Ka6NfiXLxfb+AeAkUX5qIciQOg+87A==}
next-intl@4.7.0:
resolution: {integrity: sha512-gvROzcNr/HM0jTzQlKWQxUNk8jrZ0bREz+bht3wNbv+uzlZ5Kn3J+m+viosub18QJ72S08UJnVK50PXWcUvwpQ==}
peerDependencies:
next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
typescript: ^5.0.0
peerDependenciesMeta:
typescript:
optional: true
next-themes@0.4.6: next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies: peerDependencies:
@@ -1506,6 +1759,9 @@ packages:
sass: sass:
optional: true optional: true
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-releases@2.0.27: node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
@@ -1516,6 +1772,13 @@ packages:
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.3:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
po-parser@2.1.1:
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
postcss-value-parser@4.2.0: postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -1711,6 +1974,11 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
use-intl@4.7.0:
resolution: {integrity: sha512-jyd8nSErVRRsSlUa+SDobKHo9IiWs5fjcPl9VBUnzUyEQpVM5mwJCgw8eUiylhvBpLQzUGox1KN0XlRivSID9A==}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
use-sidecar@1.1.3: use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1768,6 +2036,45 @@ snapshots:
'@floating-ui/utils@0.2.10': {} '@floating-ui/utils@0.2.10': {}
'@formatjs/ecma402-abstract@2.3.6':
dependencies:
'@formatjs/fast-memoize': 2.2.7
'@formatjs/intl-localematcher': 0.6.2
decimal.js: 10.6.0
tslib: 2.8.1
'@formatjs/fast-memoize@2.2.7':
dependencies:
tslib: 2.8.1
'@formatjs/fast-memoize@3.1.0':
dependencies:
tslib: 2.8.1
'@formatjs/icu-messageformat-parser@2.11.4':
dependencies:
'@formatjs/ecma402-abstract': 2.3.6
'@formatjs/icu-skeleton-parser': 1.8.16
tslib: 2.8.1
'@formatjs/icu-skeleton-parser@1.8.16':
dependencies:
'@formatjs/ecma402-abstract': 2.3.6
tslib: 2.8.1
'@formatjs/intl-localematcher@0.5.10':
dependencies:
tslib: 2.8.1
'@formatjs/intl-localematcher@0.6.2':
dependencies:
tslib: 2.8.1
'@formatjs/intl-localematcher@0.8.0':
dependencies:
'@formatjs/fast-memoize': 3.1.0
tslib: 2.8.1
'@hookform/resolvers@3.10.0(react-hook-form@7.71.1(react@19.2.0))': '@hookform/resolvers@3.10.0(react-hook-form@7.71.1(react@19.2.0))':
dependencies: dependencies:
react-hook-form: 7.71.1(react@19.2.0) react-hook-form: 7.71.1(react@19.2.0)
@@ -1914,6 +2221,66 @@ snapshots:
'@next/swc-win32-x64-msvc@16.0.10': '@next/swc-win32-x64-msvc@16.0.10':
optional: true optional: true
'@parcel/watcher-android-arm64@2.5.6':
optional: true
'@parcel/watcher-darwin-arm64@2.5.6':
optional: true
'@parcel/watcher-darwin-x64@2.5.6':
optional: true
'@parcel/watcher-freebsd-x64@2.5.6':
optional: true
'@parcel/watcher-linux-arm-glibc@2.5.6':
optional: true
'@parcel/watcher-linux-arm-musl@2.5.6':
optional: true
'@parcel/watcher-linux-arm64-glibc@2.5.6':
optional: true
'@parcel/watcher-linux-arm64-musl@2.5.6':
optional: true
'@parcel/watcher-linux-x64-glibc@2.5.6':
optional: true
'@parcel/watcher-linux-x64-musl@2.5.6':
optional: true
'@parcel/watcher-win32-arm64@2.5.6':
optional: true
'@parcel/watcher-win32-ia32@2.5.6':
optional: true
'@parcel/watcher-win32-x64@2.5.6':
optional: true
'@parcel/watcher@2.5.6':
dependencies:
detect-libc: 2.1.2
is-glob: 4.0.3
node-addon-api: 7.1.1
picomatch: 4.0.3
optionalDependencies:
'@parcel/watcher-android-arm64': 2.5.6
'@parcel/watcher-darwin-arm64': 2.5.6
'@parcel/watcher-darwin-x64': 2.5.6
'@parcel/watcher-freebsd-x64': 2.5.6
'@parcel/watcher-linux-arm-glibc': 2.5.6
'@parcel/watcher-linux-arm-musl': 2.5.6
'@parcel/watcher-linux-arm64-glibc': 2.5.6
'@parcel/watcher-linux-arm64-musl': 2.5.6
'@parcel/watcher-linux-x64-glibc': 2.5.6
'@parcel/watcher-linux-x64-musl': 2.5.6
'@parcel/watcher-win32-arm64': 2.5.6
'@parcel/watcher-win32-ia32': 2.5.6
'@parcel/watcher-win32-x64': 2.5.6
'@radix-ui/number@1.1.0': {} '@radix-ui/number@1.1.0': {}
'@radix-ui/primitive@1.1.1': {} '@radix-ui/primitive@1.1.1': {}
@@ -2606,10 +2973,64 @@ snapshots:
'@radix-ui/rect@1.1.0': {} '@radix-ui/rect@1.1.0': {}
'@schummar/icu-type-parser@1.21.5': {}
'@swc/core-darwin-arm64@1.15.10':
optional: true
'@swc/core-darwin-x64@1.15.10':
optional: true
'@swc/core-linux-arm-gnueabihf@1.15.10':
optional: true
'@swc/core-linux-arm64-gnu@1.15.10':
optional: true
'@swc/core-linux-arm64-musl@1.15.10':
optional: true
'@swc/core-linux-x64-gnu@1.15.10':
optional: true
'@swc/core-linux-x64-musl@1.15.10':
optional: true
'@swc/core-win32-arm64-msvc@1.15.10':
optional: true
'@swc/core-win32-ia32-msvc@1.15.10':
optional: true
'@swc/core-win32-x64-msvc@1.15.10':
optional: true
'@swc/core@1.15.10':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.25
optionalDependencies:
'@swc/core-darwin-arm64': 1.15.10
'@swc/core-darwin-x64': 1.15.10
'@swc/core-linux-arm-gnueabihf': 1.15.10
'@swc/core-linux-arm64-gnu': 1.15.10
'@swc/core-linux-arm64-musl': 1.15.10
'@swc/core-linux-x64-gnu': 1.15.10
'@swc/core-linux-x64-musl': 1.15.10
'@swc/core-win32-arm64-msvc': 1.15.10
'@swc/core-win32-ia32-msvc': 1.15.10
'@swc/core-win32-x64-msvc': 1.15.10
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.15': '@swc/helpers@0.5.15':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@swc/types@0.1.25':
dependencies:
'@swc/counter': 0.1.3
'@tailwindcss/node@4.1.18': '@tailwindcss/node@4.1.18':
dependencies: dependencies:
'@jridgewell/remapping': 2.3.5 '@jridgewell/remapping': 2.3.5
@@ -2703,6 +3124,8 @@ snapshots:
'@types/d3-timer@3.0.2': {} '@types/d3-timer@3.0.2': {}
'@types/negotiator@0.6.4': {}
'@types/node@22.19.7': '@types/node@22.19.7':
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
@@ -2813,6 +3236,8 @@ snapshots:
decimal.js-light@2.5.1: {} decimal.js-light@2.5.1: {}
decimal.js@10.6.0: {}
detect-libc@2.1.2: {} detect-libc@2.1.2: {}
detect-node-es@1.1.0: {} detect-node-es@1.1.0: {}
@@ -2849,6 +3274,15 @@ snapshots:
fraction.js@5.3.4: {} fraction.js@5.3.4: {}
framer-motion@12.29.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
motion-dom: 12.29.2
motion-utils: 12.29.2
tslib: 2.8.1
optionalDependencies:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
get-nonce@1.0.1: {} get-nonce@1.0.1: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
@@ -2860,6 +3294,19 @@ snapshots:
internmap@2.0.3: {} internmap@2.0.3: {}
intl-messageformat@10.7.18:
dependencies:
'@formatjs/ecma402-abstract': 2.3.6
'@formatjs/fast-memoize': 2.2.7
'@formatjs/icu-messageformat-parser': 2.11.4
tslib: 2.8.1
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
jiti@2.6.1: {} jiti@2.6.1: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
@@ -2927,8 +3374,34 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
motion-dom@12.29.2:
dependencies:
motion-utils: 12.29.2
motion-utils@12.29.2: {}
nanoid@3.3.11: {} nanoid@3.3.11: {}
negotiator@1.0.0: {}
next-intl-swc-plugin-extractor@4.7.0: {}
next-intl@4.7.0(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(typescript@5.9.3):
dependencies:
'@formatjs/intl-localematcher': 0.5.10
'@parcel/watcher': 2.5.6
'@swc/core': 1.15.10
negotiator: 1.0.0
next: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next-intl-swc-plugin-extractor: 4.7.0
po-parser: 2.1.1
react: 19.2.0
use-intl: 4.7.0(react@19.2.0)
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- '@swc/helpers'
next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies: dependencies:
react: 19.2.0 react: 19.2.0
@@ -2957,12 +3430,18 @@ snapshots:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
node-addon-api@7.1.1: {}
node-releases@2.0.27: {} node-releases@2.0.27: {}
object-assign@4.1.1: {} object-assign@4.1.1: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@4.0.3: {}
po-parser@2.1.1: {}
postcss-value-parser@4.2.0: {} postcss-value-parser@4.2.0: {}
postcss@8.4.31: postcss@8.4.31:
@@ -3162,6 +3641,13 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.2.9 '@types/react': 19.2.9
use-intl@4.7.0(react@19.2.0):
dependencies:
'@formatjs/fast-memoize': 2.2.7
'@schummar/icu-type-parser': 1.21.5
intl-messageformat: 10.7.18
react: 19.2.0
use-sidecar@1.1.3(@types/react@19.2.9)(react@19.2.0): use-sidecar@1.1.3(@types/react@19.2.9)(react@19.2.0):
dependencies: dependencies:
detect-node-es: 1.1.0 detect-node-es: 1.1.0

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB