Shots in this scene
Build: The Still Launch Page
In one line
Build the Lumen Volt launch page with no motion at all: header and cart, hero, accessories, gallery filter, specs and footer, in four files. It works fully, with JavaScript and without motion, so every later phase only adds motion.
Before you start
You will learn
- Set up the launch page's four files in your lumen-motion project
- See which parts of the page change instantly and need motion
- Keep the element names later phases rely on
You write code in
In your lumen-motion project: replace Vite's starter files with these four files, then run npm run dev
Files you touch
- index.html
- src/styles/base.css
- src/styles/components.css
- src/js/main.js
Think of it like this
A film is first shot as an "animatic", still drawings in order with no movement, so everyone can check the story works before animators spend weeks on motion. This page is your animatic: everything is in place and works; nothing moves.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- inline SVG
- An SVG drawing written directly inside the HTML (
<svg>…</svg>) instead of loaded as an image file, so CSS and JavaScript can reach its parts. - aria-label
- An attribute that gives an element the name screen readers read out, here "Cart, 2 items".
- aria-pressed
- Tells screen readers that a button is a toggle and whether it is on (
true) or off (false). The gallery filters use it. - hidden
- An HTML attribute (and DOM property) that removes an element from the page and from screen readers.
Step by step
01Replace index.html
Open index.html in your project and replace it with the file under "Your project files" below. It links the two stylesheets and loads src/js/main.js as a module.
02Add the two stylesheets
Create src/styles/base.css (colours, layout, header, hero) and src/styles/components.css (buttons, cards, gallery, specs). There is no transition or animation anywhere yet, on purpose.
03Add main.js
Create src/js/main.js with the three behaviours: add to cart, filter the gallery, reserve. Each one changes the page in a single step.
04Run it and find the dead moments
With npm run dev running, open the page. Add three accessories while looking at the Add buttons: you probably did not see the cart count change. Filter the gallery to Blue: four photos simply vanish. These are the moments the course will fix, one phase at a time.
Try it yourself
- 1
Find the changes you miss
In "The still launch page", look at an Add button and click it three times. Without looking up, how many items does the cart say? The count did change, three times, in one frame each. Now filter the gallery: photos disappear instantly, and the ones left jump to fill the gaps. Note which change you found most confusing: it is the one motion should fix first.
- 2
Check that the timeline is empty
Click everything on the page and look at the Motion Timeline: it says nothing is moving. That is your baseline. By Phase 19 this same page will have more than a dozen kinds of track, all of them purposeful, cheap and calm with reduced motion.
Code & diagrams
Your project's four files merged into one page (the body of index.html, both stylesheets, main.js). Add accessories, filter the gallery, open a spec, reserve: everything works, and the timeline stays empty because nothing animates yet.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Your project files
Your lumen-motion project after this topic. Click a file to see it in full.
The whole file after this topic. If yours differs, you can copy this one and carry on.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lumen Volt · Lumen Bikes</title>
<link rel="stylesheet" href="/src/styles/base.css" />
<link rel="stylesheet" href="/src/styles/components.css" />
</head>
<body>
<header class="site-header">
<a class="logo" id="logo" href="/">lumen</a>
<nav aria-label="Main"><a href="#accessories">Accessories</a> <a href="#gallery">Gallery</a> <a href="#specs">Specs</a></nav>
<button id="cart-button" class="cart-button" aria-label="Cart, 0 items">Cart <span id="cart-count">0</span></button>
</header>
<main>
<section id="hero" class="hero">
<div>
<h1>The Lumen Volt</h1>
<p class="hero-copy">Our first e-bike. 80 km on one charge, built for Ardenvale's hills.</p>
<p class="price">€2,490</p>
<button id="reserve" class="button primary">Reserve yours</button>
</div>
<svg id="volt-hero" class="volt" viewBox="0 0 240 140" role="img" aria-label="The Lumen Volt e-bike">
<g fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
<circle cx="52" cy="98" r="34" />
<circle cx="188" cy="98" r="34" />
<path d="M52 98 L96 98 L132 50 L84 50 Z M96 98 L72 38 M132 50 L188 98 M126 34 L140 34 M132 50 L130 34" />
<path d="M64 38 L82 38" />
</g>
<rect x="98" y="58" width="26" height="14" rx="4" fill="#2a9df4" />
</svg>
</section>
<section id="accessories">
<h2>Accessories</h2>
<div class="grid">
<article class="card"><h3>Lock</h3><p>€39</p><button class="add" data-product="lock">Add</button></article>
<article class="card"><h3>Lights</h3><p>€29</p><button class="add" data-product="lights">Add</button></article>
<article class="card"><h3>Rack</h3><p>€45</p><button class="add" data-product="rack">Add</button></article>
<article class="card"><h3>Bell</h3><p>€9</p><button class="add" data-product="bell">Add</button></article>
<article class="card"><h3>Basket</h3><p>€35</p><button class="add" data-product="basket">Add</button></article>
<article class="card"><h3>Helmet</h3><p>€59</p><button class="add" data-product="helmet">Add</button></article>
</div>
</section>
<section id="gallery">
<h2>Gallery</h2>
<div class="filters" role="group" aria-label="Show colour">
<button class="filter" data-color="all" aria-pressed="true">All</button>
<button class="filter" data-color="orange" aria-pressed="false">Orange</button>
<button class="filter" data-color="blue" aria-pressed="false">Blue</button>
<button class="filter" data-color="ink" aria-pressed="false">Ink</button>
</div>
<div class="photos">
<figure class="photo" data-color="orange"><div class="swatch"></div><figcaption>Volt in orange, side</figcaption></figure>
<figure class="photo" data-color="blue"><div class="swatch"></div><figcaption>Volt in blue, side</figcaption></figure>
<figure class="photo" data-color="ink"><div class="swatch"></div><figcaption>Volt in ink, side</figcaption></figure>
<figure class="photo" data-color="orange"><div class="swatch"></div><figcaption>Orange, at the harbour</figcaption></figure>
<figure class="photo" data-color="blue"><div class="swatch"></div><figcaption>Blue, in the old town</figcaption></figure>
<figure class="photo" data-color="ink"><div class="swatch"></div><figcaption>Ink, at night</figcaption></figure>
</div>
</section>
<section id="specs">
<h2>Specs</h2>
<details class="spec"><summary>Battery</summary><p>500 Wh, removable, 80 km range, full in 4.5 hours.</p></details>
<details class="spec"><summary>Motor</summary><p>250 W rear-hub motor, assists up to 25 km/h.</p></details>
<details class="spec"><summary>Frame</summary><p>Aluminium, three sizes, 21 kg with the battery.</p></details>
</section>
</main>
<footer class="site-footer"><p>Lumen Bikes · Ardenvale</p></footer>
<script type="module" src="/src/js/main.js"></script>
</body>
</html>
Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Load main.js as a classic script
In your project's index.html, change <script type="module" src="/src/js/main.js"></script> to <script src="/src/js/main.js"></script> and move it into <head>.
Myth vs fact
Myth
Motion can make up for a confusing page.
Fact
Motion helps people follow changes on a page that already makes sense. If the page itself is confusing, animation only makes the confusion move. That is why the still page comes first.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The cart button's
aria-labelchanges with the count ("Cart, 1 item", "Cart, 2 items"), so screen-reader users get the same feedback the bump gives sighted users in Phase 8. Phase 4's toast container is a live region (role="status"), and Topic 7.6 covers when screen readers announce it. - ▸
Gallery photos are hidden with the
hiddenattribute, which removes them from layout in one step. FLIP (Phase 10) has to measure positions before and after that change, so the change itself stays simple and synchronous on purpose.
Remember this
- 1
The problem this solves: adding motion to a page that does not work yet mixes two kinds of bug. With the still page finished first, every later change is only about motion, and you can always compare against how the page behaves without it.
- 2
What the page does now. Add an accessory and the cart count goes up (and the cart button's label for screen readers says how many items). Filter the gallery and photos hide or show. Open a spec. Reserve the Volt and the button confirms. All of it happens in a single frame: this is exactly the "dead" feeling Ama described, and the changes Lena's testers missed.
- 3
How it is built.
index.htmlholds the content with the ids and classes later phases need (#cart-count,.cardand.add,.filterand.photo,details.spec,#reserve).base.csslays out the page;components.cssstyles buttons, cards, the gallery and specs;main.jswires the three behaviours. The Volt is an inline SVG drawing, so CSS and JavaScript can reach it (Phase 6 fixes how it moves, and Phase 14's launch timeline slides it in). - 4
What is still missing, and where it comes from. Feedback on buttons (Phase 3), a toast when you add something (Phase 4), the accessory grid's reveal (Phase 5), a smooth cart count bump (Phase 8), the cart drawer (Phase 9), gallery photos that slide when filtered (Phase 10), the spec sections opening smoothly (Phase 10), the move to the ride page (Phase 11), the scroll story (Phase 12) and the launch timeline (Phase 14). Later phases add the loading bar (1), the motion toggle (7), the animated logo (13), the React configurator (15), the mascot (16) and the confetti (17). Each one fixes a moment where the page now changes too suddenly.
Explain it without notes
Why build the page without motion first?
Which moments on the still page most need motion, and which job would motion do there?
Practice
Copy the four files into your project and run it. Then add a fourth accessory card of your own (with a button.add). Does the cart count work for it without changing main.js?
Make the Reserve button's confirmation undoable: a second click goes back to "Reserve yours". Keep the button usable for screen readers.
Done when you can
Your project has the four files and runs with npm run dev
Adding, filtering, opening specs and reserving all work
You can point to the moments where the page changes too suddenly
You know which element names later phases rely on