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
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>
243 lines
4.7 KiB
CSS
243 lines
4.7 KiB
CSS
.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);
|
||
}
|
||
}
|