Damla Okul — Proje Mimarisi

Damla Okul — Proje Mimarisi

Dokümantasyon: Bu projenin belgeleri dört dosyada toplanmıştır; ilgili değişiklikte o dosyayı güncelleyin:

  • README.md — Genel bakış, kurulum ve hızlı başlangıç
  • project.md — Teknik mimari ve geliştirme kuralları (bu dosya)
  • design.md — Stil, tasarım sistemi ve UI bileşenleri
  • getdata.md — Dış veri çekimi (TurkiyeAPI, MEB okullar / okul detay, nüfus)

Bu belge, damlaokul.com (Damla Okul) statik sitesinin teknik yapısını, dosya organizasyonunu ve geliştirme kurallarını açıklar. Stil için design.md; TurkiyeAPI ve MEB JSON’ları için getdata.md.

Genel Bakış

Özellik Değer
Tür Statik site (Jamstack)
Motor Jekyll 3.10 (github-pages gem v232)
Dil Türkçe (language: tr)
Yayın GitHub Pages (CNAMEdamlaokul.com)
Build çıktısı _site/ (gitignore)
Node/npm Yok — CSS doğrudan düzenlenir, build adımı gerekmez

Site, Damla Okul markası altında Damla Yayınevi’nin okul yayınlarını ve eğitim setlerini listeleyen bir ürün kataloğudur. İçerik Markdown + YAML front matter ile yönetilir; görünüm Bootstrap 5 ve özel CSS katmanlarıyla sağlanır.


Mimari Diyagram

┌─────────────────────────────────────────────────────────────┐
│  İçerik (Markdown + YAML)                                   │
│  _books/  _catalogs/  _pages/  _posts/  _slides/  index   │
└──────────────────────────┬──────────────────────────────────┘
                           │ Jekyll build
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  Şablonlar                                                  │
│  _layouts/  →  default.html  →  book.html, page.html, …     │
│  _includes/  →  book-card, slider, instagram-carousel, search, footer │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│  Statik çıktı (_site/)                                      │
│  HTML + assets/css + assets/js + assets/images              │
└──────────────────────────┬──────────────────────────────────┘
                           │ git push
                           ▼
                    GitHub Pages (damlaokul.com)

CSS Katmanları

Stil dosyaları ve sorumluluk ayrımı design.md dosyasında ayrıntılı olarak tanımlıdır.

Özet: theme.css (bileşen + token) → app.css (Bootstrap override). bootstrap.min.css düzenlenmez.


Klasör Yapısı

damlaegitim/
├── _config.yml           # Site ayarları, koleksiyonlar, plugin’ler
├── README.md             # Kurulum
├── project.md            # Mimari (bu belge)
├── design.md             # Tasarım sistemi
├── getdata.md            # Dış veri çekimi (TurkiyeAPI, MEB)
├── _books/               # Ürünler (kitap / eğitim seti) — ~187 kayıt
├── _catalogs/            # PDF/flipbook kataloglar
├── _slides/              # Anasayfa slider verisi (output: false)
├── _posts/               # Blog yazıları
├── _pages/               # Statik sayfalar (hakkımızda, ürünler, iletişim…)
├── _layouts/             # HTML şablonları
├── _includes/            # Yeniden kullanılabilir parçalar
├── assets/
│   ├── css/
│   │   ├── bootstrap.min.css
│   │   ├── fontawesome-all.min.css
│   │   ├── theme.css
│   │   ├── app.css
│   │   ├── ogretmen-wizard.css   # Öğretmen talep formu (/ogretmen)
│   │   ├── spotlight.css
│   │   ├── tiny-slider.css
│   │   └── buyout.css            # Kullanılmıyor (legacy; layout’ta yüklenmez)
│   ├── fonts/
│   │   ├── fontawesome/          # Font Awesome 5.15.4 webfonts (tam paket)
│   │   ├── Koho/                 # KoHo (TTF + WOFF2 subset; gövde + navbar)
│   │   ├── punta/
│   │   ├── geometric-sans/     # CSS'te referans yok; repo'da kalır
│   │   ├── raykjavik/            # CSS'te referans yok; repo'da kalır
│   │   └── monolight/            # CSS'te referans yok; repo'da kalır
│   ├── js/
│   │   ├── bootstrap.bundle.min.js
│   │   ├── nav.js                # Navbar scroll (vanilla JS)
│   │   ├── book-filter.js        # Sınıf/tür filtreleme (/ , /urunler, /ogretmen wizard)
│   │   ├── lunr.js               # Arama (lazy-load; açılınca yüklenir)
│   │   ├── tiny-slider.js        # Anasayfa slider
│   │   └── theme.js              # Eski (jQuery) — kullanılmıyor
│   └── images/
│       ├── slides/               # Anasayfa slider görselleri (webp otomatik üretilebilir)
│       ├── ean/                  # Kitap kapak görselleri (webp otomatik üretilebilir)
│       └── favicon/
├── _data/
│   ├── webp_manifest.yml         # Otomatik: mevcut .webp listesi (generate_webp.sh)
│   ├── turkiye_adres_il_ilce.json # İl / ilçe (wizard; sync_site_data.py türetir)
│   ├── tymm.json                 # TYMM müfredat + Erdem-Değer çerçevesi (build_tymm_reference.rb)
│   ├── dersler.json              # …
├── docs/                           # Yerel kaynak (gitignore + Jekyll exclude)
│   └── data/                       # Kanonik fetch JSON (getdata.md)
│       ├── turkiye_adres.json
│       ├── turkiye_geodata.json
│       ├── okullar.json
│       ├── okullar_detay.json
│       ├── tymm/                   # TYMM ham API, PDF, referans CSV
│       └── reference/              # TurkiyeAPI / HDX vendor snapshot
├── assets/
│   └── data/
│       ├── okullar.json            # Wizard fetch (sync; gitignore)
│       ├── geodata/                # Harita sınır parçaları (sync; gitignore)
│       └── okullar-harita/         # Okul listesi + detay il parçaları (sync; gitignore)
├── scripts/
│   ├── install_image_tools.sh    # WebP/ImageMagick kurulumu (install.sh; winget/brew/apt)
│   ├── generate_webp.sh          # jpg/png → .webp + manifest güncelleme (start.sh hook)
│   ├── sync_site_data.py         # docs/data → site türetilmiş dosyalar (start.sh hook)
│   ├── refresh_image_paths.sh    # Windows winget PATH düzeltmesi (dahili)
│   ├── check_images.sh           # Büyük görsel uyarı raporu (start.sh hook)
│   ├── check_fonts.sh            # Font / WOFF2 uyarı raporu (install.sh)
│   ├── subset_font.sh            # Tüm OTF/TTF → WOFF2 subset (install.sh)
│   ├── normalize_book_frontmatter.rb   # Kitap front matter sıralama/normalize (`preview_link`, `examlink`, `damlaurl`; eski alan migrasyonu)
│   ├── map_story_metadata.rb           # Story kitap: anatema, degerler, egilimler, beceriler, unite (orchestrator)
│   ├── curriculum_lib.rb               # TYMM/anatema sözlük yardımcıları (map_story_*)
│   ├── ogretmen-submit.gs              # Öğretmen wizard → Sheets + mail (Workspace’te dağıtılır; repo referans kopyası)
│   ├── fetch_turkiyeadres.py           # → docs/data/turkiye_adres.json (getdata.md)
│   ├── fetch_okullar.py                # → docs/data/okullar.json kamu (getdata.md)
│   ├── fetch_ozel_okullar.py           # → okullar.json özel birleştirme (getdata.md)
│   ├── fetch_okuldetay.py              # → docs/data/okullar_detay.json (getdata.md)
│   ├── fetch_population.py             # → docs/data/population.json (getdata.md)
│   └── sync_site_data.py               # → site türetilmiş dosyalar (getdata.md)
├── index.html            # Anasayfa
├── Gemfile               # github-pages + webrick (canlı GitHub Pages ile aynı stack)
├── CNAME                 # damlaokul.com
├── install.sh            # İlk kurulum: bundle, fonttools, WOFF2, görsel araçları, jekyll build
├── start.sh              # Geliştirme: check_images + generate_webp + sync_site_data hook + jekyll serve
└── _site/                # Build çıktısı (gitignore)

Koleksiyonlar ve URL’ler

Koleksiyon Klasör URL Layout Çıktı
books _books/ /urunler/:title book Evet
catalogs _catalogs/ /kataloglar/:title catalog Evet
slides _slides/ Hayır (veri kaynağı)
Sayfalar _pages/ /:title/ page Evet
Yazılar _posts/ varsayılan post Evet

_pages klasörü _config.yml içinde include: ["_pages"] ile Jekyll kaynaklarına dahil edilir. docs/ klasörü gitignore ve _config.yml exclude listesindedir; Jekyll build çıktısına girmez.


Layout’lar

Dosya Kullanım
default.html Kök şablon: head, navbar, footer, script’ler
page.html Basit içerik sayfası
page-sidebar.html İçerik + yan menü
book.html Ürün detay sayfası
previewbook.html Tam ekran önizleme
catalog.html Katalog detay + iframe görüntüleyici
post.html Blog yazısı
person.html / illustrator.html / translator.html Kişi profilleri
ogretmen-wizard.html 6 adımlı öğretmen talep formu (/ogretmen)

Tüm layout’lar layout: default zinciri üzerinden default.html’i extend eder.


Öğretmen talep formu (wizard)

URL: /ogretmenDurum: Canlı (Jekyll → Apps Script → Sheets + e-posta)

Öğretmenler sınıf seçer, hikaye ve eğitim kitaplarından liste oluşturur, iletişim bilgilerini girer ve talebi gönderir. Statik sitede backend yok; gönderim Google Apps Script web uygulamasına fetch POST ile yapılır.

flowchart LR
  Browser["Tarayıcı /ogretmen"]
  GAS["Apps Script doPost"]
  Sheet["Sheets Talepler"]
  Mail["MailApp"]
  Captcha["reCAPTCHA siteverify"]
  Browser -->|"POST JSON"| GAS
  GAS --> Captcha
  GAS --> Sheet
  GAS --> Mail

Sihirbaz adımları

# Adım İçerik
1 Sınıf Okul öncesi – 8. sınıf
2 Okuma listesi Hikaye; anatema → değer → eğilim → beceri filtreleri (kademeli)
3 Eğitim kitapları Eğitim setleri; arama
4 Liste Seçilen ürünler; okuma + eğitim grupları
5 İletişim Ad, soyad, il, ilçe, telefon, e-posta, okul
6 Gönder Özet, reCAPTCHA v2, gönder

State: localStoragedamlaokul:ogretmen-wizard (version: 5). Sınıf değişince liste ve filtreler sıfırlanır. Katalog build’de site.books JSON; listeye eklenen kitap katalogdan çıkar.

Dosyalar

Dosya Görev
_pages/ogretmen.html Permalink, SEO, footer
_layouts/ogretmen-wizard.html Adımlar, state, katalog JSON, submit
_includes/ogretmen-wizard/step-*.html 6 adım UI
assets/css/ogretmen-wizard.css Wizard stilleri (design.md)
assets/js/book-filter.js TYMM filtreleri
_data/turkiye_adres_il_ilce.json İl / ilçe wizard embed; sync_site_data.py türetir — getdata.md
docs/data/okullar.json MEB kamu + OOKGM özel kurum listesi (kanonik) — getdata.md
assets/data/okullar.json Wizard fetch; sync ile üretilir, git’te yok — getdata.md
docs/data/okullar_detay.json Okul meta monolit (kurum_kodu); sync ile assets/data/okullar-harita/ il parçalarına bölünür — getdata.md
docs/data/population.json İl/ilçe nüfus ve çocuk sayıları (TurkiyeAPI + TÜİK ADNKS vendor); site sync yok — getdata.md
assets/data/okullar-harita/ Harita sayfası lazy fetch; sync türetilmiş — getdata.md
_data/tymm.json Hikâye filtre sıralaması; Erdem-Değer çerçevesi (D1–D20); öğretmen sihirbazı değer listesi
scripts/ogretmen-submit.gs Backend referansı (Workspace’te dağıtılır)

Yapılandırma (_config.yml)

ogretmen_submit_url: "https://script.google.com/macros/s/…/exec"
ogretmen_recaptcha_site_key: "…"   # v2 site key (herkese açık)

Gizli anahtar config’e yazılmaz — Apps Script Script Properties → RECAPTCHA_SECRET.

Gönderim akışı

  1. buildSheetRow() → JSON (talep_id, iletişim, ürünler, filtre_*, recaptcha_token, urunler[])
  2. POST, Content-Type: text/plain;charset=utf-8
  3. Yanıt { ok: true } → başarı ekranı; localStorage temizlenir
  4. Hata → öğretmene genel mesaj; ayrıntı console.log('Gönderim hatası:', …)

Google Workspace kurulumu

  1. Shared Drive’da Öğretmen Talepleri e-tablosu; Apps Script e-tabloya bağlı
  2. scripts/ogretmen-submit.gs içeriğini editöre yapıştır
  3. Script properties: NOTIFY_EMAIL (virgülle alıcılar), RECAPTCHA_SECRET (gizli anahtar)
  4. izinleriAl fonksiyonunu editörden çalıştır → UrlFetchApp izni ver
  5. Web app deploy: Execute as Me, Anyone; kod değişince New version
  6. /exec URL → ogretmen_submit_url; siteyi yeniden deploy et

Sheet sayfaları (ilk POST’ta oluşur): Talepler, Talep_Urunleri. Form filtre_anatema / filtre_degerler / filtre_egilimler / filtre_beceriler de gönderir; Sheet başlıklarına isteğe bağlı eklenir.

Tarayıcıda /exec URL’sini GET ile açmak doGet not found döner — normal (doPost only).

Mail hatası Sheet kaydını iptal etmez.

Sorun giderme

Console / belirti Çözüm
UrlFetchApp… izniniz yok izinleriAl + izin ver + yeni sürüm deploy
invalid-input-secret RECAPTCHA_SECRET = gizli anahtar (site key değil)
missing_token reCAPTCHA yeniden işaretle
Sheet var, mail yok NOTIFY_EMAIL / MailApp kotası
Eski davranış Deploy → New version

Yerel test: sh start.shhttp://localhost:4000/ogretmen (reCAPTCHA admin’de localhost tanımlı olmalı).


Önemli Include’lar

Dosya Görev
menu-header.html Ana navigasyon linkleri
footer-menu.html 3 sütunlu site footer (ürünler, önemli bilgiler, iletişim)
contact-info.html Site geneli iletişim bilgileri (site.contact_*, footer ve iletişim sayfası)
menu-socialmedia.html Sosyal medya ikonları
slider.html Anasayfa Tiny Slider (<picture>, WebP, lazyload, fetchpriority)
instagram-carousel.html Anasayfa Instagram carousel (okul.damla, Behold JSON feed)
book-grade-nav.html Sınıf sekmesi + tür alt menüsü
book-home-groups.html Anasayfa kitap listesi (Eğitim / Hikaye)
book-card.html Tek kitap kartı partial (<picture> + WebP, native lazy loading)
book-grade-filter.html Eski sınıf checkbox dropdown’u (artık kullanılmıyor)
search-lunr.html Spotlight arama (lazy-load Lunr + JSON indeks, modal UI, navbar tetikleyici)
popup.html Kitap detay popup’ları (Satın Al / Bilgi, İncele iframe, YouTube, HDS; koyu tema desteği)
eanimage.html Kitap kapak <picture> + koşullu WebP (eanimage partial)
tracking-header.html / tracking-footer.html Google Analytics

JavaScript

Dosya Yükleme Görev
bootstrap.bundle.min.js defer, tüm sayfalar Collapse, dropdown, modal
nav.js defer, tüm sayfalar Sticky navbar yüksekliği (--nav-height), scroll gölgesi
book-filter.js defer, /, /urunler, /ogretmen Sınıf/tür filtreleme + hash URL; wizard’da TYMM filtreleri
lunr.js Lazy — arama açılınca Spotlight kitap araması (client-side indeks)
tiny-slider.js Anasayfa (slider.html) Slider + lazyload

jQuery kullanılmaz. lazyload.js kaldırıldı; görseller native loading="lazy" ve <picture> ile yönetilir.

instagram-carousel.html kendi inline <script> bloğunu taşır; feed IntersectionObserver ile viewport’a girince yüklenir.

search-lunr.html navbar Spotlight aramasını sağlar. mode parametresiyle navbar tetikleyicisi (desktop / mobile) veya script bloğu (mode olmadan) ayrı include edilir.

  • Masaüstü (mode='desktop') — Navbar collapse içinde tam arama kutusu (Ctrl+K kısayolu)
  • Mobil (mode='mobile') — Logo ile hamburger arasında minimal arama çubuğu (Kitap ara...); collapse dışında, her zaman görünür

Performans Optimizasyonu

Site hızı için uygulanan başlıca önlemler:

LCP (Hero Slider)

  • Mobil-öncelik <picture>: mobil webp/jpeg önce, desktop min-width: 768px
  • İlk slide <img src> mobil jpeg; fetchpriority="high", width/height ipucu
  • Preload: mobil webp (max-width: 767px); desktop webp veya jpeg (min-width: 768px) — anasayfa head
  • tiny-slider.js + slider-init.js defer; inline init kaldırıldı
  • Diğer slide’lar: Tiny Slider lazyload: true + loading="lazy"
  • tiny-slider.css anasayfa head’de preload + link (body içi link yok)

Görseller

  • Kitap kartları: <picture> + koşullu WebP, loading="lazy", fetchpriority="low"
  • jpg/png Photoshop ile optimize edilir (resize/sıkıştırma); ImageMagick resize kullanılmaz
  • .webp scripts/generate_webp.sh ile otomatik üretilir (start.sh hook)
  • _data/webp_manifest.yml — mevcut webp listesi; şablonlarda koşullu <source type="image/webp">
  • install.shinstall_image_tools.sh: Windows’ta winget ile ImageMagick + libwebp kurulumu
  • scripts/refresh_image_paths.sh — winget kurulum yollarını Git Bash PATH’ine ekler
  • Araç yoksa (cwebp / ImageMagick) site jpg ile çalışır, 404 oluşmaz
  • scripts/check_images.sh büyük dosyaları raporlar (dosyaya dokunmaz; ImageMagick identify kullanır)
  • start.sh her geliştirme oturumunda görsel kontrolü ve webp üretimini denemek

JavaScript (TBT / TTI)

  • Lunr araması lazy-load: lunr.js ve indeks yalnızca arama açılınca yüklenir
  • Arama indeksi: _pages/search-index.json/assets/search-index.json (build-time JSON)
  • book-filter.js yalnızca anasayfa ve /urunler; anasayfa init requestIdleCallback (fallback setTimeout)
  • Bootstrap defer ile yüklenir

Fontlar ve CSS

  • Font Awesome yerel (assets/fonts/fontawesome/), CDN kaldırıldı
  • KoHo WOFF2 subset (~14 KB / ağırlık; Regular, Medium, Bold, Italic); TTF repo’da kalır, servis edilmez
  • KoHo Regular font preload (default.html)
  • spotlight.css async yükleme (media="print" onload)
  • .book-genre-group: content-visibility: auto (below-fold paint maliyeti)

Cloudflare mobil benchmark (referans)

Skor ~55; LCP ~11.5 s, TTI ~13.3 s (büyük mobil jpeg, senkron slider JS). Kod düzeltmeleri sonrası Cloudflare Speed Test ile mobil retest önerilir.

  • Behold feed isteği IntersectionObserver ile carousel viewport’a girince başlar

Cloudflare

  • Şu an DNS-only (gri bulut); proxy veya cache ayarı bu repoda dokümante edilmez.

Yerel Araçlar

# İlk kurulum (git clone sonrası)
sh install.sh                       # bundle, fonttools, WOFF2, görsel araçları, jekyll build

# Geliştirme (hook'lar yalnızca burada)
sh start.sh                         # check_images + generate_webp + jekyll serve

# Manuel script'ler
sh scripts/install_image_tools.sh   # WebP/ImageMagick (Windows: winget)
sh scripts/generate_webp.sh         # .webp üretimi + webp_manifest.yml
sh scripts/check_images.sh          # Büyük görsel uyarı raporu
sh scripts/check_images.sh slides   # Yalnızca slider
sh scripts/check_images.sh ean      # Yalnızca kitap kapakları
sh scripts/check_fonts.sh           # Font / WOFF2 uyarı raporu
sh scripts/subset_font.sh           # Tüm OTF/TTF → WOFF2 subset (fontawesome hariç)

install.sh vs start.sh

  install.sh start.sh
Amaç Sıfırdan kurulum Günlük geliştirme
Ruby / bundle bundle install Gemfile.lock kontrolü
Fontlar subset_font.sh, check_fonts.sh
Görsel araçları install_image_tools.sh (winget/brew/apt)
Görsel hook’ları check_images.sh, generate_webp.sh, sync_site_data.py
Jekyll jekyll build (doğrulama) jekyll serve
_data/webp_manifest.yml Yoksa oluşturur generate_webp.sh günceller

Ana navigasyon default.html içinde id="MagicMenu" ile tanımlıdır; scroll sırasında her zaman üstte sabit kalır.

Yapı, responsive davranış ve görsel stiller design.md — Site Navbar bölümünde tanımlıdır.

  • --nav-height CSS değişkenini navbar yüksekliğine göre günceller (ResizeObserver ile mobil menü açılınca da)
  • Scroll’da site-nav--scrolled sınıfı ile hafif gölge ekler

Kitap Detay Sayfası (book.html)

Ürün detay layout’u book-page sınıfı ile işaretlenir. Etiket stilleri, prose düzeni ve popup görünümü design.md — Kitap Detay bölümünde tanımlıdır.

Üst alan (hero meta)

Hikaye kitaplarında (genre: story) müfredat bilgisi başlık altında book-hero-meta.html içindeki .book-curriculum paneli ile gösterilir. Panel iki bölümden oluşur:

Türkiye Yüzyılı Maarif Modeli

UI etiketi Front matter Görünüm
Anatemalar: anatema Outline chip; yatay scroll şeridi
Erdemler / Değerler: degerler Outline yeşil chip; yatay scroll şeridi
Eğilimler: egilimler Outline chip; yatay scroll şeridi
Beceriler: beceriler Düz chip listesi; yatay scroll şeridi

Türkiye’de İlk ve Tek Damla Hikaye Kazanım Sistemi

UI etiketi Front matter Görünüm
Öykümatik Kazanım Kodu: kazanim Kod chip’leri; title ile kazanım metni; yatay scroll

Etiketler (panel altında)

UI etiketi Front matter Görünüm
(etiket) tags Turuncu pill

Mobilde müfredat satırları dikey (etiket üstte, chip şeridi altta tam genişlik); sağ kenarda fade ve chevron kaydırma ipucu verir.

Görünür metinde # / @ öneki kullanılmaz. Müfredat ve etiket alanlarının tamamı boşsa hero meta bloğu render edilmez.

Eğitim setlerinde (genre: education) müfredat kanıtı gövdedeki Maarif uyum tablosu (markdown tablo, .maarif-content stili) ile sunulur; ayrıntı maarif-modeli.

Filtre bloğu sırası (# Spesific Filterable Attributes)

genregradestagsdegerleranatemaegilimlerkazanimbecerilerunite

tags üst bölümde (categories yanında) tutulmaz; normalize_book_frontmatter.rb aynı sırayı yazar.

Eski üst alan notu (kaldırıldı)

Önceden anatemalar ve kavramlar yan kolondaki «Öğretmen için» kutusundaydı; TYMM revizyonuyla degerler, anatema, egilimler, beceriler ve kazanim hero başlık altında gösterilir (unite story kitaplarda UI/filtre dışı).

Aksiyon butonları ve popup

Tanıtım (YouTube), Satın Al / Bilgi, İncele, HDS ve Akıllı Tahta İndir butonları .js-book-action ile popup.html üzerinden açılır (book-detail-actions.html):

Buton Koşul Davranış
Tanıtım youtube dolu YouTube embed / yeni sekme
Satın Al damlaurl dolu iframe popup (ürün sayfası)
Bilgi damlaurl boş Tedarik bilgisi modal
İncele preview_link dolu iframe popup (ön izleme)
HDS examlink dolu iframe popup (PDF)
Akıllı Tahta İndir genre: education + akillitahta en az bir URL dolu Platform seçim popup (indirme)
data-popup-type Davranış
info Tedarik bilgisi modal (mobil + masaüstü)
iframe Tam ekran iframe (Satın Al, İncele, HDS)
youtube YouTube embed
akillitahta Platform seçimi: exe → Windows (.exe), deb → Pardus (.deb), appimage → Pardus (.AppImage), dmg → macOS (.dmg)

Masaüstünde iframe/youtube popup; mobilde yeni sekme. info ve akillitahta her zaman popup. info popup’u prefers-color-scheme: dark destekler.


Spotlight Arama

Navbar üzerinden kitap araması yapılır. macOS Spotlight benzeri tam ekran modal açılır.

Dosyalar

Dosya Görev
_includes/search-lunr.html Lazy-load arama scripti, navbar tetikleyici
_pages/search-index.json Build-time arama indeksi (/assets/search-index.json)
assets/js/lunr.js Lunr.js 2.1.5 kütüphanesi (arama açılınca yüklenir)
assets/css/spotlight.css Modal stilleri (async yükleme)

İndeks kapsamı

site.books koleksiyonu; alanlar: title, ean, authors, categories, grades, genre, degerler, anatema, egilimler, kazanim, beceriler, tags, body. Kapak görseli indekste assets/images/ean/{ean}.webp (yoksa .jpg) kullanılır.

4+ haneli tamamen sayısal aramalarda ean alanında doğrudan eşleştirme yapılır; diğer sorgularda Lunr wildcard araması kullanılır.

İndeks her sayfaya gömülmez; /assets/search-index.json olarak ayrı endpoint’ten fetch edilir. Bu sayede sayfa yüklenirken Lunr indeksleme maliyeti oluşmaz.

Kullanım

  • Navbar’daki arama kutusuna tıklama (masaüstü tam kutu; mobil logo–hamburger arası minimal çubuk)
  • Ctrl+K / ⌘K klavye kısayolu
  • Enter → ilk sonuca git; ESC → kapat

Eski theme.js içindeki loadSearch() (content.json tabanlı) kullanılmaz.


Anasayfada @okul.damla hesabının güncel gönderilerini yatay carousel olarak gösterir. Modül tek dosyada inline HTML, CSS ve vanilla JS içerir.

Yapılandırma (_config.yml)

instagram_carousel:
  username: okul.damla
  profile_url: https://instagram.com/okul.damla
  feed_url: ''   # Behold JSON feed URL
  limit: 12

feed_url boşsa modül build’i kırmaz; kullanıcıya yapılandırma mesajı ve Instagram profil linki gösterilir.

Veri kaynağı

Instagram, statik siteden doğrudan feed çekmeye izin vermez. Behold.so JSON feed kullanılır:

  1. Behold hesabında okul.damla kaynak olarak bağlanır
  2. Çıktı tipi JSON olan feed oluşturulur
  3. https://feeds.behold.so/XXXX URL’si feed_url alanına yazılır

Kullanım




<section class="ig-carousel"
         id="ig-carousel-okul-damla"
         data-feed-url="https://feeds.behold.so/k4ylFSaGtUBY15ZsyXAt"
         data-username="okul.damla"
         data-profile-url="https://instagram.com/okul.damla"
         data-limit="12"
         aria-label="Instagram gönderileri">
  <div class="container">
    <header class="ig-carousel__header">
      <h3 class="ig-carousel__heading">
        <i class="fab fa-instagram" aria-hidden="true"></i>
        <span>@okul.damla</span>
      </h3>
      <a class="ig-carousel__follow"
         href="https://instagram.com/okul.damla"
         target="_blank"
         rel="noopener noreferrer">
        Takip Et
      </a>
    </header>

    <div class="ig-carousel__viewport">
      <button class="ig-carousel__nav ig-carousel__nav--prev"
              type="button"
              aria-label="Önceki gönderiler"
              hidden>
        <i class="fas fa-chevron-left" aria-hidden="true"></i>
      </button>

      <div class="ig-carousel__track" role="list" aria-live="polite">
        
        <div class="ig-carousel__item ig-carousel__item--skeleton" role="listitem" aria-hidden="true">
          <div class="ig-carousel__skeleton"></div>
        </div>
        
        <div class="ig-carousel__item ig-carousel__item--skeleton" role="listitem" aria-hidden="true">
          <div class="ig-carousel__skeleton"></div>
        </div>
        
        <div class="ig-carousel__item ig-carousel__item--skeleton" role="listitem" aria-hidden="true">
          <div class="ig-carousel__skeleton"></div>
        </div>
        
        <div class="ig-carousel__item ig-carousel__item--skeleton" role="listitem" aria-hidden="true">
          <div class="ig-carousel__skeleton"></div>
        </div>
        
      </div>

      <button class="ig-carousel__nav ig-carousel__nav--next"
              type="button"
              aria-label="Sonraki gönderiler"
              hidden>
        <i class="fas fa-chevron-right" aria-hidden="true"></i>
      </button>
    </div>

    <p class="ig-carousel__status" hidden></p>
  </div>
</section>

<style>
  .ig-carousel {
    margin: 2rem 0;
  }

  .ig-carousel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1rem;
  }

  .ig-carousel__heading {
    font-family: var(--font-display, "Punta", sans-serif);
    font-size: 1.1rem;
    font-weight: 700;
    color: #343a40;
    margin: 0;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid #e9ecef;
    flex: 1;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .ig-carousel__heading .fab {
    color: var(--color-primary, #03a87c);
    font-size: 1.25rem;
  }

  .ig-carousel__follow {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    padding: 0.4rem 1rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: #fff;
    background: var(--color-primary, #03a87c);
    border-radius: var(--radius, 0.5rem);
    text-decoration: none;
    transition: background 0.2s ease;
  }

  .ig-carousel__follow:hover {
    background: var(--color-primary-dark, #028a66);
    color: #fff;
    text-decoration: none;
  }

  .ig-carousel__viewport {
    position: relative;
  }

  .ig-carousel__track {
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 0.25rem 0;
  }

  .ig-carousel__track::-webkit-scrollbar {
    display: none;
  }

  .ig-carousel__item {
    flex: 0 0 calc(50% - 0.375rem);
    scroll-snap-align: start;
    min-width: 0;
  }

  @media (min-width: 576px) {
    .ig-carousel__item {
      flex: 0 0 calc(33.333% - 0.5rem);
    }
  }

  @media (min-width: 992px) {
    .ig-carousel__item {
      flex: 0 0 calc(25% - 0.5625rem);
    }
  }

  .ig-carousel__card {
    display: block;
    position: relative;
    background: #fff;
    border: 1px solid var(--color-border, #dee2e6);
    border-radius: var(--radius, 0.5rem);
    overflow: hidden;
    aspect-ratio: 1;
    text-decoration: none;
    transition: box-shadow 0.2s ease, transform 0.2s ease;
  }

  .ig-carousel__card:hover {
    box-shadow: var(--shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.08));
    transform: translateY(-2px);
    text-decoration: none;
  }

  .ig-carousel__card img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  .ig-carousel__caption {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-end;
    padding: 0.75rem;
    background: linear-gradient(transparent 40%, rgba(0, 0, 0, 0.65));
    color: #fff;
    font-size: 0.75rem;
    line-height: 1.4;
    opacity: 0;
    transition: opacity 0.2s ease;
    pointer-events: none;
  }

  .ig-carousel__card:hover .ig-carousel__caption,
  .ig-carousel__card:focus-visible .ig-carousel__caption {
    opacity: 1;
  }

  .ig-carousel__badge {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 1.75rem;
    height: 1.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    border-radius: 50%;
    font-size: 0.7rem;
    pointer-events: none;
  }

  .ig-carousel__skeleton {
    width: 100%;
    aspect-ratio: 1;
    border-radius: var(--radius, 0.5rem);
    background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%);
    background-size: 200% 100%;
    animation: ig-carousel-shimmer 1.4s ease-in-out infinite;
  }

  @keyframes ig-carousel-shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
  }

  .ig-carousel__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 2.25rem;
    height: 2.25rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border, #dee2e6);
    border-radius: 50%;
    background: #fff;
    color: var(--color-primary, #03a87c);
    cursor: pointer;
    box-shadow: var(--shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.08));
    transition: background 0.2s ease, color 0.2s ease;
  }

  .ig-carousel__nav:hover {
    background: var(--color-primary, #03a87c);
    color: #fff;
  }

  .ig-carousel__nav--prev { left: -0.75rem; }
  .ig-carousel__nav--next { right: -0.75rem; }

  @media (max-width: 575px) {
    .ig-carousel__nav { display: none; }
  }

  .ig-carousel__status {
    margin: 0.75rem 0 0;
    font-size: 0.875rem;
    color: var(--color-muted, #6c757d);
    text-align: center;
  }

  .ig-carousel__status a {
    color: var(--color-primary, #03a87c);
    font-weight: 600;
  }

  .ig-carousel--error .ig-carousel__track {
    justify-content: center;
    min-height: 8rem;
    align-items: center;
  }
</style>

<script>
(function () {
  function escapeHtml(str) {
    if (!str) return '';
    var div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
  }

  function getSizeUrl(size) {
    if (!size) return '';
    if (typeof size === 'string') return size;
    return size.mediaUrl || size.url || '';
  }

  function getImageUrl(post) {
    if (post.sizes) {
      return getSizeUrl(post.sizes.medium) ||
        getSizeUrl(post.sizes.small) ||
        getSizeUrl(post.sizes.large) ||
        getSizeUrl(post.sizes.full) ||
        post.thumbnailUrl ||
        '';
    }
    return post.thumbnailUrl || post.mediaUrl || '';
  }

  function getBadgeIcon(mediaType) {
    if (mediaType === 'VIDEO') return 'fa-play';
    if (mediaType === 'CAROUSEL_ALBUM') return 'fa-images';
    return '';
  }

  function truncate(text, max) {
    if (!text) return '';
    return text.length > max ? text.slice(0, max).trim() + '…' : text;
  }

  function initCarousel(section) {
    var feedUrl = (section.dataset.feedUrl || '').trim();
    var profileUrl = section.dataset.profileUrl || 'https://instagram.com/' + section.dataset.username;
    var limit = parseInt(section.dataset.limit, 10) || 12;
    var track = section.querySelector('.ig-carousel__track');
    var status = section.querySelector('.ig-carousel__status');
    var prevBtn = section.querySelector('.ig-carousel__nav--prev');
    var nextBtn = section.querySelector('.ig-carousel__nav--next');

    function showStatus(message) {
      status.innerHTML = message;
      status.hidden = false;
    }

    function updateNav() {
      if (!track || track.scrollWidth <= track.clientWidth + 1) {
        prevBtn.hidden = true;
        nextBtn.hidden = true;
        return;
      }
      prevBtn.hidden = track.scrollLeft <= 1;
      nextBtn.hidden = track.scrollLeft >= track.scrollWidth - track.clientWidth - 1;
    }

    function scrollByPage(direction) {
      var item = track.querySelector('.ig-carousel__item');
      var gap = 12;
      var amount = item ? item.offsetWidth + gap : track.clientWidth * 0.8;
      track.scrollBy({ left: direction * amount, behavior: 'smooth' });
    }

    prevBtn.addEventListener('click', function () { scrollByPage(-1); });
    nextBtn.addEventListener('click', function () { scrollByPage(1); });
    track.addEventListener('scroll', updateNav, { passive: true });
    window.addEventListener('resize', updateNav);

    if (!feedUrl) {
      section.classList.add('ig-carousel--error');
      track.innerHTML = '';
      showStatus('Instagram akışı henüz yapılandırılmadı. <a href="' + escapeHtml(profileUrl) + '" target="_blank" rel="noopener noreferrer">@' + escapeHtml(section.dataset.username) + '</a> hesabını ziyaret edebilirsiniz.');
      return;
    }

    fetch(feedUrl)
      .then(function (res) {
        if (!res.ok) throw new Error('Feed yüklenemedi');
        return res.json();
      })
      .then(function (data) {
        var posts = (data.posts || []).slice(0, limit);
        if (!posts.length) throw new Error('Gönderi bulunamadı');

        track.innerHTML = posts.map(function (post) {
          var imgUrl = getImageUrl(post);
          var permalink = post.permalink || profileUrl;
          var caption = truncate(post.prunedCaption || post.caption || '', 100);
          var badge = getBadgeIcon(post.mediaType);
          var badgeHtml = badge
            ? '<span class="ig-carousel__badge"><i class="fas ' + badge + '" aria-hidden="true"></i></span>'
            : '';
          var captionHtml = caption
            ? '<span class="ig-carousel__caption">' + escapeHtml(caption) + '</span>'
            : '';

          return '<div class="ig-carousel__item" role="listitem">' +
            '<a class="ig-carousel__card" href="' + escapeHtml(permalink) + '" target="_blank" rel="noopener noreferrer" aria-label="Instagram gönderisi">' +
            '<img src="' + escapeHtml(imgUrl) + '" alt="' + escapeHtml(caption || 'Instagram gönderisi') + '" loading="lazy">' +
            badgeHtml + captionHtml +
            '</a></div>';
        }).join('');

        prevBtn.hidden = false;
        nextBtn.hidden = false;
        updateNav();
      })
      .catch(function () {
        section.classList.add('ig-carousel--error');
        track.innerHTML = '';
        showStatus('Gönderiler yüklenemedi. <a href="' + escapeHtml(profileUrl) + '" target="_blank" rel="noopener noreferrer">@' + escapeHtml(section.dataset.username) + '</a> hesabını Instagram\'da ziyaret edin.');
      });
  }

  function boot() {
    document.querySelectorAll('.ig-carousel').forEach(function (section) {
      if ('IntersectionObserver' in window) {
        var observer = new IntersectionObserver(function (entries, obs) {
          entries.forEach(function (entry) {
            if (!entry.isIntersecting) return;
            initCarousel(entry.target);
            obs.unobserve(entry.target);
          });
        }, { rootMargin: '200px 0px' });
        observer.observe(section);
      } else {
        initCarousel(section);
      }
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', boot);
  } else {
    boot();
  }
})();
</script>

Şu an index.html içinde slider ile kitap listesi arasında include edilir. Görsel tasarım design.md — Instagram Carousel bölümünde tanımlıdır.


Site alt bilgisi _includes/footer-menu.html ile 3 sütunlu olarak render edilir. default.html layout’u bu partial’ı otomatik include eder.

Sütun İçerik Kaynak
Ürünler Eğitim Kitapları, Hikaye Kitapları, Kataloglar Sabit /urunler linkleri + site.pages (permalink: /kataloglar)
Önemli Bilgiler Hakkımızda, Gizlilik vb. _pages front matter: footer_show: true, footer_order
Adres & İletişim Telefon, e-posta, adres, sosyal _config.yml contact_* + menu-socialmedia.html

Site geneli iletişim (_config.yml)

İletişim bilgileri kök seviyede tanımlanır (footer bloğu değil; sosyal ayarlar gibi site ayarı):

contact_phone: "+90 212 514 28 28"
contact_phone_href: "tel:+902125142828"
contact_email: "[email protected]"
contact_address: "Alemdar Mh. Prof. Kazım İsmail Gürkan Cad. No:8 Fatih, 34110 İstanbul"

Jekyll’da erişim: +90 212 514 28 28, [email protected] vb.

contact-info.html partial

Tek kaynak; footer ve iletişim sayfası buradan beslenir:




<div class="contact-info contact-info--footer">
  
  <p class="contact-info__item">
    <strong>Telefon:</strong>
    <a href="tel:+902125142828">+90 212 514 28 28</a>
  </p>
  
  
  <p class="contact-info__item">
    <strong>E-posta:</strong>
    <a href="mailto:[email protected]">[email protected]</a>
  </p>
  
  
  <p class="contact-info__item">
    <strong>Adres:</strong>
    <span>Alemdar Mh. Prof. Kazım İsmail Gürkan Cad. No:8 Fatih, 34110 İstanbul</span>
  </p>
  
</div>

  {# footer sütunu #}



<div class="contact-info contact-info--page">
  
  <h5>Adres</h5>
  <p class="contact-info__item">Alemdar Mh. Prof. Kazım İsmail Gürkan Cad. No:8 Fatih, 34110 İstanbul</p>
  
  
  <h5>Telefon</h5>
  <p class="contact-info__item">
    <a href="tel:+902125142828">+90 212 514 28 28</a>
  </p>
  
  
  <h5>E-posta</h5>
  <p class="contact-info__item">
    <a href="mailto:[email protected]">[email protected]</a>
  </p>
  
</div>

   {# iletişim sayfası gövdesi #}

_pages/iletisim.md harita iframe’ini sayfa gövdesinde tutar; telefon/e-posta/adres tekrarı partial üzerinden gelir.

Footer’da gösterilecek sayfalar

_pages front matter:

footer_show: true
footer_order: 10

Footer düzeni ve stiller design.md — Site Footer bölümünde tanımlıdır.


Ürün (Kitap) Front Matter Örneği

---
layout: book
title: "Deyim Öyküleri 5 Kitap"
grades: [3]
genre: story          # education | story
tags: []
anatema: [Saygı, Empati]
kazanim: [H.1.2.1, H.3.3.3]
beceriler: [Okuma Becerisi, "Problem Çözme Becerisi"]
unite: ["Oyun Dünyası"]
categories: ["Çocuk", "Hikaye"]
ean: 9786053832874
preview_link: "https://cdn.e-damla.com.tr/PUBLIC/ornek-sayfalar/9786053832874/index.html"
examlink: ""   # HDS yoksa boş; doluysa tam PDF URL (ör. https://cdn.e-damla.com.tr/PUBLIC/hds_pdf/y/...)
damlaurl: ""   # Damla Yayınevi ürün sayfası; doluysa Satın Al, boşsa Bilgi → tedarik popup
languages: ["Türkçe"]
page: Her Biri 64
size: "14x20cm"
publish-number: 1443
cover: "Karton Kapak"
---

Markdown gövdesi ürün açıklaması olarak book.html içindeki .prose--display alanında render edilir. **TEMALAR:** gibi kalın başlıklar ve madde listeleri bu alanda stillenir.

Ön izleme URL’si kitap front matter’ındaki preview_link alanında tutulur. preview_link doluysa İncele butonu ve previewbook iframe görünür; boşsa görünmez. Eksik kitaplarda varsayılan desen: https://cdn.e-damla.com.tr/PUBLIC/ornek-sayfalar/{ean}/index.html; bazı kitaplarda özel path’ler (damlaegitim/, /mobile/ vb.) korunur. /urun-inceleme-linkleri sayfası preview_link dolu tüm kitapları listeler.

HDS PDF linkleri kitap front matter’ındaki tam examlink URL’si ile tanımlanır. Site genelinde _config.yml içinde examlink: true olmalıdır; aksi halde HDS butonu hiçbir kitapta görünmez. examlink: true iken book.html içinde kitap examlink doluysa HDS butonu görünür, boş veya yoksa görünmez.

Durum _config.yml Front matter Kitap sayfası
Site kapalı examlink: false (herhangi) HDS butonu görünmez
HDS yok examlink: true examlink: "" HDS butonu görünmez
HDS var examlink: true Tam CDN URL HDS butonu görünür (popup iframe)

Örnek URL: https://cdn.e-damla.com.tr/PUBLIC/hds_pdf/y/deyim-oykuleri-y.pdf

scripts/normalize_book_frontmatter.rb tüm kitaplarda examlink satırını korur; eksikse examlink: "" yazar, göreli dosya adı verilmişse tam URL’ye çevirir.

damlaurl (Satın Al / Bilgi)

Durum Front matter Kitap sayfası
URL yok damlaurl: "" Bilgi → tedarik bilgisi popup
URL var Tam ürün sayfası URL Satın Al → iframe popup

Örnek URL: https://www.damlayayinevi.com.tr/...

scripts/normalize_book_frontmatter.rb tüm kitaplarda damlaurl satırını korur; eksikse damlaurl: "" yazar. Eski damlayayinevi front matter alanı varsa damlaurl’a taşınır. Site genelinde buyout veya damlayayinevi config ayarı yoktur.

Araç Görev
scripts/normalize_book_frontmatter.rb Front matter sıralar; preview_link, examlink, damlaurl Standart Book Attributes altında korunur; review_linkpreview_link, previewpage silinir

Front matter anahtar sırası (betik): layout, title, description, categoriesean, … → # Spesific Filterable Attributes: genre, grades, tags, degerler, anatema, egilimler, kazanim, beceriler, unite → diğer alanlar.

Yeni kitap eklerken preview_link doğrudan front matter’a yazılır; özel path gerekmezse https://cdn.e-damla.com.tr/PUBLIC/ornek-sayfalar/{ean}/index.html varsayılan desenidir.


Ürün İnceleme Linkleri Sayfası

Özellik Değer
Dosya _pages/linkler.html
URL /urun-inceleme-linkleri
Layout page (show_title: false)
Veri site.bookspreview_link dolu kitaplar (sort: title)
Stil / JS Sayfa içi inline <style> + <script> (include yok)
Footer footer_show: true, footer_order: 15

Kart düzeni: solda kapak (eanimage.html), sağda sınıf rozeti + başlık + dört aksiyon — Kitabı incele (preview_link), Kitaba git (ürün URL), Whatsappda paylaş, Linki kopyala. Arama kutusu başlık ve sınıf rozeti üzerinde client-side filtre uygular (toLocaleLowerCase('tr-TR')).

Görsel tasarım design.md — Ürün İnceleme Linkleri bölümünde tanımlıdır.


Filtreleme Mantığı

Anasayfa ve /urunler aynı bileşenleri paylaşır: book-grade-nav.html + book-home-groups.html + book-filter.js

  • Sınıf seçimi → data-grades attribute’u ile eşleşme
  • Tür (Eğitim/Hikaye) → data-genre attribute’u
  • Hedef selector: .listbooks-home-item
  • Genre grupları: .book-genre-group (boş gruplar gizlenir)

Filtreleme tamamen client-side çalışır; Jekyll rebuild gerekmez.

Hash tabanlı paylaşılabilir URL’ler

Filtre değişince URL hash güncellenir; sayfa hash ile yüklendiğinde filtre otomatik uygulanır.

Hash Filtre
#okul-oncesi Sadece sınıf (tüm türler)
#okul-oncesi/egitim Sınıf + tür
#1-sinif/hikaye Sınıf + tür

Örnekler: /urunler#okul-oncesi, /urunler#1-sinif/egitim, /#2-sinif/hikaye (anasayfa)

Okul öncesi ürünler _books/ içinde henüz tam açılmamış olsa da URL yapısı hazırdır; ileride grades: [0] + genre ile eklenen ürünler ek JS değişikliği olmadan çalışır.

book-grade-filter.html eski checkbox dropdown yapısıdır; /urunler artık kullanmaz.


Jekyll Plugin’leri

Gemfile yalnızca github-pages gem’ini kullanır; aşağıdaki plugin’ler bu paketten gelir (GitHub Pages whitelist):

  • jekyll-feed — RSS
  • jekyll-sitemap — sitemap.xml
  • jekyll-seo-tag — `
Damla Okul — Proje Mimarisi | Damla Okul

` meta etiketleri (kitap sayfaları hariç; özel book-seo-tags.html)

  • jekyll-paginate — sayfalama
  • jekyll-gist — Gist embed

Kaldırılan (whitelist dışı, projede kullanılmıyordu): jekyll-archives, jekyll-figure. Özel CSS assets/css/theme.css üzerinden; _sass / sass-embedded yok.


SEO ve AI Crawler Altyapısı

GitHub Pages uyumlu (özel Ruby plugin yok). Webmaster doğrulama DNS seviyesinde yapılır; kod tabanında meta tag gerekmez.

Dosya haritası

Dosya / URL Görev
robots.txt Tüm crawler + AI botlara Allow; sitemap; llms hub referansı; OAI-SearchBot, Claude-SearchBot
_pages/llms.txt LLM hub indeksi: özet, bölüm linkleri, öne çıkanlar, iletişim
_pages/llms/*.txt Modüler bölümler — tüm kitaplar genre dosyalarında korunur
_includes/llms-book-line.html Tek kitap markdown satırı (DRY)
_includes/llms-contact.html llms dosyalarında ortak iletişim bloğu
_pages/sss.md /sss — görünür SSS + front matter faq:
_includes/faq-list.html SSS accordion / <details> render
_includes/structured-data-faq.html FAQPage JSON-LD (page.faq kaynağı)
_includes/book-seo-description.html Kitap meta description metni üretimi
_includes/book-seo-tags.html Kitap <meta> / Open Graph / Twitter (twitter:description)
_includes/book-minimal-content.html İnce gövdeli kitaplara görünür SEO paragrafı
_includes/related-books.html İlgili ürünler: aynı grades+genre
_includes/structured-data-book.html Product + Book + BreadcrumbList JSON-LD
_includes/structured-data-site.html Organization JSON-LD; WebSite + SearchAction (/?q=)
_includes/search-lunr.html Spotlight arama; site geneli ?q= init ve URL senkronu
_includes/ai-seo-crawler.html Router + visually-hidden wrapper
_includes/ai-seo-crawler-*.html Sayfa türüne göre LLM talimat + bağlam metni

llms.txt modüler hub

Tek dosyada tüm kitapları listelemek yerine hub + spoke yapısı:

/llms.txt                      → indeks (~50 satır)
/llms/ana-sayfalar.txt         → statik sayfalar
/llms/egitim-kitaplari.txt     → genre == education (tüm kitaplar)
/llms/hikaye-kitaplari.txt     → genre == story (tüm kitaplar)
/llms/kataloglar.txt           → kataloglar
/assets/search-index.json      → makine okunur tam indeks (Optional bölümde referans)

default.html head: <link rel="alternate" type="text/plain" href="/llms.txt">

Site geneli ?q= arama

Davranış Açıklama
initFromUrlQuery() Herhangi bir sayfada ?q=terim → Spotlight modal + sonuçlar
syncUrlQuery(term) Modal’da yazarken history.replaceState ile URL güncellenir (paylaşılabilir link)
closeSpotlight() ?q= silinir; hash korunur
Ctrl+K boş açılış URL değişmez; yazınca ?q= eklenir
SearchAction schema Kanonik: https://damlaokul.com/?q={search_term_string}

Dinamik AI/SEO crawler içeriği

  • Amaç: Öğretmen ve velilere Damla ürünlerini neden tercih etmeleri gerektiğini LLM’lere ve crawler’lara anlatan bağlamsal metin
  • Format (hibrit): data-ai-role="assistant-guidance" + data-ai-role="context-narrative" + data-ai-summary (1 cümle özet)
  • Sayfa türü algılama: URL + layout (book/previewbook, /, /urunler, /kataloglar, generic)
  • Gizleme: .ai-seo-crawler visually-hidden, aria-hidden="true", data-nosnippet
  • Include zinciri: _layouts/default.html → `

` (</main> sonrası)

  • book-minimal-content.html ile ilişki: Görünür SEO metni vs. gizli LLM rehberi — farklı amaç

previewbook layout

previewbook.html (~40 eğitim seti / tatil kitabı): tam ekran preview_link iframe odaklı.

  • Gizli d-none üst hero ve yan sütun kaldırılır (ölü kod)
  • book-minimal-content.html her zaman render edilir
  • iframe title=" — ön okuma"
  • structured-data-book.html aynı kalır

Meta description otomasyonu

Kitap sayfalarında (/urunler/*) description: front matter yoksa:

  1. Markdown excerpt / gövdeden (max 155 karakter)
  2. Yoksa şablon: ` — . sınıf kitabı. Damla Okul.`

Öncelikli kitaplara elle description: eklenebilir (SERP farklılaşması).

Schema notları

  • Kitap dışı sayfalarda çift WebSite JSON-LD olmamalı (structured-data-site.html vs jekyll-seo-tag)
  • SSS: page.faq → görünür HTML + structured-data-faq.html (tek kaynak)
  • Anasayfada çift <title> olmamalı (default.html + `
Damla Okul — Proje Mimarisi | Damla Okul

`)

Yapılandırma (_config.yml)

  • locale: tr_TR, lang: tr, twitter.username
  • Kitap koleksiyonu: lang: tr, type: product, sitemap önceliği

Build sonrası doğrulama

bundle exec jekyll build

# Temel çıktılar
test -f _site/llms.txt
test -f _site/llms/egitim-kitaplari.txt
test -f _site/llms/hikaye-kitaplari.txt
grep -c "WebSite" _site/index.html          # çift schema olmamalı (1 veya jekyll-seo-tag only)
grep "twitter:description" _site/urunler/din-kulturu-ve-ahlak-bilgisi-4.html
curl -s _site/robots.txt | grep OAI-SearchBot

# llms kitap sayısı korunmuş mu
grep -c "^\-" _site/llms/egitim-kitaplari.txt
grep -c "^\-" _site/llms/hikaye-kitaplari.txt

# AI crawler bloğu
grep -l "ai-seo-crawler" _site/index.html _site/urunler.html | head -3

# Canlı önizleme (manuel)
# bundle exec jekyll serve → /?q=damla, /hakkimizda?q=matematik, ESC ile ?q= temizlenmeli

Build ve Deploy

İlk kurulum (install.sh)

Git clone sonrası Windows Git Bash / macOS / Linux:

sh install.sh

Sıra: Ruby/Bundler kontrolü → bundle install → Python fonttoolssubset_font.shcheck_fonts.shinstall_image_tools.sh (Windows: winget install ImageMagick.ImageMagick, gerekirse Google.Libwebp) → _data/webp_manifest.ymljekyll build.

Hook yok — görsel kontrol ve webp üretimi burada çalışmaz.

Yerel geliştirme (start.sh)

# Geliştirme sunucusu (hook'lar: görsel kontrol + webp üretimi)
sh start.sh            # check_images.sh + generate_webp.sh + sync_site_data.py + jekyll serve
# → http://localhost:4000

start.sh önce refresh_image_paths.sh ile Windows PATH’ini düzeltir; ardından check_images.sh, generate_webp.sh ve sync_site_data.py hook’larını çalıştırır.

Canlıya alma

bundle exec jekyll build   # isteğe bağlı yerel doğrulama
git add .
git commit -m "..."
git push

GitHub Pages, push sonrası kaynak branch’ten Jekyll build alır. CI/CD veya npm build yoktur. CSS değişiklikleri doğrudan assets/css/ altında yapılır ve commit edilir.


Yeni Ürün Ekleme

  1. _books/yeni-urun.md oluştur
  2. Front matter doldur (title, grades, genre, ean, preview_link, examlink, damlaurl…)
  3. preview_link — ön izleme URL’si; boş bırakılabilir (preview_link: "" → İncele butonu görünmez)
  4. examlink — HDS PDF tam URL’si; yoksa examlink: "" (HDS butonu görünmez)
  5. damlaurl — Damla Yayınevi ürün sayfası tam URL’si; doluysa Satın Al butonu, yoksa Bilgi → tedarik popup
  6. İsteğe bağlı description: — yoksa build sırasında otomatik üretilir
  7. Kapak görselini assets/images/ean/{ean}.jpg olarak ekle (jpg/png optimize et; webp sh start.sh ile otomatik)
  8. sh scripts/check_images.sh ile boyut kontrolü (veya sh start.sh)
  9. sh start.sh ile kontrol et
  10. git push

Yeni Bileşen / Stil Ekleme

  1. Tasarım kuralları ve dosya seçimi → design.md — Yeni Stil Ekleme Rehberi
  2. Partial gerekiyorsa → _includes/ altına ekle
  3. HTML’de Bootstrap grid class’ları (row, col-*) kullanılabilir

Harici Bağımlılıklar

Kaynak Kullanım
cdn.e-damla.com.tr Kitap preview_link ön izleme sayfaları; examlink HDS PDF’leri
feeds.behold.so Instagram carousel JSON feed
script.google.com Öğretmen talep formu Apps Script web app (ogretmen_submit_url)
google.com/recaptcha Form gönderiminde bot koruması (v2)
Google Analytics G-PR1C1WGQB6 (site.google_analytics; yalnızca production)
Cloudflare DNS (şu an proxy kapalı — gri bulut)
TurkiyeAPI / MEB Adres ve okul JSON üretimi — getdata.md

Font Awesome artık yerel olarak assets/fonts/fontawesome/ altından servis edilir; harici CDN kullanılmaz.


Bilinen Legacy Dosyalar

Aşağıdaki dosyalar geçmişten kalma olabilir; aktif kullanılmıyorsa temizlenebilir:

  • assets/js/theme.js (jQuery tabanlı; loadSearch() kullanılmıyor)
  • assets/css/buyout.css (eski satıcı barı; hiçbir layout’ta yüklenmez)
  • _pages/_draft/books.html