@foldspace_npm/harness 0.1.31 → 0.1.33

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.
Files changed (41) hide show
  1. package/CLAUDE.md +27 -8
  2. package/README.md +25 -2
  3. package/bin/ask.mjs +16 -145
  4. package/bin/attach.mjs +2 -0
  5. package/bin/cli.mjs +22 -0
  6. package/bin/inject.mjs +2 -14
  7. package/bin/record.mjs +412 -0
  8. package/bin/share.mjs +63 -0
  9. package/package.json +1 -1
  10. package/src/attach-daemon.mjs +18 -0
  11. package/src/badge-auto.mjs +38 -1
  12. package/src/cdp-client.mjs +7 -1
  13. package/src/chrome-binary.mjs +28 -0
  14. package/src/cli-help.mjs +1 -0
  15. package/src/cli-registry.mjs +97 -3
  16. package/src/init.mjs +170 -16
  17. package/src/panel-driver.mjs +283 -0
  18. package/src/session-events.mjs +1 -0
  19. package/src/upgrade.mjs +87 -1
  20. package/src/video/README.md +29 -0
  21. package/src/video/assets/foldspace-mark.svg +27 -0
  22. package/src/video/assets/logo-dark.svg +68 -0
  23. package/src/video/assets/logo-light.svg +56 -0
  24. package/src/video/brand.mjs +46 -0
  25. package/src/video/compose.mjs +476 -0
  26. package/src/video/composer/composer.mjs +554 -0
  27. package/src/video/composer/index.html +13 -0
  28. package/src/video/fonts/OFL.txt +93 -0
  29. package/src/video/fonts/Poppins-ExtraBold.woff2 +0 -0
  30. package/src/video/fonts/Poppins-Regular.woff2 +0 -0
  31. package/src/video/fonts/Poppins-SemiBold.woff2 +0 -0
  32. package/src/video/recorder.mjs +331 -0
  33. package/src/video/scenes.mjs +253 -0
  34. package/src/video/share.mjs +284 -0
  35. package/src/video/timeline.mjs +726 -0
  36. package/src/video/vendor/mp4-muxer.LICENSE +21 -0
  37. package/src/video/vendor/mp4-muxer.min.mjs +5 -0
  38. package/templates/agent-starter/README.md +5 -0
  39. package/templates/agent-starter/claude-settings.json +1 -0
  40. package/templates/agent-starter/gitignore +3 -0
  41. package/templates/agent-starter/skills/share-a-video/SKILL.md +271 -0
@@ -13,6 +13,7 @@ export const SESSION_EVENTS = Object.freeze([
13
13
  "observe",
14
14
  "ask",
15
15
  "run",
16
+ "record",
16
17
  ]);
17
18
 
18
19
  export function eventsPath(projectDir) {
package/src/upgrade.mjs CHANGED
@@ -18,6 +18,8 @@ const instructionTemplatePath = path.join(
18
18
  "agent-starter",
19
19
  "CLAUDE.md",
20
20
  );
21
+ // Flat in the template (as claude-settings.json is); .claude/skills/ in a project.
22
+ const skillsTemplateRoot = path.join(packageRoot, "templates", "agent-starter", "skills");
21
23
  const tokenPattern = /\{\{([A-Z0-9_]+)\}\}/g;
22
24
 
23
25
  export function resolveProjectDir(env = process.env, cwd = process.cwd()) {
@@ -208,6 +210,84 @@ export function refreshInstructionStub(projectDir, options = {}) {
208
210
  return { written: true, backedUp, importPresent: true };
209
211
  }
210
212
 
213
+ /** Every file of the harness's skills: [{ rel, source }], rel under .claude/skills/. */
214
+ function shippedSkillFiles(skillsRoot) {
215
+ const files = [];
216
+ if (!fs.existsSync(skillsRoot)) return files;
217
+ const walk = (relative) => {
218
+ for (const entry of fs.readdirSync(path.join(skillsRoot, relative), { withFileTypes: true })) {
219
+ const rel = path.join(relative, entry.name);
220
+ if (entry.isDirectory()) walk(rel);
221
+ else if (entry.isFile()) files.push({ rel, source: path.join(skillsRoot, rel) });
222
+ }
223
+ };
224
+ walk("");
225
+ return files;
226
+ }
227
+
228
+ /**
229
+ * The skills this harness ships, written into the project's .claude/skills/
230
+ * from the installed package: `init` writes them with the rest of the
231
+ * template, and an upgrade brings them up to date the same way. Only the
232
+ * harness's own skill folders are written; any other skill there is left
233
+ * alone. They carry no template tokens, so they are copied as they are. A
234
+ * copy that differs is kept beside it as <file>.bak first - it may hold the
235
+ * human's own edits. Returns { written, backedUp }, paths relative to the
236
+ * project.
237
+ */
238
+ export function refreshProjectSkills(projectDir, { skillsRoot = skillsTemplateRoot } = {}) {
239
+ const written = [];
240
+ const backedUp = [];
241
+ for (const { rel, source } of shippedSkillFiles(skillsRoot)) {
242
+ const target = path.join(projectDir, ".claude", "skills", rel);
243
+ const content = fs.readFileSync(source);
244
+ let current = null;
245
+ try {
246
+ current = fs.readFileSync(target);
247
+ } catch {
248
+ current = null;
249
+ }
250
+ if (current && current.equals(content)) continue;
251
+ fs.mkdirSync(path.dirname(target), { recursive: true });
252
+ if (current) {
253
+ fs.writeFileSync(`${target}.bak`, current);
254
+ backedUp.push(path.join(".claude", "skills", `${rel}.bak`));
255
+ }
256
+ fs.writeFileSync(target, content);
257
+ written.push(path.join(".claude", "skills", rel));
258
+ }
259
+ return { written, backedUp };
260
+ }
261
+
262
+ /**
263
+ * Write any of the harness's skill files the project does not have yet, and
264
+ * touch nothing else. Every command runs it: `upgrade --yes` runs the harness
265
+ * version that was installed before, which may come from before the skills
266
+ * shipped, so the first upgrade to this one cannot write them - the next
267
+ * command does. Only in a Foldspace project (foldspace.dev.json). Returns the
268
+ * paths written; never throws.
269
+ */
270
+ export function ensureProjectSkills(projectDir, { skillsRoot = skillsTemplateRoot } = {}) {
271
+ const written = [];
272
+ try {
273
+ if (!fs.existsSync(path.join(projectDir, "foldspace.dev.json"))) return written;
274
+ for (const { rel, source } of shippedSkillFiles(skillsRoot)) {
275
+ const target = path.join(projectDir, ".claude", "skills", rel);
276
+ if (fs.existsSync(target)) continue;
277
+ fs.mkdirSync(path.dirname(target), { recursive: true });
278
+ fs.copyFileSync(source, target);
279
+ written.push(path.join(".claude", "skills", rel));
280
+ }
281
+ } catch {
282
+ // a read-only or odd project folder: the manual names the shipped copy too
283
+ }
284
+ return written;
285
+ }
286
+
287
+ function skillsReport(result) {
288
+ return result.backedUp.length ? { skills: result.written, skillsBackedUp: result.backedUp } : { skills: result.written };
289
+ }
290
+
211
291
  function cachePath(projectDir) {
212
292
  return path.join(projectDir, ".foldspace-dev", "harness-update.json");
213
293
  }
@@ -483,6 +563,7 @@ export async function runUpgrade(argv, options = {}) {
483
563
  instructionsImport: true,
484
564
  refreshed: refreshed.written,
485
565
  backedUp: refreshed.backedUp,
566
+ ...skillsReport(refreshProjectSkills(projectDir, options)),
486
567
  next: refreshed.written
487
568
  ? "Imported platform instructions from the installed harness."
488
569
  : report.next,
@@ -525,7 +606,9 @@ export async function runUpgrade(argv, options = {}) {
525
606
  }
526
607
 
527
608
  if (!shouldInstall && !shouldRefresh) {
528
- return report;
609
+ // Already on the latest: --yes still brings the skills up to date, for a
610
+ // project whose last upgrade ran a harness from before they shipped.
611
+ return parsed.yes ? { ...report, ...skillsReport(refreshProjectSkills(projectDir, options)) } : report;
529
612
  }
530
613
 
531
614
  if (shouldInstall) {
@@ -541,6 +624,8 @@ export async function runUpgrade(argv, options = {}) {
541
624
  if (shouldRefresh) {
542
625
  refreshed = refreshInstructionStub(projectDir, options);
543
626
  }
627
+ // After the install, this package's folder holds the new version's files.
628
+ const skills = skillsReport(refreshProjectSkills(projectDir, options));
544
629
 
545
630
  const nextReport = {
546
631
  ...report,
@@ -551,6 +636,7 @@ export async function runUpgrade(argv, options = {}) {
551
636
  instructionsImport: shouldRefresh ? true : report.instructionsImport,
552
637
  refreshed: refreshed.written,
553
638
  backedUp: refreshed.backedUp,
639
+ ...skills,
554
640
  };
555
641
  nextReport.next = nextAction(nextReport);
556
642
  return nextReport;
@@ -0,0 +1,29 @@
1
+ # src/video - what `foldspace record` is made of
2
+
3
+ | File | What it is |
4
+ |---|---|
5
+ | `recorder.mjs` | One scene: a new conversation, the question typed key by key, the answer, recorded with Chrome's screencast into `.foldspace-dev/record/` |
6
+ | `timeline.mjs` | Pure timing and layout: shortened waits, caption fades, dissolves, the caption card (its rows, the line wrapped evenly in two, the `*highlights*`), where the logo goes, colour contrast. Runs in Node (tests) and in the composer page |
7
+ | `scenes.mjs` | `video/scenes.json`: checking it, the five capability names (one list: renaming a pill is a one-line change), each caption's "2 of 4", the file name |
8
+ | `compose.mjs` | The plan, a short-lived 127.0.0.1 server and a hidden Chrome that makes the MP4 |
9
+ | `composer/` | The page that draws each frame on a canvas, encodes H.264 with WebCodecs and muxes |
10
+ | `brand.mjs` | Foldspace's brand, as its product video's storyboard draws it - its one blue on black, white, one muted grey - the caption card's fill and edge, and the logo files. Never the app's colours |
11
+
12
+ Nothing is installed and nothing native runs: the video is encoded by the
13
+ Google Chrome that is already there.
14
+
15
+ ## Vendored, with their licences
16
+
17
+ - `vendor/mp4-muxer.min.mjs` - mp4-muxer 5.2.2 (MIT, `vendor/mp4-muxer.LICENSE`),
18
+ from the npm registry (tarball sha512
19
+ `dhozjTywI0h2qFzeShagt8YYw811fh1XlwiDCE2f6Aeqf6xG2CyuShoSa5E0AZDO8pPF0JOZ3wOmWBNWIGdSpQ==`),
20
+ `build/mp4-muxer.mjs` minified with esbuild. Not a dependency: the file is
21
+ loaded only by the composer page.
22
+ - `fonts/Poppins-{Regular,SemiBold,ExtraBold}.woff2` - Poppins from the
23
+ google/fonts repository (`ofl/poppins`), converted from TTF to WOFF2 unchanged
24
+ otherwise; SIL Open Font License 1.1 in `fonts/OFL.txt`.
25
+ - `assets/` - the official Foldspace logos, unchanged, from the website
26
+ (foldspace-ai/marketing-site): `logo-dark.svg` (src/assets: the white
27
+ wordmark, for dark backgrounds), `logo-light.svg` (src/assets: the dark
28
+ wordmark, for light backgrounds) and `foldspace-mark.svg` (public/favicon.svg:
29
+ the blue mark).
@@ -0,0 +1,27 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Foldspace_icon_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 39.89 40">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill: #fff;
7
+ }
8
+
9
+ .cls-2 {
10
+ fill: #1842EF;
11
+ }
12
+
13
+ .cls-3 {
14
+ opacity: .5;
15
+ }
16
+ </style>
17
+ </defs>
18
+ <path class="cls-2" d="M21.31,18.6S23.08,0,18.84,0C14.31,0,0,14.51,0,18.84s18.57,2.55,18.57,2.55c0,0-1.76,18.61,2.47,18.61,4.54,0,18.84-14.51,18.84-18.84s-18.57-2.55-18.57-2.55Z"/>
19
+ <g class="cls-3">
20
+ <path class="cls-1" d="M21.32,18.6c-3.54.26-15.04.77-15.04-1.8,0-2.77,7.23-9.57,15.06-12.2.78,5.51-.02,14.01-.02,14.01h0Z"/>
21
+ <path class="cls-1" d="M18.57,21.39c3.54-.25,15.04-.77,15.04,1.8,0,2.77-7.23,9.57-15.06,12.2-.78-5.51.02-14,.02-14h0Z"/>
22
+ </g>
23
+ <g class="cls-3">
24
+ <path class="cls-1" d="M21.32,18.6c-2.36.13-7.66-1.08-7.66-2.66s4.7-4.41,8.03-5.37c0,4.19-.36,8.02-.36,8.02h0Z"/>
25
+ <path class="cls-1" d="M18.58,21.39c2.36-.14,7.67,1.07,7.67,2.66s-4.7,4.41-8.03,5.37c0-4.19.36-8.02.36-8.02h0Z"/>
26
+ </g>
27
+ </svg>
@@ -0,0 +1,68 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Foldspace wordmark, white — for dark backgrounds. Cloned from logo-light.svg (the fixed
3
+ black export): identical geometry, wordmark and gradient effects inverted to white, and
4
+ the blue mark on the left left exactly as-is. -->
5
+ <svg id="Foldspace_logo_3" data-name="Foldspace logo 3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 383 40">
6
+ <defs>
7
+ <radialGradient id="radial-gradient-w" cx="-260.9" cy="-217.59" fx="-260.9" fy="-217.59" r="24.52" gradientTransform="matrix(1.999999, 0.002443, -0.000086, 0.07, 847.280029, 36.614626)" gradientUnits="userSpaceOnUse">
8
+ <stop offset="0" stop-color="#fff"/>
9
+ <stop offset=".14" stop-color="#fff" stop-opacity=".79"/>
10
+ <stop offset=".31" stop-color="#fff" stop-opacity=".56"/>
11
+ <stop offset=".48" stop-color="#fff" stop-opacity=".36"/>
12
+ <stop offset=".64" stop-color="#fff" stop-opacity=".2"/>
13
+ <stop offset=".78" stop-color="#fff" stop-opacity=".09"/>
14
+ <stop offset=".91" stop-color="#fff" stop-opacity=".02"/>
15
+ <stop offset="1" stop-color="#fff" stop-opacity="0"/>
16
+ </radialGradient>
17
+ <radialGradient id="radial-gradient-w-2" cx="-42.52" cy="252.22" fx="-42.52" fy="252.22" r="24.52" gradientTransform="matrix(1.379999, 0.001686, -0.000061, 0.05, 385.178223, 6.85996)" href="#radial-gradient-w" xlink:href="#radial-gradient-w"/>
18
+ <radialGradient id="radial-gradient-w-3" cx="1130.48" cy="503.88" fx="1130.48" fy="503.88" r="2" gradientTransform="matrix(-3.531012, -0.924368, 1.219353, -2.668178, 3648.81282, 2350.485806)" gradientUnits="userSpaceOnUse">
19
+ <stop offset="0" stop-color="#fff"/>
20
+ <stop offset=".17" stop-color="#fff" stop-opacity=".78"/>
21
+ <stop offset=".4" stop-color="#fff" stop-opacity=".5"/>
22
+ <stop offset=".6" stop-color="#fff" stop-opacity=".29"/>
23
+ <stop offset=".78" stop-color="#fff" stop-opacity=".13"/>
24
+ <stop offset=".92" stop-color="#fff" stop-opacity=".04"/>
25
+ <stop offset="1" stop-color="#fff" stop-opacity="0"/>
26
+ </radialGradient>
27
+ </defs>
28
+ <g id="Foldspace_logo_3_art" transform="matrix(1, 0, 0, 1, 26.598669, -202.805344)">
29
+ <g transform="matrix(1, 0, 0, 1, -26.598669, 202.805344)">
30
+ <path class="cls-5" d="M21.31,18.6S23.08,0,18.84,0C14.31,0,0,14.51,0,18.84s18.57,2.55,18.57,2.55c0,0-1.76,18.61,2.47,18.61,4.54,0,18.84-14.51,18.84-18.84s-18.57-2.55-18.57-2.55Z" style="fill: rgb(24, 66, 239);"/>
31
+ <g class="cls-6" style="opacity: 0.5;">
32
+ <path class="cls-1" d="M21.32,18.6c-3.54.26-15.04.77-15.04-1.8,0-2.77,7.23-9.57,15.06-12.2.78,5.51-.02,14.01-.02,14.01h0Z" style="fill: rgb(255, 255, 255);"/>
33
+ <path class="cls-1" d="M18.57,21.39c3.54-.25,15.04-.77,15.04,1.8,0,2.77-7.23,9.57-15.06,12.2-.78-5.51.02-14,.02-14h0Z" style="fill: rgb(255, 255, 255);"/>
34
+ </g>
35
+ <g class="cls-6" style="opacity: 0.5;">
36
+ <path class="cls-1" d="M21.32,18.6c-2.36.13-7.66-1.08-7.66-2.66s4.7-4.41,8.03-5.37c0,4.19-.36,8.02-.36,8.02h0Z" style="fill: rgb(255, 255, 255);"/>
37
+ <path class="cls-1" d="M18.58,21.39c2.36-.14,7.67,1.07,7.67,2.66s-4.7,4.41-8.03,5.37c0-4.19.36-8.02.36-8.02h0Z" style="fill: rgb(255, 255, 255);"/>
38
+ </g>
39
+ </g>
40
+ <g transform="matrix(1, 0, 0, 1, -26.598669, 202.805344)" fill="#fff">
41
+ <g>
42
+ <polygon points="53.77 30 55.91 30 55.91 21.21 68.29 21.21 68.29 19.07 53.77 19.06 53.77 30"/>
43
+ <rect x="53.77" y="10.28" width="19.72" height="2.14"/>
44
+ </g>
45
+ <path d="M94.82,10.28c-5.44,0-9.86,4.42-9.86,9.86s4.42,9.86,9.86,9.86,9.86-4.42,9.86-9.86-4.42-9.86-9.86-9.86ZM102.54,20.14c0,4.25-3.46,7.72-7.72,7.72s-7.72-3.46-7.72-7.72,3.46-7.72,7.72-7.72,7.72,3.46,7.72,7.72Z"/>
46
+ <polygon points="120.71 10.28 118.56 10.28 118.56 30 138.28 30 138.28 27.86 120.71 27.86 120.71 10.28"/>
47
+ <path d="M162.02,10.28h-9.86v2.14h9.86c4.25,0,7.72,3.46,7.72,7.72s-3.46,7.72-7.72,7.72h-9.86v2.14h9.86c5.44,0,9.86-4.42,9.86-9.86s-4.42-9.86-9.86-9.86Z"/>
48
+ <path d="M185.76,15.74c0,3.01,2.45,5.47,5.47,5.47h8.79c1.83,0,3.32,1.49,3.32,3.32s-1.49,3.32-3.32,3.32h-14.26v2.14h14.26c3.01,0,5.47-2.45,5.47-5.47s-2.45-5.47-5.47-5.47h-8.79c-1.83,0-3.32-1.49-3.32-3.32s1.49-3.32,3.32-3.32h14.26v-2.14h-14.26c-3.01,0-5.47,2.45-5.47,5.47Z"/>
49
+ <path d="M233.61,10.28h-14.26v2.14h14.26c1.83,0,3.32,1.49,3.32,3.32s-1.49,3.32-3.32,3.32h-14.26v10.93h2.15v-8.79h12.11c3.01,0,5.47-2.45,5.47-5.47s-2.45-5.47-5.47-5.47Z"/>
50
+ <polygon points="247.96 30 250.53 30 259.2 14.58 267.86 30 270.44 30 259.21 10 247.96 30"/>
51
+ <path d="M280.86,20.14c0,5.44,4.42,9.86,9.86,9.86h9.86v-2.14h-9.86c-4.25,0-7.72-3.46-7.72-7.72s3.46-7.72,7.72-7.72h9.86v-2.14h-9.86c-5.44,0-9.86,4.42-9.86,9.86Z"/>
52
+ <g>
53
+ <path class="cls-2" d="M 383.35 21.284 C 383.35 22.364 357.97 23.214 326.66 23.174 C 295.35 23.134 269.98 22.224 269.98 21.144 C 269.98 20.064 295.36 19.214 326.67 19.254 C 357.98 19.294 383.35 20.204 383.35 21.284 Z" style="fill: url(&quot;#radial-gradient-w&quot;);"/>
54
+ <path class="cls-4" d="M 365.688 19.379 C 365.688 20.169 348.148 20.789 326.508 20.769 C 304.868 20.739 287.328 20.079 287.338 19.289 C 287.338 18.499 304.878 17.879 326.518 17.899 C 348.158 17.929 365.698 18.589 365.688 19.379 Z" style="fill: url(&quot;#radial-gradient-w-2&quot;);"/>
55
+ <path d="M324.32,12.42h9.86v-2.14h-9.86c-5.44,0-9.86,4.42-9.86,9.86s4.42,9.86,9.86,9.86h9.86v-2.14h-9.86c-4.25,0-7.72-3.46-7.72-7.72s3.46-7.72,7.72-7.72Z"/>
56
+ <circle cx="326.67" cy="20.14" r="1.2"/>
57
+ </g>
58
+ </g>
59
+ </g>
60
+ <g id="svg-1" fill="#fff">
61
+ <g transform="matrix(1, 0, 0, 1, 55.221105, 59.088541)">
62
+ <g>
63
+ <path class="cls-3" d="M 264.114 -42.099 C 266.374 -44.822 292.155 -45.685 287.424 -34.392 C 284.798 -28.123 262.07 -35.124 261.921 -34.993" style="fill: url(&quot;#radial-gradient-w-3&quot;);"/>
64
+ <circle cx="271.424" cy="-38.941" r="1.2"/>
65
+ </g>
66
+ </g>
67
+ </g>
68
+ </svg>
@@ -0,0 +1,56 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Foldspace wordmark, black — for light backgrounds. Source: foldspace-black-fixed-08-2026-1.svg,
3
+ normalized here so the artwork sits at the viewBox origin (0 0 383 40). -->
4
+ <svg id="Foldspace_logo_1" data-name="Foldspace logo 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 383 40">
5
+ <defs>
6
+ <radialGradient id="radial-gradient" cx="-260.9" cy="-217.59" fx="-260.9" fy="-217.59" r="24.52" gradientTransform="matrix(1.999999, 0.002443, -0.000086, 0.07, 847.280029, 36.614626)" gradientUnits="userSpaceOnUse">
7
+ <stop offset="0" stop-color="#000"/>
8
+ <stop offset="1" stop-color="#fff" stop-opacity="0"/>
9
+ </radialGradient>
10
+ <radialGradient id="radial-gradient-2" cx="-42.52" cy="252.22" fx="-42.52" fy="252.22" r="24.52" gradientTransform="matrix(1.379999, 0.001686, -0.000061, 0.05, 385.178223, 6.85996)" href="#radial-gradient" xlink:href="#radial-gradient"/>
11
+ <radialGradient id="radial-gradient-3" cx="1130.48" cy="503.88" fx="1130.48" fy="503.88" r="2" gradientTransform="matrix(-3.531012, -0.924368, 1.219353, -2.668178, 3648.81282, 2350.485806)" gradientUnits="userSpaceOnUse">
12
+ <stop offset="0" stop-color="#000"/>
13
+ <stop offset="1" stop-color="#fff" stop-opacity="0"/>
14
+ </radialGradient>
15
+ </defs>
16
+ <g id="Foldspace_logo_1_art" transform="matrix(1, 0, 0, 1, 26.598669, -202.805344)">
17
+ <g transform="matrix(1, 0, 0, 1, -26.598669, 202.805344)">
18
+ <path class="cls-5" d="M21.31,18.6S23.08,0,18.84,0C14.31,0,0,14.51,0,18.84s18.57,2.55,18.57,2.55c0,0-1.76,18.61,2.47,18.61,4.54,0,18.84-14.51,18.84-18.84s-18.57-2.55-18.57-2.55Z" style="fill: rgb(24, 66, 239);"/>
19
+ <g class="cls-6" style="opacity: 0.5;">
20
+ <path class="cls-1" d="M21.32,18.6c-3.54.26-15.04.77-15.04-1.8,0-2.77,7.23-9.57,15.06-12.2.78,5.51-.02,14.01-.02,14.01h0Z" style="fill: rgb(255, 255, 255);"/>
21
+ <path class="cls-1" d="M18.57,21.39c3.54-.25,15.04-.77,15.04,1.8,0,2.77-7.23,9.57-15.06,12.2-.78-5.51.02-14,.02-14h0Z" style="fill: rgb(255, 255, 255);"/>
22
+ </g>
23
+ <g class="cls-6" style="opacity: 0.5;">
24
+ <path class="cls-1" d="M21.32,18.6c-2.36.13-7.66-1.08-7.66-2.66s4.7-4.41,8.03-5.37c0,4.19-.36,8.02-.36,8.02h0Z" style="fill: rgb(255, 255, 255);"/>
25
+ <path class="cls-1" d="M18.58,21.39c2.36-.14,7.67,1.07,7.67,2.66s-4.7,4.41-8.03,5.37c0-4.19.36-8.02.36-8.02h0Z" style="fill: rgb(255, 255, 255);"/>
26
+ </g>
27
+ </g>
28
+ <g transform="matrix(1, 0, 0, 1, -26.598669, 202.805344)">
29
+ <g>
30
+ <polygon points="53.77 30 55.91 30 55.91 21.21 68.29 21.21 68.29 19.07 53.77 19.06 53.77 30"/>
31
+ <rect x="53.77" y="10.28" width="19.72" height="2.14"/>
32
+ </g>
33
+ <path d="M94.82,10.28c-5.44,0-9.86,4.42-9.86,9.86s4.42,9.86,9.86,9.86,9.86-4.42,9.86-9.86-4.42-9.86-9.86-9.86ZM102.54,20.14c0,4.25-3.46,7.72-7.72,7.72s-7.72-3.46-7.72-7.72,3.46-7.72,7.72-7.72,7.72,3.46,7.72,7.72Z"/>
34
+ <polygon points="120.71 10.28 118.56 10.28 118.56 30 138.28 30 138.28 27.86 120.71 27.86 120.71 10.28"/>
35
+ <path d="M162.02,10.28h-9.86v2.14h9.86c4.25,0,7.72,3.46,7.72,7.72s-3.46,7.72-7.72,7.72h-9.86v2.14h9.86c5.44,0,9.86-4.42,9.86-9.86s-4.42-9.86-9.86-9.86Z"/>
36
+ <path d="M185.76,15.74c0,3.01,2.45,5.47,5.47,5.47h8.79c1.83,0,3.32,1.49,3.32,3.32s-1.49,3.32-3.32,3.32h-14.26v2.14h14.26c3.01,0,5.47-2.45,5.47-5.47s-2.45-5.47-5.47-5.47h-8.79c-1.83,0-3.32-1.49-3.32-3.32s1.49-3.32,3.32-3.32h14.26v-2.14h-14.26c-3.01,0-5.47,2.45-5.47,5.47Z"/>
37
+ <path d="M233.61,10.28h-14.26v2.14h14.26c1.83,0,3.32,1.49,3.32,3.32s-1.49,3.32-3.32,3.32h-14.26v10.93h2.15v-8.79h12.11c3.01,0,5.47-2.45,5.47-5.47s-2.45-5.47-5.47-5.47Z"/>
38
+ <polygon points="247.96 30 250.53 30 259.2 14.58 267.86 30 270.44 30 259.21 10 247.96 30"/>
39
+ <path d="M280.86,20.14c0,5.44,4.42,9.86,9.86,9.86h9.86v-2.14h-9.86c-4.25,0-7.72-3.46-7.72-7.72s3.46-7.72,7.72-7.72h9.86v-2.14h-9.86c-5.44,0-9.86,4.42-9.86,9.86Z"/>
40
+ <g>
41
+ <path class="cls-2" d="M 383.35 21.284 C 383.35 22.364 357.97 23.214 326.66 23.174 C 295.35 23.134 269.98 22.224 269.98 21.144 C 269.98 20.064 295.36 19.214 326.67 19.254 C 357.98 19.294 383.35 20.204 383.35 21.284 Z" style="fill: url(&quot;#radial-gradient&quot;);"/>
42
+ <path class="cls-4" d="M 365.688 19.379 C 365.688 20.169 348.148 20.789 326.508 20.769 C 304.868 20.739 287.328 20.079 287.338 19.289 C 287.338 18.499 304.878 17.879 326.518 17.899 C 348.158 17.929 365.698 18.589 365.688 19.379 Z" style="fill: url(&quot;#radial-gradient-2&quot;);"/>
43
+ <path d="M324.32,12.42h9.86v-2.14h-9.86c-5.44,0-9.86,4.42-9.86,9.86s4.42,9.86,9.86,9.86h9.86v-2.14h-9.86c-4.25,0-7.72-3.46-7.72-7.72s3.46-7.72,7.72-7.72Z"/>
44
+ <circle cx="326.67" cy="20.14" r="1.2"/>
45
+ </g>
46
+ </g>
47
+ </g>
48
+ <g id="svg-1">
49
+ <g transform="matrix(1, 0, 0, 1, 55.221105, 59.088541)">
50
+ <g>
51
+ <path class="cls-3" d="M 264.114 -42.099 C 266.374 -44.822 292.155 -45.685 287.424 -34.392 C 284.798 -28.123 262.07 -35.124 261.921 -34.993" style="fill: url(&quot;#radial-gradient-3&quot;);"/>
52
+ <circle cx="271.424" cy="-38.941" r="1.2"/>
53
+ </g>
54
+ </g>
55
+ </g>
56
+ </svg>
@@ -0,0 +1,46 @@
1
+ // The video's look: Foldspace's own brand, as its product video's storyboard
2
+ // draws it - never the app's colours. One accent, the brand blue, on pure
3
+ // black; white text, and one muted grey for the small lines. The logos are
4
+ // the website's own vector files, in assets/, in the brand kit's blue
5
+ // (#1842EF): the website's copies carried an older blue, #3247F2.
6
+ //
7
+ // No Node imports: the composer page loads this file too.
8
+
9
+ import { blendHex } from "./timeline.mjs";
10
+
11
+ export const BRAND = Object.freeze({
12
+ blue: "#1842EF", // the one accent
13
+ black: "#000000", // the opening and closing cards, and the bars around the product
14
+ muted: "#8E8E99", // small lines: "Built with", "2 OF 4", the question, the closing card's eyebrow and last line
15
+ white: "#FFFFFF",
16
+ });
17
+
18
+ // The caption card: the storyboard's panel colour at 88%, so the product shows
19
+ // through a little - the most see-through it can be with the muted question
20
+ // still at 4.5:1 over a white app. In a letterboxed frame it stays on the
21
+ // picture (captionLayout), so it never crosses onto the black band.
22
+ export const CAPTION_FILL = Object.freeze({ color: "#0B0B0D", alpha: 0.88 });
23
+ export const CAPTION_EDGE = "rgba(255, 255, 255, 0.12)";
24
+ // The lightest it gets - over a white app - which is where light text on it
25
+ // is hardest to read.
26
+ export const CAPTION_BACKDROP_LIGHTEST = blendHex(CAPTION_FILL.color, CAPTION_FILL.alpha, "#FFFFFF");
27
+ // The opening and closing cards' background, and the bars around the product.
28
+ export const CARD_BG = BRAND.black;
29
+
30
+ // WCAG: 4.5:1 for small text; 3:1 for large text (the blue "Product Agent"
31
+ // line) and for rules. Blue on black is only about 3.06:1, so blue is never
32
+ // small text on black: only the large title line, the rule, and a fill
33
+ // behind white text - the pills and the caption's highlight (6.85:1).
34
+ export const TEXT_CONTRAST = 4.5;
35
+ export const LARGE_TEXT_CONTRAST = 3;
36
+ export const MARK_CONTRAST = 3;
37
+
38
+ // The logos, as the website ships them (viewBox 0 0 383 40 and 0 0 39.89 40),
39
+ // in the brand kit's blue.
40
+ export const LOGO_FILES = Object.freeze({
41
+ onDark: "logo-dark.svg", // white wordmark, blue mark
42
+ onLight: "logo-light.svg", // dark wordmark, blue mark
43
+ mark: "foldspace-mark.svg", // the blue mark alone (the website's favicon)
44
+ });
45
+ export const WORDMARK_RATIO = 383 / 40;
46
+ export const MARK_RATIO = 39.89 / 40;