Files
school_compare/nextjs-app/components/Navigation.module.css
T
Tudor Sitaru a5be07ac0f
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
fix(nav): keep bottom tab bar flush to visible viewport on iOS Chrome
iOS Chrome (and some Android browsers) auto-hide their URL bar on
scroll. This grows the visual viewport without changing the layout
viewport, so a position:fixed bar pinned to bottom:0 — which is
relative to the layout viewport — appears to float mid-screen with
a gap beneath it. Safari masks the bug because its toolbar shrinks
rather than fully retracting.

Track the delta between the visual and layout viewports via the
VisualViewport API and write it to a --mobile-bar-offset CSS var.
The bar uses translate3d to apply that offset, which both fixes the
gap and enables hardware compositing so it tracks the toolbar
animation without flicker.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 15:38:28 +01:00

243 lines
4.7 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.header {
position: sticky;
top: 0;
z-index: 1000;
background: var(--bg-card, white);
border-bottom: 1px solid var(--border-color, #e5dfd5);
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06);
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 0 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
}
.logo {
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 {
color: var(--accent-coral, #e07256);
}
.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;
letter-spacing: -0.01em;
}
.nav {
display: flex;
gap: 0.5rem;
}
.navLink {
position: relative;
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1rem;
font-size: 0.9375rem;
font-weight: 500;
color: var(--text-secondary, #5c564d);
text-decoration: none;
border-radius: 6px;
transition: all 0.2s ease;
}
/* Sliding underline effect */
.navLink::after {
content: '';
position: absolute;
bottom: 4px;
left: 1rem;
right: 1rem;
height: 2px;
background: var(--accent-coral, #e07256);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.25s ease;
border-radius: 1px;
}
.navLink:hover {
color: var(--text-primary, #1a1612);
background: var(--bg-secondary, #f3ede4);
}
.navLink:hover::after {
transform: scaleX(1);
}
.navLink.active {
color: var(--accent-coral, #e07256);
background: var(--accent-coral-bg);
}
.navLink.active::after {
transform: scaleX(1);
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.25rem;
height: 1.25rem;
padding: 0 0.375rem;
font-size: 0.75rem;
font-weight: 600;
color: white;
background: var(--accent-coral, #e07256);
border-radius: 9999px;
animation: badgePop 0.3s ease-out;
box-shadow: 0 2px 6px rgba(224, 114, 86, 0.4);
}
@keyframes badgePop {
0% {
transform: scale(0.6);
opacity: 0;
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
opacity: 1;
}
}
/* ─── 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;
}
/* Hide the top text nav; the bottom bar takes over */
.nav {
display: none;
}
.logoIcon {
width: 32px;
height: 32px;
}
.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);
/* Compensate for iOS Chrome's auto-hiding URL bar — Navigation.tsx
writes the offset based on the Visual Viewport API. translate3d
(instead of translateY) forces hardware compositing so the bar
doesn't lag/flicker during the toolbar animation. */
transform: translate3d(0, var(--mobile-bar-offset, 0px), 0);
}
}