updated site first commit

This commit is contained in:
Filmon24 2026-03-24 12:18:39 +03:00
parent 386d3dd4cf
commit 85cddaf9a6
21 changed files with 343 additions and 386 deletions

2
next-env.d.ts vendored
View File

@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

149
package-lock.json generated
View File

@ -598,9 +598,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -617,9 +614,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -636,9 +630,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -655,9 +646,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -674,9 +662,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -693,9 +678,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -712,9 +694,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -731,9 +710,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@ -750,9 +726,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -775,9 +748,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -800,9 +770,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -825,9 +792,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -850,9 +814,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -875,9 +836,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -900,9 +858,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -925,9 +880,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -1119,9 +1071,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1138,9 +1087,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1157,9 +1103,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1176,9 +1119,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1382,9 +1322,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1405,9 +1342,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1428,9 +1362,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1451,9 +1382,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1474,9 +1402,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1497,9 +1422,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1657,9 +1579,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1676,9 +1595,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1695,9 +1611,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1714,9 +1627,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1933,9 +1843,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1953,9 +1860,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1973,9 +1877,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1993,9 +1894,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2510,9 +2408,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2527,9 +2422,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2544,9 +2436,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2561,9 +2450,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2578,9 +2464,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2595,9 +2478,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2612,9 +2492,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -2629,9 +2506,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -4964,9 +4838,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -4988,9 +4859,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -5012,9 +4880,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -5036,9 +4901,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -5365,6 +5227,17 @@
} }
} }
}, },
"node_modules/next-intl/node_modules/@swc/helpers": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
"license": "Apache-2.0",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",

View File

@ -12,7 +12,7 @@ export default async function CartPage() {
return ( return (
<div className="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8"> <div className="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold tracking-tight text-stone-900"> <h1 className="text-3xl font-semibold tracking-tight text-neutral-900">
{t("title")} {t("title")}
</h1> </h1>
<div className="mt-8"> <div className="mt-8">

View File

@ -11,10 +11,10 @@ export default async function CheckoutPage() {
return ( return (
<div className="mx-auto max-w-2xl px-4 py-10 sm:px-6 lg:px-8"> <div className="mx-auto max-w-2xl px-4 py-10 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold tracking-tight text-stone-900"> <h1 className="text-3xl font-semibold tracking-tight text-neutral-900">
{t("title")} {t("title")}
</h1> </h1>
<p className="mt-2 text-stone-600">{t("subtitle")}</p> <p className="mt-2 text-neutral-600">{t("subtitle")}</p>
<div className="mt-8"> <div className="mt-8">
<CheckoutForm /> <CheckoutForm />
</div> </div>

View File

@ -6,10 +6,10 @@ export default async function NotFound() {
return ( return (
<div className="mx-auto flex max-w-lg flex-col items-center px-4 py-24 text-center"> <div className="mx-auto flex max-w-lg flex-col items-center px-4 py-24 text-center">
<h1 className="text-2xl font-bold text-stone-900">{t("notFound")}</h1> <h1 className="text-2xl font-semibold text-neutral-900">{t("notFound")}</h1>
<Link <Link
href="/catalog" href="/catalog"
className="mt-6 rounded-xl bg-blue-600 px-5 py-3 text-sm font-semibold text-white hover:bg-blue-700" className="mt-6 rounded-lg bg-neutral-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800"
> >
{t("back")} {t("back")}
</Link> </Link>

View File

@ -11,31 +11,31 @@ export default async function HomePage() {
<section className="relative overflow-hidden border-b border-neutral-200/90 bg-gradient-to-b from-white to-neutral-50"> <section className="relative overflow-hidden border-b border-neutral-200/90 bg-gradient-to-b from-white to-neutral-50">
<div className="mx-auto grid max-w-[1440px] gap-10 px-4 py-14 sm:px-6 lg:grid-cols-2 lg:items-center lg:py-20 lg:px-8"> <div className="mx-auto grid max-w-[1440px] gap-10 px-4 py-14 sm:px-6 lg:grid-cols-2 lg:items-center lg:py-20 lg:px-8">
<div> <div>
<p className="text-xs font-bold uppercase tracking-widest text-neutral-500"> <p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("hero.eyebrow")} {t("hero.eyebrow")}
</p> </p>
<h1 className="mt-3 text-3xl font-bold tracking-tight text-stone-900 sm:text-4xl lg:text-5xl"> <h1 className="mt-3 text-3xl font-semibold tracking-tight text-neutral-900 sm:text-4xl lg:text-5xl">
{t("hero.title")} {t("hero.title")}
</h1> </h1>
<p className="mt-4 text-base leading-relaxed text-stone-600 sm:text-lg"> <p className="mt-4 text-base leading-relaxed text-neutral-600 sm:text-lg">
{t("hero.subtitle")} {t("hero.subtitle")}
</p> </p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row"> <div className="mt-8 flex flex-col gap-3 sm:flex-row">
<Link <Link
href="/catalog" href="/catalog"
className="inline-flex items-center justify-center rounded-full bg-blue-600 px-5 py-3.5 text-sm font-bold text-white shadow-lg shadow-blue-600/20 hover:bg-blue-700" className="inline-flex items-center justify-center rounded-lg bg-neutral-900 px-5 py-3.5 text-sm font-semibold text-white transition hover:bg-neutral-800"
> >
{t("hero.ctaCatalog")} {t("hero.ctaCatalog")}
</Link> </Link>
<Link <Link
href="/checkout" href="/checkout"
className="inline-flex items-center justify-center rounded-full border border-neutral-300 bg-white px-5 py-3.5 text-sm font-semibold text-neutral-800 shadow-sm hover:border-neutral-400 hover:text-neutral-950" className="inline-flex items-center justify-center rounded-lg border border-neutral-200 bg-white px-5 py-3.5 text-sm font-semibold text-neutral-600 transition hover:border-neutral-300 hover:text-neutral-900"
> >
{t("hero.ctaQuote")} {t("hero.ctaQuote")}
</Link> </Link>
</div> </div>
</div> </div>
<div className="relative aspect-[4/3] overflow-hidden rounded-2xl bg-[#e2e8f0] shadow-xl ring-1 ring-neutral-200/90"> <div className="relative aspect-[4/3] overflow-hidden rounded-lg bg-[#e2e8f0] border border-neutral-200">
<Image <Image
src="/metal/hero-stack.svg" src="/metal/hero-stack.svg"
alt={th("stackAlt")} alt={th("stackAlt")}
@ -51,17 +51,17 @@ export default async function HomePage() {
<section <section
id="services" id="services"
className="border-y border-stone-200/80 bg-stone-50/80" className="border-y border-neutral-200/80 bg-neutral-50/80"
aria-labelledby="services-heading" aria-labelledby="services-heading"
> >
<div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8"> <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8">
<h2 <h2
id="services-heading" id="services-heading"
className="text-2xl font-bold text-stone-900 sm:text-3xl" className="text-2xl font-semibold text-neutral-900 sm:text-3xl"
> >
{t("services.title")} {t("services.title")}
</h2> </h2>
<p className="mt-2 max-w-2xl text-stone-600">{t("services.subtitle")}</p> <p className="mt-2 max-w-2xl text-neutral-600">{t("services.subtitle")}</p>
<ul className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <ul className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{( {(
[ [
@ -75,17 +75,44 @@ export default async function HomePage() {
).map((key) => ( ).map((key) => (
<li <li
key={key} key={key}
className="rounded-2xl bg-white p-5 shadow-sm ring-1 ring-stone-200/80" className="rounded-lg bg-white p-5 border border-neutral-100"
> >
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-600 text-white"> <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-neutral-900 text-white">
<span className="text-lg font-black" aria-hidden> {key === "cut" && (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
</span> <path strokeLinecap="round" strokeLinejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 11-4.243 4.243 3 3 0 014.243-4.243zm0-5.758a3 3 0 11-4.243-4.243 3 3 0 014.243 4.243z" />
</svg>
)}
{key === "coating" && (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
</svg>
)}
{key === "docs" && (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
)}
{key === "delivery" && (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M13 16V6a1 1 0 00-1-1H4a1 1 0 00-1 1v10a1 1 0 001 1h1m8-1a1 1 0 01-1 1H9m4-1V8a1 1 0 011-1h2.586a1 1 0 01.707.293l3.414 3.414a1 1 0 01.293.707V16a1 1 0 01-1 1h-1m-6-1a1 1 0 001 1h1M5 17a2 2 0 104 0m-4 0m6 0a2 2 0 104 0m-4 0a2 2 0 114 0" />
</svg>
)}
{key === "sizing" && (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
)}
{key === "bulk" && (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
)}
</div> </div>
<h3 className="mt-4 text-lg font-semibold text-stone-900"> <h3 className="mt-4 text-lg font-semibold text-neutral-900">
{t(`services.${key}.title`)} {t(`services.${key}.title`)}
</h3> </h3>
<p className="mt-2 text-sm leading-relaxed text-stone-600"> <p className="mt-2 text-sm leading-relaxed text-neutral-600">
{t(`services.${key}.body`)} {t(`services.${key}.body`)}
</p> </p>
</li> </li>
@ -96,23 +123,23 @@ export default async function HomePage() {
<section className="bg-white"> <section className="bg-white">
<div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8"> <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-stone-900 sm:text-3xl"> <h2 className="text-2xl font-semibold text-neutral-900 sm:text-3xl">
{t("how.title")} {t("how.title")}
</h2> </h2>
<p className="mt-2 text-stone-600">{t("how.subtitle")}</p> <p className="mt-2 text-neutral-600">{t("how.subtitle")}</p>
<ol className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-4"> <ol className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{([1, 2, 3, 4] as const).map((n) => ( {([1, 2, 3, 4] as const).map((n) => (
<li <li
key={n} key={n}
className="relative rounded-2xl border border-stone-200 bg-stone-50/80 p-5" className="relative rounded-lg border border-neutral-100 bg-neutral-50/80 p-5"
> >
<span className="text-3xl font-black text-orange-500/30"> <span className="text-3xl font-semibold text-neutral-300">
{n} {n}
</span> </span>
<h3 className="mt-2 font-semibold text-stone-900"> <h3 className="mt-2 font-semibold text-neutral-900">
{t(`how.step${n}.title` as "how.step1.title")} {t(`how.step${n}.title` as "how.step1.title")}
</h3> </h3>
<p className="mt-2 text-sm text-stone-600"> <p className="mt-2 text-sm text-neutral-600">
{t(`how.step${n}.body` as "how.step1.body")} {t(`how.step${n}.body` as "how.step1.body")}
</p> </p>
</li> </li>
@ -123,28 +150,28 @@ export default async function HomePage() {
<section <section
id="faq" id="faq"
className="border-t border-stone-200/80 bg-stone-50/80" className="border-t border-neutral-100 bg-neutral-50/80"
aria-labelledby="faq-heading" aria-labelledby="faq-heading"
> >
<div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8"> <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8">
<div className="mx-auto max-w-3xl"> <div className="mx-auto max-w-3xl">
<h2 <h2
id="faq-heading" id="faq-heading"
className="text-2xl font-bold text-stone-900 sm:text-3xl" className="text-2xl font-semibold text-neutral-900 sm:text-3xl"
> >
{t("faq.title")} {t("faq.title")}
</h2> </h2>
<p className="mt-2 text-stone-600">{t("faq.subtitle")}</p> <p className="mt-2 text-neutral-600">{t("faq.subtitle")}</p>
<div className="mt-10 space-y-3"> <div className="mt-10 space-y-3">
{([1, 2, 3, 4, 5, 6] as const).map((n) => ( {([1, 2, 3, 4, 5, 6] as const).map((n) => (
<details <details
key={n} key={n}
className="group rounded-2xl border border-stone-200 bg-white px-4 py-1 shadow-sm ring-stone-200/80 open:ring-2 open:ring-blue-600/15" className="group rounded-lg border border-neutral-200 bg-white px-4 py-1"
> >
<summary className="flex cursor-pointer list-none items-center justify-between gap-3 py-3 text-left text-sm font-semibold text-stone-900 [&::-webkit-details-marker]:hidden"> <summary className="flex cursor-pointer list-none items-center justify-between gap-3 py-3 text-left text-sm font-semibold text-neutral-900 [&::-webkit-details-marker]:hidden">
<span>{t(`faq.i${n}.q` as "faq.i1.q")}</span> <span>{t(`faq.i${n}.q` as "faq.i1.q")}</span>
<svg <svg
className="faq-chevron h-5 w-5 shrink-0 text-stone-400 transition-transform duration-200" className="faq-chevron h-5 w-5 shrink-0 text-neutral-400 transition-transform duration-200"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="currentColor" fill="currentColor"
aria-hidden aria-hidden
@ -156,7 +183,7 @@ export default async function HomePage() {
/> />
</svg> </svg>
</summary> </summary>
<p className="border-t border-stone-100 pb-4 pt-3 text-sm leading-relaxed text-stone-600"> <p className="border-t border-neutral-100 pb-4 pt-3 text-sm leading-relaxed text-neutral-600">
{t(`faq.i${n}.a` as "faq.i1.a")} {t(`faq.i${n}.a` as "faq.i1.a")}
</p> </p>
</details> </details>
@ -167,21 +194,21 @@ export default async function HomePage() {
</section> </section>
<section className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8"> <section className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:px-8">
<div className="overflow-hidden rounded-3xl bg-blue-600 px-6 py-10 text-center text-white shadow-xl shadow-blue-600/25 sm:px-12"> <div className="overflow-hidden rounded-lg bg-neutral-900 px-6 py-10 text-center text-white sm:px-12">
<h2 className="text-2xl font-bold sm:text-3xl">{t("ctaBand.title")}</h2> <h2 className="text-2xl font-semibold sm:text-3xl">{t("ctaBand.title")}</h2>
<p className="mx-auto mt-3 max-w-xl text-sm text-blue-100 sm:text-base"> <p className="mx-auto mt-3 max-w-xl text-sm text-neutral-400 sm:text-base">
{t("ctaBand.body")} {t("ctaBand.body")}
</p> </p>
<div className="mt-8 flex flex-col justify-center gap-3 sm:flex-row"> <div className="mt-8 flex flex-col justify-center gap-3 sm:flex-row">
<Link <Link
href="/checkout" href="/checkout"
className="inline-flex justify-center rounded-xl bg-orange-500 px-6 py-3.5 text-sm font-bold text-white hover:bg-orange-600" className="inline-flex justify-center rounded-lg bg-white px-6 py-3.5 text-sm font-semibold text-neutral-900 transition hover:bg-neutral-100"
> >
{t("ctaBand.primary")} {t("ctaBand.primary")}
</Link> </Link>
<a <a
href={`tel:${(process.env.NEXT_PUBLIC_CONTACT_PHONE ?? "+251911000000").replace(/\s/g, "")}`} href={`tel:${(process.env.NEXT_PUBLIC_CONTACT_PHONE ?? "+251911000000").replace(/\s/g, "")}`}
className="inline-flex justify-center rounded-xl border border-white/40 bg-white/10 px-6 py-3.5 text-sm font-semibold text-white backdrop-blur hover:bg-white/20" className="inline-flex justify-center rounded-lg border border-white/20 bg-white/10 px-6 py-3.5 text-sm font-semibold text-white transition hover:bg-white/20"
> >
{t("ctaBand.secondary")} {t("ctaBand.secondary")}
</a> </a>

View File

@ -39,7 +39,7 @@ export default async function ProductPage({ params }: Props) {
<div className="mx-auto max-w-[1440px] px-4 py-10 sm:px-6 lg:px-8"> <div className="mx-auto max-w-[1440px] px-4 py-10 sm:px-6 lg:px-8">
<Link <Link
href="/catalog" href="/catalog"
className="text-sm font-medium text-blue-600 hover:text-blue-700" className="text-sm font-medium text-neutral-500 transition hover:text-neutral-900"
> >
{t("back")} {t("back")}
</Link> </Link>
@ -47,8 +47,8 @@ export default async function ProductPage({ params }: Props) {
<div className="mt-8 grid gap-10 lg:grid-cols-2 lg:gap-16"> <div className="mt-8 grid gap-10 lg:grid-cols-2 lg:gap-16">
<div> <div>
<ProductGallery slug={slug} sources={product.gallery} /> <ProductGallery slug={slug} sources={product.gallery} />
<div className="mt-4"> <div className="mt-6">
<p className="text-xs font-bold uppercase tracking-wider text-neutral-500"> <p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("thicknessLabel")} {t("thicknessLabel")}
</p> </p>
<div <div
@ -60,7 +60,7 @@ export default async function ProductPage({ params }: Props) {
<span <span
key={mm} key={mm}
role="listitem" role="listitem"
className="inline-flex rounded-full bg-neutral-100 px-3 py-1.5 text-sm font-semibold tabular-nums text-neutral-800 ring-1 ring-neutral-200/90" className="inline-flex rounded-md border border-neutral-200 bg-neutral-50 px-3 py-1.5 text-sm font-semibold tabular-nums text-neutral-800"
> >
{t("thicknessValue", { {t("thicknessValue", {
value: formatThicknessMm(mm), value: formatThicknessMm(mm),
@ -72,13 +72,13 @@ export default async function ProductPage({ params }: Props) {
</div> </div>
<div> <div>
<p className="text-[10px] font-semibold uppercase tracking-[0.16em] text-neutral-500"> <p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{tcl(product.catalogLineKey)} {tcl(product.catalogLineKey)}
</p> </p>
<p className="mt-2 text-sm font-semibold text-neutral-900"> <p className="mt-2 text-sm font-semibold text-neutral-900">
{tn("supplier")} {tn("supplier")}
</p> </p>
<h1 className="mt-1 text-2xl font-normal leading-tight tracking-tight text-neutral-700 sm:text-3xl lg:text-4xl"> <h1 className="mt-1 text-2xl font-semibold leading-tight tracking-tight text-neutral-900 sm:text-3xl lg:text-4xl">
{tp(`${slug}.name`)} {tp(`${slug}.name`)}
</h1> </h1>
<p className="mt-4 text-base leading-relaxed text-neutral-600"> <p className="mt-4 text-base leading-relaxed text-neutral-600">
@ -95,8 +95,8 @@ export default async function ProductPage({ params }: Props) {
<ProductAddToCart product={product} /> <ProductAddToCart product={product} />
</div> </div>
<div className="mt-8 rounded-2xl border border-neutral-200 bg-neutral-50/80 p-5"> <div className="mt-8 rounded-lg border border-neutral-100 bg-neutral-50/80 p-5">
<h2 className="text-xs font-semibold uppercase tracking-wider text-neutral-500"> <h2 className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("details")} {t("details")}
</h2> </h2>
<p className="mt-2 text-sm leading-relaxed text-neutral-600"> <p className="mt-2 text-sm leading-relaxed text-neutral-600">

View File

@ -11,7 +11,7 @@ const dmSans = DM_Sans({
export default function RootLayout({ children }: { children: ReactNode }) { export default function RootLayout({ children }: { children: ReactNode }) {
return ( return (
<html lang="en" className={dmSans.variable} suppressHydrationWarning> <html lang="en" className={dmSans.variable} suppressHydrationWarning>
<body className="min-h-screen bg-stone-50 font-sans text-stone-900 antialiased"> <body className="min-h-screen bg-neutral-50 font-sans text-neutral-900 antialiased">
{children} {children}
</body> </body>
</html> </html>

View File

@ -9,7 +9,7 @@ export function CartBadge() {
if (count === 0) return null; if (count === 0) return null;
return ( return (
<span className="absolute -right-1.5 -top-1.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-orange-500 px-1 text-[10px] font-bold text-white shadow-sm"> <span className="absolute -right-1.5 -top-1.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-neutral-900 px-1 text-[10px] font-semibold text-white">
{count > 99 ? "99+" : count} {count > 99 ? "99+" : count}
</span> </span>
); );

View File

@ -12,7 +12,7 @@ export function CartCheckoutLink() {
<div className="mt-8 flex justify-end"> <div className="mt-8 flex justify-end">
<Link <Link
href="/checkout" href="/checkout"
className="inline-flex rounded-xl bg-orange-500 px-6 py-3 text-sm font-bold text-white shadow-sm hover:bg-orange-600" className="inline-flex rounded-lg bg-neutral-900 px-6 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800"
> >
{t("checkout")} {t("checkout")}
</Link> </Link>

View File

@ -17,11 +17,11 @@ export function CartView() {
if (items.length === 0) { if (items.length === 0) {
return ( return (
<div className="mx-auto max-w-lg rounded-2xl bg-white p-10 text-center shadow-sm ring-1 ring-stone-200/80"> <div className="mx-auto max-w-lg rounded-lg border border-neutral-100 bg-white p-10 text-center">
<p className="text-stone-600">{t("empty")}</p> <p className="text-neutral-600">{t("empty")}</p>
<Link <Link
href="/catalog" href="/catalog"
className="mt-6 inline-flex rounded-xl bg-blue-600 px-5 py-3 text-sm font-semibold text-white hover:bg-blue-700" className="mt-6 inline-flex rounded-lg bg-neutral-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800"
> >
{t("emptyCta")} {t("emptyCta")}
</Link> </Link>
@ -79,13 +79,13 @@ function CartLineRow({
const lineTotal = price * line.quantity; const lineTotal = price * line.quantity;
return ( return (
<li className="flex flex-col gap-4 rounded-2xl bg-white p-4 shadow-sm ring-1 ring-stone-200/80 sm:flex-row sm:items-center sm:justify-between"> <li className="flex flex-col gap-4 rounded-lg border border-neutral-100 bg-white p-4 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="font-semibold text-stone-900">{name}</p> <p className="font-semibold text-neutral-900">{name}</p>
{sub ? ( {sub ? (
<p className="mt-1 text-sm text-stone-500">{sub}</p> <p className="mt-1 text-sm text-neutral-500">{sub}</p>
) : null} ) : null}
<p className="mt-2 text-sm text-stone-600"> <p className="mt-2 text-sm text-neutral-600">
{price.toFixed(2)}{" "} {price.toFixed(2)}{" "}
{product ? tu(product.unitKey) : ""} {t("each")} {product ? tu(product.unitKey) : ""} {t("each")}
</p> </p>
@ -103,9 +103,9 @@ function CartLineRow({
onChange={(e) => onChange={(e) =>
updateQuantity(line.lineId, Number(e.target.value) || 1) updateQuantity(line.lineId, Number(e.target.value) || 1)
} }
className="w-20 rounded-lg border border-stone-200 px-2 py-1.5 text-sm font-semibold" className="w-20 rounded-lg border border-neutral-200 bg-neutral-50 px-2 py-1.5 text-sm font-semibold outline-none transition duration-150 focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8"
/> />
<p className="text-sm font-bold text-orange-500"> <p className="text-sm font-semibold text-neutral-900">
{lineTotal.toFixed(2)} {lineTotal.toFixed(2)}
</p> </p>
<button <button

View File

@ -97,7 +97,7 @@ export function CatalogExplorer({
const filterPanel = ( const filterPanel = (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<p className="text-xs font-semibold uppercase tracking-wider text-neutral-500"> <p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("filterCategory")} {t("filterCategory")}
</p> </p>
<ul className="mt-3 space-y-1"> <ul className="mt-3 space-y-1">
@ -109,10 +109,10 @@ export function CatalogExplorer({
setCat(c); setCat(c);
setFiltersOpen(false); setFiltersOpen(false);
}} }}
className={`flex w-full rounded-xl px-3 py-2 text-left text-sm font-medium transition ${ className={`flex w-full rounded-lg px-3 py-2 text-left text-sm font-medium transition ${
cat === c cat === c
? "bg-blue-600 text-white" ? "bg-neutral-900 text-white"
: "text-neutral-700 hover:bg-neutral-100" : "text-neutral-500 hover:bg-neutral-50 hover:text-neutral-900"
}`} }`}
> >
{c === "all" ? t("all") : tc(c)} {c === "all" ? t("all") : tc(c)}
@ -123,13 +123,13 @@ export function CatalogExplorer({
</div> </div>
<div> <div>
<p className="text-xs font-semibold uppercase tracking-wider text-neutral-500"> <p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("filterSort")} {t("filterSort")}
</p> </p>
<select <select
value={sort} value={sort}
onChange={(e) => setSort(e.target.value as SortKey)} onChange={(e) => setSort(e.target.value as SortKey)}
className="mt-3 w-full rounded-xl border border-neutral-200 bg-white px-3 py-2.5 text-sm font-medium text-neutral-900 outline-none focus:border-blue-600 focus:ring-2 focus:ring-blue-600/20" className="mt-3 w-full rounded-lg border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm font-medium text-neutral-900 outline-none transition duration-150 focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8"
> >
<option value="name-asc">{t("sortNameAsc")}</option> <option value="name-asc">{t("sortNameAsc")}</option>
<option value="name-desc">{t("sortNameDesc")}</option> <option value="name-desc">{t("sortNameDesc")}</option>
@ -138,7 +138,7 @@ export function CatalogExplorer({
</select> </select>
</div> </div>
<div className="rounded-2xl border border-neutral-200 bg-neutral-50/80 p-4"> <div className="rounded-lg border border-neutral-100 bg-neutral-50/80 p-4">
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<span className="text-sm font-medium text-neutral-800"> <span className="text-sm font-medium text-neutral-800">
{t("galvToggle")} {t("galvToggle")}
@ -150,11 +150,11 @@ export function CatalogExplorer({
aria-label={t("galvToggle")} aria-label={t("galvToggle")}
onClick={() => setPreferGalv((v) => !v)} onClick={() => setPreferGalv((v) => !v)}
className={`relative h-7 w-12 shrink-0 rounded-full transition-colors ${ className={`relative h-7 w-12 shrink-0 rounded-full transition-colors ${
preferGalv ? "bg-blue-600" : "bg-neutral-300" preferGalv ? "bg-neutral-900" : "bg-neutral-300"
}`} }`}
> >
<span <span
className={`absolute top-1 h-5 w-5 rounded-full bg-white shadow transition-all ${ className={`absolute top-1 h-5 w-5 rounded-full bg-white transition-all ${
preferGalv ? "left-[calc(100%-1.25rem-0.25rem)]" : "left-1" preferGalv ? "left-[calc(100%-1.25rem-0.25rem)]" : "left-1"
}`} }`}
/> />
@ -165,7 +165,7 @@ export function CatalogExplorer({
</p> </p>
</div> </div>
<div className="rounded-2xl border border-neutral-200 bg-white p-4"> <div className="rounded-lg border border-neutral-100 bg-white p-4">
<p className="text-sm font-semibold text-neutral-900">{t("sidebarDocs")}</p> <p className="text-sm font-semibold text-neutral-900">{t("sidebarDocs")}</p>
<p className="mt-2 text-xs leading-relaxed text-neutral-600"> <p className="mt-2 text-xs leading-relaxed text-neutral-600">
{t("sidebarDocsBody")} {t("sidebarDocsBody")}
@ -195,7 +195,7 @@ export function CatalogExplorer({
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
placeholder={tn("searchPlaceholder")} placeholder={tn("searchPlaceholder")}
className="w-full rounded-full border border-neutral-200 bg-white py-3 pl-11 pr-4 text-sm text-neutral-900 shadow-sm outline-none focus:border-blue-600 focus:ring-2 focus:ring-blue-600/20" className="w-full rounded-lg border border-neutral-200 bg-neutral-50 py-3 pl-11 pr-4 text-sm text-neutral-900 outline-none transition duration-150 placeholder:text-neutral-400 focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8"
/> />
<span className="pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400"> <span className="pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400">
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}> <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}>
@ -207,7 +207,7 @@ export function CatalogExplorer({
<div className="mt-8 flex flex-wrap items-center gap-3"> <div className="mt-8 flex flex-wrap items-center gap-3">
<button <button
type="button" type="button"
className="inline-flex items-center gap-2 rounded-full border border-neutral-300 bg-white px-4 py-2.5 text-sm font-semibold text-neutral-800 shadow-sm lg:hidden" className="inline-flex items-center gap-2 rounded-lg border border-neutral-200 bg-white px-4 py-2.5 text-sm font-semibold text-neutral-800 transition hover:border-neutral-300 lg:hidden"
onClick={() => setFiltersOpen(true)} onClick={() => setFiltersOpen(true)}
> >
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@ -222,10 +222,10 @@ export function CatalogExplorer({
key={c} key={c}
type="button" type="button"
onClick={() => setCat(c)} onClick={() => setCat(c)}
className={`shrink-0 rounded-full px-4 py-2 text-sm font-semibold transition ${ className={`shrink-0 rounded-md px-4 py-2 text-sm font-semibold transition ${
cat === c cat === c
? "bg-blue-600 text-white shadow-sm" ? "bg-neutral-900 text-white"
: "bg-white text-neutral-700 ring-1 ring-neutral-200 hover:bg-neutral-100" : "border border-neutral-200 bg-white text-neutral-500 hover:border-neutral-300 hover:text-neutral-900"
}`} }`}
> >
{c === "all" ? t("allShort") : tc(c)} {c === "all" ? t("allShort") : tc(c)}
@ -241,7 +241,7 @@ export function CatalogExplorer({
id="catalog-sort" id="catalog-sort"
value={sort} value={sort}
onChange={(e) => setSort(e.target.value as SortKey)} onChange={(e) => setSort(e.target.value as SortKey)}
className="rounded-full border border-neutral-200 bg-white py-2 pl-3 pr-8 text-sm font-medium text-neutral-900 outline-none focus:border-blue-600 focus:ring-2 focus:ring-blue-600/20" className="rounded-lg border border-neutral-200 bg-neutral-50 py-2 pl-3 pr-8 text-sm font-medium text-neutral-900 outline-none transition duration-150 focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8"
> >
<option value="name-asc">{t("sortNameAsc")}</option> <option value="name-asc">{t("sortNameAsc")}</option>
<option value="name-desc">{t("sortNameDesc")}</option> <option value="name-desc">{t("sortNameDesc")}</option>
@ -264,19 +264,19 @@ export function CatalogExplorer({
<article className="group flex h-full flex-col"> <article className="group flex h-full flex-col">
<Link <Link
href={`/product/${p.slug}`} href={`/product/${p.slug}`}
className="group relative block overflow-hidden rounded-2xl ring-1 ring-neutral-200/80" className="group relative block overflow-hidden rounded-lg border border-neutral-100"
> >
<div className="relative aspect-square"> <div className="relative aspect-square">
<MetalProfileVisual <MetalProfileVisual
src={p.gallery[0]} src={p.gallery[0]}
alt={tp(`${p.slug}.imageAlt`)} alt={tp(`${p.slug}.imageAlt`)}
className="h-full w-full rounded-2xl" className="h-full w-full rounded-lg"
/> />
</div> </div>
</Link> </Link>
<div className="mt-3 px-0.5"> <div className="mt-4 px-0.5">
<p className="text-[10px] font-bold uppercase tracking-wider text-neutral-500"> <p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("thicknessLabel")} {t("thicknessLabel")}
</p> </p>
<div <div
@ -288,7 +288,7 @@ export function CatalogExplorer({
<span <span
key={`${p.slug}-${mm}`} key={`${p.slug}-${mm}`}
role="listitem" role="listitem"
className="inline-flex rounded-full bg-neutral-100 px-2.5 py-1 text-xs font-semibold tabular-nums text-neutral-800 ring-1 ring-neutral-200/90" className="inline-flex rounded-md border border-neutral-200 bg-neutral-50 px-2.5 py-1 text-xs font-semibold tabular-nums text-neutral-800"
> >
{t("thicknessValue", { {t("thicknessValue", {
value: formatThicknessMm(mm), value: formatThicknessMm(mm),
@ -298,7 +298,7 @@ export function CatalogExplorer({
</div> </div>
</div> </div>
<p className="mt-3 text-[10px] font-semibold uppercase tracking-[0.14em] text-neutral-500"> <p className="mt-3 text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{tcl(p.catalogLineKey)} {tcl(p.catalogLineKey)}
</p> </p>
<p className="mt-1 text-sm font-semibold text-neutral-900"> <p className="mt-1 text-sm font-semibold text-neutral-900">
@ -307,14 +307,14 @@ export function CatalogExplorer({
<h2 className="mt-0.5 text-sm font-normal leading-snug text-neutral-600"> <h2 className="mt-0.5 text-sm font-normal leading-snug text-neutral-600">
<Link <Link
href={`/product/${p.slug}`} href={`/product/${p.slug}`}
className="hover:text-blue-600" className="transition hover:text-neutral-900"
> >
{tp(`${p.slug}.name`)} {tp(`${p.slug}.name`)}
</Link> </Link>
</h2> </h2>
<p className="mt-3 text-sm text-neutral-500"> <p className="mt-3 text-sm text-neutral-500">
<span className="text-[11px] font-semibold uppercase tracking-wide"> <span className="text-[11px] font-semibold uppercase tracking-widest">
{t("from")} {t("from")}
</span>{" "} </span>{" "}
<span className="text-base font-semibold text-neutral-900"> <span className="text-base font-semibold text-neutral-900">
@ -325,7 +325,7 @@ export function CatalogExplorer({
<Link <Link
href={`/product/${p.slug}`} href={`/product/${p.slug}`}
className="mt-4 w-full rounded-full border border-neutral-300 bg-white py-2.5 text-center text-sm font-semibold text-neutral-900 transition hover:border-blue-600 hover:bg-blue-600 hover:text-white" className="mt-4 w-full rounded-lg bg-neutral-900 py-2.5 text-center text-sm font-semibold text-white transition hover:bg-neutral-800"
> >
{t("addToCart")} {t("addToCart")}
</Link> </Link>
@ -346,12 +346,12 @@ export function CatalogExplorer({
aria-label={t("closeFilters")} aria-label={t("closeFilters")}
onClick={() => setFiltersOpen(false)} onClick={() => setFiltersOpen(false)}
/> />
<div className="fixed inset-y-0 left-0 z-[60] w-[min(22rem,100vw)] overflow-y-auto border-r border-neutral-200 bg-white p-6 shadow-2xl lg:hidden"> <div className="fixed inset-y-0 left-0 z-[60] w-[min(22rem,100vw)] overflow-y-auto border-r border-neutral-100 bg-white p-6 lg:hidden">
<div className="mb-6 flex items-center justify-between"> <div className="mb-6 flex items-center justify-between">
<p className="text-lg font-semibold text-neutral-900">{t("filters")}</p> <p className="text-lg font-semibold text-neutral-900">{t("filters")}</p>
<button <button
type="button" type="button"
className="rounded-full p-2 text-neutral-500 hover:bg-neutral-100" className="rounded-lg p-2 text-neutral-500 transition hover:bg-neutral-50 hover:text-neutral-900"
onClick={() => setFiltersOpen(false)} onClick={() => setFiltersOpen(false)}
> >
<span className="sr-only">{t("closeFilters")}</span> <span className="sr-only">{t("closeFilters")}</span>

View File

@ -18,6 +18,9 @@ const formSchema = z.object({
type FormValues = z.infer<typeof formSchema>; type FormValues = z.infer<typeof formSchema>;
const inputClass =
"mt-1 w-full rounded-lg border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm text-neutral-900 outline-none transition duration-150 placeholder:text-neutral-400 focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8";
export function CheckoutForm() { export function CheckoutForm() {
const t = useTranslations("checkout"); const t = useTranslations("checkout");
const items = useCartStore((s) => s.items); const items = useCartStore((s) => s.items);
@ -43,11 +46,11 @@ export function CheckoutForm() {
if (items.length === 0 && status !== "success") { if (items.length === 0 && status !== "success") {
return ( return (
<div className="rounded-2xl bg-white p-8 text-center shadow-sm ring-1 ring-stone-200/80"> <div className="rounded-lg border border-neutral-100 bg-white p-8 text-center">
<p className="text-stone-600">{t("cartEmpty")}</p> <p className="text-neutral-600">{t("cartEmpty")}</p>
<Link <Link
href="/cart" href="/cart"
className="mt-4 inline-flex rounded-xl bg-blue-600 px-5 py-3 text-sm font-semibold text-white hover:bg-blue-700" className="mt-4 inline-flex rounded-lg bg-neutral-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800"
> >
{t("goCart")} {t("goCart")}
</Link> </Link>
@ -57,22 +60,22 @@ export function CheckoutForm() {
if (status === "success" && requestId) { if (status === "success" && requestId) {
return ( return (
<div className="rounded-2xl bg-white p-8 shadow-sm ring-1 ring-emerald-200/80"> <div className="rounded-lg border border-neutral-100 bg-white p-8">
<h2 className="text-xl font-bold text-emerald-800">{t("successTitle")}</h2> <h2 className="text-xl font-semibold text-green-800">{t("successTitle")}</h2>
<p className="mt-2 text-sm text-stone-600"> <p className="mt-2 text-sm text-neutral-600">
{t("successBody")}{" "} {t("successBody")}{" "}
<span className="font-mono font-semibold text-stone-900"> <span className="font-mono font-semibold text-neutral-900">
{requestId} {requestId}
</span> </span>
</p> </p>
{emailWarn ? ( {emailWarn ? (
<p className="mt-4 rounded-xl bg-amber-50 p-3 text-sm text-amber-900 ring-1 ring-amber-200"> <p className="mt-4 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900">
{t("emailWarn")} {t("emailWarn")}
</p> </p>
) : null} ) : null}
<Link <Link
href="/catalog" href="/catalog"
className="mt-6 inline-flex rounded-xl bg-blue-600 px-5 py-3 text-sm font-semibold text-white hover:bg-blue-700" className="mt-6 inline-flex rounded-lg bg-neutral-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800"
> >
{t("successCta")} {t("successCta")}
</Link> </Link>
@ -144,7 +147,7 @@ export function CheckoutForm() {
return ( return (
<form <form
onSubmit={onFormSubmit} onSubmit={onFormSubmit}
className="space-y-6 rounded-2xl bg-white p-6 shadow-sm ring-1 ring-stone-200/80 sm:p-8" className="space-y-6 rounded-lg border border-neutral-100 bg-white p-6 sm:p-8"
> >
<input <input
type="text" type="text"
@ -157,12 +160,12 @@ export function CheckoutForm() {
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div> <div>
<label className="text-sm font-medium text-stone-700" htmlFor="name"> <label className="text-sm font-medium text-neutral-700" htmlFor="name">
{t("name")} {t("name")}
</label> </label>
<input <input
id="name" id="name"
className="mt-1 w-full rounded-xl border border-stone-200 px-3 py-2.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/25" className={inputClass}
{...register("name")} {...register("name")}
/> />
{errors.name ? ( {errors.name ? (
@ -170,12 +173,12 @@ export function CheckoutForm() {
) : null} ) : null}
</div> </div>
<div> <div>
<label className="text-sm font-medium text-stone-700" htmlFor="phone"> <label className="text-sm font-medium text-neutral-700" htmlFor="phone">
{t("phone")} {t("phone")}
</label> </label>
<input <input
id="phone" id="phone"
className="mt-1 w-full rounded-xl border border-stone-200 px-3 py-2.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/25" className={inputClass}
{...register("phone")} {...register("phone")}
/> />
{errors.phone ? ( {errors.phone ? (
@ -185,13 +188,13 @@ export function CheckoutForm() {
</div> </div>
<div> <div>
<label className="text-sm font-medium text-stone-700" htmlFor="email"> <label className="text-sm font-medium text-neutral-700" htmlFor="email">
{t("email")} {t("email")}
</label> </label>
<input <input
id="email" id="email"
type="email" type="email"
className="mt-1 w-full rounded-xl border border-stone-200 px-3 py-2.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/25" className={inputClass}
{...register("email")} {...register("email")}
/> />
{errors.email ? ( {errors.email ? (
@ -200,30 +203,30 @@ export function CheckoutForm() {
</div> </div>
<div> <div>
<label className="text-sm font-medium text-stone-700" htmlFor="company"> <label className="text-sm font-medium text-neutral-700" htmlFor="company">
{t("company")} {t("company")}
</label> </label>
<input <input
id="company" id="company"
className="mt-1 w-full rounded-xl border border-stone-200 px-3 py-2.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/25" className={inputClass}
{...register("company")} {...register("company")}
/> />
</div> </div>
<div> <div>
<label className="text-sm font-medium text-stone-700" htmlFor="message"> <label className="text-sm font-medium text-neutral-700" htmlFor="message">
{t("message")} {t("message")}
</label> </label>
<textarea <textarea
id="message" id="message"
rows={4} rows={4}
className="mt-1 w-full rounded-xl border border-stone-200 px-3 py-2.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/25" className={inputClass}
{...register("message")} {...register("message")}
/> />
</div> </div>
<div> <div>
<label className="text-sm font-medium text-stone-700" htmlFor="refs"> <label className="text-sm font-medium text-neutral-700" htmlFor="refs">
{t("referenceImages")} {t("referenceImages")}
</label> </label>
<input <input
@ -233,13 +236,13 @@ export function CheckoutForm() {
name="referenceImages" name="referenceImages"
accept="image/jpeg,image/png,image/webp" accept="image/jpeg,image/png,image/webp"
multiple multiple
className="mt-1 block w-full text-sm text-stone-600 file:mr-3 file:rounded-lg file:border-0 file:bg-blue-50 file:px-3 file:py-2 file:text-sm file:font-semibold file:text-blue-700 hover:file:bg-blue-100" className="mt-1 block w-full text-sm text-neutral-600 file:mr-3 file:rounded-lg file:border-0 file:bg-neutral-100 file:px-3 file:py-2 file:text-sm file:font-semibold file:text-neutral-700 hover:file:bg-neutral-200"
/> />
<p className="mt-1 text-xs text-stone-500">{t("referenceHelp")}</p> <p className="mt-1 text-xs text-neutral-500">{t("referenceHelp")}</p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-stone-700" htmlFor="proforma"> <label className="text-sm font-medium text-neutral-700" htmlFor="proforma">
{t("proforma")} {t("proforma")}
</label> </label>
<input <input
@ -248,13 +251,13 @@ export function CheckoutForm() {
type="file" type="file"
name="proforma" name="proforma"
accept="application/pdf,image/jpeg,image/png,image/webp" accept="application/pdf,image/jpeg,image/png,image/webp"
className="mt-1 block w-full text-sm text-stone-600 file:mr-3 file:rounded-lg file:border-0 file:bg-orange-50 file:px-3 file:py-2 file:text-sm file:font-semibold file:text-orange-700 hover:file:bg-orange-100" className="mt-1 block w-full text-sm text-neutral-600 file:mr-3 file:rounded-lg file:border-0 file:bg-neutral-100 file:px-3 file:py-2 file:text-sm file:font-semibold file:text-neutral-700 hover:file:bg-neutral-200"
/> />
<p className="mt-1 text-xs text-stone-500">{t("proformaHelp")}</p> <p className="mt-1 text-xs text-neutral-500">{t("proformaHelp")}</p>
</div> </div>
{errorMsg ? ( {errorMsg ? (
<p className="rounded-xl bg-red-50 p-3 text-sm text-red-800 ring-1 ring-red-200"> <p className="rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800">
{errorMsg} {errorMsg}
</p> </p>
) : null} ) : null}
@ -262,7 +265,7 @@ export function CheckoutForm() {
<button <button
type="submit" type="submit"
disabled={status === "sending"} disabled={status === "sending"}
className="w-full rounded-xl bg-orange-500 py-3.5 text-sm font-bold text-white shadow-sm hover:bg-orange-600 disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto sm:px-10" className="w-full rounded-lg bg-neutral-900 py-3.5 text-sm font-semibold text-white transition hover:bg-neutral-800 disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto sm:px-10"
> >
{status === "sending" ? t("sending") : t("submit")} {status === "sending" ? t("sending") : t("submit")}
</button> </button>

View File

@ -1,4 +1,5 @@
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { Link } from "@/i18n/navigation";
import { import {
getPublicSocialLinks, getPublicSocialLinks,
publicContactAddress, publicContactAddress,
@ -15,7 +16,7 @@ function SocialGlyph({
name: "linkedin" | "facebook" | "telegram" | "instagram"; name: "linkedin" | "facebook" | "telegram" | "instagram";
className?: string; className?: string;
}) { }) {
const c = className ?? "h-5 w-5"; const c = className ?? "h-4 w-4";
switch (name) { switch (name) {
case "linkedin": case "linkedin":
return ( return (
@ -48,69 +49,105 @@ function SocialGlyph({
export async function Footer() { export async function Footer() {
const t = await getTranslations("footer"); const t = await getTranslations("footer");
const tn = await getTranslations("nav");
const year = new Date().getFullYear(); const year = new Date().getFullYear();
const socials = getPublicSocialLinks(); const socials = getPublicSocialLinks();
return ( return (
<footer className="mt-16 border-t border-stone-200/80 bg-white"> <footer className="border-t border-neutral-100 bg-white">
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6 lg:px-8"> <div className="mx-auto max-w-6xl px-4 py-14 sm:px-6 lg:px-8">
<div className="grid gap-10 sm:grid-cols-2 lg:grid-cols-3 lg:gap-12"> <div className="grid gap-12 sm:grid-cols-2 lg:grid-cols-4">
<div>
<h2 className="text-lg font-bold tracking-tight text-blue-600"> {/* Brand column */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-neutral-900 text-white">
<svg width="14" height="14" viewBox="0 0 32 32" fill="none" aria-hidden>
<path d="M6 24 16 6l10 18H6Z" stroke="currentColor" strokeWidth="2.5" strokeLinejoin="round" />
</svg>
</span>
<span className="text-[15px] font-semibold tracking-tight text-neutral-900">
TrustWin TrustWin
</h2> </span>
<p className="mt-3 max-w-sm text-sm leading-relaxed text-stone-600"> </div>
<p className="max-w-xs text-sm leading-relaxed text-neutral-500">
{t("tagline")} {t("tagline")}
</p> </p>
</div> </div>
<div> {/* Links column */}
<h3 className="text-xs font-semibold uppercase tracking-wider text-stone-500"> <div className="space-y-4">
<p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{tn("catalog")}
</p>
<ul className="space-y-2.5 text-sm">
<li>
<Link href="/" className="text-neutral-500 transition hover:text-neutral-900">
{tn("home")}
</Link>
</li>
<li>
<Link href="/catalog" className="text-neutral-500 transition hover:text-neutral-900">
{tn("products")}
</Link>
</li>
<li>
<Link href="/cart" className="text-neutral-500 transition hover:text-neutral-900">
{tn("cart")}
</Link>
</li>
</ul>
</div>
{/* Contact column */}
<div className="space-y-4">
<p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("contactTitle")} {t("contactTitle")}
</h3> </p>
<ul className="mt-4 space-y-3 text-sm"> <ul className="space-y-2.5 text-sm">
<li> <li>
<a <a
href={telHref(publicContactPhone)} href={telHref(publicContactPhone)}
className="font-medium text-stone-900 transition hover:text-blue-600" className="font-medium text-neutral-800 transition-colors hover:text-neutral-500"
> >
{publicContactPhone} {publicContactPhone}
</a> </a>
</li> </li>
{publicContactEmail ? ( {publicContactEmail && (
<li> <li>
<a <a
href={`mailto:${publicContactEmail}`} href={`mailto:${publicContactEmail}`}
className="text-stone-700 underline-offset-2 transition hover:text-blue-600 hover:underline" className="text-neutral-500 underline underline-offset-2 decoration-neutral-300 transition-colors hover:text-neutral-800 hover:decoration-neutral-500"
> >
{publicContactEmail} {publicContactEmail}
</a> </a>
</li> </li>
) : null} )}
{publicContactAddress ? ( {publicContactAddress && (
<li className="whitespace-pre-line text-stone-600"> <li className="whitespace-pre-line text-neutral-500">
{publicContactAddress} {publicContactAddress}
</li> </li>
) : null} )}
{publicContactHours ? ( {publicContactHours && (
<li className="text-stone-600">{publicContactHours}</li> <li className="text-neutral-500">{publicContactHours}</li>
) : null} )}
</ul> </ul>
</div> </div>
<div className="sm:col-span-2 lg:col-span-1"> {/* Social column */}
<h3 className="text-xs font-semibold uppercase tracking-wider text-stone-500"> <div className="flex flex-col items-center space-y-4 sm:col-span-2 lg:col-span-1 lg:items-center">
<p className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("socialTitle")} {t("socialTitle")}
</h3> </p>
{socials.length > 0 ? ( {socials.length > 0 ? (
<ul className="mt-4 flex flex-wrap gap-3"> <ul className="flex flex-wrap justify-center gap-2">
{socials.map(({ key, href }) => ( {socials.map(({ key, href }) => (
<li key={key}> <li key={key}>
<a <a
href={href} href={href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex h-11 w-11 items-center justify-center rounded-xl bg-stone-50 text-stone-600 shadow-sm ring-1 ring-stone-200/90 transition hover:bg-blue-600 hover:text-white hover:ring-blue-600" className="flex h-9 w-9 items-center justify-center rounded-lg border border-neutral-200 bg-white text-neutral-500 transition hover:border-neutral-900 hover:bg-neutral-900 hover:text-white"
aria-label={t(`social.${key}`)} aria-label={t(`social.${key}`)}
> >
<SocialGlyph name={key} /> <SocialGlyph name={key} />
@ -119,15 +156,16 @@ export async function Footer() {
))} ))}
</ul> </ul>
) : ( ) : (
<p className="mt-4 max-w-xs text-sm leading-relaxed text-stone-500"> <p className="max-w-xs text-center text-sm leading-relaxed text-neutral-400">
{t("socialHint")} {t("socialHint")}
</p> </p>
)} )}
</div> </div>
</div> </div>
<div className="mt-10 border-t border-stone-200/80 pt-8"> {/* Bottom bar */}
<p className="text-center text-xs text-stone-500 sm:text-left"> <div className="mt-14 flex flex-col items-center justify-center gap-3 border-t border-neutral-100 pt-8 sm:flex-row">
<p className="text-xs font-semibold text-black-300">
© {year} TrustWin. {t("rights")} © {year} TrustWin. {t("rights")}
</p> </p>
</div> </div>

View File

@ -11,100 +11,104 @@ export function Header() {
const t = useTranslations("nav"); const t = useTranslations("nav");
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const navLinkClass =
"relative text-sm font-medium text-neutral-500 transition-colors duration-150 hover:text-neutral-900 after:absolute after:-bottom-0.5 after:left-0 after:h-px after:w-0 after:bg-neutral-900 after:transition-all after:duration-200 hover:after:w-full";
const mobileLinks = ( const mobileLinks = (
<> <>
<Link <Link
href="/" href="/"
className="rounded-full px-4 py-3 text-sm font-medium text-neutral-800 hover:bg-neutral-100" className="rounded-lg px-3 py-2.5 text-sm font-medium text-neutral-700 transition hover:bg-neutral-50 hover:text-neutral-900"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
> >
{t("home")} {t("home")}
</Link> </Link>
<Link <Link
href="/catalog" href="/catalog"
className="rounded-full px-4 py-3 text-sm font-medium text-neutral-800 hover:bg-neutral-100" className="rounded-lg px-3 py-2.5 text-sm font-medium text-neutral-700 transition hover:bg-neutral-50 hover:text-neutral-900"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
> >
{t("catalog")} {t("catalog")}
</Link> </Link>
<Link <Link
href="/cart" href="/cart"
className="rounded-full px-4 py-3 text-sm font-medium text-neutral-800 hover:bg-neutral-100" className="rounded-lg px-3 py-2.5 text-sm font-medium text-neutral-700 transition hover:bg-neutral-50 hover:text-neutral-900"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
> >
{t("cart")} {t("cart")}
</Link> </Link>
<div className="pt-2">
<Link <Link
href="/checkout" href="/checkout"
className="mx-2 mt-2 rounded-full bg-blue-600 px-4 py-3 text-center text-sm font-semibold text-white" className="block rounded-lg bg-neutral-900 px-4 py-2.5 text-center text-sm font-semibold text-white transition hover:bg-neutral-800"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
> >
{t("joinCta")} {t("joinCta")}
</Link> </Link>
</div>
</> </>
); );
return ( return (
<header className="sticky top-0 z-40 border-b border-neutral-200/90 bg-white/95 backdrop-blur-md"> <header className="sticky top-0 z-40 border-b border-neutral-100 bg-white/98 backdrop-blur-sm">
<div className="mx-auto flex max-w-[1440px] items-center gap-3 px-4 py-3 sm:gap-4 lg:px-8"> <div className="mx-auto flex max-w-[1440px] items-center gap-6 px-4 py-3.5 sm:gap-8 lg:px-8">
{/* Logo */}
<Link <Link
href="/" href="/"
className="flex shrink-0 items-center gap-2.5 text-neutral-900" className="group flex shrink-0 items-center gap-2 text-neutral-900"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
> >
<span <span
className="flex h-9 w-9 items-center justify-center rounded-2xl bg-blue-600 text-white shadow-sm" className="flex h-8 w-8 items-center justify-center rounded-lg bg-neutral-900 text-white transition duration-200 group-hover:bg-neutral-700"
aria-hidden aria-hidden
> >
<svg width="20" height="20" viewBox="0 0 32 32" fill="none" className="text-white"> <svg width="16" height="16" viewBox="0 0 32 32" fill="none">
<path <path
d="M6 24 16 6l10 18H6Z" d="M6 24 16 6l10 18H6Z"
stroke="currentColor" stroke="currentColor"
strokeWidth="2.2" strokeWidth="2.5"
strokeLinejoin="round" strokeLinejoin="round"
fill="none"
/> />
</svg> </svg>
</span> </span>
<span className="hidden text-[17px] font-semibold tracking-tight sm:inline"> <span className="hidden text-[15px] font-semibold tracking-tight sm:inline">
{t("brand")} {t("brand")}
</span> </span>
</Link> </Link>
<nav {/* Desktop nav */}
className="hidden items-center gap-1 lg:flex" <nav className="hidden items-center gap-6 lg:flex" aria-label="Primary">
aria-label="Primary" <Link href="/" className={navLinkClass}>
>
<Link
href="/"
className="rounded-full px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100 hover:text-neutral-950"
>
{t("home")} {t("home")}
</Link> </Link>
<Link <Link href="/catalog" className={navLinkClass}>
href="/catalog"
className="rounded-full px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100 hover:text-neutral-950"
>
{t("products")} {t("products")}
</Link> </Link>
</nav> </nav>
{/* Search */}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<HeaderCatalogSearch /> <HeaderCatalogSearch />
</div> </div>
<div className="flex shrink-0 items-center gap-1.5 sm:gap-2"> {/* Right actions */}
<div className="flex shrink-0 items-center gap-2">
<LanguageSwitcher /> <LanguageSwitcher />
{/* Cart */}
<Link <Link
href="/cart" href="/cart"
className="relative rounded-full p-2.5 text-neutral-600 transition hover:bg-neutral-100 hover:text-blue-600" className="relative rounded-lg p-2 text-neutral-500 transition hover:bg-neutral-50 hover:text-neutral-900"
aria-label={t("cart")} aria-label={t("cart")}
> >
<svg <svg
className="h-6 w-6" className="h-5 w-5"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
strokeWidth={1.5} strokeWidth={1.6}
aria-hidden aria-hidden
> >
<path <path
@ -116,27 +120,29 @@ export function Header() {
<CartBadge /> <CartBadge />
</Link> </Link>
{/* CTA */}
<Link <Link
href="/checkout" href="/checkout"
className="hidden rounded-full bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 sm:inline-flex" className="hidden rounded-lg bg-neutral-900 px-4 py-2 text-sm font-semibold text-white transition hover:bg-neutral-800 sm:inline-flex"
> >
{t("joinCta")} {t("joinCta")}
</Link> </Link>
{/* Mobile menu toggle */}
<button <button
type="button" type="button"
className="rounded-full p-2.5 text-neutral-800 hover:bg-neutral-100 lg:hidden" className="rounded-lg p-2 text-neutral-500 transition hover:bg-neutral-50 hover:text-neutral-900 lg:hidden"
aria-expanded={open} aria-expanded={open}
aria-controls="mobile-nav" aria-controls="mobile-nav"
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
> >
<span className="sr-only">{open ? t("closeMenu") : t("openMenu")}</span> <span className="sr-only">{open ? t("closeMenu") : t("openMenu")}</span>
{open ? ( {open ? (
<svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75}> <svg className="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75}>
<path strokeLinecap="round" d="M6 18 18 6M6 6l12 12" /> <path strokeLinecap="round" d="M6 18 18 6M6 6l12 12" />
</svg> </svg>
) : ( ) : (
<svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75}> <svg className="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75}>
<path strokeLinecap="round" d="M4 7h16M4 12h16M4 17h16" /> <path strokeLinecap="round" d="M4 7h16M4 12h16M4 17h16" />
</svg> </svg>
)} )}
@ -144,16 +150,17 @@ export function Header() {
</div> </div>
</div> </div>
{open ? ( {/* Mobile nav drawer */}
{open && (
<div <div
id="mobile-nav" id="mobile-nav"
className="border-t border-neutral-200 bg-white px-4 py-4 lg:hidden" className="border-t border-neutral-100 bg-white px-4 pb-4 pt-2 lg:hidden"
> >
<nav className="flex flex-col gap-1" aria-label="Mobile"> <nav className="flex flex-col gap-0.5" aria-label="Mobile">
{mobileLinks} {mobileLinks}
</nav> </nav>
</div> </div>
) : null} )}
</header> </header>
); );
} }

View File

@ -28,12 +28,23 @@ function HeaderCatalogSearchInner() {
return ( return (
<form <form
onSubmit={onSubmit} onSubmit={onSubmit}
className="relative hidden w-full max-w-xl md:block" className="relative hidden w-full max-w-lg md:block"
role="search" role="search"
> >
<label htmlFor="header-catalog-search" className="sr-only"> <label htmlFor="header-catalog-search" className="sr-only">
{t("searchAria")} {t("searchAria")}
</label> </label>
{/* Search icon */}
<span
className="pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400"
aria-hidden
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}>
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-4.34-4.34M11 18a7 7 0 1 1 0-14 7 7 0 0 1 0 14Z" />
</svg>
</span>
<input <input
id="header-catalog-search" id="header-catalog-search"
type="search" type="search"
@ -41,16 +52,24 @@ function HeaderCatalogSearchInner() {
value={q} value={q}
onChange={(e) => setQ(e.target.value)} onChange={(e) => setQ(e.target.value)}
placeholder={t("searchPlaceholder")} placeholder={t("searchPlaceholder")}
className="w-full rounded-full border border-neutral-200 bg-neutral-50 py-2.5 pl-11 pr-4 text-sm text-neutral-900 shadow-inner shadow-neutral-200/40 outline-none ring-blue-600/0 transition placeholder:text-neutral-400 focus:border-blue-600 focus:bg-white focus:ring-2 focus:ring-blue-600/20" className="
w-full rounded-lg border border-neutral-200 bg-neutral-50 py-2.5 pl-10 pr-10
text-sm text-neutral-900 outline-none transition duration-150
placeholder:text-neutral-400
hover:border-neutral-300 hover:bg-white
focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8
"
/> />
<span
className="pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" {/* Submit button - visible only when there's input */}
aria-hidden {q.trim() && (
<button
type="submit"
className="absolute right-2.5 top-1/2 -translate-y-1/2 rounded-md bg-neutral-900 px-2 py-1 text-[11px] font-semibold text-white transition hover:bg-neutral-700"
> >
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}>
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-4.34-4.34M11 18a7 7 0 1 1 0-14 7 7 0 0 1 0 14Z" /> </button>
</svg> )}
</span>
</form> </form>
); );
} }
@ -60,7 +79,7 @@ export function HeaderCatalogSearch() {
<Suspense <Suspense
fallback={ fallback={
<div <div
className="hidden h-10 w-full max-w-xl rounded-full bg-neutral-100 md:block" className="hidden h-[38px] w-full max-w-lg animate-pulse rounded-lg bg-neutral-100 md:block"
aria-hidden aria-hidden
/> />
} }

View File

@ -8,21 +8,13 @@ export function LanguageSwitcher() {
const pathname = usePathname(); const pathname = usePathname();
return ( return (
<div className="flex items-center gap-1.5 rounded-full border border-neutral-200 bg-white px-1 py-1 shadow-sm"> <div className="flex items-center rounded-lg border border-neutral-200 bg-neutral-50 p-0.5">
<span className="hidden pl-2 text-neutral-400 sm:inline" aria-hidden>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M3.6 9h16.8M3.6 15h16.8M12 3a15.3 15.3 0 0 1 4 9 15.3 15.3 0 0 1-4 9 15.3 15.3 0 0 1-4-9 15.3 15.3 0 0 1 4-9Z" />
</svg>
</span>
<div className="flex items-center rounded-full bg-neutral-100 p-0.5 text-[11px] font-semibold text-neutral-600">
<Link <Link
href={pathname} href={pathname}
locale="en" locale="en"
className={`rounded-full px-2 py-1 transition ${ className={`rounded-md px-2.5 py-1.5 text-[11px] font-semibold tracking-wide transition-all duration-150 ${locale === "en"
locale === "en" ? "bg-white text-neutral-900"
? "bg-white text-blue-600 shadow-sm" : "text-neutral-400 hover:text-neutral-700"
: "hover:text-neutral-900"
}`} }`}
aria-current={locale === "en" ? "true" : undefined} aria-current={locale === "en" ? "true" : undefined}
> >
@ -31,16 +23,14 @@ export function LanguageSwitcher() {
<Link <Link
href={pathname} href={pathname}
locale="am" locale="am"
className={`rounded-full px-2 py-1 transition ${ className={`rounded-md px-2.5 py-1.5 text-[11px] font-semibold tracking-wide transition-all duration-150 ${locale === "am"
locale === "am" ? "bg-white text-neutral-900"
? "bg-white text-orange-500 shadow-sm" : "text-neutral-400 hover:text-neutral-700"
: "hover:text-neutral-900"
}`} }`}
aria-current={locale === "am" ? "true" : undefined} aria-current={locale === "am" ? "true" : undefined}
> >
</Link> </Link>
</div> </div>
</div>
); );
} }

View File

@ -40,8 +40,8 @@ export function ProductAddToCart({ product }: { product: Product }) {
} }
return ( return (
<div className="rounded-2xl border border-neutral-200 bg-white p-5 shadow-sm"> <div className="rounded-lg border border-neutral-100 bg-white p-5">
<h2 className="text-xs font-semibold uppercase tracking-wider text-neutral-500"> <h2 className="text-[11px] font-semibold uppercase tracking-widest text-neutral-400">
{t("options")} {t("options")}
</h2> </h2>
<div className="mt-4 space-y-4"> <div className="mt-4 space-y-4">
@ -51,7 +51,7 @@ export function ProductAddToCart({ product }: { product: Product }) {
{tv(v.labelKey)} {tv(v.labelKey)}
</label> </label>
<select <select
className="mt-1 w-full rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm font-medium text-neutral-900 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/15" className="mt-1 w-full rounded-lg border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm font-medium text-neutral-900 outline-none transition duration-150 focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8"
value={selections[v.key]} value={selections[v.key]}
onChange={(e) => onChange={(e) =>
setSelections((s) => ({ ...s, [v.key]: e.target.value })) setSelections((s) => ({ ...s, [v.key]: e.target.value }))
@ -78,13 +78,13 @@ export function ProductAddToCart({ product }: { product: Product }) {
max={9999} max={9999}
value={qty} value={qty}
onChange={(e) => setQty(Math.max(1, Number(e.target.value) || 1))} onChange={(e) => setQty(Math.max(1, Number(e.target.value) || 1))}
className="mt-2 w-full rounded-full border border-neutral-200 bg-white px-4 py-2.5 text-sm font-semibold focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/15" className="mt-2 w-full rounded-lg border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm font-semibold outline-none transition duration-150 focus:border-neutral-300 focus:bg-white focus:ring-2 focus:ring-neutral-900/8"
/> />
<button <button
type="button" type="button"
onClick={handleAdd} onClick={handleAdd}
disabled={!canSubmit} disabled={!canSubmit}
className="mt-4 w-full rounded-full border border-neutral-300 bg-neutral-900 py-3 text-sm font-semibold text-white transition hover:border-blue-600 hover:bg-blue-600 disabled:cursor-not-allowed disabled:opacity-40" className="mt-4 w-full rounded-lg bg-neutral-900 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800 disabled:cursor-not-allowed disabled:opacity-40"
> >
{added ? `${t("add")}` : t("add")} {added ? `${t("add")}` : t("add")}
</button> </button>

View File

@ -51,7 +51,7 @@ export function ProductGallery({ slug, sources }: Props) {
if (n === 0) { if (n === 0) {
return ( return (
<div <div
className="flex aspect-square items-center justify-center rounded-2xl bg-neutral-100 text-sm text-neutral-500 ring-1 ring-neutral-200/90" className="flex aspect-square items-center justify-center rounded-lg border border-neutral-200 bg-neutral-100 text-sm text-neutral-500"
role="img" role="img"
aria-label={alt} aria-label={alt}
> >
@ -67,7 +67,7 @@ export function ProductGallery({ slug, sources }: Props) {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div <div
className="relative aspect-square overflow-hidden rounded-2xl bg-neutral-100 ring-1 ring-neutral-200/90" className="relative aspect-square overflow-hidden rounded-lg border border-neutral-200 bg-neutral-100"
onTouchStart={(e) => { onTouchStart={(e) => {
touchStartX.current = e.touches[0]?.clientX ?? null; touchStartX.current = e.touches[0]?.clientX ?? null;
}} }}
@ -99,7 +99,7 @@ export function ProductGallery({ slug, sources }: Props) {
<button <button
type="button" type="button"
onClick={() => go(-1)} onClick={() => go(-1)}
className="absolute left-2 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-white/90 text-neutral-800 shadow-md ring-1 ring-neutral-200/80 backdrop-blur-sm transition hover:bg-white" className="absolute left-2 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-lg border border-neutral-200 bg-white/90 text-neutral-800 backdrop-blur-sm transition hover:bg-white"
aria-label={t("galleryPrev")} aria-label={t("galleryPrev")}
> >
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@ -109,14 +109,14 @@ export function ProductGallery({ slug, sources }: Props) {
<button <button
type="button" type="button"
onClick={() => go(1)} onClick={() => go(1)}
className="absolute right-2 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-white/90 text-neutral-800 shadow-md ring-1 ring-neutral-200/80 backdrop-blur-sm transition hover:bg-white" className="absolute right-2 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-lg border border-neutral-200 bg-white/90 text-neutral-800 backdrop-blur-sm transition hover:bg-white"
aria-label={t("galleryNext")} aria-label={t("galleryNext")}
> >
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg> </svg>
</button> </button>
<p className="pointer-events-none absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full bg-neutral-950/55 px-3 py-1 text-xs font-medium tabular-nums text-white backdrop-blur-sm"> <p className="pointer-events-none absolute bottom-3 left-1/2 -translate-x-1/2 rounded-md bg-neutral-950/55 px-3 py-1 text-xs font-medium tabular-nums text-white backdrop-blur-sm">
{t("galleryCounter", { current: slideIndex + 1, total: n })} {t("galleryCounter", { current: slideIndex + 1, total: n })}
</p> </p>
</> </>
@ -140,10 +140,10 @@ export function ProductGallery({ slug, sources }: Props) {
aria-selected={active} aria-selected={active}
aria-label={t("galleryGoTo", { n: i + 1 })} aria-label={t("galleryGoTo", { n: i + 1 })}
onClick={() => setSlide(i)} onClick={() => setSlide(i)}
className={`relative h-16 w-16 shrink-0 overflow-hidden rounded-xl ring-2 transition sm:h-20 sm:w-20 ${ className={`relative h-16 w-16 shrink-0 overflow-hidden rounded-lg border-2 transition sm:h-20 sm:w-20 ${
active active
? "ring-blue-600 ring-offset-2" ? "border-neutral-900"
: "ring-transparent ring-offset-0 opacity-80 hover:opacity-100" : "border-transparent opacity-70 hover:opacity-100"
}`} }`}
> >
<Image <Image

View File

@ -5,7 +5,7 @@ import { StickyCallBar } from "@/components/StickyCallBar";
export async function SiteChrome({ children }: { children: ReactNode }) { export async function SiteChrome({ children }: { children: ReactNode }) {
return ( return (
<div className="flex min-h-screen flex-col pb-24 sm:pb-28"> <div className="flex min-h-screen flex-col">
<Header /> <Header />
<main className="flex-1">{children}</main> <main className="flex-1">{children}</main>
<Footer /> <Footer />

View File

@ -14,7 +14,7 @@ export function StickyCallBar() {
> >
<a <a
href={telHref(publicContactPhone)} href={telHref(publicContactPhone)}
className="flex h-14 w-14 items-center justify-center rounded-full bg-blue-600 text-white shadow-xl shadow-blue-600/35 transition hover:bg-blue-700" className="flex h-14 w-14 items-center justify-center rounded-full bg-neutral-900 text-white transition hover:bg-neutral-800"
aria-label={t("call")} aria-label={t("call")}
> >
<svg <svg