@conduction/docusaurus-preset 1.1.1 → 1.2.1

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
@@ -11,7 +11,7 @@ Source lives inside the [design-system monorepo](https://github.com/ConductionNL
11
11
  - **Brand CSS** — tokens (cobalt palette, KNVB orange, Plex Mono, hex clip-paths, Common Ground yellow) auto-applied to Docusaurus's Infima theme variables. Navbar, footer, sidebar, buttons, code blocks all inherit the brand without a single swizzle.
12
12
  - **i18n config** — four locales pinned: NL default at the URL root, EN/DE/FR at `/en/`, `/de/`, `/fr/`. `htmlLang` per locale, `trailingSlash: true`, locale dropdown ready.
13
13
  - **Brand-default navbar** — locale-dropdown + GitHub link. Sites override `items[]` for site-specific navigation.
14
- - **Brand-default footer** — three-column link grid + Conduction-tells (KvK, BTW, address). Sites override per page or globally.
14
+ - **Brand-default footer** — three-column link grid + Conduction-tells (KvK, BTW, address). Per-property override: pass `footer: { links: [...] }` to swap columns and inherit the brand copyright unchanged. Spread `baseFooterLinks()` to keep one or two brand columns alongside site-specific ones.
15
15
  - **Sensible defaults** — `trailingSlash`, `onBrokenLinks: 'warn'`, `respectPrefersColorScheme`, dark-mode brand mapping.
16
16
 
17
17
  ## Usage
@@ -59,7 +59,7 @@ The function returns a complete Docusaurus config with brand defaults pre-applie
59
59
  | `projectName` | | `'design-system'` | GitHub repo. |
60
60
  | `i18n` | | nl / en / de / fr, NL default | Override the brand-default i18n block. |
61
61
  | `navbar` | | locale dropdown + GitHub | Merged into the brand-default navbar object. |
62
- | `footer` | | three-column link grid | Replaces the brand-default footer. |
62
+ | `footer` | | three-column link grid + KvK/BTW copyright | Per-property fallback: any of `style` / `links` / `copyright` you omit keeps the brand default. Pass `footer: { links: [...] }` to swap columns and inherit the brand copyright. |
63
63
  | `customCss` | | `[]` | Site-specific CSS, appended to `brand.css`. |
64
64
  | `presets` | | `[['classic', …]]` | Replaces the default preset list. |
65
65
  | `plugins` | | `[]` | Docusaurus plugins. |
@@ -67,7 +67,23 @@ The function returns a complete Docusaurus config with brand defaults pre-applie
67
67
  | `editUrl` | | undefined | Edit-this-doc link. |
68
68
  | `blog` | | enabled | Set `false` to disable the blog plugin. |
69
69
 
70
- Also exported: `I18N`, `baseNavbar(siteName)`, `baseFooter()` for sites that want to compose manually.
70
+ Also exported: `I18N`, `baseNavbar(siteName)`, `baseFooter()`, `baseFooterLinks()`, `baseFooterCopyright()` for sites that want to compose manually. Common pattern on a product page: pass site-specific columns plus the Conduction column from the brand default:
71
+
72
+ ```js
73
+ const { createConfig, baseFooterLinks } = require('@conduction/docusaurus-preset');
74
+
75
+ module.exports = createConfig({
76
+ // …
77
+ footer: {
78
+ links: [
79
+ { title: 'MyProduct', items: [/* …site links */] },
80
+ // Spread the brand "Conduction" column to keep the corporate anchor.
81
+ ...baseFooterLinks().filter((c) => c.title === 'Conduction'),
82
+ ],
83
+ // copyright: omitted -> brand KvK/BTW/IBAN inherits.
84
+ },
85
+ });
86
+ ```
71
87
 
72
88
  ## Brand CSS exports
73
89
 
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@conduction/docusaurus-preset",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
+ "scripts": {
5
+ "prepack": "node scripts/prepack-bundle-css.js"
6
+ },
4
7
  "description": "Conduction brand preset for Docusaurus 3. Tokens, theme, navbar, footer, i18n config for nl/en/de/fr, and the React component library that powers conduction.nl and the Conduction product sites.",
5
8
  "main": "src/index.js",
6
9
  "exports": {
@@ -8,7 +8,7 @@
8
8
  border: 1px solid var(--c-cobalt-100);
9
9
  border-radius: var(--radius-lg);
10
10
  padding: var(--space-7) var(--space-8);
11
- margin: var(--space-10) 0 var(--space-6);
11
+ margin: var(--space-12) 0 var(--space-8);
12
12
  overflow: hidden;
13
13
  font-family: var(--conduction-typography-font-family-body);
14
14
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * <Outcomes /> + <Outcome />
3
+ *
4
+ * Sibling to <Prerequisites />, intended for the *top* of academy
5
+ * tutorials. Lists the concrete things the reader will have built,
6
+ * learned, or achieved by the end of the tutorial. Goes right after
7
+ * the lede paragraph and before <Prerequisites />, so the reader
8
+ * scans:
9
+ * - "What am I going to walk away with?" (Outcomes)
10
+ * - "What do I need before I start?" (Prerequisites)
11
+ * - the actual tutorial body
12
+ *
13
+ * Visual: tinted card identical to <Prerequisites />, but each item
14
+ * uses an orange checkmark glyph (achievement) instead of the orange
15
+ * hex bullet (need). Same tone, distinct semantics.
16
+ *
17
+ * Usage:
18
+ *
19
+ * <Outcomes title="Wat je leert">
20
+ * <Outcome>Hoe je het canonieke Woo-register importeert</Outcome>
21
+ * <Outcome>Hoe je een publicatie aanmaakt en koppelt aan een TOOI-categorie</Outcome>
22
+ * <Outcome>Welke API-aanroepen hoort bij elk van de stappen</Outcome>
23
+ * </Outcomes>
24
+ *
25
+ * Mirrors the .outcomes section in
26
+ * preview/components/outcomes.html.
27
+ */
28
+
29
+ import React from 'react';
30
+ import styles from './Outcomes.module.css';
31
+
32
+ export function Outcome({children, className}) {
33
+ const composed = [styles.item, className].filter(Boolean).join(' ');
34
+ return (
35
+ <li className={composed}>
36
+ <svg className={styles.check} viewBox="0 0 24 24" aria-hidden="true"
37
+ fill="none" stroke="currentColor" strokeWidth="3"
38
+ strokeLinecap="round" strokeLinejoin="round">
39
+ <path d="M5 13l4 4L19 7"/>
40
+ </svg>
41
+ <span className={styles.body}>{children}</span>
42
+ </li>
43
+ );
44
+ }
45
+
46
+ export default function Outcomes({title = "What you'll learn", children, className}) {
47
+ const composed = [styles.card, className].filter(Boolean).join(' ');
48
+ return (
49
+ <aside className={composed}>
50
+ {title && <h2 className={styles.title}>{title}</h2>}
51
+ <ul className={styles.list}>{children}</ul>
52
+ </aside>
53
+ );
54
+ }
@@ -0,0 +1,64 @@
1
+ .card {
2
+ background: var(--c-cobalt-50);
3
+ border: 1px solid var(--c-cobalt-100);
4
+ border-radius: var(--radius-lg);
5
+ padding: var(--space-6) var(--space-7);
6
+ margin: var(--space-8) 0;
7
+ font-family: var(--conduction-typography-font-family-body);
8
+ }
9
+
10
+ .title {
11
+ font-size: 22px;
12
+ font-weight: 700;
13
+ letter-spacing: -0.01em;
14
+ color: var(--c-cobalt-900);
15
+ margin: 0 0 var(--space-4);
16
+ line-height: 1.25;
17
+ }
18
+
19
+ .list {
20
+ list-style: none;
21
+ padding: 0;
22
+ margin: 0;
23
+ display: grid;
24
+ gap: var(--space-3);
25
+ max-width: none;
26
+ }
27
+
28
+ .item {
29
+ display: grid;
30
+ grid-template-columns: 22px 1fr;
31
+ gap: var(--space-3);
32
+ align-items: start;
33
+ font-size: 16px;
34
+ line-height: 1.55;
35
+ color: var(--c-cobalt-700);
36
+ }
37
+
38
+ .check {
39
+ width: 22px;
40
+ height: 22px;
41
+ margin-top: 2px;
42
+ color: var(--c-orange-knvb);
43
+ flex-shrink: 0;
44
+ }
45
+
46
+ .body {
47
+ display: block;
48
+ }
49
+
50
+ .body a {
51
+ color: var(--c-blue-cobalt);
52
+ text-decoration: underline;
53
+ text-underline-offset: 2px;
54
+ }
55
+ .body a:hover { color: var(--c-cobalt-900); }
56
+
57
+ .body code {
58
+ background: var(--c-cobalt-100);
59
+ border-radius: 4px;
60
+ padding: 1px 6px;
61
+ font-family: var(--conduction-typography-font-family-code);
62
+ font-size: 14px;
63
+ color: var(--c-cobalt-900);
64
+ }
@@ -3,7 +3,7 @@
3
3
  border: 1px solid var(--c-cobalt-100);
4
4
  border-radius: var(--radius-lg);
5
5
  padding: var(--space-6) var(--space-7);
6
- margin: var(--space-6) 0;
6
+ margin: var(--space-10) 0;
7
7
  font-family: var(--conduction-typography-font-family-body);
8
8
  }
9
9
 
@@ -38,7 +38,7 @@
38
38
  width: 14px;
39
39
  height: 14px;
40
40
  margin-top: 6px;
41
- background: var(--c-cobalt-700);
41
+ background: var(--c-orange-knvb);
42
42
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
43
43
  flex-shrink: 0;
44
44
  }
@@ -99,6 +99,7 @@ export {default as ContentDetailHero} from './ContentDetailHero/ContentDetailHer
99
99
  "What you need", "Troubleshooting", and "Next steps" h2 + bullet
100
100
  patterns that academy tutorials kept duplicating. Designed for use
101
101
  inside an MDX academy post body. */
102
+ export {default as Outcomes, Outcome} from './Outcomes/Outcomes.jsx';
102
103
  export {default as Prerequisites, PrerequisiteItem} from './Prerequisites/Prerequisites.jsx';
103
104
  export {default as Troubleshooting, TroubleshootingItem} from './Troubleshooting/Troubleshooting.jsx';
104
105
  export {default as NextSteps, NextStep} from './NextSteps/NextSteps.jsx';
@@ -1,12 +1,448 @@
1
- /**
2
- * Conduction brand tokens, mirrored for Docusaurus consumers.
1
+ /* AUTO-GENERATED by docusaurus-preset/scripts/prepack-bundle-css.js
2
+ * during `npm publish`. Bundles the design-system monorepo's canonical
3
+ * tokens.css + typography.css into a self-contained file so consumer
4
+ * sites can resolve the imports through node_modules.
3
5
  *
4
- * Re-exports the canonical tokens from ../../tokens.css so a Docusaurus
5
- * site picking up the preset gets the same palette, typography, and hex
6
- * clip-paths as the design-system kit.
7
- *
8
- * When the preset is published to npm, this file becomes the bundled
9
- * copy; until then it imports relatively from the workspace.
6
+ * Do NOT edit this file in a published version. The working-tree
7
+ * src/css/tokens.css keeps the relative-import version that points
8
+ * back at the monorepo's source of truth.
10
9
  */
11
- @import url("../../../tokens.css");
12
- @import url("../../../typography.css");
10
+
11
+ /* ============================================================
12
+ Conduction Design System — theme-conduction-2026
13
+ Generated from brand/tokens.json (DTCG)
14
+ ============================================================ */
15
+
16
+ /* Fonts: Figtree (body+heading), IBM Plex Mono (code) — both OFL */
17
+ @import url("https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap");
18
+
19
+ :root {
20
+ /* ---------- Color primitives ---------- */
21
+ --c-blue-cobalt: #21468B; /* Dutch flag blue — primary brand */
22
+ --c-orange-knvb: #F36C21; /* KNVB orange — accent */
23
+ --c-red-vermillion: #AE1C28; /* Dutch flag red — sparingly */
24
+ --c-white: #FFFFFF;
25
+ --c-nextcloud-blue: #0082C9; /* Nextcloud official */
26
+ --c-nextcloud-cyan: #1CAFFF; /* Nextcloud gradient end */
27
+ --gradient-nextcloud: linear-gradient(45deg, #0082C9, #1CAFFF);
28
+ --c-commonground-yellow: #F6AD00; /* Common Ground official, commonground.nl */
29
+
30
+ /* Cobalt tints — derived for surfaces, hex-prism faces, soft fills */
31
+ --c-cobalt-50: #EEF2F8;
32
+ --c-cobalt-100: #DCE3F0;
33
+ --c-cobalt-200: #B6C2DD;
34
+ --c-cobalt-300: #8095BD;
35
+ --c-cobalt-400: #4D69A4;
36
+ --c-cobalt-500: #21468B; /* = primary */
37
+ --c-cobalt-600: #1B3A75;
38
+ --c-cobalt-700: #152D5C;
39
+ --c-cobalt-800: #102246;
40
+ --c-cobalt-900: #0A172F;
41
+
42
+ /* Pastel families for hex-prism categories (per visual-motifs.md)
43
+ Each family has FIVE roles for prism construction:
44
+ -50 ultra-light wash (ground / page bg accents)
45
+ -100 top-face fill (the "lit" face you see in plan view)
46
+ -300 left-face fill (the slightly shaded vertical face)
47
+ -500 right-face / stroke (the deeper vertical face + outlines)
48
+ -700 ink — text on prism, pill outline, chip text
49
+
50
+ PRISM-FAMILY POLICY (locked 2026-04):
51
+ Component prisms get ONE of: lavender, mint, forest, terracotta.
52
+ The workspace prism uses workspace-blue (= Nextcloud's own brand blue).
53
+ Cobalt is brand chrome, NOT a prism family.
54
+ "Kernel" is a banned word in the brand vocabulary (see
55
+ identity/voice.html); use "workspace" everywhere.
56
+ Coral, gold, gray are NOT prism families. They serve different jobs:
57
+ · coral = KNVB-orange accent (focus, hover, single-use highlights). ≤ 8 % page area.
58
+ · gold = "Conduction Certified" mark ONLY. Never on a generic prism.
59
+ · gray = neutral surfaces / strokes. Not a category.
60
+ */
61
+
62
+ /* — Component family 1: lavender (process / workflow) — */
63
+ --c-lavender-50: #F6F2FC;
64
+ --c-lavender-100: #ECE6F8;
65
+ --c-lavender-300: #B7A7E3;
66
+ --c-lavender-500: #7E66C9;
67
+ --c-lavender-700: #483982;
68
+
69
+ /* — Component family 2: mint (integrate / connect) — */
70
+ --c-mint-50: #EAF7F0;
71
+ --c-mint-100: #DCF1E6;
72
+ --c-mint-300: #87CFA8;
73
+ --c-mint-500: #2E9866;
74
+ --c-mint-700: #155234;
75
+
76
+ /* — Component family 3: forest (data / trustworthy / NLDS-compliant)
77
+ Distinct from mint — deeper, denser, more "official." Use
78
+ this for prisms that handle data and registers. — */
79
+ --c-forest-50: #EEF5EE;
80
+ --c-forest-100: #D7E8D6;
81
+ --c-forest-300: #7DAA7C;
82
+ --c-forest-500: #3D7C3A;
83
+ --c-forest-700: #1E461C;
84
+
85
+ /* — Component family 4: terracotta (documents / human work)
86
+ Tuned around the brand vermillion (#AE1C28) as its 700-deep,
87
+ so it ties to the Dutch flag without the "alarm" of pure red. — */
88
+ --c-terracotta-50: #FBF1EE;
89
+ --c-terracotta-100: #F4DCD3;
90
+ --c-terracotta-300: #DA9D8A;
91
+ --c-terracotta-500: #B25E48;
92
+ --c-terracotta-700: #6E2A1C;
93
+
94
+ /* — Workspace family: workspace-blue (= Nextcloud's own brand blue tints) — */
95
+ --c-workspaceblue-50: #E5F2FA;
96
+ --c-workspaceblue-100: #C8E5F5;
97
+ --c-workspaceblue-300: #67BEEA;
98
+ --c-workspaceblue-500: #0082C9; /* Nextcloud blue */
99
+ --c-workspaceblue-700: #014C77;
100
+
101
+ /* ----- Reserved colors (NOT prism families) -----
102
+ Kept in tokens because they have specific jobs elsewhere. */
103
+
104
+ /* Coral = KNVB orange accent only — focus rings, hover, occasional highlight. */
105
+ --c-coral-50: #FFF3ED;
106
+ --c-coral-100: #FFE4DA;
107
+ --c-coral-300: #FAB29C;
108
+ --c-coral-500: #F36C21; /* = KNVB */
109
+ --c-coral-700: #9B3A0E;
110
+
111
+ /* Gold = Conduction Certified mark ONLY. Reserved for the cert avatar
112
+ and trustmark badges. Do not use as a generic family color. */
113
+ --c-gold-50: #FDF8E8;
114
+ --c-gold-100: #FBF1D5;
115
+ --c-gold-300: #ECC668;
116
+ --c-gold-500: #C99A1F;
117
+ --c-gold-700: #765806;
118
+
119
+ /* Gray = neutral surfaces, strokes, side-box chrome. Not a category. */
120
+ --c-gray-50: #F4F5F8;
121
+ --c-gray-100: #ECEEF2;
122
+ --c-gray-300: #B7BDC9;
123
+ --c-gray-500: #6B7280;
124
+ --c-gray-700: #3A4150;
125
+
126
+ /* Workspace family alias — points to workspace-blue (Nextcloud).
127
+ The hex-prism API stays uniform: --c-workspace-{50,100,300,500,700} */
128
+ --c-workspace-50: var(--c-workspaceblue-50);
129
+ --c-workspace-100: var(--c-workspaceblue-100);
130
+ --c-workspace-300: var(--c-workspaceblue-300);
131
+ --c-workspace-500: var(--c-workspaceblue-500);
132
+ --c-workspace-700: var(--c-workspaceblue-700);
133
+
134
+ /* ---------- Semantic theme.conduction-2026 ---------- */
135
+ /* Brand */
136
+ --conduction-color-brand-primary: var(--c-blue-cobalt);
137
+ --conduction-color-brand-secondary: var(--c-orange-knvb);
138
+ --conduction-color-brand-tertiary: var(--c-red-vermillion);
139
+ --conduction-color-brand-nextcloud: var(--c-nextcloud-blue);
140
+ --conduction-color-brand-commonground: var(--c-commonground-yellow);
141
+
142
+ /* Background */
143
+ --conduction-color-background-default: var(--c-white);
144
+ --conduction-color-background-inverse: var(--c-blue-cobalt);
145
+ --conduction-color-background-muted: var(--c-cobalt-50);
146
+
147
+ /* Text */
148
+ --conduction-color-text-default: var(--c-blue-cobalt);
149
+ --conduction-color-text-inverse: var(--c-white);
150
+ --conduction-color-text-accent: var(--c-orange-knvb);
151
+ --conduction-color-text-muted: var(--c-cobalt-400);
152
+
153
+ /* Link */
154
+ --conduction-color-link-default: var(--c-blue-cobalt);
155
+ --conduction-color-link-hover: var(--c-orange-knvb);
156
+
157
+ /* Status */
158
+ --conduction-color-status-error: var(--c-red-vermillion);
159
+
160
+ /* Border */
161
+ --conduction-color-border-default: var(--c-cobalt-200);
162
+ --conduction-color-border-strong: var(--c-blue-cobalt);
163
+
164
+ /* ---------- Typography primitives ---------- */
165
+ --conduction-typography-font-family-body: 'Figtree', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
166
+ --conduction-typography-font-family-heading: 'Figtree', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
167
+ --conduction-typography-font-family-code: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
168
+
169
+ --fw-regular: 400;
170
+ --fw-medium: 500;
171
+ --fw-semibold: 600;
172
+ --fw-bold: 700;
173
+
174
+ --fs-xs: 12px;
175
+ --fs-sm: 14px;
176
+ --fs-base: 16px;
177
+ --fs-lg: 18px;
178
+ --fs-xl: 20px;
179
+ --fs-2xl: 24px;
180
+ --fs-3xl: 32px;
181
+ --fs-4xl: 40px;
182
+ --fs-5xl: 48px;
183
+
184
+ --lh-tight: 1.2;
185
+ --lh-normal: 1.5;
186
+ --lh-relaxed: 1.75;
187
+
188
+ /* ---------- Spacing (proposed scope-B) ---------- */
189
+ --space-1: 4px;
190
+ --space-2: 8px;
191
+ --space-3: 12px;
192
+ --space-4: 16px;
193
+ --space-5: 20px;
194
+ --space-6: 24px;
195
+ --space-8: 32px;
196
+ --space-10: 40px;
197
+ --space-12: 48px;
198
+ --space-16: 64px;
199
+ --space-20: 80px;
200
+ --space-24: 96px;
201
+
202
+ /* ---------- Radii ---------- */
203
+ --radius-sm: 4px;
204
+ --radius-md: 6px; /* default for buttons/inputs */
205
+ --radius-lg: 10px;
206
+ --radius-xl: 16px;
207
+ --radius-pill: 999px;
208
+
209
+ /* ---------- Shadows (subtle, brand-quiet) ---------- */
210
+ --shadow-1: 0 1px 2px rgba(33, 70, 139, 0.08);
211
+ --shadow-2: 0 2px 8px rgba(33, 70, 139, 0.10);
212
+ --shadow-3: 0 8px 24px rgba(33, 70, 139, 0.12);
213
+
214
+ /* ---------- Hex motif clip-paths ---------- */
215
+ --hex-pointy-top: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
216
+
217
+ /* ---------- Cut-corner clip-paths ---------- */
218
+ /* Triangular bevels mimic the hex motif on rectangles —
219
+ use these instead of (or in addition to) border-radius for
220
+ panels, cards, hero containers, modals. Use the size-specific
221
+ vars below; the polygon strings are inlined per size because
222
+ polygon() does not re-resolve var() at use time. */
223
+ --cut-sm: 6px;
224
+ --cut-md: 10px;
225
+ --cut-lg: 16px;
226
+ --cut-xl: 24px;
227
+
228
+ /* Top-left + bottom-right cut — diagonal asymmetry, our default */
229
+ --clip-cut-tlbr-sm: polygon( 6px 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%, 0 6px);
230
+ --clip-cut-tlbr-md: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
231
+ --clip-cut-tlbr-lg: polygon(16px 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%, 0 100%, 0 16px);
232
+ --clip-cut-tlbr-xl: polygon(24px 0, 100% 0, 100% calc(100% - 24px), calc(100% - 24px) 100%, 0 100%, 0 24px);
233
+ --clip-cut-tlbr: var(--clip-cut-tlbr-md);
234
+
235
+ /* Top-right + bottom-left cut — mirror */
236
+ --clip-cut-trbl-sm: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 6px 100%, 0 calc(100% - 6px));
237
+ --clip-cut-trbl-md: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
238
+ --clip-cut-trbl-lg: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px));
239
+ --clip-cut-trbl-xl: polygon(0 0, calc(100% - 24px) 0, 100% 24px, 100% 100%, 24px 100%, 0 calc(100% - 24px));
240
+ --clip-cut-trbl: var(--clip-cut-trbl-md);
241
+
242
+ /* All four corners cut */
243
+ --clip-cut-all-sm: polygon( 6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px), calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px);
244
+ --clip-cut-all-md: polygon(10px 0, calc(100% - 10px) 0, 100% 10px, 100% calc(100% - 10px), calc(100% - 10px) 100%, 10px 100%, 0 calc(100% - 10px), 0 10px);
245
+ --clip-cut-all-lg: polygon(16px 0, calc(100% - 16px) 0, 100% 16px, 100% calc(100% - 16px), calc(100% - 16px) 100%, 16px 100%, 0 calc(100% - 16px), 0 16px);
246
+ --clip-cut-all-xl: polygon(24px 0, calc(100% - 24px) 0, 100% 24px, 100% calc(100% - 24px), calc(100% - 24px) 100%, 24px 100%, 0 calc(100% - 24px), 0 24px);
247
+ --clip-cut-all: var(--clip-cut-all-md);
248
+
249
+ /* ============================================================
250
+ Print + paper sizes
251
+ ----------------------------------------------------------------
252
+ Real-world dimensions in millimetres. The HTML preview renders
253
+ these as on-screen mock-ups; the print stylesheet at the bottom
254
+ of each artefact maps them to @page rules. No bleed yet —
255
+ --bleed is plumbed at 0 and will flip to 3mm when we move to
256
+ real print runs. Don't bake bleed into individual layouts;
257
+ reference the token. */
258
+
259
+ /* Paper formats — width / height */
260
+ --paper-a4-w: 210mm;
261
+ --paper-a4-h: 297mm;
262
+ --paper-a5-w: 148mm;
263
+ --paper-a5-h: 210mm;
264
+ --paper-a3-w: 297mm;
265
+ --paper-a3-h: 420mm;
266
+
267
+ /* Business card — Dutch / European standard */
268
+ --card-w: 85mm;
269
+ --card-h: 55mm;
270
+
271
+ /* Envelopes */
272
+ --env-c5-w: 229mm;
273
+ --env-c5-h: 162mm;
274
+ --env-c4-w: 324mm;
275
+ --env-c4-h: 229mm;
276
+ --env-dl-w: 220mm;
277
+ --env-dl-h: 110mm;
278
+
279
+ /* Slide deck — 16:9 landscape, 1280×720 nominal at 96dpi */
280
+ --slide-w: 338.67mm; /* 1280px → mm */
281
+ --slide-h: 190.5mm; /* 720px → mm */
282
+ --slide-w-px: 1280px;
283
+ --slide-h-px: 720px;
284
+
285
+ /* Banner / signage — placed on screen, scale to real-world print later */
286
+ --banner-rollup-w: 850mm; /* 85cm × 200cm pull-up */
287
+ --banner-rollup-h: 2000mm;
288
+ --banner-event-w: 3000mm; /* 3m × 2m event backdrop */
289
+ --banner-event-h: 2000mm;
290
+
291
+ /* Web banners — pixel-native, kept here for one source of truth */
292
+ --banner-linkedin-w: 1584px;
293
+ --banner-linkedin-h: 396px;
294
+ --banner-x-w: 1500px;
295
+ --banner-x-h: 500px;
296
+ --banner-fb-w: 820px;
297
+ --banner-fb-h: 312px;
298
+
299
+ /* mm-based spacing for paper layouts (4mm step) */
300
+ --space-mm-1: 2mm;
301
+ --space-mm-2: 4mm;
302
+ --space-mm-3: 6mm;
303
+ --space-mm-4: 8mm;
304
+ --space-mm-5: 10mm;
305
+ --space-mm-6: 12mm;
306
+ --space-mm-8: 16mm;
307
+ --space-mm-10: 20mm;
308
+ --space-mm-12: 24mm;
309
+
310
+ /* Bleed — 0 on screen, flip to 3mm when sending to a real printer.
311
+ Keep this token referenced from artefact stylesheets (not hard-
312
+ coded) so the change is one place. */
313
+ --bleed: 0mm;
314
+ }
315
+
316
+ /* ============================================================
317
+ Base typography
318
+ ============================================================ */
319
+ html { font-family: var(--conduction-typography-font-family-body); }
320
+ body {
321
+ font-family: var(--conduction-typography-font-family-body);
322
+ font-size: var(--fs-base);
323
+ line-height: var(--lh-normal);
324
+ color: var(--conduction-color-text-default);
325
+ background: var(--conduction-color-background-default);
326
+ -webkit-font-smoothing: antialiased;
327
+ text-rendering: optimizeLegibility;
328
+ }
329
+
330
+ h1, h2, h3, h4, h5, h6 {
331
+ font-family: var(--conduction-typography-font-family-heading);
332
+ color: var(--conduction-color-text-default);
333
+ line-height: var(--lh-tight);
334
+ margin: 0;
335
+ text-wrap: balance;
336
+ }
337
+ h1 { font-size: var(--fs-5xl); font-weight: var(--fw-bold); letter-spacing: -0.02em; }
338
+ h2 { font-size: var(--fs-4xl); font-weight: var(--fw-bold); letter-spacing: -0.015em; }
339
+ h3 { font-size: var(--fs-3xl); font-weight: var(--fw-semibold); letter-spacing: -0.01em; }
340
+ h4 { font-size: var(--fs-2xl); font-weight: var(--fw-semibold); }
341
+ h5 { font-size: var(--fs-xl); font-weight: var(--fw-semibold); }
342
+ h6 { font-size: var(--fs-lg); font-weight: var(--fw-semibold); }
343
+
344
+ p { margin: 0; text-wrap: pretty; }
345
+
346
+ code, kbd, samp, pre {
347
+ font-family: var(--conduction-typography-font-family-code);
348
+ font-size: 0.92em;
349
+ }
350
+
351
+ a {
352
+ color: var(--conduction-color-link-default);
353
+ text-decoration: underline;
354
+ text-decoration-thickness: 1px;
355
+ text-underline-offset: 2px;
356
+ transition: color 120ms ease;
357
+ }
358
+ a:hover { color: var(--conduction-color-link-hover); }
359
+
360
+ /* ============================================================
361
+ Brand-citation: "Next" in Nextcloud-blue
362
+ ============================================================ */
363
+ .next-blue { color: var(--conduction-color-brand-nextcloud); }
364
+ .cg-yellow { color: var(--conduction-color-brand-commonground); }
365
+
366
+ /* ============================================================
367
+ Hex-pattern background utility
368
+ ----------------------------------------------------------------
369
+ A subtle pointy-top hexagon lattice for footers, section dividers,
370
+ empty states. The lattice itself is an inline SVG data-URI, tiled.
371
+ Use the convenience classes below or set the custom properties
372
+ directly on any element:
373
+
374
+ .hex-pattern default cobalt-100 lines @ ~6% opacity
375
+ .hex-pattern--soft barely-there (4%) — for footers
376
+ .hex-pattern--bold visible (12%) — for empty states / dividers
377
+
378
+ Override:
379
+ style="--hex-pattern-color: #21468B; --hex-pattern-opacity: 0.08;"
380
+
381
+ The pattern repeats at 56px, which matches our spacing rhythm
382
+ (multiple of --space-4 / --space-8). Don't go below 4% opacity or
383
+ the texture disappears at small sizes; don't go above 14% or it
384
+ fights the foreground.
385
+ ============================================================ */
386
+ .hex-pattern,
387
+ .hex-pattern--soft,
388
+ .hex-pattern--bold {
389
+ --hex-pattern-color: var(--c-blue-cobalt);
390
+ --hex-pattern-opacity: 0.06;
391
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='56' height='64' viewBox='0 0 56 64'><g fill='none' stroke='%2321468B' stroke-width='1'><path d='M28 1 L55 16 L55 48 L28 63 L1 48 L1 16 Z'/><path d='M0 32 L1 32 M55 32 L56 32'/></g></svg>");
392
+ background-size: 56px 64px;
393
+ background-repeat: repeat;
394
+ /* layer the color tint via a mask: easier — lower the whole bg's opacity */
395
+ background-blend-mode: multiply;
396
+ position: relative;
397
+ }
398
+ .hex-pattern::before,
399
+ .hex-pattern--soft::before,
400
+ .hex-pattern--bold::before {
401
+ content: "";
402
+ position: absolute;
403
+ inset: 0;
404
+ pointer-events: none;
405
+ background-image: inherit;
406
+ background-size: inherit;
407
+ background-repeat: inherit;
408
+ opacity: var(--hex-pattern-opacity);
409
+ }
410
+ .hex-pattern--soft { --hex-pattern-opacity: 0.04; }
411
+ .hex-pattern--bold { --hex-pattern-opacity: 0.12; }
412
+ /* When applied to a container, the pseudo-element handles the texture
413
+ so foreground content doesn't get tinted. Make children stack above: */
414
+ .hex-pattern > *,
415
+ .hex-pattern--soft > *,
416
+ .hex-pattern--bold > * { position: relative; z-index: 1; }
417
+ /* Strip the base-image (we paint via ::before only) so blend-mode
418
+ doesn't double up: */
419
+ .hex-pattern, .hex-pattern--soft, .hex-pattern--bold {
420
+ background-image: none;
421
+ background-blend-mode: normal;
422
+ }
423
+
424
+ /* Typography & colour defaults, applied via the semantic tokens.
425
+ Pages link this alongside tokens.css. Per-page CSS only overrides
426
+ when a surface needs something other than the brand default
427
+ (e.g. white text on a cobalt panel). */
428
+
429
+ @import url("./tokens.css");
430
+
431
+ body {
432
+ color: var(--conduction-color-text-default);
433
+ font-family: var(--conduction-typography-font-family-body);
434
+ -webkit-font-smoothing: antialiased;
435
+ text-rendering: optimizeLegibility;
436
+ }
437
+
438
+ h1, h2, h3, h4, h5, h6 {
439
+ color: var(--conduction-color-text-default);
440
+ font-family: var(--conduction-typography-font-family-heading);
441
+ margin: 0;
442
+ }
443
+
444
+ a {
445
+ color: var(--conduction-color-link-default);
446
+ transition: color 140ms ease;
447
+ }
448
+ a:hover { color: var(--conduction-color-link-hover); }
package/src/index.js CHANGED
@@ -54,39 +54,64 @@ const baseNavbar = (siteName) => ({
54
54
  });
55
55
 
56
56
  /**
57
- * Brand-default footer. Cobalt-900 panel with three-column link grid +
58
- * the standard Conduction tells (KvK, BTW, EUPL/MIT licence).
57
+ * Brand-default footer columns (the "links" array).
58
+ *
59
+ * Three corporate columns rendered on the cobalt-900 footer panel.
60
+ * Sites override these wholesale by passing `footer.links` to
61
+ * createConfig(); a site that wants to keep just one brand column
62
+ * (e.g. only "Conduction" on a product-page footer) can spread the
63
+ * filtered output of this function into their own array. The other
64
+ * footer fields (style, copyright) keep their brand defaults
65
+ * independently.
66
+ */
67
+ const baseFooterLinks = () => [
68
+ {
69
+ title: 'Product',
70
+ items: [
71
+ { label: 'ConNext', href: 'https://conduction.nl/connext' },
72
+ { label: 'Common Ground', href: 'https://conduction.nl/commonground' },
73
+ { label: 'App store', href: 'https://apps.conduction.nl/' },
74
+ { label: 'Common Ground hosting', href: 'https://commonground.nu/' },
75
+ ],
76
+ },
77
+ {
78
+ title: 'Conduction',
79
+ items: [
80
+ { label: 'About', href: 'https://conduction.nl/about' },
81
+ { label: 'Open source', href: 'https://conduction.nl/about#opensource' },
82
+ { label: 'Team', href: 'https://conduction.nl/about#team' },
83
+ { label: 'ISO', href: 'https://conduction.nl/iso' },
84
+ ],
85
+ },
86
+ {
87
+ title: 'Documentatie',
88
+ items: [
89
+ { label: 'Brand book', href: 'https://connext.conduction.nl/' },
90
+ { label: 'Diagram set', href: 'https://connext.conduction.nl/diagrams/' },
91
+ ],
92
+ },
93
+ ];
94
+
95
+ /**
96
+ * Brand-default footer copyright string. KvK + BTW + IBAN + address are
97
+ * the legal anchor that every Conduction-branded site needs to render
98
+ * regardless of which product-specific columns the footer shows above
99
+ * it. Sites override this only for non-Conduction surfaces (rare); on
100
+ * product pages it inherits unchanged.
101
+ */
102
+ const baseFooterCopyright = () =>
103
+ `Conduction B.V. · KvK 76741850 · BTW NL860784241B01 · IBAN NL51 ABNA 0868951550 · Lauriergracht 14h, 1016 RR Amsterdam · ${new Date().getFullYear()}`;
104
+
105
+ /**
106
+ * Brand-default footer (full object). Backward-compatible shim for
107
+ * older sites that called `baseFooter()` directly; new callers should
108
+ * compose with `baseFooterLinks` / `baseFooterCopyright` so per-property
109
+ * overrides keep working.
59
110
  */
60
111
  const baseFooter = () => ({
61
112
  style: 'dark',
62
- links: [
63
- {
64
- title: 'Product',
65
- items: [
66
- { label: 'ConNext', href: 'https://conduction.nl/connext' },
67
- { label: 'Common Ground', href: 'https://conduction.nl/commonground' },
68
- { label: 'App store', href: 'https://apps.conduction.nl/' },
69
- { label: 'Common Ground hosting', href: 'https://commonground.nu/' },
70
- ],
71
- },
72
- {
73
- title: 'Conduction',
74
- items: [
75
- { label: 'About', href: 'https://conduction.nl/about' },
76
- { label: 'Open source', href: 'https://conduction.nl/about#opensource' },
77
- { label: 'Team', href: 'https://conduction.nl/about#team' },
78
- { label: 'ISO', href: 'https://conduction.nl/iso' },
79
- ],
80
- },
81
- {
82
- title: 'Documentatie',
83
- items: [
84
- { label: 'Brand book', href: 'https://connext.conduction.nl/' },
85
- { label: 'Diagram set', href: 'https://connext.conduction.nl/diagrams/' },
86
- ],
87
- },
88
- ],
89
- copyright: `Conduction B.V. · KvK 76741850 · BTW NL860784241B01 · IBAN NL51 ABNA 0868951550 · Lauriergracht 14h, 1016 RR Amsterdam · ${new Date().getFullYear()}`,
113
+ links: baseFooterLinks(),
114
+ copyright: baseFooterCopyright(),
90
115
  });
91
116
 
92
117
  /**
@@ -97,8 +122,11 @@ const baseFooter = () => ({
97
122
  *
98
123
  * Optional:
99
124
  * organizationName, projectName, navbar (deep-merged into base),
100
- * footer (replaces base), customCss[] (appended to brand.css),
101
- * plugins[], presets, i18n (overrides defaults)
125
+ * footer (per-property fallback: any of style/links/copyright the
126
+ * site omits keeps its brand default — pass `footer: { links: [...] }`
127
+ * to swap columns while inheriting the KvK/BTW copyright),
128
+ * customCss[] (appended to brand.css), plugins[], presets,
129
+ * i18n (overrides defaults)
102
130
  */
103
131
  function createConfig(opts) {
104
132
  if (!opts || !opts.title || !opts.url) {
@@ -171,7 +199,19 @@ function createConfig(opts) {
171
199
  respectPrefersColorScheme: true,
172
200
  },
173
201
  navbar: Object.assign(baseNavbar(opts.title), opts.navbar || {}),
174
- footer: opts.footer || baseFooter(),
202
+ /* Per-property fallback so a site can override one slice of the
203
+ footer (e.g. just `links`) and inherit the rest from the brand.
204
+ Previously `opts.footer` replaced the whole footer wholesale,
205
+ which forced sites to copy the KvK/BTW copyright string just to
206
+ swap columns. */
207
+ footer: (() => {
208
+ const f = opts.footer || {};
209
+ return {
210
+ style: f.style || 'dark',
211
+ links: f.links || baseFooterLinks(),
212
+ copyright: f.copyright || baseFooterCopyright(),
213
+ };
214
+ })(),
175
215
  },
176
216
  opts.themeConfig || {}
177
217
  ),
@@ -180,4 +220,11 @@ function createConfig(opts) {
180
220
  };
181
221
  }
182
222
 
183
- module.exports = { createConfig, I18N, baseNavbar, baseFooter };
223
+ module.exports = {
224
+ createConfig,
225
+ I18N,
226
+ baseNavbar,
227
+ baseFooter,
228
+ baseFooterLinks,
229
+ baseFooterCopyright,
230
+ };