RTOpacks Design Foundation¶
Version: 1.22
Status: Canonical
Maintained at: docs.rtopacks.com.au/design/foundation
Usage: Fetch this document at the start of every brief. All values are hard constraints. Do not deviate without explicit written instruction from Tim Rignold.
0. Purpose¶
This document is the single source of truth for every visual and interaction decision on every RTOpacks surface. It is not a mood board. It contains actual values. When a brief says "follow the design foundation," this is the document that phrase refers to.
RTOpacks is a professional tool for the Australian VET sector. The design must communicate competence and clarity. Everything that looks designed is designed to reduce cognitive load. Nothing is decorative.
The design system is derived from Apple's Human Interface Guidelines applied to a web product. The font is Inter (not SF Pro — that is Apple-proprietary). The icon system is SF Symbols exported as SVG. The colour palette, type scale, motion model, and accessibility architecture are all Apple-derived but RTOpacks-owned.
No surface should look like it was generated by an AI tool. If it does, something is wrong.
Every brief touching an authenticated surface (my.rtopacks.com.au, admin.rtopacks.com.au) must fetch §21 before writing any visual treatment involving translucency, blur, or depth.
1. Colour¶
1.1 Brand Identity¶
RTOpacks brand colour is not teal. Teal is reserved exclusively for Knowledge Navigator.
The RTOpacks brand accent is #2563eb — a clean, confident blue. Used for primary actions, focus rings, interactive elements, and brand moments.
The background palette is derived from a deep blue-black stack — not grey, not green, not charcoal. This is a deliberate departure from AI-generated default palettes (dark grey + teal/green). Any surface that looks like a terminal or a Claude output is wrong.
1.2 CSS Custom Properties — Dark Mode (default)¶
:root {
/* Backgrounds — three-level depth stack */
--color-bg-base: #0d1117; /* Page background — deepest level */
--color-bg-elevated: #161b22; /* Panels, drawers, modals — one step up */
--color-bg-card: #1c2128; /* Cards, KN blocks, inset sections */
--color-bg-hover: #21262d; /* Hover state on interactive elements */
/* Text */
--color-text-primary: #e6edf3; /* Main body text — NOT pure white */
--color-text-secondary: #8b949e; /* Supporting text, metadata, captions */
--color-text-disabled: #484f58; /* Disabled state */
/* Borders and dividers */
--color-border: #30363d; /* Standard borders */
--color-border-subtle: #21262d; /* Hairline separators */
/* Brand accent — primary interactive colour */
--color-interactive: #2563eb; /* Links, focus rings, primary buttons */
--color-interactive-hover: #1d4ed8;
/* Knowledge Navigator — teal is KN-ONLY, never used elsewhere */
--color-kn-accent: #0f9e82; /* KN toggle, criterion numbers, KN borders — UI chrome only */
--color-kn-bg: rgba(15, 158, 130, 0.08); /* KN block background tint */
--color-kn-border: rgba(15, 158, 130, 0.4); /* KN block left border */
--color-kn-text: #4ec9a8; /* KN body text — distinct voice from TGA content */
/* Status colours */
--color-success: #3fb950;
--color-warning: #d29922;
--color-danger: #f85149;
--color-info: #58a6ff;
/* Recon signal (RTOpacks-specific) */
--color-recon-low: #f85149;
--color-recon-mid: #d29922;
--color-recon-high: #3fb950;
/* Surface overlay — video background HUD (see §4.6) */
--color-surface-overlay: rgba(22, 27, 34, 0.84);
}
1.3 CSS Custom Properties — Light Mode¶
Applied via :root[data-theme="light"]
:root[data-theme="light"] {
--color-bg-base: #ffffff;
--color-bg-elevated: #f6f8fa;
--color-bg-card: #eaeef2;
--color-bg-hover: #f3f4f6;
--color-text-primary: #1c2128;
--color-text-secondary: #57606a;
--color-text-disabled: #8c959f;
--color-border: #d0d7de;
--color-border-subtle: #eaeef2;
--color-interactive: #0969da;
--color-interactive-hover: #0550ae;
--color-kn-accent: #0f9e82;
--color-kn-bg: rgba(15, 158, 130, 0.06);
--color-kn-border: rgba(15, 158, 130, 0.5);
--color-kn-text: #0a7d68; /* KN body text — dark teal-green, sufficient contrast on --color-bg-card */
--color-success: #1a7f37;
--color-warning: #9a6700;
--color-danger: #cf222e;
--color-info: #0969da;
/* Surface overlay — video background HUD (see §4.6) */
--color-surface-overlay: rgba(246, 248, 250, 0.90);
}
1.4 High Contrast Overrides¶
Applied via :root[data-contrast="high"]
:root[data-contrast="high"] {
--color-text-primary: #ffffff;
--color-text-secondary: #c9d1d9;
--color-border: #8b949e;
--color-interactive: #79c0ff;
--color-kn-accent: #56d364;
}
1.5 Colour Rules¶
- Teal (
--color-kn-accent) appears only on KN-related UI chrome: the KN toggle, criterion number spans, KN block borders, KN block backgrounds. Never on buttons, links, headers, or decorative elements. --color-kn-textis for KN body text — the interpretation content itself. It creates a visible chromatic separation between TGA performance criteria (rendered in--color-text-primary) and KN interpretation blocks (rendered in--color-kn-text). This two-voice distinction is intentional and must be preserved. Do not revert--color-kn-textto a faded white or grey value.--color-interactive(blue) is for everything else that is interactive: links, focus rings, primary CTA buttons, active states.- Colour is never the only signal. Every state change communicated by colour must also be communicated by shape, label, or icon.
- WCAG AA minimum: 4.5:1 contrast ratio for body text, 3:1 for large text (18pt+) and bold text.
- All page-level background containers must use
var(--color-bg-base)as their CSS background property. The value#07090fis not in the Foundation palette and must not appear anywhere in the codebase. When the video background fails to load, the fallback must be--color-bg-base, not a hardcoded value.
1.6 Scrim & Floating Surface Tokens¶
Scrims are semi-transparent overlays used for backdrops (drawer overlays, modal underlays) and instrument backgrounds. Floating surfaces are near-opaque containers that sit above all other content (command palettes, floating panels).
:root {
--color-scrim-heavy: rgba(0, 0, 0, 0.4); /* Drawer/modal backdrop, unit panel shadow */
--color-scrim-light: rgba(0, 0, 0, 0.15); /* Instrument background, subtle depth layer */
--color-bg-float: rgba(7, 9, 15, 0.95); /* Command palette, floating panels — near-opaque over video */
}
These tokens replace hardcoded rgba(0,0,0,...) values in backdrop overlays and rgba(7,9,15,0.95) on floating containers. They are not theme-switched — scrims are always dark regardless of mode.
1.7 Video Hero Surface Tokens¶
The front page hero sits over a video background. Text and inputs on this surface use white-channel rgba values that are not part of the standard text token set (those are designed for solid backgrounds, not video overlays).
:root {
--color-hero-input: rgba(255, 255, 255, 0.92); /* Search input text on video */
--color-hero-text-primary: rgba(255, 255, 255, 0.85); /* Nav links, hero headlines on video */
--color-hero-text-secondary: rgba(255, 255, 255, 0.65); /* Supporting text, metadata on video */
}
These tokens apply only to elements rendered directly over the video background (the homepage hero). They do not apply inside panels, drawers, or any surface that has its own background-color. Once content sits on a solid background, use the standard --color-text-* tokens.
1.8 Badge Colour Tokens¶
Industry Badges¶
Industry sector badges use low-opacity tints of their category colour. These appear on RTO cards and search results to indicate the RTO's primary industry sector.
:root {
--color-badge-construction: rgba(217, 119, 6, 0.15); /* Construction & trades — amber */
--color-badge-emergency: rgba(220, 38, 38, 0.15); /* Emergency services — red */
--color-badge-creative: rgba(124, 58, 237, 0.15); /* Creative & digital — violet */
--color-badge-general: rgba(107, 114, 128, 0.15); /* General / uncategorised — neutral grey */
--color-badge-transport: rgba(6, 182, 212, 0.15); /* Transport & logistics — cyan */
}
Badge text colour is the full-opacity version of the same hue (e.g. rgb(217, 119, 6) text on --color-badge-construction background). Contrast must meet 3:1 minimum against the badge background.
RTO Type Badges¶
All RTO type badges use 0.15 opacity tints. Badge text colour is the full-opacity version of the same hue — must meet 3:1 contrast minimum against badge background.
:root {
/* Blue group — interactive-derived */
--color-badge-rto-private: color-mix(in srgb, var(--color-interactive) 15%, transparent);
--color-badge-rto-supplier: color-mix(in srgb, var(--color-interactive) 15%, transparent);
/* Distinct per-type colours */
--color-badge-rto-industry: rgba(217, 119, 6, 0.15); /* amber */
--color-badge-rto-school: rgba(22, 163, 74, 0.15); /* green */
--color-badge-rto-enterprise: rgba(71, 85, 105, 0.15); /* slate */
--color-badge-rto-professional: rgba(124, 58, 237, 0.15); /* violet */
--color-badge-rto-university: rgba(79, 70, 229, 0.15); /* indigo */
--color-badge-rto-other: rgba(107, 114, 128, 0.15); /* grey */
}
Private and Supplier RTOs share the interactive-blue tint. Community uses --color-success at 12%. TAFE uses --color-kn-accent at 12%. These are not theme-switched — consistent across light and dark mode.
Badge Colour Reference¶
| Token | Swatch | Value |
|---|---|---|
--color-badge-construction | rgba(217, 119, 6, 0.15) | |
--color-badge-emergency | rgba(220, 38, 38, 0.15) | |
--color-badge-creative | rgba(124, 58, 237, 0.15) | |
--color-badge-general | rgba(107, 114, 128, 0.15) | |
--color-badge-transport | rgba(6, 182, 212, 0.15) | |
--color-badge-rto-private | color-mix(in srgb, var(--color-interactive) 15%, transparent) | |
--color-badge-rto-supplier | color-mix(in srgb, var(--color-interactive) 15%, transparent) | |
--color-badge-rto-industry | rgba(217, 119, 6, 0.15) | |
--color-badge-rto-school | rgba(22, 163, 74, 0.15) | |
--color-badge-rto-enterprise | rgba(71, 85, 105, 0.15) | |
--color-badge-rto-professional | rgba(124, 58, 237, 0.15) | |
--color-badge-rto-university | rgba(79, 70, 229, 0.15) | |
--color-badge-rto-other | rgba(107, 114, 128, 0.15) |
1.9 Classification Badge System¶
RTOpacks operates across two classification axes: entity type (who is this?) and content type (what is this?). These axes are independent. A badge appears only when it adds signal — absence is intentional and meaningful.
Vocabulary ownership¶
ASQA owns these terms. RTOpacks renders them but does not invent alternatives:
| Badge | Term owner | Applies to | Meaning |
|---|---|---|---|
| NRT | ASQA | Content / courses | Nationally Recognised Training — mapped to a TGA training package, has a TGA component code, sits inside the national regulatory framework |
| RTO | ASQA | Entities | Registered Training Organisation — ASQA-regulated, holds a current registration |
UCCA owns these terms. These are our classification layer for content that sits outside the national framework:
| Badge | Term owner | Applies to | Meaning |
|---|---|---|---|
| GEN | UCCA | Content / courses | General — self-certified by the delivering entity, outside the national regulatory framework. Includes micro-credentials, corporate courses, onboarding modules, and any content not mapped to a TGA component code. Not inferior — simply non-regulated. |
| (nothing) | — | Non-RTO entities | Absence is the signal. Course writers, consultants, and other non-RTO entities carry no entity badge. Do not label them "non-RTO". |
The auditor mental model¶
An auditor or compliance professional looking at a course needs one immediate answer: is this regulated or not?
- NRT badge present → in scope, regulated, mapped to TGA
- GEN badge present → out of scope, self-certified, not mapped to TGA
- No badge → context makes it obvious (e.g. a unit inside a qual tab is always NRT)
The badge system does the filtering. Do not make the user read the content to determine regulatory status.
Visual treatment¶
| Badge | Style | Rationale |
|---|---|---|
| NRT | Solid fill — same weight as ASQA/Current badges | Primary classification. RTOs have paid significantly for the privilege of delivering NRT. The solid treatment honours that standing. |
| RTO | Solid fill — existing treatment | Entity classification, already established. |
| GEN | Outlined / muted — lighter visual weight | Secondary classification. Present but clearly subordinate to NRT. Never styled to compete visually with NRT. |
CSS pattern:
/* NRT — solid, primary weight */
.badge-nrt {
background: var(--badge-nrt-bg);
color: var(--badge-nrt-text);
border: none;
font-weight: 600;
}
/* GEN — outlined, muted weight */
.badge-gen {
background: transparent;
color: var(--badge-gen-text);
border: 1px solid var(--badge-gen-border);
font-weight: 500;
}
Add to CSS token definitions:
/* NRT badge */
--badge-nrt-bg: #0f4c81; /* deep blue — regulatory authority */
--badge-nrt-text: #ffffff;
/* GEN badge */
--badge-gen-text: #6b7280; /* muted grey */
--badge-gen-border: #d1d5db;
Where badges appear¶
NRT badge — show when: - Mixed-content lists where NRT and non-NRT items appear together - Search results on the public-facing site - Scope tab rows (a qual row is NRT; not all scope content is) - Landscape vendor dossier if the vendor is also delivering NRT content - Any surface where regulatory status is not already implied by context
NRT badge — omit when: - The Qualifications tab of an RTO card (everything there is NRT — badge is redundant) - Inside a training package browser (same reason) - Any section where 100% of content is NRT by definition
GEN badge — show when: - A micro-credential, corporate course, or custom content item appears alongside or near NRT content - A content listing mixes regulated and non-regulated items
RTO entity badge — show when: - An entity record is confirmed as a current RTO - An entity appears in a mixed list alongside non-RTO entities
No entity badge — when: - Entity is not an RTO (course writer, consultant, corporate trainer, etc.) - Never render a "non-RTO" label — absence is the signal
The is_nrt derivation rule¶
Content is NRT if and only if it has a valid TGA component code (tga_id is not null and maps to a known training component in rtopacks-db). This is a database-derivable boolean — no manual tagging required.
-- is_nrt: true if content maps to a TGA component
SELECT CASE WHEN tga_id IS NOT NULL THEN 1 ELSE 0 END as is_nrt
FROM content_items WHERE id = ?;
is_rto derives from organisations.status = 'Current' and the presence of a valid rto_code.
Procedures rule¶
Any new content type added to RTOpacks must be classified on both axes before it appears in any UI. Classification is not optional and is not assigned by default. The two questions to answer before a content type ships:
- Does this content map to a TGA component code? → NRT or GEN
- Is the entity delivering this content a registered RTO? → RTO badge or nothing
2. Typography¶
2.1 Font Stack¶
Inter is self-hosted. Variable font format — Inter.var.woff2. No fallback to system fonts for brand surfaces (the self-hosted file must load). The font-family declaration must be applied to body in globals — not only to component-level elements — to prevent fallback to system serif fonts.
2.2 Type Scale — Six Levels¶
No sizes outside this table. No thin weights (below 400). No italic except the meta role.
| Role | Size | Weight | Line Height | Letter Spacing | Usage |
|---|---|---|---|---|---|
| title | 1.2rem | 700 | 1.3 | -0.01em | Unit/page titles, modal headings |
| subhead | 0.95rem | 600 | 1.4 | 0 | Element headings in E&PC, section headings |
| label | 0.65rem | 600 | 1.2 | 0.08em | ALL CAPS section labels ("ELEMENTS & PERFORMANCE CRITERIA") |
| body | 0.875rem | 400 | 1.5 | 0 | PC text, prose body, all paragraph content |
| caption | 0.8rem | 400 | 1.5 | 0 | KN annotation blocks, supporting notes |
| meta | 0.75rem | 400 | 1.4 | 0 | Training package, status badges, timestamps — italic permitted here only |
2.3 Type Rules¶
- No two adjacent content levels share the same weight AND size combination. If size step is small, weight step must be decisive.
- Element headings (subhead) use 600, PC body uses 400 — the weight step is the primary differentiator.
- label role is always uppercase with 0.08em tracking. Never used for interactive elements.
- Minimum font size: 0.75rem rendered (11px at 16px base). Nothing smaller. No exceptions.
- All font sizes must use rem units. Hardcoded pixel values (e.g.
13px,14px) are not permitted — use the type scale. - Line height for multi-line content: never below 1.5 for body, 1.6 for annotation blocks.
2.4 Applied to Unit Panel¶
| Element | Role |
|---|---|
| Unit title | title (1.2rem / 700) |
| Training package | meta (0.75rem / 400 / italic) |
| Section label | label (0.65rem / 600 / uppercase) |
| Element heading | subhead (0.95rem / 600) |
| PC row | body (0.875rem / 400) |
| KN annotation | caption (0.8rem / 400) — rendered in --color-kn-text |
| Criterion number | caption (0.8rem / 600 / teal) |
| Bullet items (KE/PE) | body (0.875rem / 400) |
3. Icons¶
3.1 System¶
SF Symbols is the exclusive icon system. All icons are exported from SF Symbols as SVG and used inline. No custom icon drawings when an SF Symbol exists for the concept.
SF Symbols are weight-matched to adjacent text. A label at 600 weight uses the semibold symbol variant. Body text at 400 uses the regular variant.
Phosphor Icons are non-conforming. Any component currently using Phosphor is a known deviation pending remediation. Migration brief (ICON-01) is queued. Do not introduce new Phosphor usage. Do not mix Phosphor and SF Symbols in the same component.
3.2 Sizes¶
| Context | Size | Weight variant |
|---|---|---|
| Inline with body text | 16px | Regular |
| Standalone / button | 20px | Regular or Semibold |
| Navigation / tab bar | 24px | Regular |
| Hero / feature moment | 32px | Light or Regular |
3.3 Canonical Symbol Map (RTOpacks usage)¶
| Action/Concept | SF Symbol name |
|---|---|
| Knowledge Navigator | brain or lightbulb |
| Search | magnifyingglass |
| Filter | line.3.horizontal.decrease |
| Close / dismiss | xmark |
| Settings / preferences | gearshape |
| User / account | person.crop.circle |
| Dark mode | moon |
| Light mode | sun.max |
| Accessibility | accessibility |
| Reduce motion | figure.walk |
| High contrast | circle.lefthalf.filled |
| Checkmark / done | checkmark |
| Add | plus |
| Back (nav chevron) | chevron.left.circle |
| Forward (nav chevron) | chevron.right.circle |
| Expand | chevron.down |
| Share | square.and.arrow.up |
| Download | arrow.down.circle |
| Info | info.circle |
| Warning | exclamationmark.triangle |
| MCP | network |
| Map pin / RTO location | mappin.circle |
| Map / Find RTOs | map |
| Home | house (nav wordmark only — see §4.4) |
3.4 Icon Rules¶
- Icons never appear without an accessible label (
aria-labelor adjacent visible text). - Icons in colour use only
--color-interactive(blue) or--color-kn-accent(teal, KN only). No other colours on icons. - Icons in dark mode invert automatically via CSS
colorproperty (SVGcurrentColor). - Never use emoji as icons in the interface.
- Icon toggle exemption: The "Show icons" preference suppresses decorative and supplementary UI icons. Navigation chevrons (§10) are exempt — they are functional wayfinding and must always render. The nav wordmark/logo is also exempt — it is the primary home affordance and must always render.
4. Layout¶
4.1 Spacing Scale¶
Base unit: 4px. All spacing values are multiples of 4. No exceptions (e.g. 5px is not permitted).
| Value | Token | Usage |
|---|---|---|
| 4px | xs | Tight internal spacing, icon-to-label gap |
| 8px | sm | Between related inline elements |
| 12px | md | Internal padding in compact components |
| 16px | lg | Standard component padding, between list items |
| 24px | xl | Between component sections |
| 32px | 2xl | Between major content blocks |
| 48px | 3xl | Section breaks, generous whitespace |
| 64px | 4xl | Page-level separation |
4.2 Layout Rules¶
- Proximity communicates relationship. Elements that belong together sit close. Elements that are separate have breathing room. Negative space is structure made visible.
- Consistent left alignment for all content. Centred text only for empty states and full-panel moments (onboarding overlays).
- Content never fights controls. Panels, drawers, and overlays sit above content on their own layer — they do not push content.
- Minimum touch target: 44px × 44px (Apple HIG standard). Minimum control padding: 12px around the visible edge.
- Maximum readable line length: 72 characters for body text.
4.3 Unit Panel Spatial Logic¶
The panel has spatial memory. It slides in from the right edge. It returns to the right edge on close. These are not arbitrary — they establish direction.
- Panel open: slides in from right, starting below the nav bar (nav is never obscured)
- Panel close: slides out to right
- Second unit selected: panel content cross-fades (not a re-slide) — the panel stays, the content changes
- KN blocks: expand downward from beneath the PC row (not in from the side)
4.4 Navigation System¶
RTOpacks uses a three-element navigation system. These three elements are the complete system. No additional home affordances, back buttons, or navigation widgets are to be added.
| Element | What it does | Where it lives |
|---|---|---|
| Nav wordmark ("rtopacks" + RP logomark) | Always takes the user to / (home). This is the canonical home affordance. |
Top-left of nav bar, all surfaces |
Left chevron (chevron.left.circle) |
Navigates to parent context (e.g. qual page → catalogue). See §10. | Fixed left edge of content area, content pages only |
Right chevron (chevron.right.circle) |
Navigates to next item in defined sequence. Absent when no forward context exists. See §10. | Fixed right edge of content area, content pages only |
The nav wordmark must always be a link to /. It must always be visible. It is never suppressed by the icon toggle or any other preference.
The HUD bar (bottom of screen) contains: search, explore, sign up. It does not contain navigation. Any previous home/return affordance in the HUD (the pulsing beacon) has been removed. Do not re-introduce navigation into the HUD.
4.5 Nav Bar Height¶
Nav bar height is 56px on all surfaces. This is a hard constraint used in:
- Chevron vertical positioning: calc(56px + (100vh - 56px) / 2)
- Unit panel top offset: top: 56px
- Scroll offset calculations
Any deviation from 56px will break chevron positioning and panel clearance. Do not vary nav height between pages.
4.6 Surface Overlay System¶
RTOpacks qualification pages use a full-bleed video background. The content HUD (the panel containing all qualification text and the KN column) floats over this video. The video is texture, not content — it provides atmospheric depth, not information. The panel must read as solid and confident against it.
The rule: opacity lives on background-color only. Never on the element.
Element-level opacity makes everything inside the element semi-transparent — text, borders, UI elements, icons. This is not permitted. Use rgba() values on background-color and keep the element itself at opacity: 1.
Tokens:
/* Light mode */
--color-surface-overlay: rgba(246, 248, 250, 0.90);
/* Dark mode */
--color-surface-overlay: rgba(22, 27, 34, 0.84);
Backdrop filter:
The blur softens the video boundary at the panel edge, reinforcing the sense of layering without making the panel look frosted. Do not increase beyond 6px — at higher values the panel loses depth and reads as an opaque sheet.
Application:
These values apply to the qualification page HUD and any future full-bleed content surface placed over video. The HUD is a single panel — both the left (content) and right (KN) columns inherit the same overlay treatment. Do not apply different opacity to the KN column to signal "content pending" — the KN placeholder copy carries that signal.
The Background Intensity slider in the Experience panel controls a multiplier on this opacity. The tokens above are the 1.0 (default) values. The slider is additive, not a replacement for correct base values.
Design failures:
- opacity: 0.79 on a container element (text and UI become semi-transparent)
- Hardcoded rgba values not derived from these tokens
- backdrop-filter above blur(6px)
- Separate opacity treatment on left vs right columns
4.7 Chevron Clearance¶
Navigation chevrons occupy fixed strips on both sides of the viewport. Content panels must never extend into these strips — the chevrons must always be visible against the video background, never obscured by panel content.
The strips:
- Left strip: 0 → 60px (chevron at left: 16px, hit target 44px, right edge at 60px)
- Right strip: calc(100vw - 60px) → 100vw (mirror — right chevron at right: 16px when present)
Rule: Content panels on chevron-bearing pages must use --chevron-clearance for both padding-left and padding-right. The panel content sits in the space between the two strips.
/* Token — single value, applies symmetrically to both sides */
--chevron-clearance: 72px; /* 60px chevron footprint + 12px gap */
/* Apply to HUD and any future full-width content panel on chevron-bearing pages */
.hud {
padding-left: var(--chevron-clearance);
padding-right: var(--chevron-clearance);
}
This rule applies to all content pages (all pages except /). The front page (/) has no chevrons and no clearance requirement.
The right chevron is not yet rendered on the qual page, but the right-side clearance must be applied now — the strip is reserved. When the right chevron is introduced it will appear in already-clear space.
Design failures:
- padding-left: 24px or padding-right: 24px on a full-width content panel on a chevron-bearing page
- Chevron rendered over panel content at any viewport width
- Asymmetric clearance (left cleared, right not)
5. Motion¶
5.1 Motion Philosophy¶
Motion conveys status, provides feedback, and aids instruction. If a motion does none of these three things, it must be removed. Animation is not decoration. Gratuitous motion makes the product feel slow and unreliable. Brief, precise animation makes the product feel responsive and considered.
5.2 Timing Tokens¶
:root {
--duration-instant: 0ms; /* Immediate state changes, no transition */
--duration-fast: 120ms; /* Micro-interactions: toggle, checkbox, badge */
--duration-default: 200ms; /* Standard UI transitions: hover, focus ring */
--duration-panel: 280ms; /* Panel slide in/out */
--duration-reveal: 320ms; /* KN block opacity reveal */
--duration-overlay: 240ms; /* Preference panel, modal overlays */
--duration-stagger: 80ms; /* Delay between sequential reveals */
--duration-runner: 220ms; /* Nav runner animation */
--duration-entrance: 400ms; /* Page-level entrance animations — waypoint reveal, hero subtitle */
--duration-slide: 600ms; /* Results panel, search panel slide open */
}
5.3 Easing Tokens¶
:root {
--ease-default: ease; /* General transitions */
--ease-out: cubic-bezier(0, 0, 0.2, 1); /* Things entering — decelerates into position */
--ease-in: cubic-bezier(0.4, 0, 1, 1); /* Things leaving — accelerates away */
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Preference panel — slight physical overshoot */
}
5.4 Canonical Transitions¶
| Transition | Duration | Easing | Property |
|---|---|---|---|
| Panel open | --duration-panel (280ms) |
--ease-out |
transform: translateX |
| Panel close | 220ms | --ease-in |
transform: translateX |
| Panel content change | --duration-default (200ms) |
ease | opacity cross-fade |
| KN block reveal | --duration-reveal (320ms) |
ease | opacity, max-height |
| KN sequential stagger | --duration-stagger (80ms) |
— | delay between blocks |
| Preference panel open | --duration-overlay (240ms) |
--ease-spring |
transform: translateY |
| Preference panel close | 200ms | --ease-in |
transform: translateY |
| Hover state | --duration-fast (120ms) |
ease | background-color, color |
| Focus ring | --duration-fast (120ms) |
ease | box-shadow |
| Page cross-fade | --duration-default (200ms) |
ease | opacity |
| Toggle | --duration-fast (120ms) |
ease | transform, background-color |
| Banner slide | 350ms | --ease-out |
transform: translateY |
| Nav runner | --duration-runner (220ms) |
--ease-in |
transform: scaleX |
| Waypoint opacity reveal | --duration-entrance (400ms) |
ease | opacity |
| Hero subtitle line | --duration-entrance (400ms) |
ease | opacity |
| Results panel slide open | --duration-slide (600ms) |
--ease-out |
transform |
| Results panel slide close | --duration-entrance (400ms) |
--ease-in |
transform |
5.5 Reduced Motion¶
When data-motion="reduced" is set on :root (either from OS prefers-reduced-motion or user preference):
:root[data-motion="reduced"] * {
transition-duration: 0ms !important;
animation-duration: 0ms !important;
}
The KN reveal sequence must detect this state and skip the setTimeout stagger chain entirely — blocks appear at full opacity on initial render, banner appears immediately, no sequential delays. This is not optional. It is a WCAG 2.1 requirement.
The nav runner (§10.3) must also be skipped entirely when reduced motion is active — open the drawer directly with no runner and no slide animation.
5.6 Motion Rules¶
- Never make the user wait for an animation before they can interact. Animation happens alongside intent, not in front of it.
- No looping animations on any surface except:
- The video background (which stops in light mode and when motion is reduced)
- Spring easing (
--ease-spring) is used only for the preference panel. It is not used on content transitions. - Navigation elements (wordmark, HUD elements, chevrons) must not have looping animations. A home link or back button has no status to communicate. Pulsing, spinning, or bouncing on navigation elements is not permitted.
6. Accessibility¶
6.1 Preference Panel Controls¶
The preferences panel (bottom-right corner, always accessible) contains all accessibility controls. They do not appear elsewhere in the interface. The panel uses the --ease-spring entry animation.
Controls in the panel, in order:
1. Appearance — Light / Dark / System (segmented control)
2. Text size — Default / Large / Larger (segmented control, affects font-size on :root)
3. Reduce motion — Toggle (writes data-motion="reduced" to :root)
4. High contrast — Toggle (writes data-contrast="high" to :root)
5. Knowledge Navigator — Toggle (global KN on/off, writes kn-enabled to localStorage)
6. MCP — Info expand (educational, not functional at this stage)
6.2 Semantic HTML Requirements¶
- All interactive elements have either a visible text label or
aria-label. - Icon-only buttons always have
aria-label. - The unit panel drawer has
role="dialog"andaria-label="Unit detail". - The KN toggle has
role="switch"andaria-checkedreflecting its state. - Section labels ("ELEMENTS & PERFORMANCE CRITERIA") use
<h3>or appropriate heading level, not<div>. - KN annotation blocks have
aria-label="Knowledge Navigator interpretation". - Navigation chevrons (§10) have
aria-label="Go back"andaria-label="Go forward"respectively. - The nav wordmark link has
aria-label="RTOpacks home".
6.3 Focus Management¶
- Focus ring:
box-shadow: 0 0 0 3px var(--color-interactive),outline: none. This must be defined globally inglobals.csson:focus-visible, not per-component. - When the unit panel opens, focus moves to the panel's close button or the first interactive element inside it.
- When the panel closes, focus returns to the unit row that triggered it.
- The preference panel traps focus while open. Escape dismisses it.
6.4 Contrast Requirements¶
| Context | Minimum ratio |
|---|---|
| Body text (< 18pt) | 4.5:1 |
| Large text (≥ 18pt or 14pt bold) | 3:1 |
| UI component boundaries | 3:1 |
data-contrast="high" mode |
7:1 for body text |
Verify all colour combinations at https://webaim.org/resources/contrastchecker/
7. Voice¶
7.1 Knowledge Navigator Annotations¶
All KN annotations speak in one voice. There is no variation between units.
- Person: Second, direct. Address the reader as "you."
- Tense: Present.
- Register: Professional colleague. A knowledgeable trainer explaining what matters to another knowledgeable trainer.
- Not: A compliance document. Not a textbook. Not a policy statement. Not a government circular.
Right: You need to show you can identify at least three credible sources — industry bodies, government regulators, or accredited publications — and explain why each one is relevant to your specific role.
Wrong: Learners must demonstrate the ability to identify sources of information as per the requirements of the performance criteria. Assessors should look for evidence that...
7.2 Voice Rules¶
- No register switching within a unit. If you start with "you need to show," every criterion in that unit uses the same pattern.
- No passive voice except where the subject is genuinely unknown.
- No jargon without immediate plain English equivalent.
- Plain English minimum: readable by a person holding a Certificate IV in Training and Assessment.
- Sentence length: prefer two sentences over one long one. Never more than 35 words in a single sentence.
- No "learners," "students," or "candidates" — say "you."
- No "assessors look for" — say what they need to see, directly.
7.3 Interface Copy¶
- Labels use sentence case, not Title Case, except proper nouns and the label role (ALL CAPS, tracked).
- Buttons use verbs: "Turn on," "Got it," "See all units." Not "OK," "Yes," "Submit."
- Error messages say what to do, not what went wrong: "Try reloading the page" not "An error occurred."
- Empty states have a call to action. A blank panel is never just blank.
8. Surfaces¶
8.1 Theme Application¶
The data-theme, data-contrast, and data-motion attributes are written to :root by a single ThemeManager module. This module:
- Reads initial state from localStorage keys:
rto-theme,rto-contrast,rto-motion - Falls back to OS
prefers-color-schemeandprefers-reduced-motionif no localStorage value exists - Writes new values to both
:rootdata attributes and localStorage on change - Fires a
themechangecustom event that any component can listen to
All map surfaces, video backgrounds, dynamically styled components, and navigation chevrons must listen for themechange and re-apply styles immediately. Do not poll. Do not use intervals. Event-driven only.
8.2 Dark Mode Specifics¶
Dark mode backgrounds are a three-level depth stack. This is not arbitrary — depth communicates layering:
--color-bg-base(#0d1117): the page itself. Everything lives on top of this.--color-bg-elevated(#161b22): panels, drawers, any surface that floats above the page.--color-bg-card(#1c2128): cards, KN blocks, inset content within an elevated surface.
The elevated surface must read as above the base. This is achieved by the background value alone — no shadows required in dark mode. Shadows are used in light mode where the background values are closer together.
8.3 Light Mode Specifics¶
Light mode is a first-class experience, not an afterthought. It uses a three-level stack that mirrors the dark values:
--color-bg-base(#ffffff): the page.--color-bg-elevated(#f6f8fa): panels, drawers.--color-bg-card(#eaeef2): cards, inset content.
In light mode, the video background is replaced with a static gradient or solid colour. Looping video does not play in light mode. This is both an accessibility concern and a design decision — light mode implies document-like focus.
8.4 Video Background Protection¶
The background video element — its existence, src, loop behaviour, and playback logic — is not subject to design compliance audits or automated fix passes. It is governed exclusively by §11 (System-Wide Intensity).
Do not modify, mute, replace, remove, or suppress video elements during compliance or design fix passes. Any brief that explicitly needs to touch the video element must name it in scope with written instruction from Tim Rignold. If a brief does not explicitly name the video element, it is out of scope.
The video container div must use background: var(--color-bg-base) as its CSS background fallback. The value #07090f or any other hardcoded hex is not permitted on this element.
8.5 What RTOpacks Does Not Look Like¶
For reference — things that would indicate a design failure:
- Dark grey background with bright green or teal as the dominant colour
- Pure black (
#000000) background - Pure white (
#ffffff) text on dark background (too harsh) - Any element that looks like a terminal or command-line interface
- Any element that looks like it was generated by an AI design tool with default settings
- Buttons or headers in teal (teal is KN-only)
- Monospaced font outside of code contexts
- Pulsing or looping animations on navigation elements
9. Maps¶
This section governs every map surface across all RTOpacks surfaces. Google Maps is the current implementation. All styling is applied via the Maps JavaScript API styles array. No surface may deviate from these specs without explicit written instruction from Tim Rignold.
9.1 Map Philosophy¶
Maps in RTOpacks are orientation surfaces, not navigation surfaces. The goal is spatial recognition — a user should be able to look at a map and think "I know that area" without the map becoming a route planner or directory.
Show: suburb and locality labels, arterial road geometry, water bodies, land mass, country and state borders.
Suppress: highway route shields and numbers, all transit lines, all points of interest, local/residential streets, business names.
At zoom levels below 10 (zoomed out): suburb labels fade, only major city names and geography remain. At zoom levels 10 and above: suburb labels become the primary orientation layer. Arterials are visible as geometry only — no route numbers.
9.2 Theme Switch Wiring¶
Maps must respond to the themechange event fired by ThemeManager. On event, re-read localStorage.getItem('rto-theme') and apply the corresponding style array immediately. No page reload. No flash of wrong theme on init — read rto-theme from localStorage before the map initialises and apply the correct style array at construction time.
// Correct init pattern
const theme = localStorage.getItem('rto-theme') || 'dark';
const map = new google.maps.Map(el, {
styles: theme === 'light' ? MAP_STYLES_LIGHT : MAP_STYLES_DARK,
...options
});
// Correct theme-switch pattern
window.addEventListener('themechange', (e) => {
map.setOptions({ styles: e.detail.theme === 'light' ? MAP_STYLES_LIGHT : MAP_STYLES_DARK });
});
9.3 Dark Mode Map Style¶
Design intent: deep navy ocean, slightly lighter land mass, teal-stroke Australia keyline, muted suburb labels, arterial road geometry only. Oriented, not navigational. No clutter.
const MAP_STYLES_DARK = [
{ elementType: 'geometry', stylers: [{ color: '#141c25' }] },
{ featureType: 'water', elementType: 'geometry', stylers: [{ color: '#0d1f33' }] },
{ featureType: 'water', elementType: 'labels.text.fill', stylers: [{ color: '#2a4a6b' }] },
{ featureType: 'administrative.country', elementType: 'geometry.stroke', stylers: [{ color: '#1e6b5e' }, { weight: 1.5 }] },
{ featureType: 'administrative.province', elementType: 'geometry.stroke', stylers: [{ color: '#1a3a4f' }, { weight: 0.8 }] },
{ featureType: 'administrative.locality', elementType: 'labels.text.fill', stylers: [{ color: '#4a6a8a' }] },
{ featureType: 'administrative.locality', elementType: 'labels.text.stroke', stylers: [{ color: '#0d1117' }, { weight: 2 }] },
{ featureType: 'administrative.neighborhood', elementType: 'labels.text.fill', stylers: [{ color: '#2d4a62' }] },
{ featureType: 'road.arterial', elementType: 'geometry', stylers: [{ color: '#1e2d3d' }] },
{ featureType: 'road.arterial', elementType: 'labels', stylers: [{ visibility: 'off' }] },
{ featureType: 'road.highway', elementType: 'geometry', stylers: [{ color: '#182333' }] },
{ featureType: 'road.highway', elementType: 'labels', stylers: [{ visibility: 'off' }] },
{ featureType: 'road.highway.controlled_access', elementType: 'labels', stylers: [{ visibility: 'off' }] },
{ featureType: 'road.local', stylers: [{ visibility: 'off' }] },
{ featureType: 'transit', stylers: [{ visibility: 'off' }] },
{ featureType: 'poi', stylers: [{ visibility: 'off' }] },
{ featureType: 'landscape.natural', elementType: 'geometry', stylers: [{ color: '#141c25' }] },
{ featureType: 'landscape.man_made', elementType: 'geometry', stylers: [{ color: '#161e2a' }] },
{ elementType: 'labels.text.stroke', stylers: [{ color: '#0d1117' }] },
{ elementType: 'labels.icon', stylers: [{ visibility: 'off' }] },
];
9.4 Light Mode Map Style¶
Design intent: clean white-grey land, clear blue water, visible borders, muted arterials, suburb labels in mid-grey. Mirrors the dark mode philosophy — oriented, not navigational.
const MAP_STYLES_LIGHT = [
{ elementType: 'geometry', stylers: [{ color: '#e8ecef' }] },
{ featureType: 'water', elementType: 'geometry', stylers: [{ color: '#c8daea' }] },
{ featureType: 'water', elementType: 'labels.text.fill', stylers: [{ color: '#7a9ab5' }] },
{ featureType: 'administrative.country', elementType: 'geometry.stroke', stylers: [{ color: '#7a9ab5' }, { weight: 1.5 }] },
{ featureType: 'administrative.province', elementType: 'geometry.stroke', stylers: [{ color: '#b0c4d4' }, { weight: 0.8 }] },
{ featureType: 'administrative.locality', elementType: 'labels.text.fill', stylers: [{ color: '#5a7a8a' }] },
{ featureType: 'administrative.locality', elementType: 'labels.text.stroke', stylers: [{ color: '#e8ecef' }, { weight: 2 }] },
{ featureType: 'administrative.neighborhood', elementType: 'labels.text.fill', stylers: [{ color: '#8aacba' }] },
{ featureType: 'road.arterial', elementType: 'geometry', stylers: [{ color: '#c5cdd6' }] },
{ featureType: 'road.arterial', elementType: 'labels', stylers: [{ visibility: 'off' }] },
{ featureType: 'road.highway', elementType: 'geometry', stylers: [{ color: '#bac4cc' }] },
{ featureType: 'road.highway', elementType: 'labels', stylers: [{ visibility: 'off' }] },
{ featureType: 'road.highway.controlled_access', elementType: 'labels', stylers: [{ visibility: 'off' }] },
{ featureType: 'road.local', stylers: [{ visibility: 'off' }] },
{ featureType: 'transit', stylers: [{ visibility: 'off' }] },
{ featureType: 'poi', stylers: [{ visibility: 'off' }] },
{ featureType: 'landscape.natural', elementType: 'geometry', stylers: [{ color: '#e8ecef' }] },
{ featureType: 'landscape.man_made', elementType: 'geometry', stylers: [{ color: '#eef0f3' }] },
{ elementType: 'labels.text.stroke', stylers: [{ color: '#e8ecef' }] },
{ elementType: 'labels.icon', stylers: [{ visibility: 'off' }] },
];
9.5 Map Markers¶
RTO location pins use --color-kn-accent (#0f9e82) as fill. This is the single exception to the KN-only teal rule — map pins are a spatial identity element, not UI chrome, and the teal reads clearly against both dark and light map backgrounds.
Marker spec:
- Fill: #0f9e82
- Stroke: #ffffff (dark mode) / #1c2128 (light mode), 1.5px
- Size: 32px × 32px hit target, 10px circle visual
- Selected/active state: scale to 1.3×, add drop shadow
InfoWindow styling must match the surface theme — use --color-bg-elevated as background, --color-text-primary for RTO name, --color-text-secondary for metadata.
9.6 Map Default Options¶
const MAP_BASE_OPTIONS = {
disableDefaultUI: true,
gestureHandling: 'cooperative',
clickableIcons: false,
mapTypeControl: false,
streetViewControl: false,
fullscreenControl: false,
zoomControl: true,
zoomControlOptions: { position: google.maps.ControlPosition.RIGHT_BOTTOM },
minZoom: 4,
maxZoom: 16,
};
9.7 Auth-Gated Layers (Phase 2 — not yet implemented)¶
When RTOpacks has an active user session (Ortho or equivalent), the map may unlock additional context layers for authenticated users: transit layer (train stations, bus interchanges) and curated POI (TAFE campuses, major learning precincts). These layers are not to be built until Ortho session state is available client-side. Do not implement Phase 2 items ahead of Ortho integration.
10. Navigation Chevrons¶
Navigation chevrons provide ambient spatial wayfinding on content pages. They give the user an always-visible path back (and forward when relevant) without competing with content. The pattern is derived from Apple's sheet and navigation controller conventions applied to a web context.
10.1 Presence Rules¶
Navigation chevrons are present on all content pages except the front page (/). They are never present on the front page.
Left chevron (back): Present whenever the current page has a defined parent context. Examples:
- Qual page (/qual/[code]) → back to catalogue/search results
- Any future detail page with a clear parent
Right chevron (forward): Present only when a defined next item exists in a sequence. If no forward context exists, the right chevron is absent entirely. Do not show a disabled/greyed forward chevron — absence is cleaner than a dead affordance.
When a drawer is open: Both chevrons hide. They are not relevant while the user is inside a drawer context.
10.2 Visual Specification¶
SF Symbol: chevron.left.circle (back) and chevron.right.circle (forward). Outlined variant — not filled.
Sizing: - Hit target: 44px × 44px - Visual icon: 28px
Position:
- position: fixed
- Vertically centred on the content area — not the full viewport. Content area top = nav bottom (56px). Midpoint = calc(56px + (100vh - 56px) / 2).
- Left chevron: left: 16px
- Right chevron: right: 16px
- transform: translateY(-50%)
Colour — theme-aware, CSS custom properties only, no hardcoded hex:
/* At rest */
color: var(--color-text-disabled);
/* On hover */
color: var(--color-text-secondary);
background: var(--color-bg-hover);
border-radius: 50%;
Transition: --duration-fast (120ms), ease — colour and background only.
High contrast mode: color: var(--color-text-primary) at rest.
10.3 Interaction¶
- Left chevron: navigate to parent context URL. Prefer explicit URLs over
history.back()—history.back()breaks on direct links. - Right chevron: navigate to next item URL in defined sequence.
- Both respond to keyboard focus and activation (Enter/Space).
- Focus ring applies per §6.3.
- Nav runner fires on chevron activation (same as unit click runner) — optional, use judgement on whether it aids the transition.
- Reduced motion: no runner, no slide animation.
10.4 Accessibility¶
- Left chevron:
aria-label="Go back",role="button"or<a>with appropriatehref - Right chevron:
aria-label="Go forward", same - Both always have accessible labels — they are icon-only controls
- Neither is affected by the "Show icons" preference toggle — see §3.4 exemption
10.5 Icon Toggle Exemption¶
Navigation chevrons must always render regardless of the user's icon preference setting. They are functional navigation, not decorative. The rto-icons localStorage key and its associated CSS class must not suppress chevron visibility. Implement chevron rendering outside the icon-toggle scope, or explicitly override with display: flex !important.
11. System-Wide Intensity¶
Dynamic backgrounds, video overlays, and animated surfaces do not have per-scene brightness controls. All read from rto-video-intensity (range 0–100, default 70). Set once by the user, applied everywhere. New surfaces must inherit this value — no exceptions.
The value is stored in both localStorage and a cookie (1-year expiry) so it persists before login and survives a fresh visit. It is exposed to CSS as --video-intensity (0–1 float) on the root element.
13. Admin Surface¶
The admin surface (admin.rtopacks.com.au) is the L3 operator control panel. It is a fundamentally different surface from rtopacks.com.au and governed by a separate design language. Read this section before writing any brief that touches admin.rtopacks.com.au.
13.1 Surface Identity¶
| Attribute | Value |
|---|---|
| URL | admin.rtopacks.com.au |
| Access | Cloudflare Access — admin@ucca.online only |
| Classification | L2 INTERNAL (UCCA Security Posture) |
| Mode | Light mode only — no dark mode toggle |
| Reference | ops.ucco.foundation — the visual gold standard |
| Stack | Next.js via OpenNext on Cloudflare Workers |
| Repo path | worlds/au-vet/rtopacks/admin/ |
The admin surface is an operator console, not a consumer product. The UX model is dense and functional. The aesthetic is refined, light, and data-focused — not the dark atmospheric treatment of rtopacks.com.au. These two surfaces must never share token names or visual language.
13.2 Admin Token System¶
All tokens are declared as bare :root {} at the top level of app/globals.css — outside any @layer declaration. Tailwind's @layer base must not wrap these tokens.
Backgrounds¶
--bg-primary: #f8f8f8; /* Page background — slightly off-white */
--bg-secondary: #ffffff; /* Sidebar, cards, panels */
--bg-tertiary: #f0f0f0; /* Hover backgrounds, tertiary surfaces */
--bg-card: #ffffff; /* Metric cards, content cards */
Text — Paper Scale¶
--paper: #111111; /* Alias for primary text */
--paper-light: #1a1a1a;
--paper-bright: #0d0d0d; /* Metric values, headings */
--paper-dim: #555555; /* Secondary text */
--paper-muted: #999999; /* Labels, captions, muted content */
--paper-ghost: rgba(0,0,0,0.20);
RTOpacks Admin Accent — Teal¶
The admin panel uses #0e7490 as its accent. This is distinct from the public surface which uses #2563eb (blue). The admin teal is the ops lineage colour — derived from ops.ucco.foundation's --ucco-blue but shifted to RTOpacks teal.
--rp-teal: #0e7490;
--rp-teal-tint: #edf7f9; /* Section header background tint */
--rp-teal-tint-border: #d0eef3; /* Tint section border */
--accent: #0e7490; /* Primary accent alias */
--accent-bright: #0c6476; /* Hover state */
--accent-dim: rgba(14,116,144,0.12); /* Active background */
Status Colours¶
--rp-green: #10b981;
--rp-green-bg: #ecfdf5;
--status-green: #10b981; /* live badge */
--status-amber: #d97706; /* planned / coming-soon badge */
--status-red: #dc2626; /* error state */
--rp-bg-active: #f0f7ff; /* Active nav item background */
--rp-badge-planned-bg: #f0f0f0; /* Planned badge background */
Borders¶
Text Aliases¶
Typography¶
Monospace applies to: stat labels (KEY METRICS, ACTIVE CLIENTS), section headers (OVERVIEW, CLIENTS), status badges, and the LIVE/GUIDED/COMPLIANCE tab switcher.
13.3 Impersonation Banner¶
When an operator is in a step-down session (per Identity Layer Spec §5.4), the impersonation banner must render at all times above all other content.
| Attribute | Value |
|---|---|
| Position | position: fixed; top: 0; width: 100%; z-index: 9999 |
| Height | 36px |
| Background | #f59e0b — amber-500. This colour is reserved exclusively for the impersonation banner. Never use it elsewhere on this surface. |
| Left | Eye icon + "Viewing as:" + layer name + context name |
| Right | Exit button + live countdown timer (HH:MM:SS), turns red at 5 minutes |
| Dismissable | Never. Non-dismissable per Identity Layer Spec. |
| Trigger | Renders only when /api/admin/me returns impersonation: true |
The banner sits above the top bar when active. All other content shifts down by 36px.
13.4 Top Bar¶
Fixed below the impersonation banner. Height: 48px. Background: --bg-secondary (#ffffff). Border-bottom: 1px solid --border.
Left zone:
- RTOpacks RP mark
- "RTOpacks Admin" wordmark — Inter 13px, weight 600, --paper-bright
Centre zone — LIVE / GUIDED / COMPLIANCE tabs:
Pill/segment switcher. Font: 11px monospace uppercase, letter-spacing 0.06em.
| State | Treatment |
|---|---|
| Active tab | Background: --accent (#0e7490), text: #ffffff |
| Inactive tab | Text: --paper-muted, no background |
| Default active | LIVE |
Tab switching is client-side state only (useState). No routing changes. Each tab has a data-tab attribute for future wiring.
Tab definitions:
- LIVE — Current platform state. Real-time metrics, live status indicators, recent activity. Default view.
- GUIDED — Operational workflows and next actions. Planned: recommended actions, alerts, pending approvals. Currently: 'Coming soon' notice card at top, live content below.
- COMPLIANCE — Audit trail and regulatory posture. Planned: data currency, TEQSA sync, ASQA indicators, compliance calendar. Currently: 'Coming soon' notice card at top, live content below.
Right zone (left to right):
- UTC clock — IBM Plex Mono 12px, --paper-muted, live via setInterval(1000)
- User email — fetched from /api/admin/me, never hardcoded. 12px, --paper-dim
- Admin pill — 10px monospace uppercase, --accent background, #ffffff text, 4px radius
- Blueprint button — outlined, --accent border and text, hover fills with --accent-dim. Lucide FileText icon. Links to docs.rtopacks.com.au (new tab).
13.5 Sidebar¶
Width: 192px. Background: --bg-secondary. Border-right: 1px solid --border. Full viewport height below top bar (or below impersonation banner when active).
Section group labels:
font-family: var(--rp-font-mono);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--paper-muted);
padding: 16px 16px 4px;
Section labels have a teal tint bar: background: var(--rp-teal-tint), border-bottom: 1px solid var(--rp-teal-tint-border).
Nav items:
- Hover:
background: --bg-tertiary - Active:
background: --rp-bg-active,color: --accent,font-weight: 500 - Status dot: 6px circle before items with a status.
live=--status-green,planned=--status-amber
Coming soon stubs (Finance, Marketing):
Lock icons remain. These items link to /finance and /marketing — dedicated coming soon pages, not #. Lock icons stay in the nav label.
Resources footer (bottom of sidebar):
Links: Help, Knowledge, Docs — each with Lucide ExternalLink 12px icon.
13.6 Main Content Area¶
Background: --bg-primary (#f8f8f8). Padding: 32px. This is intentionally off-white to create visual separation from the white sidebar and white cards.
Page title: 22px, weight 600, --paper-bright
Page subtitle: 13px, --paper-muted, margin-bottom 28px
13.7 Component Specifications¶
Stat Cards¶
Metric tiles on the dashboard.
- Label: 11px monospace uppercase,
--paper-muted, margin-bottom 8px - Value: 28px, weight 600,
--paper-bright - Sub-label (e.g. "300 rpm limit"): 11px,
--paper-muted, margin-top 4px - No shadows. Flat border only.
Storefront metrics (not yet live): opacity: 0.6. Value shows — (em dash), not 0.
Section Headers¶
font-family: var(--rp-font-mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--paper-muted);
border-bottom: 1px solid --border;
padding-bottom: 8px;
margin-bottom: 16px;
Status Badges¶
font-family: var(--rp-font-mono);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 2px 8px;
border-radius: 999px;
| Status | Background | Text |
|---|---|---|
| live | --rp-green-bg (#ecfdf5) |
--rp-green (#10b981) |
| planned | --rp-badge-planned-bg (#f0f0f0) |
--paper-dim (#555555) |
| coming soon | #fffbeb |
--status-amber (#d97706) |
| error | #fef2f2 |
--status-red (#dc2626) |
Content Cards¶
Card title: 13px, weight 600, --paper-bright, flex row with icon left and status badge right.
Data Tables¶
- Header row: 11px monospace uppercase,
--paper-muted,background: --bg-primary, border-bottom: 2px solid--border - Body rows: 13px Inter,
--paper-dim, border-bottom: 1px solid--border-mid - Row hover:
background: --bg-tertiary - No outer border on the table
Buttons¶
| Type | Style |
|---|---|
| Primary | background: --accent, color: #ffffff, 12px, weight 500, 7px 14px padding, radius 6px, hover: --accent-bright |
| Outline | border: 1px solid --accent, color: --accent, transparent background, hover: --accent-dim background |
| Ghost | No border, color: --paper-dim, hover: --bg-tertiary background |
Quick Links¶
Outlined pill buttons. border: 1px solid --border, color: --paper-dim, padding 5px 12px, radius 999px, 12px font. Hover: border-color: --accent, color: --accent. Arrow → appended inline.
13.8 Admin Surface Rules¶
UCCA provenance headers are correct on this surface. Unlike rtopacks.com.au (a world surface with zero UCCA fingerprinting), admin.rtopacks.com.au is an L2 INTERNAL surface and must carry the full provenance header set (X-UCCA-Version, X-Powered-By: UCCA/1.0.1 — Hardened etc.). See Security Posture doc.
Identity is never hardcoded. The top bar user email and all identity-dependent UI comes from /api/admin/me, which reads CF-Access-Authenticated-User-Email from the Cloudflare Access JWT. Nothing is ever hardcoded.
No dark mode. The admin surface has no theme toggle. --bg-primary: #f8f8f8 is the permanent page background.
Monospace is structural, not decorative. IBM Plex Mono (or fallback) is used for stat labels, section headers, status badges, and the mode tab switcher. Inter is used for all other copy. Do not use monospace for body text, nav labels, or card titles.
No animations beyond hover. The admin panel is a data tool. Hover state colour transitions at 150ms ease are permitted. No entrance animations, no slide effects, no motion of any kind.
No page-level shadows. Cards and panels use border: 1px solid --border only. Box shadows are not used on this surface.
13.9 Admin Data Tables — Layout Rules¶
Content wrapper¶
Admin pages must not apply a max-width constraint to the content wrapper. The admin surface is a professional tool used on wide monitors — content should fill available viewport width minus the sidebar.
Correct:
Never:
No component in the admin shell may introduce a max-width on the main content area. If a page-level width constraint is needed for a specific layout reason, it must be approved and documented here.
Column wrapping¶
Admin data tables contain two classes of columns:
Fixed-vocabulary columns — values are always short and from a known set (status, dates, extent, action links). These must never wrap. Apply white-space: nowrap to both <th> and <td> for these columns, and to any badge <span> inside them.
Fixed-vocabulary columns always include: - Status (badge/pill) - Extent - Start date / End date / any date column - Delivery notification - Action links (Details, View, Edit) - Any numeric count column
Free-text columns — values are variable length (title, name, description, address). These may wrap naturally. Do not apply nowrap to free-text columns.
Implementation pattern — column definition arrays must carry a nowrap flag:
const columns = [
{ key: 'code', label: 'Code', nowrap: true },
{ key: 'title', label: 'Title', nowrap: false }, // free text — wraps
{ key: 'status', label: 'Status', nowrap: true },
{ key: 'extent', label: 'Extent', nowrap: true },
{ key: 'start', label: 'Start date', nowrap: true },
{ key: 'end', label: 'End date', nowrap: true },
{ key: 'action', label: 'Action', nowrap: true },
];
The table renderer applies style={{ whiteSpace: 'nowrap' }} to <th> and <td> when nowrap: true. Never hardcode per-cell.
Status badges (pills)¶
Status badge spans must include white-space: nowrap in addition to their pill styling. A badge that wraps internally is always a bug.
// Correct pill style — always include whiteSpace
style={{
fontSize: '0.7rem',
fontWeight: 500,
padding: '2px 8px',
borderRadius: '999px',
whiteSpace: 'nowrap', // ← required
color: 'var(--rp-status-...)',
background: '...',
}}
Rationale¶
The primary admin user is on a 49" Samsung ultrawide. Artificially constraining table width wastes screen real estate and forces column content to wrap, degrading readability of dense data. Admin tables should use every pixel available.
14. Calendar¶
The VET Industry Calendar (/calendar on admin.rtopacks.com.au, and future public surfaces) uses a specific colour system for event categories. The calendar is implemented with FullCalendar.
14.1 Calendar Philosophy¶
The calendar surfaces regulatory deadlines, industry events, and operational dates for RTOs. Events must be immediately scannable by category. Colour is the primary differentiator — each category has a distinct, non-overlapping hue. All colours must pass 3:1 contrast minimum against white card backgrounds in light mode.
14.2 Event Category Colours¶
| Category | Token | Background | Text | Border | Usage |
|---|---|---|---|---|---|
| ASQA / Regulatory | --cal-asqa |
#fef2f2 |
#b91c1c |
#fca5a5 |
ASQA audit dates, regulatory deadlines, Standards updates |
| TEQSA | --cal-teqsa |
#fef9c3 |
#854d0e |
#fde68a |
TEQSA renewal deadlines, provider registration dates |
| Industry Events | --cal-industry |
#eff6ff |
#1d4ed8 |
#bfdbfe |
Conferences, TDA, VELG, Skills Summit |
| Government / Policy | --cal-government |
#f0fdf4 |
#15803d |
#86efac |
Budget dates, policy release windows, consultation periods |
| Training Package | --cal-training-package |
#faf5ff |
#6d28d9 |
#c4b5fd |
Training package updates, TGA release dates |
| Reporting | --cal-reporting |
#fff7ed |
#c2410c |
#fed7aa |
AVETMISS reporting periods, NCVER submission deadlines |
| General / Other | --cal-general |
#f8fafc |
#475569 |
#cbd5e1 |
Uncategorised events, curated general dates |
14.3 CSS Token Declarations¶
/* Calendar event category colours */
--cal-asqa-bg: #fef2f2;
--cal-asqa-text: #b91c1c;
--cal-asqa-border: #fca5a5;
--cal-teqsa-bg: #fef9c3;
--cal-teqsa-text: #854d0e;
--cal-teqsa-border: #fde68a;
--cal-industry-bg: #eff6ff;
--cal-industry-text: #1d4ed8;
--cal-industry-border: #bfdbfe;
--cal-government-bg: #f0fdf4;
--cal-government-text: #15803d;
--cal-government-border: #86efac;
--cal-training-package-bg: #faf5ff;
--cal-training-package-text: #6d28d9;
--cal-training-package-border: #c4b5fd;
--cal-reporting-bg: #fff7ed;
--cal-reporting-text: #c2410c;
--cal-reporting-border: #fed7aa;
--cal-general-bg: #f8fafc;
--cal-general-text: #475569;
--cal-general-border: #cbd5e1;
14.4 FullCalendar Event Rendering¶
Each event is rendered with the category's background as the event chip background, text colour as the chip text, and border-left as a 3px solid accent stripe using the border token.
// Event rendering pattern — apply in eventContent or eventClassNames
const categoryStyles = {
asqa: { bg: 'var(--cal-asqa-bg)', color: 'var(--cal-asqa-text)', border: 'var(--cal-asqa-border)' },
teqsa: { bg: 'var(--cal-teqsa-bg)', color: 'var(--cal-teqsa-text)', border: 'var(--cal-teqsa-border)' },
industry: { bg: 'var(--cal-industry-bg)', color: 'var(--cal-industry-text)', border: 'var(--cal-industry-border)' },
government: { bg: 'var(--cal-government-bg)', color: 'var(--cal-government-text)', border: 'var(--cal-government-border)' },
training_package: { bg: 'var(--cal-training-package-bg)', color: 'var(--cal-training-package-text)', border: 'var(--cal-training-package-border)' },
reporting: { bg: 'var(--cal-reporting-bg)', color: 'var(--cal-reporting-text)', border: 'var(--cal-reporting-border)' },
general: { bg: 'var(--cal-general-bg)', color: 'var(--cal-general-text)', border: 'var(--cal-general-border)' },
};
Event chip CSS:
.fc-event-custom {
background: var(--event-bg);
color: var(--event-color);
border: none;
border-left: 3px solid var(--event-border);
border-radius: 4px;
padding: 2px 6px;
font-size: 11px;
font-weight: 500;
font-family: Inter, sans-serif;
}
14.5 Calendar Surface Rules¶
Category colours are fixed. Do not re-use category colours for other purposes on the calendar surface. ASQA red is ASQA red — never use it for a general alert on the same surface.
TEQSA renewal events (217 events generated from the TEQSA ingest in DATA-17) use teqsa category. The date that appears on the chip is the registration renewal deadline.
The calendar is light mode only. The calendar token system does not have dark mode equivalents. The calendar surface inherits the admin surface light mode treatment.
Legend is required whenever the calendar is visible. All seven category colours must be labelled in a persistent legend — users should not need to infer categories from colour memory.
§15 Knowledge Navigator Rendering Standard¶
This is a Foundation-level decision. It applies to every surface where KN content appears, now and in future.
§15.1 Layout rule — stack only, never side-by-side¶
TGA source content and KN interpretations are always stacked vertically. No side-by-side layout, no two-column split, no parallel rendering. This is non-negotiable across all surfaces, all viewports, all contexts.
§15.2 TGA source content¶
- Renders in white (
var(--color-text-primary)) - Full width of the content area
- Exactly as sourced from TGA — no rewriting, no reformatting, no summarisation
- Never moves, never changes regardless of KN toggle state
§15.3 KN interpretation content¶
- Renders in green (
var(--color-kn-accent, #0f9e82)) - Appears directly below the criterion text it refers to
- Slightly smaller font size than the TGA text (
0.8125remvs0.875rem) - Left-aligned with the criterion text (indented to match)
- Prefixed or visually distinguished as KN content (e.g. left border, background tint, or
[KN]marker)
§15.4 KN toggle behaviour¶
- KN ON: Show green KN interpretations below each criterion
- KN OFF: Hide all green KN text. TGA source text remains exactly as it was — no layout shift, no repositioning
- Toggle state persists in
localStorage(kn-enabled)
§15.5 Rendering order per element¶
1. Element title ← white, full width
[element_summary if available] ← green, muted, below title
1.1 Criterion text from TGA ← white
KN interpretation for 1.1 ← green, smaller, indented
1.2 Criterion text from TGA ← white
KN interpretation for 1.2 ← green, smaller, indented
2. Next element title ← white
...
§15.6 Tokens¶
| Token | Value | Use |
|---|---|---|
--color-kn-accent |
#0f9e82 |
KN text colour |
--color-kn-bg |
rgba(15, 158, 130, 0.08) |
KN block background |
--color-kn-border |
rgba(15, 158, 130, 0.15) |
KN block left border |
--color-kn-text |
#4ec9a8 |
KN indicator bar text |
16. Admin Surface — Detail Pages¶
16.1 Philosophy¶
Detail pages in the admin surface (organisations, contacts, clients) are data-dense CRM views. They must feel like a natural extension of the list pages — same tokens, same rhythm, same dark-mode-first posture. They are not public-facing. Density is a feature. Whitespace is earned, not assumed.
16.2 Token Mandate¶
All colour, typography, spacing, and border values on admin detail pages
MUST use CSS custom properties from §13.2. Hardcoded rgb(), rgba(),
hex literals, or bare font-family strings are a build failure, not a style
preference.
Zero-tolerance rule: Any element with a style attribute containing a
hardcoded colour value is non-compliant. The compliance check is:
Array.from(document.querySelectorAll('[style]'))
.filter(el => /rgb\(|rgba\(|#[0-9a-f]{3,8}/i.test(el.getAttribute('style')))
.filter(el => !el.closest('nav'))
.length // must be 0
16.3 Page Header¶
The detail page header contains: page title (legal name), RTO code badge, status badge, regulator badge, and optionally a plan badge (clients only).
[← Back to {list}]
[Legal Name H1] [RTO CODE] [STATUS] [REGULATOR] [PLAN?]
[Trading name if different]
Tokens:
| Element | Token |
|---|---|
| Back link | color: var(--paper-muted) font-size: var(--rp-text-sm) |
| H1 legal name | color: var(--paper-bright) font-size: 1.4rem font-weight: 700 |
| RTO code badge | font-family: var(--rp-font-mono) font-size: 0.8rem color: var(--paper-muted) background: var(--rp-bg-card) border: 1px solid var(--rp-border) border-radius: 4px padding: 2px 8px |
| Trading name | color: var(--paper-dim) font-size: 0.85rem |
Regulator badge uses var(--rp-accent) (teal) — not blue. No
exceptions. It follows the same pattern as secondary badges in §13.7.
/* Regulator badge — canonical */
color: var(--rp-accent);
background: rgba(14, 116, 144, 0.12); /* rp-accent at 12% opacity */
font-size: 0.65rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 4px;
16.4 Tab Rail¶
The tab rail sits below the header, flush left, with an underline-style active indicator. It is not a card — it sits on the page background.
/* Tab rail container */
display: flex;
gap: 0;
border-bottom: 1px solid var(--rp-border);
margin-bottom: 20px;
/* Individual tab button */
padding: 10px 18px;
font-size: 0.8rem;
font-family: inherit;
background: none;
border: none;
cursor: pointer;
/* Inactive */
color: var(--paper-dim);
border-bottom: 2px solid transparent;
/* Active */
color: var(--rp-accent);
font-weight: 600;
border-bottom: 2px solid var(--rp-accent);
Tab order for organisation detail pages follows TGA tab order exactly:
Summary · Scope · Qualifications · Units · Notes · Subscription · Activity
Qualifications and Units tabs render a "coming in next phase" stub — they still appear in the rail. Do not omit them.
16.5 Section Headers¶
Section headers within tabs (e.g. "REGISTRATION", "LOCATION", "SCOPE SUMMARY") use the canonical admin section header treatment from §13.7:
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--paper-muted);
margin-top: 20px;
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 1px solid var(--rp-border);
16.6 Info Rows (Label/Value Pairs)¶
The canonical pattern for displaying RTO field data:
/* Row container */
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 5px 0;
border-bottom: 1px solid var(--rp-border-mid);
font-size: 0.8rem;
/* Label */
color: var(--paper-muted);
flex-shrink: 0;
margin-right: 12px;
/* Value */
color: var(--paper-bright);
text-align: right;
Rules:
- If value is null or empty string, render nothing (return null from component).
- If value is a URL, render as a link: color: var(--rp-accent).
- Never render raw objects. Coerce to string before rendering.
16.7 Status Badges¶
Use the canonical StatusBadge component from §13.7. Do not reimplement
inline. The component already handles Current / Non-current / Cancelled /
Withdrawn / Suspended correctly.
16.8 Notes Tab¶
The notes tab is append-only. The input area:
/* Textarea */
width: 100%;
min-height: 80px;
padding: 12px;
background: var(--rp-bg-card);
border: 1px solid var(--rp-border);
border-radius: 8px;
color: var(--paper-bright);
font-size: 0.85rem;
font-family: inherit;
outline: none;
resize: vertical;
/* Submit button — uses primary action style from §13.7 */
background: var(--rp-accent);
color: #fff;
border: none;
border-radius: 8px;
padding: 8px 20px;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
Note entries in the list:
/* Note body */
font-size: 0.8rem;
color: var(--paper-bright);
line-height: 1.6;
white-space: pre-wrap;
/* Note meta (author · timestamp) */
font-size: 0.7rem;
color: var(--paper-muted);
margin-top: 4px;
16.9 Subscription Tab¶
Only visible and populated when client !== null. When client is null,
render the "Convert to client" action panel instead (see §16.10).
Client data display uses standard info rows (§16.6). Action buttons:
/* Suspend button */
background: rgba(210, 153, 34, 0.12);
color: var(--rp-yellow, #d29922);
border: 1px solid rgba(210, 153, 34, 0.3);
border-radius: 8px;
padding: 8px 16px;
font-size: 0.8rem;
font-weight: 600;
/* Reactivate button */
background: rgba(16, 185, 129, 0.12);
color: var(--rp-green);
border: 1px solid rgba(16, 185, 129, 0.3);
border-radius: 8px;
padding: 8px 16px;
font-size: 0.8rem;
font-weight: 600;
16.10 Convert to Client Panel¶
Shown in Subscription tab when organisation is not yet a client:
This RTO is not a client yet.
[Convert → free] [Convert → essential] [Convert → pro] [Convert → enterprise]
Buttons use the tertiary button style from §13.7 (bg-card, rp-border, paper-bright text, capitalised label).
16.11 Activity Tab¶
Stub state — renders a single dim message:
16.12 Two-Column Layout (Summary Tab)¶
The summary tab uses a two-column flex layout:
display: flex;
gap: 24px;
flex-wrap: wrap;
/* Left column — registration + decisions */
flex: 1 1 400px;
/* Right column — location + contact */
flex: 1 1 300px;
No explicit max-width on the columns. The page container caps at 1200px.
16.13 Admin Detail Page Rules¶
- No hardcoded colours. All values from §13.2 token system.
- No bare
font-family: monospace. Alwaysvar(--rp-font-mono). - Regulator badge is teal (
var(--rp-accent)), never blue. StatusBadgecomponent is canonical — never reimplement inline.- Tab rail border uses
var(--rp-border), not hardcoded. contact_notesis append-only — no edit or delete UI, ever.- Subscription tab guards
client !== nullbefore rendering client fields. data.activitydefaults to[]if absent from API response.- TGA tab order is fixed — Summary → Scope → Qualifications → Units → Notes → Subscription → Activity. No reordering without Tim sign-off.
- URLs in info rows render as anchor tags with
color: var(--rp-accent).
§17 Mode System¶
§17.1 Overview¶
The RTOpacks admin panel implements the UCCA Mode System Standard (UMS-001 v1.0), a licensed platform pattern that provides three contextual views of every admin surface from a single interface. The full specification governing this implementation is at docs/standards/mode-system.md (RTO-UMS-001 v1.0).
This section defines the visual design tokens, colour standards, and rendering rules specific to the RTOpacks implementation. Where this section is silent, UMS-001 governs.
§17.2 The Three Modes¶
| Mode | Audience | Overlay |
|---|---|---|
| LIVE | Experienced operator | None — clean workspace |
| GUIDED | Learner, new staff | Blue information badges on annotated elements |
| COMPLIANCE | Auditor, governance review | Amber governance badges on annotated elements |
LIVE is the default. Mode state persists in localStorage across navigation. Mode change takes effect immediately — no page reload.
§17.3 Mode Tab — Visual State¶
Mode tabs sit in the top bar, left of centre. They are always visible on admin surfaces.
Inactive tab:
- Background: transparent
- Text: var(--color-text-secondary)
- Border: none
Active tab:
- Background: var(--color-surface-raised) or equivalent subtle fill
- Text: var(--color-text-primary)
- Bottom border: 2px solid var(--color-accent)
- Font weight: 600
The active tab must be visually unambiguous at a glance. Never rely on colour alone — weight and border reinforce the state.
§17.4 GUIDED Mode Badge¶
Applied to every element carrying a data-annotation-id attribute that has a corresponding entry in the annotation file.
Visual specification:
┌─────────────────────────────────────────────────┐
│ ℹ Title of element │
│ Plain English explanation of what this │
│ element is and what to do with it. │
│ × │
└─────────────────────────────────────────────────┘
| Property | Value |
|---|---|
| Background | #E8F4FD |
| Border | 1px solid #B8D9F0 |
| Border radius | 6px |
| Left accent | 3px solid #2E86C1 |
| Icon | ℹ️ — 13px, #2E86C1 |
| Title | 12px, weight 600, #1A5276 |
| Body text | 12px, weight 400, #2C3E50 |
| Dismiss button | Small ×, top-right, #7F8C8D |
| Max width | 320px |
| Padding | 10px 12px |
| Shadow | 0 1px 4px rgba(0,0,0,0.08) |
Placement: Directly below the annotated element, left-aligned with it. If insufficient space below, place to the right.
Animation: Fade in + 4px slide down over 150ms when mode is toggled on. Fade out 100ms when dismissed or mode is toggled off.
Draft state: When _draft: true, badge renders with a dashed border instead of solid. Adds a small "Draft" label in grey below the body text. Draft badges are visible but flagged as unreviewed.
Dismissal: The × button dismisses the badge for that element for that user. Dismissal is stored in localStorage under key rtopacks_guided_dismissed. A "Reset all dismissed annotations" option is available in Settings → Display.
§17.5 COMPLIANCE Mode Badge¶
Applied to every element carrying a data-annotation-id attribute. Unlike GUIDED, COMPLIANCE badges are not dismissible.
Visual specification:
┌─────────────────────────────────────────────────┐
│ 🔖 REF-ID · Policy Reference Title │
│ Policy Document §Section │
│ One-sentence rationale for this element. │
└─────────────────────────────────────────────────┘
| Property | Value |
|---|---|
| Background | #FFF8E7 |
| Border | 1px solid #F0D9A0 |
| Border radius | 6px |
| Left accent | 3px solid #01497C |
| Icon | 🔖 — 12px |
| Ref ID | 12px, weight 700, #01497C |
| Title | 12px, weight 600, #2C3E50 |
| Clause | 11px, weight 400, #7F8C8D, italic |
| Summary | 12px, weight 400, #2C3E50 |
| Max width | 320px |
| Padding | 10px 12px |
| Shadow | 0 1px 4px rgba(0,0,0,0.08) |
Placement: Same as GUIDED — directly below the element, left-aligned.
Not dismissible. No × button. The compliance map must remain fully visible when the mode is active.
Draft state: Dashed border. "Draft — pending governance review" label in amber text below summary.
§17.6 Placeholder Badges¶
Elements with a data-annotation-id attribute but no corresponding annotation file entry render placeholder badges.
GUIDED placeholder:
| Property | Value |
|---|---|
| Background | #F5F5F5 |
| Border | 1px dashed #CCCCCC |
| Left accent | 3px solid #CCCCCC |
| Text | "Documentation pending for this element." |
| Text colour | #999999 |
| Icon | ℹ️ grey |
| Dismissible | Yes |
COMPLIANCE placeholder:
| Property | Value |
|---|---|
| Background | #FFF8E7 |
| Border | 1px dashed #F0A500 |
| Left accent | 3px solid #F0A500 |
| Text | "No compliance mapping — [element_id]" |
| Text colour | #7F6000 |
| Icon | ⚠️ |
| Dismissible | No |
COMPLIANCE placeholders are intentional gap indicators. They make the coverage gaps visible to the auditor. They are not errors — they are the to-do list.
§17.7 Export Button — COMPLIANCE Mode¶
In COMPLIANCE mode only, an Export button appears in the top bar to the right of the mode tabs.
| Property | Value |
|---|---|
| Label | "Export" |
| Icon | ↓ (download) |
| Background | var(--color-accent) |
| Text | White |
| Visibility | COMPLIANCE mode only — hidden in LIVE and GUIDED |
Clicking Export generates a JSON download: rtopacks-compliance-register-{YYYY-MM-DD}.json
The export contains: export date, surface identifier, total annotated elements, mapped elements, unmapped elements, coverage percentage, and the full mapping array (element_id, ref, title, clause, summary, framework, draft status).
§17.8 Colour Token Reference¶
These are the canonical colour values for the Mode System in RTOpacks. They must not be overridden at the component level.
| Token | Hex | Usage |
|---|---|---|
--mode-guided-bg |
#E8F4FD |
GUIDED badge background |
--mode-guided-border |
#B8D9F0 |
GUIDED badge border |
--mode-guided-accent |
#2E86C1 |
GUIDED left accent + icon |
--mode-guided-title |
#1A5276 |
GUIDED badge title text |
--mode-compliance-bg |
#FFF8E7 |
COMPLIANCE badge background |
--mode-compliance-border |
#F0D9A0 |
COMPLIANCE badge border |
--mode-compliance-accent |
#01497C |
COMPLIANCE left accent (Prussian blue) |
--mode-compliance-ref |
#01497C |
COMPLIANCE ref ID text |
--mode-placeholder-bg |
#F5F5F5 |
GUIDED placeholder background |
--mode-placeholder-gap-bg |
#FFF8E7 |
COMPLIANCE gap indicator background |
--mode-placeholder-gap-accent |
#F0A500 |
COMPLIANCE gap indicator accent |
These tokens are inherited from the UCCA master standard (UMS-001). Cross-product consistency is intentional — the same badge colours on any UCCA-engine product carry the same meaning.
§17.9 Annotation Files¶
Annotation content for the RTOpacks admin panel lives in:
docs/annotations/
organisations.en.json
radar.en.json
dashboard.en.json
corpus.en.json
regulatory.en.json
calendar.en.json
settings.en.json
Translations are stored as locale-suffixed copies of the English master:
These files are first-class source documents. They are edited by non-developers. Treat them accordingly — do not minify, do not auto-format, preserve human-readable structure.
§17.10 Element Identifier Convention¶
Every annotatable element carries a data-annotation-id attribute using the pattern:
Examples:
rtopacks.admin.radar.trigger_crawl
rtopacks.admin.radar.signal_confidence_authoritative
rtopacks.admin.organisations.abn_status
rtopacks.admin.dashboard.rto_count
rtopacks.admin.corpus.unit_count
Identifiers are permanent. Once assigned, never change. If an element is removed, archive its annotation entry — do not delete.
§17.11 Implementation Status¶
| Item | Status |
|---|---|
| Mode tabs render in top bar | ✓ Complete |
| Mode tabs wired (state management) | ✗ Pending — ADMIN-MODE-01 |
| Annotation renderer (GUIDED) | ✗ Pending — ADMIN-MODE-01 |
| Annotation renderer (COMPLIANCE) | ✗ Pending — ADMIN-MODE-01 |
| Export button | ✗ Pending — ADMIN-MODE-01 |
| Element tagging (data-annotation-id) | ✗ Pending — ADMIN-MODE-01 |
| Seed annotation files | Partial — radar.en.json seeded in RTO-UMS-001 |
| Translation pipeline | ✗ Pending — future |
Implementation is governed by brief ADMIN-MODE-01 (to be written).
§19 Current / Historical Data Display Rule¶
§19.1 Principle¶
All admin surfaces that display data with a temporal dimension (current vs historical, active vs superseded/deleted/expired) must respect a single global toggle: Current only.
This is a product-level decision, not a per-tab or per-surface decision. One toggle, one concept, one localStorage key.
§19.2 Toggle¶
- Position: RTO card header, below the name/badges, above the tab rail. Always visible regardless of which tab is active.
- Default: ON (current only)
- localStorage key:
rto_filter_current— single shared key across all tabs and surfaces - Visual: iOS-style toggle switch with "Current only" label. When OFF, show muted text: "Showing all records including historical"
§19.3 "Current" Definition by Data Type¶
| Data type | Current means | Historical means |
|---|---|---|
| Qualifications | usage_recommendation = 'Current' |
Superseded, Deleted |
| Units | usage_recommendation = 'Current' |
Superseded, Deleted |
| Skill sets | usage_recommendation = 'Current' |
Superseded, Deleted |
| Courses | usage_recommendation = 'Current' |
Non-current |
| Scope overview | Title does not contain "(Superseded by" | Contains "(Superseded by" |
| Addresses | is_current = 1 (or latest start_date per physical address for delivery locations) |
is_current = 0 |
| Contacts | Most recent entry per contact type | Older entries |
§19.4 Historical Row Treatment¶
When toggle is OFF and historical rows are visible:
- Superseded/expired rows: 55% opacity
- Deleted rows: 45% opacity
- Current rows: full opacity
- Each historical row shows a "From [date]" or "Valid [date range]" badge from start_date
- Sort order: current records first, then by date DESC
§19.5 Rule for New Features¶
Any new feature that displays data with a current/historical distinction MUST:
1. Read rto_filter_current from localStorage
2. Respect the global toggle state
3. Use the opacity dimming treatment defined in §19.4
4. NOT introduce its own per-tab toggle or per-surface toggle
This rule applies to all future admin surfaces — if it has a time dimension, it respects the global toggle.
§18 System Voice¶
§18.1 Concept¶
RTOpacks has a "system voice" — moments where the product speaks directly to the user to explain, guide, or enrich. Two features share this job:
- Knowledge Navigator — AI-generated explanation of competency units (named feature, trademark candidate)
- Guided mode annotations — UI-layer explanations of admin panel surfaces and controls
Both are the system talking to the user. Both must use the same visual language.
§18.2 System Voice Colour¶
The system voice colour is RTOpacks Green: #0f7a58 (primary), #1daa7a (accent).
This colour means: "RTOpacks is speaking to you."
It is used for: - Knowledge Navigator toggle and panel - Guided mode annotation banners - Any future surface where the product explains itself
It is not used for: - Status indicators - Data visualisation - External link styling
§18.3 Rule¶
Any UI element where RTOpacks speaks directly to the user in an explanatory or guiding capacity must use the system voice green treatment: white text on
#0f7a58background with#1daa7aleft accent.
§18.4 GUIDED Annotation Banner Spec¶
Full-width annotation banners replace constrained floating boxes. Same green system voice treatment as Knowledge Navigator.
| Property | Value |
|---|---|
| Width | Full content area — no max-width constraint |
| Position | Flush above the annotated element, in flow |
| Background | #0f7a58 |
| Left accent | 4px solid #1daa7a |
| Text | white, 13px, regular weight |
| Padding | 8px 12px |
| Border radius | 0 (flush) |
| Dismiss button | Right-aligned, white, 18px, opacity 0.7, hover 1.0 |
| Single line height | ~36px |
| Multi-line | Expands naturally |
| Draft label | Small pill, white border, white text, opacity 0.5 |
Dismissal persists in localStorage. Dismissed annotations do not reappear on reload. GUIDED mode toggle shows/hides all annotations.
§18.5 COMPLIANCE Badge — Unchanged¶
COMPLIANCE mode badges retain their amber treatment (#FFF8E7 background, #01497C Prussian blue accent). They are not affected by the system voice colour. COMPLIANCE is governance, not guidance — different voice, different colour.
§20 Workspace Surface¶
The Workspace (my.rtopacks.com.au) is the authenticated operator console for RTOs. It is distinct from both rtopacks.com.au (public catalogue) and admin.rtopacks.com.au (L2 internal operator panel). The Workspace is the product surface — what a subscribing RTO sees and uses daily.
20.1 Surface Identity¶
| Attribute | Value |
|---|---|
| URL | my.rtopacks.com.au |
| Access | Auth-gated — magic link via RTOPACKS_SESSIONS KV |
| Classification | L1 SUBSCRIBER |
| Mode | Dark mode default, light mode supported — inherits public surface token system |
| Stack | Next.js via OpenNext on Cloudflare Workers |
| Repo path | apps/workspace/ (rtopacks monorepo) |
| Token system | Inherits §1.2/§1.3 — public surface tokens, NOT admin tokens |
The Workspace uses the public surface token system (--color-bg-base, --color-bg-elevated, --color-bg-card, --color-interactive blue, etc.). It does not use admin tokens (--bg-primary, --paper-*, --rp-teal, --accent). These are different products with different visual languages. Never cross-contaminate.
20.2 AppGrid¶
The AppGrid is the Workspace home screen (/workspace). It is the primary navigation surface.
Container:
Grid label ("WORKSPACE"):
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-text-secondary);
/* label role — §2.2 */
App tiles — live (active modules):
background: var(--color-bg-elevated);
border: 1px solid var(--color-border);
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: background var(--duration-fast) ease, border-color var(--duration-fast) ease;
Hover:
App tiles — pending (not yet available):
No hover state. No link. The tile is present for orientation (shows the roadmap) but is inert.
App tile icon: 24px, --color-interactive blue for live tiles. --color-text-secondary for pending.
App tile label: body role — 0.875rem / 400 / --color-text-primary.
App tile sub-label (description or "Coming soon"): meta role — 0.75rem / 400 / --color-text-secondary.
AppGrid rows (current):
| Row | Modules |
|---|---|
| 1 | Studio · People & Culture · Document Manager |
| 2 | Radar · Landscape · Market Data · Marketer |
| 3 | SMS Connect · LMS Connector · GenEd Store |
Row 1 tiles are live. Rows 2–3 are pending. This will change as modules ship — update AppGrid layout rules when a new module goes live; do not change the pending visual without Tim sign-off.
20.3 Workspace Navigation¶
The Workspace uses a persistent sidebar on module pages (not on the AppGrid). The sidebar is not the same as the admin sidebar — it is lighter, thinner, and uses public surface tokens.
/* Sidebar container */
width: 220px;
background: var(--color-bg-elevated);
border-right: 1px solid var(--color-border);
height: 100vh; /* minus nav bar */
Section group labels (e.g. "PEOPLE", "DOCUMENTS"):
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-text-secondary);
padding: 16px 16px 4px;
/* label role */
Nav items:
font-size: 0.875rem; /* body role */
color: var(--color-text-secondary);
padding: 8px 16px;
border-radius: 6px;
transition: background var(--duration-fast) ease, color var(--duration-fast) ease;
Hover:
Active:
background: rgba(37, 99, 235, 0.08); /* --color-interactive at 8% */
color: var(--color-text-primary);
font-weight: 500;
border-left: 2px solid var(--color-interactive);
padding-left: 14px; /* compensate for 2px border */
20.4 Traffic Light System¶
Traffic lights appear in the Document Manager to indicate document review status. They use Foundation status tokens — no custom colours.
| Status | Condition | Token | Dot size |
|---|---|---|---|
| Green (current) | next_review_date > 60 days away | --color-success (#3fb950) |
8px |
| Amber (due soon) | next_review_date ≤ 60 days, > 0 days | --color-warning (#d29922) |
8px |
| Red (overdue) | next_review_date past, or null | --color-danger (#f85149) |
8px |
Dots are CSS circles — width: 8px; height: 8px; border-radius: 50%; background: var(--color-{status}). No icon. No label inline (label appears on hover or in detail view only). Never use SVG icons for traffic lights.
Traffic lights appear: - In the category sidebar next to each category name (aggregate: worst status of any document in that category) - On each document card (individual document status) - In the standards view as a document count indicator per clause mapping
20.5 Access Level Badge System¶
Documents carry an access level that determines who can view them. Access level badges appear on document cards and in the detail drawer.
| Level | Background | Text |
|---|---|---|
| public | rgba(63, 185, 80, 0.12) | --color-success |
| student | rgba(88, 166, 255, 0.12) | --color-info |
| trainer | rgba(210, 153, 34, 0.12) | --color-warning |
| staff | rgba(37, 99, 235, 0.12) | --color-interactive |
| admin | rgba(248, 81, 73, 0.12) | --color-danger |
Badge rendering (all levels):
font-size: 0.7rem;
font-weight: 500;
padding: 2px 8px;
border-radius: 999px;
font-family: 'Inter', system-ui, sans-serif;
white-space: nowrap;
Access level determines visual prominence — public is least alarming (green), admin is most restricted (red). This colour hierarchy is deliberate: it maps alarm to restriction level.
20.6 Document Manager Specific Rules¶
Clause mapping chips (appearing on document cards and in the detail drawer):
These reference compliance clause IDs from the 2025 Standards. They use a teal tint as a data identity colour — this is the single documented exception to the KN-only teal rule. Rationale: clause references are a standards-compliance identity element, analogous to map markers (§9.5).
background: rgba(15, 158, 130, 0.08); /* --color-kn-bg */
color: var(--color-kn-accent); /* #0f9e82 */
border: 1px solid rgba(15, 158, 130, 0.2);
font-size: 0.7rem;
font-weight: 500;
padding: 2px 8px;
border-radius: 4px;
white-space: nowrap;
Standards view — section headers (Outcome Standards / Compliance Standards / Credential Policy):
These use a teal left border as a standards identity element — second documented exception. Rationale: the Standards view is a compliance surface and teal carries "authoritative standard" meaning in this context.
border-left: 2px solid var(--color-kn-accent);
padding-left: 8px;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-text-secondary);
Missing document indicator:
When the standards view detects a clause with no mapped documents:
⚠ No documents mapped — --color-danger, SF Symbol exclamationmark.triangle 14px inline
Upload drawer:
Slides in from right. Uses --duration-panel (280ms) --ease-out.
background: var(--color-bg-elevated);
border-left: 1px solid var(--color-border);
/* Scrim behind: */
background: var(--color-scrim-heavy); /* rgba(0,0,0,0.4) */
Drop zone:
border: 2px dashed var(--color-border);
border-radius: 8px;
transition: border-color var(--duration-fast) ease, background var(--duration-fast) ease;
Drop zone hover / drag-over:
20.7 Workspace Surface Rules¶
- Public surface tokens only. Never use
--bg-primary,--paper-*,--rp-teal,--accent, or any admin token onmy.rtopacks.com.au. These surfaces share a repo but have separate token systems. - Pending tiles are inert.
opacity: 0.45,pointer-events: none, no hover, no link. They communicate roadmap, not availability. - Traffic lights are dots, not icons. 8px CSS circles only. No SVG, no emoji, no Phosphor icons.
- Clause chips are the only teal exception in non-Standards views. All other teal is KN-only.
- Standards section headers are the only teal exception in the Standards view. Standards identity, not KN identity.
- Both teal exceptions are documented here. Any future teal usage outside KN, clause chips, and standards headers requires a new documented exception with Tim sign-off.
backdrop-filter: blur(6px)on drawers only. Not on cards, not on sidebars, not on inline components.- Scrim always uses
--color-scrim-heavy. Never hardcodergba(0,0,0,0.4). - No animations on data content. Hover transitions only. No entrance animations on document cards, clause trees, or person records.
- AppGrid pending row structure is fixed. Do not reorder rows without Tim sign-off. Row 1 live, Rows 2–3 pending — this changes only when a module ships.
§21 Liquid Glass Surface System¶
21.1 What Liquid Glass Is (RTOpacks Definition)¶
Liquid Glass is RTOpacks' visual material system for navigation chrome. It is not a visual effect applied for decoration. It is a structured approach to communicating depth — telling the user what layer they are on, what is behind them, and what is interactive.
The underlying principle: surfaces that float above content should look like they float. They do this through controlled translucency, a specular highlight at the top edge, and a shadow that separates them from what's below. The content underneath bleeds through slightly — enough to feel spatial, not enough to compromise legibility.
This is a web implementation derived from Apple's Liquid Glass design language (WWDC 2025). The SwiftUI APIs don't exist here. What does exist is backdrop-filter, rgba() backgrounds, box-shadow, and border. Those four CSS properties are the complete toolkit.
The cardinal rule: glass belongs on the navigation layer. Never on the content layer.
21.2 The Two Variants¶
There are exactly two glass variants. They are never mixed on the same surface.
Regular Glass
Used for: sidebars, drawers, modals, the AppGrid tile surface, the top nav bar on authenticated surfaces.
/* Regular Glass — dark mode */
background: rgba(22, 27, 34, 0.75); /* --color-bg-elevated at 75% */
border: 1px solid rgba(48, 54, 61, 0.8); /* --color-border at 80% */
border-radius: 16px; /* tiles, panels */
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.07) inset, /* specular highlight — top edge */
0 4px 24px rgba(0, 0, 0, 0.3); /* depth shadow */
Regular glass is adaptive — it reads legibly over any background, dark or light, simple or complex. Text at --color-text-primary on Regular glass always meets 4.5:1 contrast.
Clear Glass
Used for: floating indicators, badge overlays, minimal-footprint controls that sit directly over rich content (video, imagery, maps). Never used for panels, sidebars, or drawers.
/* Clear Glass — requires dimming layer beneath */
background: rgba(22, 27, 34, 0.35); /* much lower opacity */
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.05) inset;
Clear glass requires a dimming layer on the content beneath it when used over text or imagery. Without it, legibility fails. The dimming layer:
If you cannot add a dimming layer, use Regular glass instead.
21.3 Token Set¶
All glass values are declared as CSS custom properties. Hardcoded rgba() values that aren't derived from these tokens are a build failure.
:root {
/* Regular Glass */
--glass-regular-bg: rgba(22, 27, 34, 0.75);
--glass-regular-border: rgba(48, 54, 61, 0.8);
--glass-regular-blur: 12px;
--glass-regular-highlight: rgba(255, 255, 255, 0.07);
--glass-regular-shadow: rgba(0, 0, 0, 0.3);
/* Regular Glass — hover state */
--glass-regular-bg-hover: rgba(33, 38, 45, 0.85);
--glass-regular-border-hover: rgba(37, 99, 235, 0.4);
--glass-regular-shadow-hover: rgba(0, 0, 0, 0.4);
--glass-regular-glow-hover: rgba(37, 99, 235, 0.15);
/* Clear Glass */
--glass-clear-bg: rgba(22, 27, 34, 0.35);
--glass-clear-border: rgba(255, 255, 255, 0.08);
--glass-clear-blur: 8px;
--glass-clear-highlight: rgba(255, 255, 255, 0.05);
--glass-clear-dimming: rgba(0, 0, 0, 0.25);
/* Shared */
--glass-radius-tile: 16px;
--glass-radius-drawer: 12px;
--glass-radius-control: 8px;
--glass-transition: var(--duration-fast) ease;
}
Light mode glass — glass is a dark-mode concept. In light mode, surfaces use solid --color-bg-elevated with border: 1px solid var(--color-border). No backdrop-filter. No translucency. Glass does not appear in light mode.
21.4 Canonical CSS Patterns¶
AppGrid tile (Regular glass, live):
.tile-live {
background: var(--glass-regular-bg);
border: 1px solid var(--glass-regular-border);
border-radius: var(--glass-radius-tile);
backdrop-filter: blur(var(--glass-regular-blur));
-webkit-backdrop-filter: blur(var(--glass-regular-blur));
box-shadow:
0 1px 0 var(--glass-regular-highlight) inset,
0 4px 24px var(--glass-regular-shadow);
transition: all var(--glass-transition);
}
.tile-live:hover {
background: var(--glass-regular-bg-hover);
border-color: var(--glass-regular-border-hover);
box-shadow:
0 1px 0 var(--glass-regular-highlight) inset,
0 8px 32px var(--glass-regular-shadow-hover),
0 0 0 1px var(--glass-regular-glow-hover);
transform: translateY(-1px);
}
Drawer / sidebar panel (Regular glass):
.drawer {
background: var(--glass-regular-bg);
border-left: 1px solid var(--glass-regular-border);
backdrop-filter: blur(var(--glass-regular-blur));
-webkit-backdrop-filter: blur(var(--glass-regular-blur));
box-shadow:
0 1px 0 var(--glass-regular-highlight) inset,
-4px 0 32px var(--glass-regular-shadow);
}
Floating badge over video (Clear glass):
.floating-badge-dimming {
background: var(--glass-clear-dimming);
border-radius: inherit;
}
.floating-badge {
background: var(--glass-clear-bg);
border: 1px solid var(--glass-clear-border);
border-radius: var(--glass-radius-control);
backdrop-filter: blur(var(--glass-clear-blur));
-webkit-backdrop-filter: blur(var(--glass-clear-blur));
box-shadow: 0 1px 0 var(--glass-clear-highlight) inset;
}
21.5 Layer Rules¶
Glass belongs on the navigation layer. Content lives on solid surfaces beneath it.
| Layer | Glass? | Surface treatment |
|---|---|---|
| Page background | Never | var(--color-bg-base) solid |
| Content cards | Never | var(--color-bg-card) solid, border: 1px solid var(--color-border) |
| Data tables | Never | var(--color-bg-base) solid |
| Sidebars | Regular Glass | |
| Drawers | Regular Glass | |
| Modals | Regular Glass | |
| AppGrid tiles (live) | Regular Glass | |
| AppGrid tiles (pending) | Never | rgba(13, 17, 23, 0.4) flat — not glass, inert |
| Top nav bar | Regular Glass (on surfaces with rich backgrounds) | |
| Floating badges over video/maps | Clear Glass + dimming layer | |
| Form inputs | Never | var(--color-bg-card) solid |
| Stat cards | Never | Solid |
The test: If you can remove the backdrop-filter and the element still looks right, it shouldn't have had glass in the first place.
21.6 Interaction Rules¶
- Hover on glass tiles:
transform: translateY(-1px)— 1px only. More than 1px looks unintentional. - Hover transition:
var(--glass-transition)(--duration-fast, 120ms, ease). No slower. - Active/pressed:
transform: translateY(0)— returns to rest position immediately. - No looping or pulsing effects on glass surfaces — motion rules from §5.6 apply.
transformon glass elements must not causebackdrop-filterto break — test on Safari. If blur disappears on hover, wrap the glass in a non-transformed parent and applybackdrop-filterto the wrapper,transformto the child.
21.7 Accessibility Fallbacks¶
These are not optional. They apply automatically whenever the user's OS or RTOpacks preference panel sets the relevant flag.
Reduced Transparency:
@media (prefers-reduced-transparency: reduce) {
.tile-live,
.drawer,
.floating-badge {
background: var(--color-bg-elevated);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}
Also applied when data-motion="reduced" is set on :root — reduced motion and reduced transparency are treated as co-occurring preferences in RTOpacks.
High Contrast:
:root[data-contrast="high"] .tile-live,
:root[data-contrast="high"] .drawer {
background: var(--color-bg-elevated);
border-color: var(--color-border);
backdrop-filter: none;
-webkit-backdrop-filter: none;
box-shadow: none;
}
In high contrast mode, glass is replaced entirely with solid surfaces and explicit borders. No opacity tricks.
Minimum contrast requirement: Text on any glass surface must meet 4.5:1 against the rgba() background value at its lowest opacity point. If it doesn't — increase the background opacity or switch to a solid surface. Never reduce font size or weight to compensate.
21.8 What Liquid Glass Does Not Look Like¶
Failure modes:
backdrop-filteron content cards, data tables, or form inputs — glass on the content layerblurabove 12px — the surface reads as an opaque sheet, depth is lostopacityon the element itself (not onbackground-color) — everything inside goes semi-transparent including text and borders- Glass in light mode — translucency on white/near-white backgrounds reads as dirt, not depth
- Regular and Clear glass mixed on the same surface
- Clear glass without a dimming layer beneath it
- Pending/inert tiles with glass treatment — they are not interactive and should not feel material
transform: translateY(-4px)on hover — tiles look like they're floating away- Pulsing or glowing
box-shadowanimations on glass surfaces
21.9 Enforcement¶
Every brief that touches any authenticated surface (my.rtopacks.com.au, admin.rtopacks.com.au) must include the following line:
Foundation ref: fetch §21 before writing any glass treatment.
The Foundation URL is https://rtopacks-docs-proxy.round-union-555d.workers.dev/3e56692821f89038791eed2cf280355d/design/foundation/. This is not optional and is not waived by time pressure.
The self-assurance question for every glass element:
- Is this navigation layer or content layer? If content — remove glass.
- Is this Regular or Clear? Never mix.
- Does it have a reduced-transparency fallback?
- Does text on it meet 4.5:1 contrast at minimum opacity?
- Can you remove the
backdrop-filterand still read the interface? If yes — the glass isn't earning its place.
Changelog¶
| Date | Version | Change |
|---|---|---|
| 2026-04-04 | 1.22 | §21: Liquid Glass Surface System added — two variants (Regular/Clear), full token set (--glass-*), layer rules table, canonical CSS patterns for tiles/drawers/badges, interaction rules, accessibility fallbacks (reduced-transparency, high-contrast), failure modes, enforcement mandate. §0 usage note updated to reference §21 for all authenticated surface briefs. |
| 2026-04-04 | 1.21 | §20: Workspace Surface added — AppGrid tile system (live vs pending), sidebar nav tokens, traffic light dot system, access level badge system, Document Manager specific rules (clause chips teal exception, standards header teal exception, upload drawer, missing doc indicator), 10 Workspace surface rules. |
| 2026-04-03 | 1.18 | §18: System Voice added — unified green treatment for Knowledge Navigator and GUIDED annotations. GUIDED-REDESIGN-01: full-width banners replace constrained floating boxes. |
| 2026-04-03 | 1.17 | §17: Mode System added — three-mode interface pattern (LIVE/GUIDED/COMPLIANCE), badge specs, colour tokens, annotation file structure, element ID convention, export button spec. Implements UMS-001 v1.0. |
| 2026-03-23 | 1.0 | Initial canonical version |
| 2026-03-26 | 1.6 | --color-kn-text updated. Maps section added (§9). ThemeManager extended to maps. |
| 2026-03-27 | 1.7 | §3.3: chevron symbols. §3.4: icon toggle exemption. §4.3: nav clearance. §5.2: --duration-runner. §5.4: nav runner transitions. §5.5: reduced motion extended. §6.2: chevron aria-labels. §8.1: chevrons to themechange listeners. §10: Navigation Chevrons section added. |
| 2026-03-27 | 1.8 | §1.5: video fallback rule. §2.1: body font-family. §2.3: rem-only. §3.1: Phosphor flagged. §4.4: nav system formalised. §4.5: nav height locked 56px. §5.2: entrance/slide tokens. §5.4: waypoint/hero/results panel transitions. §5.6: no-looping on nav. §6.2: wordmark aria-label. §6.3: focus ring global. §8.4: video protection rule. §8.5: pulsing nav to failure list. |
| 2026-03-27 | 1.9 | §4.6: Surface Overlay System. Video is texture. Opacity on rgba() bg only. Tokens. Backdrop filter locked blur(6px). |
| 2026-03-27 | 1.10 | §4.7: Chevron Clearance. --chevron-clearance: 72px. Padding both sides. Right reserved. |
| 2026-03-27 | 1.11 | §1.6: Scrim tokens. §1.7: Video hero tokens. §1.8: Industry badge tokens. SWEEP-04 Group B. |
| 2026-03-27 | 1.12 | §1.8: Six RTO type badge tokens. Badge swatch table. FOUND-02 complete. |
| 2026-04-01 | 1.14 | §15: Knowledge Navigator Rendering Standard. Stack-only layout (never side-by-side). TGA white, KN green. Toggle hides KN text only. Tokens table. |
| 2026-04-02 | 1.16 | §13.9: Admin Data Tables layout rules — no max-width on content wrapper, column wrapping rules (nowrap on fixed-vocabulary, wrap on free-text), status badge nowrap mandate, ultrawide rationale. |
| 2026-04-02 | 1.15 | §16: Admin Surface Detail Pages added — token mandate, page header spec, tab rail (TGA order), section headers, info rows, status badges, notes tab, subscription tab, convert panel, activity stub, two-column layout, 10 hard rules. Numbered as §16 (§15 was KN Rendering Standard). |
| 2026-03-31 | 1.13 | §13: Admin Surface added — L2 INTERNAL ops console. Full token system (--bg-, --paper-, --rp-teal, --accent, --status-, --border*). Impersonation banner spec. Top bar: LIVE/GUIDED/COMPLIANCE tabs, UTC clock, identity from CF-Access JWT. Sidebar: section headers, nav items, status dots, resources footer. Component specs: stat cards, section headers, status badges, tables, buttons. Admin surface rules: provenance headers correct here, no dark mode, monospace structural, no animations, no shadows. §14: Calendar added — seven event category colour tokens (ASQA, TEQSA, Industry, Government, Training Package, Reporting, General). FullCalendar rendering pattern. Category rules and legend requirement. |
This document is authoritative. When in doubt, this document wins over prior briefs, personal preferences, and AI defaults.