Navbar: logo, ikony z animacjami, shrink przy scroll
- Logo LabAI_pro.png zamiast tekstu DEX_OS (Nav + Footer) - Navbar shrink przy scroll >60px (compact mode) - Ikony SVG przy linkach: góra→lewa przy przejściu w compact - Animacje ikon na aktywnym linku (shake, bounce, glow) - backdrop-filter przeniesiony na ::before (fix overflow) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,7 @@ const Footer = () => (
|
||||
<div className="footer-container">
|
||||
<div className="footer-brand">
|
||||
<div className="nav-logo">
|
||||
<span className="pulse-dot" aria-hidden="true"></span>
|
||||
DEX_OS
|
||||
<img src="/img/LabAI_pro.png" alt="Laboratorium AI" height={36} />
|
||||
</div>
|
||||
<p>Zbudowane przez deweloperów sztucznej inteligencji, dla agentów przyszłości.</p>
|
||||
<div className="footer-social" aria-label="Social media">
|
||||
|
||||
+58
-17
@@ -1,15 +1,58 @@
|
||||
import React, { useState } from 'react';
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
const icons = {
|
||||
news: (
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/>
|
||||
<path d="M18 14h-8M15 18h-5M10 6h8v4h-8z"/>
|
||||
</svg>
|
||||
),
|
||||
products: (
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
<polyline points="3.29 7 12 12 20.71 7"/><line x1="12" y1="22" x2="12" y2="12"/>
|
||||
</svg>
|
||||
),
|
||||
ai: (
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/>
|
||||
<path d="M12 7v4M8 11V9M16 11V9M6 15h.01M10 15h.01M14 15h.01M18 15h.01M6 19h.01M18 19h.01"/>
|
||||
</svg>
|
||||
),
|
||||
contact: (
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||
</svg>
|
||||
),
|
||||
}
|
||||
|
||||
const NavLink = ({ href, label, icon, active }) => (
|
||||
<Link href={href} className={`nav-link${active ? ' active' : ''}`} aria-current={active ? 'page' : undefined} role="menuitem">
|
||||
<span className="nav-link__icon">{icon}</span>
|
||||
<span className="nav-link__label">{label}</span>
|
||||
</Link>
|
||||
)
|
||||
|
||||
const Nav = () => {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const [compact, setCompact] = useState(false)
|
||||
const pathname = usePathname()
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setCompact(window.scrollY > 60)
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
return () => window.removeEventListener('scroll', onScroll)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<nav className="main-nav" aria-label="Główna nawigacja">
|
||||
<nav className={`main-nav${compact ? ' main-nav--compact' : ''}`} aria-label="Główna nawigacja">
|
||||
<div className="nav-container">
|
||||
<div className="nav-logo">
|
||||
<span className="pulse-dot" aria-hidden="true"></span>
|
||||
DEX_OS
|
||||
</div>
|
||||
<Link href="/" className="nav-logo">
|
||||
<img src="/img/LabAI_pro.png" alt="Laboratorium AI" height={132} />
|
||||
</Link>
|
||||
|
||||
<button
|
||||
className={`nav-hamburger ${menuOpen ? 'nav-hamburger--open' : ''}`}
|
||||
@@ -17,16 +60,14 @@ const Nav = () => {
|
||||
aria-label={menuOpen ? 'Zamknij menu' : 'Otwórz menu'}
|
||||
aria-expanded={menuOpen}
|
||||
>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
|
||||
<div className={`nav-links ${menuOpen ? 'nav-links--open' : ''}`} role="menu">
|
||||
<a href="#" className="active" aria-current="page" role="menuitem">Wiadomości</a>
|
||||
<a href="#" role="menuitem">Dla zaawansowanych</a>
|
||||
<a href="#" role="menuitem">Modele AI</a>
|
||||
<a href="#" role="menuitem">Biblioteka Promptów</a>
|
||||
<NavLink href="/" label="Wiadomości" icon={icons.news} active={pathname === '/'} />
|
||||
<NavLink href="/produkty" label="Produkty" icon={icons.products} active={pathname === '/produkty'} />
|
||||
<NavLink href="#" label="Modele AI" icon={icons.ai} active={false} />
|
||||
<NavLink href="/kontakt" label="Kontakt" icon={icons.contact} active={pathname === '/kontakt'} />
|
||||
</div>
|
||||
|
||||
<div className="nav-actions">
|
||||
@@ -34,7 +75,7 @@ const Nav = () => {
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Nav;
|
||||
export default Nav
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
const DIRECTUS = 'https://cms.dexterlab.pl'
|
||||
const TYPE_LABEL = { course: 'Kurs', service: 'Usługa', book: 'Ebook' }
|
||||
|
||||
const ProductCard = ({ p, headingLevel: H = 'h3' }) => (
|
||||
<div className={`product-card${p.featured ? ' product-card--featured' : ''}`}>
|
||||
{p.featured && <span className="product-featured-badge">Polecany</span>}
|
||||
{p.product_image && (
|
||||
<div className="product-img" style={{ backgroundImage: `url(${DIRECTUS}/assets/${p.product_image})` }} />
|
||||
)}
|
||||
<div className="product-body">
|
||||
<span className="product-type">{TYPE_LABEL[p.type] ?? p.type}</span>
|
||||
<H className="product-name">{p.title}</H>
|
||||
{p.categories?.length > 0 && (
|
||||
<div className="product-tags">
|
||||
{p.categories.map((c) => <span key={c} className="product-tag">{c}</span>)}
|
||||
</div>
|
||||
)}
|
||||
<p className="product-desc">{p.short_description}</p>
|
||||
<div className="product-footer">
|
||||
<div className="product-price">
|
||||
{p.discount_price ? (
|
||||
<>
|
||||
<span className="product-price--promo">{p.discount_price} {p.currency}</span>
|
||||
<span className="product-price--old">{p.price} {p.currency}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="product-price--regular">{p.price} {p.currency}</span>
|
||||
)}
|
||||
</div>
|
||||
<a href={p.url} target="_blank" rel="noopener noreferrer" className="hero__btn-primary">
|
||||
Kup teraz →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default ProductCard
|
||||
@@ -0,0 +1,17 @@
|
||||
'use client'
|
||||
|
||||
const SubscribeBox = () => (
|
||||
<div className="subscribe-box">
|
||||
<h3>Bądź na bieżąco</h3>
|
||||
<p>Otrzymuj najnowsze wiadomości o AI prosto na swoją skrzynkę.</p>
|
||||
<form method="post" action="https://mail.dexterlab.pl/subscription/form">
|
||||
<input type="hidden" name="nonce" />
|
||||
<input type="email" name="email" required placeholder="Twój email" />
|
||||
<input type="text" name="name" placeholder="Nazwa (opcjonalnie)" style={{ marginTop: '0.5rem' }} />
|
||||
<input id="827b2" type="checkbox" name="l" defaultChecked value="5e40f006-7648-4932-88f4-564c95ed337f" style={{ display: 'none' }} />
|
||||
<button type="submit">Subskrybuj</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default SubscribeBox
|
||||
+97
-10
@@ -58,24 +58,47 @@ h1, h2, h3, h4, h5, h6 {
|
||||
/* ===== NAVIGATION ===== */
|
||||
.main-nav {
|
||||
width: 100%;
|
||||
background: rgba(15, 16, 22, 0.85);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.main-nav::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(15, 16, 22, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 2rem;
|
||||
transition: padding 0.3s ease;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-logo img {
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.main-nav--compact .nav-container {
|
||||
padding: 0.4rem 2rem;
|
||||
}
|
||||
|
||||
.main-nav--compact .nav-logo img {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
@@ -92,22 +115,86 @@ h1, h2, h3, h4, h5, h6 {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
.nav-link {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.nav-links a::after {
|
||||
@keyframes nav-bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
40% { transform: translateY(-4px); }
|
||||
60% { transform: translateY(-2px); }
|
||||
}
|
||||
|
||||
@keyframes nav-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes nav-pulse {
|
||||
0%, 100% { opacity: 1; filter: drop-shadow(0 0 0px var(--accent-color)); }
|
||||
50% { opacity: 0.8; filter: drop-shadow(0 0 6px var(--accent-color)); }
|
||||
}
|
||||
|
||||
@keyframes nav-shake {
|
||||
0%, 100% { transform: rotate(0deg); }
|
||||
20% { transform: rotate(-8deg); }
|
||||
40% { transform: rotate(8deg); }
|
||||
60% { transform: rotate(-4deg); }
|
||||
80% { transform: rotate(4deg); }
|
||||
}
|
||||
|
||||
.nav-link__icon {
|
||||
display: flex;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* ikona Wiadomości — shake jak dzwonek */
|
||||
.nav-link:nth-child(1).active .nav-link__icon {
|
||||
animation: nav-shake 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ikona Produkty — bounce */
|
||||
.nav-link:nth-child(2).active .nav-link__icon {
|
||||
animation: nav-bounce 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ikona Modele AI — pulse/glow */
|
||||
.nav-link:nth-child(3).active .nav-link__icon {
|
||||
animation: nav-pulse 1.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ikona Kontakt — bounce */
|
||||
.nav-link:nth-child(4).active .nav-link__icon {
|
||||
animation: nav-bounce 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.nav-link__label {
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.main-nav--compact .nav-link {
|
||||
flex-direction: row;
|
||||
gap: 0.4rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
background-color: var(--accent-color);
|
||||
transform: scaleX(0);
|
||||
@@ -117,13 +204,13 @@ h1, h2, h3, h4, h5, h6 {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.nav-links a:hover,
|
||||
.nav-links a.active {
|
||||
.nav-link:hover,
|
||||
.nav-link.active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-links a:hover::after,
|
||||
.nav-links a.active::after {
|
||||
.nav-link:hover::after,
|
||||
.nav-link.active::after {
|
||||
transform: scaleX(1);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const DIRECTUS = 'https://cms.dexterlab.pl'
|
||||
const BRAND = { '@type': 'Organization', name: 'Laboratorium AI' }
|
||||
|
||||
export function productSchema(p) {
|
||||
return {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'Product',
|
||||
name: p.title,
|
||||
image: p.product_image ? `${DIRECTUS}/assets/${p.product_image}` : undefined,
|
||||
description: p.short_description,
|
||||
...(p.sku && { sku: p.sku }),
|
||||
brand: BRAND,
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
url: p.url,
|
||||
priceCurrency: p.currency,
|
||||
price: String(p.discount_price ?? p.price),
|
||||
priceValidUntil: '2026-12-31',
|
||||
availability: 'https://schema.org/InStock',
|
||||
itemCondition: 'https://schema.org/NewCondition',
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
const items = [
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="12" cy="12" r="9"/><path d="M12 7v1m0 8v1M9.5 9.5A2.5 2.5 0 0 1 12 8a2.5 2.5 0 0 1 0 5 2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 2.5-1.5"/></svg>,
|
||||
title: 'Redukcja kosztów',
|
||||
desc: 'Automatyzacja rutynowych zadań administracyjnych i marketingowych eliminuje potrzebę zatrudniania agencji lub dodatkowych pracowników, drastycznie podnosząc rentowność operacyjną firmy.',
|
||||
},
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M3 12l9-9 9 9M5 10v9a1 1 0 0 0 1 1h4v-5h4v5h4a1 1 0 0 0 1-1v-9"/><path d="M16 6l2-3"/></svg>,
|
||||
title: 'Skalowanie wideo',
|
||||
desc: 'Twój jeden długi materiał (podcast/YouTube) zostaje automatycznie pocięty i przekształcony w dziesiątki viralowych klipów Shorts, Reels i TikTok, budując gigantyczne zasięgi na autopilocie.',
|
||||
},
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M12 2a10 10 0 1 1 0 20A10 10 0 0 1 12 2"/><path d="M12 6v6l4 2"/></svg>,
|
||||
title: 'Maksymalna szybkość',
|
||||
desc: 'Skracamy czas obsługi zapytań ofertowych i powtarzalnej produkcji contentu do kilku minut. Twoja firma reaguje na ból klienta natychmiast, zostawiając ociężałą konkurencję daleko w tyle.',
|
||||
},
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M12 2C8 2 5 5 5 8c0 4 4 7 7 10 3-3 7-6 7-10 0-3-2.5-6-7-6z"/><path d="M12 8v4m0 0v.01"/></svg>,
|
||||
title: 'Święty spokój',
|
||||
desc: 'Nasz mechanizm z pętlą „Ludzkiego Bezpiecznika" bierze na siebie całą odpowiedzialność za błędy i halucynacje AI. Odzyskujesz wolne weekendy, podczas gdy maszyny bezbłędnie wykonują brudną robotę.',
|
||||
},
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 12l2 2 4-4"/></svg>,
|
||||
title: 'Gotowe rezultaty',
|
||||
desc: 'Nie dostajesz skomplikowanego panelu SaaS ani promptów do wklejenia. Logujesz się bezpośrednio do panelu po odbiór finalnego, zwalidowanego i perfekcyjnie przygotowanego produktu końcowego.',
|
||||
},
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M20 8c0 2-2 3-2 3s-2-1-2-3a2 2 0 0 1 4 0z"/></svg>,
|
||||
title: 'Autentyczny głos',
|
||||
desc: 'Nasze modele GenAI trenujemy wyłącznie na Twoim unikalnym stylu komunikacji (Tone of Voice). Treści zachowują Twoją merytorykę i pasję, będąc całkowicie nieodróżnialnymi od pisanych ręcznie.',
|
||||
},
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><path d="M22 6l-10 7L2 6"/><path d="M12 13l1.5-1"/></svg>,
|
||||
title: 'Oferty 5 minut',
|
||||
desc: 'Inteligentne boty natychmiast skanują przychodzące maile, sprawdzają kalendarz i automatycznie generują szkice spersonalizowanych wycen biznesowych, eliminując ryzyko utraty gorącego leada.',
|
||||
},
|
||||
{
|
||||
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M3 18h3v-4H3v4zm4 0h3v-8H7v8zm4 0h3v-6h-3v6zm4 0h3V6h-3v12z"/></svg>,
|
||||
title: 'Przejrzyste ROI',
|
||||
desc: 'W dedykowanym dashboardzie na bieżąco monitorujesz twarde dane operacyjne. Dokładnie widzisz, ile żetonów czasu i pieniędzy zaoszczędziłeś w tym tygodniu dzięki naszym automatyzacjom.',
|
||||
},
|
||||
]
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
const Benefits = () => (
|
||||
<section className="benefits">
|
||||
<div className="benefits__header">
|
||||
<h2 className="benefits__title">Gotowe moduły AI stworzone, by natychmiast odciążyć Twoim biznesie</h2>
|
||||
<p className="benefits__subtitle">Nie kupujesz u nas skomplikowanych wdrożeń ani godzin pracy programistów. Wybierasz konkretny, gotowy moduł AI do jednej, denerwującej czynności – jak automatyczny recykling wideo czy błyskawiczne wycenianie maili – i uruchamiasz go w swojej firmie w 48 godzin.</p>
|
||||
</div>
|
||||
<div className="benefits__grid">
|
||||
{items.map((item) => (
|
||||
<div key={item.title} className="benefits__card">
|
||||
<div className="benefits__icon">{item.icon}</div>
|
||||
<h3 className="benefits__card-title">{item.title}</h3>
|
||||
<p className="benefits__card-desc">{item.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="benefits__cta">
|
||||
<Link href="/produkty" className="hero__btn-primary">Wybierz moduł AI i odzyskaj czas</Link>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
export default Benefits
|
||||
@@ -0,0 +1,21 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
const ComingSoon = () => {
|
||||
return (
|
||||
<div className="coming-soon-page">
|
||||
<div className="coming-soon-content">
|
||||
<div className="coming-soon-badge">Wkrótce</div>
|
||||
<h1 className="coming-soon-title">Dla zaawansowanych</h1>
|
||||
<p className="coming-soon-desc">
|
||||
Przygotowujemy pogłębione materiały dla doświadczonych praktyków AI —
|
||||
architektura systemów, fine-tuning, wdrożenia produkcyjne i więcej.
|
||||
</p>
|
||||
<Link href="/" className="coming-soon-back">
|
||||
← Wróć do głównej
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComingSoon
|
||||
@@ -0,0 +1,64 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
const ContactPage = () => {
|
||||
const [sent, setSent] = useState(false)
|
||||
const [form, setForm] = useState({ name: '', email: '', message: '' })
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
if (form.name && form.email && form.message) setSent(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="coming-soon-page">
|
||||
<div className="contact-content">
|
||||
<div className="coming-soon-badge">Kontakt</div>
|
||||
<h1 className="coming-soon-title">Napisz do nas</h1>
|
||||
<p className="coming-soon-desc">
|
||||
Pytania, współpraca, propozycje tematów — odpisujemy w ciągu 48h.
|
||||
</p>
|
||||
|
||||
{sent ? (
|
||||
<div className="contact-success">
|
||||
Wiadomość wysłana. Odezwiemy się wkrótce.
|
||||
</div>
|
||||
) : (
|
||||
<form className="contact-form" onSubmit={handleSubmit} noValidate>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Imię"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={form.email}
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Wiadomość"
|
||||
rows={5}
|
||||
value={form.message}
|
||||
onChange={(e) => setForm({ ...form, message: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<button type="submit" className="nav-button" style={{ width: '100%' }}>
|
||||
Wyślij wiadomość
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<Link href="/" className="coming-soon-back" style={{ marginTop: '1.5rem', display: 'inline-block' }}>
|
||||
← Wróć do głównej
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContactPage
|
||||
@@ -0,0 +1,47 @@
|
||||
import Link from 'next/link'
|
||||
import ProductCard from '../components/ProductCard'
|
||||
import { productSchema } from '../lib/productSchema'
|
||||
|
||||
const DIRECTUS = 'https://cms.dexterlab.pl'
|
||||
|
||||
async function getProducts() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${DIRECTUS}/items/products?filter[site][_eq]=labai.pro&filter[status][_eq]=published&sort=-date_updated&limit=3`,
|
||||
{ next: { revalidate: 300 } }
|
||||
)
|
||||
if (!res.ok) return []
|
||||
return (await res.json()).data ?? []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export default async function ProductsPreview() {
|
||||
const products = await getProducts()
|
||||
if (!products.length) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
{products.map((p) => (
|
||||
<script key={p.id} type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(productSchema(p)) }}
|
||||
/>
|
||||
))}
|
||||
<section className="products-preview">
|
||||
<div className="products-header">
|
||||
<h2 className="products-title">Nasze produkty</h2>
|
||||
<p className="products-subtitle">Gotowe narzędzia i kursy AI dla twórców i przedsiębiorców.</p>
|
||||
</div>
|
||||
<div className="products-grid">
|
||||
{products.map((p) => <ProductCard key={p.id} p={p} headingLevel="h3" />)}
|
||||
</div>
|
||||
<div className="benefits__cta">
|
||||
<Link href="/produkty" className="hero__btn-primary" style={{ textDecoration: 'none' }}>
|
||||
Więcej produktów →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user