Files
school_compare/mockups/header-map-hero.html
TudorandClaude Opus 4.8 d4d9ae5252
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 14s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 52s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
feat(school-detail): map-blended hero, remove at-a-glance stats
Replace the header's at-a-glance stats row with a location map that sits
atop the hero and blends into the school title. The map is a static,
non-interactive preview (never traps page scroll) with a coral pin; the
whole band — or the inline "View on map ↗" link by the address — opens a
fullscreen, interactive map. Compare floats glassy over the band.

The separate "Location" section (and its nav item) is removed; the map now
lives only in the hero. Schools without lat/long render the header with no
map band, as before.

New: SchoolHeroMap (fullscreen wrapper, forwardRef open handle) +
LeafletHeroMapInner (minimal single-school map with interaction toggle).
Applied to both primary and secondary detail views; dead heroStats/tone/
mapContainer CSS removed (shared .heroStat* card classes kept).

Also drops the orphaned "Latest data" note and does not reintroduce an
Ofsted strip in the hero (it would duplicate the Ofsted section directly
below). Mockup kept at mockups/header-map-hero.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:12:12 +01:00

180 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>School header — “Emerge” map hero (desktop + mobile)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600;9..40,700&family=Playfair+Display:wght@500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<style>
:root{
--bg-primary:#faf7f2; --bg-secondary:#f3ede4; --bg-card:#fff;
--text-primary:#1a1612; --text-secondary:#5c564d; --text-muted:#6d685f;
--accent-coral:#e07256; --accent-coral-dark:#c45a3f; --accent-teal:#2d7d7d;
--border:#e5dfd5; --shadow:0 2px 8px rgba(26,22,18,.06);
--serif:'Playfair Display',Georgia,serif; --sans:'DM Sans',-apple-system,sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:var(--sans);background:var(--bg-primary);color:var(--text-primary);line-height:1.55;padding:40px 20px 120px}
.wrap{max-width:1120px;margin:0 auto}
.pagehead h1{font-family:var(--serif);font-weight:700;font-size:30px;letter-spacing:-.01em}
.pagehead p{color:var(--text-secondary);margin-top:8px;font-size:15px;max-width:74ch}
.optlabel{display:flex;align-items:center;gap:10px;font-size:12px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--accent-teal);margin:52px 0 6px}
.optdesc{color:var(--text-muted);font-size:14px;margin-bottom:18px;max-width:74ch}
.stage{display:flex;gap:44px;align-items:flex-start;flex-wrap:wrap}
.col-desktop{flex:1 1 560px;min-width:420px}
.col-mobile{flex:0 0 auto}
.caption{font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-muted);margin-bottom:12px}
.topBack{display:inline-flex;align-items:center;gap:.4rem;margin:0 0 12px;font-size:17px;font-weight:600;color:var(--accent-coral)}
/* ── hero shell ── */
.hero{position:relative;background:var(--bg-card);border:1px solid var(--border);border-radius:14px;box-shadow:var(--shadow);overflow:hidden}
.map{width:100%;background:#dfe6e2}
.map .leaflet-control-attribution{font-size:9px;background:rgba(255,255,255,.55)}
/* coral teardrop pin */
.pin{position:relative;width:26px;height:26px}
.pin svg{filter:drop-shadow(0 3px 4px rgba(0,0,0,.35))}
.pin .ring{position:absolute;left:50%;top:72%;width:34px;height:34px;transform:translate(-50%,-50%);border-radius:50%;background:rgba(224,114,86,.28);animation:pulse 2.4s ease-out infinite}
@keyframes pulse{0%{transform:translate(-50%,-50%) scale(.5);opacity:.9}100%{transform:translate(-50%,-50%) scale(1.6);opacity:0}}
.compareBtn{position:absolute;z-index:500;top:14px;right:14px;font-size:13px;font-weight:600;padding:9px 15px;border-radius:8px;cursor:pointer;border:none;
background:rgba(255,255,255,.88);color:var(--accent-coral);backdrop-filter:blur(6px);box-shadow:0 2px 10px rgba(0,0,0,.16)}
/* the diffuse blend: tall gradient that reaches solid white well before the title */
.fade{position:absolute;left:0;right:0;z-index:400;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) 100%)}
.body{position:relative;z-index:410}
.name{font-family:var(--serif);font-weight:700;line-height:1.08;letter-spacing:-.01em}
.meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.chip{background:var(--bg-secondary);color:var(--text-secondary);font-size:13px;font-weight:500;padding:4px 11px;border-radius:6px}
.addr{display:flex;align-items:center;flex-wrap:wrap;gap:7px;color:var(--text-secondary);font-size:15px;margin-top:14px}
.addr .maplink{color:var(--accent-coral);font-weight:600;text-decoration:none;white-space:nowrap}
.details{display:flex;flex-wrap:wrap;gap:6px 22px;margin-top:14px;font-size:14px;color:var(--text-secondary)}
.details a{color:var(--accent-teal);font-weight:600;text-decoration:none}
.details b{color:var(--text-primary)}
.ofstedStrip{display:inline-block;margin-top:16px;padding:9px 14px;border-radius:8px;background:rgba(45,125,125,.10);border-left:3px solid var(--accent-teal)}
.ofstedStrip b{font-size:14px}.ofstedStrip span{display:block;font-size:12px;color:var(--text-muted)}
/* desktop sizing */
.col-desktop .map{height:210px}
.col-desktop .fade{top:70px;height:190px} /* white by ~map bottom (210) */
.col-desktop .body{margin-top:-6px;padding:0 26px 22px}
.col-desktop .name{font-size:clamp(30px,3.6vw,44px)}
/* mobile phone frame */
.phone{width:390px;border:10px solid #1a1612;border-radius:38px;overflow:hidden;box-shadow:0 18px 50px rgba(26,22,18,.22);background:var(--bg-primary)}
.phone .notch{height:26px;background:#1a1612;position:relative}
.phone .notch::after{content:"";position:absolute;left:50%;top:7px;transform:translateX(-50%);width:120px;height:20px;background:#1a1612;border-radius:0 0 14px 14px}
.phone .globalnav{height:46px;background:#fff;border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 16px;font-family:var(--serif);font-weight:700;font-size:17px}
.phone .screen{padding:14px 12px 26px}
.phone .hero .map{height:150px}
.phone .hero .fade{top:52px;height:132px} /* white by ~map bottom (150) */
.phone .hero .body{margin-top:-4px;padding:0 18px 18px}
.phone .hero .name{font-size:29px}
.phone .hero .compareBtn{top:12px;right:12px;padding:8px 12px;font-size:12.5px}
.phone .addr{font-size:14px}
.phone .details{font-size:13px;gap:4px 16px}
</style>
</head>
<body>
<div class="wrap">
<div class="pagehead">
<h1>“Emerge” map hero — refined</h1>
<p>Transition made more diffuse: the map dissolves gradually and reaches solid white before the school name, so the title sits cleanly on white with only a soft memory of the map above it. Redundant floating pill removed — the only map CTA is the inline “View on map ↗”. Desktop and mobile shown side by side.</p>
</div>
<div class="optlabel">Option A · “Emerge” (refined)</div>
<p class="optdesc">Same school throughout (Our Lady Queen of Heaven, SW19&nbsp;6AD). Live, non-interactive preview maps; the CTA opens the full map.</p>
<div class="stage">
<!-- ── DESKTOP ── -->
<div class="col-desktop">
<div class="caption">Desktop</div>
<a class="topBack">← Back</a>
<div class="hero" data-hero>
<button class="compareBtn">+ Add to Compare</button>
<div class="map" id="mapD"></div>
<div class="fade"></div>
<div class="body">
<h1 class="name">Our Lady Queen of Heaven RC School</h1>
<div class="meta"><span class="chip">Wandsworth</span><span class="chip">Voluntary aided school</span></div>
<div class="addr">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#e07256" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg>
Victoria Drive, Southfields, London, SW19 6AD &nbsp;·&nbsp; <a class="maplink" data-open>View on map ↗</a>
</div>
<div class="details">
<span><b>Headteacher:</b> Mr Jeremy Tuke</span>
<a>School website ↗</a>
<span><b>Pupils:</b> 221 (capacity: 232)</span>
</div>
<div class="ofstedStrip"><b>Ofsted Outstanding</b><span>Inspected November 2023</span></div>
</div>
</div>
</div>
<!-- ── MOBILE ── -->
<div class="col-mobile">
<div class="caption">Mobile</div>
<div class="phone">
<div class="notch"></div>
<div class="globalnav">SchoolCompare</div>
<div class="screen">
<a class="topBack" style="font-size:16px">← Back</a>
<div class="hero" data-hero>
<button class="compareBtn">+ Compare</button>
<div class="map" id="mapM"></div>
<div class="fade"></div>
<div class="body">
<h1 class="name">Our Lady Queen of Heaven RC School</h1>
<div class="meta"><span class="chip">Wandsworth</span><span class="chip">Voluntary aided</span></div>
<div class="addr">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#e07256" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg>
Victoria Drive, Southfields, SW19 6AD
</div>
<div style="margin-top:8px"><a class="maplink" data-open style="color:var(--accent-coral);font-weight:600;text-decoration:none;font-size:14px">View on map ↗</a></div>
<div class="details">
<span><b>Headteacher:</b> Mr Jeremy Tuke</span>
<a>School website ↗</a>
<span><b>Pupils:</b> 221 (cap. 232)</span>
</div>
<div class="ofstedStrip"><b>Ofsted Outstanding</b><span>Inspected November 2023</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
<p class="optdesc" style="margin-top:26px"><b style="color:var(--text-secondary)">No-location fallback</b> — schools without lat/long simply render the hero with no map band (title at the top as today), so the component degrades cleanly.</p>
</div>
<script>
const LAT = 51.4459, LNG = -0.2076;
const pinIcon = L.divIcon({
className:'', iconSize:[26,34], iconAnchor:[13,30],
html:'<div class="pin"><span class="ring"></span><svg width="26" height="34" viewBox="0 0 26 34"><path d="M13 0C5.8 0 0 5.8 0 13c0 9.2 13 21 13 21s13-11.8 13-21C26 5.8 20.2 0 13 0Z" fill="#e07256"/><circle cx="13" cy="13" r="5" fill="#fff"/></svg></div>'
});
function makePreview(id){
const m = L.map(id,{zoomControl:false,attributionControl:true,dragging:false,scrollWheelZoom:false,doubleClickZoom:false,boxZoom:false,keyboard:false,touchZoom:false}).setView([LAT,LNG],15);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19,attribution:'&copy; OpenStreetMap'}).addTo(m);
L.marker([LAT,LNG],{icon:pinIcon}).addTo(m);
setTimeout(()=>m.invalidateSize(),60);
}
['mapD','mapM'].forEach(makePreview);
document.querySelectorAll('[data-open]').forEach(el=>el.addEventListener('click',e=>{e.preventDefault();alert('→ opens the full interactive map');}));
</script>
</body>
</html>