2026-04-06 21:33:52 -04:00
..
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00
2026-04-06 21:33:52 -04:00

Frontend Development Guide

Overview

The Hotel Pi frontend is a fullscreen kiosk application built with Vite and Svelte. It provides a premium, responsive interface for browsing restaurants, attractions, and launching media apps.

Architecture

src/
├── App.svelte           # Root component (routing & state)
├── main.js              # Entry point
├── components/          # Reusable UI components
│   ├── IdleScreen.svelte        # Welcome/idle display
│   ├── HomeScreen.svelte        # Main menu
│   ├── RestaurantsPage.svelte   # Restaurant list
│   ├── AttractionsPage.svelte   # Attractions list
│   └── Clock.svelte             # Time display
├── lib/
│   ├── store.js         # Svelte state management
│   ├── api.js           # Directus CMS integration
│   ├── websocket.js     # WebSocket client
│   └── qrcode.js        # QR code generation
└── routes/              # Page components (if using SvelteKit)

index.html              # HTML entry point
vite.config.js          # Build configuration
tsconfig.json           # TypeScript config
package.json            # Dependencies

Development

Setup

cd frontend
npm install
npm run dev

Frontend will run on http://localhost:5173 with hot reload enabled.

Commands

npm run dev      # Start dev server
npm run build    # Build for production
npm run preview  # Preview production build
npm run format   # Format code with Prettier
npm run lint     # Check code style

Core Components

App.svelte

Main application component handling:

  • Screen navigation (idle → home → restaurants/attractions)
  • Input handling (keyboard and WebSocket)
  • State management
  • API data fetching
  • Idle timeout logic

Props:

  • None (root component)

State:

  • currentScreen - Active screen
  • selectedIndex - Currently selected menu item
  • restaurants, attractions - CMS content
  • wsConnected - Control service connection status

IdleScreen.svelte

Fullscreen ambient display shown when idle.

Features:

  • Animated gradient background
  • Current time display (Clock component)
  • Welcome message
  • Floating ambient elements
  • Auto-advance on any input

Props:

export let welcomeName = 'Guest';

HomeScreen.svelte

Main menu with three options:

  1. Watch Plex
  2. Restaurants
  3. Things to Do

Features:

  • Grid-based menu layout
  • Navigation with arrow keys
  • Visual feedback for selected item
  • Smooth transitions

RestaurantsPage.svelte

Carousel of restaurants with details.

Features:

  • Full-screen restaurant display
  • Image gallery
  • QR code for website
  • Description and cuisine type
  • Navigation controls

Data: Pulled from Directus restaurants collection:

  • name
  • description
  • cuisine_type
  • website_url
  • image

AttractionsPage.svelte

Similar to RestaurantsPage but for attractions.

Features:

  • Attraction showcase
  • Category badges
  • Distance information
  • Operating hours
  • Website QR code

Data: From Directus attractions collection:

  • name
  • description
  • category
  • distance_km
  • website_url
  • hours
  • image

Clock.svelte

Real-time clock display.

Features:

  • Updates every second
  • 12-hour format with AM/PM
  • Large, readable typeface
  • Text shadow for visibility

Store (State Management)

Located in src/lib/store.js:

export const currentScreen;      // Svelte store
export const selectedIndex;      // Current selection
export const restaurants;        // From CMS
export const attractions;        // From CMS
export const wsConnected;        // WebSocket status

export function pushScreen(screen);      // Navigate to screen
export function popScreen();              // Go back
export function resetNavigation();        // Reset to idle

Usage

<script>
  import { currentScreen, selectedIndex, pushScreen } from '$lib/store.js';
</script>

{#if $currentScreen === 'home'}
  <HomeScreen />
{/if}

<button on:click={() => pushScreen('restaurants')}>View Restaurants</button>

API Integration

Located in src/lib/api.js:

export async function fetchRestaurants()  // Get all restaurants
export async function fetchAttractions()  // Get all attractions
export function getImageUrl(filename)     // Construct image URL

Example

import { fetchRestaurants } from '$lib/api.js';

const restaurants = await fetchRestaurants();
// Returns:
// [
//   {
//     id: 'uuid',
//     name: 'La Bella Vita',
//     description: '...',
//     image: { ... }
//   }
// ]

WebSocket Communication

Located in src/lib/websocket.js:

const ws = new WebSocketManager(url);

ws.on('connected', () => {});
ws.on('disconnected', () => {});
ws.on('input', (data) => {});
ws.connect();
ws.send('launch-plex');

Styling

Design System

  • Colors:

    • Primary: #667eea (purple-blue)
    • Accent Red: #e94560
    • Accent Cyan: #00d4ff
    • Dark BG: #1a1a2e
    • Card BG: #0f3460
  • Typography:

    • Font: Inter (system fonts fallback)
    • Sizes: 0.875rem to 4rem
    • Weights: 300 (light), 400 (regular), 600 (semibold), 700 (bold)
  • Spacing:

    • Base unit: 1rem
    • Gaps: 0.5rem to 3rem

CSS Features

  • CSS Grid for layouts
  • Flexbox for components
  • CSS animations (preferred over JS)
  • Media queries for responsive design
  • CSS variables for theming

Animations

Key animations used:

@keyframes fade-in
@keyframes slide-down
@keyframes float
@keyframes bounce
@keyframes gradient-shift

Keep animations smooth and under 600ms for best UX.

Input Handling

Keyboard Input

'arrowup', 'arrowdown', 'arrowleft', 'arrowright' - Navigate
'enter'   - Select
'escape'  - Go back
any key   - Wake from idle (if on idle screen)

WebSocket Input

Control service sends input events:

{
  "type": "input",
  "payload": {
    "type": "up"    // "up", "down", "left", "right", "select", "back"
  }
}

Performance Optimization

  1. Lazy Loading: Images load on-demand
  2. CSS Animations: Prefer over JavaScript transitions
  3. Minimal Dependencies: Only qrcode library
  4. Vite Optimization:
    • Tree-shaking
    • Minification in production
    • Code splitting

Building for Production

npm run build
# Creates dist/ with optimized bundle

Docker Production Build

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci && npm run build
CMD ["npm", "run", "preview"]

Troubleshooting

Hot Reload Not Working

  • Clear .svelte-kit/ and dist/
  • Restart dev server
  • Check Vite config

Components Not Updating

  • Verify store subscriptions use $ prefix
  • Check for reactive declarations (:)
  • Ensure state updates are triggering changes

CSS Not Applied

  • Check CSS is within <style> block
  • Verify selector specificity
  • Use :global() for global styles carefully

API Connection Failed

  • Verify VITE_API_URL in .env
  • Check Directus is running
  • Review CORS settings

WebSocket Disconnects

  • Check VITE_WS_URL is correct
  • Verify control service is running
  • Review firewall rules
  • Check browser console for connection errors

Code Quality

Formatting

npm run format  # Fix formatting with Prettier

Linting

npm run lint    # Check code style

Type Checking

TypeScript configured but optional. Add type annotations for better IDE support:

<script>
  let count: number = 0;
  let items: Array<{id: string; name: string}> = [];
</script>

Component Template

<script>
  import { onMount } from 'svelte';

  let isLoading = false;

  onMount(async () => {
    // Initialization
  });
</script>

<div class="component">
  {#if isLoading}
    <p>Loading...</p>
  {:else}
    <p>Content</p>
  {/if}
</div>

<style>
  .component {
    /* Styles */
  }
</style>

Browser Support

  • Chrome/Chromium 90+
  • Firefox 88+
  • Safari 14+

Tested primarily on Chromium for Raspberry Pi.

Resources