@tiniestcloud/create 0.0.0-stage → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +26 -2
  2. package/index.mjs +117 -0
  3. package/package.json +33 -4
  4. package/sdk.d.ts +290 -0
  5. package/skills/tiny-design/SKILL.md +333 -0
  6. package/skills/tiny-design/app.md +72 -0
  7. package/skills/tiny-design/dashboard.md +54 -0
  8. package/skills/tiny-design/form.md +48 -0
  9. package/skills/tiny-design/presentation.md +127 -0
  10. package/skills/tiny-design/website.md +53 -0
  11. package/templates/app/AGENTS.md +41 -0
  12. package/templates/app/CLAUDE.md +1 -0
  13. package/templates/app/README.md +10 -0
  14. package/templates/app/components.json +25 -0
  15. package/templates/app/gitignore +24 -0
  16. package/templates/app/index.html +27 -0
  17. package/templates/app/package.json +32 -0
  18. package/templates/app/src/App.tsx +39 -0
  19. package/templates/app/src/components/theme-toggle.tsx +57 -0
  20. package/templates/app/src/components/ui/button.tsx +66 -0
  21. package/templates/app/src/components/ui/tooltip.tsx +54 -0
  22. package/templates/app/src/index.css +130 -0
  23. package/templates/app/src/lib/theme.ts +39 -0
  24. package/templates/app/src/lib/tiny.tsx +59 -0
  25. package/templates/app/src/lib/utils.ts +1 -0
  26. package/templates/app/src/main.tsx +20 -0
  27. package/templates/app/src/pages/home.tsx +10 -0
  28. package/templates/app/tsconfig.app.json +29 -0
  29. package/templates/app/tsconfig.json +12 -0
  30. package/templates/app/tsconfig.node.json +24 -0
  31. package/templates/app/vite.config.ts +14 -0
  32. package/templates/site/AGENTS.md +23 -0
  33. package/templates/site/CLAUDE.md +1 -0
  34. package/templates/site/README.md +10 -0
  35. package/templates/site/astro.config.mjs +11 -0
  36. package/templates/site/gitignore +24 -0
  37. package/templates/site/package.json +22 -0
  38. package/templates/site/src/layouts/Layout.astro +30 -0
  39. package/templates/site/src/pages/about.astro +10 -0
  40. package/templates/site/src/pages/index.astro +10 -0
  41. package/templates/site/src/styles/global.css +1 -0
  42. package/templates/site/tsconfig.json +5 -0
@@ -0,0 +1,333 @@
1
+ ---
2
+ name: tiny-design
3
+ description: How apps, websites, dashboards, presentations and forms built for Tiniest Cloud should look and feel — a direction chosen for the product and its industry, palette and type, layout and balance, motion, light and dark, and the checks before calling it done. Use when designing, building or restyling anything to deploy on Tiniest Cloud. Where the person has design standards of their own (a design system, components, a brand guide, an app to match), those come first and this fills only what they leave open; with none, follow it all.
4
+ ---
5
+
6
+ # Designing for Tiniest Cloud
7
+
8
+ ## Design — make it look made for its job
9
+
10
+ **Their standards come first.** When the person has design standards of their own — a design
11
+ system, a folder of components, a brand guide, a Figma file, an app or site to match — those
12
+ decide, and this section and the design playbooks only fill what theirs leave open. Don't
13
+ restyle their components to taste, swap their fonts or colours for ones suggested here, or
14
+ "fix" a choice of theirs that this section would call a giveaway (their brand may well use
15
+ Inter, or a gradient). Where something of theirs would hurt the people using the app — text too
16
+ faint to read, a control too small to tap — build it as specified and tell them, rather than
17
+ quietly changing it. When they give nothing of their own ("build me an app for…"), all of the
18
+ rest applies.
19
+
20
+ An app that works but looks like every other starter is not finished. Before the first screen,
21
+ decide what this one should feel like — and write it down: a short `DESIGN.md` at the project
22
+ root with who it is for, the feel in three words, the palette, the type and how much motion (or,
23
+ with their standards, where those live and what they settle).
24
+ Hold every screen to it, and read it first when you change the app later (for an app built on
25
+ Tiniest Cloud it is kept with the source `get_app_source` returns).
26
+
27
+ **Start from what it is and who uses it.** Read the request for its scope (a tool used every
28
+ day, a page read once, a dashboard glanced at), its audience and its field, and follow that
29
+ field's conventions:
30
+
31
+ - Money, health, legal, B2B: calm and trustworthy — quiet neutrals, one confident accent,
32
+ dense data that lines up, tabular numbers.
33
+ - Food, shops, hospitality, events: warm and inviting — richer colour, big imagery, softer shapes.
34
+ - Creative, personal, community: expressive — a bolder palette or a display typeface, more motion.
35
+ - Kids, games, fitness: energetic — saturated colour, large targets, playful feedback.
36
+ - Internal and admin tools: quiet and fast — compact, keyboard-friendly, colour only for status.
37
+
38
+ A brand, colours or an example the person names always win over these.
39
+
40
+ **Give it its own palette — theirs if they have one, never the default grey.** Set the theme once, in the CSS
41
+ variables (the app starter's `src/index.css`, `:root` and `.dark`): `--primary` is the one
42
+ accent (main actions, links, selection), `--muted` and `--accent` are quiet surfaces, plus
43
+ `--destructive`, `--ring`, `--chart-1`…`--chart-5` and `--radius`, in both themes. Components
44
+ read those tokens; never hard-code a hex colour or `bg-blue-500` in a component, or the dark
45
+ theme breaks and the palette stops being one decision. Use colour on purpose: most of a screen
46
+ is neutral, and colour marks what to do (the primary action), what is selected, status
47
+ (success, warning, error) and the one number that matters — roughly 60% background, 30%
48
+ surfaces and text, 10% accent. Keep contrast: 4.5:1 for body text, 3:1 for large text and icons,
49
+ in both themes.
50
+
51
+ **What gives generated UI away — don't** (unless their standards ask for it): the same default font for everything (Inter, system
52
+ UI) instead of one chosen for the job; purple-to-blue gradients, glassy cards and glowing blobs
53
+ as decoration; a card around everything, cards inside cards; an icon in a rounded square above
54
+ every heading, emoji standing in for icons; grey text on a coloured background, pure black and
55
+ untinted greys; every section centred, every screen a narrow column in the middle of a wide
56
+ display, and every list the same three-card grid; bouncy or slow animation and everything
57
+ fading in on load.
58
+
59
+ **Type and spacing do most of the work.** One typeface family for an app, chosen for its
60
+ character and self-hosted with its `@fontsource` package (a display face for headings suits a
61
+ site); a clear scale — page title, section heading, body, small and muted —
62
+ where size and weight carry the hierarchy, not colour. Numbers in columns use `tabular-nums`.
63
+ Space on one scale (Tailwind's 4px steps): related things close together, groups clearly apart,
64
+ generous padding at the page edge — at least 20px on a phone (`px-5`), 32px or more on a
65
+ laptop — and bars that run edge to edge pad their content by the same amount. Lines of text
66
+ around 60–75 characters. Align everything to the same few edges.
67
+
68
+ **Lay out each screen for what it holds.** A narrow centred column is right for one focused
69
+ task — answering a question, a checkout step, a long form, an article — and wrong for most other
70
+ screens, where it leaves a laptop mostly empty. Several areas to move between: a sidebar, with
71
+ the content beside it using the width. Things people browse by how they look (recipes,
72
+ products, photos, boards): a grid of cards that fills the row. Records compared by their fields
73
+ (contacts, orders, expenses): a table that becomes a list on a phone. A queue worked through
74
+ one at a time: the list with the selected item's detail beside it. Stages: columns side by
75
+ side. One app can mix them — a quiz is played in a centred column and managed in a wide
76
+ workspace. Cap the width where it stops being readable (about 1280–1440px for an app, 65–75
77
+ characters for prose), not at the width of a phone. The starters' frame is a placeholder.
78
+
79
+ **Balance every screen.** One primary action per screen, visually the strongest; secondary ones
80
+ quieter (outline, ghost); destructive ones red and confirmed. Put what matters most where the eye
81
+ lands first. Don't box everything — a card inside a card and a border on every section is noise;
82
+ group with space and a subtle background instead. Cards side by side are built alike: the same
83
+ parts in the same places — if one has an icon, a chart or a caption, they all do — so their
84
+ labels, numbers and bottoms line up and none looks emptier than its neighbour. Design the states
85
+ too: empty (what to do first), loading (skeletons shaped like the content), error (what happened
86
+ and what to do next), and long (search, paging).
87
+
88
+ **The header stays put.** The header — name or logo, navigation, theme switch, account — stays
89
+ at the top while the page scrolls, so people can always move around without scrolling back up:
90
+ `sticky top-0 z-40` on the `<header>`, a solid page background (`bg-background`, never
91
+ see-through over the content) and its bottom border. Keep it short — about 56–64px, one row
92
+ on a phone, where an app's screens move to the bottom bar — so it doesn't eat the screen. A
93
+ sidebar stays put the same way (`sticky top-0 h-svh`), and a detail pane beside a long list
94
+ scrolls on its own.
95
+ A website's header may shrink as you scroll, but stays reachable. A long list's search and
96
+ filters stick just below the header the same way, so people can search without scrolling back
97
+ up — on a phone, at least the search. Measure the header (a `ResizeObserver` writing
98
+ `--header-h` on the root) and stick below it with `top-(--header-h)`, rather than guessing
99
+ its height; a sticky element only sticks within its parent, so give it one as tall as the list.
100
+
101
+ **shadcn/ui is where you start, not how it must look.** The components in
102
+ `src/components/ui` are the app's own code: change radius, padding, sizes, shadows and variants
103
+ there so buttons, inputs and cards fit the app's character, and add a variant rather than
104
+ overriding classes at every use. Compose them into the app's own pieces (a stat tile, an item
105
+ row). Don't add a component because it exists — use the simplest thing that does the job.
106
+
107
+ **Never the browser's own controls.** What the browser draws itself ignores the theme and looks
108
+ the same in every app, so build it from the app's components, styled to its palette and radius:
109
+
110
+ - A date: the calendar in a popover (`calendar` + `popover`); a time: the app's own choices
111
+ (chips or a list). Never `type="date"`, `type="time"` or `datetime-local`.
112
+ - A dropdown: the `select` component or a combobox, never a bare `<select>`.
113
+ - A hint on hover or focus: the `tooltip` component, never a `title` attribute.
114
+ - A question or warning: `alert-dialog` or `dialog`; a message: a toast. Never `alert()`,
115
+ `confirm()` or `prompt()`.
116
+ - Checkboxes, radios, switches, sliders and file pickers: styled ones (a visually hidden native
117
+ input under the styled one keeps the keyboard and screen readers working).
118
+
119
+ `type="email"`, `tel`, `inputmode` and `autocomplete` stay — they only choose the keyboard
120
+ and autofill. Restyle the calendar, popover and tooltip like every other component: the accent
121
+ for the selected day, the app's radius and type, both themes.
122
+
123
+ **When the person points at their own work, that is the design.** "Use the components in
124
+ ../design-system", "follow the brand in ./brand", "make it look like our other app", a style
125
+ guide, a Figma export, a screenshot: read it before writing any screen, and build from it
126
+ rather than from the starter's defaults.
127
+
128
+ - **Their components.** Reuse them, with their names and props. React + Tailwind components go
129
+ into `src/components` as they are (bring their dependencies into package.json); anything
130
+ else is rebuilt faithfully on the starter's stack. Use theirs wherever one exists and
131
+ shadcn/ui only for what they lack, restyled to match — never two looks on one screen.
132
+ - **Their brand.** Take its colours into the theme variables (`--primary`, surfaces, radius) for
133
+ the themes it defines — only light if they define only light, unless they ask for dark. Fonts: files into
134
+ `public/fonts` with `@font-face`, or the matching `@fontsource` package. Logo and images
135
+ into `public/` and use them in the header, the sign-in state and the page title. Keep the
136
+ voice of their copy too.
137
+ - **Copy, never link.** Only what is inside the deployed folder ships: a path on the person's
138
+ computer or a sibling folder does not exist once the app is live. Copy the files in; keep
139
+ brand tokens in one place (the theme variables) so the next app can reuse them.
140
+ - **Can't reach the folder?** In a chat with no access to their files, ask them to attach the
141
+ files, a screenshot, or the colours, fonts and logo — don't guess a brand.
142
+
143
+ **Motion: smooth, quick and for a reason.** Animate to explain a change — an item arriving in a
144
+ list, a panel opening, a number updating — never to decorate. 150–250 ms, ease-out for things
145
+ entering, ease-in for things leaving. Everything clickable has hover and pressed states
146
+ (`transition-colors`, a slight scale on press); dialogs and sheets fade and slide in (the
147
+ starter's tw-animate-css: `animate-in fade-in slide-in-from-bottom-2`); a list that changes live
148
+ animates its items (add `motion` from npm for layout animation). Respect reduced motion
149
+ (`motion-safe:` / `motion-reduce:`). Animate only `transform` and `opacity`, name the
150
+ properties (never `transition-all`), and prefer one orchestrated entrance to every element
151
+ fading in on its own. No bouncing, no long delays, nothing that animates on every render.
152
+ One exception: a moment the person earned — a right answer, a finished goal, a win in a game —
153
+ may celebrate for up to about 500 ms, with a small overshoot or a burst, where the app's feel is
154
+ playful (games, kids, fitness, a team event). Keep it to that moment, never in the way of the
155
+ next action, and off under reduced motion like the rest.
156
+
157
+ **Make motion say where things are.** Movement should match the layout, so people feel where
158
+ they went:
159
+
160
+ - **Indicators glide.** The selected pill of tabs, segmented controls, filters and the bottom
161
+ navigation slides to the option picked rather than jumping (`motion`'s shared `layoutId` on
162
+ the pill), and a softer pill follows the pointer across the options on hover.
163
+ - **Screens move the way the navigation runs.** Going forward (to a tab further right, deeper
164
+ into a list) enters from the right; going back enters from the left. A short slide with a
165
+ fade (about 24px, 200 ms in, a little faster out).
166
+ - **Things come from where they live.** A detail opened from a list slides in from the side it
167
+ sits on, a menu or popover grows from its trigger, a bottom sheet rises from the bottom, and
168
+ a success replaces the form with a brief scale-in.
169
+ - **A theme change crossfades.** Light to dark fades smoothly across the whole page
170
+ (`document.startViewTransition` around the class change, about 250 ms ease-out on
171
+ `::view-transition-old(root)` and `-new(root)`), never a hard flash; instant under reduced
172
+ motion or where the browser has no view transitions.
173
+
174
+ Keep one easing and duration for the whole app, defined once (a small `motion.ts`), so every
175
+ movement feels like the same hand made it.
176
+
177
+ **An app doesn't bounce.** Turn off the page's rubber-band overscroll (`overscroll-behavior:
178
+ none` on `html` and `body`), so pulling past the top or bottom doesn't drag the header and
179
+ bars with it; a website reading like a document may keep it.
180
+
181
+ **Light and dark, both designed** — unless their standards settle it otherwise. Give people a
182
+ light / dark / match-device switch, somewhere visible and styled to fit: the app starter includes one (`src/components/theme-toggle.tsx`,
183
+ remembered on the device in a cookie); if the project has none, add one the same way — a cookie,
184
+ not localStorage, which a deploy flags. Check both
185
+ themes: shadows barely show in dark, so lift surfaces with a slightly lighter background
186
+ instead; pure black and pure white are harsh — use the palette's near-black and off-white.
187
+
188
+ **Phone first.** Most people open a link on a phone: touch targets at least 44px, one column
189
+ on a phone that becomes the screen's real layout on a wider one (the sidebar, the grid, the
190
+ detail beside its list), nothing reachable only by hover, inputs that bring up the right
191
+ keyboard (`type="email"`, `inputmode="numeric"`). Every control is laid out for the phone
192
+ too, not just squeezed: a row of three or more icon buttons in the header (a theme switch, view
193
+ toggles) becomes one button with a small menu that marks the current choice; segmented options
194
+ and filter tabs are each as wide as their words (never equal columns, which squeeze the longest
195
+ label), and when they no longer fit they scroll sideways or become a select; a toolbar's
196
+ secondary actions fold into a "more" menu. Check each control at 375px, not only the layout.
197
+
198
+ **On a phone, an app's screens live in a bottom bar.** Below the `sm` breakpoint, an app with
199
+ two to five main screens moves them out of the header into a navigation bar fixed to the
200
+ bottom, in reach of a thumb: an icon over a short label for each, the current one clearly
201
+ marked (the accent behind its icon), a solid background, a border on top and the phone's safe
202
+ area below. The header becomes one row — name or logo, theme switch, account. Publish the
203
+ bar's height (`--nav-h`, 0 from `sm` up) so the page pads its bottom by it and anything else
204
+ pinned to the bottom (a form's submit bar, a detail's actions, toasts) sits above it, never
205
+ under it. More than five screens: four in the bar and "More". A website keeps a menu in its
206
+ header instead. A detail screen on a phone (one item opened from a list) keeps its way back
207
+ pinned just under the header while it scrolls — a slim bar reading "← ‹the list's name›" — and
208
+ its actions in a bar of their own pinned at the bottom, above the navigation, never scrolled out
209
+ of reach.
210
+
211
+ **Words are part of the design.** Short, plain, active; a button says what it does ("Save
212
+ expense", not "Submit"); an error says what to do next; an empty state invites the first action.
213
+ When people choose between options, every option shows what the choice turns on — how many
214
+ are left, what it costs, how long it takes — not only the last few. Numbers, money and dates go
215
+ through `Intl.NumberFormat` and `Intl.DateTimeFormat`, in the reader's locale.
216
+
217
+ **Every field gets a placeholder, written for this app.** A friendly prompt or a sample answer,
218
+ in the app's own voice and about its own subject: a short question the field answers, or a
219
+ realistic example of what goes there. Where the format matters, a sample in that format (a phone
220
+ number in the reader's local format, "name@company.com"); in a search box, a real term from the
221
+ app's own data to try ("Try '‹a name or status that exists›'").
222
+ Never the label again ("Enter your name", "Email"), never generic filler ("Type here…"), and
223
+ never in place of the label, which stays visible above the field. Placeholder text uses the
224
+ muted text colour at full strength, so it is readable but clearly not an answer.
225
+
226
+ **Only content can be selected.** In an app, a drag or a double-click shouldn't paint the
227
+ interface: the header, navigation, buttons, tabs, chips, badges, labels, hints, stat tiles and
228
+ screen titles get `select-none` (one rule in the base CSS covers most of them). What people
229
+ actually copy stays selectable: names, phone numbers, emails, addresses, notes, messages,
230
+ codes and amounts — and anything typed into a field (give `input` and `textarea`
231
+ `select-text`, so an unselectable form never stops typing). Never block copying everywhere;
232
+ a website's reading text stays selectable throughout.
233
+
234
+ **Accessible is part of looking good.** Visible focus rings, a label on every input and icon
235
+ button, never colour alone to carry meaning, headings in order.
236
+
237
+ **Look before you call it done.** After deploying, open it with preview_app in light and dark
238
+ and at a phone's width, and fix what is off: edges that don't line up, cramped spacing, colour
239
+ that means nothing, an empty state that says nothing.
240
+
241
+ **Each kind of build has its own playbook** — app, website, dashboard, presentation, form — with
242
+ its layouts, patterns and checks. Read the one for what you are building before its first
243
+ screen: `tiny_guide` with its `topic` returns it together with the directions by industry and
244
+ the final checklist, and where the design skill is installed it is the file beside this one.
245
+
246
+ ## Directions by kind of product
247
+
248
+ Only for an app whose person gave no brand or standards of their own — theirs always win.
249
+ Starting points, not answers. Pick the direction that fits the brief, then make it this app's
250
+ own — shift the hue, tint the neutrals toward the accent, choose the weights — so two finance
251
+ apps never come out the same. Every font here is on npm as `@fontsource/<name>` (or
252
+ `@fontsource-variable/<name>`); self-host it rather than linking a font service.
253
+
254
+ | Kind | Direction | Palette | Type |
255
+ |---|---|---|---|
256
+ | Money, accounting, fintech | Ledger: precise, trusted | ink and cool greys, one emerald or teal accent for gains and actions | IBM Plex Sans + IBM Plex Mono for figures |
257
+ | | Modern bank: warm, calm | warm off-white, deep plum or petrol accent | Manrope |
258
+ | | Personal tracker: light, friendly | soft greys, one clear sky or coral accent | Figtree |
259
+ | Health, wellbeing, care | Calm clinic | white, blue-green accent, high contrast | Atkinson Hyperlegible |
260
+ | | Gentle | sage or eucalyptus, warm white, rounded shapes | Nunito |
261
+ | | Mindful | sand and muted lavender, lots of air | Newsreader headings + DM Sans |
262
+ | Food, cafés, restaurants | Market | deep green with a mustard or tomato accent | Bricolage Grotesque |
263
+ | | Bistro | black, white and one bold red | Playfair Display + Work Sans |
264
+ | | Photo-led | neutral frame, the food carries the colour | Fraunces + Figtree |
265
+ | Shops, products, e-commerce | Editorial | big imagery, quiet neutrals, accent only on price and buy | Cormorant Garamond + Instrument Sans |
266
+ | | Bold brand | one saturated brand colour used generously | Archivo |
267
+ | | Minimal | monochrome, accent on the buy button alone | Geist |
268
+ | Learning, kids | Playful | bright primaries, large targets, rounded | Fredoka or Baloo 2 |
269
+ | | Study | calm blue, highlighter yellow for what matters | Lexend |
270
+ | Creative, portfolio, personal | Typographic | oversized headings, one accent, the work as the image | Syne or Bricolage Grotesque |
271
+ | | Gallery | near-white, the work first, thin rules | Instrument Serif + Instrument Sans |
272
+ | | Studio at night | deep charcoal, one warm accent (not acid green) | Space Grotesk |
273
+ | Internal tools, B2B | Quiet productivity | neutral, one blue or indigo accent (no gradients) | Onest or Geist |
274
+ | | Dense operations | compact slate, colour only for status | IBM Plex Sans |
275
+ | Community, events, social | Energetic | coral or orange accent, lively type | Outfit |
276
+ | | Warm community | sunflower with deep blue | Figtree |
277
+ | | Night event | dark ground, one vivid duotone | Sora |
278
+ | Travel, stays, real estate | Photo-led | sand and ocean, generous imagery | Newsreader + DM Sans |
279
+ | | Quiet luxury | ivory and near-black, a restrained metallic accent | Cormorant Garamond + Manrope |
280
+ | Developer tools, docs | Technical | dark-first, mono for code and labels | Geist + JetBrains Mono |
281
+ | | Readable docs | white, a serif for long reading | Source Serif 4 + Public Sans |
282
+ | Public service, non-profit | Plain and accessible | high contrast, one trustworthy blue or green | Public Sans or Atkinson Hyperlegible |
283
+ | Fitness, sport | High energy | dark-friendly, electric orange or lime | Barlow Condensed + Archivo |
284
+
285
+ ## Before you call it done
286
+
287
+ - The person's own standards, where they gave any, are followed on every screen, and nothing
288
+ from these guides overrode them.
289
+ - The brief is written (`DESIGN.md`) and the screens match it.
290
+ - The palette is theirs or this app's own, in the theme variables, in light and dark; no hex colours in components.
291
+ - Colour marks actions, selection, status and the key number, and nothing else.
292
+ - Contrast: body text 4.5:1, large text and icons 3:1, both themes.
293
+ - One primary action per screen; destructive actions confirmed or undoable.
294
+ - A form's submit button is never a scroll away (a bar pinned to the bottom on a long form).
295
+ - Cards in a row have the same parts and line up; stat tiles carry an icon and a small visual
296
+ drawn from the data.
297
+ - Each screen is laid out for what it holds — a sidebar, a grid, a table, a list beside its
298
+ detail — with a narrow centred column only for one focused task; a laptop's width is used.
299
+ - Empty, loading, error and very long states all designed; long names truncate (`truncate`,
300
+ `line-clamp-*`, `min-w-0` on flex children) instead of breaking the layout.
301
+ - Every option people choose between shows what helps them choose (what's left, the price,
302
+ how long).
303
+ - Every input has a visible label, a friendly placeholder written for this app, the right
304
+ `type`, `autocomplete` and `inputmode`; errors sit next to their field.
305
+ - Every clickable thing has hover, pressed and visible focus states; icon-only buttons have an
306
+ `aria-label`; a link navigates, a button acts.
307
+ - Select-all highlights only content (names, contacts, notes, typed text), never the
308
+ header, controls, labels or titles.
309
+ - Motion is quick (150–250 ms; up to about 500 ms only for an earned moment in a playful app),
310
+ on transform and opacity only, and off under reduced motion.
311
+ - Selected pills glide between options, screens slide in the direction of the navigation, a
312
+ theme change crossfades, and an app has no rubber-band overscroll.
313
+ - Images have width and height, alt text, and load lazily below the fold.
314
+ - Filters, tabs and pages live in the URL, so a link opens the same view.
315
+ - It works at 375px wide with 44px touch targets and no sideways scroll; at least 20px of
316
+ padding at the phone's edges; an app's screens in a bottom navigation bar on a phone, with
317
+ every other bottom bar above it; a detail screen keeps its way back and its actions pinned.
318
+ - The header stays at the top while the page scrolls, on a laptop and a phone, with a solid
319
+ background; a long list's search (and filters, where there's room) stays pinned under it.
320
+ - No control the browser draws: dates use the calendar, dropdowns the select, hints the tooltip,
321
+ questions a dialog — all styled to the theme.
322
+ - None of the giveaways (the design guide's "What gives generated UI away").
323
+ - You looked at it: preview_app, light and dark, at a laptop's width and a phone's.
324
+
325
+ ## Playbooks
326
+
327
+ Read the one for what you are building before its first screen:
328
+
329
+ - [app](app.md) — something people sign in to and use
330
+ - [website](website.md) — pages people read
331
+ - [dashboard](dashboard.md) — numbers, charts and reports
332
+ - [presentation](presentation.md) — slides, as a file or a shareable web deck
333
+ - [form](form.md) — sign-ups, surveys, bookings and requests
@@ -0,0 +1,72 @@
1
+ # Designing an app — something people sign in to and use
2
+
3
+ > If the person has design standards of their own — a design system, components, a brand guide, an app or site to match — follow those, and use this playbook only for what they leave open. If they gave none, follow it all.
4
+
5
+ A tracker, a CRM, an inventory, a booking tool: people come back to it, often daily, to do the
6
+ same few things. It should feel fast, calm and obvious; the design serves the task.
7
+
8
+ ## Layout — screen by screen, for what it holds
9
+
10
+ The starter's frame (a header over one content area) is a placeholder. Choose each screen's
11
+ layout from what is on it:
12
+
13
+ - **Several areas to move between** (a CRM, an admin, the "manage" side of anything): a
14
+ sidebar with the areas and the content beside it using the width. shadcn's sidebar
15
+ (`npx shadcn@latest add sidebar`) collapses to a sheet on a phone.
16
+ - **Things people browse by how they look** (recipes, products, photos, boards, courses): a
17
+ grid of cards that fills the row — `grid gap-4 grid-cols-[repeat(auto-fill,minmax(16rem,1fr))]`
18
+ — each card led by its image or its most telling detail, not a title over three grey lines.
19
+ - **Records compared by their fields** (contacts, orders, expenses, bookings): a table with
20
+ sortable columns on a laptop, the same rows as a list on a phone.
21
+ - **A queue worked through** (requests, tickets, submissions, an inbox): the list with the
22
+ selected item's detail beside it on a wide screen (`lg:grid-cols-[22rem_1fr]`), the detail
23
+ its own screen on a phone.
24
+ - **Stages** (a pipeline, tasks by status): columns side by side that scroll sideways.
25
+ - **One focused task** (answering a question, a checkout step, a long form, reading): a
26
+ centred column (`mx-auto max-w-xl`) — the one place a narrow column is right.
27
+
28
+ One app mixes them: a quiz is played in a centred column and managed in a wide workspace with
29
+ a table of players. On a laptop use the width — content up to about 1280–1440px — and keep a
30
+ phone-width column for the screens that are one task; anywhere else it reads as unfinished.
31
+
32
+ ## Shape
33
+
34
+ - **Open on the work.** The first screen is the thing people do most — the list with a way to
35
+ add to it — not a welcome page. An empty state explains the first step and offers the button.
36
+ - **Navigation that fits the size.** Two or three areas: tabs in the header on a laptop. Four
37
+ or more: a sidebar. On a phone, either becomes a bottom navigation bar (five items at most),
38
+ and the header shrinks to one row. Keep the app's name or logo, the theme switch and the
39
+ account at the edges. The header stays pinned at the top while the page scrolls
40
+ (`sticky top-0`, solid background).
41
+ - **Lists are the heart.** A row per item: the main text strong, a secondary line muted,
42
+ status as a small consistent badge, meta (date, amount) right-aligned in tabular figures.
43
+ Search, filters and sort sit above the list and run on the server (`where`, `order`, `limit`);
44
+ more rows come with "Load more" or a cursor, never by loading everything. On a long list the
45
+ search and filters stay in view while it scrolls, pinned just under the header.
46
+ - **Adding and editing.** A short form (up to about five fields) in a dialog — a bottom sheet
47
+ on a phone; anything longer on its own page. Save shows progress and then the result in place.
48
+ - **Detail.** A header with the title, status and actions; the content; then activity — who
49
+ created it and when (`created_by`, `mine`, `created_at`), shown as "You · 2 hours ago".
50
+ On a phone the way back stays pinned under the header and the actions sit in their own bar
51
+ pinned above the bottom navigation.
52
+ - **Deleting.** Reversible actions get an Undo in a toast instead of a confirmation; only what
53
+ cannot be undone asks first, in words that name the thing.
54
+
55
+ ## Feel
56
+
57
+ - Optimistic and live: show the change at once, then confirm; `tiny.db.watch` keeps every open
58
+ screen current, and items entering or leaving a list animate their height and opacity.
59
+ - Feedback for everything: a pressed state, a spinner inside the button while saving, a toast
60
+ for the outcome (shadcn's Sonner), an inline message when something fails with how to retry.
61
+ - Density that matches use: an internal tool can be compact (32–36px rows); a personal app
62
+ breathes more (44–56px). Either way, one spacing scale.
63
+ - Keyboard: Enter submits, Escape closes, focus lands in the first field of a dialog and
64
+ returns where it was when it closes; power tools can add shortcuts, shown in tooltips.
65
+
66
+ ## Checks
67
+
68
+ - A new user understands what to do within five seconds of signing in.
69
+ - The main action is always one tap away, including on a phone (a sticky button if needed).
70
+ - Signed out, signing in, loading, empty, error and "no access" each look intentional.
71
+ - Status colours mean the same thing everywhere; nothing relies on colour alone.
72
+ - Then the general checklist, "Before you call it done".
@@ -0,0 +1,54 @@
1
+ # Designing a dashboard or report — numbers people act on
2
+
3
+ > If the person has design standards of their own — a design system, components, a brand guide, an app or site to match — follow those, and use this playbook only for what they leave open. If they gave none, follow it all.
4
+
5
+ A sales summary, a spending report, an operations view: people glance at it to answer a few
6
+ questions. Start by writing those questions down; every tile and chart answers one of them.
7
+
8
+ ## Shape
9
+
10
+ - **Top row: the three to five numbers that matter**, each a tile with the value, a label, and
11
+ the change against the previous period — an arrow and a word, not colour alone. Decide what
12
+ "good" is per number: a cost going up is bad.
13
+ - **Then the trend** over time, then the breakdowns (by category, by person, by place), then
14
+ the detail table for anyone who wants the rows.
15
+ - **Filters** (date range, segment) at the top, reflected in the URL so a link shows the same
16
+ view; every tile shows its own loading skeleton while it updates.
17
+ - **Use the screen.** A dashboard is read on a laptop first: tiles in one row
18
+ (`grid-cols-2 lg:grid-cols-4`), charts two across, the table full width; one column on a
19
+ phone.
20
+
21
+ ## Numbers
22
+
23
+ - Compute on the server — `countBy`, `sumBy`, `groupBy` with `bucket` for months — never by
24
+ loading the records into the browser.
25
+ - Format with `Intl.NumberFormat` (currency, percent, compact "12.4K") in the reader's locale;
26
+ `tabular-nums`; numeric columns right-aligned with their units.
27
+ - Say where a number comes from and how fresh it is ("Updated 2 min ago").
28
+ - A stat tile shows its number at a glance: a small icon beside the label (in the accent, not
29
+ boxed above it), the number, its context ("of ‹total›", "‹n› ‹changed›"), then a small visual
30
+ that explains it — bars over time, a split bar, a progress bar, a sparkline — drawn from the
31
+ real data, never decoration.
32
+ - Every chart answers the pointer, small ones too: the part under it lifts while the rest dim,
33
+ and the app's tooltip gives its exact numbers ("‹label› · ‹value› ‹unit›"), placed so it
34
+ doesn't cover the headline number. Give each part a hit area bigger than its ink (a whole
35
+ column for a thin bar), and keep the summary in the chart's label for screen readers.
36
+
37
+ ## Charts
38
+
39
+ - Choose by the question: change over time → line or area; comparing things → bars
40
+ (horizontal when labels are long); parts of a whole → a stacked bar (a pie only for two or
41
+ three parts); spread → a histogram.
42
+ - Use the chart colours from the theme (`--chart-1` … `--chart-5`), at most five series; label
43
+ lines directly instead of relying on a legend; bars start at zero; gridlines faint; a tooltip
44
+ with the exact value. shadcn's chart component (`npx shadcn@latest add chart`) reads the
45
+ theme's chart colours.
46
+ - Highlight the point: the series that matters in the accent, the rest muted.
47
+
48
+ ## Checks
49
+
50
+ - Each question on the list is answered on screen without scrolling on a laptop.
51
+ - An empty period, one data point, and a huge value all still look right.
52
+ - Nothing is told by colour alone; charts have text a screen reader can use.
53
+ - A report someone will forward prints cleanly or exports (CSV from the app's data).
54
+ - Then the general checklist, "Before you call it done".
@@ -0,0 +1,48 @@
1
+ # Designing a form — sign-ups, surveys, bookings, requests
2
+
3
+ > If the person has design standards of their own — a design system, components, a brand guide, an app or site to match — follow those, and use this playbook only for what they leave open. If they gave none, follow it all.
4
+
5
+ People fill it in once, often on a phone, often in a hurry. Every field is a cost to them:
6
+ ask only for what you will use.
7
+
8
+ ## Shape
9
+
10
+ - One column, in the order people think about the answers; related fields grouped under a
11
+ short heading (`fieldset` and `legend`).
12
+ - Labels above the field, always visible — never a placeholder standing in for the label; a
13
+ hint under the label when the answer needs an example; mark the optional fields instead of
14
+ every required one.
15
+ - A placeholder in every field as well: a friendly, contextual prompt or example answer for
16
+ this form (a question the field answers, or a sample answer in the expected format), never
17
+ the label repeated or "Type here…".
18
+ - The right control: radio buttons for up to five choices, a select or searchable list for
19
+ more, a segmented control for two or three, checkboxes for several; the app's calendar in a
20
+ popover for dates (never the browser's date input), with the time zone said when it matters.
21
+ - The right keyboard and autofill: `type` (email, tel, url, number), `inputmode`, `autocomplete`
22
+ and a meaningful `name`; paste never blocked.
23
+ - Long forms become steps with a progress indicator and a review step before sending; a draft
24
+ kept in `tiny.db` means nobody loses what they typed.
25
+
26
+ ## Behaviour
27
+
28
+ - Validate when a field is left, not on every keystroke; the message sits under the field,
29
+ says how to fix it, and is linked to it (`aria-describedby`) and announced.
30
+ - The submit button stays enabled until sending starts, then shows progress; on a failed
31
+ submit, focus moves to the first problem.
32
+ - The submit button is never a scroll away. On a form taller than the screen it sits in a bar
33
+ pinned to the bottom (`sticky bottom-0`, or just above the bottom navigation in an app, with a
34
+ solid background, a border on top and the phone's safe area below) with a one-line summary of what is being sent ("‹what› · ‹when›", "‹count› items ·
35
+ ‹total›").
36
+ - Success is a real moment: say it worked and what happens next (a confirmation, the next
37
+ step, when they will hear back) — not a form that silently empties.
38
+ - A visitor without an account can submit only where the owner allows it (open submissions);
39
+ give the prefix a schema in the rules file and consider the built-in spam check, as the
40
+ guide describes. Whoever runs it sees the replies in the app, in the dashboard's Data screen,
41
+ or exported as a spreadsheet — and can get a notification for each (`tiny.notify` or a job).
42
+
43
+ ## Checks
44
+
45
+ - It can be completed on a phone with one thumb, with the keyboard never covering the field.
46
+ - Every error can be understood without seeing colour.
47
+ - Nothing is asked twice, and nothing is asked that is not used.
48
+ - Then the general checklist, "Before you call it done".
@@ -0,0 +1,127 @@
1
+ # Designing a presentation — slides
2
+
3
+ > If the person has design standards of their own — a design system, components, a brand guide, an app or site to match — follow those, and use this playbook only for what they leave open. If they gave none, follow it all.
4
+
5
+ First decide what the person wants:
6
+
7
+ - **A file** — a .pptx, a PDF, a Keynote to send or present from their own laptop: make it
8
+ with your own tools (your presentation or document skill), filling it from the app's data
9
+ with `summarize_app_data` and `read_app_data`. Deploy nothing.
10
+ - **A link** — a deck to share, open on any device, or keep current from an app's data: build
11
+ a web deck and deploy it. The skeleton below is a single HTML file that needs no build; put it
12
+ in its own app (or as a page of the app whose data it shows), and "Save as PDF" in the browser
13
+ still gives one slide per page.
14
+
15
+ ## Slides that work
16
+
17
+ - One idea per slide, and the headline states the point ("Churn fell 18% after the redesign"),
18
+ not the topic ("Churn").
19
+ - Few words: around 25 on a slide; the detail goes in what the speaker says (notes).
20
+ - Big type: headlines around 72–110px and body at least 36–40px on a 1920×1080 slide.
21
+ - One grid and one margin for the whole deck; one accent colour, used for the thing to look at.
22
+ - A chart shows one comparison with the point highlighted, labelled directly; a number that
23
+ matters can be the whole slide.
24
+ - Images full-bleed or on the grid, never floating.
25
+ - A story: title → the problem → what we found → what we propose → proof → the ask; about one
26
+ slide per minute; a closing slide with the ask and how to reach you.
27
+ - The deck's palette and fonts follow the same brief as everything else (see the design guide).
28
+
29
+ ## Web deck skeleton
30
+
31
+ Keyboard (arrows, space, Page Up/Down, Home/End), click either half, swipe on a phone; `#3`
32
+ in the address opens slide three; F for full screen; fits any window; one slide per page when
33
+ printed. Change the variables at the top for the palette and font, and add sections.
34
+
35
+ ```html
36
+ <!doctype html>
37
+ <html lang="en">
38
+ <head>
39
+ <meta charset="utf-8" />
40
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
41
+ <title>Deck title</title>
42
+ <style>
43
+ :root { --bg: #f6f5f1; --ink: #1d1c1a; --muted: #6a665e; --accent: #2e6a5b; --font: system-ui, sans-serif; }
44
+ * { box-sizing: border-box; margin: 0; }
45
+ html, body { height: 100%; background: #111; overflow: hidden; }
46
+ body { font-family: var(--font); }
47
+ .deck { position: absolute; left: 50%; top: 50%; width: 1920px; height: 1080px; background: var(--bg);
48
+ transform: translate(-50%, -50%) scale(var(--scale, 1)); }
49
+ .slide { position: absolute; inset: 0; padding: 120px 150px; display: flex; flex-direction: column;
50
+ justify-content: center; gap: 40px; background: var(--bg); color: var(--ink);
51
+ opacity: 0; visibility: hidden; transition: opacity 250ms ease-out, visibility 250ms; }
52
+ .slide.current { opacity: 1; visibility: visible; }
53
+ .slide h1 { font-size: 112px; line-height: 1.04; letter-spacing: -0.02em; text-wrap: balance; }
54
+ .slide h2 { font-size: 76px; line-height: 1.08; letter-spacing: -0.01em; text-wrap: balance; }
55
+ .slide p, .slide li { font-size: 40px; line-height: 1.4; color: var(--muted); max-width: 34ch; }
56
+ .slide ul { padding-left: 1.1em; display: grid; gap: 16px; }
57
+ .slide .accent { color: var(--accent); }
58
+ .count { position: fixed; right: 20px; bottom: 14px; color: #9a9a9a; font: 14px/1 system-ui, sans-serif; }
59
+ @media (prefers-reduced-motion: reduce) { .slide { transition: none; } }
60
+ @media print {
61
+ @page { size: 1920px 1080px; margin: 0; }
62
+ html, body { height: auto; overflow: visible; background: none; }
63
+ .deck { position: static; transform: none; height: auto; }
64
+ .slide { position: relative; height: 1080px; opacity: 1; visibility: visible; break-after: page; }
65
+ .count { display: none; }
66
+ }
67
+ </style>
68
+ </head>
69
+ <body>
70
+ <main class="deck">
71
+ <section class="slide"><h1>The headline says the point, not the topic</h1><p>Speaker · Date</p></section>
72
+ <section class="slide"><h2>One idea per slide</h2><ul><li>A few words each</li><li>The detail belongs in what you say</li></ul></section>
73
+ <section class="slide"><h2><span class="accent">3×</span> faster when the number is the slide</h2></section>
74
+ </main>
75
+ <div class="count" aria-hidden="true"></div>
76
+ <script>
77
+ const deck = document.querySelector(".deck");
78
+ const slides = [...document.querySelectorAll(".slide")];
79
+ const count = document.querySelector(".count");
80
+ const clamp = (n) => Math.min(Math.max(n, 0), slides.length - 1);
81
+ let current = 0;
82
+ function fit() { deck.style.setProperty("--scale", Math.min(innerWidth / 1920, innerHeight / 1080)); }
83
+ function show(n) {
84
+ current = clamp(n);
85
+ slides.forEach((s, k) => { s.classList.toggle("current", k === current); s.inert = k !== current; });
86
+ count.textContent = `${current + 1} / ${slides.length}`;
87
+ history.replaceState(null, "", `#${current + 1}`);
88
+ }
89
+ const fromHash = () => clamp((parseInt(location.hash.slice(1), 10) || 1) - 1);
90
+ addEventListener("keydown", (e) => {
91
+ if (["ArrowRight", "ArrowDown", "PageDown", " ", "Enter"].includes(e.key)) show(current + 1);
92
+ else if (["ArrowLeft", "ArrowUp", "PageUp", "Backspace"].includes(e.key)) show(current - 1);
93
+ else if (e.key === "Home") show(0);
94
+ else if (e.key === "End") show(slides.length - 1);
95
+ else if (e.key === "f") document.documentElement.requestFullscreen?.();
96
+ else return;
97
+ e.preventDefault();
98
+ });
99
+ addEventListener("click", (e) => {
100
+ if (e.target.closest("a, button, input, select, textarea, video")) return;
101
+ show(e.clientX > innerWidth / 2 ? current + 1 : current - 1);
102
+ });
103
+ let startX = null;
104
+ addEventListener("touchstart", (e) => { startX = e.touches[0].clientX; }, { passive: true });
105
+ addEventListener("touchend", (e) => {
106
+ const dx = startX === null ? 0 : e.changedTouches[0].clientX - startX;
107
+ if (Math.abs(dx) > 40) show(dx < 0 ? current + 1 : current - 1);
108
+ startX = null;
109
+ });
110
+ addEventListener("resize", fit);
111
+ addEventListener("hashchange", () => show(fromHash()));
112
+ fit();
113
+ show(fromHash());
114
+ </script>
115
+ </body>
116
+ </html>
117
+ ```
118
+
119
+ For numbers that stay current, make the deck a page of the app that holds the data and fill
120
+ the slides from `tiny.db` (`sumBy`, `countBy`) when it opens.
121
+
122
+ ## Checks
123
+
124
+ - Each slide can be understood in the three seconds it is on screen.
125
+ - The headlines alone tell the story.
126
+ - It reads from the back of a room: nothing under 36px on a 1920px slide.
127
+ - Printed or saved as PDF, every slide is one page and nothing is cut off.