diff --git a/PROJECT_MEMORY.md b/PROJECT_MEMORY.md index 90b1665..e2e1a09 100644 --- a/PROJECT_MEMORY.md +++ b/PROJECT_MEMORY.md @@ -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//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/` diff --git a/src/components/Button.astro b/src/components/Button.astro index 3133154..c703f53 100644 --- a/src/components/Button.astro +++ b/src/components/Button.astro @@ -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 = { @@ -29,9 +41,10 @@ const classes = [ --- {href ? ( - + {icon && ) : ( + + +
+
+ +
+ {section.callsign && ( +
+
{section.callsignLabel}
+
+ {section.callsign} +
+
+ )} +
+
Web
+
+ + {host} + +
+
+
+ + diff --git a/src/components/home/Hero.astro b/src/components/home/Hero.astro new file mode 100644 index 0000000..34c89df --- /dev/null +++ b/src/components/home/Hero.astro @@ -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; +--- + +
+
+
+ +

{hero.lead}

+
+ {hero.ctas.map((cta) => ( + + ))} +
+
+ +
+
+ +
+

{hero.asideTitle}

+

{hero.asideBody}

+ +
+
+
diff --git a/src/components/home/LatestPosts.astro b/src/components/home/LatestPosts.astro new file mode 100644 index 0000000..cd0814f --- /dev/null +++ b/src/components/home/LatestPosts.astro @@ -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; + alt: boolean; + hasPosts: boolean; +} + +const { section, alt, hasPosts } = Astro.props; +const headingId = `${section.id}-title`; +--- + +
+
+ + {hasPosts && ( + + )} +
+ {hasPosts ? ( +
+ +
+ ) : ( +

{section.empty}

+ )} +
diff --git a/src/components/home/Numeral.astro b/src/components/home/Numeral.astro new file mode 100644 index 0000000..6ecb9e5 --- /dev/null +++ b/src/components/home/Numeral.astro @@ -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; +--- + + diff --git a/src/lib/data.ts b/src/lib/data.ts index 0c02073..0edae9a 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -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(name: string, schema: z.ZodType, data: unknown): T { const result = schema.safeParse(data); if (!result.success) { @@ -61,7 +120,9 @@ function parse(name: string, schema: z.ZodType, 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; +export type HomeSection = (typeof home.sections)[number]; export type SocialLink = (typeof social)[number]; diff --git a/src/pages/index.astro b/src/pages/index.astro index 35f2e48..ff38cf5 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -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, +})); --- - - - - + + + {sections.map(({ section, alt, index }) => { + switch (section.type) { + case 'feature': + return ; + case 'cards': + return ; + case 'latestPosts': + return ; + } + })}