@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.
- package/README.md +26 -2
- package/index.mjs +117 -0
- package/package.json +33 -4
- package/sdk.d.ts +290 -0
- package/skills/tiny-design/SKILL.md +333 -0
- package/skills/tiny-design/app.md +72 -0
- package/skills/tiny-design/dashboard.md +54 -0
- package/skills/tiny-design/form.md +48 -0
- package/skills/tiny-design/presentation.md +127 -0
- package/skills/tiny-design/website.md +53 -0
- package/templates/app/AGENTS.md +41 -0
- package/templates/app/CLAUDE.md +1 -0
- package/templates/app/README.md +10 -0
- package/templates/app/components.json +25 -0
- package/templates/app/gitignore +24 -0
- package/templates/app/index.html +27 -0
- package/templates/app/package.json +32 -0
- package/templates/app/src/App.tsx +39 -0
- package/templates/app/src/components/theme-toggle.tsx +57 -0
- package/templates/app/src/components/ui/button.tsx +66 -0
- package/templates/app/src/components/ui/tooltip.tsx +54 -0
- package/templates/app/src/index.css +130 -0
- package/templates/app/src/lib/theme.ts +39 -0
- package/templates/app/src/lib/tiny.tsx +59 -0
- package/templates/app/src/lib/utils.ts +1 -0
- package/templates/app/src/main.tsx +20 -0
- package/templates/app/src/pages/home.tsx +10 -0
- package/templates/app/tsconfig.app.json +29 -0
- package/templates/app/tsconfig.json +12 -0
- package/templates/app/tsconfig.node.json +24 -0
- package/templates/app/vite.config.ts +14 -0
- package/templates/site/AGENTS.md +23 -0
- package/templates/site/CLAUDE.md +1 -0
- package/templates/site/README.md +10 -0
- package/templates/site/astro.config.mjs +11 -0
- package/templates/site/gitignore +24 -0
- package/templates/site/package.json +22 -0
- package/templates/site/src/layouts/Layout.astro +30 -0
- package/templates/site/src/pages/about.astro +10 -0
- package/templates/site/src/pages/index.astro +10 -0
- package/templates/site/src/styles/global.css +1 -0
- 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.
|