diff --git a/nextjs-app/components/AdmissionsView.module.css b/nextjs-app/components/AdmissionsView.module.css index 6460ee6..9cafddb 100644 --- a/nextjs-app/components/AdmissionsView.module.css +++ b/nextjs-app/components/AdmissionsView.module.css @@ -1,7 +1,83 @@ -.page { - max-width: 900px; +.shell { + max-width: 1120px; margin: 0 auto; padding: 0 1.25rem 4rem; + display: grid; + grid-template-columns: 160px minmax(0, 1fr); + gap: 2.5rem; + align-items: start; +} + +.page { + max-width: 900px; + width: 100%; + justify-self: center; +} + +/* ─── In-page nav rail ────────────────────────────────── */ + +.nav { + position: sticky; + top: 1.5rem; + padding-top: 3.75rem; +} + +.navLabel { + font-size: 0.65rem; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-muted, #6d685f); + margin-bottom: 0.85rem; + padding-left: 0.85rem; +} + +.navList { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.1rem; + border-left: 2px solid var(--border-color, #e5dfd5); +} + +.navLink { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.45rem 0.85rem; + margin-left: -2px; + border-left: 2px solid transparent; + font-size: 0.88rem; + font-weight: 500; + color: var(--text-secondary, #5c564d); + text-decoration: none; + cursor: pointer; + transition: color 0.15s ease, border-color 0.15s ease; +} + +.navLink:hover { + color: var(--text-primary, #1a1612); +} + +.navLinkActive { + color: var(--accent-teal, #2d7d7d); + font-weight: 700; + border-left-color: var(--accent-teal, #2d7d7d); +} + +.navDot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--border-color, #e5dfd5); + flex-shrink: 0; + transition: background 0.15s ease; +} + +.navLinkActive .navDot { + background: var(--accent-teal, #2d7d7d); } /* ─── Hero ───────────────────────────────────────────── */ @@ -396,6 +472,60 @@ /* ─── Responsive ─────────────────────────────────────── */ +@media (max-width: 960px) { + .shell { + grid-template-columns: 1fr; + gap: 0; + padding-top: 0; + } + + .nav { + position: sticky; + top: 0; + padding-top: 0; + background: var(--bg-primary, #faf8f3); + border-bottom: 1px solid var(--border-color, #e5dfd5); + margin: 0 -1.25rem 1rem; + padding: 0.6rem 1.25rem; + z-index: 10; + } + + .navLabel { + display: none; + } + + .navList { + flex-direction: row; + gap: 0.35rem; + border-left: none; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .navList::-webkit-scrollbar { + display: none; + } + + .navLink { + padding: 0.4rem 0.9rem; + margin-left: 0; + border-left: none; + border-radius: 999px; + background: transparent; + white-space: nowrap; + } + + .navLinkActive { + background: var(--text-primary, #1a1612); + color: #fff; + border-left: none; + } + + .navLinkActive .navDot { + background: #fff; + } +} + @media (max-width: 768px) { .heroTitle { font-size: 2rem; diff --git a/nextjs-app/components/AdmissionsView.tsx b/nextjs-app/components/AdmissionsView.tsx index a63ce86..c4d5204 100644 --- a/nextjs-app/components/AdmissionsView.tsx +++ b/nextjs-app/components/AdmissionsView.tsx @@ -144,15 +144,79 @@ const TIPS: Tip[] = [ /* ─── Component ────────────────────────────────────────── */ +const NAV_ITEMS = [ + { id: 'primary', label: 'Primary' }, + { id: 'secondary', label: 'Secondary' }, + { id: 'tips', label: 'Tips' }, +] as const; + +type NavId = typeof NAV_ITEMS[number]['id']; + export function AdmissionsView() { const [chipDays, setChipDays] = useState<(number | null)[]>(CHIPS.map(() => null)); + const [activeId, setActiveId] = useState('primary'); useEffect(() => { setChipDays(CHIPS.map(c => daysUntil(c.month, c.day))); }, []); + useEffect(() => { + const sections = NAV_ITEMS + .map(item => document.getElementById(item.id)) + .filter((el): el is HTMLElement => el !== null); + + if (sections.length === 0) return; + + const observer = new IntersectionObserver( + (entries) => { + const visible = entries + .filter(e => e.isIntersecting) + .sort((a, b) => b.intersectionRatio - a.intersectionRatio); + if (visible[0]) setActiveId(visible[0].target.id as NavId); + }, + { rootMargin: '-30% 0px -55% 0px', threshold: [0, 0.25, 0.5, 0.75, 1] }, + ); + + sections.forEach(s => observer.observe(s)); + return () => observer.disconnect(); + }, []); + + const handleNavClick = (e: React.MouseEvent, id: NavId) => { + e.preventDefault(); + const el = document.getElementById(id); + if (!el) return; + const top = el.getBoundingClientRect().top + window.scrollY - 16; + window.scrollTo({ top, behavior: 'smooth' }); + setActiveId(id); + }; + return ( -
+
+ + {/* In-page nav — sticky left rail on desktop, sticky top pills on mobile */} + + +
{/* Hero */}
@@ -198,45 +262,8 @@ export function AdmissionsView() {
- {/* Secondary track */} -
-
-
- Year 7 entry -

Secondary school admissions

-

For children starting secondary school (Year 7) in September. Applications are submitted in the autumn of Year 6.

-
-
-
- Deadline - {fmtDate(10, 31)} -
-
- Offer Day - {fmtDate(3, 1)} -
-
-
- -
    - {SECONDARY_STEPS.map((step, i) => ( -
  1. -
    -
    - {i < SECONDARY_STEPS.length - 1 &&
    } -
    -
    - {step.date &&
    {step.date}
    } -
    {step.title}
    -

    {step.body}

    -
    -
  2. - ))} -
-
- {/* Primary track */} -
+
Reception entry @@ -272,8 +299,45 @@ export function AdmissionsView() {
+ {/* Secondary track */} +
+
+
+ Year 7 entry +

Secondary school admissions

+

For children starting secondary school (Year 7) in September. Applications are submitted in the autumn of Year 6.

+
+
+
+ Deadline + {fmtDate(10, 31)} +
+
+ Offer Day + {fmtDate(3, 1)} +
+
+
+ +
    + {SECONDARY_STEPS.map((step, i) => ( +
  1. +
    +
    + {i < SECONDARY_STEPS.length - 1 &&
    } +
    +
    + {step.date &&
    {step.date}
    } +
    {step.title}
    +

    {step.body}

    +
    +
  2. + ))} +
+
+ {/* Tips */} -
+

Three things most parents get wrong

{TIPS.map((tip, i) => ( @@ -286,6 +350,7 @@ export function AdmissionsView() {
+
); }