§ brand
Visual
identity.
Brand, colors, and typography for Zebrania.edu.pl — design system "A". Paste this address to an AI agent to keep pages, decks, and graphics consistent with the product.
§ 01 principles
Design principles
Five rules that settle every trade-off. Check them before you add a color, a shadow, or a new component.
Light by default, warm neutrals
The canvas is a warm white, never pure #fff or #000. Dark mode is derived from the same tokens via light-dark() — not a separate design. Sections stay legible and information-dense, with no dark "brand moments".
Moss for accent, olive for the button
Accent is split by role: moss (#82b842) colors links, chips, and active states; the CTA fill is a separate, deep olive (#4f7a12) with white text. Mid-tone greens sit in a contrast dead zone — neither black nor white text reads well on them.
Utilitarian minimalism
1px hairline borders plus a subtle elevation shadow — not either/or. 6px radius. One bordered surface per group of content; cards never nest inside cards.
Space Grotesk on display, Geist at work
Headings carry Space Grotesk. Body text and UI — Geist. Codes, addresses, and identifiers — Geist Mono. No other typeface enters the system.
Status kept separate from accent
Success, error, and warning have their own semantic tokens — the status green is a different green from the brand moss. Accent never doubles as a status message.
§ 02 neutrals
Canvas, surfaces, and text
Neutrals are warm — never pure #fff/#000. Every token declares a light-dark() pair; dark mode is the same tokens, not a separate palette.
§ 03 accent
Moss and olive — accent split by role
Moss colors tints and text; the CTA fill is a separate, deep olive with white text. This section exists because on mid-tone green, neither black nor white text reaches 4.5:1.
§ 04 typography
Space Grotesk on display, Geist at work
Three typefaces, three roles. Headings — Space Grotesk. Body text and UI — Geist. Class names, addresses, and identifiers — Geist Mono.
display · Space Grotesk
Aa Bb 123
body · Geist
Aa Bb 123
mono · Geist Mono
Aa Bb 123
§ 05 components
Component patterns
Patterns used across the whole site. Copy them literally — do not invent new affordances for known tasks.
brand mark — the zebra
16px
24px
32px
64px
on fill
lockup
One mark on every surface: favicon, site nav, app shell, help site, printed join poster and og-image (GH-2419). It is a single /favicon.svg, kept byte-identical in apps/www, apps/web and apps/help because the three build independently — change one, change all three. The hero's slot grid is a product motif, not the mark, and never stands in for it. Derived from the Noto Emoji zebra (Apache-2.0) with the viewBox cropped to the artwork so the whole animal fills the frame at 16px.
buttons — Button component
Variants named by code: primary (olive CTA), secondary (outline), ghost (no fill), danger (destructive outline), danger-fill (full destructive CTA), success. Scale sm/md/lg plus loading/disabled/fullWidth states. Rendered with the real Button component — a twin of the app's (ADR-104), same tokens and classes.
icons — Icon component + registry
Names are intents (what the icon means), not shapes — swapping a glyph is one line in registry.ts, never at the call sites (GH-2006). Icons inherit color via currentColor and size via font-size. A twin of the app's (ADR-104).
chips and badges
Role chip: moss tint /10 with a /30 border. Statuses: semantic-token tints /15. Codes (classes, tokens): Geist Mono on canvas with a border. Relationship context is always a label — never a role switcher (ADR-094 § 3).
top navigation bar
The brand mark is the zebra — one mark across the marketing site, the app, the help site and the printed join poster (GH-2419). The active section reads as a filled slot, not an underline. Bar height and section scroll-margin read the same --site-nav-h token, and the slot corner radius is --radius-slot (2px, deliberately not --radius-xs). Log in carries secondary weight: the page's single filled accent button belongs to the hero's register CTA (GH-2246).
paginated table
| Student | Class | Status | Actions |
|---|---|---|---|
| Emma Clark | 4a | Active | Edit |
| James Wilson | 4a | Pending | Edit |
| Olivia Bennett | 4b | Draft | Edit |
Headers: mono, uppercase, --color-muted on canvas. Even rows: canvas tint /60. Pager inside the table card: count, page-size choice (user preference), and page buttons — the active page uses the CTA fill. Long content truncates with a tooltip — never wraps.
form — fields, switch, segments
Fields: bordered surface, moss focus ring. Error: outline plus --color-danger message. Switch: 999px track, checked uses the CTA fill. Segments: active one on a surface with a subtle shadow.
overlays and states — modal, toast, menu, empty state
New event
No events yet
Create your first meeting to see it here.
Modal: header/body/footer split by hairlines; wizard steps in the header; shadows only on overlays. Toast: inverted (--color-ink background, --color-canvas text), moss checkmark. Destructive menu items — --color-danger, always routed through the shared confirmation dialog. Empty state: dashed border.
navigation — rail and breadcrumbs
One left rail, no top bar (ADR-094 § 2). Active item: moss tint plus a --color-accent-strong icon. Groups labeled in mono uppercase. Children shown as initials avatars in a moss tint. Breadcrumbs for multi-level areas (Classes › 4a › Student): earlier levels clickable in --color-muted, the current level bold; one shared drill-down path for every entry point.
slot grid card — the product motif
Open House
Class 3B · March 15
- Free — bordered surface, moss tint on hover
- ✓ My booking — CTA fill · ● Booked by others — canvas
- — Disabled · ⏰ Conflict between children (blocked) · 🔒 Own duty (stripes)
Non-bookable states explain themselves on click — an icon is never the sole carrier of meaning (ADR-094 § 4).
shell states — WakeIndicator, ShellNotice
The app is starting up
It had gone to sleep and is waking back up. This usually takes a few seconds.
WakeIndicator — role="status", aria-live="polite"
The app is starting up
It had gone to sleep and is waking back up. This takes a few seconds — please try again.
ShellNotice — role="alert"
The menu could not be loaded
The page itself is fine — only the navigation failed to load. Try again in a moment.
ShellNotice — role="alert"
Pending and failure states for the streamed app shell (ADR-122, GH-2416). WakeIndicator renders in the pending branch of the shell's {#await} while the backend wakes — a 600ms CSS reveal delay keeps a warm load from flashing it at all. ShellNotice covers the two terminal outcomes a streamed load cannot hand to +error.svelte: waking, and failed — Retry re-runs only the shell load, not the page beneath it.
§ 06 rules
Do and don't
A quick cheat sheet. When in doubt, pick the pattern already used on the homepage.
✓ do
- Keep the light canvas (--color-canvas) as the default; dark mode only through light-dark() tokens.
- Paint the primary CTA with the olive fill (--color-accent-fill) and white text (--color-accent-fg).
- Use moss (--color-accent) only as a tint: /10–/15 backgrounds, chips, focus rings, active navigation.
- Border card edges with a thin hairline (--color-border) and a 6px radius.
- Set headings in Space Grotesk, body text in Geist, codes and addresses in Geist Mono.
- Signal states with semantic tokens: --color-success, --color-danger, --color-warning.
✗ don't
- Do not put dark text on a green fill — that is the rejected emerald variant with weak contrast.
- Do not use pure #fff or #000 — neutrals are always warmed.
- Do not fill large surfaces with moss; the accent is a spotlight, not paint.
- Do not introduce new accent hues — moss, olive, and the status tokens are the full set.
- Do not nest rounded cards inside cards; one bordered surface per group of content.
- Do not build dark sections "for effect" — hierarchy is carried by typography and spacing, not a background inversion.
§ 07 for ai agents
Machine summary
Paste this block into an agent prompt to anchor the design decisions. Kept brief — the agent will infer the rest from this page.
Design system: Zebrania.edu.pl ("system A", ADR-094)
Reference: https://zebrania.edu.pl/brand
Stack Astro / SvelteKit + Tailwind CSS v4. Tokens as CSS variables
in @theme, themed via light-dark() + [data-theme].
Fonts Space Grotesk (display) · Geist (body/UI) · Geist Mono (codes).
Mood Light-first, warm, dev-tool minimalist. Product for Polish
schools — calm and trustworthy, not flashy.
Palette (light · dark)
--color-canvas #faf9f8 · #14110d page canvas (warm, never #fff)
--color-surface #ffffff · #1c1917 cards, panels
--color-border #e7e2da · #34302a 1px hairlines
--color-ink #1c1a17 · #f5f2ec primary text
--color-muted #57534e · #a8a29a secondary text
--color-accent #82b842 · #9bcd5a moss tint — links, chips, focus
--color-accent-strong #4f7a12 · #b7e07a accent-as-text
--color-accent-fill #4f7a12 (both) primary CTA fill (deep olive)
--color-accent-fg #ffffff (both) text on CTA fill
--color-success #1a7f37 · #3fb950
--color-danger #cf222e · #f85149
--color-warning #b45309 · #d29922
Brand mark (canonical, GH-2419)
the zebra · /favicon.svg · one mark on every surface, no variants
copies: apps/www/public · apps/web/src/lib/assets · apps/help
byte-identical, no shared package — change one, change all three
uses: favicon · site nav · app shell · help site · join poster · og-image
never a second mark, a wordmark-only lockup, or the 🦓 emoji as a stand-in
source Noto Emoji U+1F993 (Apache-2.0), viewBox cropped to artwork bbox
Primary CTA
bg: --color-accent-fill · text: --color-accent-fg (white) · bold
radius: 6px · hover: --color-accent-fill-strong (#3f6310)
Chip / badge
bg: accent at 10–15% opacity · text: --color-accent-strong · pill radius
Headline
font: Space Grotesk · weight 700–800 · color: --color-ink
Body / UI
font: Geist · 16px base · secondary text: --color-muted
Components (patterns from mockup "A", ADR-094 § 5)
table mono-uppercase headers on canvas · zebra rows (canvas/60)
truncate + tooltip, never wrap
pager inside the card: count · page-size select · page buttons
chip pill · role: accent/10 + accent/30 border · status: token/15
switch 999px track · checked = --color-accent-fill
modal header/body/footer split by hairlines · wizard steps in header
shadows on overlays ONLY (modal, toast, menus)
toast inverted: --color-ink bg, --color-canvas text, moss check
ctxmenu destructive items in --color-danger, routed via confirm dialog
empty dashed border on surface
navrail single left rail, no top bar · active = accent/10 tint
child avatars: initials in moss tint
crumbs drill-down areas only (Klasy › 4a › Uczeń) · prior levels
clickable in --color-muted · current level bold ink
booking slot states: free/mine/reserved/disabled/conflict/own-duty
non-bookable states explain on click, icon never sole carrier
shell WakeIndicator (role=status, aria-live=polite, 600ms reveal
delay) for the pending branch of the streamed shell load;
ShellNotice (role=alert, waking/failed variants, Retry) for
its two terminal outcomes (ADR-122, GH-2416)
Rules
1 Light-first. Dark mode only via light-dark() tokens — never a redesign.
2 Accent split by role: moss = tint/text, deep olive = CTA fill.
Never dark text on a green fill (mid-green contrast dead zone).
3 Warm neutrals only — no pure #fff/#000, no cool grays.
4 1px hairline borders + subtle elevation shadow · radius 6px.
5 Status colors are separate semantic tokens, never the brand accent.
6 No new accent hues. Moss + olive + status tokens are the full set.
7 One mark everywhere — the zebra. The hero's slot grid is a product
motif, not the mark; it never stands in for the brand mark.