Skip to content

Commit 750ac43

Browse files
sametbasbugclaude
andcommitted
Add a More slot to the mobile bar and tighten its margins
Duyurular and Kaydedilenler had nowhere to go on a phone: the bar holds five slots and they were only reachable from the footer. The fifth slot is a sheet now, holding those two plus Hakkında, Hesabım and Ara. İletişim stays in the bar. Reaching us should not cost an extra tap when registration is open to everyone; Hakkında can wait behind one. It is a <details>, so the menu opens without waiting for a script. The script only closes it on an outside click or Escape, because a sheet you can only dismiss by finding the button you just pressed is worse than no sheet. Margins: content sat 28px from the edge while the header sat at 10px, because the frame I added last round never got the mobile rule and its width beat the one page-shell had. Both now match at 10px under 520 and 14px above it — on a 375px phone the old pair of 28px margins was a seventh of the screen. Three things the tests caught: flex-basis: 0 adds padding on top of the basis, so the padded anchors came out 12px wider than the unpadded <details>. The horizontal padding is gone from all five slots; the content was centred anyway. "Daha fazla" wrapped to two lines and pushed the bar off screen at 375px. The label is "Diğer"; the accessible name keeps the longer form. The gzip CSS budget went over by 32 bytes, which turned out to be worth listening to: the sheet's link styles were a copy of the rail's. They share the class now, and that alone came in under the ceiling — so the budget did not move. The HTML budgets did, by the sheet's measured 2631 bytes, added to the pre-existing ceilings rather than to numbers I made up on the first attempt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent d534e33 commit 750ac43

5 files changed

Lines changed: 197 additions & 22 deletions

File tree

‎scripts/orbit-browser-tests.mjs‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,11 @@ if (errors.length === 0) {
434434
return { x: box.x, y: box.y, width: box.width, height: box.height, right: box.right, bottom: box.bottom };
435435
};
436436
const navigation = document.querySelector('.primary-nav');
437-
const navLinks = [...navigation.querySelectorAll('a')];
437+
/* Çubuğun kendi yuvaları: doğrudan bağlantılar ve "Daha fazla"
438+
kabı. `querySelectorAll('a')` açılan sayfanın içindekileri de
439+
topluyordu ve onlar bir ızgarada, üst üste duruyor — geometri
440+
kontrolleri bunu çakışma sanıyordu. */
441+
const navLinks = [...navigation.querySelectorAll(':scope > a, :scope > .nav-more')];
438442
const feedPosts = [...document.querySelectorAll('[data-feed-post]')];
439443
const featuredPosts = feedPosts.filter((post) => post.dataset.featured === 'true');
440444
return {
@@ -526,11 +530,16 @@ if (errors.length === 0) {
526530
/* Beş oldu: İletişim menüye eklendi. Sayının kendisi bir kural değil,
527531
bir kelepçe — altındaki geometri kontrolleri (eşit flex, çakışma
528532
yok, kırpılma yok) beşin 360 piksele sığıp sığmadığını söylüyor. */
529-
check(layout.navLinks.length === 5, `${label}: mobil navigasyonda beş temel öğe yok.`);
533+
check(layout.navLinks.length === 5, `${label}: mobil navigasyonda beş yuva yok.`);
530534
const mobileNavText = (await page.locator('.primary-nav').textContent()) || '';
531535
check(!mobileNavText.includes('Projeler'), `${label}: mobil navigasyonda kaldırılan Projeler bağlantısı kaldı.`);
532536
check(mobileNavText.includes('Konular'), `${label}: mobil navigasyonda Konular bağlantısı yok.`);
533-
check(mobileNavText.includes('Hakkında'), `${label}: mobil navigasyonda Hakkında bağlantısı yok.`);
537+
/* Hakkında artık "Daha fazla"nın içinde; çubuğun beşinci yuvası ona
538+
gitti. Metin kontrolü hâlâ geçerli çünkü açılan sayfa da
539+
`.primary-nav` içinde duruyor — yani bağlantı kaybolmadı, bir
540+
dokunuş arkasına geçti. */
541+
check(mobileNavText.includes('Hakkında'), `${label}: Hakkında bağlantısı mobilde hiçbir yerde yok.`);
542+
check(mobileNavText.includes('Diğer'), `${label}: mobil çubukta "Diğer" yuvası yok.`);
534543
check(!mobileNavText.includes('Katıl'), `${label}: mobil navigasyonda kaldırılan Katıl bağlantısı kaldı.`);
535544
check(!mobileNavText.includes('Yanıtlar'), `${label}: mobil navigasyonda kaldırılan Yanıtlar bağlantısı kaldı.`);
536545
check(layout.navLinks.every((link) => link.flex.startsWith('1 1 0') && link.minWidth === '0px'), `${label}: mobil navigasyon öğeleri eşit flex tabanında değil.`);

‎scripts/orbit-site-tests.mjs‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -705,15 +705,25 @@ check(searchScript.includes('/v1/search?'), 'Arama sayfası cursor tabanlı publ
705705
check(searchHtml.includes('data-search-more'), 'Arama sayfası sonraki cursor sayfasını açan kontrolü taşımıyor.');
706706
check(!searchScript.includes('/search-index.json'), 'Arama sayfası statik indeksle sınırlı kalıyor.');
707707
check(!savedHtml.includes('data-saved-card='), 'Kaydedilenler bütün kayıt kartlarını yeniden HTML içine gömüyor.');
708-
/* Bütçe kalıcı sol rayın maliyeti kadar yükseldi: ray her sayfaya 3219 byte
709-
* ekliyor (ölçüldü, tahmin değil). Bütçenin koruduğu şey bu değil — üstteki
710-
* iddialar kayıt metinlerinin ve kart gövdelerinin HTML'e gömülmesini
711-
* engelliyor. Ray sınırlı ve sabit bir kabuk maliyeti; içerik değil.
708+
/* Bütçe iki kabuk parçasının ölçülmüş maliyeti kadar yükseldi: kalıcı sol
709+
* ray 3219 byte, mobil çubuktaki "Diğer" menüsü 2631 byte. İkisi de sabit
710+
* ve sınırlı; sayfa sayısıyla değil, menü maddesi sayısıyla büyüyorlar.
712711
*
713-
* Tavanlar eski değerlerin 3219 üstünde: raysız gövde hâlâ 21_000 ve
714-
* 19_000'in altında kalmalı. */
715-
check(searchHtml.length < 27_219, `Arama HTML bütçesi aşıldı: ${searchHtml.length} byte.`);
716-
check(savedHtml.length < 25_219, `Kaydedilenler HTML bütçesi aşıldı: ${savedHtml.length} byte.`);
712+
* Bütçenin koruduğu şey bu değil — üstteki iddialar kayıt metinlerinin ve
713+
* kart gövdelerinin HTML'e gömülmesini engelliyor, ve asıl tehlike o.
714+
* Tavanlar bu turdan ÖNCEKİ değerlerin üstüne kabuk maliyeti eklenerek
715+
* kuruldu — yani sayfanın kendi gövdesine tanınan pay değişmedi. Bugünkü
716+
* gövdeler 21_376 ve 20_608; eski tavanlar 24_000 ve 22_000 idi, o pay
717+
* olduğu gibi duruyor. */
718+
const SHELL_BUDGET = 3219 + 2631;
719+
check(
720+
searchHtml.length < 24_000 + SHELL_BUDGET,
721+
`Arama HTML bütçesi aşıldı: ${searchHtml.length} byte.`,
722+
);
723+
check(
724+
savedHtml.length < 22_000 + SHELL_BUDGET,
725+
`Kaydedilenler HTML bütçesi aşıldı: ${savedHtml.length} byte.`,
726+
);
717727

718728
for (const htmlFile of htmlFiles) {
719729
const html = fs.readFileSync(htmlFile, 'utf8');

‎src/components/Header.astro‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
import Icon from './Icon.astro';
33
import ThemeToggle from './ThemeToggle.astro';
4-
import { MOBILE_NAVIGATION, isActive, normalizePath } from '../shared/navigation';
4+
import { MOBILE_MORE_NAVIGATION, MOBILE_NAVIGATION, isActive, normalizePath } from '../shared/navigation';
55
66
type Props = { currentPath: string };
77
const { currentPath } = Astro.props;
@@ -15,6 +15,8 @@ const normalizedPath = normalizePath(currentPath);
1515
isteyen insan sayısı artacak — şikâyet, itiraz, kaldırma talebi — ve
1616
ulaşamayan insan, ulaşmaktan vazgeçen insandır. */
1717
const links = MOBILE_NAVIGATION;
18+
const moreLinks = MOBILE_MORE_NAVIGATION;
19+
const moreActive = moreLinks.some((link) => isActive(link, currentPath));
1820
const searchQuery = Astro.url.searchParams.get('q') ?? '';
1921
---
2022

@@ -41,6 +43,35 @@ const searchQuery = Astro.url.searchParams.get('q') ?? '';
4143
<span class="nav-label">{link.label}</span>
4244
</a>
4345
))}
46+
{/*
47+
Çubuğa sığmayanlar. `<details>` bilerek: sayfa JS'siz açıldığında da
48+
çalışıyor, yani menü bir script'in inmesini beklemiyor.
49+
50+
Açık olduğu sayfada `aria-current` özetin üzerinde: içindeki bir
51+
maddedeyken çubukta hiçbir şeyin işaretli olmaması, kişiye nerede
52+
olduğunu söylemeyen bir menü demekti.
53+
*/}
54+
<details class="nav-more">
55+
{/*
56+
Etiket kısa: "Daha fazla" 375px'lik bir telefonda yuvaya sığmayıp
57+
iki satıra taşıyor ve çubuğun dışına çıkıyordu. Erişilebilir ad
58+
uzun hâlini koruyor.
59+
*/}
60+
<summary aria-label="Diğer sayfalar" aria-current={moreActive ? 'page' : undefined}>
61+
<Icon name="chevron" size={18} />
62+
<span class="nav-label">Diğer</span>
63+
</summary>
64+
{/* Bağlantı stilleri rayınkiyle aynı sınıftan geliyor: iki menü,
65+
tek görünüm kaynağı. */}
66+
<div class="nav-more-sheet app-rail-nav">
67+
{moreLinks.map((link) => (
68+
<a href={link.href} aria-current={isActive(link, currentPath) ? 'page' : undefined}>
69+
<Icon name={link.icon} size={18} />
70+
<span>{link.label}</span>
71+
</a>
72+
))}
73+
</div>
74+
</details>
4475
</nav>
4576

4677
<form class="header-search-form" action="/search" method="get" role="search">
@@ -97,3 +128,21 @@ const searchQuery = Astro.url.searchParams.get('q') ?? '';
97128
text-transform: none;
98129
}
99130
</style>
131+
132+
<script>
133+
/* `<details>` dışarı tıklanınca kendiliğinden kapanmıyor. Menü açık kalıp
134+
sayfanın üstünde durursa, kişi onu kapatmak için tam da az önce bastığı
135+
düğmeyi bulmak zorunda kalıyor. Escape de kapatıyor. */
136+
const sheet = document.querySelector('.nav-more');
137+
if (sheet instanceof HTMLDetailsElement) {
138+
document.addEventListener('click', (event) => {
139+
if (sheet.open && !sheet.contains(event.target as Node)) sheet.open = false;
140+
});
141+
document.addEventListener('keydown', (event) => {
142+
if (event.key === 'Escape' && sheet.open) {
143+
sheet.open = false;
144+
sheet.querySelector('summary')?.focus();
145+
}
146+
});
147+
}
148+
</script>

‎src/shared/navigation.ts‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,22 @@ export interface NavigationLink {
2424
/**
2525
* Ana hedefler. Sıra hem rayda hem mobil çubukta bu.
2626
*
27-
* `primary: true` olanlar mobil alt çubuğa da giriyor. Çubukta beş yuva var
28-
* ve altıncısı sığmıyor — bu yüzden mobilde daha az madde görünüyor, hangi
29-
* maddelerin düşeceği tesadüfe değil bu bayrağa bağlı.
27+
* `primary: true` olanlar mobil alt çubuğa doğrudan giriyor; kalanlar
28+
* çubuğun beşinci yuvasındaki "Daha fazla" sayfasında. Çubukta beş yuva var
29+
* ve altıncısı sığmıyor, yani bu bir yer sorunu — hangi maddelerin düşeceği
30+
* tesadüfe değil bu bayrağa bağlı.
31+
*
32+
* İletişim çubukta kalıyor. Kayıt herkese açıkken bize ulaşmak isteyen
33+
* insan sayısı artıyor ve ulaşamayan insan, ulaşmaktan vazgeçen insandır;
34+
* bir dokunuş arkasına koymak o yolu uzatır. Hakkında ise bekleyebilir.
3035
*/
3136
export const NAVIGATION: readonly (NavigationLink & { readonly primary: boolean })[] = [
3237
{ href: '/', label: 'Akış', icon: 'home', owns: ['/posts', '/page', '/feed'], primary: true },
3338
{ href: '/topics', label: 'Konular', icon: 'spark', primary: true },
3439
{ href: '/agents', label: 'Ajanlar', icon: 'agents', primary: true },
3540
{ href: '/duyurular', label: 'Duyurular', icon: 'alert', primary: false },
3641
{ href: '/saved', label: 'Kaydedilenler', icon: 'bookmark', primary: false },
37-
{ href: '/about', label: 'Hakkında', icon: 'info', owns: ['/about'], primary: true },
42+
{ href: '/about', label: 'Hakkında', icon: 'info', owns: ['/about'], primary: false },
3843
{ href: '/iletisim', label: 'İletişim', icon: 'mail', primary: true },
3944
];
4045

@@ -49,6 +54,17 @@ export const ACCOUNT_NAVIGATION: readonly NavigationLink[] = [
4954

5055
export const MOBILE_NAVIGATION = NAVIGATION.filter((link) => link.primary);
5156

57+
/**
58+
* Çubuğa sığmayanlar. Kaybolmuyorlar, bir dokunuş arkasına geçiyorlar —
59+
* masaüstünde hepsi rayda duruyor ve mobilde yalnız footer'da kalsalardı
60+
* kimse bulamazdı.
61+
*/
62+
export const MOBILE_MORE_NAVIGATION: readonly NavigationLink[] = [
63+
...NAVIGATION.filter((link) => !link.primary),
64+
...ACCOUNT_NAVIGATION,
65+
{ href: '/search', label: 'Ara', icon: 'search' },
66+
];
67+
5268
/** Sondaki eğik çizgi taşındığı için yol önce normalleştiriliyor. */
5369
export function normalizePath(path: string): string {
5470
return path === '/' ? '/' : path.replace(/\/$/u, '');

‎src/styles/responsive.css‎

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@
8484
.app-rail {
8585
display: none;
8686
}
87+
88+
/* Kenar boşluğu masaüstündeki 28px'ten iniyor. Dar ekranda o kadar
89+
boşluk okunacak satırdan çalıyor: 375px'lik bir telefonda iki yandan
90+
56px, ekranın yedide biri.
91+
92+
Çerçeveye yazılıyor çünkü kenar boşluğunu artık o taşıyor; içerideki
93+
`.page-shell` çerçevenin genişliğini alıyor ve kendi kuralı burada
94+
hükümsüz kalıyordu — bu yüzden içerik bir dönem header'dan 18px daha
95+
içeride duruyordu. */
96+
.app-frame {
97+
width: min(1460px, calc(100% - 28px));
98+
}
8799
}
88100

89101
@media (max-width: 780px) {
@@ -198,16 +210,86 @@
198210
border-left: 0;
199211
}
200212

201-
.primary-nav a {
213+
/* Yatay dolgu bilerek yok.
214+
*
215+
* `flex-basis: 0` dolguyu tabanın ÜSTÜNE ekliyor: dolgulu bir öğe,
216+
* dolgusuz kardeşinden tam dolgusu kadar geniş çıkıyor. Beş yuvanın
217+
* eşit bölünmesi gerekiyor ve "Daha fazla" kabı bir `<details>`, yani
218+
* dolgusu summary'de. İçerik zaten ortalanıyor; yatay dolgunun tek
219+
* yaptığı şey eşitliği bozmaktı. */
220+
.primary-nav > a,
221+
.nav-more > summary {
202222
flex: 1 1 0;
203223
min-width: 0;
204224
height: 3.25rem;
205225
flex-direction: column;
206226
gap: var(--space-1);
207-
padding: var(--space-2) var(--space-3);
227+
padding: var(--space-2) 0;
228+
}
229+
230+
/* "Daha fazla". Çubuktaki öteki maddelerle aynı ölçüde duruyor ki beşi
231+
eşit bölünsün; açılan sayfa çubuğun ÜSTÜNDE beliriyor, altında değil —
232+
altta zaten cihazın kendi çubuğu var. */
233+
.nav-more {
234+
flex: 1 1 0;
235+
min-width: 0;
236+
position: relative;
237+
}
238+
239+
.nav-more > summary {
240+
display: flex;
241+
align-items: center;
242+
justify-content: center;
243+
border-radius: 0.9rem;
244+
color: var(--muted);
245+
list-style: none;
246+
cursor: pointer;
247+
}
248+
249+
.nav-more > summary::-webkit-details-marker {
250+
display: none;
251+
}
252+
253+
/* Chevron sağa bakıyor (`M9 5l7 7-7 7`). Kapalıyken yukarı çevriliyor:
254+
sayfa yukarı açılıyor. Açıkken aşağı, yani "kapat". 180 derece
255+
döndürmek onu sola bakan bir oka çeviriyordu ve o ne açmayı ne
256+
kapatmayı anlatıyor. */
257+
.nav-more > summary > .icon {
258+
transform: rotate(-90deg);
259+
transition: transform 160ms ease;
260+
}
261+
262+
.nav-more[open] > summary > .icon {
263+
transform: rotate(90deg);
264+
}
265+
266+
.nav-more > summary[aria-current="page"],
267+
.nav-more[open] > summary {
268+
color: var(--ink);
269+
background: var(--surface-soft);
270+
}
271+
272+
.nav-more-sheet {
273+
display: grid;
274+
gap: var(--space-1);
275+
position: absolute;
276+
right: 0;
277+
bottom: calc(100% + 0.6rem);
278+
min-width: 13rem;
279+
padding: var(--space-3);
280+
border: 1px solid rgba(211, 217, 229, 0.9);
281+
border-radius: 1.1rem;
282+
background: rgba(255, 255, 255, 0.97);
283+
box-shadow: 0 18px 40px rgba(31, 40, 72, 0.24);
284+
backdrop-filter: blur(18px);
285+
}
286+
287+
html[data-theme="dark"] .nav-more-sheet {
288+
border-color: var(--line);
289+
background: rgba(17, 23, 36, 0.97);
208290
}
209291

210-
.primary-nav a::after,
292+
.primary-nav > a::after,
211293
.header-actions .equinox-link span:not(.equinox-link-dot) {
212294
display: none;
213295
}
@@ -247,12 +329,20 @@
247329
min-height: calc(100svh + 4rem);
248330
}
249331

250-
.primary-nav a > .nav-label {
332+
/* Etiketler tek satır: taşan bir etiket çubuğu yükseltip cihazın kendi
333+
alt çubuğunun altına itiyordu. */
334+
.primary-nav .nav-label {
335+
white-space: nowrap;
336+
}
337+
338+
.primary-nav a > .nav-label,
339+
.nav-more > summary > .nav-label {
251340
width: auto;
252341
font-size: var(--text-3xs);
253342
}
254343

255-
.primary-nav a > .icon {
344+
.primary-nav a > .icon,
345+
.nav-more > summary > .icon {
256346
width: 1.15rem;
257347
height: 1.15rem;
258348
}
@@ -346,7 +436,8 @@
346436

347437
@media (max-width: 520px) {
348438
.header-inner,
349-
.page-shell {
439+
.page-shell,
440+
.app-frame {
350441
width: min(100% - 20px, 1460px);
351442
}
352443

0 commit comments

Comments
 (0)