Branding
Customize how your tour looks and feels
Built-in branding and theming system gives you complete control over the visual appearance. Change colors, fonts, and styling to match your brand or create a unique experience.
Default theme
There is one main theme with two variants:
Default Light
Clean, distraction-free light aesthetic with high legibility and soft gray accents.
Default Dark
Refined dark aesthetic with grayscale palette and sans-serif typography.
Using a theme
Set the theme in your metadata.json:
{
"id": "barcelona",
"themeId": "default-light"
}Or "default-dark" for the dark theme.
That's it! The entire app adopts the theme—cards, buttons, player, sheets, everything.
Tour layout options
Themes control colors, fonts, and visual styling — but several layout and UI visibility options are configured per-tour in metadata.json, not in the theme file.
| Property | Type | Description |
|---|---|---|
showStopImage | boolean | "thumbnail" | Stop card layout: true = full image card, "thumbnail" = compact row with small thumbnail, false = text-only list |
showStopDuration | boolean | Show/hide the duration badge on stop cards |
showStopNumber | boolean | Show/hide the numbered circle indicator on stop cards |
showProgressBar | boolean | Show/hide the playback progress bar |
showLanguageLabel | boolean | Show/hide the language name next to the flag in the language selector |
fullscreenPlayer | boolean | Enable the fullscreen overlay player (slides up over the stop list) |
backgroundColor | string | Background color of the TourStart screen and its status bar area |
These work alongside your theme — you can define card colors in the theme while controlling whether cards show images or render as a plain list via showStopImage in metadata.
Status bar background
The narrow safe-area region at the very top of the screen (behind the iOS clock, battery, and signal icons) is automatically filled. The color depends on which screen is active:
| Screen | Color source |
|---|---|
| TourStart | backgroundColor in metadata.json |
| TourDetail | header.backgroundColor from the active theme |
Add backgroundColor to your metadata.json to control the TourStart status bar:
{
"backgroundColor": "#1A2634"
}Pick a color that matches the top edge of your cover image — this makes the status bar feel like a natural extension of the photo. If omitted, the theme's header.backgroundColor is used on both screens.
Both values also update the <meta name="theme-color"> tag, which tints the browser chrome on iOS and influences status bar icon contrast (light/dark).
What you can customize
Themes control virtually every visual aspect:
| Area | What You Control |
|---|---|
| Colors | Backgrounds, text, borders, accents |
| Typography | Font families, sizes, weights |
| Cards | Background, borders, shadows, corner radius |
| Buttons | Primary, secondary, and download button styles |
| Mini Player | Progress bar, controls, transcription text |
| Bottom Sheets | Background, handle color, title styling |
| Step Indicators | Active, inactive, and completed states |
| Branding | Custom logo on start screen |
You can tweak individual properties or create an entirely new theme from scratch. It's up to you how far you want to go.
Theme structure
Themes are TypeScript objects with a specific structure. Here's a simplified view:
const myTheme = {
id: 'my-theme',
name: 'My Theme',
header: { /* colors, progress bar */ },
mainContent: { /* background */ },
cards: { /* colors, typography, shadows */ },
buttons: { /* primary, secondary styles */ },
miniPlayer: { /* player controls, progress */ },
sheets: { /* bottom sheet styles */ },
typography: { /* font families */ },
colors: { /* semantic color palette */ },
// ...and more
};Each section controls a specific part of the UI. We'll cover all of them in Theme Reference.
Quick customization
Want to just change the accent color? You don't need a full custom theme.
The easiest approach is to copy an existing theme and modify it:
Copy a theme file
cp src/theme/themes/default-light.ts src/theme/themes/my-brand.tsChange the accent color
Find all instances of the primary color (like #6366F1) and replace with your brand color.
Register the theme
Add to src/theme/themes/index.ts:
import { myBrandTheme } from './my-brand';
export const themes = {
'default-light': defaultLightTheme,
'default-dark': defaultDarkTheme,
'my-brand': myBrandTheme,
};Use it
Set "themeId": "my-brand" in your metadata.json.
Per-language themes
You can use different themes for different languages by overriding in the language file:
// es.json
{
"id": "barcelona",
"language": "es",
"themeId": "warm-theme"
}Spanish visitors would see warm-theme while others see whatever's in metadata.json.
Custom fonts
Themes support custom fonts from Google Fonts (or self-hosted):
typography: {
fontFamily: {
sans: ['Roboto', 'system-ui', 'sans-serif'],
heading: ['Playfair Display', 'serif'],
numbers: ['JetBrains Mono', 'monospace'],
},
}Remember to add the font to index.html:
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&display=swap" rel="stylesheet">Branding: Custom logo
Display your logo on the tour start screen:
branding: {
logoUrl: 'https://your-domain.com/logo.svg',
showLogoBorder: true,
logoSize: 'fit', // or 'original'
}| Option | Effect |
|---|---|
showLogoBorder: true | Logo in a rounded rectangle with shadow |
showLogoBorder: false | Logo displayed directly, no container |
logoSize: 'fit' | Constrained to 48x48px |
logoSize: 'original' | Natural image dimensions |
What you can't customize
Some things are intentionally fixed:
- Component layout - Where elements are positioned
- Animation types - Transitions use spring physics
- Icon shapes - Only colors are themeable
- Spacing between elements - Some spacing is hardcoded
These constraints ensure the app remains usable and visually consistent regardless of theme choices.