Inspiration

"Okay… it's bedtime." "Just one more episode!" That exchange happens in my living room most nights, with my own kids, and in a lot of other homes. Three in four parents of 5–10-year-olds say TV is part of their child's bedtime routine (Owens et al., 1999), and some keep a TV in the room to help the child fall asleep (Kaiser Family Foundation, 2006). Their intent is right. The screen is wrong: it's the brightest, bluest thing in the house, it autoplays the next episode, and ending it is the fight.

I didn't want another app that tells parents to take the TV away. The TV is already in the bedtime slot, so I asked the opposite question: what if the TV put them to bed? Every other show keeps your child up. This one puts them to bed.

What it does

A grown-up sets it up once. After that, a child runs bedtime with one button, and the TV gives its light back step by step.

  • Set up once, by a grown-up. The family's steps (teeth, pajamas, the big light, a story, cuddle and goodnight, and more if you want), how many minutes each takes, and when bedtime starts. Tonight's sound is a mix you hear while you choose: rain, ocean, forest, campfire, a music box. Or ask in your own words, like "rain on a tent with a little stream", and Amazon Nova mixes it from real field recordings.
  • Bear lights a candle. It is sized to tonight's routine and burns down in real time. It is the evening's light and its clock, so there are no countdowns and no numbers on screen.
  • Real steps, in real rooms. Amy, Bear's voice, tells the child what to do ("Little circles, nice and slow"). A soft ring shows how long. The child presses We're done, and a spark leaps from the flame and flies up to become a star. There is nothing to read, so a child who can't read yet can follow it.
  • The big light goes off. "Grown-up, can you switch off the big light?" The lamp in the picture goes out at the same moment, and the candle becomes the only light.
  • A story in Amy's voice. No words on screen. The room falls asleep around it, and the story slows the child's breathing with them ("A big breath in… and a long, slow breath out").
  • Blow out the candle. Three candle breaths, then the child presses Blow out the candle: "phff", a thread of smoke, "Goodnight, little one." Then black. Only tonight's sound plays on, fading out after 20 minutes if the grown-up chose that.

Measured on the app's own frames, the screen dims from 47% to 6.5% light, then off, and warms from 6500 K to 1900 K.

How we built it

Part What it is
TV app React Native for Vega 0.83 (Vega SDK 0.24): the whole evening as one async script, with Pressable + TVFocusGuideView focus. Built and recorded on the Vega Virtual Device; spot checks on a Fire TV Stick (Vega OS 2)
Light One curve drives everything: a black veil, an amber tint, the candle's glow and the darkness gathering away from it
Candle Drawn in code (SVG flame, gradient wax, a soft glow). The flicker is a pre-computed loop on the native animation driver: no JS per frame, ≤ 8% brightness change, slow enough to be photosensitive-safe
Sound Two raw-PCM streams through keplerscript-audio-lib (voice, and a bed that mixes up to 5 of 16 layers: field-recording loops plus noises generated on the TV)
Voice Amazon Polly (generative voice Amy). Every line is recorded once by a small hub on the home network and cached, so bedtime never waits on the network
Sounds in your words Amazon Nova 2 Lite turns the parent's words into a mix of the real layers (validated, one retry, an offline word-matching fallback in the app)
Art Storybook scenes made with an image model from my own design boards, an original character I directed; light and motion layered on top in code

The light curve: with $k$ = how far the candle has burned (0 → 1),

$$c(k) = 0.55\,k^2(3-2k) + 0.45\,k \qquad \text{veil} = 0.06 + 0.5\,c + 0.14\cdot[\text{big light off}]$$

The curve is slow at first, steady in the middle and gentle at the end, the way a candle seems to burn. The screen's light is estimated from each frame's mean luma times what the veil lets through, and that's where the 47% → 6.5% figure comes from.

In numbers: about 3,500 lines of app TypeScript and 1,000 of hub JavaScript, 25 tests (17 app, 8 hub), and a friction log of 19 Vega findings. I built the current design over the last few days, on an app scaffold I started on Oct 15.

Challenges we ran into

  • Vega has no brightness API. I can't dim the panel, so the app dims every pixel itself, and I measure the result from frames instead of guessing.
  • The display turns off after ~5 minutes without a key press (reported by Amazon staff for Vega; the Virtual Device doesn't reproduce it). A story is longer than that, so the story has a "Turn the page" press between chapters, and every step ends with a press.
  • Bear had no voice, and nothing said why. Vega's WebSocket delivered my ~32 KB voice messages as ~10 KB pieces, and every JSON.parse failed silently. The fix was length-framed binary audio that the TV reassembles: 126,417 of 126,417 samples played.
  • A volume slider that ran away. ◀ ▶ changed the level and moved focus to the next control, because a component can't consume a key. Wrapping each slider in TVFocusGuideView trapFocusLeft/Right fixed it.
  • The evening froze on "We're done". One Virtual Device session played audio at ~4% of real time and reported 0 underruns. My routine awaited the stream's flush, which queued behind the stalled writes for ~35 s. Now the evening never waits on audio for more than a moment.
  • Amy turned into a robot overnight. An expired AWS login made one Polly call fail, and the hub switched every line to the Mac's voice, ignoring Amy's cached recordings. Now the cache always comes first, and Polly is retried.

Accomplishments that we're proud of

  • One object carries the meaning. The candle is the light and the clock; the sparks are the child's progress; the stars are the record. There are no streaks, timers or points.
  • A child can run it without reading a word, with one button at a time.
  • The TV ends on black by itself, the opposite of autoplay, and I can show the light curve that gets it there.
  • Every sound in the demo after the title comes from the app: its chimes, its rain, and Amy's lines (for the video, recorded again at 24 kHz with the app's exact words and pacing, then placed where the app spoke them).
  • A friction log written while building, with evidence for every entry, aimed at making Vega better for the next team.

What we learned

  • A missing platform API can become the design. With no brightness control, the whole picture had to dim, and that is what makes the evening feel like it's ending.
  • Never let the UI wait on audio. Audio stalls happen; a child's button press must always move things on.
  • Test a TV app with its remote, end to end. My worst bugs (focus, timing, voice order) only showed up when I pressed the keys in order, the way a family would.
  • Drive scripted demo takes from the app's own telemetry instead of fixed pauses, and the take matches the real pacing every time.

What's next for Goodnight TV

  • Real bedtimes with my own kids, logging start-to-dark minutes and lights-out against the plan.
  • A full run on the Fire TV Stick, to measure the 5-minute display-off during a real story.
  • Alexa: open Goodnight TV on a schedule at bedtime (not yet verified for a Vega app).
  • The real lights: dim smart bulbs with the candle (Hue bridge local API).
  • More stories in the same wind-down shape, and the family's own favourites.

Built With

  • amazon-devices-builder-tools-mcp
  • amazon-nova
  • amazon-polly
  • amazon-web-services
  • ffmpeg
  • fire-tv
  • fire-tv-stick
  • google-lyria
  • google-veo
  • javascript
  • jest
  • keplerscript-audio-lib
  • node.js
  • openai-codex
  • react-linear-gradient
  • react-native
  • react-native-for-vega
  • react-native-svg
  • svg
  • typescript
  • vega-os
  • vega-sdk
  • vega-virtual-device
  • websocket
  • wikimedia-commons
Share this project:

Updates

Submission history