CRS Signage Systems

Complete developer reference for the two parallel CRS digital signage systems. Built with React + Vite, deployed to Cloudflare Pages, integrated with Yodeck.

● Live React 18 + TypeScript Vite Cloudflare Pages Updated 25 Feb 2026

01 Overview

The CRS Signage System comprises two parallel applications — the Dark System (industrial, moody, street-facing) and the Light System (Scandinavian, clean, internal-facing). Both are standalone React + Vite applications deployed to Cloudflare Pages.

Each system contains 7 distinct "reels" (thematic carousels) plus a time-based Auto-Router that selects the correct reel by time of day and day of week. They are designed to run full-screen on digital signage displays, primarily via Yodeck integration.

02 Live URLs & Repositories

Each reel is accessible at a dedicated sub-path on its respective domain. The /auto path runs the time-based scheduler and is the recommended URL for Yodeck.

ReelSource FileDark URLLight URL
Signal PathSignageApp.tsx/signage/signage
Infrastructure EditInfrastructureEdit.tsx/infra/infra
Night EditionNightEdition.tsx/night/night
Student ModeStudentMode.tsx/student/student
Micro LoopMicroLoop.tsx/micro/micro
Mostro ModeMostroMode.tsx/mostro/mostro
Civic GreetingCivicGreeting.tsx/civic/civic
Auto-Router ★AutoRouter.tsx/auto/auto

Point Yodeck to /auto on your chosen domain. The scheduler handles everything — reel selection, Civic Greeting injection, and schedule re-evaluation on every loop.

03 Technical Architecture

Both projects share an identical architecture, differing only in their brand implementation — colours, typography, and component styling. There are no external CSS libraries or component frameworks; all styling is inline CSS-in-JS.

ConcernTechnologyNotes
FrameworkReact 18 + TypeScriptStrict mode enabled
Build toolVite 7pnpm run builddist/
StylingInline CSS-in-JSNo CSS files, no Tailwind, no libraries
RoutingCustom (History API)No React Router dependency
DeploymentCloudflare PagesVia Wrangler CLI or Git integration
Package managerpnpmDo not use npm or yarn
FontsGoogle Fonts (CDN)Loaded in index.html

04 Project Structure

The structure is identical for both crs-signage and crs-signage-light.

crs-signage/
├─ dist/ # Build output — deployed to Cloudflare
├─ public/ # Static assets (currently empty)
├─ src/
│ ├─ brand.ts # ★ Core brand constants — edit here first
│ ├─ CRSShell.tsx # Shared chrome (contact strip, LED, badge)
│ ├─ AutoRouter.tsx # ★ Schedule logic — edit SCHEDULE array here
│ ├─ main.tsx # Entry point, URL router, nav overlay
│ ├─ SignageApp.tsx # Reel: Signal Path (main, 9 frames)
│ ├─ InfrastructureEdit.tsx # Reel: Infrastructure Edit (8 frames)
│ ├─ NightEdition.tsx # Reel: Night Edition (8 frames)
│ ├─ StudentMode.tsx # Reel: Student Mode (7 frames)
│ ├─ MicroLoop.tsx # Reel: Micro Loop (3.2s interrupt)
│ ├─ MostroMode.tsx # Reel: Mostro Mode (6 frames)
│ └─ CivicGreeting.tsx # Reel: Civic Greeting (8s interjection)
├─ index.html # Loads Google Fonts, full-screen reset
├─ package.json
├─ tsconfig.json
└─ vite.config.ts

05 Brand System

brand.ts is the single source of truth for all visual tokens. It exports two primary objects and several shared style objects used across all reels.

Always edit brand.ts first when changing colours, typography, or shared layout rules. Do not hardcode hex values in reel files.

Exported tokens

ExportTypeContents
Cconst objectAll brand colours as hex strings (e.g. C.teal, C.amber, C.bg)
Tconst objectFont family strings — T.display and T.body / T.mono
frameTitleStyleCSSPropertiesShared headline style — size, weight, tracking, line-height
frameSubtitleStyleCSSPropertiesShared subtitle style
frameBodyStyleCSSPropertiesShared body copy style
priceBlockStyleCSSPropertiesFlex container for pricing items
priceItemStyleCSSPropertiesIndividual pricing card
priceAmountStyleCSSPropertiesLarge price figure
priceLabelStyleCSSPropertiesSmall price label (e.g. "per hour")

Dark System palette

TokenValueUse
C.bg#0A0A0APrimary background
C.brass#C2A85APrimary accent — headlines, labels
C.green#2E473BStructural accent
C.text#E8E0D0Primary text

Light System palette

TokenValueUse
C.bg#F7F5F2Warm off-white background
C.teal#0F766EPrimary accent — trust & professionalism
C.amber#E6A23CEnergy & CTA accent
C.text#1F2933Charcoal primary text

06 Reel Components

Each reel is a self-contained React component. It defines its own FRAMES array at the top of the file — this is where all copy, durations, and pricing live. All reels are wrapped in CRSShell for the persistent UI chrome.

Signal Path
SignageApp.tsx
Dark: /signage
Light: /signage
Infrastructure Edit
InfrastructureEdit.tsx
Dark: /infra
Light: /infra
Night Edition
NightEdition.tsx
Dark: /night
Light: /night
Student Mode
StudentMode.tsx
Dark: /student
Light: /student
Micro Loop
MicroLoop.tsx
Dark: /micro
Light: /micro
Mostro Mode
MostroMode.tsx
Dark: /mostro
Light: /mostro
Civic Greeting
CivicGreeting.tsx
Dark: /civic
Light: /civic

Editing a reel

Open the reel's .tsx file. The FRAMES array at the top controls everything:

TypeScript — FRAMES array pattern
const FRAMES = [
  {
    id: 'identity',
    duration: 12000,          // ms before advancing to next frame
    label: 'COWLEY ROAD STUDIOS', // small caps label above title
    title: "Oxford's Creative\nHeadquarters.",
    subtitle: 'Rehearse. Record. Work. Connect.',
    showPricing: true,
    pricingKey: 'recording',  // key into the PRICING object below
    accentColor: C.teal,       // overrides the default accent
  },
  // ... more frames
]

07 Auto-Router

AutoRouter.tsx contains the master schedule. It selects the correct reel by evaluating the current day and time against an ordered SCHEDULE array. The first matching window wins.

To change the signage schedule, edit the SCHEDULE array in AutoRouter.tsx. Windows are evaluated top-to-bottom — put more specific rules first.

Default schedule

PriorityWindowReel
1Wednesday 18:30–23:30Student Mode
2Friday 18:00–22:00Student Mode
3Any day 18:00–07:00Night Edition
4Weekend 11:00–16:00Mostro Mode
5Weekday 15:00–18:30Mostro Mode
6Weekday 07:00–15:00Infrastructure Edit
7Catch-all fallbackSignal Path

Civic Greeting injection cadence

ContextInject every
Daytime (07:00–18:00)2 reel loops
Evening (18:00–22:30)4 reel loops
Late night / early morning3 reel loops
Wednesday student nightNever
Micro LoopNever

How loop re-evaluation works

Each reel fires an onLoopComplete callback when it finishes one full cycle. AutoRouter increments the loop counter, re-runs the schedule resolver (so a time window change mid-loop is caught), and decides whether to inject a Civic Greeting. A 60-second fallback tick also runs in the background for any reel that does not yet fire onLoopComplete.

08 Local Development

1
Clone the repository
Use the GitHub CLI to clone either system.
bash
# Dark System
gh repo clone captainburbseye-web/crs-signage

# Light System
gh repo clone captainburbseye-web/crs-signage-light
2
Install dependencies
Use pnpm — do not use npm or yarn.
bash
cd crs-signage
pnpm install
3
Start the dev server
Available at http://localhost:5173. The REELS ▾ button in the top-right corner of every reel opens a nav overlay for switching between all 7 reels without touching the URL bar.
bash
pnpm dev

09 Deployment

Both projects are deployed to Cloudflare Pages using the Wrangler CLI. A Cloudflare API token with Pages write permissions is required for manual deploys.

Manual deployment

bash — Dark System
# 1. Build
pnpm run build

# 2. Deploy
CLOUDFLARE_API_TOKEN=<API_TOKEN> \
CLOUDFLARE_ACCOUNT_ID=<ACCOUNT_ID> \
npx wrangler pages deploy dist \
  --project-name=crs-signage \
  --branch=main
bash — Light System
# 1. Build
pnpm run build

# 2. Deploy
CLOUDFLARE_API_TOKEN=<API_TOKEN> \
CLOUDFLARE_ACCOUNT_ID=<ACCOUNT_ID> \
npx wrangler pages deploy dist \
  --project-name=crs-signage-light \
  --branch=main

Continuous deployment (recommended)

Connect the GitHub repo to Cloudflare Pages for automatic deploys on every push to main.

1
Open Cloudflare Pages settings
Navigate to Workers & Pages → [project-name] → Settings → Builds & deployments.
2
Connect to Git
Click Connect to Git and select the appropriate GitHub repository.
3
Configure build settings
Use these exact settings:
SettingValue
Framework presetVite
Build commandpnpm run build
Build output directorydist

10 Yodeck Integration

The signage systems are designed to run full-screen as Web Page widgets in Yodeck. The /auto route handles all scheduling automatically — no playlist management needed for day-to-day operation.

1
Create a Web Page widget
In your Yodeck account, create a Web Page widget and set the URL to the auto-router:
URL
https://crs-signage.pages.dev/auto
2
Manual reel overrides
To force a specific reel (e.g. for a special event), create a separate Web Page widget pointing to the reel's direct URL:
URL examples
https://crs-signage.pages.dev/night
https://crs-signage.pages.dev/student
https://crs-signage.pages.dev/mostro
3
Micro Loop interrupt
The Micro Loop is a 3.2s kinetic snap-cut interrupt. Add it as a separate widget pointing to /micro, set its duration to 4 seconds, and insert it into your evening playlist every 45–60 seconds.

The REELS ▾ nav button is invisible to viewers at signage distance and causes no interference. It is purely for operator use during testing and content review.