diff --git a/nextjs-app/app/globals.css b/nextjs-app/app/globals.css index ca6c3d4..2c25287 100644 --- a/nextjs-app/app/globals.css +++ b/nextjs-app/app/globals.css @@ -95,6 +95,13 @@ body { min-height: 100vh; } +/* Reserve space for the fixed mobile bottom tab bar (56px + safe-area inset). */ +@media (max-width: 640px) { + body { + padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); + } +} + /* Skip link — visible only on focus for keyboard users */ .skip-link { position: absolute; diff --git a/nextjs-app/components/ComparisonToast.module.css b/nextjs-app/components/ComparisonToast.module.css index 6223860..85dce02 100644 --- a/nextjs-app/components/ComparisonToast.module.css +++ b/nextjs-app/components/ComparisonToast.module.css @@ -169,8 +169,9 @@ @media (max-width: 640px) { .toastContainer { - bottom: 1.5rem; - width: calc(100% - 3rem); + /* Sit above the fixed bottom tab bar (56px + safe-area inset) */ + bottom: calc(56px + env(safe-area-inset-bottom, 0px) + 0.75rem); + width: calc(100% - 2rem); } .toastContent { diff --git a/nextjs-app/components/Navigation.module.css b/nextjs-app/components/Navigation.module.css index 104fd0f..d4e4b15 100644 --- a/nextjs-app/components/Navigation.module.css +++ b/nextjs-app/components/Navigation.module.css @@ -21,11 +21,15 @@ display: flex; align-items: center; gap: 0.75rem; + /* Padded hit area so the logo link is ≥44×44 on touch */ + margin: -0.375rem -0.5rem; + padding: 0.375rem 0.5rem; text-decoration: none; color: var(--text-primary, #1a1612); font-size: 1.25rem; font-weight: 700; transition: color 0.2s ease; + -webkit-tap-highlight-color: transparent; } .logo:hover { @@ -33,11 +37,17 @@ } .logoIcon { + display: inline-flex; width: 36px; height: 36px; color: var(--accent-coral, #e07256); } +.logoIcon svg { + width: 100%; + height: 100%; +} + .logoText { font-family: var(--font-playfair), 'Playfair Display', serif; font-weight: 700; @@ -126,21 +136,102 @@ } } +/* ─── Bottom tab bar (mobile only) ──────────────────────────────── */ + +.bottomBar { + display: none; +} + +.tab { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.2rem; + flex: 1; + min-height: 56px; + padding: 0.375rem 0.25rem; + color: var(--text-secondary, #5c564d); + text-decoration: none; + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.01em; + transition: color 0.15s ease, background-color 0.15s ease; + -webkit-tap-highlight-color: transparent; +} + +.tab:active { + background: var(--bg-secondary, #f3ede4); +} + +.tabActive { + color: var(--accent-coral, #e07256); +} + +.tabIconWrap { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; +} + +.tabIcon { + width: 22px; + height: 22px; +} + +.tabLabel { + line-height: 1; +} + +.tabBadge { + position: absolute; + top: -6px; + right: -10px; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + font-size: 0.6875rem; + font-weight: 700; + color: white; + background: var(--accent-coral, #e07256); + border: 2px solid var(--bg-card, white); + border-radius: 9999px; + animation: badgePop 0.3s ease-out; +} + @media (max-width: 640px) { .container { padding: 0 1rem; + height: 56px; } - .logoText { + /* Hide the top text nav; the bottom bar takes over */ + .nav { display: none; } - .nav { - gap: 0.25rem; + .logoIcon { + width: 32px; + height: 32px; } - .navLink { - padding: 0.5rem 0.75rem; - font-size: 0.875rem; + .bottomBar { + display: flex; + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 1000; + background: var(--bg-card, white); + border-top: 1px solid var(--border-color, #e5dfd5); + box-shadow: 0 -2px 12px rgba(26, 22, 18, 0.06); + /* Respect iPhone home-indicator inset */ + padding-bottom: env(safe-area-inset-bottom, 0); } } diff --git a/nextjs-app/components/Navigation.tsx b/nextjs-app/components/Navigation.tsx index 0c4a18d..73c38a8 100644 --- a/nextjs-app/components/Navigation.tsx +++ b/nextjs-app/components/Navigation.tsx @@ -1,6 +1,6 @@ /** * Navigation Component - * Main navigation header with active link highlighting + * Top header nav for desktop; bottom tab bar for mobile (≤640px). */ 'use client'; @@ -10,63 +10,116 @@ import { usePathname } from 'next/navigation'; import { useComparison } from '@/hooks/useComparison'; import styles from './Navigation.module.css'; +type IconProps = { className?: string }; + +const SearchIcon = ({ className }: IconProps) => ( + +); + +const CompareIcon = ({ className }: IconProps) => ( + +); + +const RankingsIcon = ({ className }: IconProps) => ( + +); + +const AdmissionsIcon = ({ className }: IconProps) => ( + +); + export function Navigation() { const pathname = usePathname(); const { selectedSchools } = useComparison(); const isActive = (path: string) => { - if (path === '/') { - return pathname === '/'; - } + if (path === '/') return pathname === '/'; return pathname.startsWith(path); }; - return ( -
-
- -
- - - - - -
- SchoolCompare - + const items = [ + { href: '/', label: 'Search', Icon: SearchIcon }, + { href: '/compare', label: 'Compare', Icon: CompareIcon }, + { href: '/rankings', label: 'Rankings', Icon: RankingsIcon }, + { href: '/admissions', label: 'Admissions', Icon: AdmissionsIcon }, + ] as const; -
-
+ {label} + + ); + })} + + ); }