feat: build home page from home.json sections
- Hero with callsign/locator tags and CTAs on khaki grid - Feature sections for ARD (YM1KTC), DMR Türkiye, QRV73!, QRZ73! - Telegram groups and resource site card grids - Latest posts block with empty state (wired to blog in stage 4) - Section wrapper, decorative numerals, external Button variant Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+11
-4
@@ -1,13 +1,13 @@
|
||||
# PROJECT_MEMORY — ta1sph.info
|
||||
|
||||
> Claude'un bu proje için kalıcı hafızası. Her görev sonunda güncellenir. Kısa ve güncel tutulur.
|
||||
> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Siteyi Kur — Aşama 2)
|
||||
> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Siteyi Kur — Aşama 3)
|
||||
|
||||
## 1. Durum
|
||||
|
||||
- **Aşama:** 2 tamamlandı (tasarım sistemi + layout). Sıradaki: **Aşama 3 — Ana Sayfa**.
|
||||
- **Tamamlanan:** Aşama 0, 1, 2. Stil önizleme yalnızca dev'de: `http://localhost:4321/styleguide/`.
|
||||
- **Açık işler:** Aşama 3–8. Footer'daki yasal sayfalar ve `/rss.xml` henüz yok (Aşama 4/6). Favicon hâlâ Astro varsayılanı (Aşama 7). `LICENSE` dosyaları Aşama 6.
|
||||
- **Aşama:** 3 tamamlandı (ana sayfa). Sıradaki: **Aşama 4 — Blog altyapısı**.
|
||||
- **Tamamlanan:** Aşama 0–3. Stil önizleme yalnızca dev'de: `http://localhost:4321/styleguide/`.
|
||||
- **Açık işler:** Aşama 4–8. Ana sayfa `LatestPosts` şimdilik `hasPosts={false}` (boş durum) → Aşama 4'te PostCard'larla bağlanacak. Footer yasal sayfalar + `/rss.xml` yok (A4/A6). Favicon varsayılan (A7). `LICENSE` dosyaları A6.
|
||||
- **Bilinen sorunlar:** —
|
||||
|
||||
## 2. Ortam
|
||||
@@ -25,6 +25,10 @@
|
||||
- `src/data/site.json` (ad, açıklama, nav, footer, `ui` metinleri, postsPerPage) · `src/data/social.json` (boş `href` → gizli) · `src/lib/data.ts` (zod; hata build'i kırar)
|
||||
- `src/layouts/BaseLayout.astro` — head (ThemeInit, Seo, RSS alternate), skip link, Header, `main#main`, Footer
|
||||
- `src/components/` — `Seo` (title/desc/canonical/OG/Twitter + WebSite/Person JSON-LD graph, `jsonLd` prop'u ile ek düğüm), `ThemeInit` (FOUC önleyici inline), `ThemeToggle`, `Header` (mobil menü = native `popover`, JS yok), `Footer` (4 sütun), `Container`, `GridLines`, `SectionHeading` (eyebrow + kırmızı çizgi), `Button` (primary/secondary/link), `Badge` (solid/outline/category), `Card`, `ExternalLink`
|
||||
- `src/data/home.json` — `seo`, `hero`, `sections[]` (`feature` | `cards` | `latestPosts`); şema `data.ts` → `home`, `HomeSection`. Tanıtım metinleri sitelerin kendi tanıtımlarından (2026-10-16 tarandı)
|
||||
- `src/pages/index.astro` — sections'ı sırayla render eder; arka plan dönüşümlü (hero khaki → paper, khaki…)
|
||||
- `src/components/Section.astro` (tam genişlik bölüm + GridLines, `alt` = khaki) · `src/components/home/` — `Hero`, `FeatureSection` (numara + ikon | çağrı işareti/alan adı | başlık+metin+CTA; 3 sütun grid çizgileriyle hizalı), `CardGrid`, `LatestPosts` (slot), `Numeral` (dev rakam `::before` ile → a11y/kontrast dışı)
|
||||
- `Button` — `external` prop'u (target/rel + sr-only "yeni sekmede açılır")
|
||||
- `src/dev/Styleguide.astro` — `astro.config.mjs`'deki `devRoutes` entegrasyonu ile sadece `astro dev`'de `/styleguide/` (`_` önekli sayfalar Astro'da rota olmaz)
|
||||
- `src/lib/slug.ts` — Türkçe karakter dönüştürücü `slugify`
|
||||
- `scripts/new-post.mjs` — `npm run new:post -- "Başlık"` → `src/content/blog/<slug>/index.md` (draft: true); `slug.ts`'yi Node type-stripping ile import eder
|
||||
@@ -45,6 +49,8 @@
|
||||
| 2026-10-15 | Kanonik alan: apex `ta1sph.info`, www → 301 | SEO tekilleştirme |
|
||||
| 2026-10-15 | Fontlar self-host (Fontsource: Rubik + Inter) | Performans + gizlilik |
|
||||
| 2026-10-16 | Ek token'lar: `accent-fill`/`on-accent-fill`, `accent-text` (khaki'de fg'ye döner) | Ölçüm: ink/red 4.46, red-strong/khaki 3.85 → küçük metin AA'yı geçmiyordu. Kırmızı zeminli butonlar ≥19px bold |
|
||||
| 2026-10-16 | Dekoratif dev rakamlar CSS `::before` içeriği | axe/Lighthouse aria-hidden metinde bile kontrast hatası veriyordu |
|
||||
| 2026-10-16 | Kuruluş/site tanıtımları yalnızca sitelerin kendi ifadelerinden | Uydurma bilgi yok; Telegram kartlarında açıklama yok (doğrulanmadı) |
|
||||
| 2026-10-16 | Mobil menü native `popover` | Escape/odak/light-dismiss JS'siz |
|
||||
| 2026-10-16 | Pagefind CLI postbuild (`astro-pagefind` yok) | Daha az bağımlılık; node adapter çıktısı `dist/client` |
|
||||
| 2026-10-16 | Skill repoya kopyalanmadı, kullanıcı düzeyinde | Çift kopya senkron riski |
|
||||
@@ -58,6 +64,7 @@
|
||||
|
||||
## 6. Değişiklik Günlüğü (en yeni üstte)
|
||||
|
||||
- 2026-10-16 | Siteyi Kur A3 | Ana sayfa: hero, 4 kuruluş, Telegram, son yazılar (boş durum), site tanıtımları; axe temiz | `src/data/home.json`, `src/components/home/`, `src/components/Section.astro`, `src/pages/index.astro`, `src/lib/data.ts`
|
||||
- 2026-10-16 | Siteyi Kur A2 | Token'lar, tema anahtarı, SEO bileşeni, layout, header/footer, temel bileşenler, site/social JSON, dev styleguide; axe temiz (2 tema) | `src/styles/`, `src/components/`, `src/layouts/`, `src/data/`, `src/lib/data.ts`, `src/dev/`, `astro.config.mjs`
|
||||
- 2026-10-16 | Siteyi Kur A1 | İskelet: Tailwind 4, mdx, sitemap, node, icon, rss, fontlar, pagefind, prettier, new:post | `astro.config.mjs`, `package.json`, `scripts/`, `src/lib/slug.ts`, `src/styles/`, `README.md`
|
||||
- 2026-10-16 | Siteyi Kur A0 | Ortam kontrolü; hafıza + docs kitten projeye kopyalandı | `CLAUDE.md`, `PROJECT_MEMORY.md`, `docs/`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import { site } from '../lib/data';
|
||||
|
||||
type Variant = 'primary' | 'secondary' | 'link';
|
||||
|
||||
@@ -9,9 +10,20 @@ export interface Props extends HTMLAttributes<'a'> {
|
||||
variant?: Variant;
|
||||
icon?: string;
|
||||
type?: 'button' | 'submit';
|
||||
/** Opens in a new tab with rel=noopener and a screen-reader hint. */
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
const { href, variant = 'primary', icon, type = 'button', class: className, ...rest } = Astro.props;
|
||||
const {
|
||||
href,
|
||||
variant = 'primary',
|
||||
icon,
|
||||
type = 'button',
|
||||
external = false,
|
||||
class: className,
|
||||
...rest
|
||||
} = Astro.props;
|
||||
const externalAttrs = external ? { target: '_blank', rel: 'noopener noreferrer' } : {};
|
||||
|
||||
// Primary uses ink on red at ≥19px bold (large text, ratio 4.46 ≥ 3).
|
||||
const variants: Record<Variant, string> = {
|
||||
@@ -29,9 +41,10 @@ const classes = [
|
||||
---
|
||||
|
||||
{href ? (
|
||||
<a href={href} class:list={classes} {...rest}>
|
||||
<a href={href} class:list={classes} {...externalAttrs} {...rest}>
|
||||
<slot />
|
||||
{icon && <Icon name={icon} class="size-5" aria-hidden="true" />}
|
||||
{external && <span class="sr-only">{site.ui.newTab}</span>}
|
||||
</a>
|
||||
) : (
|
||||
<button type={type} class:list={[classes, 'cursor-pointer']}>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
// Full-bleed page section: paper or khaki background with the magazine grid lines behind content.
|
||||
import Container from './Container.astro';
|
||||
import GridLines from './GridLines.astro';
|
||||
|
||||
export interface Props {
|
||||
id?: string;
|
||||
alt?: boolean;
|
||||
labelledby?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { id, alt = false, labelledby, class: className } = Astro.props;
|
||||
---
|
||||
|
||||
<section
|
||||
id={id}
|
||||
aria-labelledby={labelledby}
|
||||
class:list={['relative overflow-hidden', alt && 'surface-alt']}
|
||||
>
|
||||
<GridLines />
|
||||
<Container class:list={['relative py-16 md:py-24', className]}>
|
||||
<slot />
|
||||
</Container>
|
||||
</section>
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import type { HomeSection } from '../../lib/data';
|
||||
import Card from '../Card.astro';
|
||||
import ExternalLink from '../ExternalLink.astro';
|
||||
import Section from '../Section.astro';
|
||||
import SectionHeading from '../SectionHeading.astro';
|
||||
|
||||
export interface Props {
|
||||
section: Extract<HomeSection, { type: 'cards' }>;
|
||||
alt: boolean;
|
||||
}
|
||||
|
||||
const { section, alt } = Astro.props;
|
||||
const headingId = `${section.id}-title`;
|
||||
const display = (href: string) => {
|
||||
const u = new URL(href);
|
||||
return (u.host + u.pathname).replace(/^www\./, '').replace(/\/$/, '');
|
||||
};
|
||||
---
|
||||
|
||||
<Section id={section.id} alt={alt} labelledby={headingId}>
|
||||
<SectionHeading id={headingId} eyebrow={section.eyebrow} title={section.title} />
|
||||
<ul class="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{section.items.map((item) => (
|
||||
<li class="flex">
|
||||
<Card interactive class="w-full">
|
||||
<div class="flex flex-1 flex-col gap-4 p-6">
|
||||
<span class="bg-bg-alt text-fg inline-flex size-12 items-center justify-center">
|
||||
<Icon name={item.icon} class="size-6" aria-hidden="true" />
|
||||
</span>
|
||||
<h3 class="text-xl leading-snug font-bold">
|
||||
<ExternalLink
|
||||
href={item.href}
|
||||
icon={false}
|
||||
class="after:absolute after:inset-0 hover:underline"
|
||||
>
|
||||
{item.title}
|
||||
</ExternalLink>
|
||||
</h3>
|
||||
{item.description && <p class="text-fg-muted text-sm">{item.description}</p>}
|
||||
<p class="text-accent-text mt-auto inline-flex items-center gap-1 text-sm font-semibold">
|
||||
{display(item.href)}
|
||||
<Icon name="tabler:external-link" class="size-4" aria-hidden="true" />
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Section>
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import type { HomeSection } from '../../lib/data';
|
||||
import Badge from '../Badge.astro';
|
||||
import Button from '../Button.astro';
|
||||
import ExternalLink from '../ExternalLink.astro';
|
||||
import Section from '../Section.astro';
|
||||
import SectionHeading from '../SectionHeading.astro';
|
||||
import Numeral from './Numeral.astro';
|
||||
|
||||
export interface Props {
|
||||
section: Extract<HomeSection, { type: 'feature' }>;
|
||||
index: number;
|
||||
alt: boolean;
|
||||
}
|
||||
|
||||
const { section, index, alt } = Astro.props;
|
||||
const headingId = `${section.id}-title`;
|
||||
const host = new URL(section.link.href).host.replace(/^www\./, '');
|
||||
---
|
||||
|
||||
<Section id={section.id} alt={alt} labelledby={headingId}>
|
||||
<div class="grid gap-8 md:grid-cols-[1.4fr_1.6fr_4.2fr] md:gap-0">
|
||||
<div class="flex flex-col gap-6 md:col-start-3 md:row-start-1 md:pl-8">
|
||||
<SectionHeading id={headingId} eyebrow={section.eyebrow} title={section.title} />
|
||||
<p class="max-w-prose text-lg">{section.body}</p>
|
||||
<Button href={section.link.href} external icon="tabler:arrow-up-right" class="self-start">
|
||||
{section.link.label}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 md:col-start-1 md:row-start-1 md:flex-col md:items-start">
|
||||
<Numeral
|
||||
value={String(index).padStart(2, '0')}
|
||||
alt={alt}
|
||||
class="hidden text-[clamp(4rem,8vw,7rem)] md:block"
|
||||
/>
|
||||
<Icon name={section.icon} class="size-10" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<dl class="flex flex-col gap-4 text-sm md:col-start-2 md:row-start-1 md:px-6 md:pt-2">
|
||||
{section.callsign && (
|
||||
<div class="flex flex-col gap-1">
|
||||
<dt class="font-display font-bold tracking-wide uppercase">{section.callsignLabel}</dt>
|
||||
<dd>
|
||||
<Badge icon="tabler:antenna">{section.callsign}</Badge>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
<div class="flex flex-col gap-1">
|
||||
<dt class="sr-only">Web</dt>
|
||||
<dd>
|
||||
<ExternalLink href={section.link.href} class="link-strong text-base">
|
||||
{host}
|
||||
</ExternalLink>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</Section>
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import { home } from '../../lib/data';
|
||||
import Button from '../Button.astro';
|
||||
import Section from '../Section.astro';
|
||||
import SectionHeading from '../SectionHeading.astro';
|
||||
import Numeral from './Numeral.astro';
|
||||
|
||||
const { hero } = home;
|
||||
---
|
||||
|
||||
<Section alt labelledby="hero-title" class="md:py-28">
|
||||
<div class="grid gap-10 md:grid-cols-[1.4fr_1.6fr_4.2fr] md:gap-0">
|
||||
<div class="flex flex-col gap-8 md:col-start-3 md:row-start-1 md:pl-8">
|
||||
<SectionHeading level={1} id="hero-title" eyebrow={hero.eyebrow} title={hero.title} />
|
||||
<p class="max-w-xl text-lg">{hero.lead}</p>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
{hero.ctas.map((cta) => (
|
||||
<Button href={cta.href} variant={cta.variant} icon={cta.icon}>
|
||||
{cta.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6 md:col-start-1 md:row-start-1 md:pr-6">
|
||||
<Numeral value={hero.numeral} alt class="hidden text-[clamp(5rem,9vw,8rem)] md:block" />
|
||||
<ul class="flex flex-wrap gap-2 md:flex-col md:items-start">
|
||||
{hero.tags.map((tag) => (
|
||||
<li class="font-display border-fg inline-flex items-center gap-1.5 border px-2 py-1 text-xs font-bold tracking-wide uppercase">
|
||||
<Icon name={tag.icon} class="size-4" aria-hidden="true" />
|
||||
{tag.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 md:col-start-2 md:row-start-1 md:px-6">
|
||||
<p class="font-display text-2xl leading-tight font-bold">{hero.asideTitle}</p>
|
||||
<p class="text-sm">{hero.asideBody}</p>
|
||||
<Button href={hero.asideLink.href} variant="link" class="self-start">
|
||||
{hero.asideLink.label}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
// Latest posts block. Post cards are passed through the default slot (wired up in stage 4).
|
||||
import type { HomeSection } from '../../lib/data';
|
||||
import Button from '../Button.astro';
|
||||
import Section from '../Section.astro';
|
||||
import SectionHeading from '../SectionHeading.astro';
|
||||
|
||||
export interface Props {
|
||||
section: Extract<HomeSection, { type: 'latestPosts' }>;
|
||||
alt: boolean;
|
||||
hasPosts: boolean;
|
||||
}
|
||||
|
||||
const { section, alt, hasPosts } = Astro.props;
|
||||
const headingId = `${section.id}-title`;
|
||||
---
|
||||
|
||||
<Section id={section.id} alt={alt} labelledby={headingId}>
|
||||
<div class="flex flex-wrap items-end justify-between gap-6">
|
||||
<SectionHeading id={headingId} eyebrow={section.eyebrow} title={section.title} />
|
||||
{hasPosts && (
|
||||
<Button href={section.allLink.href} variant="link" icon="tabler:arrow-right">
|
||||
{section.allLink.label}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{hasPosts ? (
|
||||
<div class="mt-10">
|
||||
<slot />
|
||||
</div>
|
||||
) : (
|
||||
<p class="border-fg/30 mt-10 border-2 border-dashed p-8 text-center text-lg">{section.empty}</p>
|
||||
)}
|
||||
</Section>
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
// Giant decorative numeral (reference: Paper-coloured "2" on khaki).
|
||||
// Rendered via ::before so it is not text content (no a11y/contrast noise).
|
||||
export interface Props {
|
||||
value: string;
|
||||
alt?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
const { value, alt = false, class: className } = Astro.props;
|
||||
---
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
data-numeral={value}
|
||||
class:list={[
|
||||
'font-display block leading-[0.8] font-black tracking-tighter select-none before:content-[attr(data-numeral)]',
|
||||
alt ? 'text-paper dark:text-fg/15' : 'text-bg-alt dark:text-fg/15',
|
||||
className,
|
||||
]}
|
||||
></span>
|
||||
+62
-1
@@ -5,8 +5,10 @@
|
||||
import { z } from 'astro/zod';
|
||||
import siteJson from '../data/site.json';
|
||||
import socialJson from '../data/social.json';
|
||||
import homeJson from '../data/home.json';
|
||||
|
||||
const link = z.object({ label: z.string().min(1), href: z.string().min(1) });
|
||||
const icon = z.string().startsWith('tabler:');
|
||||
|
||||
const siteSchema = z.object({
|
||||
name: z.string(),
|
||||
@@ -47,11 +49,68 @@ const socialSchema = z.array(
|
||||
label: z.string(),
|
||||
// Empty string = account not set → hidden in the UI.
|
||||
href: z.union([z.url(), z.literal('')]),
|
||||
icon: z.string().startsWith('tabler:'),
|
||||
icon,
|
||||
handle: z.string().optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
const homeSchema = z.object({
|
||||
seo: z.object({ description: z.string().max(160) }),
|
||||
hero: z.object({
|
||||
numeral: z.string(),
|
||||
tags: z.array(z.object({ icon, label: z.string() })),
|
||||
asideTitle: z.string(),
|
||||
asideBody: z.string(),
|
||||
asideLink: link,
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
lead: z.string(),
|
||||
ctas: z.array(
|
||||
link.extend({ variant: z.enum(['primary', 'secondary', 'link']), icon: icon.optional() }),
|
||||
),
|
||||
}),
|
||||
sections: z.array(
|
||||
z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('feature'),
|
||||
id: z.string(),
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
body: z.string(),
|
||||
callsign: z.string().optional(),
|
||||
callsignLabel: z.string().optional(),
|
||||
icon,
|
||||
link: link.extend({ href: z.url() }),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('cards'),
|
||||
id: z.string(),
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
items: z
|
||||
.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
description: z.string().optional(),
|
||||
href: z.url(),
|
||||
icon,
|
||||
}),
|
||||
)
|
||||
.min(1),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('latestPosts'),
|
||||
id: z.string(),
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
count: z.number().int().positive(),
|
||||
allLink: link,
|
||||
empty: z.string(),
|
||||
}),
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
function parse<T>(name: string, schema: z.ZodType<T>, data: unknown): T {
|
||||
const result = schema.safeParse(data);
|
||||
if (!result.success) {
|
||||
@@ -61,7 +120,9 @@ function parse<T>(name: string, schema: z.ZodType<T>, data: unknown): T {
|
||||
}
|
||||
|
||||
export const site = parse('site.json', siteSchema, siteJson);
|
||||
export const home = parse('home.json', homeSchema, homeJson);
|
||||
export const social = parse('social.json', socialSchema, socialJson).filter((s) => s.href);
|
||||
|
||||
export type Site = z.infer<typeof siteSchema>;
|
||||
export type HomeSection = (typeof home.sections)[number];
|
||||
export type SocialLink = (typeof social)[number];
|
||||
|
||||
+25
-8
@@ -1,13 +1,30 @@
|
||||
---
|
||||
// Temporary placeholder; replaced by the real home page in stage 3.
|
||||
import Container from '../components/Container.astro';
|
||||
import SectionHeading from '../components/SectionHeading.astro';
|
||||
import CardGrid from '../components/home/CardGrid.astro';
|
||||
import FeatureSection from '../components/home/FeatureSection.astro';
|
||||
import Hero from '../components/home/Hero.astro';
|
||||
import LatestPosts from '../components/home/LatestPosts.astro';
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { site } from '../lib/data';
|
||||
import { home } from '../lib/data';
|
||||
|
||||
// Hero is khaki, so sections alternate paper → khaki → … after it.
|
||||
let featureIndex = 0;
|
||||
const sections = home.sections.map((section, i) => ({
|
||||
section,
|
||||
alt: i % 2 === 1,
|
||||
index: section.type === 'feature' ? ++featureIndex : 0,
|
||||
}));
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<Container class="py-20">
|
||||
<SectionHeading level={1} eyebrow={site.author.locator} title={site.name} />
|
||||
</Container>
|
||||
<BaseLayout description={home.seo.description}>
|
||||
<Hero />
|
||||
{sections.map(({ section, alt, index }) => {
|
||||
switch (section.type) {
|
||||
case 'feature':
|
||||
return <FeatureSection section={section} index={index} alt={alt} />;
|
||||
case 'cards':
|
||||
return <CardGrid section={section} alt={alt} />;
|
||||
case 'latestPosts':
|
||||
return <LatestPosts section={section} alt={alt} hasPosts={false} />;
|
||||
}
|
||||
})}
|
||||
</BaseLayout>
|
||||
|
||||
Reference in New Issue
Block a user