@conduction/docusaurus-preset 2.0.0 → 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/docusaurus-preset",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "scripts": {
5
5
  "prepack": "node scripts/prepack-bundle-css.js"
6
6
  },
@@ -69,18 +69,22 @@ export default function AppMock({app, size = 'md', caption = false, className})
69
69
  const variant = VARIANTS[app];
70
70
  if (!variant) {
71
71
  return (
72
- <div className={[styles.frame, styles[`size-${size}`], className].filter(Boolean).join(' ')}>
73
- <div className={styles.empty}>Unknown app: {app}</div>
72
+ <div className={styles.am}>
73
+ <div className={[styles.frame, styles[`size-${size}`], className].filter(Boolean).join(' ')}>
74
+ <div className={styles.empty}>Unknown app: {app}</div>
75
+ </div>
74
76
  </div>
75
77
  );
76
78
  }
77
79
  const {Component, label} = variant;
78
80
  return (
79
- <figure className={[styles.figure, className].filter(Boolean).join(' ')}>
80
- <div className={[styles.frame, styles[`size-${size}`]].filter(Boolean).join(' ')}>
81
- <Component />
82
- </div>
83
- {caption && <figcaption className={styles.caption}>{label}</figcaption>}
84
- </figure>
81
+ <div className={styles.am}>
82
+ <figure className={[styles.figure, className].filter(Boolean).join(' ')}>
83
+ <div className={[styles.frame, styles[`size-${size}`]].filter(Boolean).join(' ')}>
84
+ <Component />
85
+ </div>
86
+ {caption && <figcaption className={styles.caption}>{label}</figcaption>}
87
+ </figure>
88
+ </div>
85
89
  );
86
90
  }
@@ -1,6 +1,19 @@
1
1
  /**
2
2
  * <AppMock /> styles.
3
3
  *
4
+ * Every rule below is scoped under a `.am` parent. AppMock content
5
+ * has to be wrapped in `<div className={styles.am}>` (React) or
6
+ * `<div class="am">` (static HTML) for any of these styles to take
7
+ * effect. The scope wrapper is `display: contents` so it doesn't
8
+ * affect layout — it exists only to namespace the very generic
9
+ * class names (.frame, .topbar, .col, .nav, .grid, .head, .item,
10
+ * .row, etc.) that would otherwise collide with anything else on
11
+ * the host page when the file is loaded as a global stylesheet by
12
+ * the design-system kit's preview pages. Webpack CSS Modules sees
13
+ * `.am` and `.frame` as two locally-scoped classes; the static-HTML
14
+ * consumer sees the literal `.am .frame` selector and only matches
15
+ * when the parent wrapper is present.
16
+ *
4
17
  * One outer .frame, one optional .topbar, then a per-app .body.
5
18
  * All variants share the same atom toolkit — .nav, .col, .panel, .pill,
6
19
  * .row, .kpi, .icon, .stack — so a future variant assembles in JSX
@@ -9,9 +22,14 @@
9
22
  * Tokens only; no images. One orange accent per variant max.
10
23
  */
11
24
 
12
- .figure { margin: 0; display: inline-flex; flex-direction: column; gap: var(--space-2); width: 100%; max-width: 100%; }
25
+ /* Scope wrapper display:contents keeps it transparent to layout.
26
+ Children act as direct children of the parent for flex/grid/block
27
+ purposes; selectors still match through the DOM. */
28
+ .am { display: contents; }
29
+
30
+ .am .figure { margin: 0; display: inline-flex; flex-direction: column; gap: var(--space-2); width: 100%; max-width: 100%; }
13
31
 
14
- .frame {
32
+ .am .frame {
15
33
  width: 100%;
16
34
  max-width: 720px;
17
35
  aspect-ratio: 16 / 10;
@@ -25,11 +43,11 @@
25
43
  font-family: var(--conduction-typography-font-family-body);
26
44
  position: relative;
27
45
  }
28
- .size-sm { max-width: 480px; }
29
- .size-md { max-width: 720px; }
30
- .size-lg { max-width: 960px; }
46
+ .am .size-sm { max-width: 480px; }
47
+ .am .size-md { max-width: 720px; }
48
+ .am .size-lg { max-width: 960px; }
31
49
 
32
- .caption {
50
+ .am .caption {
33
51
  font-family: var(--conduction-typography-font-family-code);
34
52
  font-size: 11px;
35
53
  letter-spacing: 0.06em;
@@ -38,7 +56,7 @@
38
56
  text-align: center;
39
57
  }
40
58
 
41
- .empty {
59
+ .am .empty {
42
60
  display: flex;
43
61
  align-items: center;
44
62
  justify-content: center;
@@ -53,7 +71,7 @@
53
71
 
54
72
  /* Top bar — Nextcloud-style row of small icons + spotlight + bell + avatar.
55
73
  Used by mydash + the three admin variants below. */
56
- .topbar {
74
+ .am .topbar {
57
75
  display: flex;
58
76
  align-items: center;
59
77
  gap: 6px;
@@ -62,22 +80,22 @@
62
80
  padding: 0 10px;
63
81
  flex-shrink: 0;
64
82
  }
65
- .topbar .logo { width: 16px; height: 12px; border-radius: 6px; background: white; }
66
- .topbar .icon { width: 8px; height: 8px; border-radius: 1px; background: rgba(255,255,255,0.7); }
67
- .topbar .spacer { flex: 1; }
68
- .topbar .pill { width: 12px; height: 8px; border-radius: 4px; background: rgba(255,255,255,0.4); }
69
- .topbar .bell { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,0.7); }
70
- .topbar .avatar { width: 10px; height: 10px; border-radius: 50%; background: white; }
83
+ .am .topbar .logo { width: 16px; height: 12px; border-radius: 6px; background: white; }
84
+ .am .topbar .icon { width: 8px; height: 8px; border-radius: 1px; background: rgba(255,255,255,0.7); }
85
+ .am .topbar .spacer { flex: 1; }
86
+ .am .topbar .pill { width: 12px; height: 8px; border-radius: 4px; background: rgba(255,255,255,0.4); }
87
+ .am .topbar .bell { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,0.7); }
88
+ .am .topbar .avatar { width: 10px; height: 10px; border-radius: 50%; background: white; }
71
89
 
72
90
  /* Body wrapper — per-variant content fills this. */
73
- .body { flex: 1; display: flex; min-height: 0; min-width: 0; overflow: hidden; }
91
+ .am .body { flex: 1; display: flex; min-height: 0; min-width: 0; overflow: hidden; }
74
92
 
75
93
  /* ======================================================================
76
94
  Atom toolkit — variants compose from these
77
95
  ====================================================================== */
78
96
 
79
97
  /* Vertical sidebar nav — used by openregister, decidesk, procest. */
80
- .nav {
98
+ .am .nav {
81
99
  width: 22%;
82
100
  min-width: 100px;
83
101
  background: var(--c-cobalt-50);
@@ -88,31 +106,31 @@
88
106
  gap: 5px;
89
107
  flex-shrink: 0;
90
108
  }
91
- .nav .navHead {
109
+ .am .nav .navHead {
92
110
  display: flex; align-items: center; gap: 5px;
93
111
  background: var(--c-blue-cobalt);
94
112
  border-radius: 3px;
95
113
  padding: 5px 6px;
96
114
  margin-bottom: 4px;
97
115
  }
98
- .nav .navHead .h { width: 8px; height: 9px; clip-path: var(--hex-pointy-top); background: var(--c-orange-knvb); }
99
- .nav .navHead .l { flex: 1; height: 4px; background: rgba(255,255,255,0.7); border-radius: 1px; }
100
- .nav .item {
116
+ .am .nav .navHead .h { width: 8px; height: 9px; clip-path: var(--hex-pointy-top); background: var(--c-orange-knvb); }
117
+ .am .nav .navHead .l { flex: 1; height: 4px; background: rgba(255,255,255,0.7); border-radius: 1px; }
118
+ .am .nav .item {
101
119
  display: flex; align-items: center; gap: 5px;
102
120
  padding: 4px 5px;
103
121
  border-radius: 2px;
104
122
  }
105
- .nav .item.active { background: var(--c-cobalt-100); }
106
- .nav .item .ico { width: 8px; height: 8px; border-radius: 1px; background: var(--c-cobalt-400); flex-shrink: 0; }
107
- .nav .item.active .ico { background: var(--c-blue-cobalt); }
108
- .nav .item .l { flex: 1; height: 4px; background: var(--c-cobalt-200); border-radius: 1px; }
109
- .nav .item.active .l { background: var(--c-cobalt-700); height: 5px; }
123
+ .am .nav .item.active { background: var(--c-cobalt-100); }
124
+ .am .nav .item .ico { width: 8px; height: 8px; border-radius: 1px; background: var(--c-cobalt-400); flex-shrink: 0; }
125
+ .am .nav .item.active .ico { background: var(--c-blue-cobalt); }
126
+ .am .nav .item .l { flex: 1; height: 4px; background: var(--c-cobalt-200); border-radius: 1px; }
127
+ .am .nav .item.active .l { background: var(--c-cobalt-700); height: 5px; }
110
128
 
111
129
  /* Centre column — main work surface. */
112
- .col { flex: 1; padding: 14px; display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; overflow: hidden; }
130
+ .am .col { flex: 1; padding: 14px; display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; overflow: hidden; }
113
131
 
114
132
  /* Right-rail detail panel. */
115
- .detail {
133
+ .am .detail {
116
134
  width: 26%;
117
135
  min-width: 130px;
118
136
  background: white;
@@ -125,7 +143,7 @@
125
143
  }
126
144
 
127
145
  /* Generic content panel — white card with rounded corners. */
128
- .panel {
146
+ .am .panel {
129
147
  background: white;
130
148
  border: 1px solid var(--c-cobalt-100);
131
149
  border-radius: var(--radius-sm);
@@ -135,83 +153,83 @@
135
153
  gap: 6px;
136
154
  min-width: 0;
137
155
  }
138
- .panel .head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
139
- .panel .head .title { height: 6px; background: var(--c-cobalt-700); border-radius: 1px; width: 30%; }
156
+ .am .panel .head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
157
+ .am .panel .head .title { height: 6px; background: var(--c-cobalt-700); border-radius: 1px; width: 30%; }
140
158
 
141
159
  /* Text-line bars used everywhere a row of placeholder text would go. */
142
- .row { height: 4px; background: var(--c-cobalt-100); border-radius: 1px; width: 100%; }
143
- .row.short { width: 60%; }
144
- .row.dark { background: var(--c-cobalt-300); }
145
- .row.head { height: 6px; background: var(--c-cobalt-700); width: 40%; }
146
- .row.accent { background: var(--c-orange-knvb); width: 30%; }
160
+ .am .row { height: 4px; background: var(--c-cobalt-100); border-radius: 1px; width: 100%; }
161
+ .am .row.short { width: 60%; }
162
+ .am .row.dark { background: var(--c-cobalt-300); }
163
+ .am .row.head { height: 6px; background: var(--c-cobalt-700); width: 40%; }
164
+ .am .row.accent { background: var(--c-orange-knvb); width: 30%; }
147
165
 
148
166
  /* Small status pill (mint / orange / cobalt). */
149
- .statusPill {
167
+ .am .statusPill {
150
168
  display: inline-flex; align-items: center; gap: 3px;
151
169
  padding: 2px 6px;
152
170
  border-radius: 999px;
153
171
  background: var(--c-mint-100);
154
172
  }
155
- .statusPill .h { width: 6px; height: 7px; clip-path: var(--hex-pointy-top); background: var(--c-mint-500); }
156
- .statusPill .t { height: 3px; width: 22px; background: var(--c-mint-700); border-radius: 1px; }
157
- .statusPill.beta { background: var(--c-coral-50); }
158
- .statusPill.beta .h { background: var(--c-orange-knvb); }
159
- .statusPill.beta .t { background: var(--c-coral-700); }
173
+ .am .statusPill .h { width: 6px; height: 7px; clip-path: var(--hex-pointy-top); background: var(--c-mint-500); }
174
+ .am .statusPill .t { height: 3px; width: 22px; background: var(--c-mint-700); border-radius: 1px; }
175
+ .am .statusPill.beta { background: var(--c-coral-50); }
176
+ .am .statusPill.beta .h { background: var(--c-orange-knvb); }
177
+ .am .statusPill.beta .t { background: var(--c-coral-700); }
160
178
 
161
179
  /* KPI strip cell — big number left, label right. */
162
- .kpi {
180
+ .am .kpi {
163
181
  flex: 1;
164
182
  display: flex; align-items: center; gap: 8px;
165
183
  padding: 8px 10px;
166
184
  background: var(--c-cobalt-50);
167
185
  border-radius: var(--radius-sm);
168
186
  }
169
- .kpi .ico { width: 16px; height: 18px; clip-path: var(--hex-pointy-top); background: var(--c-mint-300); flex-shrink: 0; }
170
- .kpi.amber .ico { background: var(--c-orange-knvb); }
171
- .kpi.lavender .ico { background: var(--c-lavender-300); }
172
- .kpi.forest .ico { background: var(--c-forest-300); }
173
- .kpi .meta { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
174
- .kpi .num { height: 8px; background: var(--c-cobalt-900); border-radius: 1px; width: 30%; }
175
- .kpi .label { height: 3px; background: var(--c-cobalt-400); border-radius: 1px; width: 70%; }
187
+ .am .kpi .ico { width: 16px; height: 18px; clip-path: var(--hex-pointy-top); background: var(--c-mint-300); flex-shrink: 0; }
188
+ .am .kpi.amber .ico { background: var(--c-orange-knvb); }
189
+ .am .kpi.lavender .ico { background: var(--c-lavender-300); }
190
+ .am .kpi.forest .ico { background: var(--c-forest-300); }
191
+ .am .kpi .meta { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
192
+ .am .kpi .num { height: 8px; background: var(--c-cobalt-900); border-radius: 1px; width: 30%; }
193
+ .am .kpi .label { height: 3px; background: var(--c-cobalt-400); border-radius: 1px; width: 70%; }
176
194
 
177
195
  /* Stack of rows — table-like or list-like content. */
178
- .stack { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
179
- .stack .item {
196
+ .am .stack { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
197
+ .am .stack .item {
180
198
  display: flex; align-items: center; gap: 6px;
181
199
  padding: 4px 0;
182
200
  border-bottom: 1px solid var(--c-cobalt-50);
183
201
  }
184
- .stack .item:last-child { border-bottom: none; }
185
- .stack .item .av { width: 14px; height: 14px; border-radius: 50%; background: var(--c-mint-300); flex-shrink: 0; }
186
- .stack .item .av.b { background: var(--c-lavender-300); }
187
- .stack .item .av.c { background: var(--c-orange-knvb); }
188
- .stack .item .av.d { background: var(--c-forest-300); }
189
- .stack .item .av.e { background: var(--c-terracotta-300); }
190
- .stack .item .lines { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
191
- .stack .item .lines .l1 { height: 4px; background: var(--c-cobalt-700); border-radius: 1px; width: 60%; }
192
- .stack .item .lines .l2 { height: 3px; background: var(--c-cobalt-200); border-radius: 1px; width: 80%; }
202
+ .am .stack .item:last-child { border-bottom: none; }
203
+ .am .stack .item .av { width: 14px; height: 14px; border-radius: 50%; background: var(--c-mint-300); flex-shrink: 0; }
204
+ .am .stack .item .av.b { background: var(--c-lavender-300); }
205
+ .am .stack .item .av.c { background: var(--c-orange-knvb); }
206
+ .am .stack .item .av.d { background: var(--c-forest-300); }
207
+ .am .stack .item .av.e { background: var(--c-terracotta-300); }
208
+ .am .stack .item .lines { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
209
+ .am .stack .item .lines .l1 { height: 4px; background: var(--c-cobalt-700); border-radius: 1px; width: 60%; }
210
+ .am .stack .item .lines .l2 { height: 3px; background: var(--c-cobalt-200); border-radius: 1px; width: 80%; }
193
211
 
194
212
  /* Action-button row at the top of an admin page. */
195
- .actions { display: flex; gap: 6px; }
196
- .actions .btn {
213
+ .am .actions { display: flex; gap: 6px; }
214
+ .am .actions .btn {
197
215
  background: var(--c-blue-cobalt);
198
216
  height: 16px;
199
217
  width: 56px;
200
218
  border-radius: 3px;
201
219
  }
202
- .actions .btn.ghost { background: white; border: 1px solid var(--c-cobalt-200); }
220
+ .am .actions .btn.ghost { background: white; border: 1px solid var(--c-cobalt-200); }
203
221
 
204
222
  /* ======================================================================
205
223
  MyDash — full-bleed cobalt background with widget grid
206
224
  ====================================================================== */
207
225
 
208
- .mydash {
226
+ .am .mydash {
209
227
  background: linear-gradient(135deg, var(--c-blue-cobalt) 0%, var(--c-cobalt-700) 100%);
210
228
  position: relative;
211
229
  display: flex;
212
230
  flex-direction: column;
213
231
  }
214
- .mydash .grid {
232
+ .am .mydash .grid {
215
233
  flex: 1;
216
234
  display: grid;
217
235
  grid-template-columns: 1fr 1.2fr 1fr 1fr;
@@ -219,8 +237,8 @@
219
237
  padding: 12px;
220
238
  min-height: 0;
221
239
  }
222
- .mydash .col { padding: 0; gap: 8px; }
223
- .mydash .tile {
240
+ .am .mydash .col { padding: 0; gap: 8px; }
241
+ .am .mydash .tile {
224
242
  background: rgba(255,255,255,0.94);
225
243
  border-radius: var(--radius-sm);
226
244
  padding: 8px 10px;
@@ -229,7 +247,7 @@
229
247
  gap: 4px;
230
248
  min-height: 0;
231
249
  }
232
- .mydash .tile.primary {
250
+ .am .mydash .tile.primary {
233
251
  background: var(--c-blue-cobalt);
234
252
  align-items: center;
235
253
  justify-content: center;
@@ -237,15 +255,15 @@
237
255
  flex: 1;
238
256
  min-height: 50px;
239
257
  }
240
- .mydash .tile.primary .icon { width: 22px; height: 25px; clip-path: var(--hex-pointy-top); background: white; }
241
- .mydash .tile.primary .label { height: 5px; width: 50%; background: white; border-radius: 1px; }
242
- .mydash .tile.empty { background: rgba(255,255,255,0.55); flex: 0 0 26%; }
243
- .mydash .tileHead {
258
+ .am .mydash .tile.primary .icon { width: 22px; height: 25px; clip-path: var(--hex-pointy-top); background: white; }
259
+ .am .mydash .tile.primary .label { height: 5px; width: 50%; background: white; border-radius: 1px; }
260
+ .am .mydash .tile.empty { background: rgba(255,255,255,0.55); flex: 0 0 26%; }
261
+ .am .mydash .tileHead {
244
262
  display: flex; align-items: center; gap: 5px;
245
263
  margin-bottom: 2px;
246
264
  }
247
- .mydash .tileHead .h { width: 9px; height: 9px; border-radius: 1px; background: var(--c-cobalt-400); }
248
- .mydash .tileHead .t { flex: 1; height: 4px; background: var(--c-cobalt-700); border-radius: 1px; }
265
+ .am .mydash .tileHead .h { width: 9px; height: 9px; border-radius: 1px; background: var(--c-cobalt-400); }
266
+ .am .mydash .tileHead .t { flex: 1; height: 4px; background: var(--c-cobalt-700); border-radius: 1px; }
249
267
 
250
268
  /* ==========================================================
251
269
  Widget atoms — composable inside any AppMock dashboard
@@ -254,82 +272,82 @@
254
272
 
255
273
  /* Generic widget shell — same as .mydash .tile but reusable
256
274
  under any dashboard variant. */
257
- .w {
275
+ .am .w {
258
276
  background: rgba(255, 255, 255, 0.94);
259
277
  border-radius: var(--radius-sm);
260
278
  padding: 8px 10px;
261
279
  display: flex; flex-direction: column;
262
280
  gap: 4px; min-height: 0;
263
281
  }
264
- .w .wHead {
282
+ .am .w .wHead {
265
283
  display: flex; align-items: center; gap: 5px;
266
284
  margin-bottom: 2px;
267
285
  }
268
- .w .wHead .h { width: 9px; height: 9px; border-radius: 1px; background: var(--c-cobalt-400); }
269
- .w .wHead .t { flex: 1; height: 4px; background: var(--c-cobalt-700); border-radius: 1px; }
286
+ .am .w .wHead .h { width: 9px; height: 9px; border-radius: 1px; background: var(--c-cobalt-400); }
287
+ .am .w .wHead .t { flex: 1; height: 4px; background: var(--c-cobalt-700); border-radius: 1px; }
270
288
 
271
289
  /* Bar-chart widget — vertical bars with one KNVB-orange accent. */
272
- .w-graph-bar .bars {
290
+ .am .w-graph-bar .bars {
273
291
  display: flex; align-items: flex-end; gap: 4px;
274
292
  flex: 1; min-height: 50px; padding-top: 4px;
275
293
  }
276
- .w-graph-bar .bar {
294
+ .am .w-graph-bar .bar {
277
295
  flex: 1; background: var(--c-cobalt-300); border-radius: 1px 1px 0 0;
278
296
  }
279
- .w-graph-bar .bar.accent { background: var(--c-orange-knvb); }
280
- .w-graph-bar .axis { height: 1px; background: var(--c-cobalt-200); }
297
+ .am .w-graph-bar .bar.accent { background: var(--c-orange-knvb); }
298
+ .am .w-graph-bar .axis { height: 1px; background: var(--c-cobalt-200); }
281
299
 
282
300
  /* Line-chart widget — sparkline-style polyline. */
283
- .w-graph-line .chart {
301
+ .am .w-graph-line .chart {
284
302
  flex: 1; min-height: 50px;
285
303
  display: flex; align-items: flex-end;
286
304
  position: relative; padding-top: 4px;
287
305
  }
288
- .w-graph-line .chart svg { width: 100%; height: 100%; min-height: 50px; }
289
- .w-graph-line .chart .stroke { fill: none; stroke: var(--c-blue-cobalt); stroke-width: 2; }
290
- .w-graph-line .chart .fill { fill: var(--c-cobalt-100); }
291
- .w-graph-line .chart .dot { fill: var(--c-orange-knvb); }
292
- .w-graph-line .axis { height: 1px; background: var(--c-cobalt-200); }
306
+ .am .w-graph-line .chart svg { width: 100%; height: 100%; min-height: 50px; }
307
+ .am .w-graph-line .chart .stroke { fill: none; stroke: var(--c-blue-cobalt); stroke-width: 2; }
308
+ .am .w-graph-line .chart .fill { fill: var(--c-cobalt-100); }
309
+ .am .w-graph-line .chart .dot { fill: var(--c-orange-knvb); }
310
+ .am .w-graph-line .axis { height: 1px; background: var(--c-cobalt-200); }
293
311
 
294
312
  /* Donut-chart widget — KPI-style ring with one accent slice. */
295
- .w-graph-donut .chart {
313
+ .am .w-graph-donut .chart {
296
314
  flex: 1; min-height: 50px;
297
315
  display: flex; align-items: center; justify-content: center;
298
316
  gap: 8px;
299
317
  }
300
- .w-graph-donut .donut {
318
+ .am .w-graph-donut .donut {
301
319
  width: 44px; height: 44px;
302
320
  border-radius: 50%;
303
321
  background: conic-gradient(var(--c-blue-cobalt) 0 60%, var(--c-orange-knvb) 60% 78%, var(--c-cobalt-200) 78% 100%);
304
322
  position: relative;
305
323
  flex-shrink: 0;
306
324
  }
307
- .w-graph-donut .donut::after {
325
+ .am .w-graph-donut .donut::after {
308
326
  content: ""; position: absolute; inset: 8px;
309
327
  background: white; border-radius: 50%;
310
328
  }
311
- .w-graph-donut .legend { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
312
- .w-graph-donut .legend .row { display: flex; align-items: center; gap: 4px; }
313
- .w-graph-donut .legend .swatch { width: 8px; height: 8px; border-radius: 1px; flex-shrink: 0; }
314
- .w-graph-donut .legend .swatch.a { background: var(--c-blue-cobalt); }
315
- .w-graph-donut .legend .swatch.b { background: var(--c-orange-knvb); }
316
- .w-graph-donut .legend .swatch.c { background: var(--c-cobalt-200); }
317
- .w-graph-donut .legend .label { flex: 1; height: 3px; background: var(--c-cobalt-200); border-radius: 1px; }
329
+ .am .w-graph-donut .legend { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
330
+ .am .w-graph-donut .legend .row { display: flex; align-items: center; gap: 4px; }
331
+ .am .w-graph-donut .legend .swatch { width: 8px; height: 8px; border-radius: 1px; flex-shrink: 0; }
332
+ .am .w-graph-donut .legend .swatch.a { background: var(--c-blue-cobalt); }
333
+ .am .w-graph-donut .legend .swatch.b { background: var(--c-orange-knvb); }
334
+ .am .w-graph-donut .legend .swatch.c { background: var(--c-cobalt-200); }
335
+ .am .w-graph-donut .legend .label { flex: 1; height: 3px; background: var(--c-cobalt-200); border-radius: 1px; }
318
336
 
319
337
  /* Mail widget — avatar list with subject + sender lines. */
320
- .w-mail .list { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
321
- .w-mail .item { display: flex; align-items: center; gap: 5px; }
322
- .w-mail .item .av { width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0; background: var(--c-mint-300); }
323
- .w-mail .item.b .av { background: var(--c-lavender-300); }
324
- .w-mail .item.c .av { background: var(--c-orange-knvb); }
325
- .w-mail .item.d .av { background: var(--c-forest-300); }
326
- .w-mail .item.e .av { background: var(--c-terracotta-300); }
327
- .w-mail .item .lines { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
328
- .w-mail .item .l1 { height: 3px; width: 60%; background: var(--c-cobalt-700); border-radius: 1px; }
329
- .w-mail .item .l2 { height: 2px; width: 80%; background: var(--c-cobalt-200); border-radius: 1px; }
338
+ .am .w-mail .list { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
339
+ .am .w-mail .item { display: flex; align-items: center; gap: 5px; }
340
+ .am .w-mail .item .av { width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0; background: var(--c-mint-300); }
341
+ .am .w-mail .item.b .av { background: var(--c-lavender-300); }
342
+ .am .w-mail .item.c .av { background: var(--c-orange-knvb); }
343
+ .am .w-mail .item.d .av { background: var(--c-forest-300); }
344
+ .am .w-mail .item.e .av { background: var(--c-terracotta-300); }
345
+ .am .w-mail .item .lines { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
346
+ .am .w-mail .item .l1 { height: 3px; width: 60%; background: var(--c-cobalt-700); border-radius: 1px; }
347
+ .am .w-mail .item .l2 { height: 2px; width: 80%; background: var(--c-cobalt-200); border-radius: 1px; }
330
348
 
331
349
  /* Calendar widget — 7×4 mini grid with one orange "today" cell. */
332
- .w-calendar .grid {
350
+ .am .w-calendar .grid {
333
351
  flex: 1;
334
352
  display: grid;
335
353
  grid-template-columns: repeat(7, 1fr);
@@ -337,76 +355,76 @@
337
355
  gap: 2px;
338
356
  min-height: 40px;
339
357
  }
340
- .w-calendar .grid .day {
358
+ .am .w-calendar .grid .day {
341
359
  background: var(--c-cobalt-50);
342
360
  border-radius: 1px;
343
361
  }
344
- .w-calendar .grid .day.muted { background: transparent; }
345
- .w-calendar .grid .day.event { background: var(--c-cobalt-300); }
346
- .w-calendar .grid .day.today { background: var(--c-orange-knvb); }
362
+ .am .w-calendar .grid .day.muted { background: transparent; }
363
+ .am .w-calendar .grid .day.event { background: var(--c-cobalt-300); }
364
+ .am .w-calendar .grid .day.today { background: var(--c-orange-knvb); }
347
365
 
348
366
  /* Files widget — file rows with hex-folder icon + name line. */
349
- .w-files .list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
350
- .w-files .item { display: flex; align-items: center; gap: 5px; }
351
- .w-files .item .ico {
367
+ .am .w-files .list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
368
+ .am .w-files .item { display: flex; align-items: center; gap: 5px; }
369
+ .am .w-files .item .ico {
352
370
  width: 11px; height: 13px;
353
371
  background: var(--c-blue-cobalt);
354
372
  clip-path: var(--hex-pointy-top);
355
373
  flex-shrink: 0;
356
374
  }
357
- .w-files .item.folder .ico { background: var(--c-orange-knvb); }
358
- .w-files .item.doc .ico { background: var(--c-terracotta-300); }
359
- .w-files .item.sheet .ico { background: var(--c-forest-300); }
360
- .w-files .item .l { flex: 1; height: 3px; background: var(--c-cobalt-200); border-radius: 1px; }
375
+ .am .w-files .item.folder .ico { background: var(--c-orange-knvb); }
376
+ .am .w-files .item.doc .ico { background: var(--c-terracotta-300); }
377
+ .am .w-files .item.sheet .ico { background: var(--c-forest-300); }
378
+ .am .w-files .item .l { flex: 1; height: 3px; background: var(--c-cobalt-200); border-radius: 1px; }
361
379
 
362
380
  /* Decks widget — kanban with three columns of card-bars. */
363
- .w-decks .columns {
381
+ .am .w-decks .columns {
364
382
  flex: 1;
365
383
  display: grid;
366
384
  grid-template-columns: repeat(3, 1fr);
367
385
  gap: 4px;
368
386
  min-height: 50px;
369
387
  }
370
- .w-decks .columns .col {
388
+ .am .w-decks .columns .col {
371
389
  background: var(--c-cobalt-50);
372
390
  border-radius: 2px;
373
391
  padding: 3px;
374
392
  display: flex; flex-direction: column;
375
393
  gap: 2px;
376
394
  }
377
- .w-decks .columns .card {
395
+ .am .w-decks .columns .card {
378
396
  height: 8px;
379
397
  background: white;
380
398
  border-radius: 1px;
381
399
  border-left: 2px solid var(--c-blue-cobalt);
382
400
  }
383
- .w-decks .columns .card.b { border-left-color: var(--c-orange-knvb); }
384
- .w-decks .columns .card.c { border-left-color: var(--c-mint-500); }
401
+ .am .w-decks .columns .card.b { border-left-color: var(--c-orange-knvb); }
402
+ .am .w-decks .columns .card.c { border-left-color: var(--c-mint-500); }
385
403
 
386
404
  /* RSS widget — long item rows with timestamp accent. */
387
- .w-rss .list { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
388
- .w-rss .item { display: flex; align-items: center; gap: 5px; }
389
- .w-rss .item .src { width: 14px; height: 3px; background: var(--c-cobalt-400); border-radius: 1px; flex-shrink: 0; }
390
- .w-rss .item .title { flex: 1; height: 3px; background: var(--c-cobalt-700); border-radius: 1px; }
391
- .w-rss .item .when { width: 18px; height: 3px; background: var(--c-cobalt-200); border-radius: 1px; flex-shrink: 0; }
405
+ .am .w-rss .list { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
406
+ .am .w-rss .item { display: flex; align-items: center; gap: 5px; }
407
+ .am .w-rss .item .src { width: 14px; height: 3px; background: var(--c-cobalt-400); border-radius: 1px; flex-shrink: 0; }
408
+ .am .w-rss .item .title { flex: 1; height: 3px; background: var(--c-cobalt-700); border-radius: 1px; }
409
+ .am .w-rss .item .when { width: 18px; height: 3px; background: var(--c-cobalt-200); border-radius: 1px; flex-shrink: 0; }
392
410
 
393
411
  /* Jira / status board widget — rows with status pill + title. */
394
- .w-jira .list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
395
- .w-jira .item { display: flex; align-items: center; gap: 5px; }
396
- .w-jira .item .id { width: 22px; height: 4px; background: var(--c-lavender-300); border-radius: 1px; flex-shrink: 0; }
397
- .w-jira .item .l { flex: 1; height: 3px; background: var(--c-cobalt-700); border-radius: 1px; }
398
- .w-jira .item .pip {
412
+ .am .w-jira .list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
413
+ .am .w-jira .item { display: flex; align-items: center; gap: 5px; }
414
+ .am .w-jira .item .id { width: 22px; height: 4px; background: var(--c-lavender-300); border-radius: 1px; flex-shrink: 0; }
415
+ .am .w-jira .item .l { flex: 1; height: 3px; background: var(--c-cobalt-700); border-radius: 1px; }
416
+ .am .w-jira .item .pip {
399
417
  width: 10px; height: 11px;
400
418
  clip-path: var(--hex-pointy-top);
401
419
  background: var(--c-mint-500);
402
420
  flex-shrink: 0;
403
421
  }
404
- .w-jira .item.review .pip { background: var(--c-orange-knvb); }
405
- .w-jira .item.todo .pip { background: var(--c-cobalt-200); }
406
- .w-jira .item.blocked .pip { background: var(--c-red-vermillion); }
422
+ .am .w-jira .item.review .pip { background: var(--c-orange-knvb); }
423
+ .am .w-jira .item.todo .pip { background: var(--c-cobalt-200); }
424
+ .am .w-jira .item.blocked .pip { background: var(--c-red-vermillion); }
407
425
 
408
426
  /* Video widget — thumbnail with play triangle + duration pill. */
409
- .w-video .frame {
427
+ .am .w-video .frame {
410
428
  flex: 1;
411
429
  background: var(--c-cobalt-700);
412
430
  border-radius: 2px;
@@ -414,11 +432,11 @@
414
432
  min-height: 50px;
415
433
  overflow: hidden;
416
434
  }
417
- .w-video .frame::before {
435
+ .am .w-video .frame::before {
418
436
  content: ""; position: absolute; inset: 0;
419
437
  background: linear-gradient(135deg, var(--c-cobalt-700) 0%, var(--c-cobalt-900) 100%);
420
438
  }
421
- .w-video .frame .play {
439
+ .am .w-video .frame .play {
422
440
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
423
441
  width: 0; height: 0;
424
442
  border-left: 10px solid white;
@@ -426,14 +444,14 @@
426
444
  border-bottom: 6px solid transparent;
427
445
  z-index: 1;
428
446
  }
429
- .w-video .frame .duration {
447
+ .am .w-video .frame .duration {
430
448
  position: absolute; bottom: 4px; right: 4px;
431
449
  width: 18px; height: 4px; background: var(--c-orange-knvb); border-radius: 1px;
432
450
  z-index: 1;
433
451
  }
434
452
 
435
453
  /* DocuDesk-style upload widget — dropzone with hex-stack accent. */
436
- .w-upload .zone {
454
+ .am .w-upload .zone {
437
455
  flex: 1;
438
456
  border: 1px dashed var(--c-cobalt-300);
439
457
  border-radius: 2px;
@@ -442,38 +460,38 @@
442
460
  gap: 4px;
443
461
  min-height: 50px;
444
462
  }
445
- .w-upload .zone .ico {
463
+ .am .w-upload .zone .ico {
446
464
  width: 16px; height: 18px;
447
465
  clip-path: var(--hex-pointy-top);
448
466
  background: var(--c-terracotta-500);
449
467
  }
450
- .w-upload .zone .label {
468
+ .am .w-upload .zone .label {
451
469
  height: 3px; width: 32px;
452
470
  background: var(--c-cobalt-400);
453
471
  border-radius: 1px;
454
472
  }
455
473
 
456
474
  /* Procest werkvoorraad widget — case rows with stage pip + assignee. */
457
- .w-werkvoorraad .list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
458
- .w-werkvoorraad .item { display: flex; align-items: center; gap: 5px; }
459
- .w-werkvoorraad .item .stage {
475
+ .am .w-werkvoorraad .list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
476
+ .am .w-werkvoorraad .item { display: flex; align-items: center; gap: 5px; }
477
+ .am .w-werkvoorraad .item .stage {
460
478
  width: 10px; height: 11px;
461
479
  clip-path: var(--hex-pointy-top);
462
480
  background: var(--c-lavender-300);
463
481
  flex-shrink: 0;
464
482
  }
465
- .w-werkvoorraad .item.now .stage { background: var(--c-orange-knvb); }
466
- .w-werkvoorraad .item.late .stage { background: var(--c-red-vermillion); }
467
- .w-werkvoorraad .item .l1 { flex: 1; height: 4px; background: var(--c-cobalt-700); border-radius: 1px; }
468
- .w-werkvoorraad .item .av {
483
+ .am .w-werkvoorraad .item.now .stage { background: var(--c-orange-knvb); }
484
+ .am .w-werkvoorraad .item.late .stage { background: var(--c-red-vermillion); }
485
+ .am .w-werkvoorraad .item .l1 { flex: 1; height: 4px; background: var(--c-cobalt-700); border-radius: 1px; }
486
+ .am .w-werkvoorraad .item .av {
469
487
  width: 12px; height: 12px; border-radius: 50%;
470
488
  background: var(--c-mint-300); flex-shrink: 0;
471
489
  }
472
- .w-werkvoorraad .item.b .av { background: var(--c-lavender-300); }
473
- .w-werkvoorraad .item.c .av { background: var(--c-forest-300); }
490
+ .am .w-werkvoorraad .item.b .av { background: var(--c-lavender-300); }
491
+ .am .w-werkvoorraad .item.c .av { background: var(--c-forest-300); }
474
492
 
475
493
  /* Custom-tile widget — large hex-icon launcher (Intranet, Calendar, Files). */
476
- .w-launcher {
494
+ .am .w-launcher {
477
495
  background: var(--c-blue-cobalt);
478
496
  display: flex; flex-direction: column;
479
497
  align-items: center; justify-content: center;
@@ -482,69 +500,69 @@
482
500
  border-radius: var(--radius-sm);
483
501
  min-height: 60px;
484
502
  }
485
- .w-launcher .ico {
503
+ .am .w-launcher .ico {
486
504
  width: 22px; height: 25px;
487
505
  clip-path: var(--hex-pointy-top);
488
506
  background: white;
489
507
  }
490
- .w-launcher .label {
508
+ .am .w-launcher .label {
491
509
  height: 5px; width: 50%;
492
510
  background: white; border-radius: 1px;
493
511
  }
494
- .w-launcher.amber { background: var(--c-orange-knvb); }
495
- .w-launcher.mint { background: var(--c-mint-500); }
496
- .w-launcher.lavender { background: var(--c-lavender-500); }
497
- .w-launcher.forest { background: var(--c-forest-500); }
512
+ .am .w-launcher.amber { background: var(--c-orange-knvb); }
513
+ .am .w-launcher.mint { background: var(--c-mint-500); }
514
+ .am .w-launcher.lavender { background: var(--c-lavender-500); }
515
+ .am .w-launcher.forest { background: var(--c-forest-500); }
498
516
 
499
517
  /* Sub-grid container — for showing tile groups inside a tile. */
500
- .w-subgrid .grid {
518
+ .am .w-subgrid .grid {
501
519
  flex: 1;
502
520
  display: grid;
503
521
  grid-template-columns: repeat(2, 1fr);
504
522
  gap: 4px;
505
523
  min-height: 50px;
506
524
  }
507
- .w-subgrid .grid .cell {
525
+ .am .w-subgrid .grid .cell {
508
526
  background: var(--c-cobalt-50);
509
527
  border-radius: 2px;
510
528
  display: flex; align-items: center; justify-content: center;
511
529
  }
512
- .w-subgrid .grid .cell .ico {
530
+ .am .w-subgrid .grid .cell .ico {
513
531
  width: 14px; height: 16px;
514
532
  clip-path: var(--hex-pointy-top);
515
533
  background: var(--c-blue-cobalt);
516
534
  }
517
- .w-subgrid .grid .cell.b .ico { background: var(--c-orange-knvb); }
518
- .w-subgrid .grid .cell.c .ico { background: var(--c-mint-500); }
519
- .w-subgrid .grid .cell.d .ico { background: var(--c-lavender-500); }
535
+ .am .w-subgrid .grid .cell.b .ico { background: var(--c-orange-knvb); }
536
+ .am .w-subgrid .grid .cell.c .ico { background: var(--c-mint-500); }
537
+ .am .w-subgrid .grid .cell.d .ico { background: var(--c-lavender-500); }
520
538
 
521
539
  /* ======================================================================
522
540
  OpenRegister — left nav + centre dashboard + right detail panel
523
541
  ====================================================================== */
524
542
 
525
- .openregister .col .kpiRow { display: flex; gap: 6px; }
526
- .openregister .col .panelRow { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; flex: 1; min-height: 0; }
543
+ .am .openregister .col .kpiRow { display: flex; gap: 6px; }
544
+ .am .openregister .col .panelRow { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; flex: 1; min-height: 0; }
527
545
 
528
546
  /* ======================================================================
529
547
  DeciDesk — left nav + centre with action row + KPIs + 2 tables
530
548
  ====================================================================== */
531
549
 
532
- .decidesk .col .kpiRow { display: flex; gap: 6px; }
533
- .decidesk .col .panelRow { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; flex: 1; min-height: 0; }
534
- .decidesk .col .head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
550
+ .am .decidesk .col .kpiRow { display: flex; gap: 6px; }
551
+ .am .decidesk .col .panelRow { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; flex: 1; min-height: 0; }
552
+ .am .decidesk .col .head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
535
553
 
536
554
  /* ======================================================================
537
555
  OpenCatalogi — list of catalogue cards
538
556
  ====================================================================== */
539
557
 
540
- .opencatalogi .col .grid {
558
+ .am .opencatalogi .col .grid {
541
559
  display: grid;
542
560
  grid-template-columns: repeat(3, 1fr);
543
561
  gap: 8px;
544
562
  flex: 1;
545
563
  min-height: 0;
546
564
  }
547
- .opencatalogi .card {
565
+ .am .opencatalogi .card {
548
566
  background: white;
549
567
  border: 1px solid var(--c-cobalt-100);
550
568
  border-radius: var(--radius-sm);
@@ -553,22 +571,22 @@
553
571
  flex-direction: column;
554
572
  gap: 5px;
555
573
  }
556
- .opencatalogi .card .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: var(--c-blue-cobalt); }
557
- .opencatalogi .card.b .ico { background: var(--c-forest-500); }
558
- .opencatalogi .card.c .ico { background: var(--c-lavender-500); }
559
- .opencatalogi .card.d .ico { background: var(--c-terracotta-500); }
560
- .opencatalogi .card.e .ico { background: var(--c-mint-500); }
574
+ .am .opencatalogi .card .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: var(--c-blue-cobalt); }
575
+ .am .opencatalogi .card.b .ico { background: var(--c-forest-500); }
576
+ .am .opencatalogi .card.c .ico { background: var(--c-lavender-500); }
577
+ .am .opencatalogi .card.d .ico { background: var(--c-terracotta-500); }
578
+ .am .opencatalogi .card.e .ico { background: var(--c-mint-500); }
561
579
 
562
580
  /* ======================================================================
563
581
  OpenConnector — pipeline / source-connector-target visual
564
582
  ====================================================================== */
565
583
 
566
- .openconnector .stage {
584
+ .am .openconnector .stage {
567
585
  display: flex; align-items: center; gap: 10px;
568
586
  padding: 12px 0;
569
587
  }
570
- .openconnector .stage .source,
571
- .openconnector .stage .target {
588
+ .am .openconnector .stage .source,
589
+ .am .openconnector .stage .target {
572
590
  width: 28%; height: 56px;
573
591
  background: var(--c-cobalt-50);
574
592
  border: 1px solid var(--c-cobalt-200);
@@ -577,16 +595,16 @@
577
595
  align-items: center; justify-content: center;
578
596
  gap: 4px;
579
597
  }
580
- .openconnector .stage .source .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: var(--c-lavender-500); }
581
- .openconnector .stage .target .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: var(--c-forest-500); }
582
- .openconnector .stage .arrow { flex: 1; height: 2px; background: var(--c-cobalt-300); position: relative; }
583
- .openconnector .stage .arrow::after {
598
+ .am .openconnector .stage .source .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: var(--c-lavender-500); }
599
+ .am .openconnector .stage .target .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: var(--c-forest-500); }
600
+ .am .openconnector .stage .arrow { flex: 1; height: 2px; background: var(--c-cobalt-300); position: relative; }
601
+ .am .openconnector .stage .arrow::after {
584
602
  content: ""; position: absolute; right: 0; top: 50%; transform: translateY(-50%);
585
603
  border-left: 6px solid var(--c-cobalt-300);
586
604
  border-top: 4px solid transparent;
587
605
  border-bottom: 4px solid transparent;
588
606
  }
589
- .openconnector .stage .conn {
607
+ .am .openconnector .stage .conn {
590
608
  width: 18%;
591
609
  background: var(--c-blue-cobalt);
592
610
  border-radius: var(--radius-sm);
@@ -595,35 +613,35 @@
595
613
  align-items: center; justify-content: center;
596
614
  gap: 4px;
597
615
  }
598
- .openconnector .stage .conn .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: white; }
599
- .openconnector .stage .conn .label { height: 4px; width: 60%; background: rgba(255,255,255,0.8); border-radius: 1px; }
616
+ .am .openconnector .stage .conn .ico { width: 18px; height: 21px; clip-path: var(--hex-pointy-top); background: white; }
617
+ .am .openconnector .stage .conn .label { height: 4px; width: 60%; background: rgba(255,255,255,0.8); border-radius: 1px; }
600
618
 
601
619
  /* ======================================================================
602
620
  Procest — process timeline / case detail
603
621
  ====================================================================== */
604
622
 
605
- .procest .timeline {
623
+ .am .procest .timeline {
606
624
  display: flex; align-items: center; gap: 0;
607
625
  padding: 8px 0;
608
626
  position: relative;
609
627
  }
610
- .procest .timeline::before {
628
+ .am .procest .timeline::before {
611
629
  content: ""; position: absolute; left: 12px; right: 12px; top: 50%;
612
630
  height: 2px; background: var(--c-cobalt-200);
613
631
  z-index: 0;
614
632
  }
615
- .procest .timeline .step {
633
+ .am .procest .timeline .step {
616
634
  flex: 1;
617
635
  display: flex; flex-direction: column;
618
636
  align-items: center;
619
637
  gap: 4px;
620
638
  position: relative; z-index: 1;
621
639
  }
622
- .procest .timeline .step .h {
640
+ .am .procest .timeline .step .h {
623
641
  width: 18px; height: 21px;
624
642
  clip-path: var(--hex-pointy-top);
625
643
  background: var(--c-mint-500);
626
644
  }
627
- .procest .timeline .step.todo .h { background: var(--c-cobalt-100); }
628
- .procest .timeline .step.now .h { background: var(--c-orange-knvb); }
629
- .procest .timeline .step .label { height: 3px; width: 60%; background: var(--c-cobalt-400); border-radius: 1px; }
645
+ .am .procest .timeline .step.todo .h { background: var(--c-cobalt-100); }
646
+ .am .procest .timeline .step.now .h { background: var(--c-orange-knvb); }
647
+ .am .procest .timeline .step .label { height: 3px; width: 60%; background: var(--c-cobalt-400); border-radius: 1px; }
@@ -40,6 +40,7 @@
40
40
 
41
41
  import React from 'react';
42
42
  import HexBullet from '../primitives/HexBullet';
43
+ import {appHrefByName} from '../../data/apps-registry';
43
44
  import styles from './PartnerCard.module.css';
44
45
 
45
46
  const TIER_LABELS = {host: 'Host', service: 'Service', certified: 'Certified'};
@@ -88,17 +89,30 @@ export default function PartnerCard({
88
89
  );
89
90
  }
90
91
 
91
- /* Default: full partner card */
92
+ /* Default: full partner card.
93
+ The card wrapper is always a <div> rather than an <a>, even when
94
+ a partner detail href is set, so the per-app pills can be
95
+ individually clickable links to /apps/<slug>. The card-wide click
96
+ target is rendered as a stretched-link overlay (.cardLink) that
97
+ covers the whole card via position:absolute; nested <a> pills
98
+ sit on top via z-index, so clicks reach the pill instead of the
99
+ overlay when they land on a pill, and reach the overlay otherwise. */
92
100
  const composed = [
93
101
  styles.card,
94
102
  styles['tier-' + tier],
95
103
  className,
96
104
  ].filter(Boolean).join(' ');
97
- const Tag = href ? 'a' : 'div';
98
105
  const bulletColor = tier === 'certified' ? 'var(--c-orange-knvb)' : 'var(--c-blue-cobalt)';
99
106
 
100
107
  return (
101
- <Tag href={href} className={composed}>
108
+ <div className={composed}>
109
+ {href && (
110
+ <a
111
+ href={href}
112
+ className={styles.cardLink}
113
+ aria-label={name ? `${name} partner page` : 'Partner page'}
114
+ />
115
+ )}
102
116
  <span className={styles.tier}>{TIER_LABELS[tier] || tier}</span>
103
117
  {logo && (
104
118
  <div className={styles.avatar}>
@@ -109,15 +123,29 @@ export default function PartnerCard({
109
123
  {summary && <div className={styles.summary}>{summary}</div>}
110
124
  {apps.length > 0 && (
111
125
  <div className={styles.apps}>
112
- {apps.map((app, i) => (
113
- <span key={i} className={styles.appPill}>
114
- <HexBullet size="sm" color={bulletColor} />
115
- {app}
116
- </span>
117
- ))}
126
+ {apps.map((app, i) => {
127
+ const appUrl = appHrefByName(app);
128
+ const inner = (
129
+ <>
130
+ <HexBullet size="sm" color={bulletColor} />
131
+ {app}
132
+ </>
133
+ );
134
+ return appUrl ? (
135
+ <a
136
+ key={app}
137
+ href={appUrl}
138
+ className={styles.appPill}
139
+ >
140
+ {inner}
141
+ </a>
142
+ ) : (
143
+ <span key={app} className={styles.appPill}>{inner}</span>
144
+ );
145
+ })}
118
146
  </div>
119
147
  )}
120
- </Tag>
148
+ </div>
121
149
  );
122
150
  }
123
151
 
@@ -33,6 +33,21 @@
33
33
  text-decoration: none;
34
34
  color: inherit;
35
35
  }
36
+ /* Stretched-link overlay. Covers the whole card so any click on
37
+ non-interactive content navigates to the partner detail page,
38
+ while leaving room for nested links (app pills) to take precedence
39
+ via z-index. The overlay carries the aria-label since the card
40
+ itself is no longer the anchor. */
41
+ .cardLink {
42
+ position: absolute;
43
+ inset: 0;
44
+ border-radius: inherit;
45
+ z-index: 1;
46
+ }
47
+ .cardLink:focus-visible {
48
+ outline: 2px solid var(--c-blue-cobalt);
49
+ outline-offset: -2px;
50
+ }
36
51
 
37
52
  .tier {
38
53
  position: absolute;
@@ -107,6 +122,24 @@
107
122
  border-radius: var(--radius-pill);
108
123
  font-size: 12px;
109
124
  font-family: var(--conduction-typography-font-family-code);
125
+ /* When the pill is rendered as <a> it sits above the cardLink
126
+ overlay so clicks land on the pill, not the card-wide link. */
127
+ position: relative;
128
+ z-index: 2;
129
+ text-decoration: none;
130
+ transition: background 160ms, color 160ms;
131
+ }
132
+ /* Hover affordance only when the pill is a real link (i.e. the
133
+ app is in the registry). Plain <span> pills (e.g. "Nextcloud")
134
+ stay static. */
135
+ a.appPill:hover {
136
+ background: var(--c-blue-cobalt);
137
+ color: white;
138
+ text-decoration: none;
139
+ }
140
+ .card.tier-certified a.appPill:hover {
141
+ background: white;
142
+ color: var(--c-blue-cobalt);
110
143
  }
111
144
  .tier-certified .appPill { background: rgba(255, 255, 255, 0.12); color: white; }
112
145
 
@@ -37,6 +37,7 @@
37
37
  */
38
38
 
39
39
  import React from 'react';
40
+ import {appHrefByName} from '../../data/apps-registry';
40
41
  import styles from './PartnerSidecard.module.css';
41
42
 
42
43
  const TIER_LABELS = {
@@ -71,7 +72,7 @@ export default function PartnerSidecard({
71
72
  {tier === 'certified' && (
72
73
  <img
73
74
  className={styles.tierBadge}
74
- src="/img/brand/avatar-conduction-gold-on-white.svg"
75
+ src="/img/brand/avatar-conduction-gold.svg"
75
76
  alt="Conduction-certified mark"
76
77
  width="44"
77
78
  height="44"
@@ -85,9 +86,14 @@ export default function PartnerSidecard({
85
86
  <div className={styles.section}>
86
87
  <h4 className={styles.sectionTitle}>Apps they ship</h4>
87
88
  <div className={styles.chipRow}>
88
- {partner.apps.map((app) => (
89
- <span key={app} className={styles.chip}>{app}</span>
90
- ))}
89
+ {partner.apps.map((app) => {
90
+ const url = appHrefByName(app);
91
+ return url ? (
92
+ <a key={app} href={url} className={styles.chip}>{app}</a>
93
+ ) : (
94
+ <span key={app} className={styles.chip}>{app}</span>
95
+ );
96
+ })}
91
97
  </div>
92
98
  </div>
93
99
  )}
@@ -103,6 +103,15 @@
103
103
  border-radius: var(--radius-pill);
104
104
  font-size: 12px;
105
105
  font-family: var(--conduction-typography-font-family-code);
106
+ text-decoration: none;
107
+ transition: background 160ms, color 160ms;
108
+ }
109
+ /* Only the <a> form gets hover affordance — plain <span> chips
110
+ (apps not in the apps-registry, e.g. Nextcloud) stay static. */
111
+ a.chip:hover {
112
+ background: var(--c-blue-cobalt);
113
+ color: white;
114
+ text-decoration: none;
106
115
  }
107
116
 
108
117
  /* ---------- Solutions list ---------- */
@@ -421,6 +421,22 @@ a:hover { color: var(--conduction-color-link-hover); }
421
421
  background-blend-mode: normal;
422
422
  }
423
423
 
424
+ /* ============================================================
425
+ .crumb · breadcrumb on every preview page.
426
+ Defined in tokens.css so component specimens that import
427
+ tokens.css (but not preview.css) still pick up the styling.
428
+ .hero .crumb in preview.css overrides on the dark hub header.
429
+ ============================================================ */
430
+ .crumb {
431
+ font-family: var(--conduction-typography-font-family-code);
432
+ font-size: 12px;
433
+ letter-spacing: 0.1em;
434
+ text-transform: uppercase;
435
+ color: var(--c-cobalt-400);
436
+ }
437
+ .crumb a { color: var(--c-cobalt-700); text-decoration: none; }
438
+ .crumb a:hover { color: var(--c-orange-knvb); }
439
+
424
440
  /* Typography & colour defaults, applied via the semantic tokens.
425
441
  Pages link this alongside tokens.css. Per-page CSS only overrides
426
442
  when a surface needs something other than the brand default
@@ -59,3 +59,26 @@ export function getApp(slug) {
59
59
  export function getApps(slugs = []) {
60
60
  return slugs.map(getApp).filter(Boolean);
61
61
  }
62
+
63
+ /**
64
+ * Resolve a display-name (e.g. "OpenCatalogi", "DocuDesk", "MyDash")
65
+ * to its product page href, or undefined when the name is not in the
66
+ * registry. Used by partner cards / sidecards to turn the apps-shipped
67
+ * chip row into a clickable link list. Names like "Nextcloud" that
68
+ * aren't ours fall through and the consumer renders a plain span.
69
+ *
70
+ * Match is case-insensitive on both name and slug so consumers can
71
+ * pass either form ("OpenCatalogi", "opencatalogi", or "OpenCATALOGI")
72
+ * without each adding their own normalisation.
73
+ */
74
+ export function appHrefByName(name) {
75
+ if (!name) return undefined;
76
+ const target = String(name).toLowerCase();
77
+ for (const slug of APP_SLUGS) {
78
+ const entry = APPS_REGISTRY[slug];
79
+ if (slug === target || entry.name.toLowerCase() === target) {
80
+ return entry.productHref;
81
+ }
82
+ }
83
+ return undefined;
84
+ }