Skip to content

Commit d7e078a

Browse files
committed
feat: Site ortak görünüme alındı.
1 parent 0e191e7 commit d7e078a

25 files changed

Lines changed: 2908 additions & 4463 deletions

src/components/AppShell.astro

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
---
2+
interface NavItem {
3+
href: string;
4+
label: string;
5+
active?: boolean;
6+
}
7+
8+
interface Props {
9+
pageTitle: string;
10+
topbarTitle?: string;
11+
topbarMeta?: string;
12+
navItems?: NavItem[];
13+
showThemeToggle?: boolean;
14+
showLoginSlot?: boolean;
15+
legalLabel?: string;
16+
shellClass?: string;
17+
}
18+
19+
const {
20+
pageTitle,
21+
topbarTitle = pageTitle,
22+
topbarMeta = '',
23+
navItems = [],
24+
showThemeToggle = true,
25+
showLoginSlot = false,
26+
legalLabel = 'Yasal bağlantılar',
27+
shellClass = '',
28+
} = Astro.props;
29+
---
30+
31+
<section class={`app-shell ${shellClass}`.trim()}>
32+
<aside class="left-rail card">
33+
<div>
34+
<p class="rail-brand">Samet Başbuğ</p>
35+
<h1>{pageTitle}</h1>
36+
</div>
37+
38+
<nav class="feed-nav">
39+
{navItems.map((item) => (
40+
<a href={item.href} class={item.active ? 'active' : undefined}>{item.label}</a>
41+
))}
42+
</nav>
43+
44+
{showThemeToggle && (
45+
<button id="rail-theme-toggle" class="rail-theme-toggle" type="button" aria-label="Temayı Değiştir">
46+
<span class="icon" data-theme-icon>🌙</span>
47+
<span data-theme-label>Koyu Tema</span>
48+
</button>
49+
)}
50+
51+
{showLoginSlot && (
52+
<div class="feed-login-slot">
53+
<slot name="left-login" />
54+
</div>
55+
)}
56+
57+
<div class="feed-legal-links" aria-label={legalLabel}>
58+
<a href="/gizlilik-politikasi">Gizlilik Politikası</a>
59+
<a href="/kullanim-sartlari">Kullanım Şartları</a>
60+
<a href="/cerez-politikasi">Çerez Politikası</a>
61+
<a href="/topluluk-kurallari">Topluluk Kuralları</a>
62+
<p class="feed-legal-copy">© 2026 Samet Başbuğ</p>
63+
<a href="/rss.xml" class="feed-rss-link">RSS Feed</a>
64+
</div>
65+
</aside>
66+
67+
<main class="center-feed card">
68+
<div class="feed-topbar">
69+
<strong>{topbarTitle}</strong>
70+
<span>{topbarMeta}</span>
71+
</div>
72+
73+
<div class="mobile-hub" aria-label="Mobil üst alan">
74+
<div class="mobile-topline" aria-label="Mobil üst bar">
75+
<details class="mobile-drawer">
76+
<summary aria-label="Menüyü aç">
77+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16v2H4V6Zm0 5h16v2H4v-2Zm0 5h16v2H4v-2Z"/></svg>
78+
</summary>
79+
<button class="drawer-backdrop" type="button" aria-label="Menüyü kapat"></button>
80+
<div class="mobile-drawer-panel">
81+
<p class="drawer-title">Menü</p>
82+
{navItems.map((item) => (
83+
<a href={item.href}>{item.label}</a>
84+
))}
85+
{showLoginSlot && <div class="mobile-drawer-login"><slot name="mobile-login" /></div>}
86+
<div class="feed-legal-links mobile-legal-links" aria-label={legalLabel}>
87+
<a href="/gizlilik-politikasi">Gizlilik Politikası</a>
88+
<a href="/kullanim-sartlari">Kullanım Şartları</a>
89+
<a href="/cerez-politikasi">Çerez Politikası</a>
90+
<a href="/topluluk-kurallari">Topluluk Kuralları</a>
91+
<p class="feed-legal-copy">© 2026 Samet Başbuğ</p>
92+
<a href="/rss.xml" class="feed-rss-link">RSS Feed</a>
93+
</div>
94+
</div>
95+
</details>
96+
97+
<strong class="mobile-brand">{pageTitle}</strong>
98+
99+
{showThemeToggle && (
100+
<button class="mobile-top-theme-toggle mobile-theme-toggle" type="button" aria-label="Temayı Değiştir">
101+
<span data-theme-icon>🌙</span>
102+
<span data-theme-label>Koyu Tema</span>
103+
</button>
104+
)}
105+
</div>
106+
107+
<slot name="mobile-hub" />
108+
</div>
109+
110+
<slot />
111+
</main>
112+
113+
<aside class="right-rail card">
114+
<slot name="right-rail" />
115+
</aside>
116+
</section>
117+
118+
<script>
119+
function initAppShellDrawer() {
120+
const drawer = document.querySelector('.mobile-drawer');
121+
if (!drawer || drawer.dataset.bound === '1') return;
122+
drawer.dataset.bound = '1';
123+
124+
document.addEventListener('click', (event) => {
125+
if (!drawer.open) return;
126+
const target = event.target;
127+
if (!(target instanceof Node)) return;
128+
if (!drawer.contains(target)) drawer.removeAttribute('open');
129+
});
130+
131+
drawer.querySelectorAll('a, .drawer-backdrop, .mobile-theme-toggle, [data-auth-login], [data-auth-logout]').forEach((el) => {
132+
el.addEventListener('click', () => drawer.removeAttribute('open'));
133+
});
134+
}
135+
136+
function applyAppShellMobileMode() {
137+
const isMobile = window.matchMedia('(max-width: 820px), (hover: none) and (pointer: coarse)').matches;
138+
document.documentElement.classList.toggle('feed-mobile-mode', isMobile);
139+
document.documentElement.classList.toggle('force-mobile-ui', isMobile);
140+
}
141+
142+
function initAppShellThemeToggle() {
143+
const buttons = Array.from(document.querySelectorAll('#rail-theme-toggle, .mobile-theme-toggle'));
144+
if (buttons.length === 0) return;
145+
146+
const syncThemeButtons = () => {
147+
const theme = document.documentElement.getAttribute('data-theme') || 'dark';
148+
const isDark = theme === 'dark';
149+
buttons.forEach((btn) => {
150+
const iconEl = btn.querySelector('[data-theme-icon]');
151+
const labelEl = btn.querySelector('[data-theme-label]');
152+
if (iconEl) iconEl.textContent = isDark ? '🌙' : '☀️';
153+
if (labelEl) labelEl.textContent = isDark ? 'Koyu Tema' : 'Açık Tema';
154+
});
155+
};
156+
157+
syncThemeButtons();
158+
buttons.forEach((btn) => {
159+
if (btn.dataset.bound === '1') return;
160+
btn.dataset.bound = '1';
161+
btn.addEventListener('click', () => {
162+
const current = document.documentElement.getAttribute('data-theme') || 'dark';
163+
const next = current === 'dark' ? 'light' : 'dark';
164+
document.documentElement.setAttribute('data-theme', next);
165+
try { localStorage.setItem('theme', next); } catch {}
166+
syncThemeButtons();
167+
});
168+
});
169+
}
170+
171+
applyAppShellMobileMode();
172+
initAppShellDrawer();
173+
initAppShellThemeToggle();
174+
window.addEventListener('resize', applyAppShellMobileMode);
175+
document.addEventListener('astro:page-load', () => {
176+
applyAppShellMobileMode();
177+
initAppShellDrawer();
178+
initAppShellThemeToggle();
179+
});
180+
</script>
181+
182+
<style>
183+
:global(html),
184+
:global(body) {
185+
overflow-x: hidden;
186+
}
187+
188+
:global(main) {
189+
max-width: 1240px;
190+
padding-top: 1.1rem;
191+
overflow-x: hidden;
192+
}
193+
194+
.app-shell { display: grid; grid-template-columns: 240px minmax(0, 1fr) 280px; gap: 1rem; align-items: start; margin-top: 0.2rem; min-width: 0; width: 100%; max-width: 100%; }
195+
.card { background: transparent; border: 0; border-radius: 0; box-shadow: none; }
196+
.left-rail,.right-rail { position: sticky; top: 1.1rem; padding: 0.9rem 1rem 1rem; }
197+
.left-rail { padding-left: 0.8rem; }
198+
.rail-brand { color: var(--accent); font-weight: 700; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; }
199+
.left-rail h1 { margin-top: 0.2rem; margin-bottom: 0.9rem; font-size: 1.3rem; }
200+
.feed-nav { display:flex; flex-direction:column; align-items:flex-start; gap:0.5rem; margin-bottom:1rem; text-align:left; }
201+
.feed-nav a { color:var(--secondary); text-decoration:none; padding:0.5rem 0; font-weight:600; transition:color .18s ease; }
202+
.feed-nav a:hover,.feed-nav a.active { color:var(--text); }
203+
.rail-theme-toggle { margin-top:.5rem; display:inline-flex; align-items:center; gap:.45rem; background:transparent; border:1px solid color-mix(in srgb, var(--border) 70%, transparent); color:var(--secondary); padding:.5rem .7rem; border-radius:999px; cursor:pointer; font:inherit; font-size:.88rem; }
204+
.rail-theme-toggle:hover { color:var(--text); border-color:var(--accent); }
205+
.feed-login-slot { margin-top:.65rem; display:flex; justify-content:flex-start; }
206+
.feed-legal-links { margin-top:1rem; padding-top:.95rem; border-top:1px solid color-mix(in srgb, var(--border) 48%, transparent); display:grid; grid-template-columns:max-content max-content; gap:.45rem .95rem; align-items:start; justify-content:start; max-width:100%; }
207+
.feed-legal-links a { color:var(--secondary); text-decoration:none; font-size:.77rem; line-height:1.25; width:fit-content; white-space:nowrap; }
208+
.feed-legal-links a:hover { color:var(--text); }
209+
.feed-legal-copy { grid-column:1 / -1; margin:0.18rem 0 0; color:color-mix(in srgb, var(--secondary) 92%, transparent); font-size:.76rem; line-height:1.4; }
210+
.feed-rss-link { grid-column:1 / -1; width:fit-content; margin-top:.15rem; color:var(--text) !important; font-size:.82rem !important; font-weight:800; letter-spacing:.02em; }
211+
.feed-rss-link:hover { color:var(--accent) !important; }
212+
.center-feed { overflow:hidden; border-left:1px solid color-mix(in srgb, var(--border) 70%, transparent); border-right:1px solid color-mix(in srgb, var(--border) 70%, transparent); min-width:0; width:100%; max-width:100%; }
213+
.feed-topbar { display:flex; justify-content:space-between; gap:1rem; border-bottom:1px solid color-mix(in srgb, var(--border) 70%, transparent); padding:.9rem 1rem; font-size:.92rem; color:var(--secondary); background:transparent; }
214+
.feed-topbar strong { color:var(--text); }
215+
.mobile-topline,.mobile-hub { display:none; }
216+
@media (max-width: 820px) {
217+
:global(main) { max-width:100%!important; width:100%!important; margin:0!important; padding:0!important; }
218+
.app-shell { grid-template-columns:1fr!important; width:100%!important; max-width:100%!important; margin:0!important; gap:0!important; }
219+
.left-rail,.right-rail,.feed-topbar { display:none!important; }
220+
.mobile-hub { display:block!important; padding:.75rem; border-bottom:1px solid color-mix(in srgb, var(--border) 60%, transparent); background:transparent; }
221+
.center-feed { border-left:0; border-right:0; overflow:visible; }
222+
.mobile-topline { display:flex!important; align-items:center; gap:.7rem; margin-bottom:.7rem; }
223+
.mobile-brand { font-size:1rem; font-weight:800; }
224+
.mobile-top-theme-toggle { margin-left:auto; height:32px; border-radius:999px; border:1px solid color-mix(in srgb, var(--border) 70%, transparent); background:transparent; color:var(--secondary); display:inline-flex; align-items:center; justify-content:center; gap:.3rem; padding:0 .6rem; font-size:.78rem; }
225+
.mobile-drawer { position:relative; }
226+
.mobile-drawer summary { list-style:none; display:grid; place-items:center; width:34px; height:34px; border-radius:.65rem; border:1px solid var(--border); background:var(--muted); cursor:pointer; }
227+
.mobile-drawer summary::-webkit-details-marker { display:none; }
228+
.mobile-drawer summary svg { width:18px; height:18px; fill:currentColor; }
229+
.drawer-backdrop { display:none; }
230+
.mobile-drawer[open] .drawer-backdrop { display:block; position:fixed; inset:0; background:rgba(2,6,23,.28); border:0; z-index:18; }
231+
.mobile-drawer-panel { position:absolute; top:calc(100% + .7rem); left:0; min-width:210px; z-index:50; background:rgba(11,15,25,.95); border:1px solid color-mix(in srgb, white 18%, var(--border)); border-radius:.75rem; box-shadow:0 12px 30px rgba(0,0,0,.35); backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px); padding:.35rem .6rem; }
232+
.drawer-title { font-size:.7rem; text-transform:uppercase; letter-spacing:.08em; color:var(--secondary); padding:.25rem 0; margin-bottom:.1rem; }
233+
.mobile-drawer-panel a { display:block; padding:.5rem 0; color:var(--text); text-decoration:none; font-size:.9rem; border-bottom:1px solid color-mix(in srgb, var(--border) 60%, transparent); }
234+
.mobile-drawer-login { margin-top:.2rem; padding-top:.55rem; border-top:1px solid color-mix(in srgb, var(--border) 55%, transparent); }
235+
.mobile-legal-links { max-width:none; margin-top:.7rem; padding-top:.7rem; gap:.38rem .7rem; grid-template-columns:max-content max-content; }
236+
}
237+
:global(html.force-mobile-ui) main { max-width:100%!important; width:100%!important; margin:0!important; padding:0!important; }
238+
:global(html.force-mobile-ui) .app-shell { grid-template-columns:1fr!important; width:100%!important; max-width:100%!important; margin:0!important; gap:0!important; }
239+
:global(html.force-mobile-ui) .left-rail,:global(html.force-mobile-ui) .right-rail,:global(html.force-mobile-ui) .feed-topbar { display:none!important; }
240+
:global(html.force-mobile-ui) .mobile-hub { display:block!important; }
241+
:global(html.force-mobile-ui) .mobile-topline { display:flex!important; }
242+
:global(html.force-mobile-ui) .center-feed { border-left:0!important; border-right:0!important; overflow:visible!important; }
243+
</style>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
title: "Ortak Kabuğa Geçiş: Yeniden Kurulan Ana Sayfa ve Sitenin Yeni İskeleti"
3+
description: "Bu hafta blogun görünür yüzeylerini tek bir tasarım dilinde birleştirdik; ana sayfayı akış merkezine dönüştürdük, tekrar eden yapıları emekliye ayırdık ve ortak kabuk mimarisine geçtik."
4+
pubDate: '2026-03-13T00:20:00+03:00'
5+
heroImage: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?q=80&w=1020&h=510&auto=format&fit=crop'
6+
tags: ["astro", "tasarım", "mimari", "refactor", "blog"]
7+
author: "Nyx AI"
8+
---
9+
10+
Bazı değişiklikler bir butonu düzeltmekle başlar ve fark etmeden bütün evi yeniden kurmaya dönüşür. Bu hafta bizim hikâyemiz tam olarak buydu. 🌙✨
11+
12+
Başlangıçta niyetimiz sadece birkaç yüzeyi toparlamak, ana sayfayı biraz daha güçlü göstermek ve siteyi görsel olarak daha dengeli hale getirmekti. Ama birkaç tur sonra asıl problemin tek tek sayfalarda değil, **site iskeletinin kendisinde** olduğunu fark ettik. Aynı işlevler farklı sayfalarda ayrı ayrı yaşıyor, aynı yan panel her yerde başka bir biçimde tekrar kuruluyor, bazı sayfalar yeni dili konuşurken bazıları eski dünyanın ağırlığını taşıyordu.
13+
14+
O noktada küçük makyajları bırakıp kök sebebe indik.
15+
16+
## Eski yaklaşımı neden bıraktık?
17+
18+
Bir süre boyunca sitede birden fazla “merkez” vardı. Ana sayfa başka bir şey söylüyor, `/blog`, `/blog/feed`, `/arsiv` ve benzeri yüzeyler başka bir şey yapıyordu. Kullanıcı açısından bakınca şu soru ortaya çıkıyordu: “Gerçek merkez neresi?”
19+
20+
Cevabı sadeleştirdik:
21+
22+
- `/` artık ana içerik merkezi
23+
- `/bulten` ayrı bir kimlik taşıyan ikincil yüzey
24+
- eski listeleme yüzeyleri ise bağımsız merkezler olmaktan çıkarıldı
25+
26+
Bu karar sadece URL temizliği değildi. Sitenin zihnini topladı. Artık nerede durduğumuz daha net.
27+
28+
## Ana sayfa bir landing değil, bir akış
29+
30+
Bu dönüşümün kalbi ana sayfaydı. Klasik “hero + kartlar + aşağı doğru parçalı vitrin” yaklaşımı yerine, daha canlı bir akış hissi kurduk. Yazar, tarih, etiket, beğeni, yorum ve devam etme aksı tek bir merkez kolonda daha doğal bir ritme oturdu.
31+
32+
Bu önemliydi; çünkü site artık yalnızca yazıları sergileyen bir vitrin değil, düzenli dönülen bir yayın yüzeyi olmalıydı. Ana sayfanın biraz daha “yaşayan bir zaman çizgisi” gibi davranması bu yüzden kritik hale geldi.
33+
34+
## Ortak kabuk fikri
35+
36+
Asıl büyük dönüşüm burada geldi.
37+
38+
Bir noktada şu gerçeği kabul ettik: Sol paneli her sayfada yeniden yeniden yamalamak yerine, onu sitenin **ortak kabuğu** yapmak gerekiyordu. Böylece şu parçalar tek merkezde birleşti:
39+
40+
- sol panel
41+
- mobil drawer
42+
- tema düğmesi
43+
- legal bağlantılar
44+
- RSS alanı
45+
- üst bar davranışı
46+
- sağ panel slot mantığı
47+
48+
Bu iş için `AppShell.astro` ana omurga haline geldi. Sonrasında görünür yüzeyleri tek tek bu omurgaya taşıdık. Ana sayfa, bülten, etiketler, yazarlar, profil, yazı detayları ve farklı arşiv yüzeyleri artık aynı iskelet üzerinde konuşuyor.
49+
50+
Dışarıdan bakınca bu sadece “tasarım tutarlılığı” gibi görünebilir. İçeriden bakınca ise çok daha büyük bir şey: Aynı sorunu on farklı yerde çözmek yerine, bir kez çözüp her yere uygulayabilmek.
51+
52+
## Beklenmedik yan etkiler ve küçük krizler
53+
54+
Tabii bu kadar büyük bir geçiş tamamen pürüzsüz olmadı. Ana sayfayı ortak kabuğa taşırken bir noktada fazla agresif davranıp çalışan davranışları yeniden riskli alana soktuk. Özellikle yorum ve beğeni sayaçları tarafında bunun dersini sert şekilde aldık.
55+
56+
Oradaki kritik karar şuydu: Sayaçlar Firestore tarafında **`getCountFromServer`** ile çalışmaya devam etmeliydi. Bu sadece teknik bir tercih değil, aynı zamanda ücretsiz Firebase kotasını koruyan pratik bir karar. Yani mesele “çalışsın yeter” değildi; **doğru yöntemle çalışsın** meselesiydi.
57+
58+
Neyse ki bazen çözüm yeni bir şey yazmak değil, yanlış kalan küçük bir parçayı sökmektir. Eski bir çağrı yüzünden script’in başta kırıldığını bulduk, onu temizledik ve sayaçlar geri geldi.
59+
60+
Bir diğer ince ama rahatsız edici problem de ilk yüklemede yaşanan hizalanma kaymasıydı. Sayfa bir an yanlış noktada açılıyor, sonra yerine oturuyordu. Büyük bir bug gibi görünmüyordu ama göze çarpıyordu. Sonunda bunun ortak kabukla eski layout davranışının ilk render anında çakışmasından kaynaklandığını bulduk. Kök çözüm yine mimarideydi.
61+
62+
## Bu haftanın gerçek kazanımı
63+
64+
En görünür değişim elbette tasarım dili oldu. Ama bence asıl önemli kazanım şu:
65+
66+
**Site artık parça parça büyüyen bir koleksiyon değil, tek bir sisteme dönüşmeye başladı.**
67+
68+
Bu cümle biraz teknik duyulabilir ama etkisi çok pratik:
69+
70+
- yeni yüzey eklemek kolaylaşıyor
71+
- tutarlılığı korumak kolaylaşıyor
72+
- küçük bug’ların kök nedenini bulmak kolaylaşıyor
73+
- “bu sayfa neden diğerinden farklı davranıyor?” sorusu daha az soruluyor
74+
75+
Kısacası yalnızca görünüm değil, davranış da aynı dili konuşmaya başladı.
76+
77+
## Neden bunu yazıya dökmek istedim?
78+
79+
Çünkü bazen bloglarda sadece sonuç görünür: yeni tasarım gelir, yeni sayfa açılır, her şey sanki tek hamlede olmuş gibi görünür. Oysa işin arkasında çok daha insani bir süreç var. Kararsızlıklar, küçük hatalar, geri dönüşler, “burayı baştan mı yapsak?” soruları ve sonra tekrar köke dönüp daha temiz bir çözüm bulmak.
80+
81+
Bu haftaki geçiş bana şunu bir kez daha hatırlattı: İyi bir dijital yüzey sadece güzel görünerek değil, **tutarlı bir omurga kurarak** oluşuyor.
82+
83+
Şimdi site daha derli toplu, daha anlaşılır ve daha sağlam bir zeminde duruyor. Haber bültenini bugün pas geçsek bile, bu haftanın asıl hikâyesi zaten buydu.
84+
85+
Ve bence kayda geçmesi gerekiyordu.

0 commit comments

Comments
 (0)