@kierto/design-system 0.1.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 ADDED
@@ -0,0 +1,71 @@
1
+ # @kierto/design-system
2
+
3
+ The **canonical Kierto brand design system** — one source of truth for every
4
+ Kierto frontend. A change to the brand tokens, fonts, or base theme here
5
+ propagates to all consuming apps automatically.
6
+
7
+ It exports:
8
+
9
+ - **`brand`** — the brand color tokens (spruce ink, recycle green, bone paper, …).
10
+ - **`fonts`** + **`fontImportUrl`** — the Bricolage Grotesque / Hanken Grotesk /
11
+ Space Mono stack and the Google Fonts URL that backs it.
12
+ - **`radii`** — shared corner radii.
13
+ - **`baseThemeOptions`** — a plain MUI `ThemeOptions` object (no MUI version
14
+ coupling), and **`createBaseTheme(overrides?)`** / **`baseTheme`** convenience
15
+ builders.
16
+
17
+ ## Why `ThemeOptions`, not a constructed theme
18
+
19
+ The marketing site (`kierto-commerce`) is on **MUI 6** while the store apps
20
+ (`kierto-store`) are on **MUI 7**. To stay agnostic to the consumer's MUI major
21
+ version, the package declares `@mui/material` as a wide **peer dependency**
22
+ (`>=6 <8`) and exports plain options. Each app calls its **own** `createTheme`:
23
+
24
+ ```ts
25
+ import { createTheme } from '@mui/material/styles';
26
+ import { baseThemeOptions, brand } from '@kierto/design-system';
27
+
28
+ const theme = createTheme(baseThemeOptions, {
29
+ /* app-specific overrides — see consumers below */
30
+ });
31
+ ```
32
+
33
+ ## Consumers
34
+
35
+ | App | How it consumes | Adoption |
36
+ | --- | --- | --- |
37
+ | `kierto-commerce` (marketing) | published package | full base theme (`baseTheme`) |
38
+ | `kierto-store` / dashboard | via `@kierto-store/frontend-shared` | brand tokens + fonts; keeps dense app UI, neutral surfaces, dark mode. Green stays the primary action color. |
39
+ | `kierto-store` / tenant storefronts | via `@kierto-store/frontend-shared` | full design system; **per-tenant** primary/secondary colors still override |
40
+
41
+ **The golden rule:** apps reference `brand.*` / `fonts.*` tokens — never hardcode
42
+ brand hex values. That is what makes a brand change propagate everywhere.
43
+
44
+ > Each consuming app must also load the font stack. Add `fontImportUrl` (or the
45
+ > equivalent `<link>`) to the app's `index.html`.
46
+
47
+ ## How propagation works
48
+
49
+ - **Within `kierto-store`** the three frontends consume this package through the
50
+ pnpm **workspace**, so a change here is reflected **instantly** — no publish.
51
+ - **Across repos** (to `kierto-commerce`) the package is **published**; consumers
52
+ track a `^` range and pick up changes with `pnpm update @kierto/design-system`
53
+ (or a Renovate/Dependabot auto-PR). The `.github/workflows/publish-design-system.yml`
54
+ workflow builds and publishes on a `design-system-vX.Y.Z` tag.
55
+
56
+ ### Releasing a change
57
+
58
+ 1. Edit tokens/theme here; bump `version` in `package.json`.
59
+ 2. Within `kierto-store`, nothing else is needed — workspace consumers are current.
60
+ 3. To roll it out to `kierto-commerce`: tag `design-system-vX.Y.Z` (or run the
61
+ publish workflow), then `pnpm update @kierto/design-system` in that repo.
62
+
63
+ Published as a **public** package on **npmjs.org** under the `kierto` npm org. The
64
+ CI workflow needs an `NPM_TOKEN` repository secret (an npm automation/granular
65
+ token with publish rights on the `@kierto` scope). No consumer `.npmrc` changes are
66
+ needed — public npm is the default registry.
67
+
68
+ ## Scripts
69
+
70
+ - `pnpm build` — emit `dist/` (ESM JS + d.ts) for publishing.
71
+ - `pnpm typecheck` / `pnpm test` / `pnpm lint`.
@@ -0,0 +1,2 @@
1
+ export { brand, fonts, fontImportUrl, radii, type Brand, type Fonts } from './tokens.js';
2
+ export { baseThemeOptions, createBaseTheme, default as baseTheme } from './theme.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // @kierto/design-system — canonical Kierto brand design system.
2
+ // Tokens + fonts + base MUI theme, shared across every Kierto frontend so a
3
+ // brand change here propagates automatically to all apps that consume it.
4
+ export { brand, fonts, fontImportUrl, radii } from './tokens.js';
5
+ export { baseThemeOptions, createBaseTheme, default as baseTheme } from './theme.js';
@@ -0,0 +1,11 @@
1
+ import { type Theme, type ThemeOptions } from '@mui/material/styles';
2
+ export declare const baseThemeOptions: ThemeOptions;
3
+ /**
4
+ * Build a MUI theme from the canonical base, optionally layering app-specific
5
+ * overrides on top (e.g. the dashboard's dark mode + dense surfaces, or a
6
+ * tenant's custom brand colors).
7
+ */
8
+ export declare function createBaseTheme(overrides?: ThemeOptions): Theme;
9
+ /** The canonical base theme, ready to use directly (marketing site default). */
10
+ declare const baseTheme: Theme;
11
+ export default baseTheme;
package/dist/theme.js ADDED
@@ -0,0 +1,152 @@
1
+ import { createTheme } from '@mui/material/styles';
2
+ import { brand, fonts } from './tokens.js';
3
+ // ── Kierto base theme — "Nordic Field Manual" ─────────────────────────────
4
+ // Dominant ink + paper, one sharp green signal — a warm, editorial, utilitarian
5
+ // key echoing the looping arrows in the logo. This is the canonical base that
6
+ // every Kierto frontend builds on via `createTheme(baseThemeOptions, appOverrides)`.
7
+ //
8
+ // Exported as plain `ThemeOptions` (not a constructed Theme) so the package stays
9
+ // agnostic to the consumer's MUI major version: the marketing site is on MUI 6,
10
+ // the store apps on MUI 7. Each app calls its OWN `createTheme` with these options.
11
+ export const baseThemeOptions = {
12
+ palette: {
13
+ mode: 'light',
14
+ primary: {
15
+ main: brand.ink,
16
+ dark: brand.inkDeep,
17
+ light: brand.inkSoft,
18
+ contrastText: brand.paper,
19
+ },
20
+ secondary: {
21
+ main: brand.signal,
22
+ dark: brand.signalDeep,
23
+ light: brand.signalSoft,
24
+ contrastText: '#FFFFFF', // white reads on the green accent (and its darker hover)
25
+ },
26
+ background: {
27
+ default: brand.paper,
28
+ paper: brand.surface,
29
+ },
30
+ text: {
31
+ primary: brand.ink,
32
+ secondary: brand.inkSoft,
33
+ },
34
+ success: { main: '#2E7D5B' },
35
+ warning: { main: '#C77A0A' },
36
+ error: { main: '#B23A22' },
37
+ divider: brand.line,
38
+ },
39
+ shape: { borderRadius: 6 },
40
+ typography: {
41
+ fontFamily: fonts.body,
42
+ h1: {
43
+ fontFamily: fonts.display,
44
+ fontWeight: 800,
45
+ letterSpacing: '-0.035em',
46
+ lineHeight: 0.96,
47
+ fontSize: 'clamp(2.9rem, 6.2vw, 5.1rem)',
48
+ },
49
+ h2: {
50
+ fontFamily: fonts.display,
51
+ fontWeight: 800,
52
+ letterSpacing: '-0.03em',
53
+ lineHeight: 1.0,
54
+ fontSize: 'clamp(2.3rem, 4.6vw, 3.6rem)',
55
+ },
56
+ h3: {
57
+ fontFamily: fonts.display,
58
+ fontWeight: 700,
59
+ letterSpacing: '-0.025em',
60
+ lineHeight: 1.05,
61
+ fontSize: 'clamp(1.8rem, 3.4vw, 2.7rem)',
62
+ },
63
+ h4: {
64
+ fontFamily: fonts.display,
65
+ fontWeight: 700,
66
+ letterSpacing: '-0.02em',
67
+ lineHeight: 1.1,
68
+ },
69
+ h5: { fontFamily: fonts.display, fontWeight: 700, letterSpacing: '-0.015em' },
70
+ h6: { fontFamily: fonts.display, fontWeight: 700, letterSpacing: '-0.01em' },
71
+ subtitle1: { fontWeight: 600 },
72
+ subtitle2: { fontWeight: 700 },
73
+ body1: { lineHeight: 1.72 },
74
+ body2: { lineHeight: 1.7 },
75
+ button: {
76
+ fontFamily: fonts.body,
77
+ textTransform: 'none',
78
+ fontWeight: 700,
79
+ letterSpacing: '0.01em',
80
+ },
81
+ overline: {
82
+ fontFamily: fonts.mono,
83
+ fontWeight: 700,
84
+ letterSpacing: '0.2em',
85
+ textTransform: 'uppercase',
86
+ fontSize: '0.72rem',
87
+ lineHeight: 1.6,
88
+ },
89
+ },
90
+ components: {
91
+ MuiButton: {
92
+ defaultProps: { disableElevation: true },
93
+ styleOverrides: {
94
+ root: {
95
+ borderRadius: 4,
96
+ paddingInline: 20,
97
+ paddingBlock: 9,
98
+ fontWeight: 700,
99
+ },
100
+ sizeLarge: {
101
+ paddingInline: 30,
102
+ paddingBlock: 14,
103
+ fontSize: '1.02rem',
104
+ },
105
+ containedSecondary: {
106
+ '&:hover': { backgroundColor: brand.signalDeep },
107
+ },
108
+ outlined: {
109
+ borderWidth: '1.5px',
110
+ '&:hover': { borderWidth: '1.5px' },
111
+ },
112
+ },
113
+ },
114
+ MuiPaper: {
115
+ styleOverrides: { root: { backgroundImage: 'none' } },
116
+ },
117
+ MuiCard: {
118
+ defaultProps: { elevation: 0 },
119
+ styleOverrides: {
120
+ root: {
121
+ backgroundImage: 'none',
122
+ border: `1px solid ${brand.line}`,
123
+ borderRadius: 8,
124
+ },
125
+ },
126
+ },
127
+ MuiContainer: {
128
+ defaultProps: { maxWidth: 'lg' },
129
+ },
130
+ MuiChip: {
131
+ styleOverrides: {
132
+ root: {
133
+ fontFamily: fonts.mono,
134
+ fontWeight: 700,
135
+ letterSpacing: '0.08em',
136
+ borderRadius: 4,
137
+ },
138
+ },
139
+ },
140
+ },
141
+ };
142
+ /**
143
+ * Build a MUI theme from the canonical base, optionally layering app-specific
144
+ * overrides on top (e.g. the dashboard's dark mode + dense surfaces, or a
145
+ * tenant's custom brand colors).
146
+ */
147
+ export function createBaseTheme(overrides) {
148
+ return overrides ? createTheme(baseThemeOptions, overrides) : createTheme(baseThemeOptions);
149
+ }
150
+ /** The canonical base theme, ready to use directly (marketing site default). */
151
+ const baseTheme = createTheme(baseThemeOptions);
152
+ export default baseTheme;
@@ -0,0 +1,28 @@
1
+ export declare const brand: {
2
+ readonly paper: "#F1ECDF";
3
+ readonly paperDeep: "#E8E1CF";
4
+ readonly surface: "#FBF8F0";
5
+ readonly ink: "#15322A";
6
+ readonly inkDeep: "#0D211B";
7
+ readonly inkSoft: "#4B6258";
8
+ readonly signal: "#177A3A";
9
+ readonly signalDeep: "#125C2C";
10
+ readonly signalSoft: "#C4E8D1";
11
+ readonly signalBright: "#2FBF71";
12
+ readonly line: "rgba(21,50,42,0.16)";
13
+ readonly lineStrong: "rgba(21,50,42,0.42)";
14
+ readonly paperLine: "rgba(241,236,223,0.16)";
15
+ };
16
+ export declare const fonts: {
17
+ readonly display: "\"Bricolage Grotesque\", \"Hanken Grotesk\", system-ui, sans-serif";
18
+ readonly body: "\"Hanken Grotesk\", system-ui, -apple-system, BlinkMacSystemFont, sans-serif";
19
+ readonly mono: "\"Space Mono\", ui-monospace, \"SFMono-Regular\", Menlo, monospace";
20
+ };
21
+ export declare const fontImportUrl = "https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,600;12..96,700;12..96,800&family=Hanken+Grotesk:wght@400;500;600;700;800&family=Space+Mono:wght@400;700&display=swap";
22
+ export declare const radii: {
23
+ readonly sm: 4;
24
+ readonly md: 6;
25
+ readonly lg: 8;
26
+ };
27
+ export type Brand = typeof brand;
28
+ export type Fonts = typeof fonts;
package/dist/tokens.js ADDED
@@ -0,0 +1,40 @@
1
+ // ── Kierto design system — canonical brand tokens ─────────────────────────
2
+ // Single source of truth for every Kierto frontend (marketing site, merchant
3
+ // dashboard, tenant storefronts). Apps MUST reference these tokens rather than
4
+ // hardcoding hex values, so a brand change here propagates everywhere.
5
+ //
6
+ // kierto (fi.) = circulation / cycle. The rental loop: book → pay → pick up →
7
+ // return → repeat. Kierto exists to keep goods in circulation and spare the
8
+ // planet — so the palette is rooted in green: deep spruce ink + a single vivid
9
+ // recycle-green accent.
10
+ export const brand = {
11
+ paper: '#F1ECDF', // warm bone — page background
12
+ paperDeep: '#E8E1CF', // tinted band for section rhythm
13
+ surface: '#FBF8F0', // raised card surface
14
+ ink: '#15322A', // deep spruce — primary text & brand color
15
+ inkDeep: '#0D211B', // near-black spruce — full-bleed dark sections
16
+ inkSoft: '#4B6258', // muted spruce — secondary text
17
+ signal: '#177A3A', // recycle green — the single sharp accent (eco values). Deep
18
+ // enough to clear WCAG AA as text on the bone paper (≈4.6:1) and under white (≈5.4:1).
19
+ signalDeep: '#125C2C', // deeper green — hover / pressed (white text ≈7:1)
20
+ signalSoft: '#C4E8D1', // pale green — soft accent on dark sections
21
+ signalBright: '#2FBF71', // vivid green — accents on dark surfaces (dashboard dark mode)
22
+ line: 'rgba(21,50,42,0.16)', // hairline on paper
23
+ lineStrong: 'rgba(21,50,42,0.42)',
24
+ paperLine: 'rgba(241,236,223,0.16)', // hairline on dark
25
+ };
26
+ const displayFamily = '"Bricolage Grotesque", "Hanken Grotesk", system-ui, sans-serif';
27
+ const bodyFamily = '"Hanken Grotesk", system-ui, -apple-system, BlinkMacSystemFont, sans-serif';
28
+ const monoFamily = '"Space Mono", ui-monospace, "SFMono-Regular", Menlo, monospace';
29
+ export const fonts = {
30
+ display: displayFamily,
31
+ body: bodyFamily,
32
+ mono: monoFamily,
33
+ };
34
+ // The Google Fonts stylesheet that backs `fonts`. Every consuming app loads this
35
+ // exact URL (index.html <link> or @import) so the type system never falls back.
36
+ // Keep in sync with `fonts` above.
37
+ export const fontImportUrl = 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,600;12..96,700;12..96,800&family=Hanken+Grotesk:wght@400;500;600;700;800&family=Space+Mono:wght@400;700&display=swap';
38
+ // Corner radii used across the system. Base shape is `md`; buttons trend tighter,
39
+ // cards looser.
40
+ export const radii = { sm: 4, md: 6, lg: 8 };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@kierto/design-system",
3
+ "version": "0.1.0",
4
+ "description": "Canonical Kierto brand design system — tokens, fonts, and base MUI theme shared across every Kierto frontend.",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/PoggersOy/kierto-store.git",
9
+ "directory": "packages/design-system"
10
+ },
11
+ "type": "module",
12
+ "main": "./src/index.ts",
13
+ "types": "./src/index.ts",
14
+ "exports": {
15
+ ".": "./src/index.ts",
16
+ "./tokens": "./src/tokens.ts",
17
+ "./theme": "./src/theme.ts"
18
+ },
19
+ "files": ["src", "dist"],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "lint": "eslint . --report-unused-disable-directives --max-warnings 0",
23
+ "typecheck": "tsc --noEmit",
24
+ "build": "tsc -p tsconfig.build.json",
25
+ "prepack": "pnpm build",
26
+ "test": "vitest run"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org/",
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.js"
37
+ },
38
+ "./tokens": {
39
+ "types": "./dist/tokens.d.ts",
40
+ "default": "./dist/tokens.js"
41
+ },
42
+ "./theme": {
43
+ "types": "./dist/theme.d.ts",
44
+ "default": "./dist/theme.js"
45
+ }
46
+ }
47
+ },
48
+ "peerDependencies": {
49
+ "@emotion/react": "^11",
50
+ "@emotion/styled": "^11",
51
+ "@mui/material": ">=6 <8"
52
+ },
53
+ "devDependencies": {
54
+ "@emotion/react": "^11.13.3",
55
+ "@emotion/styled": "^11.13.0",
56
+ "@mui/material": "^7.3.11",
57
+ "typescript": "^6.0.3",
58
+ "vitest": "^4.1.9"
59
+ }
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // @kierto/design-system — canonical Kierto brand design system.
2
+ // Tokens + fonts + base MUI theme, shared across every Kierto frontend so a
3
+ // brand change here propagates automatically to all apps that consume it.
4
+ export { brand, fonts, fontImportUrl, radii, type Brand, type Fonts } from './tokens.js';
5
+ export { baseThemeOptions, createBaseTheme, default as baseTheme } from './theme.js';
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ import baseTheme, { baseThemeOptions, createBaseTheme } from './theme';
4
+ import { brand, fonts } from './tokens';
5
+
6
+ describe('@kierto/design-system base theme', () => {
7
+ it('maps the brand palette: spruce ink primary, recycle green secondary', () => {
8
+ expect(baseTheme.palette.primary.main).toBe(brand.ink);
9
+ expect(baseTheme.palette.secondary.main).toBe(brand.signal);
10
+ });
11
+
12
+ it('uses the Hanken Grotesk body font family', () => {
13
+ expect(baseTheme.typography.fontFamily).toBe(fonts.body);
14
+ expect(baseTheme.typography.fontFamily).toContain('Hanken Grotesk');
15
+ });
16
+
17
+ it('exposes base options that can be extended with app overrides', () => {
18
+ const dark = createBaseTheme({ palette: { mode: 'dark' } });
19
+ expect(dark.palette.mode).toBe('dark');
20
+ // brand secondary still flows through from the base options
21
+ expect(dark.palette.secondary.main).toBe(brand.signal);
22
+ });
23
+
24
+ it('keeps baseThemeOptions as plain options (MUI-major agnostic)', () => {
25
+ expect(baseThemeOptions.shape?.borderRadius).toBe(6);
26
+ });
27
+ });
package/src/theme.ts ADDED
@@ -0,0 +1,157 @@
1
+ import { createTheme, type Theme, type ThemeOptions } from '@mui/material/styles';
2
+
3
+ import { brand, fonts } from './tokens.js';
4
+
5
+ // ── Kierto base theme — "Nordic Field Manual" ─────────────────────────────
6
+ // Dominant ink + paper, one sharp green signal — a warm, editorial, utilitarian
7
+ // key echoing the looping arrows in the logo. This is the canonical base that
8
+ // every Kierto frontend builds on via `createTheme(baseThemeOptions, appOverrides)`.
9
+ //
10
+ // Exported as plain `ThemeOptions` (not a constructed Theme) so the package stays
11
+ // agnostic to the consumer's MUI major version: the marketing site is on MUI 6,
12
+ // the store apps on MUI 7. Each app calls its OWN `createTheme` with these options.
13
+ export const baseThemeOptions: ThemeOptions = {
14
+ palette: {
15
+ mode: 'light',
16
+ primary: {
17
+ main: brand.ink,
18
+ dark: brand.inkDeep,
19
+ light: brand.inkSoft,
20
+ contrastText: brand.paper,
21
+ },
22
+ secondary: {
23
+ main: brand.signal,
24
+ dark: brand.signalDeep,
25
+ light: brand.signalSoft,
26
+ contrastText: '#FFFFFF', // white reads on the green accent (and its darker hover)
27
+ },
28
+ background: {
29
+ default: brand.paper,
30
+ paper: brand.surface,
31
+ },
32
+ text: {
33
+ primary: brand.ink,
34
+ secondary: brand.inkSoft,
35
+ },
36
+ success: { main: '#2E7D5B' },
37
+ warning: { main: '#C77A0A' },
38
+ error: { main: '#B23A22' },
39
+ divider: brand.line,
40
+ },
41
+ shape: { borderRadius: 6 },
42
+ typography: {
43
+ fontFamily: fonts.body,
44
+ h1: {
45
+ fontFamily: fonts.display,
46
+ fontWeight: 800,
47
+ letterSpacing: '-0.035em',
48
+ lineHeight: 0.96,
49
+ fontSize: 'clamp(2.9rem, 6.2vw, 5.1rem)',
50
+ },
51
+ h2: {
52
+ fontFamily: fonts.display,
53
+ fontWeight: 800,
54
+ letterSpacing: '-0.03em',
55
+ lineHeight: 1.0,
56
+ fontSize: 'clamp(2.3rem, 4.6vw, 3.6rem)',
57
+ },
58
+ h3: {
59
+ fontFamily: fonts.display,
60
+ fontWeight: 700,
61
+ letterSpacing: '-0.025em',
62
+ lineHeight: 1.05,
63
+ fontSize: 'clamp(1.8rem, 3.4vw, 2.7rem)',
64
+ },
65
+ h4: {
66
+ fontFamily: fonts.display,
67
+ fontWeight: 700,
68
+ letterSpacing: '-0.02em',
69
+ lineHeight: 1.1,
70
+ },
71
+ h5: { fontFamily: fonts.display, fontWeight: 700, letterSpacing: '-0.015em' },
72
+ h6: { fontFamily: fonts.display, fontWeight: 700, letterSpacing: '-0.01em' },
73
+ subtitle1: { fontWeight: 600 },
74
+ subtitle2: { fontWeight: 700 },
75
+ body1: { lineHeight: 1.72 },
76
+ body2: { lineHeight: 1.7 },
77
+ button: {
78
+ fontFamily: fonts.body,
79
+ textTransform: 'none',
80
+ fontWeight: 700,
81
+ letterSpacing: '0.01em',
82
+ },
83
+ overline: {
84
+ fontFamily: fonts.mono,
85
+ fontWeight: 700,
86
+ letterSpacing: '0.2em',
87
+ textTransform: 'uppercase',
88
+ fontSize: '0.72rem',
89
+ lineHeight: 1.6,
90
+ },
91
+ },
92
+ components: {
93
+ MuiButton: {
94
+ defaultProps: { disableElevation: true },
95
+ styleOverrides: {
96
+ root: {
97
+ borderRadius: 4,
98
+ paddingInline: 20,
99
+ paddingBlock: 9,
100
+ fontWeight: 700,
101
+ },
102
+ sizeLarge: {
103
+ paddingInline: 30,
104
+ paddingBlock: 14,
105
+ fontSize: '1.02rem',
106
+ },
107
+ containedSecondary: {
108
+ '&:hover': { backgroundColor: brand.signalDeep },
109
+ },
110
+ outlined: {
111
+ borderWidth: '1.5px',
112
+ '&:hover': { borderWidth: '1.5px' },
113
+ },
114
+ },
115
+ },
116
+ MuiPaper: {
117
+ styleOverrides: { root: { backgroundImage: 'none' } },
118
+ },
119
+ MuiCard: {
120
+ defaultProps: { elevation: 0 },
121
+ styleOverrides: {
122
+ root: {
123
+ backgroundImage: 'none',
124
+ border: `1px solid ${brand.line}`,
125
+ borderRadius: 8,
126
+ },
127
+ },
128
+ },
129
+ MuiContainer: {
130
+ defaultProps: { maxWidth: 'lg' },
131
+ },
132
+ MuiChip: {
133
+ styleOverrides: {
134
+ root: {
135
+ fontFamily: fonts.mono,
136
+ fontWeight: 700,
137
+ letterSpacing: '0.08em',
138
+ borderRadius: 4,
139
+ },
140
+ },
141
+ },
142
+ },
143
+ };
144
+
145
+ /**
146
+ * Build a MUI theme from the canonical base, optionally layering app-specific
147
+ * overrides on top (e.g. the dashboard's dark mode + dense surfaces, or a
148
+ * tenant's custom brand colors).
149
+ */
150
+ export function createBaseTheme(overrides?: ThemeOptions): Theme {
151
+ return overrides ? createTheme(baseThemeOptions, overrides) : createTheme(baseThemeOptions);
152
+ }
153
+
154
+ /** The canonical base theme, ready to use directly (marketing site default). */
155
+ const baseTheme: Theme = createTheme(baseThemeOptions);
156
+
157
+ export default baseTheme;
package/src/tokens.ts ADDED
@@ -0,0 +1,48 @@
1
+ // ── Kierto design system — canonical brand tokens ─────────────────────────
2
+ // Single source of truth for every Kierto frontend (marketing site, merchant
3
+ // dashboard, tenant storefronts). Apps MUST reference these tokens rather than
4
+ // hardcoding hex values, so a brand change here propagates everywhere.
5
+ //
6
+ // kierto (fi.) = circulation / cycle. The rental loop: book → pay → pick up →
7
+ // return → repeat. Kierto exists to keep goods in circulation and spare the
8
+ // planet — so the palette is rooted in green: deep spruce ink + a single vivid
9
+ // recycle-green accent.
10
+ export const brand = {
11
+ paper: '#F1ECDF', // warm bone — page background
12
+ paperDeep: '#E8E1CF', // tinted band for section rhythm
13
+ surface: '#FBF8F0', // raised card surface
14
+ ink: '#15322A', // deep spruce — primary text & brand color
15
+ inkDeep: '#0D211B', // near-black spruce — full-bleed dark sections
16
+ inkSoft: '#4B6258', // muted spruce — secondary text
17
+ signal: '#177A3A', // recycle green — the single sharp accent (eco values). Deep
18
+ // enough to clear WCAG AA as text on the bone paper (≈4.6:1) and under white (≈5.4:1).
19
+ signalDeep: '#125C2C', // deeper green — hover / pressed (white text ≈7:1)
20
+ signalSoft: '#C4E8D1', // pale green — soft accent on dark sections
21
+ signalBright: '#2FBF71', // vivid green — accents on dark surfaces (dashboard dark mode)
22
+ line: 'rgba(21,50,42,0.16)', // hairline on paper
23
+ lineStrong: 'rgba(21,50,42,0.42)',
24
+ paperLine: 'rgba(241,236,223,0.16)', // hairline on dark
25
+ } as const;
26
+
27
+ const displayFamily = '"Bricolage Grotesque", "Hanken Grotesk", system-ui, sans-serif';
28
+ const bodyFamily = '"Hanken Grotesk", system-ui, -apple-system, BlinkMacSystemFont, sans-serif';
29
+ const monoFamily = '"Space Mono", ui-monospace, "SFMono-Regular", Menlo, monospace';
30
+
31
+ export const fonts = {
32
+ display: displayFamily,
33
+ body: bodyFamily,
34
+ mono: monoFamily,
35
+ } as const;
36
+
37
+ // The Google Fonts stylesheet that backs `fonts`. Every consuming app loads this
38
+ // exact URL (index.html <link> or @import) so the type system never falls back.
39
+ // Keep in sync with `fonts` above.
40
+ export const fontImportUrl =
41
+ 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,600;12..96,700;12..96,800&family=Hanken+Grotesk:wght@400;500;600;700;800&family=Space+Mono:wght@400;700&display=swap';
42
+
43
+ // Corner radii used across the system. Base shape is `md`; buttons trend tighter,
44
+ // cards looser.
45
+ export const radii = { sm: 4, md: 6, lg: 8 } as const;
46
+
47
+ export type Brand = typeof brand;
48
+ export type Fonts = typeof fonts;