@welcome-back/brand 0.1.3 → 0.3.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.
package/README.md CHANGED
@@ -18,6 +18,8 @@ Use it for loading moments that mean "on the way to the next step".
18
18
  - [Quick start](#quick-start)
19
19
  - [API](#api)
20
20
  - [Examples](#examples)
21
+ - [POS design tokens](#pos-design-tokens)
22
+ - [Product image frames](#product-image-frames)
21
23
  - [Accessibility](#accessibility)
22
24
  - [Where to use it, and where not](#where-to-use-it-and-where-not)
23
25
  - [Troubleshooting](#troubleshooting)
@@ -289,6 +291,57 @@ render(<Loading />);
289
291
  expect(screen.getByRole("img", { name: "Loading" })).toHaveClass("wb-runner");
290
292
  ```
291
293
 
294
+ ## POS design tokens
295
+
296
+ The WelcomeBack POS look: the `--wb-*` CSS custom properties that pos-ui paints
297
+ with. **The POS is not themable** (owner decision, 2026-09-28): every business
298
+ gets this same look, so nothing overrides these per tenant. The dashboard's POS
299
+ and KDS previews read the same values, so a preview cannot drift from the
300
+ register.
301
+
302
+ The source is `src/posTokens.ts`. `npm run build` also writes
303
+ `dist/pos-tokens.css` from it (`scripts/write-pos-tokens-css.mjs`), so the
304
+ stylesheet and the TS export always agree. Change a value there, never in an app.
305
+
306
+ In a stylesheet (pos-ui), before any other rule:
307
+
308
+ ```css
309
+ @import '@welcome-back/brand/pos-tokens.css';
310
+ ```
311
+
312
+ In React (the dashboard previews):
313
+
314
+ ```tsx
315
+ import { POS_TOKENS } from '@welcome-back/brand';
316
+
317
+ <div style={POS_TOKENS as React.CSSProperties}>…</div>;
318
+ POS_TOKENS['--wb-accent']; // '#171717'
319
+ ```
320
+
321
+ `posTokensCss(selector)` returns the same rule for another selector.
322
+
323
+ ## Product image frames
324
+
325
+ The frames a product photo is shown in: `1:1`, `4:3`, `3:2`, `16:9`. The
326
+ dashboard cropper saves one square photo. The digital menu, the kiosk and the
327
+ POS show it centered with `object-cover` in one of these frames, and the cropper
328
+ draws a guide for each one. So what the owner crops is what each surface shows.
329
+
330
+ A surface that shows a product photo in a frame outside this list breaks that
331
+ promise. Add the frame here, or use one that is already here.
332
+
333
+ ```tsx
334
+ import {
335
+ productImageAspectRatio,
336
+ productImageKeptArea,
337
+ } from '@welcome-back/brand';
338
+
339
+ <div style={{ aspectRatio: productImageAspectRatio('4:3') }}>…</div>;
340
+ productImageKeptArea('16:9'); // { width: 1, height: 0.5625 } of the square, centered
341
+ ```
342
+
343
+ `PRODUCT_IMAGE_FRAMES` holds the raw `{ width, height }` of each frame.
344
+
292
345
  ## Accessibility
293
346
 
294
347
  - **Give exactly one accessible name per loading state.** Either put `label` on
package/dist/cjs/index.js CHANGED
@@ -1,5 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RunnerLoader = void 0;
3
+ exports.productImageKeptArea = exports.productImageAspectRatio = exports.PRODUCT_IMAGE_FRAMES = exports.posTokensCss = exports.POS_TOKENS = exports.RunnerLoader = void 0;
4
4
  var RunnerLoader_js_1 = require("./RunnerLoader.js");
5
5
  Object.defineProperty(exports, "RunnerLoader", { enumerable: true, get: function () { return RunnerLoader_js_1.RunnerLoader; } });
6
+ var posTokens_js_1 = require("./posTokens.js");
7
+ Object.defineProperty(exports, "POS_TOKENS", { enumerable: true, get: function () { return posTokens_js_1.POS_TOKENS; } });
8
+ Object.defineProperty(exports, "posTokensCss", { enumerable: true, get: function () { return posTokens_js_1.posTokensCss; } });
9
+ var productImageFrames_js_1 = require("./productImageFrames.js");
10
+ Object.defineProperty(exports, "PRODUCT_IMAGE_FRAMES", { enumerable: true, get: function () { return productImageFrames_js_1.PRODUCT_IMAGE_FRAMES; } });
11
+ Object.defineProperty(exports, "productImageAspectRatio", { enumerable: true, get: function () { return productImageFrames_js_1.productImageAspectRatio; } });
12
+ Object.defineProperty(exports, "productImageKeptArea", { enumerable: true, get: function () { return productImageFrames_js_1.productImageKeptArea; } });
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POS_TOKENS = void 0;
4
+ exports.posTokensCss = posTokensCss;
5
+ /**
6
+ * The WelcomeBack POS look: the CSS custom properties pos-ui paints with, and
7
+ * the ones the dashboard's POS / KDS previews copy. One source, so a preview
8
+ * can never drift from the register.
9
+ *
10
+ * The POS is not themable (owner decision, 2026-09-28): every business gets
11
+ * this same look. Nothing overrides these per tenant.
12
+ *
13
+ * pos-ui imports the generated `@welcome-back/brand/pos-tokens.css`; React code
14
+ * reads `POS_TOKENS`.
15
+ */
16
+ exports.POS_TOKENS = {
17
+ // Monochrome chrome on a warm ivory canvas (pos-ui DESIGN.md §3).
18
+ "--wb-primary": "#171717",
19
+ "--wb-accent": "#171717",
20
+ "--wb-on-accent": "#ffffff",
21
+ // Tonal fill for hover/selected. A `-soft` token exists because the theme
22
+ // tokens are complete colors — Tailwind's `/NN` alpha modifier on them
23
+ // compiles to an invalid rgb() and renders transparent.
24
+ "--wb-accent-soft": "rgba(23, 23, 23, 0.06)",
25
+ // Warm ivory neutrals — the identity. Never cool gray.
26
+ "--wb-bg": "#f6f4e9",
27
+ "--wb-surface": "#ffffff",
28
+ "--wb-surface2": "#f2f1ec",
29
+ "--wb-ticket-bg": "#f2f1ec",
30
+ "--wb-border": "#e5e2d6",
31
+ // Translucent ivory veil for overlays (e.g. "Agotado").
32
+ "--wb-veil": "rgba(246, 244, 233, 0.72)",
33
+ "--wb-text": "#000000",
34
+ "--wb-muted": "#404040",
35
+ "--wb-success": "#22c55e",
36
+ "--wb-amber": "#f59e0b",
37
+ "--wb-amber-soft": "rgba(245, 158, 11, 0.15)",
38
+ "--wb-error": "#ef4444",
39
+ "--wb-error-soft": "rgba(239, 68, 68, 0.15)",
40
+ "--wb-primary-soft": "rgba(23, 23, 23, 0.15)",
41
+ // The brand tone. Same as the primary: the POS is monochrome.
42
+ "--wb-brand": "#171717",
43
+ "--wb-radius": "12px",
44
+ "--wb-radius-lg": "16px",
45
+ "--wb-font": "'Montserrat'",
46
+ };
47
+ /** The tokens as one CSS rule, for apps that load them as a stylesheet. */
48
+ function posTokensCss(selector = ":root") {
49
+ const lines = Object.entries(exports.POS_TOKENS).map(([name, value]) => ` ${name}: ${value};`);
50
+ return `${selector} {\n${lines.join("\n")}\n}\n`;
51
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRODUCT_IMAGE_FRAMES = void 0;
4
+ exports.productImageAspectRatio = productImageAspectRatio;
5
+ exports.productImageKeptArea = productImageKeptArea;
6
+ /**
7
+ * The frames a product photo is shown in. The dashboard cropper saves one
8
+ * square photo; the digital menu (catalog-ui), the kiosk and the POS show it
9
+ * centered with `object-cover` in one of these frames. The cropper draws a
10
+ * guide for each frame, so what the owner crops is what each surface shows.
11
+ *
12
+ * A surface that shows a product photo in a frame outside this list breaks
13
+ * that promise: add the frame here, or use one that is already here.
14
+ */
15
+ exports.PRODUCT_IMAGE_FRAMES = {
16
+ "1:1": { width: 1, height: 1 },
17
+ "4:3": { width: 4, height: 3 },
18
+ "3:2": { width: 3, height: 2 },
19
+ "16:9": { width: 16, height: 9 },
20
+ };
21
+ /** CSS `aspect-ratio` value for a frame, e.g. `"4 / 3"`. */
22
+ function productImageAspectRatio(frame) {
23
+ const { width, height } = exports.PRODUCT_IMAGE_FRAMES[frame];
24
+ return `${width} / ${height}`;
25
+ }
26
+ /**
27
+ * The part of the square photo a frame keeps, centered, as fractions of the
28
+ * square's side. Every frame is as wide as or wider than it is tall, so it
29
+ * keeps the full width and cuts the top and bottom equally.
30
+ */
31
+ function productImageKeptArea(frame) {
32
+ const { width, height } = exports.PRODUCT_IMAGE_FRAMES[frame];
33
+ return { width: 1, height: height / width };
34
+ }
@@ -1,2 +1,6 @@
1
1
  export { RunnerLoader } from "./RunnerLoader.js";
2
2
  export type { RunnerLoaderProps } from "./RunnerLoader.js";
3
+ export { POS_TOKENS, posTokensCss } from "./posTokens.js";
4
+ export type { PosToken } from "./posTokens.js";
5
+ export { PRODUCT_IMAGE_FRAMES, productImageAspectRatio, productImageKeptArea, } from "./productImageFrames.js";
6
+ export type { ProductImageFrame } from "./productImageFrames.js";
package/dist/esm/index.js CHANGED
@@ -1 +1,3 @@
1
1
  export { RunnerLoader } from "./RunnerLoader.js";
2
+ export { POS_TOKENS, posTokensCss } from "./posTokens.js";
3
+ export { PRODUCT_IMAGE_FRAMES, productImageAspectRatio, productImageKeptArea, } from "./productImageFrames.js";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The WelcomeBack POS look: the CSS custom properties pos-ui paints with, and
3
+ * the ones the dashboard's POS / KDS previews copy. One source, so a preview
4
+ * can never drift from the register.
5
+ *
6
+ * The POS is not themable (owner decision, 2026-09-28): every business gets
7
+ * this same look. Nothing overrides these per tenant.
8
+ *
9
+ * pos-ui imports the generated `@welcome-back/brand/pos-tokens.css`; React code
10
+ * reads `POS_TOKENS`.
11
+ */
12
+ export declare const POS_TOKENS: {
13
+ readonly "--wb-primary": "#171717";
14
+ readonly "--wb-accent": "#171717";
15
+ readonly "--wb-on-accent": "#ffffff";
16
+ readonly "--wb-accent-soft": "rgba(23, 23, 23, 0.06)";
17
+ readonly "--wb-bg": "#f6f4e9";
18
+ readonly "--wb-surface": "#ffffff";
19
+ readonly "--wb-surface2": "#f2f1ec";
20
+ readonly "--wb-ticket-bg": "#f2f1ec";
21
+ readonly "--wb-border": "#e5e2d6";
22
+ readonly "--wb-veil": "rgba(246, 244, 233, 0.72)";
23
+ readonly "--wb-text": "#000000";
24
+ readonly "--wb-muted": "#404040";
25
+ readonly "--wb-success": "#22c55e";
26
+ readonly "--wb-amber": "#f59e0b";
27
+ readonly "--wb-amber-soft": "rgba(245, 158, 11, 0.15)";
28
+ readonly "--wb-error": "#ef4444";
29
+ readonly "--wb-error-soft": "rgba(239, 68, 68, 0.15)";
30
+ readonly "--wb-primary-soft": "rgba(23, 23, 23, 0.15)";
31
+ readonly "--wb-brand": "#171717";
32
+ readonly "--wb-radius": "12px";
33
+ readonly "--wb-radius-lg": "16px";
34
+ readonly "--wb-font": "'Montserrat'";
35
+ };
36
+ export type PosToken = keyof typeof POS_TOKENS;
37
+ /** The tokens as one CSS rule, for apps that load them as a stylesheet. */
38
+ export declare function posTokensCss(selector?: string): string;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The WelcomeBack POS look: the CSS custom properties pos-ui paints with, and
3
+ * the ones the dashboard's POS / KDS previews copy. One source, so a preview
4
+ * can never drift from the register.
5
+ *
6
+ * The POS is not themable (owner decision, 2026-09-28): every business gets
7
+ * this same look. Nothing overrides these per tenant.
8
+ *
9
+ * pos-ui imports the generated `@welcome-back/brand/pos-tokens.css`; React code
10
+ * reads `POS_TOKENS`.
11
+ */
12
+ export const POS_TOKENS = {
13
+ // Monochrome chrome on a warm ivory canvas (pos-ui DESIGN.md §3).
14
+ "--wb-primary": "#171717",
15
+ "--wb-accent": "#171717",
16
+ "--wb-on-accent": "#ffffff",
17
+ // Tonal fill for hover/selected. A `-soft` token exists because the theme
18
+ // tokens are complete colors — Tailwind's `/NN` alpha modifier on them
19
+ // compiles to an invalid rgb() and renders transparent.
20
+ "--wb-accent-soft": "rgba(23, 23, 23, 0.06)",
21
+ // Warm ivory neutrals — the identity. Never cool gray.
22
+ "--wb-bg": "#f6f4e9",
23
+ "--wb-surface": "#ffffff",
24
+ "--wb-surface2": "#f2f1ec",
25
+ "--wb-ticket-bg": "#f2f1ec",
26
+ "--wb-border": "#e5e2d6",
27
+ // Translucent ivory veil for overlays (e.g. "Agotado").
28
+ "--wb-veil": "rgba(246, 244, 233, 0.72)",
29
+ "--wb-text": "#000000",
30
+ "--wb-muted": "#404040",
31
+ "--wb-success": "#22c55e",
32
+ "--wb-amber": "#f59e0b",
33
+ "--wb-amber-soft": "rgba(245, 158, 11, 0.15)",
34
+ "--wb-error": "#ef4444",
35
+ "--wb-error-soft": "rgba(239, 68, 68, 0.15)",
36
+ "--wb-primary-soft": "rgba(23, 23, 23, 0.15)",
37
+ // The brand tone. Same as the primary: the POS is monochrome.
38
+ "--wb-brand": "#171717",
39
+ "--wb-radius": "12px",
40
+ "--wb-radius-lg": "16px",
41
+ "--wb-font": "'Montserrat'",
42
+ };
43
+ /** The tokens as one CSS rule, for apps that load them as a stylesheet. */
44
+ export function posTokensCss(selector = ":root") {
45
+ const lines = Object.entries(POS_TOKENS).map(([name, value]) => ` ${name}: ${value};`);
46
+ return `${selector} {\n${lines.join("\n")}\n}\n`;
47
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The frames a product photo is shown in. The dashboard cropper saves one
3
+ * square photo; the digital menu (catalog-ui), the kiosk and the POS show it
4
+ * centered with `object-cover` in one of these frames. The cropper draws a
5
+ * guide for each frame, so what the owner crops is what each surface shows.
6
+ *
7
+ * A surface that shows a product photo in a frame outside this list breaks
8
+ * that promise: add the frame here, or use one that is already here.
9
+ */
10
+ export declare const PRODUCT_IMAGE_FRAMES: {
11
+ readonly "1:1": {
12
+ readonly width: 1;
13
+ readonly height: 1;
14
+ };
15
+ readonly "4:3": {
16
+ readonly width: 4;
17
+ readonly height: 3;
18
+ };
19
+ readonly "3:2": {
20
+ readonly width: 3;
21
+ readonly height: 2;
22
+ };
23
+ readonly "16:9": {
24
+ readonly width: 16;
25
+ readonly height: 9;
26
+ };
27
+ };
28
+ export type ProductImageFrame = keyof typeof PRODUCT_IMAGE_FRAMES;
29
+ /** CSS `aspect-ratio` value for a frame, e.g. `"4 / 3"`. */
30
+ export declare function productImageAspectRatio(frame: ProductImageFrame): string;
31
+ /**
32
+ * The part of the square photo a frame keeps, centered, as fractions of the
33
+ * square's side. Every frame is as wide as or wider than it is tall, so it
34
+ * keeps the full width and cuts the top and bottom equally.
35
+ */
36
+ export declare function productImageKeptArea(frame: ProductImageFrame): {
37
+ width: number;
38
+ height: number;
39
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The frames a product photo is shown in. The dashboard cropper saves one
3
+ * square photo; the digital menu (catalog-ui), the kiosk and the POS show it
4
+ * centered with `object-cover` in one of these frames. The cropper draws a
5
+ * guide for each frame, so what the owner crops is what each surface shows.
6
+ *
7
+ * A surface that shows a product photo in a frame outside this list breaks
8
+ * that promise: add the frame here, or use one that is already here.
9
+ */
10
+ export const PRODUCT_IMAGE_FRAMES = {
11
+ "1:1": { width: 1, height: 1 },
12
+ "4:3": { width: 4, height: 3 },
13
+ "3:2": { width: 3, height: 2 },
14
+ "16:9": { width: 16, height: 9 },
15
+ };
16
+ /** CSS `aspect-ratio` value for a frame, e.g. `"4 / 3"`. */
17
+ export function productImageAspectRatio(frame) {
18
+ const { width, height } = PRODUCT_IMAGE_FRAMES[frame];
19
+ return `${width} / ${height}`;
20
+ }
21
+ /**
22
+ * The part of the square photo a frame keeps, centered, as fractions of the
23
+ * square's side. Every frame is as wide as or wider than it is tall, so it
24
+ * keeps the full width and cuts the top and bottom equally.
25
+ */
26
+ export function productImageKeptArea(frame) {
27
+ const { width, height } = PRODUCT_IMAGE_FRAMES[frame];
28
+ return { width: 1, height: height / width };
29
+ }
@@ -0,0 +1,25 @@
1
+ /* Generated from src/posTokens.ts by scripts/write-pos-tokens-css.mjs. Do not edit. */
2
+ :root {
3
+ --wb-primary: #171717;
4
+ --wb-accent: #171717;
5
+ --wb-on-accent: #ffffff;
6
+ --wb-accent-soft: rgba(23, 23, 23, 0.06);
7
+ --wb-bg: #f6f4e9;
8
+ --wb-surface: #ffffff;
9
+ --wb-surface2: #f2f1ec;
10
+ --wb-ticket-bg: #f2f1ec;
11
+ --wb-border: #e5e2d6;
12
+ --wb-veil: rgba(246, 244, 233, 0.72);
13
+ --wb-text: #000000;
14
+ --wb-muted: #404040;
15
+ --wb-success: #22c55e;
16
+ --wb-amber: #f59e0b;
17
+ --wb-amber-soft: rgba(245, 158, 11, 0.15);
18
+ --wb-error: #ef4444;
19
+ --wb-error-soft: rgba(239, 68, 68, 0.15);
20
+ --wb-primary-soft: rgba(23, 23, 23, 0.15);
21
+ --wb-brand: #171717;
22
+ --wb-radius: 12px;
23
+ --wb-radius-lg: 16px;
24
+ --wb-font: 'Montserrat';
25
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@welcome-back/brand",
3
- "version": "0.1.3",
4
- "description": "WelcomeBack brand assets for React apps: the running-W loader.",
3
+ "version": "0.3.0",
4
+ "description": "WelcomeBack brand assets for React apps: the running-W loader and the POS design tokens.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
7
7
  "type": "git",
@@ -17,13 +17,14 @@
17
17
  "types": "./dist/esm/index.d.ts",
18
18
  "import": "./dist/esm/index.js",
19
19
  "require": "./dist/cjs/index.js"
20
- }
20
+ },
21
+ "./pos-tokens.css": "./dist/pos-tokens.css"
21
22
  },
22
23
  "files": [
23
24
  "dist"
24
25
  ],
25
26
  "scripts": {
26
- "build": "rm -rf dist && tsc -p tsconfig.build.json && tsc -p tsconfig.cjs.json && node scripts/mark-cjs.mjs",
27
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && tsc -p tsconfig.cjs.json && node scripts/mark-cjs.mjs && node scripts/write-pos-tokens-css.mjs",
27
28
  "typecheck": "tsc --noEmit",
28
29
  "test": "vitest run",
29
30
  "prepublishOnly": "npm run typecheck && npm test && npm run build"