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 'tw-animate-css';
@import "tailwindcss";
@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 {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
@@ -19,7 +70,6 @@
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
@@ -28,7 +78,6 @@
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
@@ -42,11 +91,11 @@
.dark {
--background: oklch(0.145 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);
--popover: oklch(0.145 0 0);
--popover: oklch(0.205 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);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
@@ -54,11 +103,10 @@
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
@@ -70,49 +118,8 @@
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 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);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
@@ -124,26 +131,6 @@
}
}
.loi{
color:#a6040400;
body {
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 "./globals.css";
import { Footer, Navbar } from "@/components/layout";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
const _geist = Geist({ subsets: ["latin"] });
const _geistMono = Geist_Mono({ subsets: ["latin"] });
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "FireForce - Emergency Services",
@@ -32,18 +41,26 @@ export const metadata: Metadata = {
},
};
export default function RootLayout({
export default async function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: any;
}>) {
const { locale } = await params;
const messages: any = await getMessages();
return (
<html lang="en">
<body className={`font-sans antialiased`}>
<Navbar />
{children}
<Footer />
<Analytics />
<html lang={locale} suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<NextIntlClientProvider messages={messages} locale={locale}>
<Navbar />
{children}
<Footer />
<Analytics />
</NextIntlClientProvider>
</body>
</html>
);

View File

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

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 { 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() {
const locale = useLocale();
const t = useTranslations();
const [email, setEmail] = useState("");
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%)",
}}
>
{/* Newsletter Section */}
{/* Newsletter Section for gitea */}
<div className=" absolute w-full -top-40 px-4 py-12 md:py-16">
<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="mb-8 md:mb-0">
<h2 className="text-2xl font-bold text-white md:text-3xl">
SUBSCRIBE OUR NEWSLETTER
<h2 className="font-unbounded text-2xl font-bold text-white md:text-3xl">
{t("contactTitle")}
</h2>
<p className="mt-2 text-gray-100">
Expect a friendly letter from us once a week. No spam.
<p className="font-almarai mt-2 text-gray-100">
{t("contactSubTitle")}
</p>
</div>
@@ -45,17 +50,17 @@ export function Footer() {
>
<input
type="email"
placeholder="Enter your email address"
placeholder={t("enterPhone")}
value={email}
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
/>
<button
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>
</form>
</div>
@@ -69,91 +74,71 @@ export function Footer() {
{/* Brand Section */}
<div>
<div className="mb-4 flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-[#fa1d1d]">
<span className="text-lg font-bold text-white"></span>
<div className=" flex items-center justify-center">
<Image
src="/images/IGNUM/PNG/1.@6x.png"
alt="logo image"
width={70}
height={70}
className=""
/>
</div>
<span className="text-xl font-bold text-white">FIREFORCE</span>
</div>
<p className="text-sm leading-relaxed text-gray-300">
They are seen as a beacon of hope, a figure who brings calm
amidst chaos and light in the darkest of moments.
<p className="font-almarai text-sm leading-relaxed text-gray-300">
{t("footer.description")}
</p>
</div>
{/* Quick Links */}
<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">
<li>
<a href="#home" className="transition hover:text-[#fa1d1d]">
Home
</a>
</li>
<li>
<a href="#about" className="transition hover:text-[#fa1d1d]">
About
</a>
</li>
<li>
<a
href="#services"
className="transition hover:text-[#fa1d1d]"
<Link
href={`/${locale}/home`}
className="font-almarai transition hover:text-[#fa1d1d]"
>
Services
</a>
{t("footer.quickLinks.home")}
</Link>
</li>
<li>
<a
href="#pricing"
className="transition hover:text-[#fa1d1d]"
<Link
href={`/${locale}/about`}
className="font-almarai transition hover:text-[#fa1d1d]"
>
Pricing
</a>
{t("footer.quickLinks.about")}
</Link>
</li>
<li>
<a href="#blog" className="transition hover:text-[#fa1d1d]">
Blog
</a>
<Link
href={`/${locale}/services`}
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>
</ul>
</div>
{/* Support */}
<div>
<h3 className="mb-6 text-lg font-bold text-white">SUPPORT</h3>
<ul className="space-y-3 text-gray-300">
<h3 className="font-unbounded mb-6 text-lg font-bold text-white">
{t("footer.support.title")}
</h3>
<ul className="font-almarai space-y-3 text-gray-300">
<li>
<a href="#help" className="transition hover:text-[#fa1d1d]">
Help Center
</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
{t("footer.support.contact")}
</a>
</li>
</ul>
@@ -161,12 +146,14 @@ export function Footer() {
{/* Contact */}
<div>
<h3 className="mb-6 text-lg font-bold text-white">CONTACT</h3>
<ul className="space-y-4 text-gray-300">
<h3 className="font-unbounded uppercase mb-6 text-lg font-bold text-white">
{t("footer.support.contact")}
</h3>
<ul className="font-almarai space-y-4 text-gray-300">
<li className="flex items-start gap-3">
<Phone className="mt-1 h-5 w-5 shrink-0 text-white" />
<a href="tel:+1234567890" className="hover:text-[#fa1d1d]">
+123-456-7890
<a href="tel:+998773722121" className="hover:text-[#fa1d1d]">
+998-77-372-21-21
</a>
</li>
<li className="flex items-start gap-3">
@@ -191,13 +178,9 @@ export function Footer() {
{/* Copyright Section */}
<div className="border-t border-gray-800 px-4 py-8">
<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>
Copyright © 2025 Fireforce. Built with
<span className="text-white">
{" "}
Guniverse WordPress Blocks Addons
</span>
Copyright © 2025 Ignum Company.
</div>
<div className="flex gap-6">
<a href="#terms" className="hover:text-white">

View File

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

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function AboutBanner() {
const t = useTranslations();
return (
<section className="relative w-full lg:h-[60vh] h-screen min-h-100 overflow-hidden pt-10">
{/* Background Image */}
@@ -24,17 +26,21 @@ export function AboutBanner() {
<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="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
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"
>
Fire Fighter <br /> At The Ready
{t("about.banner.subtitle")}
</p>
</div>
<div className="lg:w-[40%] text-gray-300 mt-20">
It emphasizes that these firefighters are there not just as public
servants but as a vital part of the community.
<div className="font-almarai lg:w-[40%] text-gray-300 mt-20">
{t("about.banner.description")}
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
"use client";
import { Check } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
interface FormData {
@@ -22,6 +23,7 @@ interface FormErrors {
}
export default function Form() {
const t = useTranslations();
const [formData, setFormData] = useState<FormData>({
firstName: "",
lastName: "",
@@ -124,30 +126,31 @@ export default function Form() {
<input
type="text"
name="firstName"
placeholder="First Name"
placeholder={t("contact.form.placeholders.firstName")}
value={formData.firstName}
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 && (
<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>
<input
type="text"
name="lastName"
placeholder="Last Name"
placeholder={t("contact.form.placeholders.lastName")}
value={formData.lastName}
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 && (
<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>
@@ -158,30 +161,30 @@ export default function Form() {
<input
type="email"
name="email"
placeholder="Your Email Address"
placeholder={t("contact.form.placeholders.email")}
value={formData.email}
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 && (
<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>
<input
type="text"
name="subject"
placeholder="Subject"
placeholder={t("contact.form.placeholders.subject")}
value={formData.subject}
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 && (
<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>
@@ -190,16 +193,16 @@ export default function Form() {
<div>
<textarea
name="message"
placeholder="Leave us a message"
placeholder={t("contact.form.placeholders.message")}
rows={8}
value={formData.message}
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 && (
<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>
@@ -220,30 +223,30 @@ export default function Form() {
<Check className="h-3 w-3 text-white" strokeWidth={3} />
)}
</button>
<span className="text-sm text-gray-300">
You agree to our friendly privacy policy
<span className="font-almarai text-sm text-gray-300">
{t("contact.form.privacy")}
</span>
</div>
<button
type="submit"
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>
</div>
{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 */}
{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!
</p>
)}
{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.
</p>
)}

View File

@@ -2,23 +2,25 @@ import Image from "next/image";
import { Mail, MapPin, Phone, Check } from "lucide-react";
import ContactHeader from "./contactHeader";
import Form from "./form";
import { useTranslations } from "next-intl";
export function Contact() {
const t = useTranslations();
const contactInfo = [
{
icon: Mail,
title: "EMAIL",
detail: "support@fireforce",
title:t("contact.form.email"),
detail: t("contact.form.emailAddress"),
},
{
icon: MapPin,
title: "OUR LOCATION",
detail: "Jl. Dr. Ir. Soekarno No. 99x Tabanan - Bali",
title: t("contact.form.location"),
detail: t("contact.form.address"),
},
{
icon: Phone,
title: "PHONE",
detail: "+123-456-7890",
title: t("contact.form.phone"),
detail: "+998-77-372-21-21",
},
];
@@ -38,7 +40,7 @@ export function Contact() {
className="absolute inset-0"
style={{
background:
"radial-gradient(ellipse at bottom center, #d2610ab0 0%, #1e1d1ce3 70%)",
"radial-gradient(ellipse at bottom center, #d2610a 0%, #1e1d1ce9 70% , #1e1d1ce9 70%)",
}}
/>
</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]">
<info.icon className="h-6 w-6 text-red-600" />
</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}
</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>

View File

@@ -1,44 +1,73 @@
import Link from "next/link";
import FAQAccordion from "./faqAccardion";
import { faqItems } from "@/lib/demoData";
import { useTranslations } from "next-intl";
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 (
<div className="min-h-screen bg-[#1e1d1c]">
<main className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8">
{/* 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">
<h1
className="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"
className="font-unbounded uppercase bg-linear-to-br from-white via-white/50 to-black
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>
</div>
{/* FAQ Section */}
<div className="md:col-span-2">
<div className="max-w-250 w-full">
<FAQAccordion items={faqItems} />
</div>
</div>
{/* ASK QUESTION */}
<div className="space-y-8 w-full mt-10 ">
<h1
className="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"
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 leading-tight sm:text-4xl md:text-5xl lg:text-6xl"
>
Still Have Question?
{t("faq.ask.question")}
</h1>
<p className="text-center text-white/80 text-lg">
Nullam dictum felis eu pede mollis pretium integer tincidunt.
<p className="font-almarai text-center text-white/80 text-lg">
{t("faq.ask.subtitle")}
</p>
<div className="w-full flex items-center justify-center">
<Link
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>
</div>
</div>

View File

@@ -19,7 +19,7 @@ export default function FAQAccordion({ items }: FAQAccordionProps) {
{items.map((item, index) => (
<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">
<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}
</h3>
<div className="ml-4 shrink-0">
@@ -31,7 +31,7 @@ export default function FAQAccordion({ items }: FAQAccordionProps) {
</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">
<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.Item>
))}

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function FaqBanner() {
const t = useTranslations();
return (
<section className="relative w-full h-[55vh] min-h-100 overflow-hidden pt-10">
{/* Background Image */}
@@ -24,12 +26,17 @@ export function FaqBanner() {
<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="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
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"
>
GENERAL <br /> QUESTIONS
{t("faq.banner.subtitle")}
</p>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
import Image from "next/image";
import { Flame, Building2, Ambulance } from "lucide-react";
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
interface ServiceItem {
icon: React.ReactNode;
@@ -13,28 +14,25 @@ interface ServiceItem {
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() {
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 (
<section className="bg-[#1e1d1c] py-16 px-4 sm:py-20 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
@@ -45,17 +43,15 @@ export function AboutUs() {
<div className="space-y-6">
<div className="flex items-center gap-3">
<DotAnimatsiya />
<span className="text-white font-bold text-sm tracking-wide">
ABOUT US
<span className="font-almarai text-white font-bold text-sm tracking-wide">
{t("home.about.title")}
</span>
</div>
<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"
>
FIREFIGHTERS
<br />
AT THE READY
{t("home.about.subtitle")}
</h2>
</div>
@@ -67,10 +63,10 @@ export function AboutUs() {
{service.icon}
</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}
</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}
</p>
</div>
@@ -80,8 +76,8 @@ export function AboutUs() {
{/* Button */}
<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]">
ABOUT US
<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]">
{t("home.about.title")}
</Button>
</div>
</div>
@@ -112,10 +108,7 @@ export function AboutUs() {
/>
<div className="text-center">
<p className="text-white font-bold text-sm sm:text-base">
BEST AWARD
</p>
<p className="text-white font-bold text-sm sm:text-base">
FIREFIGHTER 2025
{t("home.about.award")}
</p>
</div>
</div>

View File

@@ -1,6 +1,8 @@
import { useTranslations } from "next-intl";
import DotAnimatsiya from "../../dot/DotAnimatsiya";
export function Banner() {
const t = useTranslations();
return (
<section className="relative w-full lg:h-[86vh] h-screen min-h-150 overflow-hidden pt-20">
{/* Background Image */}
@@ -17,78 +19,77 @@ export function Banner() {
<div
className="absolute inset-0 z-10"
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 */}
<div className="relative z-20 h-full flex items-end lg:mt-0 md:mt-[15vh] sm:mt-[5vh] mt-0">
<div className="max-w-375 mx-auto px-4 sm:px-6 lg:px-8 w-full">
<div className="relative z-20 h-full flex items-center lg:mt-0 sm:mt-[10vh] mt-[5vh]">
<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">
{/* Right side - Text Content */}
<div className="lg:hidden inline-block space-y-6 text-white">
{/* Badge */}
<div className="flex items-center gap-2 w-fit">
<DotAnimatsiya />
<span className="text-sm font-semibold tracking-wide">
WELCOME TO FIREFORCE
<span className="text-sm font-semibold tracking-wide font-almarai">
{t("home.banner.title1")}
</span>
</div>
{/* Main Heading */}
<h1
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>
{/* Description */}
<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
amidst chaos and light in the darkest of moments.
{t("home.banner.description")}
</p>
{/* 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">
GET STARTED
{t("home.banner.cta")}
</button>
</div>
{/* Left side - Firefighters Image */}
<div className="flex items-end justify-center">
<div className="flex items-end justify-center ">
<img
src="/images/home/bannerHuman.png"
src="/images/homeBanner.png"
alt="Firefighters"
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>
{/* 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 */}
<div className="flex items-center gap-2 w-fit">
<DotAnimatsiya />
<span className="text-sm font-semibold tracking-wide">
WELCOME TO FIREFORCE
<span className="text-sm font-semibold text-white tracking-wide font-almarai">
{t("home.banner.title1")}
</span>
</div>
{/* Main Heading */}
<h1 className="gradient-text text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-pretty">
THE FIRE GUARDIAN
<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("home.banner.title2")}
</h1>
{/* Description */}
<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
amidst chaos and light in the darkest of moments.
<p className="font-almarai text-base sm:text-lg text-gray-300 leading-relaxed max-w-md">
{t("home.banner.description")}
</p>
{/* 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">
GET STARTED
<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">
{t("home.banner.cta")}
</button>
</div>
</div>

View File

@@ -1,35 +1,38 @@
import Image from "next/image";
import { ChevronRight } from "lucide-react";
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
const blogPosts = [
{
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",
},
];
import { useTranslations } from "next-intl";
import ProductCard from "../products/productCard";
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 (
<section className="bg-[#1f1f1f] py-45">
<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-4 flex items-center justify-center gap-2">
<DotAnimatsiya />
<span className="text-sm font-semibold tracking-wider text-white uppercase">
Blog & Articles
<span className="font-almarai text-sm font-semibold tracking-wider text-white uppercase">
{t("products.banner.title")}
</span>
</div>
<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"
>
LATEST BLOG & NEWS
{t("products.ourproducts")}
</h2>
</div>
{/* Blog Cards Grid */}
<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">
{/* Image Container */}
<div className="relative mb-6 aspect-4/2 md:aspect-4/3 overflow-hidden rounded-lg">
<Image
src={post.image || "/placeholder.svg"}
@@ -61,20 +64,19 @@ export function Blog() {
fill
className="object-cover transition-transform duration-300 group-hover:scale-105"
/>
{/* Category Badge */}
<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}
</span>
</div>
</div>
{/* Content */}
<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}
</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-white">{post.author}</span>
<span className="mx-2 text-gray-500">•</span>
@@ -82,14 +84,26 @@ export function Blog() {
</p>
<a
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" />
</a>
</div>
</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>
</section>

View File

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

View File

@@ -1,63 +1,64 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { ChevronRight } from "lucide-react";
import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
export function OurService() {
const t = useTranslations();
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">
{/* Header */}
<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 />
OUR SERVICES
{t("home.services.title")}
</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">
FIREFIGHTER RESPONSIBILITIES
<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">
{t("home.services.subtitle")}
</h1>
<p className="text-center text-sm sm:text-base md:text-lg text-gray-400 max-w-3xl mx-auto px-4">
Aliquam lorem ante dapibus in viverra quis feugiat a tellus
phasellus viverra nulla ut metus varius laoreet quisque rutrum.
<p className="font-almarai text-center text-sm sm:text-base md:text-lg text-gray-400 max-w-4xl mx-auto px-4">
{t("home.services.description")}
</p>
</div>
{/* cards */}
<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)]">
<p className="font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
Operation Force
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.operation.title")}
</p>
<p className="text-gray-400 max-w-80 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus.
<p className="font-almarai text-gray-400 max-w-80 w-full">
{t("home.services.services.operation.description")}
</p>
<button className="text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} />
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/gruop.png"
alt="images"
width={200}
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 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">
Operation Force
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.suppression.title")}
</p>
<p className="text-gray-400 max-w-70 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus.
<p className="font-almarai text-gray-400 max-w-70 w-full">
{t("home.services.services.suppression.description")}
</p>
<button className="text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} />
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/redShlang.png"
alt="images"
width={200}
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>
@@ -72,45 +73,45 @@ export function OurService() {
className="object-contain mt-5"
/>
<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">
Operation Force
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.safety.title")}
</p>
<p className="text-gray-400 max-w-80 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus.
<p className="font-almarai text-gray-400 max-w-80 w-full">
{t("home.services.services.safety.description")}
</p>
<button className="text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} />
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
</div>
</div>
<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)]">
<p className="font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
Operation Force
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.monitoring.title")}
</p>
<p className="text-gray-400 max-w-70 w-full">
Aliquam lorem ante dapibus in viverra feugiat phasellus.
<p className="font-almarai text-gray-400 max-w-70 w-full">
{t("home.services.services.monitoring.description")}
</p>
<button className="sm:mt-38 mt-0 text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
LEARN MORE <ChevronRight size={20} />
<button className="font-almarai sm:mt-38 mt-0 text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/balon.png"
alt="images"
width={200}
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 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">
View more service
<h2 className="font-unbounded sm:text-3xl text-xl font-semibold font-armanai text-white">
{t("home.services.viewMoreServices")}
</h2>
<Link
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>
</div>
</div>

View File

@@ -1,24 +1,28 @@
import { Counter } from "@/components/Counter";
import { useTranslations } from "next-intl";
export function Statistics() {
const t = useTranslations();
const stats = [
{
number: '25',
symbol: '+',
label: 'Years Experience',
number: "25",
symbol: "+",
label: t("home.statistics.experience"),
},
{
number: '450',
symbol: '+',
label: 'Families Saved',
number: "450",
symbol: "+",
label: t("home.statistics.projectsCompleted"),
},
{
number: '99',
symbol: '+',
label: 'Trained Staff',
number: "99",
symbol: "+",
label: t("home.statistics.trainedSpecialists"),
},
{
number: '93',
symbol: '%',
label: 'Trusted Clients',
number: "93",
symbol: "%",
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="grid grid-cols-2 md:grid-cols-4 gap-8 lg:gap-12">
{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 */}
<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">
{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 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-red-600">{stat.symbol}</span>
</div>
{/* 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}
</p>
</div>

View File

@@ -5,36 +5,7 @@ import { Autoplay } from "swiper/modules";
import Image from "next/image";
import "swiper/css";
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
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,
},
];
import { useTranslations } from "next-intl";
function StarRating({ rating }: { rating: number }) {
return (
@@ -53,6 +24,33 @@ function StarRating({ rating }: { rating: number }) {
}
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 (
<section className="w-full bg-[#1a1a1a]">
<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="flex items-center gap-2">
<DotAnimatsiya />
<span className="text-white font-semibold text-sm tracking-wider">
TESTIMONIALS
<span className="font-unbounded text-white font-semibold text-sm tracking-wider">
{t("home.testimonials.title")}
</span>
</div>
</div>
@@ -108,7 +106,7 @@ export function Testimonial() {
<SwiperSlide key={testimonial.id}>
<div className="space-y-6">
{/* 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}"
</p>
@@ -123,10 +121,10 @@ export function Testimonial() {
/>
</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}
</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}
</p>
</div>

View File

@@ -1,15 +1,27 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import HomeMarquee from "@/components/HomeMarquee";
import { Play } from "lucide-react";
import { useTranslations } from "next-intl";
export function Video() {
const t = useTranslations();
return (
<>
<div
className="bg-[#1e1d1c] bg-fixed max-sm:bg-center"
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>

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function ProductBanner() {
const t = useTranslations();
return (
<section className="relative w-full h-[60vh] min-h-100 overflow-hidden pt-10">
{/* Background Image */}
@@ -24,17 +26,21 @@ export function ProductBanner() {
<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="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
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"
className="font-unbounded uppercase bg-linear-to-br from-white via-white to-black
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>
</div>
<div className="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
servants but as a vital part of the community.
<div className="font-almarai lg:w-[40%] text-gray-300 lg:mt-20 md:mt-10 sm:mt-5 ">
{t("products.banner.description")}
</div>
</div>
</div>

View File

@@ -36,7 +36,7 @@ export default function ProductCard({
<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 ">
{/* 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
src={image || "/placeholder.svg"}
alt={title}

View File

@@ -1,6 +1,8 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { useTranslations } from "next-intl";
export function ServiceBanner() {
const t = useTranslations();
return (
<section className="relative w-full h-[60vh] min-h-100 overflow-hidden pt-10">
{/* Background Image */}
@@ -24,17 +26,21 @@ export function ServiceBanner() {
<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="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
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"
>
GENERAL <br /> QUESTIONS
{t("services.banner.subtitle")}
</p>
</div>
<div className="lg:w-[40%] text-gray-300 mt-10 text-lg">
Always Ready, Always On Time: Rescuing Lives represents an
unwavering commitment to emergency.
<div className="font-almarai lg:w-[40%] text-gray-300 mt-10 text-lg">
{t("services.banner.description")}
</div>
</div>
</div>

View File

@@ -1,8 +1,36 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import FAQAccordion from "../faq/faqAccardion";
import { faqItems } from "@/lib/demoData";
import { useTranslations } from "next-intl";
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 (
<div className="bg-[#1e1d1c] py-20 pb-50 space-y-8">
{/* 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 = [
{
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": {
"banner": {
"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.",
"cta": "Get Started"
},
@@ -150,7 +150,8 @@
"title": "Products",
"subtitle": "Ignum Technology Ready",
"description": "We not only supply equipment but become a successful partner for every client."
}
},
"ourproducts": "Our Products"
},
"faq": {
"banner": {
@@ -158,28 +159,31 @@
"subtitle": "General Questions",
"topic": "ABOUT WORK"
},
"questions": [
{
"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."
},
{
"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."
},
{
"question": "How is maintenance carried out?",
"answer": "We offer regular maintenance and 24/7 emergency service. Preventive inspections are conducted every six months."
},
{
"question": "What is the warranty period?",
"answer": "All our equipment comes with a 2-year warranty and 10-year maintenance guarantee."
},
{
"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."
}
]
"question1": {
"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."
},
"question2": {
"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."
},
"question3": {
"question": "How is maintenance carried out?",
"answer": "We offer regular maintenance and 24/7 emergency service. Preventive inspections are conducted every six months."
},
"question4": {
"question": "What is the warranty period?",
"answer": "All our equipment comes with a 2-year warranty and 10-year maintenance guarantee."
},
"question5": {
"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."
},
"ask": {
"question": "Do you still have questions?",
"subtitle": "Contact us and our specialists will answer you",
"btn": "Ask Question"
}
},
"services": {
"banner": {
@@ -213,5 +217,11 @@
"contact": "Contact",
"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": "Продукты",
"subtitle": "Технология Ignum Готова",
"description": "Мы не просто поставляем оборудование, мы становимся успешным партнером для каждого клиента."
}
},
"ourproducts": "Наши продукты"
},
"faq": {
"banner": {
@@ -158,28 +159,31 @@
"subtitle": "Общие Вопросы",
"topic": "О РАБОТЕ"
},
"questions": [
{
"question": "Как выбрать систему пожарной сигнализации?",
"answer": "В зависимости от площади, типа и особенностей объекта наши специалисты предложат вам наиболее оптимальное решение. Вы можете получить бесплатную консультацию."
},
{
"question": "Сколько времени занимает установка оборудования?",
"answer": "Время установки зависит от объема проекта. Обычно для небольших объектов требуется 1-3 дня, для крупных объектов - 1-2 недели."
},
{
"question": "Как проводится техническое обслуживание?",
"answer": "Мы предлагаем регулярное техническое обслуживание и экстренную службу 24/7. Профилактические проверки проводятся каждые шесть месяцев."
},
{
"question": "Какой срок гарантии?",
"answer": "На все наше оборудование предоставляется 2-летняя гарантия и 10-летняя гарантия на техническое обслуживание."
},
{
"question": "Для каких объектов подходят?",
"answer": "У нас есть подходящие решения для офисных зданий, складов, промышленных предприятий, торговых центров, жилых зданий и всех видов общественных мест."
}
]
"question1": {
"question": "Как выбрать систему пожарной сигнализации?",
"answer": "В зависимости от площади, типа и особенностей объекта наши специалисты предложат вам наиболее оптимальное решение. Вы можете получить бесплатную консультацию."
},
"question2": {
"question": "Сколько времени занимает установка оборудования?",
"answer": "Время установки зависит от объема проекта. Обычно для небольших объектов требуется 1-3 дня, для крупных объектов - 1-2 недели."
},
"question3": {
"question": "Как проводится техническое обслуживание?",
"answer": "Мы предлагаем регулярное техническое обслуживание и экстренную службу 24/7. Профилактические проверки проводятся каждые шесть месяцев."
},
"question4": {
"question": "Какой срок гарантии?",
"answer": "На все наше оборудование предоставляется 2-летняя гарантия и 10-летняя гарантия на техническое обслуживание."
},
"question5": {
"question": "Для каких объектов подходят?",
"answer": "У нас есть подходящие решения для офисных зданий, складов, промышленных предприятий, торговых центров, жилых зданий и всех видов общественных мест."
},
"ask": {
"question": "Остались вопросы?",
"subtitle": "Свяжитесь с нами, и наши специалисты ответят вам",
"btn": "Задать вопрос"
}
},
"services": {
"banner": {
@@ -213,5 +217,11 @@
"contact": "Контакты",
"help": "Помощь"
}
}
}
},
"rasmlar": "Изображения",
"fotogalereya": "Фотогалерея",
"contactTitle": "Отправьте нам свой номер",
"contactSubTitle": "Наши сотрудники свяжутся с вами",
"enterPhone": "Введите ваш номер телефона",
"send": "Отправить"
}

View File

@@ -2,7 +2,7 @@
"home": {
"banner": {
"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.",
"cta": "Boshlash"
},
@@ -14,17 +14,17 @@
},
"about": {
"title": "Biz haqimizda",
"subtitle": "Yong'inga Qarshi Tizimlar Tayyor",
"subtitle": "YONG'INGA QARSHI TIZIMLAR TAYYOR",
"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."
},
"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."
},
"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."
},
"award": "Eng Yaxshi Yong'in Himoyasi 2025"
@@ -53,7 +53,7 @@
}
},
"viewMoreServices": "Ko'proq Xizmatlar",
"viewMore": "Ko'proq"
"viewMore": "Ko'proq ko'rish"
},
"testimonials": {
"title": "Mijozlar Fikrlari",
@@ -80,7 +80,7 @@
},
"blog": {
"title": "Blog & Maqolalar",
"subtitle": "SO'NGI BLOG & YANGILIKLAR",
"subtitle": "BLOG & SO'NGI YANGILIKLAR",
"articles": {
"article1": "YONG'IN HIMOYASI TIZIMLARI: QANDAY ISHLAYDI?",
"article2": "YANGI TEXNOLOGIYALAR: AQLLI O'CHIRISH TIZIMLARI",
@@ -150,7 +150,8 @@
"title": "Mahsulotlar",
"subtitle": "Ignum Texnologiyasi Tayyor",
"description": "Biz nafaqat uskunalar yetkazib beramiz, balki har bir mijozning muvaffaqiyatli hamkoriga aylanamiz."
}
},
"ourproducts": "Bizning mahsulotlarimiz"
},
"faq": {
"banner": {
@@ -158,28 +159,31 @@
"subtitle": "Umumiy Savollar",
"topic": "ISH HAQIDA"
},
"questions": [
{
"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."
},
{
"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."
},
{
"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."
},
{
"question": "Kafolatiy muddati qancha?",
"answer": "Barcha uskunalarimiz uchun 2 yillik kafolat va 10 yillik texnik xizmat ko'rsatish kafolati beriladi."
},
{
"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."
}
]
"question1": {
"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."
},
"question2": {
"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."
},
"question3": {
"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."
},
"question4": {
"question": "Kafolatiy muddati qancha?",
"answer": "Barcha uskunalarimiz uchun 2 yillik kafolat va 10 yillik texnik xizmat ko'rsatish kafolati beriladi."
},
"question5": {
"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."
},
"ask": {
"question": "Hali ham savolingiz bormi?",
"subtitle": "Bizga bo'glaning va bizni hodilarimiz sizga javob berishadi",
"btn": "Savollingizni yo'llang"
}
},
"services": {
"banner": {
@@ -213,5 +217,11 @@
"contact": "Aloqa",
"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} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
images: {
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"
},
"dependencies": {
"@formatjs/intl-localematcher": "^0.8.0",
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
@@ -44,9 +45,12 @@
"cmdk": "1.0.4",
"date-fns": "4.1.0",
"embla-carousel-react": "8.5.1",
"framer-motion": "^12.29.2",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"negotiator": "^1.0.0",
"next": "16.0.10",
"next-intl": "^4.7.0",
"next-themes": "^0.4.6",
"react": "19.2.0",
"react-day-picker": "9.8.0",
@@ -64,6 +68,7 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.9",
"@types/negotiator": "^0.6.4",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",

486
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@formatjs/intl-localematcher':
specifier: ^0.8.0
version: 0.8.0
'@hookform/resolvers':
specifier: ^3.10.0
version: 3.10.0(react-hook-form@7.71.1(react@19.2.0))
@@ -113,15 +116,24 @@ importers:
embla-carousel-react:
specifier: 8.5.1
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:
specifier: 1.4.1
version: 1.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
lucide-react:
specifier: ^0.454.0
version: 0.454.0(react@19.2.0)
negotiator:
specifier: ^1.0.0
version: 1.0.0
next:
specifier: 16.0.10
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:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -168,6 +180,9 @@ importers:
'@tailwindcss/postcss':
specifier: ^4.1.9
version: 4.1.18
'@types/negotiator':
specifier: ^0.6.4
version: 0.6.4
'@types/node':
specifier: ^22
version: 22.19.7
@@ -221,6 +236,30 @@ packages:
'@floating-ui/utils@0.2.10':
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':
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
peerDependencies:
@@ -430,6 +469,88 @@ packages:
cpu: [x64]
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':
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
@@ -1086,9 +1207,87 @@ packages:
'@radix-ui/rect@1.1.0':
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':
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':
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
@@ -1204,6 +1403,9 @@ packages:
'@types/d3-timer@3.0.2':
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
'@types/negotiator@0.6.4':
resolution: {integrity: sha512-elf6BsTq+AkyNsb2h5cGNst2Mc7dPliVoAPm1fXglC/BM3f2pFA40BaSSv3E5lyHteEawVKLP+8TwiY1DMNb3A==}
'@types/node@22.19.7':
resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==}
@@ -1321,6 +1523,9 @@ packages:
decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -1365,6 +1570,20 @@ packages:
fraction.js@5.3.4:
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:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
@@ -1382,6 +1601,17 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
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:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
@@ -1474,11 +1704,34 @@ packages:
magic-string@0.30.21:
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:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
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:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
@@ -1506,6 +1759,9 @@ packages:
sass:
optional: true
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
@@ -1516,6 +1772,13 @@ packages:
picocolors@1.1.1:
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:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -1711,6 +1974,11 @@ packages:
'@types/react':
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:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
@@ -1768,6 +2036,45 @@ snapshots:
'@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))':
dependencies:
react-hook-form: 7.71.1(react@19.2.0)
@@ -1914,6 +2221,66 @@ snapshots:
'@next/swc-win32-x64-msvc@16.0.10':
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/primitive@1.1.1': {}
@@ -2606,10 +2973,64 @@ snapshots:
'@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':
dependencies:
tslib: 2.8.1
'@swc/types@0.1.25':
dependencies:
'@swc/counter': 0.1.3
'@tailwindcss/node@4.1.18':
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -2703,6 +3124,8 @@ snapshots:
'@types/d3-timer@3.0.2': {}
'@types/negotiator@0.6.4': {}
'@types/node@22.19.7':
dependencies:
undici-types: 6.21.0
@@ -2813,6 +3236,8 @@ snapshots:
decimal.js-light@2.5.1: {}
decimal.js@10.6.0: {}
detect-libc@2.1.2: {}
detect-node-es@1.1.0: {}
@@ -2849,6 +3274,15 @@ snapshots:
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: {}
graceful-fs@4.2.11: {}
@@ -2860,6 +3294,19 @@ snapshots:
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: {}
js-tokens@4.0.0: {}
@@ -2927,8 +3374,34 @@ snapshots:
dependencies:
'@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: {}
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):
dependencies:
react: 19.2.0
@@ -2957,12 +3430,18 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
node-addon-api@7.1.1: {}
node-releases@2.0.27: {}
object-assign@4.1.1: {}
picocolors@1.1.1: {}
picomatch@4.0.3: {}
po-parser@2.1.1: {}
postcss-value-parser@4.2.0: {}
postcss@8.4.31:
@@ -3162,6 +3641,13 @@ snapshots:
optionalDependencies:
'@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):
dependencies:
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