Integracja Directus LabAI, naprawa Schema.org, poprawki UI

- Zmiana kolekcji Directus z Laboratorium_AI na LabAI
- Usunięcie fallbacku na fake dane — brak wpisów = komunikat
- revalidate=0 na stronie głównej (zawsze świeże dane)
- BlogPosting przeniesiony do @graph (poprawna spec Schema.org)
- Inbox Libredesk zaktualizowany
- Hero image-wrap powiększony (scale 1.56)
- Dokumentacja projektu w CLAUDE.md

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paweł Domański
2026-07-05 13:14:43 +02:00
parent f39551d0d0
commit eb09da9e4d
9 changed files with 943 additions and 1909 deletions
+201 -1781
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
import ComingSoon from '../../sections/ComingSoon'
export const metadata = {
title: 'Dla zaawansowanych — Laboratorium AI',
description: 'Pogłębione materiały dla doświadczonych praktyków AI.',
}
export default function DlaZaawansowanychPage() {
return <ComingSoon />
}
+10
View File
@@ -0,0 +1,10 @@
import ContactPage from '../../sections/ContactPage'
export const metadata = {
title: 'Kontakt — Laboratorium AI',
description: 'Napisz do nas. Odpowiadamy w ciągu 48h.',
}
export default function KontaktPage() {
return <ContactPage />
}
+42
View File
@@ -0,0 +1,42 @@
import Script from 'next/script'
import { Inter } from 'next/font/google'
import '../index.css'
import Nav from '../components/Nav'
import Footer from '../components/Footer'
import CookieBanner from '../components/CookieBanner'
import SchemaOrg from '../components/SchemaOrg'
const inter = Inter({
subsets: ['latin-ext'],
weight: ['300', '400', '500', '600', '700', '800'],
})
export const metadata = {
title: 'Laboratorium AI — Automatyzacja procesów biznesowych',
description: 'Automatyzacja procesów biznesowych bez programowania. Wdrażamy AI i no-code w 48h.',
openGraph: {
type: 'website',
title: 'Laboratorium AI — Automatyzacja procesów biznesowych',
description: 'Automatyzacja procesów biznesowych bez programowania. Wdrażamy AI i no-code w 48h.',
images: ['/img/robot.png'],
},
}
export default function RootLayout({ children }) {
return (
<html lang="pl">
<head>
<SchemaOrg />
</head>
<body className={inter.className}>
<Nav />
{children}
<Footer />
<CookieBanner />
<Script src="https://stat.dexterlab.pl/api/script.js" data-site-id="e7d7e9823a23" strategy="afterInteractive" />
<Script id="libredesk-config" strategy="beforeInteractive">{`window.LibredeskSettings = { baseURL: 'https://pomoc.dexterlab.pl', inboxID: 'bffd72d7-a528-42ca-bfed-377670d49364' };`}</Script>
<Script src="https://pomoc.dexterlab.pl/widget.js" strategy="afterInteractive" />
</body>
</html>
)
}
+94
View File
@@ -0,0 +1,94 @@
import Hero from '../sections/Hero'
import Benefits from '../sections/Benefits'
import ProductsPreview from '../sections/ProductsPreview'
import BentoCard from '../components/BentoCard'
import BentoSlideshow from '../components/BentoSlideshow'
import SubscribeBox from '../components/SubscribeBox'
import { fetchLaboratoriumData, mapDirectusToContent } from '../lib/directus'
import { newsData, videosData } from '../data/content'
export const revalidate = 0
async function getContent() {
try {
const items = await fetchLaboratoriumData()
return mapDirectusToContent(items)
} catch {
return null
}
}
export default async function HomePage() {
const content = await getContent()
return (
<>
<Hero />
<Benefits />
<ProductsPreview />
<div className="page-wrapper">
<main className="bento-area" aria-label="Artykuły">
{!content ? (
<p style={{ gridColumn: '1 / 5', padding: '4rem', textAlign: 'center', color: '#888' }}>
W tej chwili brak wpisów.
</p>
) : (
<>
<BentoSlideshow
slides={content.slideshow}
style={{ gridColumn: '1 / 5', gridRow: '1 / 2' }}
/>
{content.articles.map((article, index) => (
<BentoCard
key={index}
type={article.type}
badge={article.badge}
title={article.title}
category={article.category}
readTime={article.readTime}
bgImage={article.bgImage}
hero={article.hero}
style={{ gridColumn: article.gridColumn, gridRow: article.gridRow }}
/>
))}
</>
)}
</main>
<aside className="sidebar sidebar-right" aria-label="Najnowsze wideo">
<div>
<h3 className="sidebar-title">LATEST VIDEOS</h3>
{videosData.map((video, index) => (
<div key={index} className="video-item" tabIndex={0}>
<div
className="video-thumb"
style={{ backgroundImage: `url(${video.bgImage})` }}
role="img"
aria-label={video.title}
/>
<div className="video-info">
<div className="video-title">{video.title}</div>
<div className="video-views">{video.views}</div>
</div>
</div>
))}
</div>
<SubscribeBox />
<div style={{ marginTop: '2rem' }}>
<h3 className="sidebar-title">LATEST AI NEWS</h3>
<div className="news-list">
{newsData.map((item, index) => (
<div key={index} className="news-item" tabIndex={0}>
<div className="news-time">{item.time}</div>
<div className="news-title">{item.title}</div>
</div>
))}
</div>
</div>
</aside>
</div>
</>
)
}
+48
View File
@@ -0,0 +1,48 @@
import ProductCard from '../../components/ProductCard'
import { productSchema } from '../../lib/productSchema'
const DIRECTUS = 'https://cms.dexterlab.pl'
export const revalidate = 300
export const metadata = {
title: 'Produkty — Laboratorium AI',
description: 'Moduły AI-RaaS — gotowe automatyzacje dla mikroprzedsiębiorców.',
}
async function getProducts() {
try {
const res = await fetch(
`${DIRECTUS}/items/products?filter[site][_eq]=labai.pro&filter[status][_eq]=published&sort=sort`,
{ next: { revalidate: 300 } }
)
if (!res.ok) return []
return (await res.json()).data ?? []
} catch {
return []
}
}
export default async function ProduktyPage() {
const products = await getProducts()
return (
<>
{products.map((p) => (
<script key={p.id} type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(productSchema(p)) }}
/>
))}
<div className="products-page">
<div className="products-header">
<div className="coming-soon-badge">Moduły AI-RaaS</div>
<h1 className="products-title">Wybierz swój moduł AI</h1>
<p className="products-subtitle">Gotowe automatyzacje dla mikroprzedsiębiorców i twórców treści.</p>
</div>
<div className="products-grid">
{products.map((p) => <ProductCard key={p.id} p={p} headingLevel="h2" />)}
</div>
</div>
</>
)
}
+104
View File
@@ -0,0 +1,104 @@
const DIRECTUS_URL = 'https://cms.dexterlab.pl'
const DOMAIN = 'labai.pro'
const BASE_URL = `https://${DOMAIN}`
const FALLBACK = {
name: 'Laboratorium AI',
description: 'Automatyzacja procesów biznesowych bez programowania. Wdrażamy AI i no-code w 48h.',
logo: `${BASE_URL}/logo.webp`,
sameAs: [
'https://linkedin.com/company/dexterlab',
'https://x.com/dexterlab_pl',
'https://facebook.com/dexterlab',
],
}
async function fetchSiteData() {
try {
const res = await fetch(
`${DIRECTUS_URL}/items/sites?filter[domain][_eq]=${DOMAIN}`,
{ next: { revalidate: 3600 } }
)
if (!res.ok) return null
const json = await res.json()
return json.data?.[0] ?? null
} catch {
return null
}
}
async function fetchArticles() {
try {
const res = await fetch(
`${DIRECTUS_URL}/items/LabAI?sort=-date_published&limit=5&fields=Title,Slug`,
{ next: { revalidate: 300 } }
)
if (!res.ok) return []
const json = await res.json()
return json.data ?? []
} catch {
return []
}
}
function buildSchema(site, articles) {
const s = site ?? {}
const baseUrl = (s.url ?? BASE_URL).replace(/\/$/, '')
const name = s.site_name ?? FALLBACK.name
const description = s.site_description ?? FALLBACK.description
const logo = s.logo ?? { '@type': 'ImageObject', url: FALLBACK.logo }
const sameAs = Array.isArray(s.social_links) && s.social_links.length
? s.social_links
: FALLBACK.sameAs
const blogPostings = articles.map((a) => ({
'@type': 'BlogPosting',
'@id': `${baseUrl}/blog/${a.Slug}`,
headline: a.Title,
url: `${baseUrl}/blog/${a.Slug}`,
isPartOf: { '@id': `${baseUrl}/#webpage` },
}))
return {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'WebSite',
'@id': `${baseUrl}/#website`,
url: `${baseUrl}/`,
name,
publisher: { '@id': `${baseUrl}/#organization` },
},
{
'@type': 'WebPage',
'@id': `${baseUrl}/#webpage`,
url: `${baseUrl}/`,
name,
isPartOf: { '@id': `${baseUrl}/#website` },
description,
},
{
'@type': 'Organization',
'@id': `${baseUrl}/#organization`,
name: s.company_name ?? name,
url: `${baseUrl}/`,
logo,
sameAs,
...(s.contact && { contactPoint: s.contact }),
},
...blogPostings,
],
}
}
export default async function SchemaOrg() {
const [site, articles] = await Promise.all([fetchSiteData(), fetchArticles()])
const schema = buildSchema(site, articles)
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
)
}
+387 -8
View File
@@ -13,6 +13,7 @@
--card-radius: 1.25rem;
--grid-gap: 1.5rem;
--content-width: 1200px;
/* Badges */
--badge-hot: #e11d48;
@@ -181,7 +182,7 @@ h1, h2, h3, h4, h5, h6 {
/* ===== PAGE WRAPPER ===== */
.page-wrapper {
max-width: 1600px;
max-width: var(--content-width);
margin: 0 auto;
padding: 2rem;
display: grid;
@@ -663,7 +664,7 @@ h1, h2, h3, h4, h5, h6 {
}
.footer-container {
max-width: 1200px;
max-width: var(--content-width);
margin: 0 auto;
display: flex;
justify-content: space-between;
@@ -681,6 +682,31 @@ h1, h2, h3, h4, h5, h6 {
line-height: 1.5;
}
.footer-social {
display: flex;
gap: 0.75rem;
margin-top: 1.25rem;
}
.footer-social__link {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-color);
color: var(--text-muted);
transition: color 0.2s, background 0.2s, border-color 0.2s;
}
.footer-social__link:hover {
color: var(--text-main);
background: rgba(79, 70, 229, 0.15);
border-color: rgba(79, 70, 229, 0.4);
}
.footer-links {
display: flex;
flex-wrap: wrap;
@@ -711,7 +737,7 @@ h1, h2, h3, h4, h5, h6 {
}
.footer-bottom {
max-width: 1200px;
max-width: var(--content-width);
margin: 0 auto;
padding-top: 2rem;
text-align: center;
@@ -818,7 +844,6 @@ h1, h2, h3, h4, h5, h6 {
padding: 0.75rem;
gap: 0.75rem;
}
}
.cookie-banner {
bottom: 1rem;
right: 1rem;
@@ -827,14 +852,257 @@ h1, h2, h3, h4, h5, h6 {
}
}
/* ===== PRODUCTS PAGE ===== */
.products-page {
max-width: var(--content-width);
margin: 0 auto;
padding: 4rem 2rem;
}
.products-header {
text-align: center;
margin-bottom: 3rem;
}
.products-title {
font-size: 2rem;
font-weight: 700;
color: var(--text-main);
margin: 1rem 0 0.5rem;
}
.products-subtitle {
color: var(--text-muted);
font-size: 1rem;
}
.products-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
.product-card {
background: var(--bg-accent);
border: 1px solid var(--border-color);
border-radius: var(--card-radius);
overflow: hidden;
display: flex;
flex-direction: column;
transition: border-color 0.2s;
}
.product-card:hover { border-color: rgba(79, 70, 229, 0.4); }
.product-img {
height: 220px;
background-size: cover;
background-position: center;
}
.product-body {
padding: 1.5rem;
display: flex;
flex-direction: column;
flex: 1;
gap: 0.5rem;
}
.product-card--featured { border-color: rgba(79, 70, 229, 0.5); }
.product-featured-badge {
position: absolute;
top: 0.75rem;
right: 0.75rem;
background: var(--accent-color);
color: #fff;
font-size: 0.7rem;
font-weight: 600;
padding: 0.2rem 0.6rem;
border-radius: 999px;
}
.product-card { position: relative; }
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
margin-bottom: 0.25rem;
}
.product-tag {
font-size: 0.7rem;
background: rgba(79, 70, 229, 0.12);
color: #818cf8;
padding: 0.15rem 0.5rem;
border-radius: 999px;
}
.product-type {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--accent-color);
}
.product-name {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-main);
}
.product-desc {
font-size: 0.875rem;
color: var(--text-muted);
line-height: 1.6;
flex: 1;
}
.product-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 1rem;
gap: 1rem;
}
.product-price { display: flex; align-items: baseline; gap: 0.5rem; }
.product-price--promo {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-main);
}
.product-price--old {
font-size: 0.875rem;
color: var(--text-muted);
text-decoration: line-through;
}
.product-price--regular {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-main);
}
@media (max-width: 900px) { .products-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .products-grid { grid-template-columns: 1fr; } }
.products-preview {
max-width: var(--content-width);
margin: 0 auto;
padding: 4rem 2rem;
}
/* ===== BENEFITS SECTION ===== */
.benefits {
padding: 5rem 2rem;
border-bottom: 1px solid var(--border-color);
}
.benefits__header {
max-width: 720px;
margin: 0 auto 3rem;
text-align: center;
}
.benefits__title {
font-size: 2rem;
font-weight: 700;
color: var(--text-main);
margin-bottom: 1rem;
}
.benefits__subtitle {
color: var(--text-muted);
font-size: 1rem;
line-height: 1.7;
}
.benefits__grid {
max-width: var(--content-width);
margin: 0 auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
}
.benefits__card {
background: var(--bg-accent);
border: 1px solid var(--border-color);
border-radius: var(--card-radius);
padding: 1.75rem;
transition: border-color 0.2s;
}
.benefits__card:hover {
border-color: rgba(79, 70, 229, 0.4);
}
.benefits__icon {
width: 40px;
height: 40px;
color: var(--accent-color);
margin-bottom: 1rem;
}
.benefits__icon svg {
width: 100%;
height: 100%;
}
.benefits__card-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-main);
margin-bottom: 0.5rem;
}
.benefits__card-desc {
font-size: 0.875rem;
color: var(--text-muted);
line-height: 1.6;
}
.benefits__cta {
text-align: center;
margin-top: 3rem;
}
@media (max-width: 1100px) {
.benefits__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
.benefits__grid { grid-template-columns: 1fr; }
.benefits__title { font-size: 1.5rem; }
}
/* ===== HERO SECTION ===== */
.hero {
position: relative;
overflow: hidden;
padding: 5rem 2rem 4rem;
padding: 4rem 2rem 4rem;
border-bottom: 1px solid var(--border-color);
}
.hero__eyebrow-list {
list-style: none;
padding: 0;
margin: 0;
margin-top: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.hero__eyebrow-list li {
list-style: none;
}
.hero__bg {
position: absolute;
inset: 0;
@@ -847,10 +1115,10 @@ h1, h2, h3, h4, h5, h6 {
.hero__inner {
position: relative;
z-index: 1;
max-width: 1200px;
max-width: var(--content-width);
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 2fr 1fr;
gap: 4rem;
align-items: center;
}
@@ -925,6 +1193,7 @@ h1, h2, h3, h4, h5, h6 {
background: var(--accent-color);
color: #fff;
border: none;
text-decoration: none;
padding: 0.9rem 2rem;
border-radius: 10px;
font-size: 1rem;
@@ -1011,6 +1280,8 @@ h1, h2, h3, h4, h5, h6 {
/* ── Prawa strona (zdjęcie) ── */
.hero__image-wrap {
position: relative;
transform: scale(1.56);
transform-origin: center center;
}
/* warstwa 2: float — wszystko lata razem */
@@ -1030,7 +1301,7 @@ h1, h2, h3, h4, h5, h6 {
/* warstwa 4: obraz z blaskiem żarówki (żółty drop-shadow kierunkowy) */
.hero__robot-layers {
width: 100%;
max-width: 600px;
max-width: 1350px;
aspect-ratio: 1 / 1;
position: relative;
z-index: 1;
@@ -1193,6 +1464,114 @@ h1, h2, h3, h4, h5, h6 {
font-size: 1rem;
}
.coming-soon-page {
min-height: 80vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.coming-soon-content {
text-align: center;
max-width: 480px;
}
.coming-soon-badge {
display: inline-block;
background: rgba(79, 70, 229, 0.15);
border: 1px solid rgba(79, 70, 229, 0.4);
color: #818cf8;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 0.35rem 0.9rem;
border-radius: 999px;
margin-bottom: 1.5rem;
}
.coming-soon-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-main);
margin-bottom: 1rem;
}
.coming-soon-desc {
color: var(--text-muted);
font-size: 1rem;
line-height: 1.7;
margin-bottom: 2rem;
}
.contact-content {
text-align: center;
max-width: 480px;
width: 100%;
}
.contact-form {
display: flex;
flex-direction: column;
gap: 0.75rem;
text-align: left;
}
.contact-form input,
.contact-form textarea {
background: var(--bg-accent);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
color: var(--text-main);
font-family: inherit;
font-size: 0.9rem;
padding: 0.75rem 1rem;
transition: border-color 0.2s;
width: 100%;
box-sizing: border-box;
}
.contact-form input::placeholder,
.contact-form textarea::placeholder {
color: var(--text-muted);
}
.contact-form input:focus,
.contact-form textarea:focus {
border-color: var(--accent-color);
outline: none;
}
.contact-form textarea {
resize: vertical;
}
.contact-success {
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.3);
border-radius: 0.5rem;
color: #6ee7b7;
padding: 1rem 1.5rem;
margin-bottom: 1rem;
}
.coming-soon-back {
background: none;
border: 1px solid var(--border-hover);
color: var(--text-muted);
padding: 0.6rem 1.4rem;
border-radius: 0.5rem;
font-size: 0.9rem;
cursor: pointer;
transition: color 0.2s, border-color 0.2s;
}
.coming-soon-back:hover {
color: var(--text-main);
border-color: var(--accent-color);
}
.error-banner {
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
+46 -119
View File
@@ -1,145 +1,72 @@
/**
* Directus API Client
* Źródło danych: https://cms.dexterlab.pl/ - kolekcja Laboratorium_AI
*/
const DIRECTUS_URL = 'https://cms.dexterlab.pl'
const COLLECTION = 'LabAI'
const DIRECTUS_URL = import.meta.env.DEV ? '/api' : 'https://cms.dexterlab.pl';
const COLLECTION = 'Laboratorium_AI';
// Cache storage
let dataCache = null;
let cacheTimestamp = 0;
const CACHE_DURATION = 5 * 60 * 1000; // 5 minut
/**
* Pobiera wszystkie elementy z kolekcji Laboratorium_AI
*/
export async function fetchLaboratoriumData() {
// Sprawdź cache
if (dataCache && Date.now() - cacheTimestamp < CACHE_DURATION) {
return dataCache;
}
try {
const response = await fetch(
`${DIRECTUS_URL}/items/${COLLECTION}?sort=-date_published&limit=-1`,
{
headers: {
'Content-Type': 'application/json',
'Origin': window.location.origin,
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
dataCache = result.data;
cacheTimestamp = Date.now();
return result.data;
} catch (error) {
console.error('Błąd pobierania danych z Directus:', error);
throw error;
}
const url = `${DIRECTUS_URL}/items/${COLLECTION}?filter[status][_eq]=published&sort=-date_published&limit=-1`
const headers = process.env.DIRECTUS_TOKEN
? { Authorization: `Bearer ${process.env.DIRECTUS_TOKEN}` }
: {}
const response = await fetch(url, { headers })
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
const result = await response.json()
return result.data
}
/**
* Pobiera pojedynczy element po ID
*/
export async function fetchLaboratoriumItem(id) {
try {
const response = await fetch(`${DIRECTUS_URL}/items/${COLLECTION}/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result.data;
} catch (error) {
console.error(`Błąd pobierania elementu ${id}:`, error);
return null;
}
}
/**
* Czyści cache - wymusza odświeżenie
*/
export function clearCache() {
dataCache = null;
cacheTimestamp = 0;
}
/**
* Mapuje dane z Directus na format strony
*/
export function mapDirectusToContent(items) {
if (!items || items.length === 0) return null;
if (!items || items.length === 0) return null
// Preferuj published, fallback na wszystkie
const published = items.filter(item => item.status === 'published');
const sourceItems = published.length > 0 ? published : items;
const sourceItems = items
// Slideshow - featured lub pierwsze 3
const featured = sourceItems.filter(item => item.is_featured === true);
const featured = sourceItems.filter(item => item.Is_Featured === true)
const slideshow = (featured.length > 0 ? featured : sourceItems.slice(0, 3)).map(item => ({
type: 'hot',
badge: item.badge || null,
title: item.title,
category: item.category || 'AI',
badge: item.Badge || null,
title: item.Title,
category: item.Category || 'AI',
readTime: '5 min read',
bgImage: item.cover_image ? `${DIRECTUS_URL}/assets/${item.cover_image}` : '',
}));
bgImage: item.Cover_Image ? `${DIRECTUS_URL}/assets/${item.Cover_Image}` : '',
}))
const slideshowTitles = new Set(slideshow.map(s => s.title));
const slideshowTitles = new Set(slideshow.map(s => s.title))
// Pozostałe jako articles (max 8)
const articles = sourceItems
.filter(item => !slideshowTitles.has(item.title))
.filter(item => !slideshowTitles.has(item.Title))
.slice(0, 8)
.map((item, index) => ({
type: item.badge?.toLowerCase() || 'article',
badge: item.badge || null,
title: item.title,
category: item.category || 'AI',
readTime: item.read_time || '5 min read',
bgImage: item.cover_image ? `${DIRECTUS_URL}/assets/${item.cover_image}` : '',
type: item.Badge?.toLowerCase() || 'article',
badge: item.Badge || null,
title: item.Title,
category: item.Category || 'AI',
readTime: '5 min read',
bgImage: item.Cover_Image ? `${DIRECTUS_URL}/assets/${item.Cover_Image}` : '',
gridColumn: getGridColumn(index),
gridRow: getGridRow(index),
hero: index === 0,
}));
}))
return { slideshow, articles };
return { slideshow, articles }
}
function getGridColumn(index) {
// Jasny układ dla 4-kolumnowej siatki
// 1: 2/3 (2 kol), 2: 3/4 (1 kol), 3: 4/5 (1 kol), 4: 4/5 (1 kol)
// 5: 1/2 (1 kol), 6: 2/3 (1 kol), 7: 3/4 (1 kol), 8: 4/5 (1 kol)
if (index === 0) return '1 / 3'; // 2 kolumny
if (index === 1) return '3 / 4'; // 1 kolumna
if (index === 2) return '4 / 5'; // 1 kolumna
if (index === 3) return '4 / 5'; // 1 kolumna
if (index === 4) return '1 / 2'; // 1 kolumna
if (index === 5) return '2 / 3'; // 1 kolumna
if (index === 6) return '3 / 4'; // 1 kolumna
if (index === 7) return '4 / 5'; // 1 kolumna
return '1 / 2';
if (index === 0) return '1 / 3'
if (index === 1) return '3 / 4'
if (index === 2) return '4 / 5'
if (index === 3) return '4 / 5'
if (index === 4) return '1 / 2'
if (index === 5) return '2 / 3'
if (index === 6) return '3 / 4'
if (index === 7) return '4 / 5'
return '1 / 2'
}
function getGridRow(index) {
// Wiersze dla układu: 2 duże na górze (2/4), reszta po 1 (3/4, 4/5)
if (index === 0) return '2 / 4'; // 2 rzędy
if (index === 1) return '2 / 4'; // 2 rzędy
if (index === 2) return '2 / 3'; // 1 rząd
if (index === 3) return '3 / 4'; // 1 rząd
if (index === 4) return '4 / 5'; // 1 rząd
if (index === 5) return '4 / 5'; // 1 rząd
if (index === 6) return '4 / 5'; // 1 rząd
if (index === 7) return '4 / 5'; // 1 rząd
return '4 / 5';
if (index === 0) return '2 / 4'
if (index === 1) return '2 / 4'
if (index === 2) return '2 / 3'
if (index === 3) return '3 / 4'
if (index === 4) return '4 / 5'
if (index === 5) return '4 / 5'
if (index === 6) return '4 / 5'
if (index === 7) return '4 / 5'
return '4 / 5'
}