SmartCompanion Audioguide App — a free, open-source PWA for interactive audioguide experiences in museums and cultural institutions. It runs in any modern mobile browser without installation and is fully offline-capable.
This repo is the shell/wrapper application. The bulk of functionality lives in workspace packages (@smartcompanion/ui, @smartcompanion/services, @smartcompanion/data, @smartcompanion/native-audio-player).
- Stencil.js v4 — Web Components framework (JSX with
hfactory, Preact-style) - Ionic Framework v8 — UI components and routing
- TypeScript — Target ES2022, strict unused locals/params enabled
- SCSS/SASS — Styling with CSS custom properties
- Workbox v7 — Service worker and offline caching (conditional)
- npm — Package manager
npm start # Dev server with hot reload (--dev --watch --serve)
npm run build # Production build → www/
npm test # Run spec & e2e tests once
npm run test.watch # Continuous test watching
npm run generate # Generate new Stencil component boilerplatesrc/
├── components/app-root/ # Single Stencil component (app shell)
│ ├── app-root.tsx # Main component: routing, menu, navigation
│ └── app-root.css
├── global/
│ ├── app.ts # Global initialization — applies dark mode via prefers-color-scheme
│ └── app.scss # Global styles & CSS custom property (color) variables (light + dark)
├── services/
│ └── index.ts # ServiceFacade initialization & export
├── assets/icon/ # PWA icons (favicon.ico, icon.png 512x512)
├── index.ts # Entry point — imports Ionic & @smartcompanion/ui
├── index.html # HTML shell
├── sw.js # Service worker (Workbox)
└── manifest.json # PWA manifest
stencil.config.ts # Build config & runtime environment variables
www/ # Build output (gitignored)
- Single Stencil component (
app-root) defined in this repo; all other UI components come from@smartcompanion/ui - Stencil decorators:
@Component,@State(no@Propon app-root) - Lifecycle:
componentDidLoad()for initialization;render()returns JSX
- Ionic Router with hash-based routes
- Routes:
/(loading),/language,/selection,/stations/:stationId,/pin,/error - Route guards via
serviceFacade.canLoadRoute()inbeforeEnter - Route change listener on
/stations/defaultto update reactive state
- ServiceFacade — single facade coordinates all services
- Imported from
src/services/index.tsasserviceFacade - Key methods:
__()(i18n),getRoutingService(),getMenuService(),canLoadRoute(),registerCollectibleAudioPlayerService(),registerOfflineLoadService(),registerOnlineLoadService() - Domain services, routing and menu are available directly from the facade — there is no registration step.
registerDefaultServices()was removed in library 0.10.0; registering an audio player and a load service is still required.
- Stencil
@State()for reactive component state - Translation strings held as
@Stateproperties, updated on route changes
- Online mode:
registerOnlineLoadService()— fetches fromEnv.DATA_URL - Offline mode:
registerOfflineLoadService()— fetch with service worker caching - Toggle via
OFFLINE_SUPPORTinstencil.config.ts
- Uses Ionic's class-based dark palette (
@ionic/core/css/palettes/dark.class.css) src/global/app.tslistens toprefers-color-scheme: darkand toggles.ion-palette-darkon<html>- Color tokens defined twice in
src/global/app.scss: light values on:root, dark overrides on:root.ion-palette-dark - Menu logo: two
<img>elements (#main-menu-image-light,#main-menu-image-dark); CSS toggles visibility — do not usecontent: url(...)on<img>(unreliable cross-browser) - Embedders can force a palette via the
UPDATE_DARK_MODEpostMessage and swap logos viaUPDATE_MENU_IMAGEwith{ light?, dark? }
All runtime configuration lives in stencil.config.ts (rebuild required to apply changes):
| Variable | Default | Description |
|---|---|---|
Env.TITLE |
"Animals" |
App title |
Env.DATA_URL |
GitHub sample JSON | External data source URL |
Env.OFFLINE_SUPPORT |
"disabled" |
Enable service worker ("enabled") |
Customization points:
- Colors: SCSS variables in
src/global/app.scss(CSS custom properties with--sc-prefix); each color has a light and a*-darkcounterpart - Title:
index.html,manifest.json, andstencil.config.ts - Data URL:
stencil.config.ts→DATA_URL - Offline:
stencil.config.ts→OFFLINE_SUPPORT: "enabled" - Icons: Replace files in
src/assets/icon/ - Logo: Replace
src/assets/logo.png(light) andsrc/assets/logo-dark.png(dark)
- Component tags: kebab-case (
app-root,sc-page-*) - Classes: PascalCase
- Functions/variables: camelCase
- CSS variables:
--sc-prefix, kebab-case - Imports: Async/await throughout; no callbacks
- JSX: Stencil's
hfactory (not React'screateElement) - Formatting: Prettier with 180-char print width, single quotes, 2-space indent, LF line endings
- Navigation: always call
serviceFacade.getMenuService().close()before navigating - Hash navigation: check current hash before navigating to prevent duplicate routes
Internal packages from the SmartCompanion monorepo — do not modify these directly from this repo:
@smartcompanion/ui— UI components@smartcompanion/services— core services (routing, menu, i18n, data loading)@smartcompanion/data— data types and structures@smartcompanion/native-audio-player— audio playback
- Output:
www/(gitignored) - Generates ESM bundles + legacy JS
- Service worker generated conditionally based on
OFFLINE_SUPPORT - Static assets only — no backend required
- Deployable to GitHub Pages, Netlify, or any static host