One product, two spaces. A parent should never wonder whether they are looking at their own dashboard or their child's. The mechanism to do this already exists in the code, and it is a single attribute.
This is the same screen, /app/child/[id], the one both actors share. Flip the
switch. Nothing about the markup changes, only which token set is in play and which blocks
the server decided to render.
Nine of twenty four quests complete. Next up is Science, Week 3.
Science ยท Lesson 3
By today Ruby should be around Week 3, roughly eight lessons in. She is at nine.
Forty lessons generated. Have a look through, then approve to make it live for Ruby.
Parent only. A kid never sees this card.
Ruby marked Maths, Week 2 as "a little unsure". Sarah has already sent her a simpler explanation.
Parent room. Light ground, tighter spacing, teal labels, information first.
This is what makes it one brand rather than two products. Every one of these is identical in both rooms, so a parent looking over a shoulder is never disoriented.
Navy, sun, teal, cream, coral. Both rooms draw from the same five. Neither introduces a colour the other does not have.
Nunito throughout, at every weight. The design system bans a second display font, and this proposal does not ask for one. Size and weight shift between rooms. The face does not.
The chunky hard bottom edge is the most recognisable thing in the system. Both rooms keep it exactly: sun fill, navy text, four pixel edge, press down on tap. Only the padding and the corner radius grow in the kid room.
Same wordmark, same sun dot, same position. The only addition is a small room tag beside it so the answer to "where am I" is on screen at all times.
Eleven tokens carry the whole difference. Every app stylesheet is already written against variables, so redefining these repaints the product without touching a single component.
| Token | Parent room | Kid room | Why |
|---|---|---|---|
| Ground | White | Deep navy | The instant, across-the-room signal. You know which space you are in before you read a word |
| Surface | White on white, hairline border | Lifted navy panels | Parent scans a document. A kid moves between objects |
| Accent | Teal | Sun | Teal reads calm and administrative. Sun reads like reward |
| Card radius | 20px | 26px | Softer, friendlier, more toy-like without leaving the system |
| Button padding | 13 / 22 | 17 / 26 | Bigger tap targets for smaller hands |
| Heading size | 23px | 30px | A kid's screen carries one idea. A parent's carries six |
| Heading weight | 800 | 900 | More presence, less density |
| Body size | 14px | 15.5px | Readability for a developing reader |
| Padding and gap | 18 / 14 | 22 / 18 | Room to breathe, fewer things per screen |
| Label size | 11.5px | 12.5px | Small caps still need to be legible at seven years old |
| Room tag | "Parent view" | Child's own name | Names the room in words, not just colour, which also covers colour blindness |
The server already knows which room you are in. AppLayout.astro computes
inChildContext on every request, and today it uses it for exactly one thing: a
banner that says "Viewing as Ruby". The change is to let that same variable reach the CSS.
<!-- AppLayout.astro, the one line that does it --> <body data-mode={inChildContext ? 'child' : 'parent'}> /* app-tokens.css, the two blocks that carry it */ [data-mode="parent"] { --room-bg: #FFFFFF; --room-accent: #0E9384; ... } [data-mode="child"] { --room-bg: #16223C; --room-accent: #FFB703; ... }
I checked whether the stylesheets can actually take it. Across the app pages and styles there are 5,260 uses of CSS variables against 159 hardcoded colours. That is about 97 percent clean, so the swap genuinely repaints the product. The 159 stragglers are a tidy-up pass, not a rebuild. All of this sits in layouts and styles, which is our lane, so it needs no server work and no schema change.
Theming makes the two rooms look different. It does not fix the fact that they are the
same room wearing two coats. /app/child/[id], the lesson and the quest map are
shared routes, and the parent-only controls live inside the kid's own space.
You can see it in the demo. In the kid room, where a parent gets "Review and approve", a kid currently gets a line of text reading "Ask a parent to set up your next term". A dead control sitting exactly where their next lesson should be.
Theming hides that. Moving it fixes it. The second move is to lift generate, approve, adjust start date and the rationale panels out of the child page onto a parent-side overview screen, leaving the child page genuinely child-only. That one is structural, so it needs Nathan.
app-tokens.css, on a karl/room-theming branch. Nothing else touched.