#blazor-error-ui { color-scheme: light only; background: lightyellow; bottom: 0; box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); box-sizing: border-box; display: none; left: 0; padding: 0.6rem 1.25rem 0.7rem 1.25rem; position: fixed; width: 100%; z-index: 1000; } #blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; } /* Header: pinned to the top while the page scrolls, so the brand and (on mobile) the hamburger button are always reachable. It is a plain block child of (NOT a flex item) -- a stretched flex child fills its cell and never sticks. z-index keeps it above the content; the opaque background stops content showing through as it scrolls underneath. */ .site-header { /* The brand gradient, blue into green -- the strongest, most visible cue that carries the school's colours across every page. Built on --color-header, so it stays deep in dark mode too (see app.css). No accent underline any more: the gradient already ends in green, and the active menu item carries the green stroke. */ background: var(--gradient-brand); box-shadow: var(--shadow); position: sticky; top: 0; z-index: 20; } .header-inner { max-width: var(--content-width); margin: 0 auto; padding: var(--space-3) 1.25rem; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); } /* Signet and two-line lettering side by side. The lettering is a column of its own, so the name and the school line up on the left edge. */ .brand { display: flex; align-items: center; gap: var(--space-2); color: var(--color-on-header); text-decoration: none; white-space: nowrap; } .brand:hover { color: var(--color-on-header); } /* On the blue gradient the global brand-blue focus ring would vanish; white stays visible on both ends of it. */ .brand:focus-visible { outline-color: var(--color-focus-inverse); } /* The signet lives in its own component (Signet.razor), so its carries that component's scope attribute, not this one's; ::deep reaches it from here. */ .brand ::deep .signet { flex-shrink: 0; width: 2.5rem; height: 2.5rem; } .brand-text { display: flex; flex-direction: column; line-height: 1.15; } .brand-name { font-size: 1.35rem; font-weight: var(--font-weight-heading); letter-spacing: var(--letter-spacing-heading); } .brand-school { font-size: 0.85rem; font-weight: 600; } /* The links are pills with their own padding (see app.css), so the gap between them can stay small. */ .main-nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-2); } /* The hamburger button and its state checkbox are hidden on wide screens; the media query below turns them on for narrow ones. display:none (beating the global .visually-hidden the checkbox also carries) takes the checkbox out of the tab order here, where there is no menu to open. */ .nav-toggle, .nav-toggle-label { display: none; } /* The link colours/states for .main-nav a live in the GLOBAL app.css, not here. Reason: NavLink renders plain elements that carry no scoped-CSS attribute (b-xxxxx), so a scoped rule ".main-nav a" would not match them. See app.css. */ /* Content: at least tall enough that the footer sits at the bottom of the viewport on short pages. 100dvh - 5rem leaves room for the header with its signet (which sits above in normal flow); dvh follows the mobile browser's shrinking toolbar. On long pages the content simply grows past this and pushes the footer down naturally. */ .content { min-height: calc(100dvh - 5rem); box-sizing: border-box; width: 100%; max-width: var(--content-width); margin: 0 auto; padding: var(--space-4) 1.25rem var(--space-6); } /* Footer: a dark brand-blue band with light text, so the page visibly ends here. Its own tokens (--color-footer, --color-on-footer...) keep it dark in both colour schemes, like the header. position:relative anchors the wave. */ .site-footer { position: relative; margin-top: var(--space-6); background: var(--color-footer); color: var(--color-on-footer-soft); font-size: 0.95rem; } /* The wave sits just above the footer's top edge (bottom: 100%), drawn over the page background in the footer colour, so the band seems to rise softly instead of starting with a hard line. Fixed height, full width: on a phone it simply flattens out. Overlapped by 1px so no hairline gap shows between wave and band when the browser rounds. */ .footer-wave { position: absolute; bottom: calc(100% - 1px); left: 0; display: block; width: 100%; height: 2.5rem; fill: var(--color-footer); } /* Three columns: brand, quick links, further pages. The brand gets a bit more room because it holds the signet. A missing link column simply leaves its cell empty, so the others keep their place. */ .footer-inner { max-width: var(--content-width); margin: 0 auto; padding: var(--space-5) 1.25rem var(--space-4); display: grid; grid-template-columns: 1.5fr 1fr 1fr; gap: var(--space-4); } .footer-brand { display: flex; align-items: center; align-self: start; gap: var(--space-2); color: var(--color-on-footer); font-weight: 700; } .footer-brand ::deep .signet { flex-shrink: 0; width: 3rem; height: 3rem; } /* Column headings label the lists; they are not a second page heading, so they stay small and quiet. */ .footer-heading { margin: 0 0 var(--space-2); color: var(--color-on-footer); font-size: 0.8rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } .footer-nav ul { list-style: none; margin: 0; padding: 0; } .footer-nav li + li { margin-top: var(--space-1); } .footer-nav a { color: var(--color-on-footer-soft); text-decoration: none; } .footer-nav a:hover { color: var(--color-on-footer); text-decoration: underline; } /* Same reason as the brand link in the header: a light ring on the dark band. */ .footer-nav a:focus-visible { outline-color: var(--color-focus-inverse); } /* Copyright: a thin rule separates it from the columns. The rule spans the content width, not the whole window, so it lines up with the columns above. */ .footer-copyright { box-sizing: border-box; max-width: var(--content-width); margin: 0 auto; padding: var(--space-3) 1.25rem; border-top: 1px solid var(--color-footer-border); font-size: 0.85rem; } /* Mobile: on narrow screens the nav collapses behind a hamburger button. This is pure CSS -- the #nav-toggle checkbox holds the open/closed state and its :checked state reveals the nav. No JavaScript, so it works under static SSR. */ @media (max-width: 40rem) { /* The brand stays on the left, the hamburger goes to the right, and the nav occupies its own full-width row below both. */ .header-inner { flex-wrap: wrap; align-items: center; justify-content: space-between; } /* The checkbox comes back as a real, focusable control -- only visually hidden (global .visually-hidden), so Tab stops on it and Space toggles the menu. It lives under the label, so the focus ring is drawn there instead. */ .nav-toggle { display: block; } .nav-toggle:focus-visible + .nav-toggle-label { outline: 3px solid var(--color-focus-inverse); outline-offset: 2px; border-radius: var(--radius); } /* Show the hamburger button. */ .nav-toggle-label { display: block; cursor: pointer; padding: 0.5rem; margin: -0.5rem; /* keep the tap target large without shifting layout */ } /* The three-bar icon, drawn with one element plus its ::before/::after. */ .nav-toggle-bar, .nav-toggle-bar::before, .nav-toggle-bar::after { display: block; width: 1.5rem; height: 2px; background: var(--color-on-header); content: ""; transition: transform var(--transition), background-color var(--transition); } .nav-toggle-bar::before { transform: translateY(-6px); } .nav-toggle-bar::after { transform: translateY(4px); /* 6px minus the bar's own 2px height */ } /* Open: the hamburger turns into an X. The middle bar fades out and the outer two meet in the middle and cross. Only the icon changes shape; nothing around it moves. The checkbox sits right before the label, hence the + combinator. */ .nav-toggle:checked + .nav-toggle-label .nav-toggle-bar { background-color: transparent; } .nav-toggle:checked + .nav-toggle-label .nav-toggle-bar::before { transform: rotate(45deg); } .nav-toggle:checked + .nav-toggle-label .nav-toggle-bar::after { transform: translateY(-2px) rotate(-45deg); /* back up by its own height */ } /* The nav takes its own row and is hidden until the checkbox is checked. max-height:0 alone would only hide it from the eye: its links would still be Tab stops and still be read out. visibility:hidden takes them out of both. Transitioning visibility delays the switch to hidden until the fold has finished, so closing still animates. Opening uses the transition of the :checked rule below, which leaves visibility out: the links must be Tab stops the moment Space is pressed, not 0.2s later. */ .main-nav { flex-basis: 100%; flex-direction: column; gap: 0; max-height: 0; overflow: hidden; visibility: hidden; transition: max-height 0.2s ease, visibility 0.2s; } /* Checked state: reveal the stacked links. The max-height caps the open menu to the space below the sticky header; a longer list scrolls inside it instead of running off the screen. */ .nav-toggle:checked ~ .main-nav { visibility: visible; transition: max-height 0.2s ease; max-height: 70vh; overflow-y: auto; margin-top: 0.5rem; } /* Mobile per-link spacing for .main-nav a is in the global app.css too, for the same scoped-attribute reason as above. */ /* The footer columns stack: one column, brand on top. */ .footer-inner { grid-template-columns: 1fr; } }