Make Habla Hermano fully mobile-responsive so the app delivers a native-like experience on phones and tablets. The app already has a solid foundation (viewport meta tag, touch-friendly button sizing, one mobile media query), but most pages were designed desktop-first with minimal responsive breakpoint usage. This phase systematically addresses every page, partial, and interaction pattern to ensure a polished mobile experience across all screen sizes.
Business Value: Language learning is fundamentally a mobile activity. Users practice during commutes, breaks, and downtime. A friction-free mobile experience directly increases session frequency and retention.
- F1: Chat page must use full viewport height on mobile with no content hidden behind browser chrome or virtual keyboards
- F2: All dropdown menus (language, level, hamburger menu) must work correctly on touch devices with proper tap targets and dismiss behavior
- F3: Lesson player must have a fixed footer that doesn't shift on scroll, with adequate touch targets for navigation
- F4: Progress dashboard charts must resize gracefully on small screens without overlapping labels
- F5: Scaffolding word bank must wrap naturally and remain easily tappable on narrow screens
- F6: Review mode cards must be fully usable on 320px-wide screens
- F7: Auth pages (login/signup) must be centered and usable on all screen sizes
- F8: Navigation between pages must be accessible via the existing mobile-friendly hamburger menu
- NFR1: No horizontal scrolling on any page at any viewport width >= 320px
- NFR2: All interactive elements must have minimum 44x44px touch targets (WCAG 2.1 AA)
- NFR3: Page load performance must not degrade - no additional JS viewport detection libraries
- NFR4: All existing accessibility features (reduced motion, high contrast) must continue working
- NFR5: All three themes (light/dark/ocean) must look correct at every breakpoint
- NFR6: Text must remain readable (>= 16px body, >= 14px secondary) without pinch-to-zoom
What already works:
- Viewport meta tag in
base.html:width=device-width, initial-scale=1.0 overscroll-behavior: noneon mobile (prevents iOS bounce)- 48px min-height for buttons on mobile via
@media (max-width: 640px) - Chat bubbles widen to 90% on mobile
prefers-reduced-motionandprefers-contrast: highsupport- Basic responsive grids:
grid-cols-2 md:grid-cols-4for stats,sm:grid-cols-2for lessons - Max-width containers (
max-w-3xl,max-w-4xl,max-w-2xl) prevent over-wide layouts
What needs work:
| Page | Issue | Severity |
|---|---|---|
| Chat | Input footer doesn't account for iOS safe areas or virtual keyboards | High |
| Chat | Header controls (3 dropdowns) feel cramped below 375px | Medium |
| Chat | No safe-area-inset padding for notched phones | High |
| Lesson Player | Footer nav buttons lack adequate mobile touch targets | Medium |
| Lesson Player | Step content can overflow horizontally if code/tables present | Medium |
| Progress | Charts render at fixed h-48 - labels overlap on small screens |
Medium |
| Progress | Stats cards 2-col grid works but spacing is tight on small screens | Low |
| Lessons | Card descriptions truncate aggressively on narrow screens | Low |
| Scaffold | max-w-[85%] clips content on mobile |
Medium |
| Review | Review cards/questions don't adapt spacing for mobile | Medium |
| Auth | Already responsive (sm:max-w-md) - minor polish needed |
Low |
| Global | No env(safe-area-inset-*) for notched devices |
High |
| Global | Scrollbar styling wastes 8px on mobile where overlay scrollbars are native | Low |
| Global | No touch-action optimizations for scroll containers | Low |
Strategy: CSS-only, mobile-first enhancement using Tailwind responsive prefixes
No JavaScript viewport detection. No new dependencies. Pure Tailwind classes + targeted CSS media queries in input.css. This aligns with the existing architecture (HTMX + Alpine.js + Tailwind CDN).
The only JS addition is a small visualViewport listener for virtual keyboard handling on the chat page.
Use Tailwind's default breakpoints (already configured):
| Token | Width | Target Devices |
|---|---|---|
| (base) | 0-639px | Phones (portrait) |
sm: |
>= 640px | Phones (landscape), small tablets |
md: |
>= 768px | Tablets (portrait) |
lg: |
>= 1024px | Tablets (landscape), laptops |
No custom breakpoints needed. The app's max-width containers (max-w-3xl = 768px, max-w-4xl = 896px) already prevent the layout from stretching beyond reasonable widths.
Templates (7 files):
src/templates/base.html # Safe area insets, viewport fixes, scrollbar
src/templates/chat.html # Header spacing, keyboard-aware footer
src/templates/lesson_player.html # Responsive footer, overflow handling
src/templates/lessons.html # Card description overflow
src/templates/progress.html # Chart container, stats grid
src/templates/auth/login.html # Input font-size for iOS
src/templates/auth/signup.html # Input font-size for iOS
Partials (11 files):
src/templates/partials/scaffold.html # Remove max-w clip on mobile
src/templates/partials/pronunciation_tips.html # Remove max-w clip on mobile
src/templates/partials/grammar_feedback.html # Remove max-w clip on mobile
src/templates/partials/review_start.html # Mobile spacing + button sizing
src/templates/partials/review_question.html # Mobile card layout
src/templates/partials/review_feedback.html # Mobile card layout
src/templates/partials/review_complete.html # Mobile spacing
src/templates/partials/stats_summary.html # Tighter mobile padding
src/templates/partials/vocab_sidebar.html # Mobile collapse behavior
src/templates/partials/lesson_step.html # Overflow handling
src/templates/partials/lesson_step_enhanced.html # Overflow handling
CSS (1 file):
src/static/css/input.css # Safe areas, touch utilities, scrollbar
JavaScript (1 file):
src/static/js/app.js # Virtual keyboard scroll fix
Problem: iPhones with notches/Dynamic Island clip content behind system UI. The virtual keyboard pushes content off-screen on iOS Safari. h-full (100vh) includes the browser chrome height on mobile Safari, causing content to extend behind the URL bar.
Changes to base.html:
<!-- Update viewport meta (line 5) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<!-- Update #app container (line 487) -->
<!-- Change: class="h-full flex flex-col" -->
<!-- To: class="h-dvh flex flex-col" -->Changes to input.css - add at end of file:
/* ============================================
MOBILE RESPONSIVE - Phase 13
============================================ */
/* Dynamic viewport height - accounts for mobile browser chrome */
.h-dvh {
height: 100vh; /* fallback */
height: 100dvh;
}
/* Safe area utilities for notched devices */
.safe-top { padding-top: env(safe-area-inset-top); }
.safe-bottom { padding-bottom: env(safe-area-inset-bottom); }
.safe-x {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
/* Prevent text size adjustment on orientation change */
html { -webkit-text-size-adjust: 100%; }
/* Prevent 300ms tap delay */
html { touch-action: manipulation; }
/* Prevent iOS input zoom (inputs < 16px trigger zoom) */
@media (max-width: 640px) {
input, select, textarea {
font-size: 16px !important;
}
/* Hide scrollbar on mobile (overlay scrollbars are native) */
::-webkit-scrollbar { width: 0; height: 0; }
* { scrollbar-width: none; }
}Header (line 8):
- Add
safe-top safe-xclasses to header - Tighten selector gap on mobile: change
gap-2togap-1.5 sm:gap-2on the right-side controls div (line 136)
Chat container (line 236):
- Reduce vertical padding on mobile: change
py-8topy-4 sm:py-8
Input footer (line 300):
- Add
safe-bottom safe-xclasses to footer - Reduce padding on mobile: change
px-4 py-4 sm:px-6topx-3 py-3 sm:px-6 sm:py-4
Add to end of file:
// Virtual keyboard: scroll chat to bottom when keyboard opens/closes
if ('visualViewport' in window) {
window.visualViewport.addEventListener('resize', function() {
var chatContainer = document.getElementById('chat-container');
if (chatContainer) {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
});
}Header (line 8):
- Add
safe-top safe-xclasses - Add
truncateto title span (line 18) to prevent overflow on narrow screens
Content area (line 37-38):
- Add
overflow-x-hiddento the scrollable div - Reduce padding: change
px-4 py-8topx-3 py-4 sm:px-4 sm:py-8
Footer (line 46):
- Add
safe-bottom safe-xclasses - Increase button padding for touch: prev button
px-4 py-2topx-4 py-3 sm:py-2, next/complete buttonpx-5 py-2topx-5 py-3 sm:py-2
Section spacing (line 28):
- Change
space-y-6tospace-y-4 sm:space-y-6
Chart containers (lines 60, 67):
- Change
p-5top-4 sm:p-5 - Change
h-48toh-40 sm:h-48
Chart.js options (in script block):
- Add
maxTicksLimit: 5to x-axis ticks for mobile readability (line 113)
scaffold.html (line 14):
- Change
max-w-[85%]tomax-w-full sm:max-w-[85%]
pronunciation_tips.html and grammar_feedback.html:
- Same treatment: any
max-w-[85%]becomesmax-w-full sm:max-w-[85%]
review_start.html, review_question.html, review_feedback.html, review_complete.html:
- Reduce padding: any
p-6becomesp-4 sm:p-6 - Action buttons: add
w-full sm:w-autofor full-width buttons on mobile - Increase spacing between answer options for touch accuracy
Already well-structured. One improvement:
- Lesson card descriptions (lines 35-36, 69-70): change
truncatetoline-clamp-2 sm:truncatefor better mobile readability
Already responsive. Minor fixes:
- Add
safe-topclass to the outer container - Input
font-size: 16pxfix is handled globally by the CSS rule in Task 1
- No new libraries or packages
- No backend changes required
- No database migrations
- No API contract changes
All modifications are frontend-only: templates, CSS, and a small JS addition.
- Update viewport meta to
viewport-fit=cover - Add safe area CSS utilities to
input.css - Add
h-dvhutility class - Replace
h-fullwithh-dvhon#appcontainer - Add touch optimization CSS (manipulation, input font-size fix)
- Add mobile scrollbar hiding
- Add
-webkit-text-size-adjust: 100%
- Add
safe-top safe-xto header - Add
safe-bottom safe-xto footer - Tighten header control spacing for mobile (
gap-1.5 sm:gap-2) - Reduce chat container padding on mobile (
py-4 sm:py-8) - Reduce footer padding on mobile (
px-3 py-3 sm:px-6 sm:py-4) - Add virtual keyboard handling in
app.js
- Add safe area classes to header and footer
- Add
truncateto lesson title - Add
overflow-x-hiddento content area - Reduce content padding on mobile
- Increase footer button touch targets (
py-3 sm:py-2)
- Reduce chart height on mobile (
h-40 sm:h-48) - Tighten section spacing (
space-y-4 sm:space-y-6) - Reduce card padding (
p-4 sm:p-5) - Add
maxTicksLimit: 5to chart x-axis
- Fix scaffold
max-w-[85%]→max-w-full sm:max-w-[85%] - Fix pronunciation_tips and grammar_feedback
max-won mobile - Update review partials with responsive padding (
p-4 sm:p-6) - Make review buttons full-width on mobile (
w-full sm:w-auto) - Verify word bank chips meet 44px touch target
- Add
line-clamp-2 sm:truncateto lesson card descriptions - Add safe area padding to auth pages
- Verify form inputs are 16px on mobile (handled by global CSS)
- Visual testing at 320px, 375px, 393px, 768px, 1024px viewports
- Test all three themes at each breakpoint
- Test virtual keyboard behavior on chat page
- Test dropdown menus on touch devices
- Verify no horizontal scrolling on any page
- Verify accessibility (reduced motion, high contrast) still works
- Test lesson player step navigation on mobile
- Test review mode full flow on mobile
Total estimated effort: ~4 hours
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
100dvh not supported in old browsers |
Low | Slight gap on older Safari | Fallback: height: 100vh; height: 100dvh; (stacked declarations) |
env(safe-area-inset-*) not supported |
Low | Extra padding doesn't apply | Feature is purely additive; no breakage on unsupported browsers |
| iOS zoom on input focus | Medium | Disorienting zoom when tapping chat input | Force font-size: 16px on mobile inputs via CSS |
| Virtual keyboard pushes layout | Medium | Chat input hidden behind keyboard | visualViewport API listener + scroll fix in app.js |
| Chart.js labels overlap on small screens | Medium | Unreadable chart axis labels | Set maxTicksLimit: 5 on x-axis for mobile |
scrollbar-width: none hides scrollbar everywhere on mobile |
Low | Users lose scroll position indicator | Acceptable trade-off; content is vertically scrollable and users expect this on mobile |
- No new unit tests needed (CSS-only changes don't affect Python logic)
- Existing 1445+ tests remain unaffected
- Run full suite to confirm:
make test
Screenshot comparisons at 5 viewport widths:
- 320px (iPhone SE / smallest supported)
- 375px (iPhone SE 3rd gen)
- 393px (iPhone 15 Pro)
- 768px (iPad Mini portrait)
- 1024px (iPad landscape / small laptop)
All 3 themes at each width. Pages: chat, lessons, lesson player, progress, login.
- iPhone SE (375x667) - smallest common iPhone
- iPhone 15 Pro (393x852) - modern iPhone with Dynamic Island
- iPad Mini (768x1024) - tablet portrait
- Galaxy S21 (360x800) - common Android phone
- Virtual keyboard open/close on chat page
- Dropdown menus dismiss on outside tap
- Landscape orientation on phones (no horizontal overflow)
- All three themes at every viewport
- Scaffold word bank tappability
- Lesson player step navigation
- Review mode full flow on mobile
- No horizontal scrollbar on any page at >= 320px width
- All interactive elements >= 44x44px touch target
- Chat input remains visible when virtual keyboard opens
- Safe area insets applied on notched devices
- Charts readable at 375px width
- All three themes render correctly at all breakpoints
- Existing accessibility features unaffected
- No new JavaScript dependencies added
- All existing tests pass unchanged