Create brand guidelines for your product
@slashclone/brand-book
Create brand guidelines for your product: one shareable HTML brand book covering logo, colors, type, icons, share images, and voice. It follows your real design tokens in light and dark themes, and a checker confirms contrast in both themes, icon and image sizes, and banned copy such as em dashes. Writes only to brand/ and public/brand/.
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1<!doctype html>
- 2<html lang="en" data-theme="light">
- 3 <head>
- 4 <meta charset="utf-8" />
- 5 <meta name="viewport" content="width=device-width, initial-scale=1" />
- 6 <title>Product brand book</title>
- 7 <link rel="icon" href="icons/favicon.svg" />
- 8 <style>
- 9 /* Fill these from brand.json. The swatch chapter reads them back, so they cannot drift. */
- 10 :root {
- 11 --page: #ffffff;
- 12 --card: #f4f5f7;
- 13 --text: #1b1d22;
- 14 --muted: #5b6070;
- 15 --accent: #2d5bd3;
- 16 --mark: #2d5bd3;
- 17 --line: rgba(0, 0, 0, 0.12);
- 18 --font-display: system-ui, sans-serif;
- 19 --font-body: system-ui, sans-serif;
- 20 --font-mono: ui-monospace, monospace;
- 21 }
- 22 :root[data-theme="dark"] {
- 23 --page: #0f1115;
- 24 --card: #181b21;
- 25 --text: #eceef2;
- 26 --muted: #a3a8b4;
- 27 --accent: #7ea2f5;
- 28 --mark: #4c78e6;
- 29 --line: rgba(255, 255, 255, 0.14);
- 30 }
- 31 * {
- 32 box-sizing: border-box;
- 33 }
- 34 body {
- 35 margin: 0;
- 36 background: var(--page);
- 37 color: var(--text);
- 38 font: 16px/1.6 var(--font-body);
- 39 }
- 40 nav {
- 41 position: sticky;
- 42 top: 0;
- 43 display: flex;
- 44 gap: 12px;
- 45 flex-wrap: wrap;
- 46 padding: 10px 16px;
- 47 background: var(--page);
- 48 border-bottom: 1px solid var(--line);
- 49 font-size: 14px;
- 50 z-index: 2;
- 51 }
- 52 nav a {
- 53 color: var(--muted);
- 54 text-decoration: none;
- 55 }
- 56 nav button {
- 57 margin-left: auto;
- 58 }
- 59 main {
- 60 max-width: 1040px;
- 61 margin: 0 auto;
- 62 padding: 0 16px 96px;
- 63 }
- 64 section {
- 65 padding: 56px 0;
- 66 border-bottom: 1px solid var(--line);
- 67 }
- 68 h1,
- 69 h2 {
- 70 font-family: var(--font-display);
- 71 line-height: 1.15;
- 72 }
- 73 h2 small {
- 74 display: block;
- 75 font: 13px var(--font-mono);
- 76 color: var(--muted);
- 77 }
- 78 .grid {
- 79 display: grid;
- 80 gap: 16px;
- 81 grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
- 82 }
- 83 .card {
- 84 background: var(--card);
- 85 border: 1px solid var(--line);
- 86 border-radius: 12px;
- 87 padding: 16px;
- 88 }
- 89 .swatch {
- 90 height: 72px;
- 91 border-radius: 8px;
- 92 border: 1px solid var(--line);
- 93 }
- 94 .mono {
- 95 font-family: var(--font-mono);
- 96 font-size: 13px;
- 97 }
- 98 .clear {
- 99 display: inline-block;
- 100 padding: var(--clear, 24px);
- 101 outline: 1px dashed var(--accent);
- 102 }
- 103 .mark {
- 104 color: var(--mark);
- 105 display: block;
- 106 }
- 107 .misuse {
- 108 position: relative;
- 109 }
- 110 .misuse::after {
- 111 content: "Don't";
- 112 position: absolute;
- 113 top: 8px;
- 114 right: 8px;
- 115 font-size: 12px;
- 116 color: var(--muted);
- 117 }
- 118 .do-dont {
- 119 display: grid;
- 120 gap: 16px;
- 121 grid-template-columns: 1fr 1fr;
- 122 }
- 123 @media (max-width: 640px) {
- 124 .do-dont {
- 125 grid-template-columns: 1fr;
- 126 }
- 127 }
- 128 </style>
- 129 </head>
- 130 <body>
- 131 <nav>
- 132 <a href="#foundation">Foundation</a><a href="#logo">Logo</a><a href="#color">Color</a>
- 133 <a href="#type">Type</a><a href="#imagery">Imagery</a><a href="#share">Share</a>
- 134 <a href="#icons">Icons</a><a href="#voice">Voice</a><a href="#downloads">Downloads</a>
- 135 <a href="#sources">Sources</a>
- 136 <button id="theme" type="button">Switch theme</button>
- 137 </nav>
- 138 <main>
- 139 <header>
- 140 <h1>Product brand book</h1>
- 141 <p class="mono">Edition 1</p>
- 142 </header>
- 144 <section id="foundation">
- 145 <h2><small>01 Foundation</small>What we make, for whom</h2>
- 146 <p>
- 147 Mission in one sentence. The one line people should repeat. Three to five principles, each
- 148 with a do and a don't.
- 149 </p>
- 150 </section>
- 152 <section id="logo">
- 153 <h2><small>02 Logo</small>The mark and the wordmark</h2>
- 154 <!-- Replace the placeholder with the real mark, inline, using currentColor so themes recolor it. -->
- 155 <template id="mark-src"
- 156 ><svg viewBox="0 0 64 64" width="100%" height="100%" aria-hidden="true">
- 157 <rect x="8" y="8" width="48" height="48" rx="12" fill="currentColor" /></svg
- 158 ></template>
- 159 <h3>Sizes</h3>
- 160 <div class="grid" id="sizes"></div>
- 161 <h3>Clear space</h3>
- 162 <p>Keep a margin of at least one unit of the mark (named here) on every side.</p>
- 163 <div class="clear" style="--clear: 24px">
- 164 <span class="mark" data-mark style="width: 96px; height: 96px"></span>
- 165 </div>
- 166 <h3>Misuse</h3>
- 167 <div class="grid" id="misuse"></div>
- 168 </section>
- 170 <section id="color">
- 171 <h2><small>03 Color</small>Tokens, light and dark</h2>
- 172 <p>
- 173 Every value comes from the product's tokens. Each swatch shows its use and the theme it
- 174 belongs to.
- 175 </p>
- 176 <div class="grid" id="swatches"></div>
- 177 </section>
- 179 <section id="type">
- 180 <h2><small>04 Type</small>Faces, sizes, and where each goes</h2>
- 181 <p style="font: 700 40px var(--font-display)">Display sample</p>
- 182 <p>Body sample. Say what the body face is for and its sizes.</p>
- 183 <p class="mono">Mono sample, for code and data.</p>
- 184 </section>
- 186 <section id="imagery">
- 187 <h2><small>05 Imagery</small>What pictures look like</h2>
- 188 <div class="do-dont">
- 189 <div class="card">
- 190 <strong>Do</strong>
- 191 <p>Describe the subject, light, and crop.</p>
- 192 </div>
- 193 <div class="card">
- 194 <strong>Don't</strong>
- 195 <p>Describe what never appears.</p>
- 196 </div>
- 197 </div>
- 198 </section>
- 200 <section id="share">
- 201 <h2><small>06 Share images</small>Link previews and social sizes</h2>
- 202 <div class="grid" id="share-grid"></div>
- 203 </section>
- 205 <section id="icons">
- 206 <h2><small>07 Icons</small>Favicon and app icons</h2>
- 207 <p>The favicon is a flat, simplified mark: detail turns to noise at 16 pixels.</p>
- 208 <div class="grid" id="icon-grid"></div>
- 209 </section>
- 211 <section id="voice">
- 212 <h2><small>08 Voice</small>How we write</h2>
- 213 <div class="do-dont">
- 214 <div class="card">
- 215 <strong>Do</strong>
- 216 <p>Plain, short sentences.</p>
- 217 </div>
- 218 <div class="card">
- 219 <strong>Don't</strong>
- 220 <p>No em dashes. Use a period, a comma, or a colon.</p>
- 221 </div>
- 222 </div>
- 223 </section>
- 225 <section id="downloads">
- 226 <h2><small>09 Downloads</small>Everything, in one place</h2>
- 227 <ul id="download-list"></ul>
- 228 </section>
- 230 <section id="sources">
- 231 <h2><small>10 Sources and changes</small>Where each part comes from</h2>
- 232 <p>
- 233 Code paths and scripts that generate each file go here, not in captions. Then a short log
- 234 of each edition.
- 235 </p>
- 236 </section>
- 237 </main>
- 238 <script>
- 239 // Fill from brand.json: token names, uses, and the files under brand/.
- 240 const TOKENS = ["page", "card", "text", "muted", "accent", "mark"];
- 241 const SIZES = [16, 32, 64, 128];
- 242 const SHARE = [["share/link-preview.png", "Link preview, 1200x630"]];
- 243 const ICONS = [
- 244 ["icons/favicon.svg", "Favicon, SVG"],
- 245 ["icons/icon-192.png", "App icon 192"],
- 246 ["icons/icon-512.png", "App icon 512"],
- 247 ["icons/icon-maskable-512.png", "Maskable 512"],
- 248 ["icons/apple-touch-icon.png", "Touch icon 180"],
- 249 ];
- 250 const MISUSE = [
- 251 ["scaleX(1.6)", "Do not stretch"],
- 252 ["rotate(-20deg)", "Do not rotate"],
- 253 ["none", "Do not recolor", "hue-rotate(140deg)"],
- 254 ["none", "No shadows or effects", "drop-shadow(4px 4px 4px #000)"],
- 255 ];
- 257 const root = document.documentElement;
- 258 const saved = (() => {
- 259 try {
- 260 return localStorage.getItem("theme");
- 261 } catch {
- 262 return null;
- 263 }
- 264 })();
- 265 root.dataset.theme =
- 266 saved || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
- 267 document.getElementById("theme").onclick = () => {
- 268 root.dataset.theme = root.dataset.theme === "dark" ? "light" : "dark";
- 269 try {
- 270 localStorage.setItem("theme", root.dataset.theme);
- 271 } catch {}
- 272 paint();
- 273 };
- 275 const markHtml = document.getElementById("mark-src").innerHTML;
- 276 const el = (tag, attrs, html) =>
- 277 Object.assign(document.createElement(tag), attrs, html ? { innerHTML: html } : {});
- 278 document.querySelectorAll("[data-mark]").forEach((n) => (n.innerHTML = markHtml));
- 279 for (const px of SIZES) {
- 280 const c = el("div", { className: "card" });
- 281 c.append(
- 282 el("span", { className: "mark", style: `width:${px}px;height:${px}px` }, markHtml),
- 283 el("p", { className: "mono" }, `${px}px`),
- 284 );
- 285 document.getElementById("sizes").append(c);
- 286 }
- 287 for (const [transform, label, filter] of MISUSE) {
- 288 const c = el("div", { className: "card misuse" });
- 289 c.append(
- 290 el(
- 291 "span",
- 292 {
- 293 className: "mark",
- 294 style: `width:72px;height:72px;transform:${transform};filter:${filter || "none"}`,
- 295 },
- 296 markHtml,
- 297 ),
- 298 el("p", {}, label),
- 299 );
- 300 document.getElementById("misuse").append(c);
- 301 }
- 302 const figure =
- 303 (id) =>
- 304 ([src, label]) => {
- 305 const c = el("div", { className: "card" });
- 306 c.append(
- 307 el("img", { src, alt: label, style: "width:100%" }),
- 308 el("p", { className: "mono" }, label),
- 309 );
- 310 document.getElementById(id).append(c);
- 311 };
- 312 SHARE.forEach(figure("share-grid"));
- 313 ICONS.forEach(figure("icon-grid"));
- 314 for (const [src, label] of [...SHARE, ...ICONS]) {
- 315 const li = el("li");
- 316 li.append(el("a", { href: src, download: "" }, label));
- 317 document.getElementById("download-list").append(li);
- 318 }
- 320 function paint() {
- 321 const box = document.getElementById("swatches");
- 322 box.innerHTML = "";
- 323 const css = getComputedStyle(root);
- 324 for (const name of TOKENS) {
- 325 const value = css.getPropertyValue(`--${name}`).trim();
- 326 const c = el("div", { className: "card" });
- 327 c.append(
- 328 el("div", { className: "swatch", style: `background:${value}` }),
- 329 el("p", { className: "mono" }, `${name} ${value} (${root.dataset.theme})`),
- 330 );
- 331 box.append(c);
- 332 }
- 333 }
- 334 paint();
- 335 </script>
- 336 </body>
- 337</html>
