Skip to content

Commit 53d0384

Browse files
estradinoclaude
andauthored
mobile: fix iOS full-height gap, drawer header/scroll, theme+language row, 404 robot (#279)
Follow-ups from on-device (iPhone) testing after #278: - Full-height gap on iOS: the app shell's `margin-top` (navbar offset) collapsed through <body> and stacked on `min-height:100dvh`, adding the navbar height to the scrollable document (~56px of document overflow). That let iOS Safari's toolbar engage on scroll and left a gap at the bottom. Give <body> `display: flow-root` to contain the margin — document overflow drops to 0; the shell / `.or-main` still own scrolling. - Header turned grey when the drawer opened: the header's translucent background + backdrop blur was sampling the dark `#mobile-overlay` sitting behind it. Make the header solid (no blur) while the drawer is open. - Hard to scroll the open drawer back up: scroll chained into the page behind it. Lock the background `.or-main` scroll while the drawer is open and add `overscroll-behavior: contain` to the drawer. - Theme toggle was alone at the bottom of the drawer: remove the redundant standalone `.or-mobile-footer` toggle and keep LeftSidebar's toggle, which sits in the same row as the language selector. (Reverts #278's hide of that toggle.) - 404 robot was static: the `@keyframes r4_*` referenced by the SVG's inline `animation:` were never defined anywhere in the repo. Add them ("The Yank": tug the cord, spark, lights-out, recoil). Verified in the WebKit (Safari) engine at an iPhone viewport; desktop unaffected. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e8c5bde commit 53d0384

4 files changed

Lines changed: 83 additions & 55 deletions

File tree

public/index.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,14 @@ html {
6262

6363
body {
6464
color: var(--theme-text);
65+
/* Establish a block formatting context so the app shell's `margin-top` (the
66+
navbar offset) is contained instead of collapsing through the body and adding
67+
the navbar height to the scrollable document. That stray document overflow let
68+
iOS Safari's toolbar engage on scroll and left a gap ("not full height") at the
69+
bottom of the viewport. */
70+
display: flow-root;
6571
min-height: 100vh; /* fallback for browsers without dvh */
66-
min-height: 100dvh; /* iOS Safari: excludes the retracted toolbar strip → no bottom gap */
72+
min-height: 100dvh; /* dynamic viewport height (iOS Safari) */
6773
font-family: var(--font-body);
6874
font-size: 16px;
6975
line-height: 1.5;

public/redesign.css

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -81,56 +81,33 @@ body {
8181
border-inline-end: 1px solid var(--or-border);
8282
transform: translateX(-100%);
8383
transition: transform 0.25s ease;
84+
/* Keep scroll momentum inside the drawer instead of chaining to the page behind
85+
it (which made it hard to scroll the menu back up on iOS). */
86+
overscroll-behavior: contain;
8487
}
8588
[dir='rtl'] .or-drawer { transform: translateX(100%); }
8689
body.mobile-sidebar-toggle #mobile-left-sidebar.or-drawer { transform: translateX(0); box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); }
8790
#mobile-overlay { transition: opacity 0.25s ease; }
8891
body.mobile-sidebar-toggle #mobile-overlay { opacity: 1 !important; pointer-events: auto !important; }
8992

90-
/* mobile drawer footer */
91-
.or-mobile-footer { display: none; }
92-
@media (max-width: 768px) {
93-
.or-mobile-footer {
94-
display: flex;
95-
align-items: center;
96-
gap: 10px;
97-
margin: 18px 14px;
98-
padding-top: 16px;
99-
border-top: 1px solid var(--or-border);
100-
}
101-
.or-mobile-theme {
102-
width: 38px;
103-
height: 38px;
104-
display: inline-flex;
105-
align-items: center;
106-
justify-content: center;
107-
border-radius: 9999px;
108-
border: 1px solid var(--or-border);
109-
background: var(--surface);
110-
color: var(--text-2);
111-
cursor: pointer;
112-
}
113-
}
114-
115-
/* The mobile drawer footer (.or-mobile-footer > .or-mobile-theme) already supplies
116-
the theme toggle. LeftSidebar still ships a legacy in-sidebar ThemeToggleButton
117-
(isInsideHeader=false → .theme-toggle-pill:not(.in-header)) that duplicates it and
118-
crowds/overlaps the bottom nav rows in the drawer. Hide the legacy one in the
119-
sidebar contexts; the LanguageSelect in that row stays. The header toggle
120-
(.in-header) is unaffected. */
121-
.or-drawer .theme-toggle-pill:not(.in-header),
122-
.or-left .theme-toggle-pill:not(.in-header) {
123-
display: none !important;
124-
}
125-
126-
/* In the mobile drawer the <nav> and the footer (.or-mobile-footer) are siblings,
127-
so the nav must size to its content and let the footer flow BELOW all nav rows.
128-
LeftSidebar's nav carries Tailwind `h-full` (height:100%), which caps the nav
129-
box to the drawer height while its rows overflow *visibly* past it — the footer
130-
(theme toggle) was then painted ON TOP of the mid-list nav rows. Override h-full
131-
here so the nav grows to its real height. Scoped to the drawer only: the desktop
132-
`.or-left` keeps its in-nav footer as a flow list-item (not a sibling), so it has
133-
no overlap and needs no override. */
93+
/* When the drawer is open, lock the background page scroll (.or-main) so touch
94+
scrolling stays inside the drawer instead of chaining into the page behind it. */
95+
body.mobile-sidebar-toggle .or-main { overflow: hidden; }
96+
97+
/* The header has a translucent background + backdrop blur. The dark #mobile-overlay
98+
sits behind it, so the blur sampled the overlay and turned the header grey when the
99+
drawer opened. Make the header solid (no blur) while the drawer is open. */
100+
body.mobile-sidebar-toggle header {
101+
background: var(--page);
102+
-webkit-backdrop-filter: none;
103+
backdrop-filter: none;
104+
}
105+
106+
/* In the mobile drawer the whole <nav> scrolls. LeftSidebar's nav carries Tailwind
107+
`h-full` (height:100%), which caps the nav box to the drawer height while its
108+
content (nav rows + the theme/language footer row) overflows past it. Let the nav
109+
size to its real height so the drawer scrolls cleanly. Scoped to the drawer; the
110+
desktop `.or-left` is unaffected. */
134111
.or-drawer .or-sidenav {
135112
height: auto;
136113
}

src/layouts/BaseLayout.astro

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,6 @@ const breadcrumb = getBreadcrumb(currentPage);
5656
class="md:hidden fixed top-14 bottom-0 left-0 w-[300px] max-w-[85vw] overflow-y-auto z-50 or-noscroll or-drawer"
5757
>
5858
<LeftSidebar {currentPage} />
59-
<div class="or-mobile-footer">
60-
<button
61-
type="button"
62-
class="or-mobile-theme"
63-
aria-label="Toggle theme"
64-
onclick="(function(){var r=document.documentElement;var d=r.classList.toggle('theme-dark');r.classList.toggle('theme-light',!d);try{localStorage.setItem('theme',d?'dark':'light')}catch(e){}})()"
65-
>
66-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"></path></svg>
67-
</button>
68-
</div>
6959
</aside>
7060

7161
{/* Mobile Background Overlay */}

src/pages/[lang]/404.astro

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,61 @@ const t = useTranslations(Astro);
7777
/* ---- Animated 404 robot ("The Yank") + page chrome ----
7878
Everything is namespaced under .or-404 so nothing leaks. Keyframes are global
7979
(not Astro-scoped) so the SVG's inline `animation:` references resolve. */
80+
81+
/* "The Yank" loop (3.8s): the robot tugs its power cord, a spark pops at the
82+
outlet, its screen flickers out, and it recoils — then recovers and repeats.
83+
The arm group (r4_yank) has no transform-origin, so it only translates; the
84+
body/antenna/spark/shadow groups set transform-box:fill-box inline and rotate
85+
or scale about their own box. */
86+
@keyframes r4_yank {
87+
0%, 18% { transform: translate(0, 0); }
88+
30% { transform: translate(-10px, -4px); } /* pull the cord taut */
89+
40% { transform: translate(4px, 2px); } /* it pops loose */
90+
55%, 100% { transform: translate(0, 0); }
91+
}
92+
@keyframes r4_rock {
93+
0%, 22% { transform: rotate(0deg); }
94+
32% { transform: rotate(2.5deg); } /* lean into the pull */
95+
42% { transform: rotate(-4.5deg); } /* recoil from the jolt */
96+
54% { transform: rotate(2deg); }
97+
64% { transform: rotate(-1deg); }
98+
74%, 100% { transform: rotate(0deg); }
99+
}
100+
@keyframes r4_whip {
101+
0%, 22% { transform: rotate(0deg); }
102+
34% { transform: rotate(12deg); }
103+
44% { transform: rotate(-14deg); } /* antenna snaps back */
104+
56% { transform: rotate(7deg); }
105+
66% { transform: rotate(-3deg); }
106+
76%, 100% { transform: rotate(0deg); }
107+
}
108+
@keyframes r4_spark {
109+
0%, 33% { opacity: 0; transform: scale(0.4); }
110+
37% { opacity: 1; transform: scale(1.15); } /* flash at the outlet */
111+
46% { opacity: 0; transform: scale(0.6); }
112+
100% { opacity: 0; }
113+
}
114+
@keyframes r4_glow {
115+
0%, 34% { opacity: 0.85; }
116+
37% { opacity: 0.25; } /* flicker as power is cut */
117+
40% { opacity: 0; } /* lights out */
118+
82% { opacity: 0; }
119+
90% { opacity: 0.5; } /* power restored */
120+
100% { opacity: 0.85; }
121+
}
122+
@keyframes r4_off {
123+
0%, 38% { opacity: 0; }
124+
41% { opacity: 1; } /* dark screen overlay shows */
125+
82% { opacity: 1; }
126+
92%, 100% { opacity: 0; } /* screen comes back on */
127+
}
128+
@keyframes r4_shadow {
129+
0%, 22% { transform: scaleX(1); opacity: 1; }
130+
42% { transform: scaleX(0.9); opacity: 0.78; } /* recoil lifts it slightly */
131+
64% { transform: scaleX(1.03); opacity: 1; }
132+
74%, 100% { transform: scaleX(1); opacity: 1; }
133+
}
134+
80135
.or-404 {
81136
text-align: center;
82137
max-width: 560px;

0 commit comments

Comments
 (0)