# Could the website and the controller share one design? — measured, then answered **Report only. Nothing in this document was built.** Written 2026-10-11 while building the M1/M2 mockups, which is the only reason the numbers below are measurements rather than estimates. Baselines: `felhom.eu` `0c86e698b0`, `felhom-controller` `dcd89364a4` (**read only**), `app-catalog-felhom.eu` `8ea72aa`. --- ## 1. What M1 actually reused `website/preview/mockup.css` is deliberately three labelled blocks so this could be counted: | Block | Rules | Size | What it is | |---|---:|---:|---| | 1. BASE, **adapted** | 8 | 2.5 KB | The controller's reset, tokens and `@font-face`, with the font URLs repointed `/static/fonts/` → `/assets/fonts/` | | 2. CONTROLLER, **verbatim** | **188** | 19.4 KB | Copied unchanged out of `style.css` | | 3. MOCKUP-ONLY, **new** | 64 | 7.2 KB | What neither product has an answer for | | **Total** | **260** | **29.1 KB** | | So of the 260 rules M1 needs, **188 (72%) are an unchanged copy** and only **64 (25%) are new**. The controller's stylesheet has **826 rules**; M1 needed 188 of them, or **23%**. The rest is storage, deploy wizards, monitoring, restore flows — surfaces a marketing site has no use for. **The 18 design tokens are already identical.** Verified this session by parsing both `:root` blocks: 18 names in each, **zero differing values, none missing on either side**. The brief said so and it is true. What the 64 new rules are actually for: the welcome card, the `minta` badge, the disabled-menu-item treatment, the `:target` app panel, and M2's browser-window frame. Every one of them exists because a *website* needs something a *dashboard* does not — none is a restyling of a controller component. **Honest caveat on the 188.** Only **129 of the 186** class rules shipped (69%) match a class the mockup markup actually uses; 57 came along because my extractor matched on selector prefix. A hand-curated copy would be smaller. The 72% reuse figure is about *how much fits*, not about how tight the file is. ## 2. The finding that matters more than the percentage **A component-level copy silently drops the element-level rules those components depend on.** The extractor keys on class names. It copied every `.sidebar`, `.content`, `.mobile-topbar` and `.nav-burger` rule, including the whole `@media(max-width: 768px)` block — and silently missed this: ```css @media (max-width: 768px) { body { flex-direction: column; } } ``` One bare element rule. Without it, measured at 390 px: the sticky mobile topbar stayed a **row** flex item, pushed `.content` 135 px to the right, collapsed the launcher grid to **one column**, made the front page **15 597 px** tall on a phone, and gave two pages a `scrollWidth` of 467 in a 375 px viewport. Everything *looked* copied. Nothing said it was incomplete. A second instance in the same build: the controller's off-canvas drawer is gated on a `js` class that `layout.html` sets in an inline head script. The CSS was all there; the hook was not, so the drawer never engaged. **Both failures are invisible to a diff and invisible to review.** They are the argument for a mechanism over a convention, and they are why I would not recommend "copy the components when you need them" as the long-term answer. ## 3. The two mechanisms, with cost and risk ### (a) One `felhom-ui.css`, owned in `documentation/design/`, copied into both repos, with a gate The pattern this project already uses for things that must stay identical in two places. - **Cost.** Write the shared file once by pulling the common components out of both stylesheets (realistically a day, because the two have drifted in ways nobody has catalogued). Then a gate in each repo comparing its copy to the canonical one — the same shape as the existing copy-drift gates, so roughly an afternoon including its decoy. - **Risk.** Low and *visible*. A drift turns a gate red on the next push, naming the file. The failure mode is an annoying red gate, not a silently broken phone layout. - **What it does not solve.** Someone still has to decide what belongs in the shared file. Put too little in and §2 happens again; put too much in and a controller-only change starts failing the website's gate for no reason. ### (b) Serve the controller's `style.css` to the website as the source - **Cost.** Near zero to start: one build step copying the file in. - **Risk.** High, and the risks are the interesting ones: - **It inverts ownership.** The marketing site's appearance becomes a side effect of a controller release. A controller change made for a deploy wizard can alter the home page of the public site without anyone intending it, between two releases nobody reviewed for that. - **Weight.** The controller's stylesheet is **826 rules**; the website would carry all of them to use 23%. On a site that currently serves **nothing compressed at all** (R-931), that is a real cost on every page, not a theoretical one. - **Different release cadences.** The controller ships several times a day; the website is git-synced from `main` in a minute. They would be coupled at the speed of the faster one. ### Which I would pick **(a), and not yet.** (a) is right in shape: a canonical file plus a gate is the only one of the two where a divergence *announces itself*, and §2 is a measured demonstration that divergence does not announce itself otherwise. But I would **not build it until the operator has chosen a direction**, because the question (a) answers only exists if the website keeps wearing the dashboard's clothes. If M1 is rejected, the website needs about a dozen shared atoms (button, tag, card, alert) and a shared stylesheet would be a mechanism built for a problem that went away. If M1 is chosen, (a) stops being an improvement and becomes **necessary** — at that point the website *is* a second renderer of the controller's design, and keeping two hand-synced copies of 188 rules is exactly the arrangement that produced §2. **A third option worth naming, which I would pick over (b):** keep the tokens shared (they already are, and they are the half that never drifts) and let the components diverge on purpose. That is today's arrangement, and it has cost nothing so far — because until this week nothing tried to show the same component in both places. ## 4. What breaks if the controller's look changes Under (a) or (b), the website follows the controller automatically. That cuts both ways: - **Good.** A customer who sees the website and then their own box sees one product. Today they see two designs that merely share colours. That is the whole case for M1. - **Bad, and specific.** The controller's UI is driven by what a *household operating a box* needs, and it changes for operational reasons several times a day. A denser `.launch-cell` for boxes with 40 apps would silently re-flow the public front page. A new state colour added for a restore wizard would appear in the website's palette. A controller release that narrows the sidebar changes the public site's navigation width. - **The asymmetry to be explicit about:** the controller has a known audience of a handful of test households and can be changed freely. The website is the public face and carries SEO and first-impression weight. Coupling them means the *low-stakes* surface sets the pace for the *high-stakes* one. Any shared-design mechanism needs a rule for that, and the simplest is that the shared file holds only components both surfaces actually render — which is the discipline (a) enforces and (b) does not.