# M3 — the shell, and the home page inside it (STOP 1) Built 2026-10-11 on `felhom.eu` `7413e0dfab` + 2 (the brief's baseline plus the R-899 close and the mockup ruling). `felhom-controller` `dcd89364a4`, **read only**. **Live: ** — `noindex`, blocked by `robots.txt`, no sitemap entry, no link from a live page. The live site is untouched. ## Part A — the shell `website/assets/shell.css`: **117 rules copied verbatim** from the controller's `style.css`, plus **exactly one adapted rule** that says so (`body.shell { display:flex; min-height:100vh }`, which lives in the controller's base rather than in a component). The generator names every rule it copies and **refuses to write the file if one has disappeared upstream**. That refusal already earned itself twice in this session: - `.sidebar::-webkit-scrollbar` does not exist upstream (only the `-track` override does); - `prefers-reduced-motion` is split across **five separate blocks** in the controller, and reading only the first would have silently dropped four rules. The bare `body { flex-direction: column }` that broke M1's phone layout is copied this time, by name, along with `body.nav-open` and the `no-js`/`js` pair. ## The thing that was wrong, and how it was found `shell.css` loads after `site.css`, but **"later wins" is per property, not per rule.** Where `site.css` declares something the controller's rule does not, the website's value survives inside the shell. Diffing the two files' declaration sets per selector found **exactly six** such declarations: | Selector | Leaking | Effect | |---|---|---| | `.nav-links` | `display: flex`, `gap: 32px` | the sidebar menu rendered as a **horizontal row** spilling across the content column | | `.lang-globe-menu` | `top`, `right` | fought the controller's `bottom`/`left`, so the globe opened the wrong way | | `.page-header` | `padding`, `text-align` | the website's centred subpage header fought the controller's left-aligned flex row | Reset in `site.css` (the website's own file), so `shell.css` stays a verbatim copy. Only the first was visible on the page; the other four were found by diffing, not by looking. ## Measured on the deployed page | | current live `/` | `/preview/` | **`/preview/m3/`** | |---|---:|---:|---:| | Requests, first load | 13 | 12 | **21** | | Weight, first load | 308 KB | 270 KB | **285 KB** | | HTML document | 35 KB | 26 KB | **24 KB** | | Height, 1440 px | 10 967 px | 6 385 px | **4 181 px** | | Height, 390 px | 19 913 px | 10 145 px | **7 530 px** | | Third-party hosts | 0 | 0 | **0** | | Horizontal overflow at 390 px | none | none | **none** | The extra requests are `shell.css` and the four app logos in the hero window. The page is still lighter than today's live home page and **less than half its height**. **Contrast: no failures.** Everything measured with translucent ancestors composited. Two rules copied from the controller did fail at first and were fixed: - `.btn-primary` 4.01:1 (white on `--blue`, 13.6px/600) — **R-930 arriving a second time by another route.** Fixing `.cta-button` on the website did not fix the *controller's* button, and the shell imports it. Same remedy the operator chose for R-930: 1.2rem/700 puts it in WCAG's large-text band, where 3:1 applies and 4.01 clears, without touching `--blue`. - `.nav-group-label` 3.14:1 — moved to `--text-2`. The sidebar's own call to action is an **outline** button, not a filled one: blue on transparent measures 6.12:1, and a 19.2px filled button does not fit a 240 px sidebar without wrapping. Measured both ways. **The controller has both defects on its own surface** — filed, not fixed (read-only). **Phone:** top bar and burger appear below 768 px, the drawer opens and closes, the backdrop shows, `body.nav-open` is set, Escape closes it and returns focus to the burger. Focus-visible and reduced-motion rules both present. > **A measurement trap worth recording.** In a background tab, Chrome freezes CSS transitions *and* > `requestAnimationFrame`. The drawer read as permanently shut — `is-open` applied, the right rule > matching with the right specificity, and the transform stuck at `translateX(-100%)` — and even an > inline `transform: none` would not move it. Nothing was wrong with the page. Setting > `transition: none` before measuring showed the real end state: closed at −280, open at 0. A > `requestAnimationFrame` await in the same probe froze the whole call for 45 s. ## Four changes from the operator's mockup, each with its reason 1. **The active item follows the page.** The mockups show „Indítópult" active on every page. 2. **„Jelentkezem tesztelőnek" is also a button** at the top of the lower group. It is the one action we want, and the mockup buries it at the bottom of a list. 3. **The „Alkalmazások" categories are anchors on `/alkalmazasok`** — real URLs, no JS-only state, so they work with JavaScript off and a crawler can follow them. 4. **GYIK was added.** It is in today's nav, and dropping it silently would take a page out of navigation. That is not a change a restyle gets to make. ## The home page Six sections, the controller's page header and cards, left-aligned headers. Content is `/preview/`'s cleaned text, so no new claim and no em dash. ``, meta description, canonical and hreflang are **already the final values**, so the switch in Part D needs no head edit. The compact „who is behind it" row sits by the closing button, as before. `body` carries `shell page-index`, which reuses the existing `.page-index` content rules (`caps`, `flow`, `vs`, `band`, `who-mini`) rather than duplicating them. ## Still to do after STOP 1 Every other Hungarian page, then the English twins (Part C), then the whole-site switch on „go" (Part D). Nothing goes live before that.