@notambourine/brand-kit 1.0.0

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/SKILL.md +303 -0
  4. package/components.css +186 -0
  5. package/deck.css +124 -0
  6. package/elements.css +100 -0
  7. package/fonts/OFL.txt +95 -0
  8. package/fonts/hanken-grotesk-latin-var-italic.woff2 +0 -0
  9. package/fonts/hanken-grotesk-latin-var.woff2 +0 -0
  10. package/fonts/jetbrains-mono-latin-var-italic.woff2 +0 -0
  11. package/fonts/jetbrains-mono-latin-var.woff2 +0 -0
  12. package/fonts/nunito-latin-var-italic.woff2 +0 -0
  13. package/fonts/nunito-latin-var.woff2 +0 -0
  14. package/fonts/nunito-wordmark-800.woff2 +0 -0
  15. package/fonts.css +66 -0
  16. package/logo/export/apple-touch-icon.png +0 -0
  17. package/logo/export/favicon-16x16.png +0 -0
  18. package/logo/export/favicon-32x32.png +0 -0
  19. package/logo/export/favicon-48x48.png +0 -0
  20. package/logo/export/favicon.ico +0 -0
  21. package/logo/export/icon-192.png +0 -0
  22. package/logo/export/icon-512.png +0 -0
  23. package/logo/export/icon-maskable-512.png +0 -0
  24. package/logo/export/lockup-1024.png +0 -0
  25. package/logo/export/lockup-2048.png +0 -0
  26. package/logo/export/lockup-ink-1024.png +0 -0
  27. package/logo/export/lockup-white-1024.png +0 -0
  28. package/logo/export/mark-1024.png +0 -0
  29. package/logo/export/mark-256.png +0 -0
  30. package/logo/export/mark-512.png +0 -0
  31. package/logo/export/mark-white-512.png +0 -0
  32. package/logo/export/monogram-1024.png +0 -0
  33. package/logo/export/monogram-256.png +0 -0
  34. package/logo/export/monogram-512.png +0 -0
  35. package/logo/export/monogram-white-512.png +0 -0
  36. package/logo/favicon.svg +11 -0
  37. package/logo/icon-maskable.svg +11 -0
  38. package/logo/icon-square.svg +11 -0
  39. package/logo/icon.svg +11 -0
  40. package/logo/lockup-ink.svg +10 -0
  41. package/logo/lockup-text.svg +15 -0
  42. package/logo/lockup-white.svg +10 -0
  43. package/logo/lockup.svg +10 -0
  44. package/logo/mark-ink.svg +9 -0
  45. package/logo/mark-white.svg +9 -0
  46. package/logo/mark.svg +9 -0
  47. package/logo/monogram-ink.svg +10 -0
  48. package/logo/monogram-white.svg +10 -0
  49. package/logo/monogram.svg +10 -0
  50. package/logo/site.webmanifest +13 -0
  51. package/package.json +34 -0
  52. package/tokens.css +19 -0
  53. package/vars.css +228 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NoTambourine LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # brand-kit
2
+
3
+ The NoTambourine brand's golden set: `tokens.css`, `components.css`, `deck.css`, the
4
+ font binaries, the logo in `logo/`, and `SKILL.md`, which is the written system behind
5
+ them - color, type, spacing, component recipes, and voice.
6
+
7
+ Correct a brand value here. Every other copy is downstream.
8
+
9
+ | Link this | To get |
10
+ |---|---|
11
+ | `tokens.css` | The whole system: the faces, every value, and styled bare HTML. |
12
+ | `vars.css` | The values alone, for a surface with its own faces and base layer. |
13
+ | `components.css` | `.nt-btn`, `.nt-card`, `.nt-nav`, and the rest, all `var()`-based. |
14
+ | `deck.css` | The Marpit slide theme, 1280x720. Load `vars.css` on the page too. |
15
+ | `logo/` | The mark, the lockup, the icons, and the rasters cut from them. |
16
+
17
+ ## Logo
18
+
19
+ `scripts/build-logo.py` holds the mark's path data and the wordmark's type
20
+ parameters, and writes every file under `logo/`. It traces the wordmark out of
21
+ `fonts/nunito-latin-var.woff2`, so the outlines cannot drift from the face the
22
+ stylesheets load. Change the logo by editing that script and re-running it:
23
+
24
+ ```bash
25
+ ./scripts/build-logo.py # needs rsvg-convert on PATH
26
+ ```
27
+
28
+ The raster half shells out to `rsvg-convert`, which ships in librsvg: `brew
29
+ install librsvg` on macOS or Linux, the GTK runtime on Windows. The vector half
30
+ runs without it.
31
+
32
+ ### Vector
33
+
34
+ | File | Use |
35
+ |---|---|
36
+ | `logo/lockup.svg` | Default. Mark plus wordmark, outlined, so it needs no font. |
37
+ | `logo/lockup-white.svg` | On dark, on pink, on a photo. |
38
+ | `logo/lockup-ink.svg` | One-color print, fax-grade output, a light background that fights pink. |
39
+ | `logo/lockup-text.svg` | The wordmark as live `<text>` over an inlined ten-glyph Nunito subset. Open this one to edit the type; ship `lockup.svg`. |
40
+ | `logo/monogram.svg` | Mark with `no` nested in the crescent, transparent, square. The short form: avatar, app icon, stamp. |
41
+ | `logo/monogram-white.svg`, `logo/monogram-ink.svg` | The same two reversals. |
42
+ | `logo/mark.svg` | Mark alone, no letters. Watermarks, a bullet, anywhere `no` would be read as a word. |
43
+ | `logo/mark-white.svg`, `logo/mark-ink.svg` | The same two reversals. |
44
+ | `logo/favicon.svg` | Browser tab: monogram on the dark tile, cut tight so 16px survives. |
45
+ | `logo/icon.svg` | App tile, rounded corners, opaque. |
46
+ | `logo/icon-square.svg` | App tile, square edges, for iOS and anything else that masks the icon itself. |
47
+ | `logo/icon-maskable.svg` | Android maskable: full bleed, monogram inside the 80% safe circle. |
48
+
49
+ The monogram is the lockup truncated to its first two letters, not a second
50
+ drawing: same face, same size, same origin, so the `n` nests into the crescent's
51
+ mouth exactly as it does in the full lockup. At 16px the letters go soft, which
52
+ is the price of the monogram over a bare crescent; every browser that matters
53
+ reads `favicon.svg` and scales it instead.
54
+
55
+ `lockup-text.svg` overruns its viewBox in librsvg and resvg, which apply
56
+ `letter-spacing` differently than a browser does. That is why the outlined
57
+ `lockup.svg` is the default and the source of every raster.
58
+
59
+ ### Raster
60
+
61
+ Everything in `logo/export/` is cut from the SVGs above, so treat it as output:
62
+ regenerate it, never retouch it.
63
+
64
+ | File | Cut from |
65
+ |---|---|
66
+ | `favicon.ico` (16, 32, 48) | `favicon.svg` |
67
+ | `favicon-16x16.png`, `favicon-32x32.png`, `favicon-48x48.png` | `favicon.svg` |
68
+ | `apple-touch-icon.png` (180) | `icon-square.svg` |
69
+ | `icon-192.png`, `icon-512.png` | `icon.svg` |
70
+ | `icon-maskable-512.png` | `icon-maskable.svg` |
71
+ | `monogram-256.png`, `monogram-512.png`, `monogram-1024.png`, `monogram-white-512.png` | `monogram.svg`, `monogram-white.svg` |
72
+ | `mark-256.png`, `mark-512.png`, `mark-1024.png`, `mark-white-512.png` | `mark.svg`, `mark-white.svg` |
73
+ | `lockup-1024.png`, `lockup-2048.png`, `lockup-white-1024.png`, `lockup-ink-1024.png` | the three lockups |
74
+
75
+ ### Wire it up
76
+
77
+ `logo/site.webmanifest` names its icons relative to itself, so it works wherever
78
+ `logo/` is served as a unit.
79
+
80
+ ```html
81
+ <link rel="icon" href="/logo/favicon.svg" type="image/svg+xml">
82
+ <link rel="icon" href="/logo/export/favicon.ico" sizes="32x32">
83
+ <link rel="apple-touch-icon" href="/logo/export/apple-touch-icon.png">
84
+ <link rel="manifest" href="/logo/site.webmanifest">
85
+ ```
86
+
87
+ ## Consume it
88
+
89
+ Install it and read the bytes out of `node_modules`. Do not copy a stylesheet into a
90
+ consumer; a copy drifts and nothing fails when it does.
91
+
92
+ ```bash
93
+ npm install @notambourine/brand-kit
94
+ ```
95
+
96
+ There is no `exports` map, so import or copy any shipped path directly:
97
+
98
+ ```js
99
+ import "@notambourine/brand-kit/tokens.css";
100
+ ```
101
+
102
+ ```bash
103
+ cp -R node_modules/@notambourine/brand-kit/{fonts,logo} public/
104
+ ```
105
+
106
+ `tokens.css` declares the `@font-face` rules and every `var()` the other two read, so
107
+ load it first and serve `fonts/` beside it. The paths inside it are relative to the CSS
108
+ file, so inlining it into a `<style>` block breaks the faces.
109
+
110
+ The package ships the stylesheets, `fonts/`, `logo/`, and `SKILL.md`. The logo build
111
+ script and `hello-world.html` stay in the repo. Pin an exact version and bump it on
112
+ purpose; a caret range moves the brand under a consumer with no diff to review.
113
+
114
+ Consumers today:
115
+
116
+ | | |
117
+ |---|---|
118
+ | `notambourine/claude` | Ships it as the `/nt-brand:system` skill. |
119
+ | `notambourine/share` | Serves `tokens.css` and `deck.css` from its own origin; a self-only CSP forbids a CDN. |
120
+ | `notambourine/notambourine.com` | The site's stylesheet. |
121
+
122
+ ## Gate a consumer
123
+
124
+ A `var(--x)` that reads a token this repo stopped declaring falls through to its
125
+ fallback and the page still renders, so a bump can go wrong quietly. Check three things
126
+ in the consumer's CI: the font bytes hash against `fonts/`, every color is one this kit
127
+ defines, and every `var()` reads a property it still declares. `notambourine/share`'s
128
+ `npm run brand` is the reference implementation.
129
+
130
+ ## Release
131
+
132
+ Bump `version` in `package.json`, land it, then tag the merge commit:
133
+
134
+ ```bash
135
+ git tag v1.0.1 && git push origin v1.0.1
136
+ ```
137
+
138
+ `.github/workflows/npm-publish.yml` publishes the tag through npm trusted publishing.
139
+ No token lives in this repo, and npm attaches a provenance attestation tying the
140
+ published tarball to that workflow run. The workflow fails if the tag and the manifest
141
+ disagree.
142
+
143
+ ## License
144
+
145
+ MIT for the stylesheets and `SKILL.md`. The faces in `fonts/` are SIL Open Font License;
146
+ see `fonts/OFL.txt`.
package/SKILL.md ADDED
@@ -0,0 +1,303 @@
1
+ ---
2
+ name: system
3
+ description: NoTambourine brand system - colors, type, spacing, component recipes, voice, and the audit that checks work against them. Use when building or styling anything NoTambourine-branded (site, landing page, mock, prototype, deck, email, README banner, slide), when asked for "the brand colors"/"brand tokens"/"our pink", when a design needs to match notambourine.com, or when checking copy before it ships to a client - wordmark forms, ASCII punctuation, sentence case, and AI tells. Ships tokens.css, components.css, and a Marpit deck theme to drop straight in.
4
+ ---
5
+
6
+ # NoTambourine brand
7
+
8
+ A boutique skunkworks engineering agency, working as a hands-on operating partner inside
9
+ the client's org. The client's growth is capped by their tech: a legacy commerce stack, a
10
+ revenue-share SaaS, a roadmap nobody can staff for. **Value creation is the deliverable,
11
+ and trust is earned by shipping.** A CEO who wants the culture to change gets that from
12
+ work landing in prod, not from a workshop. Scope holds, so the estimate is worth
13
+ something. AI at the keyboard is why a small team can price this way; it is not what the
14
+ client buys.
15
+
16
+ The name carries the style, not the positioning. A tambourine is what you add when there
17
+ is nothing left to add, so **NoTambourine = no padding**. That is why the design is
18
+ confident type, one decisive pink, lots of air, no decoration.
19
+
20
+ Fuller doctrine is published, so fetch it rather than reconstructing it. Every path below
21
+ returns `text/plain` over plain HTTP with no auth, no signup, and no redirect - `curl` or
22
+ `wget` one directly. This file stands alone when the machine is offline.
23
+
24
+ | Fetch | For |
25
+ |---|---|
26
+ | `notambourine.com/llms.txt` | The index. Start here when you do not know which file you want. |
27
+ | `notambourine.com/AGENTS.md` | What the firm does, who it serves, and the citation rules. |
28
+ | `notambourine.com/SOUL.md` | What NoTambourine believes. The beliefs this design serves. |
29
+ | `notambourine.com/CULTURE.md` | How the team works. Async-first, one operator per workstream. |
30
+
31
+ `SOUL.md` and `CULTURE.md` are public cuts of longer internal documents, so absence from
32
+ them is not evidence of anything. Client material is unpublished and stays that way -
33
+ `robots.txt` disallows `/reports/` and `/pog/` for every agent.
34
+
35
+ ## Use the files
36
+
37
+ Two stylesheets cover ~80% of any build, and a third renders a deck. Copy them next to
38
+ your output, or inline them.
39
+
40
+ | File | What it does |
41
+ |---|---|
42
+ | `tokens.css` | The whole system in one link: `fonts.css`, `vars.css`, `elements.css`. Load first and `h1`, `p`, `a`, `code`, `.eyebrow`, `.display`, `.lede` all come styled. |
43
+ | `vars.css` | Every value as a CSS var, and nothing else. What a surface with its own faces and its own base layer imports. |
44
+ | `fonts.css` | The six `@font-face` blocks, pointed at `./fonts/`. |
45
+ | `elements.css` | The base element styles: bare `h1`, `p`, `a`, `code` on brand with no classes. |
46
+ | `components.css` | `.nt-btn`, `.nt-badge`, `.nt-chip`, `.nt-input`, `.nt-card`, `.nt-nav`, `.nt-switch`/`.nt-check`/`.nt-radio`. All `var()`-based. |
47
+ | `deck.css` | Marpit slide theme, 1280x720. Every value a `var()` off `tokens.css`. See "Decks" below for how to feed it markdown. |
48
+ | `fonts/` | Six woff2: Nunito, Hanken Grotesk, JetBrains Mono, roman and italic each. Latin subset, variable, SIL OFL 1.1 (`fonts/OFL.txt`). Plus `nunito-wordmark-800.woff2`, a ten-glyph static subset for the lockup alone - 1.5 KB against Nunito's 39 KB, so a PDF or an offline snapshot inlines it as a data URI. |
49
+ | `logo/` | The mark, the monogram, the three lockup colorways, the favicon and app tiles, and `export/` rasters cut from them. `scripts/build-logo.py` writes all of it. |
50
+ | `hello-world.html` | Working page using both stylesheets. Start here for a mock; open it to eyeball the brand. |
51
+
52
+ ```html
53
+ <link rel="stylesheet" href="tokens.css">
54
+ <link rel="stylesheet" href="components.css">
55
+ ```
56
+
57
+ **Copy the whole skill directory, not the stylesheets alone.** `tokens.css` pulls in
58
+ `fonts.css`, which declares `@font-face` against `./fonts/`, so type is wired up with
59
+ no network and no third party - a mock renders on brand from `file://` on a plane.
60
+ Those paths resolve against the CSS file, so inlining the CSS into a `<style>` block
61
+ instead means `fonts/` has to sit beside the HTML.
62
+
63
+ A surface that already ships its own faces and its own base layer imports `vars.css`
64
+ alone. It gets every value and none of the opinions, and there is still one copy of
65
+ each number.
66
+
67
+ Never swap that for a Google Fonts `@import`. It sends the visited URL to Google as a
68
+ `Referer` and blocks a self-only CSP, which is why the site deleted its own. A mock is
69
+ the artifact most likely to graduate into a real page, so it carries the same rule.
70
+
71
+ ### Fonts on a locked-down surface
72
+
73
+ Some surfaces cannot load `./fonts/` at all: a claude.ai artifact, an email client, a
74
+ README banner. There the faces fall back silently and the brand degrades without a
75
+ decision. Make it a decision:
76
+
77
+ - Inline the face as a data URI in the `@font-face` `src`: `base64 -i fonts/<file>.woff2`
78
+ emits the payload. The wordmark subset is 1.5 KB, so inline it always; a full face runs
79
+ 30-40 KB, so inline one only when the surface is worth the bytes.
80
+ - Where inlining is not worth it, the fallback stacks in `vars.css` are the brand's
81
+ degraded form: `system-ui` for display, `ui-monospace` for body. Ship them knowingly.
82
+ The Google Fonts rule above holds here too.
83
+
84
+ ## Non-negotiables
85
+
86
+ These are the rules that get broken. Check your output against them.
87
+
88
+ - **Dark by default.** Page is `var(--bg)` (#0B0B0C), cards `var(--bg-card)` (#141416).
89
+ Pure white is never a surface. `class="theme-light"` is opt-in and rare - print, a press
90
+ page, a one-off email.
91
+ - **One pink.** `#E75A7C` is the only accent. One pink CTA per screen. Mint (`#58C9B9`) is
92
+ structural - borders, status dots, supporting icons - **never** a CTA.
93
+ - **No gradients.** Not on sections, not on buttons, not on heroes. One radial halo behind
94
+ a hero lockup is the sole exception, and it must read as lighting.
95
+ - **No stock photography, no AI imagery, no hand-drawn textures.** Imagery is flat,
96
+ geometric, two-color. Ask for real assets before papering over.
97
+ - **Pill buttons** (`--r-pill`), 14px cards (`--r-md`). Never mix a sharp and a round
98
+ corner in one component.
99
+ - **No colored-left-border cards.** Full borders, not stripes.
100
+ - **Press scales to 0.97, hover does not move.** Hover darkens fill or lifts shadow. No
101
+ translate, no scale on hover, no parallax, no looping animation.
102
+ - **No emoji in UI chrome, no icon fonts.** Icons are Lucide SVG, 1.75px stroke, rounded
103
+ caps - 20px in nav/buttons, 24px in features, 16px in inputs.
104
+ - **Never redraw the logo; link a file out of `logo/`.** The lockup is `notambourine` set
105
+ in Nunito 800 beside the tambourine mark. Ship `logo/lockup.svg`, or its `-white` and
106
+ `-ink` reversals. Where the lockup will not fit - an avatar, an app icon, a stamp - use
107
+ `logo/monogram.svg`, the mark with `no` nested in the crescent; `logo/mark.svg` drops
108
+ the letters for a watermark or a bullet. `logo/lockup-text.svg` keeps the wordmark as
109
+ live `<text>` over an inlined ten-glyph Nunito subset - open that one to edit the type,
110
+ and re-run `scripts/build-logo.py` rather than hand-patching an outline.
111
+ `--font-wordmark` exists for the lockup and nothing else; a heading that reaches for it
112
+ is a bug.
113
+ - **One pink word per headline**, via `<em>` - pink *and* italic. Hanken Grotesk ships a
114
+ drawn italic, so emphasis carries both and never a browser-faked skew.
115
+ - **Every interactive element keeps a visible focus ring.** `tokens.css` ships it
116
+ (2px accent, 4px offset). Don't `outline: none` without replacing it.
117
+
118
+ ## Voice
119
+
120
+ Concise, warm, playful - in that order.
121
+
122
+ - **Register follows the artifact.** Site and marketing copy says **you** to the reader.
123
+ A client deliverable (deck, plan, README) says **we**, meaning the client's org with us
124
+ inside it. A proposal or SOW names the parties. Never "I".
125
+ - **A client deliverable does not sell.** No logo wall, no team slide, no methodology.
126
+ Explain the client's own system back to them and let the restraint be the credential.
127
+ - **Proof is a count, not an adjective.** Use the client's own numbers, before and after.
128
+ Reach for an adjective only where no number exists.
129
+ - **Name the mess without blaming anyone for it.** The client lived every decision that
130
+ built it. Copy that indicts them loses the room.
131
+ - **Four wordmark forms, and only four.** `NoTambourine` in every human-facing sentence.
132
+ `notambourine` is the technical slug, correct only in a path, URL, domain, GitHub org,
133
+ npm name, or CSS class. `NoTambourine LLC` is the legal entity and appears twice at most
134
+ in a contract - the signature block and one Definitions anchor - and nowhere else.
135
+ `Notambourine` is not a valid form. The same rules are published for outside agents at
136
+ `notambourine.com/AGENTS.md`, so a disputed flag has a public citation.
137
+ - **Sentence case everywhere** - headlines, buttons, nav, labels. "Get started", not
138
+ "Get Started". The lockup renders lowercase.
139
+ - **ALL CAPS has one job:** the pink eyebrow above a heading, tracked `+0.08em`. Never a
140
+ button, never body.
141
+ - Five to twelve word sentences. No throat-clearing, no superlatives, no exclamation
142
+ marks in body.
143
+ - **ASCII punctuation, with one unicode exception.** Hyphen, straight quote, straight
144
+ apostrophe. No em dash, no en dash, no curly quote, no single-character ellipsis. The
145
+ site source ships zero of each, and an em dash is the most reported AI tell in
146
+ marketing copy. The interpunct stays, because the brand uses it as a separator:
147
+ `Tom Fuertes · Principal · NoTambourine`.
148
+ - **Yes:** "Senior engineers. No tambourine." · "Two engineers, six weeks, one shipped
149
+ feature." · "Tell us what you're building. We'll write back the same day."
150
+ - **No:** "Unlock your team's full potential with our proven 7-phase framework!" ·
151
+ "results-driven, agile-first, AI-powered engineering studio"
152
+
153
+ ### Public copy carries no AI tells
154
+
155
+ Read anything a stranger sees for these tells: hero, section body, email, deck,
156
+ proposal, README. NoTambourine sells the absence of padding, so padded copy argues
157
+ against the pitch. Four tells land hardest in consultancy copy:
158
+
159
+ - Puffery adjectives. "Boutique" is specific and true; "proven", "world-class",
160
+ "results-driven", "battle-tested" are not. Cut the adjective and keep the number.
161
+ - Importance-flagging. "This matters." "Speed is not a footnote." Show the consequence
162
+ instead.
163
+ - The participle tail. "We ship in six weeks, ensuring your roadmap stays on track." Cut
164
+ at the comma.
165
+ - Grandiose scope. "changes how you build", "the future of engineering". Scope it to a
166
+ claim a client could hold you to.
167
+
168
+ Three brand fixtures read as tells and must survive the read:
169
+
170
+ - **"Senior engineers. No tambourine."** A tailing negation plus a two-beat fragment,
171
+ both tells on their own. The line carries the name and the positioning, so it stays.
172
+ - **A three-item line whose items are facts.** "Two engineers, six weeks, one shipped
173
+ feature" spends all three slots on numbers. A three-item line spending them on
174
+ adjectives is the tell.
175
+ - **The single pink `<em>` in a headline.** Typographic emphasis, not the boldface tic.
176
+
177
+ ## Type and layout
178
+
179
+ - **Headings** Hanken Grotesk 700, negative tracking - a serif holds a headline at 400
180
+ and a sans goes limp, so the weight is what reads as deliberate. 800 is the hero's, so
181
+ `.display` stays distinct from an ordinary `h1`. **Wordmark** Nunito 800, untracked,
182
+ the lockup only. **Accent** JetBrains Mono 600 for eyebrows, buttons, badges, nav.
183
+ **Body and mono** JetBrains Mono 400. Monospace body is the practitioner signal; it is
184
+ the point, not an accident. Every face is variable across its full axis, so no weight
185
+ here is ever synthesized.
186
+ - **The rounded lockup carries the play; the headings stay sober.** The reader is a PE
187
+ operating partner, so only the lockup gets to be warm.
188
+ - Body runs `1.7` leading and `+0.01em` tracking. Monospace at a sans's 1.55 reads cramped.
189
+ - Tracking tightens as size grows: `--ls-tight` (-0.02em) is the floor, and `--ls-display`
190
+ (-0.03em) is what hero type above ~56px wants. Zero on body, +0.08em on ALL-CAPS eyebrows.
191
+ - `text-wrap: balance` on headings, `pretty` on paragraphs. Always.
192
+ - Body copy is a single column, max ~640px. Never full-viewport.
193
+ - Card grids are 2 or 3 columns, single on mobile. Never 4.
194
+ - 4pt grid. `--sp-16` between big blocks, `--sp-6` inside a card, `--sp-2` label-to-field.
195
+ When unsure, add room.
196
+ - The nav is the only sticky element. No sticky CTAs, chat bubbles, or cookie banners.
197
+
198
+ ## Audit copy against all of the above
199
+
200
+ Run this over anything a client or a stranger sees before it ships: deck, proposal, SOW,
201
+ cover email, README, landing page, release note. The greps are a first pass that finds
202
+ candidates. Every hit needs the rules above to judge it, which is why they live in one
203
+ file with them.
204
+
205
+ ```bash
206
+ # Wordmark: lowercase in prose, outside code, paths, and URLs
207
+ grep -nE '(^|[^/.\-_a-z`])notambourine([^/.\-_a-z`]|$)' <file>
208
+
209
+ # Sentence case: never a valid form
210
+ grep -n 'Notambourine' <file>
211
+
212
+ # Legal entity, 2x max (signature block + Definitions anchor)
213
+ grep -nc 'NoTambourine LLC' <file>
214
+
215
+ # Em dash, en dash, curly quotes, single-char ellipsis. Interpunct deliberately absent.
216
+ perl -CSD -ne 'print "$.:$_" if /[\x{2014}\x{2013}\x{2018}\x{2019}\x{201C}\x{201D}\x{2026}]/' <file>
217
+
218
+ # Puffery adjectives
219
+ grep -niE '\b(proven|world-class|battle-tested|results-driven|cutting-edge|best-in-class|seamless|robust|leverage|synergy|holistic|bespoke)\b' <file>
220
+
221
+ # Participle tails: cut the sentence at the comma
222
+ grep -niE ',\s+(ensuring|enabling|allowing|helping|driving|empowering|delivering|providing)\b' <file>
223
+
224
+ # Importance-flagging and throat-clearing
225
+ grep -niE "\b(this matters|it's worth noting|it is worth noting|needless to say|at the end of the day|in today's)\b" <file>
226
+
227
+ # Exclamation marks in body, and first person
228
+ grep -nE '!(\s|$)' <file>
229
+ grep -nE '\bI\b' <file>
230
+ ```
231
+
232
+ Perl rather than `grep -P` for the punctuation pass. Stock macOS `grep` is BSD and
233
+ rejects `-P`, so that flag only works where someone installed GNU grep. Escapes rather
234
+ than literal characters, so this file stays ASCII and never trips its own check.
235
+
236
+ Across a directory, or a staged diff so a flag lands on work in progress:
237
+
238
+ ```bash
239
+ grep -rnE '(^|[^/.\-_a-z`])notambourine([^/.\-_a-z`]|$)' <dir>/ --include='*.md'
240
+
241
+ git diff --cached | grep -nE '^\+.*[^/.\-_a-z`]notambourine[^/.\-_a-z`]'
242
+ git diff --cached | perl -CSD -ne 'print if /^\+.*[\x{2014}\x{2013}\x{2018}\x{2019}\x{201C}\x{201D}\x{2026}]/'
243
+ ```
244
+
245
+ Then read for the two tells no grep finds: **grandiose scope**, and a **three-item line
246
+ spending its slots on adjectives** rather than numbers. Leave the three fixtures above
247
+ alone - they read as tells and stay.
248
+
249
+ **Ignore:** the slug in frontmatter, paths, URLs, CSS classes, npm names, GH remotes, and
250
+ anything inside a code fence or backticks (the wordmark pattern already excludes them). A
251
+ puffery word used literally about a thing rather than as a boast - "a robust error path".
252
+ `I` inside a quotation, a code identifier, or a name.
253
+
254
+ **Judge a hit two ways.** For the wordmark: would a client reading this expect the
255
+ wordmark or the slug? A path, URL, or identifier is the slug, so ignore it. For
256
+ everything else: would cutting this weaken the claim? If the sentence survives the cut,
257
+ the words were padding.
258
+
259
+ ## Where truth lives
260
+
261
+ **`notambourine/brand-kit` is the golden set.** `tokens.css`, `components.css`, and
262
+ `deck.css` here are the brand's only corrected copy. Read a value from this file, correct
263
+ a value in this file, and never sync one in. Anything that disagrees with it is downstream
264
+ and stale, however it renders.
265
+
266
+ A surface that cannot fetch at runtime pins this repo and reads the bytes out of the
267
+ checkout - `share.notambourine.com` serves `tokens.css` and `deck.css` from its own origin,
268
+ because a self-only CSP forbids a CDN. A pinned checkout is not a fork: the pin names one
269
+ commit, and the consumer's CI fails when a `var()` reads a token this repo stopped
270
+ declaring. Correct the value here and bump the pin.
271
+
272
+ This skill is the complete system, so nothing has to be looked up elsewhere. It carries
273
+ both themes: dark surfaces by default, and the light primitives behind `.theme-light`
274
+ (`--nt-ink`, `--nt-white`, `--nt-line`, the pink and mint tints, `--nt-paper-warm`).
275
+ A shipping surface may render dark only. That is a choice it made, not a shorter system.
276
+
277
+ Every stylesheet here is native CSS with no build step, so they load into a plain HTML file,
278
+ a Worker, an email template, or a React app unchanged. Write against the semantic layer -
279
+ `--bg`, `--bg-card`, `--fg1`/`2`/`3`, `--fg-mute`, `--accent`, `--accent-fg`,
280
+ `--accent-text`, `--support`, `--line`, `--sp-*`, `--r-*`, `--shadow-*`, `--ring-accent`,
281
+ `--font-*`. It is the stable API and it
282
+ survives a theme swap. The `--nt-*` primitives beneath it are the raw palette; reach past
283
+ the alias to one only when no alias covers what you need, and never inline a literal
284
+ color - `.theme-light` only works because no component hardcodes one. A `var()` fallback
285
+ is the one place a literal is correct, and `deck.css` uses them: a renderer can receive
286
+ that theme without `tokens.css` beside it, and the deck still comes out on brand.
287
+
288
+ ## Decks
289
+
290
+ Grammar first: numbered eyebrow → lowercase display headline with one pink `<em>` word →
291
+ short body → ALL-CAPS sublabel → typographic concept diagram → running footer. Every
292
+ "image" is a diagram built from the same tokens. No photography, no icons standing in for
293
+ a chart.
294
+
295
+ `deck.css` is the theme that renders it. Markdown in, slides out, through Marpit: `---`
296
+ splits a slide, `<!-- _class: lead -->` centers a title slide, `<!-- paginate: true -->`
297
+ turns on slide numbers. Load `tokens.css` on the page too - `deck.css` declares no
298
+ `:root`, so it reads its colors and faces from whatever the document root defines, and a
299
+ brand correction reaches a deck with no second edit.
300
+
301
+ `share.notambourine.com` runs this theme in its browser renderer and its PDF export, so a
302
+ deck shared from there and a deck built by hand come out the same. It serves these bytes
303
+ from a pinned checkout of this repo; correct a value here.
package/components.css ADDED
@@ -0,0 +1,186 @@
1
+ /* =========================================================
2
+ NoTambourine: component recipes
3
+ ---------------------------------------------------------
4
+ The ~80% of UI you actually build: button, badge, chip,
5
+ input, card, nav, switch/check/radio.
6
+
7
+ Native CSS, no build step, no framework. Every color, radius,
8
+ space, and duration is a var() from tokens.css, so load that
9
+ first and a theme swap carries the whole file. Bare px survives
10
+ only where it is component geometry - a 20px checkbox, a 40x24
11
+ switch - never where a token exists.
12
+
13
+ For sections (hero, pricing, feature row, footer) and the deck
14
+ language, see SKILL.md.
15
+ ========================================================= */
16
+
17
+ /* --- Button: pill is the default shape --------------------- */
18
+ .nt-btn {
19
+ font-family: var(--font-accent);
20
+ font-weight: var(--fw-semibold);
21
+ font-size: var(--fs-small);
22
+ letter-spacing: 0.01em;
23
+ padding: var(--sp-3) 22px;
24
+ border: 0;
25
+ border-radius: var(--r-pill);
26
+ cursor: pointer;
27
+ display: inline-flex;
28
+ align-items: center;
29
+ gap: var(--sp-2);
30
+ white-space: nowrap;
31
+ /* Named props, not `all`: the focus outline must land instantly, and
32
+ transform rides its own shorter beat for the press snap. */
33
+ transition: background var(--dur-2) var(--ease-out),
34
+ color var(--dur-2) var(--ease-out),
35
+ border-color var(--dur-2) var(--ease-out),
36
+ box-shadow var(--dur-2) var(--ease-out),
37
+ transform var(--dur-1) var(--ease-out);
38
+ }
39
+ .nt-btn--sm { padding: var(--sp-2) 14px; font-size: var(--fs-caption); }
40
+ .nt-btn--lg { padding: var(--sp-4) 28px; font-size: var(--fs-body); }
41
+
42
+ /* One pink CTA per screen. Hover lifts the shadow rather than darkening
43
+ the fill: ink text on the press tone lands at 4.3:1 and fails AA. */
44
+ .nt-btn--primary { background: var(--accent); color: var(--fg-on-pink); box-shadow: var(--shadow-accent); }
45
+ .nt-btn--primary:hover { box-shadow: var(--shadow-pink); }
46
+ .nt-btn--secondary { background: transparent; color: var(--fg1); border: 1px solid var(--fg1); }
47
+ .nt-btn--secondary:hover { background: var(--fg1); color: var(--bg); }
48
+ .nt-btn--ghost { background: transparent; color: var(--fg1); }
49
+ .nt-btn--ghost:hover { background: var(--accent-soft); color: var(--accent-text); }
50
+ .nt-btn--inverse { background: var(--fg1); color: var(--bg); }
51
+ .nt-btn:disabled { background: var(--bg-raised); color: var(--fg-mute); cursor: not-allowed; box-shadow: none; }
52
+
53
+ /* Scale only on press, never on hover. The one playful beat. */
54
+ .nt-btn:active:not(:disabled) { transform: scale(0.97); }
55
+
56
+ /* --- Badge -------------------------------------------------- */
57
+ .nt-badge {
58
+ font-family: var(--font-accent);
59
+ font-size: var(--fs-badge);
60
+ font-weight: var(--fw-semibold);
61
+ letter-spacing: 0.04em;
62
+ padding: var(--sp-1) var(--sp-3);
63
+ border-radius: var(--r-pill);
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: 6px;
67
+ white-space: nowrap;
68
+ }
69
+ .nt-badge--pink { background: var(--accent-soft); color: var(--accent-fg); }
70
+ .nt-badge--mint { background: var(--support-soft); color: var(--support-fg); }
71
+ .nt-badge--ink { background: var(--fg1); color: var(--bg); }
72
+ .nt-badge--outline { background: transparent; color: var(--fg2); border: 1px solid var(--line); }
73
+ .nt-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
74
+
75
+ /* --- Filter chip -------------------------------------------- */
76
+ .nt-chip {
77
+ font-family: var(--font-accent);
78
+ font-size: var(--fs-label);
79
+ font-weight: var(--fw-medium);
80
+ padding: 6px var(--sp-3);
81
+ border-radius: var(--r-pill);
82
+ border: 1px solid var(--line);
83
+ background: var(--bg-card);
84
+ color: var(--fg1);
85
+ cursor: pointer;
86
+ }
87
+ .nt-chip[aria-pressed="true"] { background: var(--fg1); color: var(--bg); border-color: var(--fg1); }
88
+
89
+ /* --- Input -------------------------------------------------- */
90
+ .nt-field { display: flex; flex-direction: column; gap: 6px; }
91
+ .nt-field > label { font-family: var(--font-accent); font-size: var(--fs-label); font-weight: var(--fw-semibold); color: var(--fg1); }
92
+ .nt-field .nt-hint { font-size: var(--fs-caption); color: var(--fg3); }
93
+ .nt-field .nt-err { font-size: var(--fs-caption); color: var(--accent-fg); }
94
+
95
+ .nt-input {
96
+ font-family: var(--font-body);
97
+ /* Never below 1rem: iOS Safari auto-zooms a focused input under 16px. */
98
+ font-size: var(--fs-body);
99
+ color: var(--fg1);
100
+ padding: var(--sp-3) 14px;
101
+ border-radius: var(--r-md);
102
+ border: 1px solid var(--line);
103
+ background: var(--bg-input);
104
+ outline: none;
105
+ transition: border-color var(--dur-2), box-shadow var(--dur-2);
106
+ }
107
+ .nt-input::placeholder { color: var(--fg-mute); }
108
+ .nt-input:focus { border-color: var(--accent); box-shadow: var(--ring-accent); }
109
+ .nt-input[aria-invalid="true"] { border-color: var(--accent); }
110
+ .nt-input:disabled { background: var(--bg-card); color: var(--fg-mute); cursor: not-allowed; }
111
+
112
+ /* --- Card: full border, never a colored left stripe -------- */
113
+ .nt-card {
114
+ background: var(--bg-card);
115
+ border: 1px solid var(--line-soft);
116
+ border-radius: var(--r-md);
117
+ padding: var(--sp-6);
118
+ box-shadow: var(--shadow-1);
119
+ transition: background var(--dur-2), box-shadow var(--dur-2) var(--ease-out), border-color var(--dur-2);
120
+ }
121
+ /* Hover lifts the shadow. No translate, no scale. */
122
+ .nt-card--hover:hover { background: var(--bg-raised); box-shadow: var(--shadow-2); }
123
+ /* A pink/mint border is a feature: callouts, active step, selected tier. */
124
+ .nt-card--accent { border-color: var(--accent); }
125
+ .nt-card--support { border-color: var(--support); }
126
+
127
+ /* --- Nav: frosted, the only sticky element on a page ------- */
128
+ .nt-nav {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: var(--sp-6);
132
+ padding: var(--sp-4) var(--sp-6);
133
+ background: var(--bg-veil);
134
+ backdrop-filter: blur(18px);
135
+ border-bottom: 1px solid var(--line);
136
+ }
137
+ /* Nunito 800, and untracked, so the text lockup matches logo.svg glyph
138
+ for glyph. The only place --font-wordmark is allowed. */
139
+ .nt-nav__brand { display: flex; align-items: center; gap: var(--sp-2); color: var(--accent);
140
+ font-family: var(--font-wordmark); font-weight: var(--fw-black); font-size: 17px;
141
+ letter-spacing: normal; }
142
+ .nt-nav__links { display: flex; gap: 22px; margin-left: var(--sp-4); }
143
+ .nt-nav__links a { font-family: var(--font-accent); font-size: var(--fs-label); font-weight: var(--fw-medium);
144
+ color: var(--fg3); padding: 6px 0; border-bottom: 1.5px solid transparent; }
145
+ .nt-nav__links a:hover { color: var(--fg1); border-bottom-color: transparent; }
146
+ .nt-nav__links a[aria-current] { color: var(--fg1); border-bottom-color: var(--accent); }
147
+
148
+ /* --- Switch / checkbox / radio ------------------------------ */
149
+ .nt-switch {
150
+ width: 40px; height: 24px; flex-shrink: 0; cursor: pointer; position: relative;
151
+ border-radius: var(--r-pill); border: 1px solid var(--line);
152
+ background: var(--bg-input); transition: background var(--dur-2);
153
+ }
154
+ .nt-switch::after { content: ''; position: absolute; top: 3px; left: 3px;
155
+ width: 16px; height: 16px; border-radius: 50%; background: var(--fg3);
156
+ transition: left var(--dur-2), background var(--dur-2); }
157
+ .nt-switch[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }
158
+ .nt-switch[aria-checked="true"]::after { left: 21px; background: var(--fg-on-pink); }
159
+
160
+ /* Put .nt-check/.nt-radio on a <button>: it tabs and clicks natively,
161
+ where a span needs tabindex and key handling bolted on. */
162
+ .nt-check {
163
+ width: 20px; height: 20px; flex-shrink: 0; padding: 0; cursor: pointer;
164
+ appearance: none;
165
+ border-radius: var(--r-xs); border: 1.5px solid var(--fg3); background: var(--bg-card);
166
+ display: inline-flex; align-items: center; justify-content: center;
167
+ }
168
+ .nt-check[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }
169
+ .nt-check svg { display: none; }
170
+ .nt-check[aria-checked="true"] svg { display: block; }
171
+
172
+ .nt-radio {
173
+ width: 20px; height: 20px; flex-shrink: 0; position: relative; padding: 0;
174
+ cursor: pointer; appearance: none;
175
+ border-radius: 50%; border: 1.5px solid var(--fg3); background: var(--bg-card);
176
+ }
177
+ .nt-radio[aria-checked="true"] { border-color: var(--accent); }
178
+ .nt-radio[aria-checked="true"]::after { content: ''; position: absolute; inset: 3px;
179
+ border-radius: 50%; background: var(--accent); }
180
+
181
+ /* Row wrapper for a toggle + its label */
182
+ .nt-toggle-row {
183
+ display: flex; align-items: center; gap: 14px;
184
+ padding: var(--sp-3) 14px;
185
+ border: 1px solid var(--line); border-radius: var(--r-md); background: var(--bg-raised);
186
+ }