Kelas Sekejap — Foundations
The designer-facing guidance behind Say it with confidence v7: the rules for type, space, shape, states, accessibility, motion, iconography, and voice. Tokens named here live in src/styles/tokens.css (and typed in src/tokens/tokens.ts).
Everything carries the four brand rules: named colour blocks · type-first · keep the product calm · organic shapes.
02 · Typography
Three roles do all the work: a heavy Display for loud moments, a friendly Body for reading, a Mono for labels/tokens/eyebrows. One Display element per view — the same discipline as "one loud primary."
| Role | Family | Usage |
|---|---|---|
| Display | Archivo Black | UPPERCASE, stacked. Tracking −0.065 to −0.085em, leading 0.80–0.86 |
| Body | Plus Jakarta Sans | Sentence case. Runs heavy — 700 floor, 900 for buttons |
| Mono / Label | JetBrains Mono | UPPERCASE eyebrows, tags, tokens. Tracking +6–10% |
Type scale (token · desktop / mobile · line-height):
| Token | Size | Line-height |
|---|---|---|
--ks-text-display-xl |
64 / 40px | .95 |
--ks-text-display-l |
40 / 32px | 1.0 |
--ks-text-heading-l |
28px | 1.1 |
--ks-text-heading-m |
20px | 1.25 |
--ks-text-body-l |
18px | 1.5 |
--ks-text-body-m |
16px | 1.5 |
--ks-text-body-s |
14px | 1.45 |
--ks-text-label |
12px | — (UPPER, mono) |
Rules — One Display element per screen. Never stretch, condense, or outline the Display face. Body line length ≤ 66 characters. Uppercase is Display & Mono only — never uppercase body copy. Minimum body size on mobile is 16px.
03 · Spacing & Layout
A 4px base keeps rhythm predictable. Everything — padding, gaps, insets — snaps to --ks-space-1…10 = 4 · 8 · 12 · 16 · 20 · 24 · 32 · 40 · 48 · 64.
Breakpoints (mobile-first): --ks-bp-sm 480 · --ks-bp-md 768 · --ks-bp-lg 1024 · --ks-bp-xl 1280.
Containers & grid
| Use | Value |
|---|---|
Reading width (--ks-container-reading) |
720px max |
App / page max (--ks-container-app) |
1120px |
| Grid — mobile | 4 col · 16px gutter |
| Grid — desktop | 12 col · 24px gutter |
| Screen edge margin (mobile) | 16–20px |
The primary surface is a phone — design at 375–430px first, respect safe-area insets (notch, home indicator), and keep the primary CTA within thumb reach at the bottom.
04 · Shape & Elevation
The hard, blur-free offset shadow paired with a 2px Ink outline is the single most recognisable thing about the system.
Radii: --ks-radius-sm 14 (inputs) · md 20 (chips) · lg 28 (cards) · xl 36 (hero) · pill 999.
Elevation (always Ink, never grey, never blurred):
| Token | Value | Use |
|---|---|---|
--ks-shadow-sm |
4px 4px 0 | small chips |
--ks-shadow-button |
5px 5px 0 | resting buttons, pills, tags |
--ks-shadow-card |
8px 8px 0 | cards, modals |
--ks-shadow-hover |
7px 7px 0 | hover lift |
--ks-shadow-soft |
0 22px 60px rgba(16,16,20,.14) | marketing / hero docs only — never interactive UI |
The press rule is mandatory: every shadowed element carries the 2px Ink border. On press the element "sits down" — translate(5px, 5px) and drop the shadow to 0. That physical click is the brand's core interaction feel.
Organic shapes — the system explicitly calls for fluid shape language: blobs, swashes, sticker cut-outs (see DoodleAccent). Radii are the floor, not the whole story — reach for organic forms in marketing and hero moments.
06 · Component States
Define the full set for every interactive component — not just rest and disabled.
| State | Signal | Notes |
|---|---|---|
rest |
Fill + 2px Ink + shadow-button | Default |
hover |
Lift −2/−2, shadow-hover | Pointer only |
focus-visible |
Blue ring offset by Haze (--ks-focus-ring) |
Keyboard — never remove |
active / pressed |
Sink +5/+5, shadow 0 | The signature press feel |
loading |
Label → spinner, size held | Block re-tap; announce via aria-live |
selected |
Ink fill or check badge | Toggles, nav, choices |
error / invalid |
Flame border + message + icon | Never colour alone |
disabled |
#DEDBD3 fill, 45% Ink text, 18% border, no shadow |
not-allowed cursor |
Keep the product calm — the loud, high-contrast treatment belongs to marketing and hero moments. Inside the learning UI, dial states down: readable and quiet so practice stays low-pressure. Same tokens, gentler application.
07 · Accessibility
Measured WCAG 2.1 contrast for the real palette. Default to Ink text; only Blue is safe for white body text. (AA body needs 4.5; large/bold ≥24px needs 3.0.)
Ink text on… (recommended default)
| Surface | Ratio | |
|---|---|---|
| Paper | 19.0 | AAA |
| Haze | 15.9 | AAA |
| Sunflower | 11.8 | AAA |
| Pulse | 9.4 | AAA |
| Spark | 7.8 | AAA |
| Moss | 5.9 | AA |
| Flame | 4.9 | AA |
| Blue | 3.5 | Large text only |
White text on…
| Surface | Ratio | |
|---|---|---|
| Blue | 5.4 | AA |
| Flame | 3.9 | Large only |
| Moss | 3.2 | Large only |
| Spark | 2.4 | ✗ Fail |
| Pulse | 2.0 | ✗ Fail |
| Sunflower | 1.6 | ✗ Fail |
| Haze | 1.2 | ✗ Fail |
Do — Ink on any accent. White only on Blue (body), or Flame/Moss at large sizes. Give every state a non-colour signal (icon, label, shape). Focus ring always visible. Touch targets ≥ 44×44px. Support 200% zoom & dynamic type.
Decision A1 (2026-07): Flame and Moss surfaces carry Ink text system-wide (4.88 / 5.94 AA). White icons on them are fine (non-text, ≥3:1), and display-size white (≥24px, e.g. PosterCard headlines) passes AA-large for marketing surfaces.
Decision B2 (2026-07): the v7 board's "Soft" layer — Lavender
#CAA7FF, Mint#B8DDCE, Cream#FFF7ED— is formally retired. In-product calm is expressed with Haze + Paper only; the board should no longer be read as spec for those three colours.
Don't — White text on Sunflower/Pulse/Spark/Haze. Colour as the only meaning (a streak or progress state needs a label). Removing focus outlines. Sub-44px tap areas. Body text below 16px on mobile.
Audio-specific (this app is voice-first) — transcripts/captions for every listening task; announce recording start/stop and results with aria-live; a visible non-audio state for "tap to speak"; never make audio the only way to complete a task; respect prefers-reduced-motion on the mic pulse.
08 · Motion
Physical (matching the tactile shadow) and quick. Purposeful, never decorative. Every looping effect has a reduced-motion fallback.
| Token | Value | Use |
|---|---|---|
--ks-dur-press |
80ms | Press / snap — no ease |
--ks-dur-fast |
150ms | Hover |
--ks-dur-base |
200ms | Most transitions |
--ks-dur-slow |
300ms | Entrances, toasts |
--ks-dur-reward |
500ms | Celebrations |
Easing --ks-ease = cubic-bezier(0.2, 0.8, 0.2, 1). Signatures: press snaps down/up (no ease); toast slides up from bottom and settles; reward pops (overshoot) then settles; card hover lifts −2/−2 into shadow-hover. Recording pulse = ksPulse on the Flame mic while listening.
Reduced motion — under prefers-reduced-motion: reduce, replace movement and loops with a simple opacity change and hold the mic on a static "listening" state.
10 · Iconography
Lucide React only — not custom icons. Lucide's rounded caps and even weight already match the brand.
| Rule | Value |
|---|---|
| Library | lucide-react |
| Default stroke | 2 (reserve 2.4–3 for rare emphasis) |
| Sizes | 16 · 20 · 24 (match text scale) |
| Colour | Ink single-tone; accent only for reward moments |
| Alignment | Optical-centre with adjacent text baseline |
Live set includes mic, house, chart-column, user, check, sparkles, arrow-right, circle-alert. Don't swap in another icon set, mix stroke weights in one view, recolour with low-contrast accents, or draw custom one-offs when a Lucide glyph exists.
The only sanctioned non-Lucide expressive graphic is the branded Expression set — see the emoji-rule exception in the README.
11 · Voice & Tone
The brand speaks in a distinct voice — "No scores — only progress," the Say it / Say more / Say better ladder.
- Loud, but warm — confident and energetic in Display; kind and encouraging in body. Hype the learner, never the grade.
- Progress, not scores — frame everything as practice and growth. "Getting clearer," not "62%." Celebrate attempts.
- Spoken & short — write how Malaysians speak: direct, plain, brief. Read it aloud; if it's a mouthful, cut it.
Core terminology
| Use | Not | Why |
|---|---|---|
| Practice / session | Test / exam | Lowers pressure |
| Streak | Score / rank | Momentum, not judgement |
| Say it · Say more · Say better | Level 1/2/3 | The brand's speaking ladder |
| Getting clearer / Building | Fail / Wrong | Growth framing (see BandBadge) |
| SPM · Form 5 · Paper 3 | (localise, keep proper nouns) | Malaysian exam context |
Do — "We couldn't hear you — try again." · "Your streak is safe for today." · "Practice today to start your streak." Encourage after every attempt.
Don't — "Error: invalid input." · "You scored 3/10." · "Incorrect." Cold, clinical, or score-first language.
Bilingual — natural Malay-English code-switching is welcome in warmth ("Sekejap," playful lines), but keep instructions in clear English.
Sections numbered to match the source foundations pack (02–04, 06–08, 10–11). Colour (01), the component set (05), and the app kit (09) live in the README and src/.