The single reference for the prototype and for the Flutter build that follows.
Every value here is taken from the live app
(at_bell_colors.dart, at_bell_tokens.dart,
at_bell_typography.dart) — nothing was invented. Where the prototype needed
something the app doesn't have yet, it's marked NEW and belongs in the Flutter theme too.
Brand palette owner-confirmed 2026-06-13. The semantic colours below are derived from the brand — no new hex was introduced, which is why success is mint and warning is peach rather than the usual green/amber.
Neutrals
Tinted surfaces
Semantic — derived, not new
Roboto for Latin, Tajawal for Arabic — both bundled in the app, so text renders correctly offline. Weights in use: 300, 400, 500. Bold (700) is reserved and rarely used.
Scale
Hero · 32/40 · 300
Heading 1 · 24/32 · 300
Heading 2 · 20/28 · 400
Heading 3 · 18/24 · 500
Body large · 16/24 · 400
Body · 14/20 · 400 — the default for everything
Caption · 12/16 · 400 — secondary information
Micro · 11/15 · 400 — notes and hints only
Spacing scale
Screen padding is 18px. Minimum touch target 44px.
Radius
One primary action per screen. The orange pill carries a soft red shadow — that shadow is part of the brand, not decoration.
Primary
Dark
Ghost
Disabled
Loading
Small
Text
We’ll send a code on WhatsApp.
That doesn’t look like a Kuwait mobile number.
Search
Chips state a fact, never a failure. “Pickup only” is information, not an error — this is the single most important tone rule in the product.
Selector — the "who is this for" control
Tabs — the niche switcher
List row
Option (radio)
Product
Toast
Skeleton
Every list screen must specify all of these. In the prototype they're switchable from the side panel so they can be reviewed without faking data.
| State | When | What the user sees |
|---|---|---|
| loading | Fetching | Skeleton rows in the shape of the real content |
| empty | Nothing published | “Nothing on today’s list” — never a blank screen |
| no-results | Search found nothing | Suggest a wider area, offer “order to my address” |
| sold-out | Item unavailable | Row dimmed, chip “sold out today”, add button inert |
| cut-off | Past the order deadline | Warn banner + list stays usable, now for tomorrow |
| closed | Place not serving | “Closed today” + a route out (order to my address) |
| error | Request failed | “On our side, not yours” + Try again |
| offline | No connection | Explains it will load itself when back online |
| not-enrolled | No room at this place | Chip “pickup only” — shown up-front, never at checkout |
| payment-failed | Card declined | “Nothing was charged” stated explicitly |
Written down before any coding, because it decides both the app and the admin panel. The one hard line: a module is behaviour, not a label.
PLACE Sunrise International School ← super-admin creates └─ MODULE Food · School shop · Library · ← switched ON, never created Plans · Bills (each one is code) └─ CATEGORY Mains · Light · Drinks ← the supplier creates freely Stationery · Uniforms · Bags └─ ITEM Daily shirt — white ← the supplier adds └─ VARIANT Size 8-9y ← an option, not a category
| Level | Who | Notes |
|---|---|---|
| Place | Super-admin | Creates the school/office, switches modules on |
| Module → supplier | Super-admin | Food → the canteen · Shop → a supplier · Library → the school |
| Categories + items | That supplier only | The canteen never sees the uniform catalogue |
Admin panel
One nav item — Catalogue — with a module switcher at the top of the page, rather than one nav item per module. The nav then stops growing every time a module is added, and a canteen that only sells food never sees pages it can't use. For the super-admin the same catalogue also appears nested under the vendor (Vendors → Final Kitchen → Catalogue), since a global list would beg the question "whose?".
| # | Rule | How it shows up |
|---|---|---|
| 1 | No roles, anywhere | Sign-up asks name + phone only. No “I am a…” step exists. |
| 2 | Location decides, not identity | “Where will you eat?” is the first question after sign-up. |
| 3 | A place limits supply, never permission | A stranger sees the whole menu; only delivery differs. |
| 4 | No room → pickup only | A fact chip on the catalogue, before anything is added. |
| 5 | Kid eater → kid list, kid price | Menu re-filters the instant the eater changes. |
| 6 | Plans → only with a room here | Blocks a plan, never a meal. Day-by-day always works. |
| 7 | Self-declare — no verifier | “Add a person” saves immediately. Nobody approves anything. |
| 8 | The list is never generic | Always scoped to one person, switchable in one tap. |
| 9 | Bills are pushed, never added | Own section. Never beside a “+” button. |
| 10 | Facts, not errors | Chips state what is true. Red is for genuine failures only. |