@conduction/docusaurus-preset 3.31.0 → 3.32.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/MISSING_COMPONENTS.md +6 -2
- package/package.json +1 -1
- package/src/components/BuildMock/BuildMock.jsx +101 -0
- package/src/components/BuildMock/BuildMock.module.css +290 -0
- package/src/components/DetailHero/DetailHero.jsx +68 -16
- package/src/components/DetailHero/DetailHero.module.css +31 -1
- package/src/components/FlowMock/FlowMock.jsx +151 -0
- package/src/components/FlowMock/FlowMock.module.css +164 -0
- package/src/components/KanbanMock/KanbanMock.jsx +119 -0
- package/src/components/KanbanMock/KanbanMock.module.css +233 -0
- package/src/components/LeafMock/LeafMock.jsx +173 -0
- package/src/components/LeafMock/LeafMock.module.css +237 -0
- package/src/components/RotatingCards/RotatingCards.module.css +5 -2
- package/src/components/Showcase/Showcase.module.css +5 -2
- package/src/components/WidgetShelf/WidgetShelf.jsx +52 -13
- package/src/components/WidgetShelf/WidgetShelf.module.css +84 -5
- package/src/components/index.js +4 -0
- package/src/components/primitives/SectionHead.module.css +6 -0
package/MISSING_COMPONENTS.md
CHANGED
|
@@ -47,7 +47,7 @@ Most consume one or more primitives.
|
|
|
47
47
|
- **FeatureGrid / FeatureGridItem / FeatureGridGroup** — `FeatureGrid/FeatureGrid.jsx`. Compact spec list with hover-tooltip and stable/beta/soon status hexes.
|
|
48
48
|
- **HowSteps / HowStep** — `HowSteps/HowSteps.jsx`. Numbered process-step row. Auto-numbers and auto-tints (cobalt → orange → cobalt-700) when `number` and `tier` are omitted.
|
|
49
49
|
- **Pipeline / PipelineStep / IconList** — `Pipeline/Pipeline.jsx`. Horizontal hex-numbered pipeline with chevron flow lines and optional source/consumer end-boxes.
|
|
50
|
-
- **DetailHero** — `DetailHero/DetailHero.jsx`. Shared 1fr/360px hero pattern between app/solution/partner detail pages. Crumb + status + version + locales + downloads badge + h1 + tagline +
|
|
50
|
+
- **DetailHero** — `DetailHero/DetailHero.jsx`. Shared 1fr/360px hero pattern between app/solution/partner detail pages. Crumb + status + version + locales + downloads badge + "View on GitHub" chip (both link to the repo via `repoHref` / GitHub tertiaryCta / ConductionNL fallback) + h1 + tagline + primary/secondary CTA pair + right-column illustration.
|
|
51
51
|
- **FacetedFilters / FilterChip** — `FacetedFilters/FacetedFilters.jsx`. Sidebar facet checkbox panel + active-chip strip.
|
|
52
52
|
- **ConductionBg** — `ConductionBg/ConductionBg.jsx`. Parallax honeycomb background. Real component with self-contained runtime hook.
|
|
53
53
|
- **HexNetwork** — `HexNetwork/HexNetwork.jsx`. Centre hex with surrounding satellites. Used on `/support` to render the partner network around Conduction.
|
|
@@ -58,8 +58,12 @@ Most consume one or more primitives.
|
|
|
58
58
|
- **ComposeBlock** — `ComposeBlock/ComposeBlock.jsx`. Branded code block (cobalt-900 + Plex Mono + filename pill + copy button). For docker-compose, bash recipes, and any verbatim copy-paste content. Used on `/demo`.
|
|
59
59
|
- **AgentTrace** — `AgentTrace/AgentTrace.jsx`. Terminal-styled agent execution trace with cursor + mode chrome. Specimen: `preview/components/agent-trace.html`.
|
|
60
60
|
- **AppMock** — `AppMock/AppMock.jsx`. Selector that renders one of 16 app-specific UI mockups (DeciDesk, DocuDesk, LarpingApp, LaunchPad + variants, NLDesign, OpenCatalogi, OpenConnector, OpenRegister, OpenWoo, PipelinQ, Procest, SoftwareCatalog, ZaakAfhandelApp). Each variant lives under `AppMock/variants/`. Specimen: `preview/components/app-mock.html`.
|
|
61
|
+
- **FlowMock** — `FlowMock/FlowMock.jsx`. Token-painted abstract of the flow editor: node cards on a dotted canvas with coloured left bars (mint trigger, lavender steps, vermillion end), curved grey edges with pill labels, and a mint run-line looping trigger → step → approval → end. Freezes to the full static route under `prefers-reduced-motion` or `running={false}`. Specimen: `preview/components/flow-mock.html`.
|
|
62
|
+
- **LeafMock** — `LeafMock/LeafMock.jsx`. Small integration-leaf panels for Showcase items: `leaf="contacts"` (hex-avatar rows + mint sync arrow to a phone), `"calendar"` (mini month grid + linked-event chip), `"mail"` (message list + mint create-from-email chip), `"files"` (folder/file rows + forest link-to-record chip).
|
|
63
|
+
- **BuildMock** — `BuildMock/BuildMock.jsx`. Looping "make it yours" animation: the orange OpenBuild edit FAB is pressed, a component palette opens, and a widget is dragged into the page layout where it snaps in (~8s loop). `prefers-reduced-motion` / `running={false}` renders the static end state (component placed).
|
|
64
|
+
- **KanbanMock** — `KanbanMock/KanbanMock.jsx`. Looping kanban drag for the pipelinq hero: a cursor lifts the top lead card, drags it to the next column, drops it, and the column counts nudge (~7s loop). `prefers-reduced-motion` / `running={false}` renders the static board.
|
|
61
65
|
- **ExternalAppShelf** — `ExternalAppShelf/ExternalAppShelf.jsx`. Third-party-app shelf (Outlook-style, Mattermost, Keycloak, etc.) used on `/connext`. Specimen: `preview/components/external-app-shelf.html`.
|
|
62
|
-
- **WidgetShelf** — `WidgetShelf/WidgetShelf.jsx`. Widget showcase
|
|
66
|
+
- **WidgetShelf** — `WidgetShelf/WidgetShelf.jsx`. Widget showcase with eyebrow + title + lede. Default is a slow single-row carousel (seamless duplicated track, pause on hover/focus, static wrapping grid under `prefers-reduced-motion`); `carousel={false}` renders the N-column grid. Specimen: `preview/components/widget-shelf.html`.
|
|
63
67
|
- **Showcase** — `Showcase/Showcase.jsx`. Multi-item collapsible showcase with screenshot + body. Used on `/commonground` for the "5-lagen model". Specimen: `preview/components/showcase.html`.
|
|
64
68
|
- **RotatingCards** — `RotatingCards/RotatingCards.jsx`. Rotating card carousel. Specimen: `preview/components/rotating-cards.html`.
|
|
65
69
|
- **FAQ / FAQItem** — `FAQ/FAQ.jsx`. Disclosure-pattern FAQ block. Used on `/support`. Specimen: `preview/components/faq.html`.
|
package/package.json
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <BuildMock />
|
|
3
|
+
*
|
|
4
|
+
* Token-built abstract of the OpenBuild "make it yours" moment: the
|
|
5
|
+
* orange edit button gets pressed, a small component palette opens, a
|
|
6
|
+
* widget is picked and dragged onto the page, and it snaps into the
|
|
7
|
+
* empty layout slot. Loops every ~8 seconds. Used on every app's
|
|
8
|
+
* Make-it-yours card.
|
|
9
|
+
*
|
|
10
|
+
* Same abstraction level as <AppMock>: greeked content, tokens only.
|
|
11
|
+
* The orange FAB is this component's single orange accent (it *is*
|
|
12
|
+
* the OpenBuild edit button).
|
|
13
|
+
*
|
|
14
|
+
* `prefers-reduced-motion: reduce` (and `running={false}`) renders
|
|
15
|
+
* the static end state: palette closed, cursor gone, the dragged
|
|
16
|
+
* component already snapped into the layout.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
*
|
|
20
|
+
* <BuildMock />
|
|
21
|
+
* <BuildMock size="sm" caption />
|
|
22
|
+
* <BuildMock running={false} />
|
|
23
|
+
*
|
|
24
|
+
* Props:
|
|
25
|
+
* - size: 'sm' | 'md' (default) — frame width, as AppMock
|
|
26
|
+
* - caption: string | true — small label below the frame;
|
|
27
|
+
* `true` renders the default "Make it yours"
|
|
28
|
+
* - running: boolean (default true) — false freezes to the end state
|
|
29
|
+
* - className: string
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import React from 'react';
|
|
33
|
+
import styles from './BuildMock.module.css';
|
|
34
|
+
|
|
35
|
+
function MiniTile({wide}) {
|
|
36
|
+
return (
|
|
37
|
+
<span className={styles.miniTile}>
|
|
38
|
+
<span className={styles.miniBar} style={{width: wide ? '70%' : '50%'}} />
|
|
39
|
+
<span className={styles.miniLine} />
|
|
40
|
+
<span className={styles.miniLine} style={{width: '60%'}} />
|
|
41
|
+
</span>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function BuildMock({size = 'md', caption, running = true, className}) {
|
|
46
|
+
return (
|
|
47
|
+
<div className={styles.bm}>
|
|
48
|
+
<figure className={[styles.figure, className].filter(Boolean).join(' ')}>
|
|
49
|
+
<div
|
|
50
|
+
className={[styles.frame, styles[`size-${size}`], !running && styles.static].filter(Boolean).join(' ')}
|
|
51
|
+
role="img"
|
|
52
|
+
aria-label="Abstract page editor: the edit button opens a component palette and a widget is dragged into the page layout."
|
|
53
|
+
>
|
|
54
|
+
<div className={styles.page}>
|
|
55
|
+
<div className={styles.pageHead}>
|
|
56
|
+
<span className={styles.headGreek} />
|
|
57
|
+
</div>
|
|
58
|
+
<div className={styles.pageGrid}>
|
|
59
|
+
<div className={styles.tile}><MiniTile wide /></div>
|
|
60
|
+
<div className={styles.tile}><MiniTile /></div>
|
|
61
|
+
<div className={styles.slot}>
|
|
62
|
+
{/* The component the drag delivers. Fades in when the
|
|
63
|
+
ghost lands; permanently visible in the static /
|
|
64
|
+
reduced-motion end state. */}
|
|
65
|
+
<div className={styles.snapTile}><MiniTile wide /></div>
|
|
66
|
+
</div>
|
|
67
|
+
<div className={styles.tile}><MiniTile /></div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* Component palette, opened by the FAB press. */}
|
|
72
|
+
<div className={styles.palette}>
|
|
73
|
+
<span className={styles.paletteTile} />
|
|
74
|
+
<span className={[styles.paletteTile, styles.paletteTileActive].join(' ')} />
|
|
75
|
+
<span className={styles.paletteTile} />
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{/* The OpenBuild edit button — the one orange accent. */}
|
|
79
|
+
<div className={styles.fab}>
|
|
80
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
81
|
+
<path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
|
82
|
+
</svg>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* The dragged component ghost. */}
|
|
86
|
+
<div className={styles.ghost}><MiniTile wide /></div>
|
|
87
|
+
|
|
88
|
+
{/* The cursor driving the loop. */}
|
|
89
|
+
<svg className={styles.cursor} viewBox="0 0 24 24" aria-hidden="true">
|
|
90
|
+
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z" />
|
|
91
|
+
</svg>
|
|
92
|
+
</div>
|
|
93
|
+
{caption && (
|
|
94
|
+
<figcaption className={styles.caption}>
|
|
95
|
+
{caption === true ? 'Make it yours' : caption}
|
|
96
|
+
</figcaption>
|
|
97
|
+
)}
|
|
98
|
+
</figure>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <BuildMock /> styles. Scoped under `.bm` (display: contents), the
|
|
3
|
+
* AppMock `.am` convention, so specimen pages can reuse the file as a
|
|
4
|
+
* plain global stylesheet.
|
|
5
|
+
*
|
|
6
|
+
* One shared timeline (--bm-dur, default 8s) drives five animations:
|
|
7
|
+
* the cursor path, the FAB press, the palette open/close, the dragged
|
|
8
|
+
* ghost, and the snap-in of the placed component. Percentages line up
|
|
9
|
+
* across the keyframe sets:
|
|
10
|
+
*
|
|
11
|
+
* 0–14% cursor travels to the FAB, presses it
|
|
12
|
+
* 16–22% palette opens, cursor moves to the middle palette tile
|
|
13
|
+
* 30–58% ghost is dragged from the palette to the empty slot
|
|
14
|
+
* 58–62% snap: ghost settles, placed component fades in
|
|
15
|
+
* 62–88% hold the result
|
|
16
|
+
* 88–100% everything resets for the next loop
|
|
17
|
+
*
|
|
18
|
+
* prefers-reduced-motion (and the .static class from running={false})
|
|
19
|
+
* freezes the mock at the end state: component placed, palette closed,
|
|
20
|
+
* no cursor. Tokens only; the orange FAB is the single orange accent.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
.bm { display: contents; }
|
|
24
|
+
|
|
25
|
+
.bm .figure {
|
|
26
|
+
margin: 0;
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
gap: var(--space-2);
|
|
30
|
+
width: 100%;
|
|
31
|
+
max-width: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.bm .frame {
|
|
35
|
+
--bm-dur: 8s;
|
|
36
|
+
width: 100%;
|
|
37
|
+
max-width: 720px;
|
|
38
|
+
aspect-ratio: 16 / 10;
|
|
39
|
+
background: white;
|
|
40
|
+
border: 1px solid var(--c-cobalt-100);
|
|
41
|
+
border-radius: var(--radius-lg);
|
|
42
|
+
box-shadow: var(--shadow-3);
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
position: relative;
|
|
45
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
46
|
+
}
|
|
47
|
+
.bm .size-sm { max-width: 480px; }
|
|
48
|
+
.bm .size-md { max-width: 720px; }
|
|
49
|
+
|
|
50
|
+
.bm .caption {
|
|
51
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
52
|
+
font-size: 11px;
|
|
53
|
+
letter-spacing: 0.06em;
|
|
54
|
+
color: var(--c-cobalt-400);
|
|
55
|
+
text-transform: uppercase;
|
|
56
|
+
text-align: center;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ---- Page skeleton ---- */
|
|
60
|
+
|
|
61
|
+
.bm .page {
|
|
62
|
+
position: absolute;
|
|
63
|
+
inset: 0;
|
|
64
|
+
padding: 7% 8%;
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
gap: 5%;
|
|
68
|
+
}
|
|
69
|
+
.bm .pageHead { flex-shrink: 0; }
|
|
70
|
+
.bm .headGreek {
|
|
71
|
+
display: block;
|
|
72
|
+
width: 34%;
|
|
73
|
+
height: 8px;
|
|
74
|
+
border-radius: 2px;
|
|
75
|
+
background: var(--c-cobalt-700);
|
|
76
|
+
}
|
|
77
|
+
.bm .pageGrid {
|
|
78
|
+
flex: 1;
|
|
79
|
+
display: grid;
|
|
80
|
+
grid-template-columns: 1fr 1fr;
|
|
81
|
+
grid-template-rows: 1fr 1fr;
|
|
82
|
+
gap: 5%;
|
|
83
|
+
/* Leave the right quarter free for the palette + FAB. */
|
|
84
|
+
margin-right: 22%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.bm .tile,
|
|
88
|
+
.bm .slot {
|
|
89
|
+
border-radius: var(--radius-md);
|
|
90
|
+
padding: 8px 10px;
|
|
91
|
+
}
|
|
92
|
+
.bm .tile {
|
|
93
|
+
background: var(--c-cobalt-50);
|
|
94
|
+
border: 1px solid var(--c-cobalt-100);
|
|
95
|
+
}
|
|
96
|
+
.bm .slot {
|
|
97
|
+
border: 2px dashed var(--c-cobalt-200);
|
|
98
|
+
animation: bmSlotHint var(--bm-dur) linear infinite;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.bm .miniTile {
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
gap: 5px;
|
|
105
|
+
width: 100%;
|
|
106
|
+
}
|
|
107
|
+
.bm .miniBar { display: block; height: 5px; border-radius: 1.5px; background: var(--c-cobalt-700); }
|
|
108
|
+
.bm .miniLine { display: block; height: 3px; border-radius: 1px; background: var(--c-cobalt-200); width: 80%; }
|
|
109
|
+
|
|
110
|
+
/* The delivered component inside the slot. */
|
|
111
|
+
.bm .snapTile {
|
|
112
|
+
opacity: 0;
|
|
113
|
+
animation: bmSnapIn var(--bm-dur) linear infinite;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ---- Palette ---- */
|
|
117
|
+
|
|
118
|
+
.bm .palette {
|
|
119
|
+
position: absolute;
|
|
120
|
+
right: 4%;
|
|
121
|
+
top: 22%;
|
|
122
|
+
width: 13%;
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
gap: 8px;
|
|
126
|
+
background: white;
|
|
127
|
+
border: 1px solid var(--c-cobalt-100);
|
|
128
|
+
border-radius: var(--radius-md);
|
|
129
|
+
box-shadow: var(--shadow-3);
|
|
130
|
+
padding: 8px;
|
|
131
|
+
opacity: 0;
|
|
132
|
+
transform: translateX(10px);
|
|
133
|
+
animation: bmPalette var(--bm-dur) linear infinite;
|
|
134
|
+
}
|
|
135
|
+
.bm .paletteTile {
|
|
136
|
+
display: block;
|
|
137
|
+
height: 18px;
|
|
138
|
+
border-radius: 3px;
|
|
139
|
+
background: var(--c-cobalt-50);
|
|
140
|
+
border: 1px solid var(--c-cobalt-100);
|
|
141
|
+
}
|
|
142
|
+
.bm .paletteTileActive { animation: bmPaletteTile var(--bm-dur) linear infinite; }
|
|
143
|
+
|
|
144
|
+
/* ---- FAB (the one orange accent) ---- */
|
|
145
|
+
|
|
146
|
+
.bm .fab {
|
|
147
|
+
position: absolute;
|
|
148
|
+
right: 5%;
|
|
149
|
+
bottom: 7%;
|
|
150
|
+
width: 34px;
|
|
151
|
+
height: 34px;
|
|
152
|
+
border-radius: 50%;
|
|
153
|
+
background: var(--c-orange-knvb);
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
justify-content: center;
|
|
157
|
+
box-shadow: var(--shadow-2);
|
|
158
|
+
animation: bmFabPress var(--bm-dur) linear infinite;
|
|
159
|
+
}
|
|
160
|
+
.bm .fab svg {
|
|
161
|
+
width: 15px;
|
|
162
|
+
height: 15px;
|
|
163
|
+
fill: none;
|
|
164
|
+
stroke: white;
|
|
165
|
+
stroke-width: 2;
|
|
166
|
+
stroke-linecap: round;
|
|
167
|
+
stroke-linejoin: round;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* ---- Dragged ghost ---- */
|
|
171
|
+
|
|
172
|
+
.bm .ghost {
|
|
173
|
+
position: absolute;
|
|
174
|
+
width: 22%;
|
|
175
|
+
left: 76%;
|
|
176
|
+
top: 30%;
|
|
177
|
+
background: white;
|
|
178
|
+
border: 1px solid var(--c-lavender-500);
|
|
179
|
+
border-radius: var(--radius-md);
|
|
180
|
+
box-shadow: var(--shadow-3);
|
|
181
|
+
padding: 8px 10px;
|
|
182
|
+
opacity: 0;
|
|
183
|
+
animation: bmGhost var(--bm-dur) linear infinite;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* ---- Cursor ---- */
|
|
187
|
+
|
|
188
|
+
.bm .cursor {
|
|
189
|
+
position: absolute;
|
|
190
|
+
width: 16px;
|
|
191
|
+
height: 16px;
|
|
192
|
+
left: 55%;
|
|
193
|
+
top: 42%;
|
|
194
|
+
fill: var(--c-cobalt-900);
|
|
195
|
+
stroke: white;
|
|
196
|
+
stroke-width: 1;
|
|
197
|
+
filter: drop-shadow(0 1px 2px rgba(10, 23, 47, 0.35));
|
|
198
|
+
animation: bmCursor var(--bm-dur) linear infinite;
|
|
199
|
+
z-index: 2;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* ---- Timeline keyframes ---- */
|
|
203
|
+
|
|
204
|
+
@keyframes bmCursor {
|
|
205
|
+
0% { left: 55%; top: 42%; }
|
|
206
|
+
12% { left: 88%; top: 82%; } /* over the FAB */
|
|
207
|
+
16% { left: 88%; top: 82%; } /* press */
|
|
208
|
+
24% { left: 82%; top: 36%; } /* over the middle palette tile */
|
|
209
|
+
30% { left: 82%; top: 36%; } /* grab */
|
|
210
|
+
58% { left: 26%; top: 66%; } /* drag to the slot */
|
|
211
|
+
64% { left: 26%; top: 66%; } /* release */
|
|
212
|
+
82% { left: 55%; top: 42%; }
|
|
213
|
+
100% { left: 55%; top: 42%; }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@keyframes bmFabPress {
|
|
217
|
+
0%, 11% { transform: scale(1); }
|
|
218
|
+
13% { transform: scale(0.85); }
|
|
219
|
+
16%, 100% { transform: scale(1); }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@keyframes bmPalette {
|
|
223
|
+
0%, 15% { opacity: 0; transform: translateX(10px); }
|
|
224
|
+
18%, 72% { opacity: 1; transform: translateX(0); }
|
|
225
|
+
78%, 100% { opacity: 0; transform: translateX(10px); }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@keyframes bmPaletteTile {
|
|
229
|
+
0%, 22% { background: var(--c-cobalt-50); border-color: var(--c-cobalt-100); }
|
|
230
|
+
26%, 34% { background: var(--c-cobalt-100); border-color: var(--c-lavender-500); }
|
|
231
|
+
38%, 100% { background: var(--c-cobalt-50); border-color: var(--c-cobalt-100); }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@keyframes bmGhost {
|
|
235
|
+
0%, 29% { opacity: 0; left: 76%; top: 30%; transform: scale(1); }
|
|
236
|
+
31% { opacity: 1; left: 76%; top: 30%; transform: scale(1.02); }
|
|
237
|
+
58% { opacity: 1; left: 12%; top: 58%; transform: scale(1.02); }
|
|
238
|
+
61% { opacity: 1; left: 12%; top: 60%; transform: scale(1); }
|
|
239
|
+
63% { opacity: 0; left: 12%; top: 60%; transform: scale(1); }
|
|
240
|
+
100% { opacity: 0; left: 12%; top: 60%; transform: scale(1); }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@keyframes bmSnapIn {
|
|
244
|
+
0%, 60% { opacity: 0; }
|
|
245
|
+
63%, 88% { opacity: 1; }
|
|
246
|
+
94%, 100% { opacity: 0; }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@keyframes bmSlotHint {
|
|
250
|
+
0%, 38% { border-color: var(--c-cobalt-200); }
|
|
251
|
+
44%, 58% { border-color: var(--c-lavender-500); }
|
|
252
|
+
63%, 100% { border-color: var(--c-cobalt-200); }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* ---- Static end state (running={false} and reduced motion) ----
|
|
256
|
+
* Component placed, palette closed, no cursor, no ghost. */
|
|
257
|
+
|
|
258
|
+
.bm .static .snapTile,
|
|
259
|
+
.bm .static .slot,
|
|
260
|
+
.bm .static .fab,
|
|
261
|
+
.bm .static .palette,
|
|
262
|
+
.bm .static .paletteTile,
|
|
263
|
+
.bm .static .ghost,
|
|
264
|
+
.bm .static .cursor { animation: none; }
|
|
265
|
+
.bm .static .snapTile { opacity: 1; }
|
|
266
|
+
.bm .static .slot {
|
|
267
|
+
border-style: solid;
|
|
268
|
+
border-color: var(--c-cobalt-100);
|
|
269
|
+
border-width: 1px;
|
|
270
|
+
background: var(--c-cobalt-50);
|
|
271
|
+
}
|
|
272
|
+
.bm .static .palette, .bm .static .ghost, .bm .static .cursor { opacity: 0; visibility: hidden; }
|
|
273
|
+
|
|
274
|
+
@media (prefers-reduced-motion: reduce) {
|
|
275
|
+
.bm .snapTile,
|
|
276
|
+
.bm .slot,
|
|
277
|
+
.bm .fab,
|
|
278
|
+
.bm .palette,
|
|
279
|
+
.bm .paletteTile,
|
|
280
|
+
.bm .ghost,
|
|
281
|
+
.bm .cursor { animation: none; }
|
|
282
|
+
.bm .snapTile { opacity: 1; }
|
|
283
|
+
.bm .slot {
|
|
284
|
+
border-style: solid;
|
|
285
|
+
border-color: var(--c-cobalt-100);
|
|
286
|
+
border-width: 1px;
|
|
287
|
+
background: var(--c-cobalt-50);
|
|
288
|
+
}
|
|
289
|
+
.bm .palette, .bm .ghost, .bm .cursor { opacity: 0; visibility: hidden; }
|
|
290
|
+
}
|
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
* (or secondary) variant to the KNVB-orange accent. Reserved for
|
|
39
39
|
* product pages with an orange-leaning brand identity (launchpad).
|
|
40
40
|
*
|
|
41
|
+
* GitHub lives in the badge row, not the CTA row: the downloads
|
|
42
|
+
* counter links to the app's repository and a "View on GitHub" chip
|
|
43
|
+
* sits next to it (both new-tab). The URL comes from the `repoHref`
|
|
44
|
+
* prop when given; otherwise a GitHub-pointing `tertiaryCta` href is
|
|
45
|
+
* reused (and that tertiary is then dropped from the CTA row, so
|
|
46
|
+
* existing pages upgrade without edits); otherwise it falls back to
|
|
47
|
+
* https://github.com/ConductionNL/{appId}. The CTA row therefore
|
|
48
|
+
* normally holds just the primary + secondary pair; a non-GitHub
|
|
49
|
+
* tertiary still renders for compatibility.
|
|
50
|
+
*
|
|
41
51
|
* `background="cobalt"` paints the hero in a full-bleed cobalt panel
|
|
42
52
|
* with white type — the product-page identity used on the
|
|
43
53
|
* {slug}.conduction.nl landings. Default (undefined) keeps the
|
|
@@ -85,8 +95,26 @@ export default function DetailHero({
|
|
|
85
95
|
appId,
|
|
86
96
|
downloads,
|
|
87
97
|
background,
|
|
98
|
+
repoHref,
|
|
88
99
|
}) {
|
|
89
100
|
const dlCount = downloads != null ? downloads : (appId ? downloadsForApp(appId) : 0);
|
|
101
|
+
/* GitHub repo link for the badge row. Priority: explicit `repoHref`
|
|
102
|
+
prop → a GitHub-pointing tertiaryCta (the old "View on GitHub"
|
|
103
|
+
ghost button, which this hero now renders as a meta-row chip
|
|
104
|
+
instead of a third CTA) → the ConductionNL org default for the
|
|
105
|
+
appId. Both the downloads counter and the "View on GitHub" chip
|
|
106
|
+
link here, in a new tab. */
|
|
107
|
+
const tertiaryIsRepo = Boolean(
|
|
108
|
+
tertiaryCta && typeof tertiaryCta.href === 'string' && tertiaryCta.href.includes('github.com')
|
|
109
|
+
);
|
|
110
|
+
const resolvedRepoHref = repoHref
|
|
111
|
+
|| (tertiaryIsRepo ? tertiaryCta.href : undefined)
|
|
112
|
+
|| (appId ? `https://github.com/ConductionNL/${appId}` : undefined);
|
|
113
|
+
/* A GitHub tertiary CTA is "moved up top": it renders as the meta-row
|
|
114
|
+
chip and disappears from the CTA row, which then holds just the
|
|
115
|
+
primary + secondary pair. Any other tertiary (docs, demo, ...)
|
|
116
|
+
keeps rendering as before for compatibility. */
|
|
117
|
+
const renderedTertiaryCta = tertiaryIsRepo ? null : tertiaryCta;
|
|
90
118
|
const hasIllustration = Boolean(illustration);
|
|
91
119
|
/* Default the title mark to the canonical app glyph (the same logo
|
|
92
120
|
served on identity.conduction.nl/apps) when the caller doesn't pass
|
|
@@ -238,7 +266,7 @@ export default function DetailHero({
|
|
|
238
266
|
|
|
239
267
|
<div className={styles.headInner}>
|
|
240
268
|
<div className={styles.copy}>
|
|
241
|
-
{(resolvedStatus || resolvedVersion || locales || dlCount > 0) && (
|
|
269
|
+
{(resolvedStatus || resolvedVersion || locales || dlCount > 0 || resolvedRepoHref) && (
|
|
242
270
|
<div className={styles.badgeRow}>
|
|
243
271
|
{resolvedStatus && (
|
|
244
272
|
<span className={styles.badge}>
|
|
@@ -248,17 +276,39 @@ export default function DetailHero({
|
|
|
248
276
|
)}
|
|
249
277
|
{resolvedVersion && <span className={[styles.badge, styles.versionBadge].join(' ')}>{resolvedVersion}</span>}
|
|
250
278
|
{locales && <span className={[styles.badge, styles.versionBadge].join(' ')}>{locales}</span>}
|
|
251
|
-
{dlCount > 0 && (
|
|
252
|
-
|
|
279
|
+
{dlCount > 0 && (() => {
|
|
280
|
+
/* The downloads counter links to the repo when one
|
|
281
|
+
resolves; a plain chip otherwise. */
|
|
282
|
+
const DlTag = resolvedRepoHref ? 'a' : 'span';
|
|
283
|
+
const dlLinkProps = resolvedRepoHref
|
|
284
|
+
? {href: resolvedRepoHref, target: '_blank', rel: 'noopener noreferrer'}
|
|
285
|
+
: {};
|
|
286
|
+
return (
|
|
287
|
+
<DlTag
|
|
288
|
+
className={[styles.badge, styles.downloadsBadge].join(' ')}
|
|
289
|
+
title="Total release-asset downloads from GitHub. Updated weekdays at 09:00."
|
|
290
|
+
data-app-downloads={appId || ''}
|
|
291
|
+
{...dlLinkProps}
|
|
292
|
+
>
|
|
293
|
+
<svg className={styles.downloadIcon} viewBox="0 0 24 24" aria-hidden="true">
|
|
294
|
+
<path d="M12 3v12m0 0l-5-5m5 5l5-5M5 21h14"/>
|
|
295
|
+
</svg>
|
|
296
|
+
{formatDownloads(dlCount)} downloads
|
|
297
|
+
</DlTag>
|
|
298
|
+
);
|
|
299
|
+
})()}
|
|
300
|
+
{resolvedRepoHref && (
|
|
301
|
+
<a
|
|
253
302
|
className={[styles.badge, styles.downloadsBadge].join(' ')}
|
|
254
|
-
|
|
255
|
-
|
|
303
|
+
href={resolvedRepoHref}
|
|
304
|
+
target="_blank"
|
|
305
|
+
rel="noopener noreferrer"
|
|
256
306
|
>
|
|
257
|
-
<svg className={styles.
|
|
258
|
-
<path d="
|
|
307
|
+
<svg className={styles.repoIcon} viewBox="0 0 16 16" aria-hidden="true">
|
|
308
|
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
|
259
309
|
</svg>
|
|
260
|
-
|
|
261
|
-
</
|
|
310
|
+
View on GitHub
|
|
311
|
+
</a>
|
|
262
312
|
)}
|
|
263
313
|
</div>
|
|
264
314
|
)}
|
|
@@ -280,7 +330,7 @@ export default function DetailHero({
|
|
|
280
330
|
{tagline && <p className={styles.tagline}>{tagline}</p>}
|
|
281
331
|
{intro && <div className={styles.intro}>{intro}</div>}
|
|
282
332
|
|
|
283
|
-
{(primaryCta || secondaryCta ||
|
|
333
|
+
{(primaryCta || secondaryCta || renderedTertiaryCta) && (
|
|
284
334
|
<div className={styles.actions}>
|
|
285
335
|
{primaryCta && (
|
|
286
336
|
<Button
|
|
@@ -306,19 +356,21 @@ export default function DetailHero({
|
|
|
306
356
|
{secondaryCta.label}
|
|
307
357
|
</Button>
|
|
308
358
|
)}
|
|
309
|
-
{
|
|
359
|
+
{renderedTertiaryCta && (
|
|
310
360
|
/* On a cobalt-bg hero the default ghost variant
|
|
311
361
|
(cobalt-700 text) disappears against the dark panel;
|
|
312
362
|
auto-switch to on-dark-tertiary (white text + white
|
|
313
363
|
border) so the CTA reads at parity with the primary
|
|
314
364
|
and secondary buttons. Sites can still pass an
|
|
315
|
-
explicit `variant` to opt out.
|
|
365
|
+
explicit `variant` to opt out. GitHub-pointing
|
|
366
|
+
tertiaries never reach this row: they render as the
|
|
367
|
+
"View on GitHub" chip in the badge row instead. */
|
|
316
368
|
<Button
|
|
317
|
-
variant={
|
|
318
|
-
href={
|
|
319
|
-
icon={
|
|
369
|
+
variant={renderedTertiaryCta.variant || (background === 'cobalt' ? 'on-dark-tertiary' : 'ghost')}
|
|
370
|
+
href={renderedTertiaryCta.href}
|
|
371
|
+
icon={renderedTertiaryCta.icon}
|
|
320
372
|
>
|
|
321
|
-
{
|
|
373
|
+
{renderedTertiaryCta.label} →
|
|
322
374
|
</Button>
|
|
323
375
|
)}
|
|
324
376
|
</div>
|
|
@@ -37,7 +37,22 @@
|
|
|
37
37
|
.bgCobalt .sep { color: var(--c-cobalt-300); }
|
|
38
38
|
.bgCobalt .badge { color: var(--c-cobalt-100); }
|
|
39
39
|
.bgCobalt .versionBadge { color: var(--c-cobalt-200); }
|
|
40
|
-
|
|
40
|
+
/* On the cobalt hero the default chip (cobalt text on a cobalt-50
|
|
41
|
+
plate) inverts to full-opacity white text on a translucent white
|
|
42
|
+
chip. The 16%-white plate blends to #45649E over cobalt, which
|
|
43
|
+
keeps white text at 5.9:1 — above the 4.5:1 WCAG AA floor. The
|
|
44
|
+
earlier rule only recoloured the text and left the light plate,
|
|
45
|
+
which made the chip near-invisible on the blue panel. */
|
|
46
|
+
.bgCobalt .downloadsBadge {
|
|
47
|
+
color: white;
|
|
48
|
+
background: rgba(255, 255, 255, 0.16);
|
|
49
|
+
border-color: rgba(255, 255, 255, 0.35);
|
|
50
|
+
}
|
|
51
|
+
.bgCobalt a.downloadsBadge:hover {
|
|
52
|
+
color: white;
|
|
53
|
+
background: rgba(255, 255, 255, 0.26);
|
|
54
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
55
|
+
}
|
|
41
56
|
.bgCobalt .title,
|
|
42
57
|
.bgCobalt .titleText { color: white; }
|
|
43
58
|
.bgCobalt .tagline { color: var(--c-cobalt-100); }
|
|
@@ -91,6 +106,9 @@
|
|
|
91
106
|
}
|
|
92
107
|
.versionBadge { color: var(--c-cobalt-400); }
|
|
93
108
|
|
|
109
|
+
/* Meta-row chip. Shared by the downloads counter and the "View on
|
|
110
|
+
GitHub" link; both are usually <a> elements pointing at the app's
|
|
111
|
+
repository, so the chip carries link resets + a hover state. */
|
|
94
112
|
.downloadsBadge {
|
|
95
113
|
color: var(--c-blue-cobalt);
|
|
96
114
|
background: var(--c-cobalt-50);
|
|
@@ -98,6 +116,13 @@
|
|
|
98
116
|
border-radius: var(--radius-sm);
|
|
99
117
|
padding: 4px 10px;
|
|
100
118
|
letter-spacing: 0.04em;
|
|
119
|
+
text-decoration: none;
|
|
120
|
+
transition: background 120ms ease, border-color 120ms ease;
|
|
121
|
+
}
|
|
122
|
+
a.downloadsBadge:hover {
|
|
123
|
+
color: var(--c-blue-cobalt);
|
|
124
|
+
background: var(--c-cobalt-100);
|
|
125
|
+
border-color: var(--c-cobalt-200);
|
|
101
126
|
}
|
|
102
127
|
.downloadIcon {
|
|
103
128
|
width: 12px;
|
|
@@ -108,6 +133,11 @@
|
|
|
108
133
|
stroke-linecap: round;
|
|
109
134
|
stroke-linejoin: round;
|
|
110
135
|
}
|
|
136
|
+
.repoIcon {
|
|
137
|
+
width: 12px;
|
|
138
|
+
height: 12px;
|
|
139
|
+
fill: currentColor;
|
|
140
|
+
}
|
|
111
141
|
|
|
112
142
|
.title {
|
|
113
143
|
font-size: 64px;
|