From 41a22e3c3601ca9c1aa8dc33b0f369c89a05201c Mon Sep 17 00:00:00 2001 From: "Thomas (Aeshus)" Date: Sat, 25 Apr 2026 09:48:50 -0400 Subject: [PATCH 1/2] Suffer --- src/app/exercise/[id]/ExercisePageClient.tsx | 10 +++++- src/app/globals.css | 36 ++++++++++++++++++-- src/app/page.tsx | 10 ++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/app/exercise/[id]/ExercisePageClient.tsx b/src/app/exercise/[id]/ExercisePageClient.tsx index 9f64018..cc78f52 100644 --- a/src/app/exercise/[id]/ExercisePageClient.tsx +++ b/src/app/exercise/[id]/ExercisePageClient.tsx @@ -176,19 +176,27 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s useEffect(() => { const mainElement = document.querySelector('#app-body > main'); const appElement = document.getElementById('app'); + const bodyElement = document.body; + const htmlElement = document.documentElement; if (!mainElement) return; if (isMobile) { mainElement.classList.add('exercise-main-mobile-shell'); appElement?.classList.add('exercise-mobile-nav-bottom'); + bodyElement.classList.add('exercise-mobile-scroll-lock'); + htmlElement.classList.add('exercise-mobile-scroll-lock'); } else { mainElement.classList.remove('exercise-main-mobile-shell'); appElement?.classList.remove('exercise-mobile-nav-bottom'); + bodyElement.classList.remove('exercise-mobile-scroll-lock'); + htmlElement.classList.remove('exercise-mobile-scroll-lock'); } return () => { mainElement.classList.remove('exercise-main-mobile-shell'); appElement?.classList.remove('exercise-mobile-nav-bottom'); + bodyElement.classList.remove('exercise-mobile-scroll-lock'); + htmlElement.classList.remove('exercise-mobile-scroll-lock'); }; }, [isMobile, pathname]); @@ -401,7 +409,7 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s if (id === 'rit-00') { dispatch({ type: 'EXERCISE_COMPLETED', exerciseId: id }); } - }, [id]); // eslint-disable-line + }, [id]); const mobileVizLabel = 'Assembly'; diff --git a/src/app/globals.css b/src/app/globals.css index fe705b8..3fa70a2 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -96,6 +96,19 @@ body { overflow-x: hidden; } +html.intro-page, +body.intro-page { + background: #061018; +} + +body.intro-page { + background: #061018; +} + +body.intro-page::before { + display: none; +} + body::before { content: ''; position: fixed; @@ -783,6 +796,12 @@ button.link-button:focus-visible, ::-webkit-scrollbar-thumb { background: var(--panel-border); } @media (max-width: 900px) { + html.exercise-mobile-scroll-lock, + body.exercise-mobile-scroll-lock { + overflow: hidden; + overscroll-behavior: none; + } + #app { height: 100dvh; } @@ -856,6 +875,7 @@ button.link-button:focus-visible, main.exercise-main-mobile-shell { display: block; + min-height: 0; padding: var(--padding-sm); overflow: hidden; } @@ -866,6 +886,7 @@ button.link-button:focus-visible, height: 100%; min-height: 0; gap: var(--padding-sm); + overflow: hidden; } .mobile-directions-panel { @@ -904,16 +925,20 @@ button.link-button:focus-visible, } .input-panel-header { - align-items: flex-start; + align-items: center; } .input-panel-header-actions { gap: 0.35rem; + min-width: 0; + flex-wrap: nowrap; } .input-panel-progress { - width: 100%; + min-width: 0; + width: auto; text-align: right; + white-space: nowrap; } .input-panel-action { @@ -990,6 +1015,7 @@ button.link-button:focus-visible, .mobile-workspace-panel { min-height: 0; flex: 1; + overflow: hidden; } .mobile-workspace-panel > .panel { @@ -1007,19 +1033,25 @@ button.link-button:focus-visible, .mobile-bottom-dock { flex-shrink: 0; + min-height: 0; display: flex; flex-direction: column; gap: 0.5rem; + overflow: hidden; } .mobile-bottom-dock #input-panel { + display: flex; + flex-direction: column; min-height: 0; height: auto; max-height: min(42dvh, 26rem); + overflow: hidden; } .mobile-bottom-dock #input-panel .panel-body { overflow: auto; + min-height: 0; } .mobile-bottom-dock #input-area { diff --git a/src/app/page.tsx b/src/app/page.tsx index 015abef..8225f2e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -16,6 +16,16 @@ export default function Dashboard() { setMounted(true); }, []); + useEffect(() => { + document.body.classList.add('intro-page'); + document.documentElement.classList.add('intro-page'); + + return () => { + document.body.classList.remove('intro-page'); + document.documentElement.classList.remove('intro-page'); + }; + }, []); + if (!mounted) return null; const allExercises = getAllExercises(); From 89d23a41c2224d0762b733b7d717da701a32aaed Mon Sep 17 00:00:00 2001 From: "Thomas (Aeshus)" Date: Sat, 25 Apr 2026 09:59:20 -0400 Subject: [PATCH 2/2] Cry at sefari's existance --- src/app/exercise/[id]/ExercisePageClient.tsx | 32 ++++++++++++++++++++ src/app/globals.css | 26 ++++++++++++---- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/app/exercise/[id]/ExercisePageClient.tsx b/src/app/exercise/[id]/ExercisePageClient.tsx index cc78f52..83de890 100644 --- a/src/app/exercise/[id]/ExercisePageClient.tsx +++ b/src/app/exercise/[id]/ExercisePageClient.tsx @@ -169,6 +169,38 @@ export default function ExercisePageClient({ params }: { params: Promise<{ id: s return () => mediaQuery.removeListener(syncIsMobile); }, []); + useEffect(() => { + if (typeof window === 'undefined') return; + + const htmlElement = document.documentElement; + + if (!isMobile) { + htmlElement.style.removeProperty('--mobile-visual-viewport-height'); + return; + } + + const syncViewportHeight = () => { + const viewportHeight = Math.round(window.visualViewport?.height ?? window.innerHeight); + htmlElement.style.setProperty('--mobile-visual-viewport-height', `${viewportHeight}px`); + }; + + syncViewportHeight(); + + const viewport = window.visualViewport; + viewport?.addEventListener('resize', syncViewportHeight); + viewport?.addEventListener('scroll', syncViewportHeight); + window.addEventListener('resize', syncViewportHeight); + window.addEventListener('orientationchange', syncViewportHeight); + + return () => { + viewport?.removeEventListener('resize', syncViewportHeight); + viewport?.removeEventListener('scroll', syncViewportHeight); + window.removeEventListener('resize', syncViewportHeight); + window.removeEventListener('orientationchange', syncViewportHeight); + htmlElement.style.removeProperty('--mobile-visual-viewport-height'); + }; + }, [isMobile]); + useEffect(() => { setActiveMobileTab('source'); }, [id]); diff --git a/src/app/globals.css b/src/app/globals.css index 3fa70a2..e527ae1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -254,6 +254,8 @@ header h1 { #app-body { display: flex; flex-direction: row; flex: 1; overflow: hidden; + min-height: 0; + min-width: 0; } main { @@ -264,6 +266,8 @@ main { gap: var(--padding-sm); background: transparent; padding: var(--padding-sm); + min-height: 0; + min-width: 0; } .panel { @@ -272,6 +276,8 @@ main { border-radius: 0.35rem; box-shadow: var(--shadow-md); backdrop-filter: blur(18px); + min-height: 0; + min-width: 0; } .panel-hdr { padding: 0.5rem 0.9rem; font-size: 11px; color: var(--fg-subtle); @@ -280,7 +286,7 @@ main { font-family: var(--font-sans); font-weight: 700; } -.panel-body { flex: 1; overflow-y: auto; padding: 0.85rem; } +.panel-body { flex: 1; overflow-y: auto; padding: 0.85rem; min-height: 0; min-width: 0; } /* Source panel */ #source-panel { grid-column: 1; grid-row: 1; } @@ -803,7 +809,7 @@ button.link-button:focus-visible, } #app { - height: 100dvh; + height: var(--mobile-visual-viewport-height, 100dvh); } header { @@ -875,14 +881,15 @@ button.link-button:focus-visible, main.exercise-main-mobile-shell { display: block; + height: 100%; min-height: 0; padding: var(--padding-sm); overflow: hidden; } .mobile-exercise-shell { - display: flex; - flex-direction: column; + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; height: 100%; min-height: 0; gap: var(--padding-sm); @@ -977,6 +984,7 @@ button.link-button:focus-visible, display: flex; flex-direction: column; gap: 0.5rem; + overflow: hidden; } .mobile-workspace-tabs { @@ -1013,18 +1021,23 @@ button.link-button:focus-visible, } .mobile-workspace-panel { + display: flex; + flex: 1 1 auto; + flex-direction: column; min-height: 0; - flex: 1; overflow: hidden; } .mobile-workspace-panel > .panel { - height: 100%; + flex: 1 1 auto; + height: auto; min-height: 0; } .mobile-workspace-panel > .panel .panel-body { min-height: 0; + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; } .mobile-misc-panel .panel-body { @@ -1038,6 +1051,7 @@ button.link-button:focus-visible, flex-direction: column; gap: 0.5rem; overflow: hidden; + padding-bottom: env(safe-area-inset-bottom); } .mobile-bottom-dock #input-panel {