@struq/skill 0.3.0 → 0.4.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.
Files changed (129) hide show
  1. package/README.md +22 -36
  2. package/package.json +4 -2
  3. package/skills/struq/SKILL.md +139 -307
  4. package/skills/struq/blocks/CATALOG.md +426 -0
  5. package/skills/struq/blocks/backgrounds/background-aurora.tsx +818 -0
  6. package/skills/struq/blocks/backgrounds/background-facets.tsx +1283 -0
  7. package/skills/struq/blocks/backgrounds/background-silk.tsx +906 -0
  8. package/skills/struq/blocks/catalog.json +1302 -0
  9. package/skills/struq/blocks/footers/footer-contact.tsx +789 -0
  10. package/skills/struq/blocks/footers/footer-reveal.tsx +313 -0
  11. package/skills/struq/blocks/headers/header-bar.tsx +652 -0
  12. package/skills/struq/blocks/headers/menu-curtain.tsx +790 -0
  13. package/skills/struq/blocks/heroes/hero-framed.tsx +627 -0
  14. package/skills/struq/blocks/heroes/hero-index.tsx +855 -0
  15. package/skills/struq/blocks/heroes/hero-poster.tsx +1086 -0
  16. package/skills/struq/blocks/heroes/hero-split.tsx +685 -0
  17. package/skills/struq/blocks/heroes/hero-still.tsx +862 -0
  18. package/skills/struq/{recipes/components/struq → blocks/kit}/Cursor.tsx +1 -1
  19. package/skills/struq/{recipes/components/struq → blocks/kit}/HeroCanvas.tsx +4 -4
  20. package/skills/struq/{recipes/components/struq → blocks/kit}/HorizontalGallery.tsx +2 -2
  21. package/skills/struq/{recipes/components/struq → blocks/kit}/MagneticButton.tsx +1 -1
  22. package/skills/struq/{recipes/components/struq → blocks/kit}/MediaFrame.tsx +2 -2
  23. package/skills/struq/{recipes/components/struq → blocks/kit}/PinnedZoom.tsx +14 -4
  24. package/skills/struq/{recipes/components/struq → blocks/kit}/Reveal.tsx +1 -1
  25. package/skills/struq/{recipes/components/struq → blocks/kit}/SmoothScroll.tsx +1 -1
  26. package/skills/struq/{recipes/components/struq → blocks/kit}/SplitHeadline.tsx +1 -1
  27. package/skills/struq/blocks/kit/blocks.css +2102 -0
  28. package/skills/struq/blocks/kit/images.ts +17 -0
  29. package/skills/struq/blocks/kit/photo-read.ts +102 -0
  30. package/skills/struq/blocks/page-transitions/page-transition-gate.tsx +706 -0
  31. package/skills/struq/blocks/page-transitions/page-transition-slices.tsx +469 -0
  32. package/skills/struq/blocks/page-transitions/page-transition-stack.tsx +583 -0
  33. package/skills/struq/blocks/preloaders/preloader-gate.tsx +358 -0
  34. package/skills/struq/blocks/preloaders/preloader-slices-uncovered.ts +50 -0
  35. package/skills/struq/blocks/preloaders/preloader-slices.tsx +289 -0
  36. package/skills/struq/blocks/preloaders/preloader-stack.tsx +367 -0
  37. package/skills/struq/blocks/preloaders/preloader-wordmark.tsx +568 -0
  38. package/skills/struq/blocks/sections/about-studio.tsx +436 -0
  39. package/skills/struq/blocks/sections/approach.tsx +661 -0
  40. package/skills/struq/blocks/sections/contact-form.tsx +664 -0
  41. package/skills/struq/blocks/sections/cta-closing.tsx +763 -0
  42. package/skills/struq/blocks/sections/fanned-cards.tsx +547 -0
  43. package/skills/struq/blocks/sections/film-strip.tsx +385 -0
  44. package/skills/struq/blocks/sections/grid-assembly.tsx +616 -0
  45. package/skills/struq/blocks/sections/hover-list.tsx +467 -0
  46. package/skills/struq/blocks/sections/image-trail.tsx +432 -0
  47. package/skills/struq/blocks/sections/manifesto.tsx +691 -0
  48. package/skills/struq/blocks/sections/panel-accordion.tsx +380 -0
  49. package/skills/struq/blocks/sections/reveals.tsx +416 -0
  50. package/skills/struq/blocks/sections/split-pair.tsx +670 -0
  51. package/skills/struq/blocks/sections/sticky-chapters.tsx +256 -0
  52. package/skills/struq/blocks/sections/velocity-type.tsx +269 -0
  53. package/skills/struq/{cinematic/showpieces/toonstuk-fill-rows.tsx → blocks/showpieces/fill-rows.tsx} +1 -1
  54. package/skills/struq/{cinematic/showpieces/toonstuk-hero-opener.tsx → blocks/showpieces/hero-opener.tsx} +5 -5
  55. package/skills/struq/{cinematic/showpieces/toonstuk-image-to-fullbleed.tsx → blocks/showpieces/image-to-fullbleed.tsx} +16 -5
  56. package/skills/struq/blocks/showpieces/manifesto-scrub.tsx +48 -0
  57. package/skills/struq/{cinematic/showpieces/toonstuk-panel-wipe.tsx → blocks/showpieces/panel-wipe.tsx} +2 -2
  58. package/skills/struq/{cinematic/showpieces/toonstuk-pixel-resolve.tsx → blocks/showpieces/pixel-resolve.tsx} +1 -1
  59. package/skills/struq/{cinematic/showpieces/toonstuk-project-stack.tsx → blocks/showpieces/project-stack.tsx} +2 -2
  60. package/skills/struq/blocks/showpieces/scrub-connector.tsx +307 -0
  61. package/skills/struq/{cinematic/showpieces/toonstuk-wordmark-finale.tsx → blocks/showpieces/wordmark-finale.tsx} +1 -1
  62. package/skills/struq/blocks/transitions/zoom-through.tsx +613 -0
  63. package/skills/struq/canon/canon.json +627 -0
  64. package/skills/struq/cinematic/README.md +7 -18
  65. package/skills/struq/cinematic/examples/code/nextjs/app/components/LenisRoot.tsx +1 -1
  66. package/skills/struq/cinematic/examples/code/shared/styles.css +1 -1
  67. package/skills/struq/cinematic/examples/code/vite/src/LenisRoot.tsx +1 -1
  68. package/skills/struq/cinematic/references/01-core-mental-model.md +1 -1
  69. package/skills/struq/cinematic/references/02-vocabulary.md +1 -1
  70. package/skills/struq/cinematic/references/07-stack-integration.md +2 -2
  71. package/skills/struq/recipes/app/fonts.ts +12 -22
  72. package/skills/struq/recipes/app/globals.css +34 -177
  73. package/skills/struq/recipes/app/layout.tsx +13 -11
  74. package/skills/struq/references/craft.md +77 -0
  75. package/skills/struq/references/iconography.md +28 -230
  76. package/skills/struq/references/stack-targets.md +23 -74
  77. package/skills/struq/references/tells.md +52 -0
  78. package/skills/struq/references/triage.md +6 -7
  79. package/skills/struq/scripts/blocks.mjs +156 -0
  80. package/skills/struq/scripts/canon.mjs +106 -281
  81. package/skills/struq/scripts/check.mjs +92 -126
  82. package/skills/struq/scripts/images.mjs +2 -2
  83. package/skills/struq/scripts/log.mjs +6 -23
  84. package/skills/struq/scripts/tokens.mjs +222 -0
  85. package/skills/struq/cinematic/showpieces/README.md +0 -18
  86. package/skills/struq/cinematic/showpieces/toonstuk-manifesto-scrub.tsx +0 -81
  87. package/skills/struq/cinematic/showpieces/toonstuk-scrub-connector.tsx +0 -78
  88. package/skills/struq/references/anti-patterns.md +0 -330
  89. package/skills/struq/references/baseline-prompt.md +0 -122
  90. package/skills/struq/references/brand.md +0 -143
  91. package/skills/struq/references/brief-router.md +0 -218
  92. package/skills/struq/references/composition.md +0 -190
  93. package/skills/struq/references/depth-parallax.md +0 -235
  94. package/skills/struq/references/direction.md +0 -188
  95. package/skills/struq/references/feel-layer.md +0 -107
  96. package/skills/struq/references/gsap-api.md +0 -293
  97. package/skills/struq/references/hero-scenes.md +0 -107
  98. package/skills/struq/references/imagery.md +0 -125
  99. package/skills/struq/references/loaders.md +0 -287
  100. package/skills/struq/references/micro-interactions.md +0 -270
  101. package/skills/struq/references/mobile-composition.md +0 -70
  102. package/skills/struq/references/nav-header.md +0 -296
  103. package/skills/struq/references/niche-packets.md +0 -338
  104. package/skills/struq/references/page-architecture.md +0 -134
  105. package/skills/struq/references/page-entrances.md +0 -198
  106. package/skills/struq/references/performance.md +0 -235
  107. package/skills/struq/references/pinning-scrub.md +0 -340
  108. package/skills/struq/references/pitfalls.md +0 -290
  109. package/skills/struq/references/route-transitions.md +0 -281
  110. package/skills/struq/references/scaling.md +0 -151
  111. package/skills/struq/references/scroll-reveals.md +0 -279
  112. package/skills/struq/references/sections.md +0 -141
  113. package/skills/struq/references/seo.md +0 -219
  114. package/skills/struq/references/setup.md +0 -397
  115. package/skills/struq/references/showcase.md +0 -87
  116. package/skills/struq/references/struq-imagery.md +0 -107
  117. package/skills/struq/references/svg-motion.md +0 -390
  118. package/skills/struq/references/transitions.md +0 -160
  119. package/skills/struq/references/typography.md +0 -720
  120. package/skills/struq/references/vibe-presets.md +0 -287
  121. package/skills/struq/references/webgl-r3f.md +0 -274
  122. package/skills/struq/references/webgl-scenes.md +0 -593
  123. package/skills/struq/scripts/font-names.mjs +0 -40
  124. package/skills/struq/scripts/recipes.mjs +0 -135
  125. /package/skills/struq/{recipes/lib → blocks/kit}/gsap.ts +0 -0
  126. /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/contours.ts +0 -0
  127. /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/dots.ts +0 -0
  128. /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/ripple.ts +0 -0
  129. /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/types.ts +0 -0
@@ -0,0 +1,17 @@
1
+ // The image a block receives. In a client project the skill's images.mjs writes the list of these from a
2
+ // beeldbank pack; the lab passes a few fixed beeldbank photos so every block is judged on real photography.
3
+
4
+ export type StruqImage = {
5
+ src: string;
6
+ alt: string;
7
+ width: number;
8
+ height: number;
9
+ /** "16:9", "21:9", "3:2", "4:5" or "1:1" */
10
+ ratio: string;
11
+ /** What the photo shows, in more words than the alt: read it to choose the right slot. */
12
+ description: string;
13
+ /** The photo's calm side ("left", "right", "top", "bottom", "center"): where copy reads over it. */
14
+ negativeSpace?: string;
15
+ /** Set on fallback plates (SVG), which next/image must not optimise. */
16
+ unoptimized?: boolean;
17
+ };
@@ -0,0 +1,102 @@
1
+ // photo-read (Struq kit)
2
+ // Reading what a photograph holds under a box on the screen, for the blocks that choose an ink or a veil from the
3
+ // photo itself. The whole image is drawn once to a small offscreen canvas and kept as a grid of luminances; a box is
4
+ // then mapped onto that grid through the element's object-fit: cover and its object-position.
5
+ //
6
+ // Why the whole image: with a srcset, img.naturalWidth is density-corrected (a 1920w file shown at 1440 reports
7
+ // 1440), while drawImage's source rectangle counts the file's real pixels. A sub-rectangle mapped from naturalWidth
8
+ // therefore lands up and to the left of where it should. Drawing the whole image to fill the grid only uses the ratio
9
+ // of naturalWidth to naturalHeight, which is right either way.
10
+ //
11
+ // const ys = photoLuminances(img, frame, box); // sorted ascending, or null when it cannot be read
12
+ // const median = ys && ys[Math.floor(ys.length * 0.5)];
13
+ //
14
+ // Returns null, so the caller keeps its safe treatment, when the photo is not loaded, comes from another origin
15
+ // (reading it would taint the canvas) or the canvas cannot be read.
16
+
17
+ export type PhotoBox = { x: number; y: number; w: number; h: number };
18
+
19
+ const GRID_W = 320;
20
+
21
+ const linear = (c: number) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
22
+
23
+ type Grid = { w: number; h: number; y: Float32Array };
24
+ const grids = new WeakMap<HTMLImageElement, { src: string; grid: Grid | null }>();
25
+
26
+ /** The photo's luminance on a small grid of the whole image, read once per source and kept. */
27
+ function gridOf(img: HTMLImageElement): Grid | null {
28
+ const src = img.currentSrc || img.src;
29
+ const iw = img.naturalWidth;
30
+ const ih = img.naturalHeight;
31
+ if (!src || !img.complete || !iw || !ih) return null;
32
+ const kept = grids.get(img);
33
+ if (kept && kept.src === src) return kept.grid;
34
+ let grid: Grid | null = null;
35
+ try {
36
+ if (new URL(src, location.href).origin !== location.origin) throw new Error("other origin");
37
+ const w = GRID_W;
38
+ const h = Math.max(1, Math.round((w * ih) / iw));
39
+ const cv = document.createElement("canvas");
40
+ cv.width = w;
41
+ cv.height = h;
42
+ const ctx = cv.getContext("2d", { willReadFrequently: true });
43
+ if (ctx) {
44
+ ctx.drawImage(img, 0, 0, w, h);
45
+ const d = ctx.getImageData(0, 0, w, h).data;
46
+ const y = new Float32Array(w * h);
47
+ for (let k = 0; k < w * h; k += 1) {
48
+ y[k] = 0.2126 * linear(d[k * 4] / 255) + 0.7152 * linear(d[k * 4 + 1] / 255) + 0.0722 * linear(d[k * 4 + 2] / 255);
49
+ }
50
+ grid = { w, h, y };
51
+ }
52
+ } catch {
53
+ grid = null;
54
+ }
55
+ grids.set(img, { src, grid });
56
+ return grid;
57
+ }
58
+
59
+ /** The element's object-position as two shares (0 to 1); percentages and keywords, anything else is centred. */
60
+ function positionOf(img: HTMLImageElement): [number, number] {
61
+ const parts = (getComputedStyle(img).objectPosition || "50% 50%").trim().split(/\s+/);
62
+ const share = (v: string | undefined) => {
63
+ if (!v) return 0.5;
64
+ if (v.endsWith("%")) {
65
+ const n = parseFloat(v) / 100;
66
+ return Number.isFinite(n) ? n : 0.5;
67
+ }
68
+ if (v === "left" || v === "top") return 0;
69
+ if (v === "right" || v === "bottom") return 1;
70
+ if (v === "center") return 0.5;
71
+ return 0.5;
72
+ };
73
+ return [share(parts[0]), share(parts[1] ?? parts[0])];
74
+ }
75
+
76
+ /**
77
+ * The luminances of the photo under `at`, sorted ascending. `frame` is the box the image is drawn into (object-fit:
78
+ * cover at its object-position), in the same coordinates as `at`; pass `position` to override the element's own.
79
+ */
80
+ export function photoLuminances(
81
+ img: HTMLImageElement,
82
+ frame: PhotoBox,
83
+ at: PhotoBox,
84
+ position?: [number, number],
85
+ ): number[] | null {
86
+ if (frame.w <= 0 || frame.h <= 0 || at.w <= 0 || at.h <= 0) return null;
87
+ const g = gridOf(img);
88
+ if (!g) return null;
89
+ const [px, py] = position ?? positionOf(img);
90
+ // Frame pixels per grid cell, and where the covered image starts in the frame.
91
+ const c = Math.max(frame.w / g.w, frame.h / g.h);
92
+ const ox = frame.x + (frame.w - g.w * c) * px;
93
+ const oy = frame.y + (frame.h - g.h * c) * py;
94
+ const x0 = Math.max(0, Math.floor((at.x - ox) / c));
95
+ const x1 = Math.min(g.w, Math.max(x0 + 1, Math.ceil((at.x + at.w - ox) / c)));
96
+ const y0 = Math.max(0, Math.floor((at.y - oy) / c));
97
+ const y1 = Math.min(g.h, Math.max(y0 + 1, Math.ceil((at.y + at.h - oy) / c)));
98
+ if (x0 >= g.w || y0 >= g.h || x1 <= 0 || y1 <= 0) return null;
99
+ const ys: number[] = [];
100
+ for (let y = y0; y < y1; y += 1) for (let x = x0; x < x1; x += 1) ys.push(g.y[y * g.w + x]);
101
+ return ys.length ? ys.sort((a, b) => a - b) : null;
102
+ }