Files
TudorandClaude Fable 5 d52d384cf2
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m39s
PR Checks / Backend Smoke (pull_request) Successful in 5s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 47s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 9s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 4m15s
fix(school): make hero map fullscreen work on iOS Safari
iOS Safari has no Element.requestFullscreen (fullscreen is video-only),
so tapping the map band or 'View on map' silently did nothing on
iPhones. Fall back to a fixed-position CSS overlay driven by state when
the Fullscreen API is missing or its promise rejects, locking body
scroll while open. The Leaflet map already re-measures via the shared
isFullscreen flag. New e2e journey simulates the iOS condition by
deleting the API and asserts the overlay opens and closes; it fails
against the current production build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:34:22 +01:00

145 lines
3.9 KiB
CSS

/* Preview band that blends into the hero title; expands to fullscreen.
isolation:isolate contains the map + fade + open-button z-indices (which
must beat Leaflet's internal panes) so they don't leak out and outrank the
header's Compare button. */
.wrapper {
position: relative;
width: 100%;
height: 210px;
background: #dfe6e2;
isolation: isolate;
}
@media (max-width: 640px) {
.wrapper {
height: 150px;
}
}
/* Preview: move the OSM attribution off the bottom edge, where the school
title slides up under the fade — top-left is the band's only quiet corner
(Compare floats top-right). Fullscreen keeps Leaflet's default bottom-right,
since the zoom control occupies the top-left there. */
.wrapper:not([data-fullscreen]) :global(.leaflet-bottom.leaflet-right) {
top: 0;
left: 0;
right: auto;
bottom: auto;
}
/* Fullscreen: the Fullscreen API promotes this element to fill the viewport. */
.wrapper[data-fullscreen] {
height: 100vh;
height: 100dvh;
background: #fff;
}
/* Fallback fullscreen (iOS Safari — no Element.requestFullscreen): the API
can't promote the element, so pin it over the page ourselves. Above the
comparison toast (3000) and everything else except modals (9999+). */
.wrapper[data-fs-fallback] {
position: fixed;
inset: 0;
z-index: 5000;
}
.skeleton {
width: 100%;
height: 100%;
background:
linear-gradient(100deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, .5) 50%, rgba(255, 255, 255, 0) 60%) #e7e2da;
background-size: 200% 100%;
animation: shimmer 1.4s infinite;
}
@keyframes shimmer {
to { background-position: -200% 0; }
}
/* Full-band click target. Transparent so the map shows through; reveals a
hint pill on hover/focus. Sits above the map but below the header's fade
and Compare button (which carry higher stacking in the header). */
.openBtn {
position: absolute;
inset: 0;
/* Above Leaflet's marker pane (600) so the whole band — pin included — is
one click target. Transparent, so map + pin still show through. */
z-index: 800;
width: 100%;
height: 100%;
padding: 0;
border: 0;
background: transparent;
cursor: pointer;
display: flex;
align-items: flex-end;
justify-content: center;
}
.openHint {
display: inline-flex;
align-items: center;
gap: 7px;
margin-bottom: 16px;
padding: 8px 14px;
border-radius: 999px;
font-size: 13px;
font-weight: 600;
color: var(--text-primary, #1a1612);
background: rgba(255, 255, 255, .85);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
box-shadow: 0 2px 10px rgba(0, 0, 0, .16);
opacity: 0;
transform: translateY(4px);
transition: opacity .16s ease, transform .16s ease;
}
.openBtn:hover .openHint,
.openBtn:focus-visible .openHint {
opacity: 1;
transform: none;
}
/* Diffuse fade: map dissolves to the card background before the title. */
.fade {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 33%;
/* Above Leaflet's tile (200) and overlay (400) panes so the gradient is
actually visible, but below the marker pane (600) so the pin stays crisp
on top of the blend. */
z-index: 450;
pointer-events: none;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, .35) 35%,
rgba(255, 255, 255, .75) 62%,
rgba(255, 255, 255, .95) 82%,
var(--bg-card, #fff) 100%);
}
.closeBtn {
position: absolute;
top: 0.75rem;
right: 0.75rem;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: none;
border-radius: 8px;
background: rgba(255, 255, 255, .92);
color: var(--text-primary, #1a1612);
cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, .2);
}
.closeBtn:hover {
background: #fff;
}