Add country localization
This commit is contained in:
101
messages/en-gb.json
Normal file
101
messages/en-gb.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"Meta": {
|
||||||
|
"title": "Kairas | Web design studio for UK brands",
|
||||||
|
"description": "Kairas is a web design firm crafting refined websites, brand systems, and digital products for UK teams."
|
||||||
|
},
|
||||||
|
"Home": {
|
||||||
|
"nav": {
|
||||||
|
"studio": "Studio",
|
||||||
|
"services": "Services",
|
||||||
|
"work": "Work",
|
||||||
|
"notes": "Notes",
|
||||||
|
"contact": "hello@kairas.io"
|
||||||
|
},
|
||||||
|
"localeSwitcher": {
|
||||||
|
"label": "Country",
|
||||||
|
"options": {
|
||||||
|
"en-ie": "Ireland",
|
||||||
|
"en-gb": "United Kingdom",
|
||||||
|
"en-us": "United States"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"kicker": "Web design firm for UK brands",
|
||||||
|
"siteUrl": "kairas.io",
|
||||||
|
"words": ["Websites", "with quiet", "force."],
|
||||||
|
"interfaceLabel": "Selected interface",
|
||||||
|
"brandLabel": "Brand system",
|
||||||
|
"interfaceCopy": "Layouts tuned for calm reading and decisive action.",
|
||||||
|
"ticker": ["Strategy", "Design", "Build"],
|
||||||
|
"copy": "Kairas designs and builds refined websites for UK founders, studios, and service brands that care about taste, clarity, and commercial performance."
|
||||||
|
},
|
||||||
|
"marquee": ["Strategy", "Design", "Development", "Systems", "Launch"],
|
||||||
|
"studio": {
|
||||||
|
"eyebrow": "About us",
|
||||||
|
"title": "We shape digital homes for UK brands that need to feel exact, intentional, and easy to trust.",
|
||||||
|
"copy": "Our work sits between design studio and front-end craft. We use strong typography, generous pacing, and disciplined systems to make every page feel composed without becoming static."
|
||||||
|
},
|
||||||
|
"servicesTitle": "Services",
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"number": "01",
|
||||||
|
"title": "Brand-led websites",
|
||||||
|
"text": "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "02",
|
||||||
|
"title": "Editorial product pages",
|
||||||
|
"text": "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "03",
|
||||||
|
"title": "Design systems",
|
||||||
|
"text": "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"work": {
|
||||||
|
"eyebrow": "Selected work",
|
||||||
|
"title": "Places where the brand can breathe.",
|
||||||
|
"items": [
|
||||||
|
["Atelier North", "Architecture portfolio", "2026"],
|
||||||
|
["Vellum Labs", "SaaS website", "2025"],
|
||||||
|
["Morrow House", "Hospitality booking", "2025"],
|
||||||
|
["Plainform", "Brand system", "2024"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"title": "Process",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Read",
|
||||||
|
"text": "We clarify the offer, audience, proof, and moments where a visitor needs confidence."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Compose",
|
||||||
|
"text": "We design the system: type, pacing, components, motion notes, and content hierarchy."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ship",
|
||||||
|
"text": "We build responsive pages with practical handoff, analytics, and room to grow."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"title": "Notes",
|
||||||
|
"eyebrow": "Studio journal",
|
||||||
|
"items": [
|
||||||
|
["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"],
|
||||||
|
["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"],
|
||||||
|
["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"brand": "Kairas",
|
||||||
|
"headline": "Build the site your brand has been waiting for.",
|
||||||
|
"siteUrl": "kairas.io",
|
||||||
|
"email": "hello@kairas.io",
|
||||||
|
"services": "Strategy / Design / Development",
|
||||||
|
"copyright": "© 2026 Kairas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
messages/en-ie.json
Normal file
101
messages/en-ie.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"Meta": {
|
||||||
|
"title": "Kairas | Web design studio in Ireland",
|
||||||
|
"description": "Kairas is an Ireland-based web design firm crafting refined websites, brand systems, and digital products."
|
||||||
|
},
|
||||||
|
"Home": {
|
||||||
|
"nav": {
|
||||||
|
"studio": "Studio",
|
||||||
|
"services": "Services",
|
||||||
|
"work": "Work",
|
||||||
|
"notes": "Notes",
|
||||||
|
"contact": "hello@kairas.io"
|
||||||
|
},
|
||||||
|
"localeSwitcher": {
|
||||||
|
"label": "Country",
|
||||||
|
"options": {
|
||||||
|
"en-ie": "Ireland",
|
||||||
|
"en-gb": "United Kingdom",
|
||||||
|
"en-us": "United States"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"kicker": "Web design firm in Ireland",
|
||||||
|
"siteUrl": "kairas.io",
|
||||||
|
"words": ["Websites", "with quiet", "force."],
|
||||||
|
"interfaceLabel": "Selected interface",
|
||||||
|
"brandLabel": "Brand system",
|
||||||
|
"interfaceCopy": "Layouts tuned for calm reading and decisive action.",
|
||||||
|
"ticker": ["Strategy", "Design", "Build"],
|
||||||
|
"copy": "Kairas designs and builds refined websites for founders, studios, and service brands across Ireland that care about taste, clarity, and commercial performance."
|
||||||
|
},
|
||||||
|
"marquee": ["Strategy", "Design", "Development", "Systems", "Launch"],
|
||||||
|
"studio": {
|
||||||
|
"eyebrow": "About us",
|
||||||
|
"title": "We shape digital homes for Irish brands that need to feel exact, intentional, and easy to trust.",
|
||||||
|
"copy": "Our work sits between design studio and front-end craft. We use strong typography, generous pacing, and disciplined systems to make every page feel composed without becoming static."
|
||||||
|
},
|
||||||
|
"servicesTitle": "Services",
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"number": "01",
|
||||||
|
"title": "Brand-led websites",
|
||||||
|
"text": "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "02",
|
||||||
|
"title": "Editorial product pages",
|
||||||
|
"text": "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "03",
|
||||||
|
"title": "Design systems",
|
||||||
|
"text": "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"work": {
|
||||||
|
"eyebrow": "Selected work",
|
||||||
|
"title": "Places where the brand can breathe.",
|
||||||
|
"items": [
|
||||||
|
["Atelier North", "Architecture portfolio", "2026"],
|
||||||
|
["Vellum Labs", "SaaS website", "2025"],
|
||||||
|
["Morrow House", "Hospitality booking", "2025"],
|
||||||
|
["Plainform", "Brand system", "2024"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"title": "Process",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Read",
|
||||||
|
"text": "We clarify the offer, audience, proof, and moments where a visitor needs confidence."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Compose",
|
||||||
|
"text": "We design the system: type, pacing, components, motion notes, and content hierarchy."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ship",
|
||||||
|
"text": "We build responsive pages with practical handoff, analytics, and room to grow."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"title": "Notes",
|
||||||
|
"eyebrow": "Studio journal",
|
||||||
|
"items": [
|
||||||
|
["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"],
|
||||||
|
["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"],
|
||||||
|
["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"brand": "Kairas",
|
||||||
|
"headline": "Build the site your brand has been waiting for.",
|
||||||
|
"siteUrl": "kairas.io",
|
||||||
|
"email": "hello@kairas.io",
|
||||||
|
"services": "Strategy / Design / Development",
|
||||||
|
"copyright": "© 2026 Kairas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
messages/en-us.json
Normal file
101
messages/en-us.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"Meta": {
|
||||||
|
"title": "Kairas | Web design studio for US teams",
|
||||||
|
"description": "Kairas is a web design firm crafting refined websites, brand systems, and digital products for US founders and service brands."
|
||||||
|
},
|
||||||
|
"Home": {
|
||||||
|
"nav": {
|
||||||
|
"studio": "Studio",
|
||||||
|
"services": "Services",
|
||||||
|
"work": "Work",
|
||||||
|
"notes": "Notes",
|
||||||
|
"contact": "hello@kairas.io"
|
||||||
|
},
|
||||||
|
"localeSwitcher": {
|
||||||
|
"label": "Country",
|
||||||
|
"options": {
|
||||||
|
"en-ie": "Ireland",
|
||||||
|
"en-gb": "United Kingdom",
|
||||||
|
"en-us": "United States"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"kicker": "Web design firm for US teams",
|
||||||
|
"siteUrl": "kairas.io",
|
||||||
|
"words": ["Websites", "with quiet", "force."],
|
||||||
|
"interfaceLabel": "Selected interface",
|
||||||
|
"brandLabel": "Brand system",
|
||||||
|
"interfaceCopy": "Layouts tuned for calm reading and decisive action.",
|
||||||
|
"ticker": ["Strategy", "Design", "Build"],
|
||||||
|
"copy": "Kairas designs and builds refined websites for US founders, studios, and service brands that care about taste, clarity, and commercial performance."
|
||||||
|
},
|
||||||
|
"marquee": ["Strategy", "Design", "Development", "Systems", "Launch"],
|
||||||
|
"studio": {
|
||||||
|
"eyebrow": "About us",
|
||||||
|
"title": "We shape digital homes for US teams that need to feel exact, intentional, and easy to trust.",
|
||||||
|
"copy": "Our work sits between design studio and front-end craft. We use strong typography, generous pacing, and disciplined systems to make every page feel composed without becoming static."
|
||||||
|
},
|
||||||
|
"servicesTitle": "Services",
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"number": "01",
|
||||||
|
"title": "Brand-led websites",
|
||||||
|
"text": "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "02",
|
||||||
|
"title": "Editorial product pages",
|
||||||
|
"text": "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number": "03",
|
||||||
|
"title": "Design systems",
|
||||||
|
"text": "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"work": {
|
||||||
|
"eyebrow": "Selected work",
|
||||||
|
"title": "Places where the brand can breathe.",
|
||||||
|
"items": [
|
||||||
|
["Atelier North", "Architecture portfolio", "2026"],
|
||||||
|
["Vellum Labs", "SaaS website", "2025"],
|
||||||
|
["Morrow House", "Hospitality booking", "2025"],
|
||||||
|
["Plainform", "Brand system", "2024"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"title": "Process",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Read",
|
||||||
|
"text": "We clarify the offer, audience, proof, and moments where a visitor needs confidence."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Compose",
|
||||||
|
"text": "We design the system: type, pacing, components, motion notes, and content hierarchy."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ship",
|
||||||
|
"text": "We build responsive pages with practical handoff, analytics, and room to grow."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"title": "Notes",
|
||||||
|
"eyebrow": "Studio journal",
|
||||||
|
"items": [
|
||||||
|
["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"],
|
||||||
|
["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"],
|
||||||
|
["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"brand": "Kairas",
|
||||||
|
"headline": "Build the site your brand has been waiting for.",
|
||||||
|
"siteUrl": "kairas.io",
|
||||||
|
"email": "hello@kairas.io",
|
||||||
|
"services": "Strategy / Design / Development",
|
||||||
|
"copyright": "© 2026 Kairas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
middleware.ts
Normal file
9
middleware.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import createMiddleware from "next-intl/middleware";
|
||||||
|
import {routing} from "./src/i18n/routing";
|
||||||
|
|
||||||
|
export default createMiddleware(routing);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {};
|
const nextConfig: NextConfig = {};
|
||||||
|
|
||||||
export default nextConfig;
|
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
||||||
|
|
||||||
|
export default withNextIntl(nextConfig);
|
||||||
|
|||||||
709
package-lock.json
generated
709
package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"animejs": "^4.4.1",
|
"animejs": "^4.4.1",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
|
"next-intl": "^4.11.0",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest"
|
"react-dom": "latest"
|
||||||
},
|
},
|
||||||
@@ -467,6 +468,36 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-Ocd1vPuD68rW6BJDuAOtnnc1GPeVepY5kZXML1psGVFQ+1Q8CfkftT3Tnam+Mxx97Pz08jIEDCotl/GV+Naccg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "3.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.6.tgz",
|
||||||
|
"integrity": "sha512-04ZjRIeQCnR/h32wBP9/S7rkyy1hLAs2fXJcNwc7hseJd//K9TMBqK0ukb4dXqnALKQ9m5ruZeOD2qqEkK9ixg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/icu-skeleton-parser": "2.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "2.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.6.tgz",
|
||||||
|
"integrity": "sha512-9f1VQ2kaaLHK0WPU1OrAmiNKCKJwyoDmwNzQXbUa6XtFBOgHZ4YZURE8sSedHmMr0kvpB75OtplB0hMYkfdwfg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.5.tgz",
|
||||||
|
"integrity": "sha512-TEW/NR367c3PcQ2AXfkNig9jC740+qbkM0LgKl7UCE7Xtv7C5Uk1mvlu86MjQZBmscUai8HSWjcEETpwaVvJ6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "3.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@@ -1254,6 +1285,313 @@
|
|||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@parcel/watcher": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.3",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"node-addon-api": "^7.0.0",
|
||||||
|
"picomatch": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-android-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-ia32": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher/node_modules/picomatch": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
@@ -1261,6 +1599,210 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@schummar/icu-type-parser": {
|
||||||
|
"version": "1.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
|
||||||
|
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-arm64": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-x64": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-musl": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-ppc64-gnu": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-s390x-gnu": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-gnu": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-musl": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-x64-msvc": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/counter": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.15",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
@@ -1270,6 +1812,15 @@
|
|||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@swc/types": {
|
||||||
|
"version": "0.1.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
|
||||||
|
"integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz",
|
||||||
@@ -2825,7 +3376,6 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -3926,6 +4476,21 @@
|
|||||||
"hermes-estree": "0.25.1"
|
"hermes-estree": "0.25.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/icu-minify": {
|
||||||
|
"version": "4.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.11.0.tgz",
|
||||||
|
"integrity": "sha512-XRvblCwLqWXio5ZLcmDqXvJv7alSACK6UjXuuMOdQWB//d25AQX6xlVlI1FEbc3Q6iPLXXo6HaVLn8LcAFhn1Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/icu-messageformat-parser": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -3978,6 +4543,16 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "11.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.3.tgz",
|
||||||
|
"integrity": "sha512-kZthTU+3WLcoWoRg5j6LOkN1TeUBtmkX0OIwSAbcHVIfQAEbGVdmANM8u6GL3eUDOqLwheYoXMUshAh1UdeXlQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "3.1.3",
|
||||||
|
"@formatjs/icu-messageformat-parser": "3.5.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||||
@@ -4140,7 +4715,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -4186,7 +4760,6 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@@ -4984,6 +5557,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz",
|
||||||
@@ -5037,6 +5619,94 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-intl": {
|
||||||
|
"version": "4.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.11.0.tgz",
|
||||||
|
"integrity": "sha512-Chp8rgEVUYOX/bCtYy+PXH6lDX3X+GPT9sR9HScHroL283em/4urP9btfdHEMEHJJXdq2W/5wDaDDtWONPdNSA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "^0.8.1",
|
||||||
|
"@parcel/watcher": "^2.4.1",
|
||||||
|
"@swc/core": "^1.15.2",
|
||||||
|
"icu-minify": "^4.11.0",
|
||||||
|
"negotiator": "^1.0.0",
|
||||||
|
"next-intl-swc-plugin-extractor": "^4.11.0",
|
||||||
|
"po-parser": "^2.1.1",
|
||||||
|
"use-intl": "^4.11.0"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-intl-swc-plugin-extractor": {
|
||||||
|
"version": "4.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.11.0.tgz",
|
||||||
|
"integrity": "sha512-WUGBSxGNd8eQ0rAsJHFmRw2H7+SZAXQIY/HAnYM57JaUsj5D2vx4KOz4zFtXlyKDtsw9awHfgWVvBae2/RDF9A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/next-intl/node_modules/@swc/core": {
|
||||||
|
"version": "1.15.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.33.tgz",
|
||||||
|
"integrity": "sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3",
|
||||||
|
"@swc/types": "^0.1.26"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/swc"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@swc/core-darwin-arm64": "1.15.33",
|
||||||
|
"@swc/core-darwin-x64": "1.15.33",
|
||||||
|
"@swc/core-linux-arm-gnueabihf": "1.15.33",
|
||||||
|
"@swc/core-linux-arm64-gnu": "1.15.33",
|
||||||
|
"@swc/core-linux-arm64-musl": "1.15.33",
|
||||||
|
"@swc/core-linux-ppc64-gnu": "1.15.33",
|
||||||
|
"@swc/core-linux-s390x-gnu": "1.15.33",
|
||||||
|
"@swc/core-linux-x64-gnu": "1.15.33",
|
||||||
|
"@swc/core-linux-x64-musl": "1.15.33",
|
||||||
|
"@swc/core-win32-arm64-msvc": "1.15.33",
|
||||||
|
"@swc/core-win32-ia32-msvc": "1.15.33",
|
||||||
|
"@swc/core-win32-x64-msvc": "1.15.33"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@swc/helpers": ">=0.5.17"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@swc/helpers": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||||
|
"version": "0.5.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz",
|
||||||
|
"integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==",
|
||||||
|
"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",
|
||||||
@@ -5065,6 +5735,12 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/node-exports-info": {
|
"node_modules/node-exports-info": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
|
||||||
@@ -5341,6 +6017,12 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/po-parser": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||||
@@ -6434,6 +7116,27 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-intl": {
|
||||||
|
"version": "4.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.11.0.tgz",
|
||||||
|
"integrity": "sha512-7ILhTLuo3fnSKhoTGDk5X9591pjtWr6qB4inrlvGkN9OEyKhoiG73GZFoLSs68wz3BsSGtoWa62iWvrYEYU+iA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "^3.1.0",
|
||||||
|
"@schummar/icu-type-parser": "1.21.5",
|
||||||
|
"icu-minify": "^4.11.0",
|
||||||
|
"intl-messageformat": "^11.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"animejs": "^4.4.1",
|
"animejs": "^4.4.1",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
|
"next-intl": "^4.11.0",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest"
|
"react-dom": "latest"
|
||||||
},
|
},
|
||||||
|
|||||||
80
src/app/[locale]/layout.tsx
Normal file
80
src/app/[locale]/layout.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import type {Metadata} from "next";
|
||||||
|
import {NextIntlClientProvider, hasLocale} from "next-intl";
|
||||||
|
import {notFound} from "next/navigation";
|
||||||
|
import "../globals.css";
|
||||||
|
import {routing, type Locale} from "@/i18n/routing";
|
||||||
|
|
||||||
|
type LocaleLayoutProps = Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: Promise<{locale: string}>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type LocaleMessages = {
|
||||||
|
Meta: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const alternates = Object.fromEntries([
|
||||||
|
...routing.locales.map((locale) => [locale, `/${locale}`]),
|
||||||
|
["x-default", `/${routing.defaultLocale}`],
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function getLocaleMessages(locale: Locale): Promise<LocaleMessages> {
|
||||||
|
return (await import(`../../../messages/${locale}.json`)).default;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return routing.locales.map((locale) => ({locale}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: Pick<LocaleLayoutProps, "params">): Promise<Metadata> {
|
||||||
|
const {locale} = await params;
|
||||||
|
|
||||||
|
if (!hasLocale(routing.locales, locale)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = await getLocaleMessages(locale);
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadataBase: new URL("https://kairas.io"),
|
||||||
|
title: messages.Meta.title,
|
||||||
|
description: messages.Meta.description,
|
||||||
|
alternates: {
|
||||||
|
canonical: `/${locale}`,
|
||||||
|
languages: alternates,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: messages.Meta.title,
|
||||||
|
description: messages.Meta.description,
|
||||||
|
url: `/${locale}`,
|
||||||
|
siteName: "Kairas",
|
||||||
|
locale: locale.replace("-", "_"),
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function LocaleLayout({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: LocaleLayoutProps) {
|
||||||
|
const {locale} = await params;
|
||||||
|
|
||||||
|
if (!hasLocale(routing.locales, locale)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale.split("-")[0]}>
|
||||||
|
<body>
|
||||||
|
<NextIntlClientProvider>{children}</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,41 +1,37 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useRef } from "react";
|
import {useEffect, useRef} from "react";
|
||||||
import { animate, createTimeline, stagger } from "animejs";
|
import {animate, createTimeline, stagger} from "animejs";
|
||||||
|
import {useLocale, useTranslations} from "next-intl";
|
||||||
|
import {Link} from "@/i18n/navigation";
|
||||||
|
import {routing, type Locale} from "@/i18n/routing";
|
||||||
|
|
||||||
const services = [
|
type Service = {
|
||||||
{
|
number: string;
|
||||||
number: "01",
|
title: string;
|
||||||
title: "Brand-led websites",
|
text: string;
|
||||||
text: "Identity, UX, interface design, and front-end systems for companies that need their site to carry the weight of the brand.",
|
};
|
||||||
},
|
|
||||||
{
|
|
||||||
number: "02",
|
|
||||||
title: "Editorial product pages",
|
|
||||||
text: "Launch pages, case studies, and content structures that make complex offers feel considered, useful, and easy to move through.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
number: "03",
|
|
||||||
title: "Design systems",
|
|
||||||
text: "Reusable components, visual rules, and interaction patterns that help teams ship new pages without losing quality.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const work = [
|
type WorkItem = [string, string, string];
|
||||||
["Atelier North", "Architecture portfolio", "2026"],
|
type NoteItem = [string, string, string];
|
||||||
["Vellum Labs", "SaaS website", "2025"],
|
|
||||||
["Morrow House", "Hospitality booking", "2025"],
|
|
||||||
["Plainform", "Brand system", "2024"],
|
|
||||||
];
|
|
||||||
|
|
||||||
const notes = [
|
type ProcessStep = {
|
||||||
["2026.04.18", "Essay", "Designing quieter conversion paths for premium service brands"],
|
title: string;
|
||||||
["2026.03.02", "Studio", "Kairas opens a focused website sprint for early-stage teams"],
|
text: string;
|
||||||
["2026.01.14", "Guide", "What belongs above the fold when the work is the proof"],
|
};
|
||||||
];
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const rootRef = useRef<HTMLElement>(null);
|
const rootRef = useRef<HTMLElement>(null);
|
||||||
|
const locale = useLocale() as Locale;
|
||||||
|
const t = useTranslations("Home");
|
||||||
|
|
||||||
|
const heroWords = t.raw("hero.words") as string[];
|
||||||
|
const tickerItems = t.raw("hero.ticker") as string[];
|
||||||
|
const marqueeItems = t.raw("marquee") as string[];
|
||||||
|
const services = t.raw("services") as Service[];
|
||||||
|
const work = t.raw("work.items") as WorkItem[];
|
||||||
|
const processSteps = t.raw("process.steps") as ProcessStep[];
|
||||||
|
const notes = t.raw("notes.items") as NoteItem[];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = rootRef.current;
|
const root = rootRef.current;
|
||||||
@@ -179,7 +175,7 @@ export default function Home() {
|
|||||||
observer.unobserve(target);
|
observer.unobserve(target);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ threshold: 0.18 },
|
{threshold: 0.18},
|
||||||
);
|
);
|
||||||
|
|
||||||
root.querySelectorAll("[data-reveal]").forEach((element) => {
|
root.querySelectorAll("[data-reveal]").forEach((element) => {
|
||||||
@@ -207,7 +203,7 @@ export default function Home() {
|
|||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
animations.forEach((animation) => animation.revert());
|
animations.forEach((animation) => animation.revert());
|
||||||
};
|
};
|
||||||
}, []);
|
}, [locale]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main
|
<main
|
||||||
@@ -221,40 +217,92 @@ export default function Home() {
|
|||||||
<div className="intro-mask bg-[var(--navy)]" />
|
<div className="intro-mask bg-[var(--navy)]" />
|
||||||
</div>
|
</div>
|
||||||
<header className="fixed left-0 right-0 top-0 z-20 border-b border-[var(--line)] bg-[rgba(238,228,212,0.86)] backdrop-blur">
|
<header className="fixed left-0 right-0 top-0 z-20 border-b border-[var(--line)] bg-[rgba(238,228,212,0.86)] backdrop-blur">
|
||||||
<nav className="mx-auto flex max-w-[1500px] items-center justify-between px-5 py-4 text-[13px] uppercase leading-none tracking-[0.12em] sm:px-8 lg:px-12">
|
<nav className="mx-auto flex max-w-[1500px] items-center justify-between gap-4 px-5 py-4 text-[13px] uppercase leading-none tracking-[0.12em] sm:px-8 lg:px-12">
|
||||||
<a href="#" className="nav-item text-lg font-semibold tracking-[0.18em]">
|
<Link href="/" className="nav-item text-lg font-semibold tracking-[0.18em]">
|
||||||
Kairas
|
Kairas
|
||||||
</a>
|
</Link>
|
||||||
<div className="hidden items-center gap-8 md:flex">
|
<div className="hidden items-center gap-8 md:flex">
|
||||||
<a className="nav-item" href="#studio">Studio</a>
|
<a className="nav-item" href="#studio">
|
||||||
<a className="nav-item" href="#services">Services</a>
|
{t("nav.studio")}
|
||||||
<a className="nav-item" href="#work">Work</a>
|
</a>
|
||||||
<a className="nav-item" href="#notes">Notes</a>
|
<a className="nav-item" href="#services">
|
||||||
|
{t("nav.services")}
|
||||||
|
</a>
|
||||||
|
<a className="nav-item" href="#work">
|
||||||
|
{t("nav.work")}
|
||||||
|
</a>
|
||||||
|
<a className="nav-item" href="#notes">
|
||||||
|
{t("nav.notes")}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="nav-item hidden items-center gap-2 lg:flex">
|
||||||
|
<span className="sr-only">{t("localeSwitcher.label")}</span>
|
||||||
|
{routing.locales.map((option) => (
|
||||||
|
<Link
|
||||||
|
key={option}
|
||||||
|
href="/"
|
||||||
|
locale={option}
|
||||||
|
aria-current={option === locale ? "page" : undefined}
|
||||||
|
style={option === locale ? {color: "var(--paper)"} : undefined}
|
||||||
|
className={`border px-3 py-2 transition ${
|
||||||
|
option === locale
|
||||||
|
? "border-[var(--navy)] bg-[var(--navy)] text-[var(--paper)]"
|
||||||
|
: "border-[var(--line)] hover:border-[var(--navy)]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{option.split("-")[1].toUpperCase()}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="mailto:hello@kairas.io"
|
href="mailto:hello@kairas.io"
|
||||||
className="nav-item border border-[var(--navy)] px-4 py-3 transition hover:bg-[var(--navy)] hover:text-[var(--paper)]"
|
className="nav-item border border-[var(--navy)] px-4 py-3 transition hover:bg-[var(--navy)] hover:text-[var(--paper)]"
|
||||||
>
|
>
|
||||||
hello@kairas.io
|
{t("nav.contact")}
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div className="flex items-center justify-center gap-2 border-t border-[var(--line)] px-5 py-2 text-[11px] uppercase leading-none tracking-[0.12em] lg:hidden">
|
||||||
|
<span className="text-[var(--ink-muted)]">
|
||||||
|
{t("localeSwitcher.label")}
|
||||||
|
</span>
|
||||||
|
{routing.locales.map((option) => (
|
||||||
|
<Link
|
||||||
|
key={option}
|
||||||
|
href="/"
|
||||||
|
locale={option}
|
||||||
|
aria-current={option === locale ? "page" : undefined}
|
||||||
|
style={option === locale ? {color: "var(--paper)"} : undefined}
|
||||||
|
className={`border px-3 py-2 transition ${
|
||||||
|
option === locale
|
||||||
|
? "border-[var(--navy)] bg-[var(--navy)] text-[var(--paper)]"
|
||||||
|
: "border-[var(--line)] hover:border-[var(--navy)]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{option.split("-")[1].toUpperCase()}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section className="relative z-10 mx-auto grid min-h-screen max-w-[1500px] content-end gap-12 px-5 pb-12 pt-28 sm:px-8 md:pt-36 lg:grid-cols-[1.05fr_0.95fr] lg:px-12">
|
<section className="relative z-10 mx-auto grid min-h-screen max-w-[1500px] content-end gap-12 px-5 pb-12 pt-40 sm:px-8 md:pt-36 lg:grid-cols-[1.05fr_0.95fr] lg:px-12">
|
||||||
<div className="flex flex-col justify-end gap-8">
|
<div className="flex flex-col justify-end gap-8">
|
||||||
<div className="hero-kicker flex max-w-sm flex-wrap items-baseline gap-x-3 gap-y-1 text-sm leading-6 text-[var(--navy-soft)]">
|
<div className="hero-kicker flex max-w-sm flex-wrap items-baseline gap-x-3 gap-y-1 text-sm leading-6 text-[var(--navy-soft)]">
|
||||||
<span className="uppercase tracking-[0.14em]">Web design firm</span>
|
<span className="uppercase tracking-[0.14em]">
|
||||||
|
{t("hero.kicker")}
|
||||||
|
</span>
|
||||||
<a
|
<a
|
||||||
href="https://kairas.io"
|
href="https://kairas.io"
|
||||||
className="font-semibold italic tracking-normal"
|
className="font-semibold italic tracking-normal"
|
||||||
>
|
>
|
||||||
kairas.io
|
{t("hero.siteUrl")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="max-w-5xl overflow-hidden text-7xl font-semibold leading-[0.86] tracking-normal sm:text-8xl md:text-9xl lg:text-[10rem] xl:text-[12rem]">
|
<h1 className="max-w-5xl overflow-hidden text-7xl font-semibold leading-[0.86] tracking-normal sm:text-8xl md:text-9xl lg:text-[10rem] xl:text-[12rem]">
|
||||||
<span className="hero-word motion-hidden">Websites</span>
|
{heroWords.map((word) => (
|
||||||
<span className="hero-word motion-hidden">with quiet</span>
|
<span key={word} className="hero-word motion-hidden">
|
||||||
<span className="hero-word motion-hidden">force.</span>
|
{word}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -262,18 +310,20 @@ export default function Home() {
|
|||||||
<div className="hero-card motion-hidden relative min-h-[420px] overflow-hidden border border-[var(--line)] bg-[var(--paper)] p-4 shadow-[0_18px_60px_rgba(8,27,51,0.08)]">
|
<div className="hero-card motion-hidden relative min-h-[420px] overflow-hidden border border-[var(--line)] bg-[var(--paper)] p-4 shadow-[0_18px_60px_rgba(8,27,51,0.08)]">
|
||||||
<div className="grid h-full min-h-[388px] grid-rows-[auto_1fr_auto] border border-[var(--line)] bg-[var(--beige)]">
|
<div className="grid h-full min-h-[388px] grid-rows-[auto_1fr_auto] border border-[var(--line)] bg-[var(--beige)]">
|
||||||
<div className="flex items-center justify-between border-b border-[var(--line)] px-4 py-3 text-xs uppercase tracking-[0.16em]">
|
<div className="flex items-center justify-between border-b border-[var(--line)] px-4 py-3 text-xs uppercase tracking-[0.16em]">
|
||||||
<span>Selected interface</span>
|
<span>{t("hero.interfaceLabel")}</span>
|
||||||
<span>01 / 04</span>
|
<span>01 / 04</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-[1fr_0.72fr] gap-3 p-4">
|
<div className="grid grid-cols-[1fr_0.72fr] gap-3 p-4">
|
||||||
<div className="float-piece flex flex-col justify-between bg-[var(--navy)] p-5 text-[var(--paper)]">
|
<div className="float-piece flex flex-col justify-between bg-[var(--navy)] p-5 text-[var(--paper)]">
|
||||||
<span className="text-xs uppercase tracking-[0.18em] text-[#d9ccb8]">
|
<span className="text-xs uppercase tracking-[0.18em] text-[#d9ccb8]">
|
||||||
Brand system
|
{t("hero.brandLabel")}
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<p className="spin-mark inline-block text-5xl font-semibold leading-none">KA</p>
|
<p className="spin-mark inline-block text-5xl font-semibold leading-none">
|
||||||
|
KA
|
||||||
|
</p>
|
||||||
<p className="mt-3 max-w-[14rem] text-sm leading-6 text-[#d9ccb8]">
|
<p className="mt-3 max-w-[14rem] text-sm leading-6 text-[#d9ccb8]">
|
||||||
Layouts tuned for calm reading and decisive action.
|
{t("hero.interfaceCopy")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -290,33 +340,27 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between border-t border-[var(--line)] px-4 py-3 text-xs uppercase tracking-[0.16em]">
|
<div className="flex items-center justify-between border-t border-[var(--line)] px-4 py-3 text-xs uppercase tracking-[0.16em]">
|
||||||
<span className="ticker-item">Strategy</span>
|
{tickerItems.map((item) => (
|
||||||
<span className="ticker-item">Design</span>
|
<span key={item} className="ticker-item">
|
||||||
<span className="ticker-item">Build</span>
|
{item}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="hero-copy motion-hidden max-w-xl text-xl leading-9 text-[var(--navy-soft)]">
|
<p className="hero-copy motion-hidden max-w-xl text-xl leading-9 text-[var(--navy-soft)]">
|
||||||
Kairas designs and builds refined websites for founders, studios,
|
{t("hero.copy")}
|
||||||
and service brands that care about taste, clarity, and commercial
|
|
||||||
performance.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div className="marquee relative z-10 border-y border-[var(--line)] bg-[var(--navy)] py-5 text-[var(--paper)]">
|
<div className="marquee relative z-10 border-y border-[var(--line)] bg-[var(--navy)] py-5 text-[var(--paper)]">
|
||||||
<div className="marquee-track text-4xl italic leading-none sm:text-6xl">
|
<div className="marquee-track text-4xl italic leading-none sm:text-6xl">
|
||||||
{Array.from({ length: 2 }).map((_, group) => (
|
{Array.from({length: 2}).map((_, group) => (
|
||||||
<div key={group} className="flex shrink-0 items-center gap-8 pr-8">
|
<div key={group} className="flex shrink-0 items-center gap-8 pr-8">
|
||||||
<span>Strategy</span>
|
{marqueeItems.map((item) => (
|
||||||
<span>/</span>
|
<span key={`${group}-${item}`}>{item}</span>
|
||||||
<span>Design</span>
|
))}
|
||||||
<span>/</span>
|
|
||||||
<span>Development</span>
|
|
||||||
<span>/</span>
|
|
||||||
<span>Systems</span>
|
|
||||||
<span>/</span>
|
|
||||||
<span>Launch</span>
|
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -329,24 +373,29 @@ export default function Home() {
|
|||||||
className="motion-hidden relative z-10 border-y border-[var(--line)] bg-[var(--paper)]"
|
className="motion-hidden relative z-10 border-y border-[var(--line)] bg-[var(--paper)]"
|
||||||
>
|
>
|
||||||
<div className="mx-auto grid max-w-[1500px] gap-10 px-5 py-20 sm:px-8 lg:grid-cols-[0.7fr_1.3fr] lg:px-12 lg:py-28">
|
<div className="mx-auto grid max-w-[1500px] gap-10 px-5 py-20 sm:px-8 lg:grid-cols-[0.7fr_1.3fr] lg:px-12 lg:py-28">
|
||||||
<p className="text-sm uppercase tracking-[0.16em]">About us</p>
|
<p className="text-sm uppercase tracking-[0.16em]">
|
||||||
|
{t("studio.eyebrow")}
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="max-w-4xl text-4xl font-semibold leading-tight sm:text-6xl">
|
<h2 className="max-w-4xl text-4xl font-semibold leading-tight sm:text-6xl">
|
||||||
We shape digital homes for brands that need to feel exact,
|
{t("studio.title")}
|
||||||
intentional, and easy to trust.
|
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-8 max-w-2xl text-lg leading-8 text-[var(--ink-muted)]">
|
<p className="mt-8 max-w-2xl text-lg leading-8 text-[var(--ink-muted)]">
|
||||||
Our work sits between design studio and front-end craft. We use
|
{t("studio.copy")}
|
||||||
strong typography, generous pacing, and disciplined systems to
|
|
||||||
make every page feel composed without becoming static.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="services" data-reveal className="motion-hidden relative z-10 mx-auto max-w-[1500px] px-5 py-20 sm:px-8 lg:px-12 lg:py-28">
|
<section
|
||||||
|
id="services"
|
||||||
|
data-reveal
|
||||||
|
className="motion-hidden relative z-10 mx-auto max-w-[1500px] px-5 py-20 sm:px-8 lg:px-12 lg:py-28"
|
||||||
|
>
|
||||||
<div className="mb-12 flex items-end justify-between border-b border-[var(--line)] pb-6">
|
<div className="mb-12 flex items-end justify-between border-b border-[var(--line)] pb-6">
|
||||||
<h2 className="text-5xl font-semibold sm:text-7xl">Services</h2>
|
<h2 className="text-5xl font-semibold sm:text-7xl">
|
||||||
|
{t("servicesTitle")}
|
||||||
|
</h2>
|
||||||
<span className="text-sm uppercase tracking-[0.16em]">03</span>
|
<span className="text-sm uppercase tracking-[0.16em]">03</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-0 border-t border-[var(--line)]">
|
<div className="grid gap-0 border-t border-[var(--line)]">
|
||||||
@@ -355,7 +404,9 @@ export default function Home() {
|
|||||||
key={service.title}
|
key={service.title}
|
||||||
className="grid gap-6 border-b border-[var(--line)] py-9 md:grid-cols-[0.22fr_0.78fr] lg:grid-cols-[0.18fr_0.38fr_0.44fr]"
|
className="grid gap-6 border-b border-[var(--line)] py-9 md:grid-cols-[0.22fr_0.78fr] lg:grid-cols-[0.18fr_0.38fr_0.44fr]"
|
||||||
>
|
>
|
||||||
<span className="text-sm tracking-[0.16em]">{service.number}</span>
|
<span className="text-sm tracking-[0.16em]">
|
||||||
|
{service.number}
|
||||||
|
</span>
|
||||||
<h3 className="text-3xl font-semibold">{service.title}</h3>
|
<h3 className="text-3xl font-semibold">{service.title}</h3>
|
||||||
<p className="max-w-2xl text-lg leading-8 text-[var(--ink-muted)]">
|
<p className="max-w-2xl text-lg leading-8 text-[var(--ink-muted)]">
|
||||||
{service.text}
|
{service.text}
|
||||||
@@ -366,14 +417,18 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="work" data-reveal className="motion-hidden relative z-10 bg-[var(--navy)] text-[var(--paper)]">
|
<section
|
||||||
|
id="work"
|
||||||
|
data-reveal
|
||||||
|
className="motion-hidden relative z-10 bg-[var(--navy)] text-[var(--paper)]"
|
||||||
|
>
|
||||||
<div className="mx-auto grid max-w-[1500px] gap-12 px-5 py-20 sm:px-8 lg:grid-cols-[0.42fr_0.58fr] lg:px-12 lg:py-28">
|
<div className="mx-auto grid max-w-[1500px] gap-12 px-5 py-20 sm:px-8 lg:grid-cols-[0.42fr_0.58fr] lg:px-12 lg:py-28">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm uppercase tracking-[0.16em] text-[#d9ccb8]">
|
<p className="text-sm uppercase tracking-[0.16em] text-[#d9ccb8]">
|
||||||
Selected work
|
{t("work.eyebrow")}
|
||||||
</p>
|
</p>
|
||||||
<h2 className="mt-6 max-w-lg text-5xl font-semibold leading-none sm:text-7xl">
|
<h2 className="mt-6 max-w-lg text-5xl font-semibold leading-none sm:text-7xl">
|
||||||
Places where the brand can breathe.
|
{t("work.title")}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid content-start border-t border-[rgba(247,241,231,0.24)]">
|
<div className="grid content-start border-t border-[rgba(247,241,231,0.24)]">
|
||||||
@@ -396,34 +451,40 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section data-reveal className="motion-hidden relative z-10 mx-auto grid max-w-[1500px] gap-10 px-5 py-20 sm:px-8 lg:grid-cols-[0.35fr_0.65fr] lg:px-12 lg:py-28">
|
<section
|
||||||
<h2 className="text-5xl font-semibold sm:text-7xl">Process</h2>
|
data-reveal
|
||||||
|
className="motion-hidden relative z-10 mx-auto grid max-w-[1500px] gap-10 px-5 py-20 sm:px-8 lg:grid-cols-[0.35fr_0.65fr] lg:px-12 lg:py-28"
|
||||||
|
>
|
||||||
|
<h2 className="text-5xl font-semibold sm:text-7xl">
|
||||||
|
{t("process.title")}
|
||||||
|
</h2>
|
||||||
<div className="grid gap-8 md:grid-cols-3">
|
<div className="grid gap-8 md:grid-cols-3">
|
||||||
{["Read", "Compose", "Ship"].map((step, index) => (
|
{processSteps.map((step, index) => (
|
||||||
<div key={step} className="border-t border-[var(--line)] pt-5">
|
<div key={step.title} className="border-t border-[var(--line)] pt-5">
|
||||||
<span className="text-sm tracking-[0.16em]">
|
<span className="text-sm tracking-[0.16em]">
|
||||||
{String(index + 1).padStart(2, "0")}
|
{String(index + 1).padStart(2, "0")}
|
||||||
</span>
|
</span>
|
||||||
<h3 className="mt-8 text-3xl font-semibold">{step}</h3>
|
<h3 className="mt-8 text-3xl font-semibold">{step.title}</h3>
|
||||||
<p className="mt-4 leading-7 text-[var(--ink-muted)]">
|
<p className="mt-4 leading-7 text-[var(--ink-muted)]">
|
||||||
{index === 0 &&
|
{step.text}
|
||||||
"We clarify the offer, audience, proof, and moments where a visitor needs confidence."}
|
|
||||||
{index === 1 &&
|
|
||||||
"We design the system: type, pacing, components, motion notes, and content hierarchy."}
|
|
||||||
{index === 2 &&
|
|
||||||
"We build responsive pages with practical handoff, analytics, and room to grow."}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="notes" data-reveal className="motion-hidden relative z-10 border-y border-[var(--line)] bg-[var(--paper)]">
|
<section
|
||||||
|
id="notes"
|
||||||
|
data-reveal
|
||||||
|
className="motion-hidden relative z-10 border-y border-[var(--line)] bg-[var(--paper)]"
|
||||||
|
>
|
||||||
<div className="mx-auto grid max-w-[1500px] gap-10 px-5 py-20 sm:px-8 lg:grid-cols-[0.35fr_0.65fr] lg:px-12 lg:py-28">
|
<div className="mx-auto grid max-w-[1500px] gap-10 px-5 py-20 sm:px-8 lg:grid-cols-[0.35fr_0.65fr] lg:px-12 lg:py-28">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-5xl font-semibold sm:text-7xl">Notes</h2>
|
<h2 className="text-5xl font-semibold sm:text-7xl">
|
||||||
|
{t("notes.title")}
|
||||||
|
</h2>
|
||||||
<p className="mt-5 text-sm uppercase tracking-[0.16em] text-[var(--ink-muted)]">
|
<p className="mt-5 text-sm uppercase tracking-[0.16em] text-[var(--ink-muted)]">
|
||||||
Studio journal
|
{t("notes.eyebrow")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-[var(--line)]">
|
<div className="border-t border-[var(--line)]">
|
||||||
@@ -450,16 +511,18 @@ export default function Home() {
|
|||||||
className="motion-hidden relative z-10 mx-auto grid max-w-[1500px] gap-12 px-5 py-16 sm:px-8 lg:grid-cols-[1fr_auto] lg:px-12"
|
className="motion-hidden relative z-10 mx-auto grid max-w-[1500px] gap-12 px-5 py-16 sm:px-8 lg:grid-cols-[1fr_auto] lg:px-12"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm uppercase tracking-[0.16em]">Kairas</p>
|
<p className="text-sm uppercase tracking-[0.16em]">
|
||||||
|
{t("footer.brand")}
|
||||||
|
</p>
|
||||||
<h2 className="mt-5 max-w-4xl text-5xl font-semibold leading-none sm:text-8xl">
|
<h2 className="mt-5 max-w-4xl text-5xl font-semibold leading-none sm:text-8xl">
|
||||||
Build the site your brand has been waiting for.
|
{t("footer.headline")}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid content-between gap-10 text-sm uppercase tracking-[0.14em] lg:text-right">
|
<div className="grid content-between gap-10 text-sm uppercase tracking-[0.14em] lg:text-right">
|
||||||
<a href="https://kairas.io">kairas.io</a>
|
<a href="https://kairas.io">{t("footer.siteUrl")}</a>
|
||||||
<a href="mailto:hello@kairas.io">hello@kairas.io</a>
|
<a href="mailto:hello@kairas.io">{t("footer.email")}</a>
|
||||||
<p>Strategy / Design / Development</p>
|
<p>{t("footer.services")}</p>
|
||||||
<p>© 2026 Kairas</p>
|
<p>{t("footer.copyright")}</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import type { Metadata } from "next";
|
|
||||||
import "./globals.css";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
metadataBase: new URL("https://kairas.io"),
|
|
||||||
title: "Kairas | Web design studio",
|
|
||||||
description:
|
|
||||||
"Kairas is a web design firm crafting refined websites, brand systems, and digital products.",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body>{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
6
src/i18n/navigation.ts
Normal file
6
src/i18n/navigation.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import {createNavigation} from "next-intl/navigation";
|
||||||
|
import {routing} from "./routing";
|
||||||
|
|
||||||
|
export const {Link, redirect, usePathname, useRouter, getPathname} =
|
||||||
|
createNavigation(routing);
|
||||||
|
|
||||||
16
src/i18n/request.ts
Normal file
16
src/i18n/request.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {getRequestConfig} from "next-intl/server";
|
||||||
|
import {hasLocale} from "next-intl";
|
||||||
|
import {routing} from "./routing";
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({requestLocale}) => {
|
||||||
|
const requested = await requestLocale;
|
||||||
|
const locale = hasLocale(routing.locales, requested)
|
||||||
|
? requested
|
||||||
|
: routing.defaultLocale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages: (await import(`../../messages/${locale}.json`)).default,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
11
src/i18n/routing.ts
Normal file
11
src/i18n/routing.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import {defineRouting} from "next-intl/routing";
|
||||||
|
|
||||||
|
export const routing = defineRouting({
|
||||||
|
locales: ["en-ie", "en-gb", "en-us"],
|
||||||
|
defaultLocale: "en-ie",
|
||||||
|
localePrefix: "always",
|
||||||
|
localeDetection: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Locale = (typeof routing.locales)[number];
|
||||||
|
|
||||||
Reference in New Issue
Block a user