Canvas Design System
Main Site Tokens

Workshopr Companion (iOS)

The Companion is a facilitator's in-the-room tool: a native React Native / Expo app that runs live while the web platform stays back at the planning stage. It is not a phase app in the Learn → Plan → Facilitate → Reflect sense — it's a cross-phase in-room companion that a facilitator opens once the workshop is running. Web plans; Companion runs the room.

App Identity

Phase
Cross-phase (in-room tool)
Primary Color
#E54D4D
Font Stack
Fraunces + Inter

Not a web page. This is a native iOS app (React Native / Expo) — a separate codebase at workshopr-companion/, with no route under this platform and no --companion-primary CSS token. It ships the same unified Learn Red family as the rest of the platform (D1, Red Unification) — there is no separate "mobile brand."

Design Philosophy

The Companion is a stage tool, not a dashboard. It's read at arm's length on a table or glanced at mid-sentence while facilitating — every decision optimizes for that, not for density.

home
One home, two escapes
Run Mode is the root/home route. Cards and Parking Lot are secondary modal escapes reachable in one tap, each with an explicit close control that returns home. There is no tab bar and never three equal-weight destinations — the moment they become equal, it's the cluttered grab-bag the product deliberately avoids.
timer
One dominant stage surface
The warm-charcoal "stage" card carrying the current block and its timer is the one strong structural move on the Run Mode screen. Everything else recedes around it — no box-in-box clutter competing for attention.
touch_app
Tactile, not hover
There is no hover on a phone. Every tappable surface uses PressableScale — a scale-down (0.97) plus haptic feedback on press — instead of opacity-only or color-only feedback.
visibility
Arm's-length legibility
No text below ~13px anywhere in the app. Generous 4pt-derived spacing. Dynamic Type is respected throughout. The timer numeral (64px Fraunces Bold, tabular figures) is the largest, most confident piece of type on the platform — it has to be readable from across a room.

Mobile Palette

Defined in workshopr-companion/src/theme/colors.ts — the code source of truth. Never hardcode hex in a Companion component; always go through useAppTheme().colors. Light-first: the app is used in daylight rooms, not a dark-mode dashboard. A darkColors variant exists and is wired through ThemeProvider, but v1 pins light unconditionally.

Surface & Text

Page
pageCream
#FDF8F1
Surface
surfaceWhite
#FFFFFF
Stage
stageCharcoal
#241C13
Ink
ink
#2A2116
Ink Secondary
inkSecondary
#6B6152
Ink on Stage
inkOnStage
#FDF8F1
Ink on Stage Sec.
inkOnStageSecondary
#C9BFAE

Brand & Accent

Red
#E54D4D
Red Dark
#C73E3E
Red Tint
#FBEAE8
Gold
#B8894B
Border
#E7E5E4
Border on Stage
#3C3226
Success
#3F8A5C

Timer States

The single highest-stakes visual signal in the app. Per the accessibility NFR, state is never conveyed by color alone — each state also changes the label and icon.

Normal
ink numerals, no badge
Warning
gold + "WRAPPING UP" + clock badge
Overrun
red-dark + "OVER TIME" + alert icon
/* workshopr-companion/src/theme/colors.ts — the code source of truth.
   These are NOT platform-tokens.css variables: the Companion is a
   standalone RN/Expo app, not a page rendered by this stylesheet. */
pageCream:            '#FDF8F1'
surfaceWhite:          '#FFFFFF'
stageCharcoal:         '#241C13'  /* the one dark "stage" surface */
ink:                   '#2A2116'
inkSecondary:          '#6B6152'
inkOnStage:            '#FDF8F1'
inkOnStageSecondary:   '#C9BFAE'
red:                   '#E54D4D'
redDark:               '#C73E3E'
redTint:               '#FBEAE8'
gold:                  '#B8894B'
border:                '#E7E5E4'
borderOnStage:         '#3C3226'
success:               '#3F8A5C'

/* Timer states */
timerNormal:   ink       /* #2A2116 */
timerWarning:  gold      /* #B8894B */
timerOverrun:  redDark   /* #C73E3E */

Mobile Type Scale

Same brand fonts as web — Fraunces for display, Inter for body — never swapped. Defined in workshopr-companion/src/theme/typography.ts and only ever consumed through the Text primitive, never React Native's raw Text. No text below ~13px anywhere in the app.

Variant Font Size / Line Height Use
display Fraunces Bold 34 / 40 Largest display moments
title Fraunces SemiBold 26 / 32 Screen titles
heading Fraunces SemiBold 20 / 26 Block titles, card headers
body Inter Regular 16 / 23 Body copy
bodyMedium Inter Medium 16 / 23 Emphasized body copy
label Inter SemiBold 13 / 17 (+0.3 tracking) Labels, chips, outline steps
caption Inter Regular 13 / 18 Captions, meta text
timer Fraunces Bold, tabular-nums 64 / 68 The hero numeral of Run Mode
timerCompact Fraunces Bold, tabular-nums 28 / 32 Compact timer contexts (rows, sheets)

Spacing & Radius (4pt grid)

Generous by default — Run Mode is read at arm's length, not scanned like a dense dashboard. Defined in src/theme/spacing.ts.

spacing: xxs 4 · xs 8 · sm 12 · md 16 · lg 24 · xl 32 · xxl 48 · xxxl 64
radius:  sm 8 · md 12 · lg 16 · xl 24 · pill 999

iOS Component Inventory

Live in workshopr-companion/src/components/. Grouped by area — primitives every screen uses, Run Mode (the home surface), Capture (Parking Lot), and Cards.

Primitives

Screen
iOS-specific
Safe-area wrapper every screen root should use. Accepts a background theme token and an optional padded flag. Respects top/bottom insets and the home indicator.
Text
iOS-specific
The only text component in the app — never React Native's raw Text. Takes a variant (display/title/heading/body/bodyMedium/label/caption/timer/timerCompact) and a color (any theme color token). Supports Dynamic Type.
Card
iOS-specific
Base grouped-content surface: rounded, optionally bordered or elevated. The foundation the stage card and capture cards build on.
PressableScale
iOS-specific
The tactile press primitive — the mobile replacement for hover. Scale-down (default 0.97) plus haptic feedback on press, never opacity-only. Every tappable surface uses this instead of raw Pressable/TouchableOpacity. Fast spring: speed 40 no-bounce on press-in, speed 30 slight-bounce on release.
AppIcon
iOS-specific
Thin wrapper over SF Symbols on iOS / Material Symbols on Android (expo-symbols' SymbolView). Pass plain icon-name strings; verify against the Apple SF Symbols app or fonts.google.com/icons before adding new ones.

Run Mode

CurrentBlockCard
iOS-specific
The warm-charcoal "stage" card carrying the current block and its big timer — the one dominant structural element on the Run Mode screen. Everything else recedes around it.
TimerDisplay
iOS-specific
Renders the timer numeral and its three color/label/icon states (normal, warning, overrun). Timestamp-based: computed from a stored startedAt, not a ticking interval, so it survives the device lock screen without drifting.
BlockRow
iOS-specific
A single row in the running order — used in the block outline formSheet to show what's next and what's past.
BlockTypeBadge
iOS-specific
Small type-coded badge (exercise / icebreaker / break / custom) on block rows — the mobile analog of the planner's activity-type coding.
EscapeBar
iOS-specific
Persistent secondary footer offering the two escapes (Cards, Parking Lot). Explicitly not a tab bar — it must never read as three equal-weight destinations alongside Run Mode.
EmptyState
Shared pattern
No session selected, no blocks yet, etc. Always an invitation with a next step, never a blank screen — mirrors ws_empty_state()'s philosophy on web.

Capture (Parking Lot)

ShutterButton
iOS-specific
The primary capture action — large, camera-app-style tap target for logging an Output/Note/Quote in the moment.
CaptureTypeControl
iOS-specific
Segmented control for Output / Note / Quote — sets what kind of capture is being logged.
BlockTagChip
iOS-specific
Auto-tag chip attaching a capture to the block that was running when it was taken.
AudioRecorderControls
iOS-specific
Record/stop/playback controls for audio captures.
QueueStrip
iOS-specific
Shows synced vs. queued counts — the visible sign that offline-first capture is working while the app waits for connectivity.
PermissionGate
iOS-specific
Blame-free permission-request screen (mic/camera) with a clear retry path — never a dead-end denial.
TextCaptureSheet
iOS-specific
Bottom sheet for typed captures, as an alternative to the shutter/audio flows.
CaptureConfirmation
iOS-specific
Brief confirmation state after a capture is saved, before returning to the capture flow or Run Mode.

Cards

TakeABeatHeader
iOS-specific
Header for the Cards escape — framing a quick reset/reflection moment mid-facilitation.
CategoryGrid / CategoryTile
iOS-specific
Grid of card categories to pick from, each tile a tappable entry point into a category's cards.
MoveCard
iOS-specific
The 30-second card itself — the phrase or prompt is the Fraunces hero of the card, set large and confident, the same way the timer is the hero of Run Mode.
CardStepper
iOS-specific
Step-through control for moving between cards in a category.

iOS Patterns & Platform Notes

What's different from the web platform, and why. These aren't stylistic choices — they follow from the app being native, live, and used mid-facilitation.

Pattern What it means
One home, two escapes Run Mode is the root route. Cards (src/app/cards.tsx) and Parking Lot (src/app/parking-lot.tsx) are presentation: 'modal' routes, each with an explicit close control back to Run Mode. Never a tab bar; never three equal-weight destinations.
Safe areas Every screen root uses the Screen primitive to respect top/bottom insets and the home indicator, portrait only.
Tactile press + haptics No hover exists on a phone. PressableScale (0.97 scale + haptic) replaces hover as the feedback channel everywhere.
Dynamic Type & arm's-length legibility This is a stage tool glanced at from a table, not scanned up close. Type respects Dynamic Type; nothing renders below ~13px; the timer is set at 64px specifically to be readable across a room.
Dark-mode-ready, light-first A full darkColors variant exists and is wired through ThemeProvider, but v1 pins light unconditionally — Run Mode is a daylight tool and the timer's color-coded states need to stay predictable across every venue. Flipping to useColorScheme() is a one-line change when dark mode is prioritized.
Timestamp-based timer The Run Mode timer is computed from a stored startedAt value, not a ticking interval — it survives the device being locked and stays accurate when the app resumes.
Offline-first Captures queue locally and sync when connectivity returns. QueueStrip makes the synced/queued state visible so the facilitator trusts the app mid-session without a network.
SF Symbols iconography Icons render as native SF Symbols on iOS (Material Symbols on Android) via AppIcon, not the web platform's Material-only icon font.
info
Tech: Expo Router (file-based routing). Tokens live in workshopr-companion/src/theme/ and mirror the values on this page exactly — if they ever disagree, the code wins. See workshopr-companion/DESIGN.md for the full design bible.

Do / Don't

Do: Keep Run Mode the unambiguous home
Cards and Parking Lot are escapes the facilitator visits and leaves. Every path back leads to Run Mode.
Don't: Add a tab bar
Three equal-weight tabs turn a focused stage tool into a grab-bag. This has been explicitly rejected in the app's own design bible.
Do: Use PressableScale on every tappable surface
Scale + haptic feedback replaces hover, which doesn't exist on a phone.
Don't: Rely on opacity or color alone for press feedback
It reads as unresponsive on a physical tap, and fails the "more than color" accessibility bar the timer states also have to clear.
Do: Let the timer or the card phrase be the hero
One strong structural move per screen — the stage card in Run Mode, the Fraunces phrase on a Move Card.
Don't: Stack competing focal points
Box-in-box clutter defeats arm's-length legibility. If everything is emphasized, nothing is.

Shared System Alignment

The Companion doesn't consume ws_* PHP helpers — it's a separate native codebase — but its component philosophy mirrors the web design system's discipline.

Web concept Companion equivalent Alignment
ws_empty_state() EmptyState Same philosophy: an invitation with a next step, never a blank screen.
Learn Red brand family colors.ts red / redDark / redTint Identical hex values (#E54D4D / #C73E3E), per D1 Red Unification — one brand, two codebases.
Fraunces + Inter font mandate typography.ts textVariants Same two font families, same never-swap rule.
Planner activity-type colors (exercise blue, icebreaker green, break gray, custom purple) BlockTypeBadge Same functional-coding exception rationale: items must stay distinguishable at a glance.
Token-first styling discipline useAppTheme().colors Never hardcode hex in a component — always resolve through the theme, same rule as "never hardcode hex when a token exists" on web.