Space Tourism
Four routes, one supplied design, built across three widths.
A responsive space tourism site with destination, crew and technology sections, built from a supplied Frontend Mentor design with React and Tailwind.
- React
- React Router
- Framer Motion
- Tailwind CSS
- Vite
The premise
Space Tourism is a responsive front-end implementation of a multi-section site supplied as a Frontend Mentor design. It has four routes: a home page, a destination selector covering Moon, Mars, Europa and Titan, a crew selector with four members, and a technology section with three vehicles. The design is credited to Frontend Mentor and the implementation is mine. Content is bundled rather than fetched, and each section keeps its selection in local component state rather than in the URL.
Resize the viewport
Explore the source-backed breakpoint choices, then load the live site to resize its actual viewport. The supplied desktop captures below remain the static reading path.
Jump to a figure
- 01
Home composition

Desktop home view with numbered navigation, Earth imagery and the Explore action. Design supplied by Frontend Mentor. - 02
Moon selected

Destination view with Moon selected and the other destination controls beside it. A selected appearance does not establish an ARIA tabs pattern. - 03
Mars selected

The same destination composition with Mars selected. The image and text change with the local selection. Read the related decision - 04
Crew portrait

Crew view with Douglas Hurley and a visible focus outline on the navigation link. Imported portrait assets are explained in the linked decision. Read the related decision - 05
Technology selection

Technology view with Launch vehicle selected using numbered controls and a portrait image.
Decisions
Import crew images instead of linking their asset paths
The crew data pointed at images with literal /src/assets/crew/... paths.
- Options
- Keep literal paths that resolve in development but never enter the bundler graph.
- Import the WebP and PNG files and store the imported URLs in the data records.
- Choice
- Import both formats and assign the imported URLs to each record, one file per format.
- Trade-off
- Build output now emits and hashes the images, and a missing file fails the build instead of rendering empty. No responsive sources were added, so each record still holds exactly one file per format.
Evidence
- Import change over the crew data records
space-tourism commit a97919d - Picture elements consuming both formats
space-tourism:src/sections/Crew.jsx - Seed record and specificity verdict
docs/truth/space-tourism-decisions.md, seed 1; docs/seed-specificity-review.md
Shorten the image transitions around selected content
Page transitions animate whole sections while image swaps keep their own timing, and the second duration is the one a visitor waits on.
- Options
- Leave the configured transition durations untouched across sections.
- Shorten the image transition around the selected image and keep the rest of the motion configured as-is.
- Choice
- Shorten the image transition so the swapped image settles before the surrounding copy does.
- Trade-off
- The swap reads faster, and the section now has two motion speeds rather than one, so the shorter duration has to hold against the page transition it sits inside.
Evidence
- Configured transition durations per section
space-tourism:src/utils/transitionPresets.js - Image swap motion around selected content
space-tourism:src/sections/Crew.jsx; space-tourism:src/sections/Destination.jsx - Seed record and specificity verdict
docs/truth/space-tourism-decisions.md, seed 3; docs/seed-specificity-review.md
Split vendor chunks and run Terser for the production build
The production build is a single output, and the image asset strategy is fixed by the design's fixed-size pictures.
- Options
- Ship the default Vite output with one chunk and the default minifier.
- Register named vendor chunks and run Terser in the production build.
- Choice
- Register vendor chunks for React, the router, motion and Tailwind, and minify production output with Terser.
- Trade-off
- Vendor code caches separately from app code across deploys, and the build gains a minifier step. No before-and-after numbers were recorded, so the gain is architectural rather than measured.
Evidence
- Chunk and minifier configuration
space-tourism:vite.config.js - Build tooling and plugin registration
space-tourism:vite.config.js (react, tailwindcss plugins) - Seed record and specificity verdict
docs/truth/space-tourism-decisions.md, seed 4; docs/seed-specificity-review.md
The tools
- React
- Component model for the four route pages and their selection controls.
- React Router
- Four nested routes resolved through the router, each loaded by a route-level lazy import.
- Framer Motion
- Keyed transitions around selected content and images, with configured durations per section.
- Tailwind CSS
- Theme tokens for type and spacing live in CSS, labelled as coming from the supplied style guide.
- Vite
- Registers the React and Tailwind plugins, runs Terser for production and emits the named vendor chunks.
Problems and fixes
Crew images never became build dependencies
Crew imagery was referenced by literal /src/assets/crew/... paths. Those resolve during development but never enter the bundler's dependency graph, so nothing guaranteed they reached the output or were hashed. Commit a97919d imports the WebP and PNG files and assigns the imported URLs to the data records. Both formats are now emitted into the build output, and each record still holds one file per format with no width or density alternatives.
The performance guide describes decisions the code does not make
PERFORMANCE_OPTIMIZATIONS.md says lazy routing was abandoned for direct imports, while commit dd21daa replaces direct imports with route-level lazy ones that remain at HEAD. The guide also attributes the font change to display=swap, which the parent revision already had. The audit treats the source as the truth, so no claimed measurement from that guide appears on this page.
No reduced-motion path and no selected state
Animated page components and hover transitions have no reduced-motion handling in the components or the global styles, and the README's keyboard and WCAG claims carry no audit artifact. Destination and crew expose native buttons that work on click but no selected state and no arrow-key handler, and the mobile menu has no Escape handling or focus return. This page reports what the markup does rather than what the design intended.
What I would change
What I would change first is the accessibility work: a reduced-motion path for the keyed transitions, selected state on the destination and crew selectors, and Escape handling with focus return in the mobile menu. The performance guide would be rewritten to match the code, since it currently describes a routing decision the history does not support. I would also capture the real page at three widths before making any responsive claim, and add a not-found route with an authored no-JavaScript fallback. None of that exists today, and none of it is claimed.