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.
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.
| Reel | Source File | Dark URL | Light URL |
|---|---|---|---|
| Signal Path | SignageApp.tsx | /signage | /signage |
| Infrastructure Edit | InfrastructureEdit.tsx | /infra | /infra |
| Night Edition | NightEdition.tsx | /night | /night |
| Student Mode | StudentMode.tsx | /student | /student |
| Micro Loop | MicroLoop.tsx | /micro | /micro |
| Mostro Mode | MostroMode.tsx | /mostro | /mostro |
| Civic Greeting | CivicGreeting.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.
| Concern | Technology | Notes |
|---|---|---|
| Framework | React 18 + TypeScript | Strict mode enabled |
| Build tool | Vite 7 | pnpm run build → dist/ |
| Styling | Inline CSS-in-JS | No CSS files, no Tailwind, no libraries |
| Routing | Custom (History API) | No React Router dependency |
| Deployment | Cloudflare Pages | Via Wrangler CLI or Git integration |
| Package manager | pnpm | Do not use npm or yarn |
| Fonts | Google Fonts (CDN) | Loaded in index.html |
04 Project Structure
The structure is identical for both crs-signage and crs-signage-light.
├─ 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
| Export | Type | Contents |
|---|---|---|
C | const object | All brand colours as hex strings (e.g. C.teal, C.amber, C.bg) |
T | const object | Font family strings — T.display and T.body / T.mono |
frameTitleStyle | CSSProperties | Shared headline style — size, weight, tracking, line-height |
frameSubtitleStyle | CSSProperties | Shared subtitle style |
frameBodyStyle | CSSProperties | Shared body copy style |
priceBlockStyle | CSSProperties | Flex container for pricing items |
priceItemStyle | CSSProperties | Individual pricing card |
priceAmountStyle | CSSProperties | Large price figure |
priceLabelStyle | CSSProperties | Small price label (e.g. "per hour") |
Dark System palette
| Token | Value | Use |
|---|---|---|
C.bg | #0A0A0A | Primary background |
C.brass | #C2A85A | Primary accent — headlines, labels |
C.green | #2E473B | Structural accent |
C.text | #E8E0D0 | Primary text |
Light System palette
| Token | Value | Use |
|---|---|---|
C.bg | #F7F5F2 | Warm off-white background |
C.teal | #0F766E | Primary accent — trust & professionalism |
C.amber | #E6A23C | Energy & CTA accent |
C.text | #1F2933 | Charcoal 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.
Editing a reel
Open the reel's .tsx file. The FRAMES array at the top controls everything:
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
| Priority | Window | Reel |
|---|---|---|
| 1 | Wednesday 18:30–23:30 | Student Mode |
| 2 | Friday 18:00–22:00 | Student Mode |
| 3 | Any day 18:00–07:00 | Night Edition |
| 4 | Weekend 11:00–16:00 | Mostro Mode |
| 5 | Weekday 15:00–18:30 | Mostro Mode |
| 6 | Weekday 07:00–15:00 | Infrastructure Edit |
| 7 | Catch-all fallback | Signal Path |
Civic Greeting injection cadence
| Context | Inject every |
|---|---|
| Daytime (07:00–18:00) | 2 reel loops |
| Evening (18:00–22:30) | 4 reel loops |
| Late night / early morning | 3 reel loops |
| Wednesday student night | Never |
| Micro Loop | Never |
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
# Dark System
gh repo clone captainburbseye-web/crs-signage
# Light System
gh repo clone captainburbseye-web/crs-signage-light
pnpm — do not use npm or yarn.cd crs-signage
pnpm install
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.
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
# 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
# 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.
| Setting | Value |
|---|---|
| Framework preset | Vite |
| Build command | pnpm run build |
| Build output directory | dist |
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.
https://crs-signage.pages.dev/auto
https://crs-signage.pages.dev/night
https://crs-signage.pages.dev/student
https://crs-signage.pages.dev/mostro
/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.