Design proposal, 19 July 2026

Same brand, different room.

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.

Try it

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.

Lightkeepers
Parent view
Term 3 ยท Ruby

Ruby is two lessons ahead this week.

Nine of twenty four quests complete. Next up is Science, Week 3.

9 of 24 complete 37%
Today

Why do volcanoes erupt?

Science ยท Lesson 3

Your badges

Two more to earn this week

๐ŸŒ‹
๐Ÿ”ญ
๐Ÿฆ•
๐ŸŒฑ
๐Ÿ”ฅ 6 day streak โญ 90 XP
Your trail

Science

โœ“ Rocks and layers Done
3 Why volcanoes erupt Next up
4 Islands from fire Locked
Pace

On track

By today Ruby should be around Week 3, roughly eight lessons in. She is at nine.

E English 3 of 3 this week
M Maths 2 of 3 this week
S Science 2 of 3 this week
Needs you

Term 4 is ready for review

Forty lessons generated. Have a look through, then approve to make it live for Ruby.

Parent only. A kid never sees this card.

This week

One lesson worth a look

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.

What never changes

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.

The palette

Navy, sun, teal, cream, coral. Both rooms draw from the same five. Neither introduces a colour the other does not have.

The typeface

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 button

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.

The logo and the mark

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.

What changes

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.

TokenParent roomKid roomWhy
GroundWhiteDeep navyThe instant, across-the-room signal. You know which space you are in before you read a word
SurfaceWhite on white, hairline borderLifted navy panelsParent scans a document. A kid moves between objects
AccentTealSunTeal reads calm and administrative. Sun reads like reward
Card radius20px26pxSofter, friendlier, more toy-like without leaving the system
Button padding13 / 2217 / 26Bigger tap targets for smaller hands
Heading size23px30pxA kid's screen carries one idea. A parent's carries six
Heading weight800900More presence, less density
Body size14px15.5pxReadability for a developing reader
Padding and gap18 / 1422 / 18Room to breathe, fewer things per screen
Label size11.5px12.5pxSmall caps still need to be legible at seven years old
Room tag"Parent view"Child's own nameNames the room in words, not just colour, which also covers colour blindness

The hinge

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.

The catch worth deciding on

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.

What I would do next

  1. Build the two token sets for realIn app-tokens.css, on a karl/room-theming branch. Nothing else touched.
  2. Sweep the 159 hardcoded coloursAnything that does not respond to the swap will look broken in one room. This is the unglamorous half.
  3. Wire the room tag and the exit controlThe existing "Viewing as Ruby" banner becomes the room's own header rather than a strip pasted on top.
  4. Screenshot every app screen in both roomsCheapest way to find where the theme leaks. Roughly twenty screens, forty shots.
  5. Then raise the route split with NathanOnce the theming is visibly working, the argument for moving the parent controls makes itself.