diff --git a/AGENTS.md b/AGENTS.md
index b00b5ca..90216d1 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -15,15 +15,15 @@ This block is written and re-added by `next dev` — verify at `node_modules/nex
- **Dynamic Source:** All content MUST come dynamically from Payload CMS (collections, globals like `Company` and `Navigation`, block fields, seed script, or `@intecion/ipal-kit`).
- If a prop is optional, render it conditionally only when present (`{prop && {prop}}`), NEVER provide fallback hardcoded Polish or English text inside JSX.
-## 2. BRANDING & UI GUIDELINES (KÄRCHER STYLE)
-- Colors:
- - Primary Yellow: `#FFED00` (Hover: `#FAE100`)
- - Dark Anthracite: `#1F1F1F`
- - Deep Black: `#141414`
- - Industrial Light Gray: `#F4F4F4`
+## 2. BRANDING & UI GUIDELINES
+- Colors (Managed centrally in `src/app/(frontend)/theme.css`):
+ - Primary Cyan: `#01aeee` (Hover: `#0098d4` / class `bg-primary`, `text-primary`, `border-primary`)
+ - Accent Red: `#ee1d23` (Hover: `#d9141a` / class `bg-accent`, `text-accent`, `border-accent`)
+ - Dark Anthracite/Black: `#231f20` (Hover: `#332d2e` / class `bg-dark`, `text-dark`, `border-dark`)
+ - Industrial Light Gray: `#f4f4f4` (class `bg-surface-gray`)
- Architecture:
- - Sharp, geometric corners (`rounded-none` / `0px radius`). No bubbly rounded-3xl or generic AI circles.
- - Checkboxes: Square with 2px black border, yellow `#FFED00` checked background, and thick black checkmark (`stroke-[3]`).
+ - Sharp, geometric corners (`rounded-none` / `0px radius`). No bubbly rounded-3xl or generic circles.
+ - Checkboxes: Square with 2px dark border (`border-dark`), primary cyan (`bg-primary`) checked background, and thick dark checkmark (`stroke-[3] text-dark`).
- High contrast typography: Uppercase, bold/black weights with letter-spacing.
## 3. UX & ACCESSIBILITY
diff --git a/public/images/logo-icon.webp b/public/images/logo-icon.webp
new file mode 100644
index 0000000..821dd0f
Binary files /dev/null and b/public/images/logo-icon.webp differ
diff --git a/public/images/logo-white.webp b/public/images/logo-white.webp
new file mode 100644
index 0000000..a0443d8
Binary files /dev/null and b/public/images/logo-white.webp differ
diff --git a/public/images/logo.webp b/public/images/logo.webp
new file mode 100644
index 0000000..64f0bfb
Binary files /dev/null and b/public/images/logo.webp differ
diff --git a/public/images/paving-hero.jpg b/public/images/paving-hero.jpg
deleted file mode 100644
index d942e74..0000000
Binary files a/public/images/paving-hero.jpg and /dev/null differ
diff --git a/public/images/paving-parallax.webp b/public/images/paving-parallax.webp
new file mode 100644
index 0000000..7c11c6d
Binary files /dev/null and b/public/images/paving-parallax.webp differ
diff --git a/public/images/process-parallax.webp b/public/images/process-parallax.webp
new file mode 100644
index 0000000..6015a72
Binary files /dev/null and b/public/images/process-parallax.webp differ
diff --git a/src/app/(frontend)/[locale]/layout.tsx b/src/app/(frontend)/[locale]/layout.tsx
index 47d821e..67802cc 100644
--- a/src/app/(frontend)/[locale]/layout.tsx
+++ b/src/app/(frontend)/[locale]/layout.tsx
@@ -58,18 +58,6 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
// fallback if globals not yet created
}
- const privacyHref = privacyPage
- ? getSystemPagePath({ config: i18nConfig, locale, page: privacyPage })
- : undefined
-
- const cookieHref = cookiePage
- ? getSystemPagePath({ config: i18nConfig, locale, page: cookiePage })
- : undefined
-
- const homeHref = homePage
- ? getSystemPagePath({ config: i18nConfig, locale, page: homePage }) || '/'
- : '/'
-
const [texts, analytics] = await Promise.all([
getConsentTexts({
config: i18nConfig,
@@ -83,19 +71,69 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
getAnalyticsConfig(payload),
])
+ const privacyPath = privacyPage
+ ? getSystemPagePath({ config: i18nConfig, locale, page: privacyPage })
+ : undefined
+ const privacyHref = privacyPath || `/${locale}/polityka-prywatnosci`
+ const privacyLabel =
+ (typeof privacyPage === 'object' && privacyPage && 'title' in privacyPage && privacyPage.title
+ ? (privacyPage.title as string)
+ : texts.privacyLink?.label) || (locale === 'pl' ? 'Polityka prywatności' : 'Privacy Policy')
+
+ const cookiePath = cookiePage
+ ? getSystemPagePath({ config: i18nConfig, locale, page: cookiePage })
+ : undefined
+ const cookieHref = cookiePath || `/${locale}/polityka-cookies`
+ const cookieLabel =
+ (typeof cookiePage === 'object' && cookiePage && 'title' in cookiePage && cookiePage.title
+ ? (cookiePage.title as string)
+ : undefined) || (locale === 'pl' ? 'Polityka cookies' : 'Cookie Policy')
+
+ const termsPage = (settings as { termsOfService?: unknown })?.termsOfService
+ const termsPath = termsPage
+ ? getSystemPagePath({ config: i18nConfig, locale, page: termsPage })
+ : undefined
+ const termsHref = termsPath || `/${locale}/regulamin`
+ const termsLabel =
+ (typeof termsPage === 'object' && termsPage && 'title' in termsPage && termsPage.title
+ ? (termsPage.title as string)
+ : undefined) || (locale === 'pl' ? 'Regulamin strony' : 'Terms of Service')
+
+ const homeHref = homePage
+ ? getSystemPagePath({ config: i18nConfig, locale, page: homePage }) || '/'
+ : '/'
+
const headerItems: NavItem[] =
- navigation?.headerItems?.map((item) => ({
- id: item.id || undefined,
- label: item.label,
- href: resolveNavItemHref(item),
- })) || []
+ navigation?.headerItems
+ ?.filter((item) => {
+ if (!item.label) return false
+ if (item.url) return true
+ if (item.page && typeof item.page === 'object' && 'slug' in item.page && Boolean(item.page.slug)) {
+ return true
+ }
+ return false
+ })
+ .map((item) => ({
+ id: item.id || undefined,
+ label: item.label,
+ href: resolveNavItemHref(item),
+ })) || []
const footerItems: FooterNavItem[] =
- navigation?.footerItems?.map((item) => ({
- id: item.id || undefined,
- label: item.label,
- href: resolveNavItemHref(item),
- })) || []
+ navigation?.footerItems
+ ?.filter((item) => {
+ if (!item.label) return false
+ if (item.url) return true
+ if (item.page && typeof item.page === 'object' && 'slug' in item.page && Boolean(item.page.slug)) {
+ return true
+ }
+ return false
+ })
+ .map((item) => ({
+ id: item.id || undefined,
+ label: item.label,
+ href: resolveNavItemHref(item),
+ })) || []
const contactHref =
navigation?.ctaButton?.url ||
@@ -104,7 +142,7 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
return (
-
+
-
+
diff --git a/src/app/(frontend)/styles.css b/src/app/(frontend)/styles.css
index eb00681..34adb65 100644
--- a/src/app/(frontend)/styles.css
+++ b/src/app/(frontend)/styles.css
@@ -1,22 +1,7 @@
@import "tailwindcss";
@source "../../../node_modules/@intecion/ipal-kit/dist/**/*.js";
-@theme {
- --color-karcher-yellow: #ffed00;
- --color-karcher-yellow-hover: #fae100;
- --color-karcher-dark: #1f1f1f;
- --color-karcher-black: #141414;
- --color-karcher-gray: #f4f4f4;
- --color-karcher-border: #e0e0e0;
- --color-karcher-text: #222222;
- --color-karcher-muted: #666666;
-}
-
-:root {
- --ipal-primary: #ffed00;
- --ipal-primary-hover: #fae100;
- --ipal-radius: 0px;
-}
+@import "./theme.css";
html {
scroll-behavior: smooth;
@@ -26,7 +11,7 @@ html {
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: #ffffff;
- color: #222222;
+ color: var(--color-brand-dark);
}
h1, h2, h3, h4, .font-karcher {
diff --git a/src/app/(frontend)/theme.css b/src/app/(frontend)/theme.css
new file mode 100644
index 0000000..cbfe9c0
--- /dev/null
+++ b/src/app/(frontend)/theme.css
@@ -0,0 +1,33 @@
+/* Centralized Brand Color Palette & Theme Tokens */
+@theme {
+ /* Core 3 Brand Colors */
+ --color-brand-cyan: #01aeee;
+ --color-brand-cyan-hover: #0098d4;
+ --color-brand-red: #ee1d23;
+ --color-brand-red-hover: #d9141a;
+ --color-brand-dark: #231f20;
+ --color-brand-dark-hover: #332d2e;
+
+ /* Semantic Aliases */
+ --color-primary: #01aeee;
+ --color-primary-hover: #0098d4;
+ --color-accent: #ee1d23;
+ --color-accent-hover: #d9141a;
+ --color-dark: #231f20;
+ --color-dark-hover: #332d2e;
+
+ /* Neutrals & Industrial Surfaces */
+ --color-surface-white: #ffffff;
+ --color-surface-gray: #f4f4f4;
+ --color-surface-gray-alt: #f8f8f8;
+ --color-surface-border: #e0e0e0;
+ --color-muted-text: #666666;
+}
+
+:root {
+ /* IPAL Kit / Framework Variables */
+ --ipal-primary: #01aeee;
+ --ipal-primary-hover: #0098d4;
+ --ipal-secondary: #ee1d23;
+ --ipal-radius: 0px;
+}
diff --git a/src/app/actions/submitFormAction.ts b/src/app/actions/submitFormAction.ts
new file mode 100644
index 0000000..85b5a1d
--- /dev/null
+++ b/src/app/actions/submitFormAction.ts
@@ -0,0 +1,49 @@
+'use server'
+
+import { submitForm } from '@intecion/ipal-kit/server'
+import { getCachedPayload } from '@/lib/content'
+import { getNotificationTexts, resolveFormMessage } from '@intecion/ipal-kit'
+import { headers } from 'next/headers'
+
+export async function submitFormAction({
+ formId,
+ data,
+ locale = 'pl',
+}: {
+ formId: string | number
+ data: Record
+ locale?: string
+}) {
+ try {
+ const payload = await getCachedPayload()
+ const headerList = await headers()
+ const ip = headerList.get('x-forwarded-for')?.split(',')[0]?.trim() || headerList.get('x-real-ip') || '127.0.0.1'
+
+ const result = await submitForm({
+ payload,
+ formId: String(formId),
+ data,
+ ip,
+ })
+
+ if (!result.success) {
+ const notifications = await getNotificationTexts({ payload, locale })
+ const errorMessage = resolveFormMessage(result, notifications?.form)
+ return {
+ success: false,
+ message: errorMessage || 'Wystąpił błąd podczas wysyłania formularza. Spróbuj ponownie później.',
+ }
+ }
+
+ return {
+ success: true,
+ submissionId: result.submissionId,
+ }
+ } catch (err) {
+ console.error('Error executing submitFormAction:', err)
+ return {
+ success: false,
+ message: 'Wystąpił nieoczekiwany błąd serwera. Skontaktuj się z nami telefonicznie.',
+ }
+ }
+}
diff --git a/src/blocks/BeforeAfterGallery/Component.tsx b/src/blocks/BeforeAfterGallery/Component.tsx
index 60437df..63f5b39 100644
--- a/src/blocks/BeforeAfterGallery/Component.tsx
+++ b/src/blocks/BeforeAfterGallery/Component.tsx
@@ -20,19 +20,19 @@ export function BeforeAfterGalleryBlockComponent(props: BeforeAfterGalleryBlockP
const { badge, heading, subheading, items } = props
return (
-
+
{badge && (
-
+
{badge}
)}
-
+
{heading}
{subheading && (
-
+
{subheading}
)}
@@ -43,15 +43,15 @@ export function BeforeAfterGalleryBlockComponent(props: BeforeAfterGalleryBlockP
{items.map((item, idx) => (
{item.category && (
-
+
{item.category}
)}
-
+
{item.title}
{item.description && (
@@ -61,13 +61,13 @@ export function BeforeAfterGalleryBlockComponent(props: BeforeAfterGalleryBlockP
)}
-
-
+
+
Czyszczenie gorącowodne Mosmatic
-
diff --git a/src/blocks/BlogList/Component.tsx b/src/blocks/BlogList/Component.tsx
index 4ac0ebc..3c22455 100644
--- a/src/blocks/BlogList/Component.tsx
+++ b/src/blocks/BlogList/Component.tsx
@@ -23,20 +23,20 @@ export function BlogListBlockComponent(props: BlogListBlockProps) {
const { badge, heading, subheading, articles } = props
return (
-
+
{badge && (
-
+
{badge}
)}
-
+
{heading}
{subheading && (
-
+
{subheading}
)}
@@ -48,42 +48,44 @@ export function BlogListBlockComponent(props: BlogListBlockProps) {
{articles.map((art, idx) => (
-
- {/* Top Row: Category Badge & Read Time (No Wrapping / No Overlap) */}
-
-
+
+ {/* Top Row: Category Badge */}
+
+
{art.category}
+
+
+
+
+ {art.title}
+
{art.readTime && (
-
-
+
+
{art.readTime}
-
+
)}
-
- {art.title}
-
-
-
+
{art.excerpt}
-
+
{art.date && (
-
+
{art.date}
)}
{props.readMoreLabel && (
-
+
{props.readMoreLabel}
-
+
)}
diff --git a/src/blocks/Content/Component.tsx b/src/blocks/Content/Component.tsx
index 26d621b..7d3bd57 100644
--- a/src/blocks/Content/Component.tsx
+++ b/src/blocks/Content/Component.tsx
@@ -10,15 +10,15 @@ export function ContentBlockComponent(props: ContentBlockProps) {
const { badge, heading, body } = props
return (
-
+
{badge && (
-
+
{badge}
)}
{heading && (
-
+
{heading}
)}
diff --git a/src/blocks/FaqAccordion/Component.tsx b/src/blocks/FaqAccordion/Component.tsx
index d00520e..2a07045 100644
--- a/src/blocks/FaqAccordion/Component.tsx
+++ b/src/blocks/FaqAccordion/Component.tsx
@@ -25,20 +25,20 @@ export function FaqAccordionBlockComponent(props: FaqAccordionBlockProps) {
}
return (
-
+
{badge && (
-
+
{badge}
)}
-
+
{heading}
{subheading && (
-
+
{subheading}
)}
@@ -52,17 +52,17 @@ export function FaqAccordionBlockComponent(props: FaqAccordionBlockProps) {
return (