Luna Framework

Journey's modular platform for interactive 3D property experiences — reusable page types, components, and patterns assembled into bespoke applications per client.

Interactive 3D

Splat base + GLB overlay with selectable unit layers. Tap a unit, get data.

Multi-Page App

Splash, 3D, gallery, location, favourites, share. Pick per project.

iPad-First

SwiftUI wrapper, AirPlay to screen. Moving away from PCs.

Sales Centre

Multi-device sync, physical model lighting, idle reset.

Typical Physical Setup

iPadon stand
Wall ScreenAirPlay / HDMI
+
Linux Boxif lighting
LightSwarmserial → LEDs
01

Framework & Key Features

Not every project uses everything. Selection is per client. Everything listed has shipped in production.

01

Splash

Brand, preload, enter

02

3D Explore

Building model, select units

03

Filter

Price, floor, type, status

04

Detail

Plans, tours, gallery

05

Compare

Favourites, side-by-side

06

Share

QR, PDF, email

3D Visualisation

  • Gaussian Splatting (photorealistic base)
  • GLB overlay model (selectable unit layers)
  • Orbit camera (pan, zoom, rotate)
  • Click-to-select units on 3D
  • Material highlight for selection state
  • Per-floor splats with transitions
  • Camera presets per view
  • Post-processing (CAS, bloom)
  • World-space labels
  • Auto-rotation when idle

Property Discovery

  • Unit finder with multi-criteria filters
  • Filter: floor, type, beds, price, status
  • Sortable unit table / card view
  • Unit detail panel (specs, price, area)
  • Floor plan viewer
  • Favourites / bookmarking
  • Side-by-side comparison
  • CRM API live availability
  • Before/after comparison slider
  • 360° rotation viewer (image sequences)

Content & Sales

  • Photo/video gallery carousel
  • QR code sharing
  • Idle auto-reset
  • 360° panoramic tours
  • Interactive maps (Mapbox/Leaflet)
  • PDF unit spec export
  • VR headset mode (WebXR)
  • Multi-device sync (Socket.IO)
  • Physical model lighting (LightSwarm)
  • Multi-language (Arabic/English)
02

How the 3D Page Works

The 3D experience is built from layers, not a single model.

LAYER 3 — TOP Markers & Labels HTML elements positioned in 3D world-space. POI pins, unit labels, popup cards. LAYER 2 — INTERACTIVE GLB / SVG Overlay Selectable unit meshes. Each mesh ID maps to accommodation schedule. Tap to select. LAYER 1 — BASE Gaussian Splat Photorealistic point cloud. Orbit/pan/zoom around it. Units not individually selectable here.

Layer 1: Gaussian Splat (Base)

The photorealistic base. Point cloud data rendered in-browser via PlayCanvas GSplat. Looks like a photograph you can orbit, pan, and zoom around. You can navigate the splat freely (orbit camera with constraints), but you can't click on individual units within it — that's what the overlay layer is for.

Sources: Can be fully CG (rendered from Unreal or 3ds Max), drone photography, or a combination (CG building + drone context). The combo approach is the most common — CG for the building, drone for surrounding context.

Per-floor splats: For projects with floor plan exploration, a separate splat is generated per floor (cut-through showing that level). The viewer transitions between floor splats with a crossfade, keeping only ~3 in memory via LRU cache to avoid crashes.

Layer 2: GLB Overlay (Interactive)

A simplified 3D model (GLB/glTF) layered on top of the splat. This is what makes units selectable. Each unit is a separate mesh/layer in the GLB with an ID that maps to the unit data. When a user taps a unit, the system:

  1. Raycasts against the GLB overlay (not the splat)
  2. Identifies which mesh was hit → gets the unit ID
  3. Pulls unit data from the accommodation schedule
  4. Highlights the selected mesh (material swap)
  5. Dims/fades unselected units
  6. Shows the unit info panel with specs, pricing, plans

SVG alternative: When there are floor cut-throughs (seeing into the building from above), an SVG overlay projected onto the 3D scene works better than a GLB — the splat shows the interior, and the SVG provides the selectable unit outlines on top.

Layer 3: UI Markers & Labels

HTML elements positioned in 3D world-space (Element2d). Marker icons, unit labels, popup cards, location pins. These float above the 3D scene and respond to camera movement. Each marker has a 3D position [x,y,z] and can trigger camera animations on click.

Splat Pipeline INTERNAL

Source
CG / Drone / Both
Reconstruct
COLMAP SfM
Generate
PostShot
Convert
PLY → SOG
Output
.sog (15-30MB)
Floor cut-throughs: A separate splat per floor level (GF.sog, 1F.sog, 2F.sog... + ALL.sog). Source model sectioned at each floor height and run through the pipeline individually.
File sizes & memory: Each splat is typically 15–30MB. A project with 8 floors + an ALL view = ~280MB of splat assets total. The viewer keeps only ~3 floors in memory at once (LRU cache) to prevent WebGL crashes on iPad. Floor transitions crossfade between the outgoing and incoming splat. This is a real constraint — more floors = more assets to generate, host, and download on first load.
03

Page Types in Detail

Click to expand each page type.

Splash / Intro PAGE

First screen. Brand animation, asset preloading, transition into experience. Doubles as screensaver/idle state in kiosk mode — app returns here after configurable timeout (typically 2–20 min). Can include background video loop, parallax scroll gallery, or simple logo + enter button.

  • Logo animation (GSAP/Motion)
  • Asset preloading + progress
  • Enter CTA
  • Page transition animation
  • Background video loop
  • Parallax image gallery
  • Idle timeout auto-reset
  • Auth gate (Okta/Firebase)

3D Residences / Masterplan PAGE

The hero. Gaussian Splat base + GLB overlay with selectable unit layers. User taps a unit on the 3D model, it highlights, pulls unit data (from accommodation schedule), shows info panel. Filters narrow which units are visible/highlighted. Camera animates between views (overview, per-building, per-floor). For floor exploration: per-floor splats with crossfade transitions.

  • Splat base layer (photorealistic)
  • GLB overlay (selectable units)
  • Orbit camera with constraints
  • Click-to-select → unit data panel
  • Multi-criteria filtering
  • Unit table with sort
  • Favouriting from unit card
  • Camera presets per view
  • Per-floor splats with transition
  • Floor selector UI
  • SVG overlay instead of GLB (for cut-throughs / top-down)
  • CRM API for live status
  • Colour coding (available/sold/reserved)
  • 3D markers + popups
  • Scale comparison models
  • Dynamic DPR (perf optimisation)

Floor Plans PAGE

Per-unit floor plan display. 2D images, interactive SVGs with room labels, or 3D splat walkthroughs. Floor selector, pinch-to-zoom.

  • Floor plan images per unit type
  • Floor level selector
  • Pinch-to-zoom + pan
  • 3D splat floorplan walkthrough
  • SVG with hotspots
  • PDF export

Gallery PAGE

Full-screen image/video carousel. Category tabs, swipe nav, lazy loading. Video playback inline.

  • Full-screen carousel (Embla/Swiper)
  • Category filtering tabs
  • Touch/swipe + arrows
  • Video playback inline
  • Masonry grid layout
  • Lightbox modal

360° Tours PAGE

Spherical image viewer for interior/exterior panoramas. Drag to look around. Navigate between rooms via hotspots or buttons. Can extend to full VR headset mode (WebXR) or use device gyroscope on mobile.

  • Spherical rendering
  • Drag/touch to look around
  • Multi-scene navigation
  • Hotspot overlays
  • Gyroscope control
  • WebXR VR headset

Location PAGE

Stylised branded map — not a raw satellite embed. POI markers are tappable, each opening to show image, description, distance, category.

  • Stylised/branded map
  • Property location pin
  • Points of interest markers
  • POI tap → popup with image + info
  • Category filters (dining, transport, parks, etc.)
  • Subtle animations on marker appear/select
  • Mapbox GL with custom style JSON
  • Custom aerial tile map (from renders/drone)
  • 3D globe with flight path animation
  • Distance indicators from property
  • District/zone SVG overlays
  • Transport route highlighting

Favourites & Comparison PAGE

Saved units list. Persistent via localStorage/Zustand. Side-by-side comparison (max 2). Export shortlist as PDF or share via QR.

Amenities PAGE

Building/community amenities. List with imagery, category grouping, detail modals. Optionally interactive floor plan with hotspots or 3D camera fly-to per amenity.

About / Investor Info PAGE

Project narrative, developer info, optionally financial/investment details, charts, timeline.

Share & Export FEATURE

QR codes, shareable unit links, PDF export, email/social sharing for post-visit follow-up.

04

Project Lifecycle

Brief to launch. Timelines vary, arc is consistent.

1
Discovery
Discovery Session
Property, audience, sales process. What pages? iPad/web/kiosk? Hardware needs? Launch date?
Outputs: Brief, feature selection, scope, timeline, asset requirements.
2
Design
Figma Design & Prototype
Hi-fi mockups, branded, interactive prototype. Define 3D approach.
Outputs: Figma file, prototype link, 3D strategy, asset spec.
3
Approval
Client Sign-Off on Design
Walk client through prototype. Scope finalised. No dev until sign-off.
4
Build
Build Luna Skeleton
Clone template, scaffold, brand, wire interactions, integrate 3D, deploy to staging.
Note: Longest phase. Splat pipeline runs in parallel.
5
Check-in
Client Review — Progress
Demo skeleton on staging. Progress check, not final review.
6
Refinement
Finalisation
Feedback incorporated. Placeholders → final assets. Polish, optimise, QA.
7
Approval
Client Sign-Off — Final
Near-final build on actual hardware. Approval to proceed to install.
8
Install
Site Visit & Install
On-site. Configure devices, network, sync, hardware. 1–3 days before opening.
Checklist: iPads, kiosk mode, WiFi, sync, idle timeout, lights, backups.
9
Pre-Launch
Final Checks
Full run-through on every device. Data, images, 3D cached, network stable.
10
Launch
Launch Day
Sales centre opens. Dev on standby. Last-minute changes deploy in minutes.
11
Post-Launch
Adjustments
Real usage feedback. Refinements, data updates, content changes, maintenance.
05

Required Assets per Page

What's needed per page. Review at kick-off.

3D Residences / Masterplan

Must Have

  • Fully textured 3D model — with lighting adjusted. This is what gets processed into the Gaussian Splat (.sog) internally. Source: CG (Unreal/3ds Max), drone photogrammetry, or combination
  • Context source — surrounding environment. Usually from drone shoot, can also be CG. Combined with the building model to produce the final splat
  • 3D GLB/glTF overlay model — simplified model with each unit as a separate named mesh/layer. Critical: mesh names in the GLB must exactly match the unit IDs in the accommodation schedule (e.g. mesh "1F-01" maps to unit "1F-01"). If these don't match, unit selection breaks silently
  • Accommodation schedule — the single source of truth for all unit data. Minimum fields: unit ID, unit type, floor, bedrooms, bathrooms, area (sqft/sqm), price, availability status. Typically provided as a spreadsheet and converted to JSON. Example structure per unit:
    { id: "1F-01", label: "1F1-01", floor: "1F", type: "2bed", beds: 2, baths: 1, area: 85, areaSqFt: "914.9", price: 450000, status: "available", category: "residential" }

If Floor Exploration

  • Per-floor splats — separate .sog per floor level (GF, 1F, 2F... + ALL). Generated by sectioning the 3D model at each floor height and running through the splat pipeline individually
  • Floor plan SVGs or images — top-down cut-through per floor, used as 2D overlay on the 3D scene or in the floor plan page
  • Marketing floor plans — per unit type, from the client's sales pack. Usually PDFs or high-res images
Splat source decision: Fully CG (from Unreal or Max) gives complete control but needs 3D team time. Drone gives real context but only works if the building exists or the site is accessible. The most common approach is CG building + drone context — best of both. If 360° views are also needed, the drone shoot covers both the context splat and the 360 photography.

Gallery

Must Have

  • High-res images — CG renders and/or photography. WebP format preferred. Categories: exterior, interior, amenities, lifestyle
  • Image metadata — titles, categories, sort order

Optional

  • Videos — MP4, walkthrough/flythrough animations, lifestyle content
  • Captions/descriptions per image

360° Tours

Must Have

  • 360° equirectangular images — one per room/viewpoint. Source: drone photography (exterior/views) or CG rendering (interior). High-res (typically 8K+)
  • Scene map — which scenes connect to which, hotspot positions

Note

  • If view photography is needed (what you see from the unit), this requires a drone shoot at the actual site — same shoot can cover context splat + 360 views
  • Alternatively, views can come from CG renders if the building isn't built yet

Location

Must Have

  • POI list — name, category (dining, transport, parks, education, etc.), coordinates or relative positions
  • POI images — photo per point of interest for the popup/detail view
  • POI descriptions — short copy per POI (distance, what it is, why it matters)
  • Property coordinates (lat/lng)
  • Map style direction — what the map should look like (minimal, branded, satellite-ish)

If Custom Map

  • Aerial imagery — drone or CG aerial for custom tile generation
  • Mapbox style JSON — custom branded map style
  • Transport route data — if highlighting specific lines/routes

Floor Plans

Must Have

  • Marketing floor plans — per unit type. From client's sales pack. High-res images or PDFs
  • Floor level mapping — which plans go on which floor

Splash / Intro

Must Have

  • Client logo — SVG format, light + dark variants
  • Brand colours + typography — from client brand guidelines

Optional

  • Intro video — MP4, typically a flythrough/lifestyle reel, for background loop
  • Screensaver images — for kiosk idle rotation

About / Investor Info

Must Have

  • Project copy — description, vision statement, developer info
  • Hero images — lifestyle/architectural renders

Optional

  • Investor data — yields, pricing structure, financial projections
  • Timeline milestones

Always Required (Global)

Every Project

  • Brand guidelines — colours, typography, logo usage
  • Accommodation schedule — complete unit data (this drives everything)
  • Target devices — iPad model, screen sizes, kiosk specs if applicable
  • Content sign-off contacts — who approves copy, images, data
06

Common Patterns & Conventions

Patterns that recur across Luna projects. Understanding these makes onboarding to any project faster.

Navigation

How users move through the app

Typically a burger icon (bottom-left or top-left) that opens a side drawer or modal overlay. Menu items stagger in with GSAP (0.08s each). Active page highlighted. Nav closes on outside click or item select.

Navigation has evolved through several patterns: vertical sidebar with icon buttons, full-height drawers, and more recently modal overlays with animated circular backgrounds. The burger → X animation is always GSAP-driven.

Long-press logo (3s+) resets the entire app and clears favourites — this is the "escape hatch" for sales staff if something goes wrong.

State Management

Single Zustand store

Every project uses a single Zustand store with one setLocalState(partial) update function. No Redux. Everything lives in one flat object: nav state, selected unit, filters, gallery state, map state.

What goes where:

  • Store: nav open, selected unit, filters, gallery popup
  • URL params: current page, view, floor
  • localStorage: favourites (persists between sessions)
  • Local useState: hover states, transient UI
  • useRef: animation state, timelines, GSAP targets

Animation

GSAP everywhere

GSAP is the animation engine across all projects. Key patterns:

  • Page transitions: navigating flag → loader fades in, page fades out → new page fades in, loader fades out. Duration controlled by navDurationSec in store (0.5–0.8s)
  • Menu stagger: items animate in with 0.08s stagger on open, no stagger on close. Uses autoAlpha (opacity + visibility)
  • Timeline reuse: single timeline instance, clear() + re-populate for reversible animations
  • Easing: power2.out is the default, power3.out for intro sequences
  • CSS animations only for spinners: keyframe rotate for simple loops, GSAP for everything else

Unit Data Flow

How accommodation data reaches the UI

Two approaches depending on project:

Static JSON (most common): unit data lives in a TypeScript data file. Each unit has an ID, label, floor, area, type, category, and a 3D position [x,y,z] for marker placement. Data is grouped by floor level.

CRM API (live projects): fetches from middleware on init with Bearer token auth. Returns units keyed by project name. Filtered version maintained separately in store. Filters update the filtered set, not the raw data.

In both cases: user taps unit on 3D → raycast gets mesh ID → looks up unit data by ID → setLocalState({ activeID, selectedUnit: true }) → info panel renders.

Typography & Fonts

Per-project branding

Fonts are always project-specific — loaded from /public/fonts/ via @font-face. Common approach: custom family names per weight (e.g. "Bold", "Light") rather than using font-weight property. Dual format: woff2 primary, woff fallback.

Arabic-supporting projects load IBM Plex Sans Arabic or FS Albert Arabic. Font sizing is often viewport-based (1vw on body) for consistent scaling across devices.

Styling Approach

CSS-in-JS or Modules

Two approaches in use:

  • Styled Components — used in older/mid projects. Theme context integration. Dynamic props for variants.
  • CSS/SCSS Modules — used in newer projects. Isolated scoping. BEM-like naming. Lighter weight.

Both approaches use CSS custom properties (variables) for theming. Colours, spacing, and typography tokens defined globally.

Loading & Error States

How assets load, what happens when they fail

Loading: A boolean flag in the store. Full-screen overlay with either a CSS spinner (simple) or a GSAP-animated progress bar with clipPath (fancier). Some show percentage, most just show a branded animation.

3D asset loading: Splats load with progress tracking. Dynamic DPR: drops to 1x while camera is moving, back to 2x when static — big perf win on iPad.

Errors: Minimal. API failures log to console and return empty objects. No error boundaries, no retry logic, no user-facing error states. This is an area that could improve.

Idle & Reset Behaviour

What happens when nobody touches it

Idle timer (where implemented): tracks mouse/touch events, resets to splash after configurable timeout (2–20 min). Not present in all projects.

Manual reset: Long-press the logo for 3+ seconds. Triggers a full page reload and clears favourites/localStorage. This is the universal "escape hatch" across all projects — sales staff can reset without technical knowledge.

Screensaver: Some projects rotate through images or show a looping video on the splash screen while idle.

Routing

How pages are structured

Most projects use React Router v6+ with hash-based routing (HashRouter) for easy static hosting — no server-side rewrites needed. Newer projects are moving to Vike (file-based routing).

Deep linking via URL query params is common: /masterplan?page=unitFinder&floor=2F&selected=unit-3b. This means views are shareable and bookmarkable.

Provider Stack

What wraps the app

Typical provider order from outside in:

  • Router (BrowserRouter or Vike)
  • SocketIOProvider (if multi-device sync)
  • ThemeProvider (styled-components theme)
  • Layout (nav, loading screen, page slot)
  • Routes / Pages

Known Gaps & Gotchas

Things that have caused issues or are inconsistently handled across projects.

Technical

  • No error boundaries — if a component crashes, the whole app goes white. No graceful fallback.
  • API failures are silent — if CRM data fails to load, it logs to console and returns empty. No user feedback, no retry.
  • No font-display: swap — custom fonts load without fallback, causing a flash of invisible text on slow connections.
  • No node engine pinned — different machines may use different Node versions, causing inconsistent builds.

Process

  • GLB mesh IDs must match unit IDs — if the 3D team names a mesh "Unit_01" but the schedule says "1F-01", selection breaks silently. Needs a handshake between 3D and dev.
  • Camera positions are set in code, not Figma — producers sometimes expect camera angles from design phase. They're actually set during dev using Tweakpane. Provide intent, not coordinates.
  • Splat generation takes time — the COLMAP → PostShot pipeline isn't instant. Factor it into the timeline, especially for per-floor splats (multiply by number of floors).
  • Per-floor splats = ~280MB total — first load on slow sales centre WiFi can be painful. Consider preloading strategy or offline caching.
07

Technology

General pattern. Evolves continuously.

@playcanvas/react is an internal library (monorepo with lib, UI blocks, and docs) that wraps PlayCanvas for React. It provides declarative components (Application, Entity, Camera, Light), hooks (useApp, useSplat, useTexture, useFrame), and built-in scripts (OrbitControls). All recent 3D work goes through this — it's the foundation that makes splats and WebGL work in React.
FRONTENDReactTypeScriptVite
3DPlayCanvas@playcanvas/reactThree.js / R3FGaussian SplattingGLSL Shaders
ANIMATIONGSAPFramer Motion
STATEZustandURL params
SYNCSocket.IOMultipeer ConnectivitySerial Port
DEPLOYCloudflare PagesAWSCapacitor iOSWails Desktop
08

Architecture

Client Apps
Bespoke per property development
↓ built from ↓
Templates
Page structures, routing, theming
Services
Splat viewer, LightSwarm, sync
↓ powered by ↓
3D
PlayCanvas React
State
Zustand
Sync
Socket.IO
Anim
GSAP + Motion
↓ runs on ↓
Foundation
React + TypeScript + Vite · Web / iOS / Desktop
09

Typical Installations

What physically goes into a sales centre. The approach has evolved significantly — the direction is always toward simplifying and eliminating things that can go wrong.

How It's Evolved

Legacy: PC + Wails App

Moving away from this

Dedicated PC (often Windows) running a Wails desktop app (Go + React). Connected to wall screen(s) via HDMI. Separate LightSwarm server running on the same PC or a Raspberry Pi for physical model lighting.

Problems:

  • PCs are expensive to procure and ship
  • More hardware to maintain (fan failures, OS updates, driver issues)
  • Windows environment inconsistencies between installs
  • Client staff can accidentally close apps, change settings
  • Remote troubleshooting is harder
  • Physically bulky — needs space, cable management, ventilation

Current: iPad + Mirrored Screen

The direction we're heading

iPad running the Luna app (SwiftUI wrapper + WebView), AirPlaying or extending to a wall-mounted screen. If lighting control is needed, a small Linux box handles LightSwarm — that's it. The iPad is the single source of truth.

Why this is better:

  • iPad is cheap, reliable, familiar to clients
  • OTA updates via TestFlight — no site visit for software changes
  • Single device to manage, charge, and troubleshoot
  • Kiosk mode locks it down — clients can't break it
  • Linux box for lights is low-power, headless, set-and-forget
  • Remote support is straightforward
  • Physically small — iPad on a stand, Linux box hidden

Typical Physical Setup

Standard Sales Centre Installation
Control
iPad
Display
Wall Screen
AirPlay / HDMI via Apple TV / Extended Display
Control
iPad
Display
Wall Screen
+
Lighting
Linux Box
Hardware
LightSwarm
Physical
Model Lights
+ physical model lighting via serial connection

What Goes On-Site

DeviceRoleNotes
iPadRuns the Luna app. Sales agent holds/uses this.Kiosk mode, idle timeout, TestFlight updates. Usually on a stand or counter mount.
Wall screen / TVDisplays the experience to the buyer.Connected via AirPlay (wireless) or HDMI (wired via Apple TV or adapter). 16:9 preferred.
Apple TV (if needed)Receives AirPlay or runs its own app.Only if mirroring wirelessly to screen, or running independent ATV app for immersion rooms.
Linux box (if needed)Runs LightSwarm server for physical model lighting.Small, headless, low-power. Serial connection to LightSwarm controller. Set and forget.
LightSwarm controller (if needed)Controls LEDs on physical architectural model.Serial protocol. Syncs light states with unit selection in the app.
WiFi router (if needed)Network for AirPlay, Socket.IO sync, OTA updates.Dedicated network recommended. Sales centre WiFi is often unreliable.

Installation Complexity Tiers

Simple

iPad only

Just the iPad. No wall screen, no hardware. Sales agent walks around with it. Good for events, temporary showrooms, early-stage sales.

Devices: 1 · Setup time: minutes

Standard

iPad + screen

iPad mirroring or extending to wall screen. The most common setup. AirPlay or HDMI. No lighting hardware.

Devices: 2-3 · Setup time: hours

Full

iPad + screen + lighting

iPad, wall screen, Linux box, LightSwarm, physical model. The full experience. Requires site visit, cable routing, calibration.

Devices: 4-6 · Setup time: 1-2 days

10

Multi-Screen Modes

How the iPad connects to the wall screen. React app loads in a SwiftUI wrapper (WKWebView) that handles the multi-screen logic.

1. Mirroring

AirPlay — simplest

iPad AirPlays to display. Same content, 4:3.

  • Zero overhead, no extra dev
  • 4:3 letterboxed, can't differ

2. Extended Display

Different content per screen

iPad (4:3) + screen (16:9), different content. Same app, shared ViewModel, two WebViews.

  • Bespoke layout, 16:9, single app
  • Dual render overhead

3. Apple TV App

Two apps, Multipeer Connectivity

Separate iPad + ATV apps. ATV advertises, iPad discovers. Most flexible.

  • Independent, multi-screen
  • Two apps, peer complexity

React side: Sender in Zustand store, Receiver hook on ScreenView. App doesn't care which mode. Philosophy: simplest config that meets requirements.

11

Deployment

Web

Vite SPA → Cloudflare Pages / AWS / Vercel. Bitbucket Pipelines CI/CD. Deploys in minutes.

iPad

Capacitor wrapper. TestFlight OTA updates. Idle timeout auto-reset, offline caching, full-screen locked down.

Desktop

Wails (Go + React). Standalone executables. Legacy approach — being phased out in favour of iPad.

Linux (Lighting)

Headless Linux box running LightSwarm server. Serial connection to hardware. Set and forget.