@struq/skill 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -27
- package/cli/struq.mjs +2 -2
- package/package.json +6 -6
- package/skills/struq/SKILL.md +145 -89
- package/skills/struq/blocks/CATALOG.md +74 -22
- package/skills/struq/blocks/backgrounds/background-aurora.tsx +34 -12
- package/skills/struq/blocks/backgrounds/background-facets.tsx +14 -6
- package/skills/struq/blocks/backgrounds/background-silk.tsx +40 -15
- package/skills/struq/blocks/catalog.json +697 -16
- package/skills/struq/blocks/film.json +426 -0
- package/skills/struq/blocks/footers/footer-contact.tsx +81 -55
- package/skills/struq/blocks/footers/footer-reveal.tsx +1 -1
- package/skills/struq/blocks/headers/header-bar.tsx +24 -13
- package/skills/struq/blocks/headers/menu-curtain.tsx +24 -11
- package/skills/struq/blocks/heroes/hero-framed.tsx +33 -20
- package/skills/struq/blocks/heroes/hero-index.tsx +32 -11
- package/skills/struq/blocks/heroes/hero-poster.tsx +68 -35
- package/skills/struq/blocks/heroes/hero-split.tsx +22 -9
- package/skills/struq/blocks/heroes/hero-still.tsx +80 -74
- package/skills/struq/blocks/kit/accent.ts +82 -0
- package/skills/struq/blocks/kit/blocks.css +78 -35
- package/skills/struq/blocks/kit/quiet/Close.tsx +73 -0
- package/skills/struq/blocks/kit/quiet/PhotoText.tsx +79 -0
- package/skills/struq/blocks/kit/quiet/Rows.tsx +81 -0
- package/skills/struq/blocks/kit/quiet/Statement.tsx +157 -0
- package/skills/struq/blocks/kit/quiet/index.ts +7 -0
- package/skills/struq/blocks/kit/quiet/sheet.ts +12 -0
- package/skills/struq/blocks/page-transitions/page-transition-gate.tsx +87 -36
- package/skills/struq/blocks/preloaders/preloader-gate.tsx +116 -46
- package/skills/struq/blocks/preloaders/preloader-stack.tsx +10 -7
- package/skills/struq/blocks/sections/contact-form.tsx +1 -1
- package/skills/struq/blocks/sections/cta-closing.tsx +3 -2
- package/skills/struq/blocks/sections/fanned-cards.tsx +14 -10
- package/skills/struq/blocks/sections/film-strip.tsx +18 -14
- package/skills/struq/blocks/sections/grid-assembly.tsx +16 -11
- package/skills/struq/blocks/sections/manifesto.tsx +3 -2
- package/skills/struq/blocks/sections/panel-accordion.tsx +3 -2
- package/skills/struq/blocks/sections/split-pair.tsx +4 -3
- package/skills/struq/blocks/showpieces/hero-opener.tsx +76 -17
- package/skills/struq/blocks/showpieces/wordmark-finale.tsx +3 -1
- package/skills/struq/blocks/transitions/zoom-through.tsx +9 -10
- package/skills/struq/canon/canon.json +1 -1
- package/skills/struq/references/craft.md +4 -0
- package/skills/struq/references/film.md +158 -0
- package/skills/struq/references/iconography.md +1 -1
- package/skills/struq/references/triage.md +3 -0
- package/skills/struq/scripts/blocks.mjs +89 -7
- package/skills/struq/scripts/canon.mjs +8 -1
- package/skills/struq/scripts/check.mjs +99 -15
- package/skills/struq/scripts/film-check.mjs +997 -0
- package/skills/struq/scripts/gate.mjs +146 -0
- package/skills/struq/scripts/images.mjs +64 -37
- package/skills/struq/scripts/packs.mjs +73 -12
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
// and one link sit on the calm side, where the surface lies flat and is the page ground itself. The surface lies flat
|
|
9
9
|
// into the ground over the bottom fifth of the hero, so the hand-over to the next section has no seam.
|
|
10
10
|
//
|
|
11
|
-
// <BackgroundFacets variant="shard" lines={["Ruimte die", "licht vangt"]}
|
|
11
|
+
// <BackgroundFacets variant="shard" lines={["Ruimte die", "licht vangt"]} accent="licht"
|
|
12
12
|
// lead="Noord ontwerpt woningen en ateliers in Rotterdam." link={{ label: "Bekijk het werk", href: "#werk" }} />
|
|
13
13
|
//
|
|
14
|
+
// `accent` (optional) is the site's one accent word: where it first stands in the two lines it is set in the pairing's
|
|
15
|
+
// italic accent face, inside its line's mask, and the type's rect is measured again once that face is in. The
|
|
16
|
+
// Statement and the Close carry the same word. Left out, or not in the lines, the hero is exactly as it was.
|
|
17
|
+
//
|
|
14
18
|
// Variants (different compositions and moves of the same surface, each in light and dark):
|
|
15
19
|
// shard a few cut shards standing apart in the right third, each a pyramid of unequal planes on its own
|
|
16
20
|
// footprint, pointing out from the cluster, a light circling them; type on the calm left.
|
|
@@ -40,6 +44,7 @@
|
|
|
40
44
|
// 2D canvas. No scripts: the composed
|
|
41
45
|
// hero on the plain ground (blocks.css, "--- background-facets ---", with the 3 s fallback for the lines).
|
|
42
46
|
import { useEffect, useRef } from "react";
|
|
47
|
+
import { accentFaceReady, accentLines } from "../kit/accent";
|
|
43
48
|
import { gsap } from "../kit/gsap";
|
|
44
49
|
import { whenUncovered } from "../preloaders/preloader-slices-uncovered";
|
|
45
50
|
|
|
@@ -68,6 +73,9 @@ type Props = {
|
|
|
68
73
|
variant?: FacetsVariant;
|
|
69
74
|
/** Two short display lines. */
|
|
70
75
|
lines: [string, string];
|
|
76
|
+
/** The site's one accent word: where it first stands in the two lines it is set in the pairing's italic accent face
|
|
77
|
+
* (--face-accent), as the Statement and the Close set it. A word the lines do not hold changes nothing. */
|
|
78
|
+
accent?: string;
|
|
71
79
|
/** One lead sentence. */
|
|
72
80
|
lead: string;
|
|
73
81
|
/** One text link. */
|
|
@@ -980,7 +988,7 @@ function toRGB(probe: CanvasRenderingContext2D, color: string): RGB {
|
|
|
980
988
|
return [r / 255, g / 255, b / 255];
|
|
981
989
|
}
|
|
982
990
|
|
|
983
|
-
export function BackgroundFacets({ variant = "shard", lines, lead, link, className = "" }: Props) {
|
|
991
|
+
export function BackgroundFacets({ variant = "shard", lines, accent, lead, link, className = "" }: Props) {
|
|
984
992
|
const root = useRef<HTMLElement>(null);
|
|
985
993
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
986
994
|
|
|
@@ -1216,8 +1224,8 @@ export function BackgroundFacets({ variant = "shard", lines, lead, link, classNa
|
|
|
1216
1224
|
const frameEl = el.closest("[data-blocks]");
|
|
1217
1225
|
if (frameEl) mo.observe(frameEl, { attributes: true, attributeFilter: ["style", "class", "data-theme"] });
|
|
1218
1226
|
mo.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-theme", "style"] });
|
|
1219
|
-
// The face arriving late moves the type: measure its rect again.
|
|
1220
|
-
document.fonts?.ready.then(() => {
|
|
1227
|
+
// The face arriving late moves the type: measure its rect again (with the accent face in, when a word is marked).
|
|
1228
|
+
document.fonts?.ready.then(() => accentFaceReady(el)).then(() => {
|
|
1221
1229
|
if (gone) return;
|
|
1222
1230
|
measure();
|
|
1223
1231
|
if (!raf) render();
|
|
@@ -1248,7 +1256,7 @@ export function BackgroundFacets({ variant = "shard", lines, lead, link, classNa
|
|
|
1248
1256
|
}, [variant]);
|
|
1249
1257
|
|
|
1250
1258
|
const mask = (child: React.ReactNode, key?: number) => (
|
|
1251
|
-
<span key={key} className="struq-line-mask block
|
|
1259
|
+
<span key={key} className="struq-line-mask block">
|
|
1252
1260
|
<span data-rise="" className="block">
|
|
1253
1261
|
{child}
|
|
1254
1262
|
</span>
|
|
@@ -1265,7 +1273,7 @@ export function BackgroundFacets({ variant = "shard", lines, lead, link, classNa
|
|
|
1265
1273
|
>
|
|
1266
1274
|
<canvas ref={canvasRef} data-bf-canvas="" aria-hidden="true" className="absolute inset-0 h-full w-full" />
|
|
1267
1275
|
<div data-copy="" className="absolute">
|
|
1268
|
-
<h1 className="m-0 font-display leading-[0.95] tracking-[-0.03em]">{lines.map((line, i) => mask(line, i))}</h1>
|
|
1276
|
+
<h1 className="m-0 font-display leading-[0.95] tracking-[-0.03em]">{accentLines(lines, accent).map((line, i) => mask(line, i))}</h1>
|
|
1269
1277
|
<div className="mt-6 md:mt-8">
|
|
1270
1278
|
{/* Full ink: the muted ink measures about 4:1 on the lightest grounds, short of 4.5 for body text. */}
|
|
1271
1279
|
{mask(<span className="text-body block max-w-[40ch] text-ink">{lead}</span>)}
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
// real normal and a low light instead of a halftone grid. Raw WebGL (GLSL ES 1.00, so a WebGL1 context runs it too),
|
|
8
8
|
// no three.js, self-contained.
|
|
9
9
|
//
|
|
10
|
-
// <BackgroundSilk variant="fold" lines={["Zacht licht,", "zware stof."]} lead="..." link={{ href: "#werk", label: "Bekijk het werk" }} />
|
|
10
|
+
// <BackgroundSilk variant="fold" lines={["Zacht licht,", "zware stof."]} accent="licht" lead="..." link={{ href: "#werk", label: "Bekijk het werk" }} />
|
|
11
|
+
//
|
|
12
|
+
// `accent` (optional) is the site's one accent word: where it first stands in the headline it is set in the pairing's
|
|
13
|
+
// italic accent face, inside its line's mask, and the type's box is read once that face is in. The Statement and the
|
|
14
|
+
// Close carry the same word. Left out, or not in the headline, the hero is exactly as it was.
|
|
11
15
|
//
|
|
12
16
|
// Three compositions of the same cloth:
|
|
13
17
|
// - fold: one large diagonal fold through the right half; the type stands on the calm left. The pointer turns the
|
|
@@ -24,14 +28,17 @@
|
|
|
24
28
|
// sheen. That measure is taken once (and again on a resize or a palette switch), at three moments of the wandering
|
|
25
29
|
// light, and the cloth eases toward it.
|
|
26
30
|
//
|
|
27
|
-
// Entrance: the canvas is hidden until its first frame is drawn, then fades in
|
|
28
|
-
//
|
|
31
|
+
// Entrance: the canvas is hidden until its first frame is drawn, then fades in; the folds rise from a flat sheet and the
|
|
32
|
+
// type rises through masks once nothing covers the page (whenUncovered: a preloader or page transition has started to
|
|
33
|
+
// leave, or there is none). Performance: device pixel ratio capped at 1.5, the field drawn at 0.9 of that on a 1x
|
|
29
34
|
// screen and two thirds on a dense one (half on phones, with fewer noise octaves), paused off screen and in a hidden
|
|
30
35
|
// tab, everything disposed on
|
|
31
36
|
// unmount, context loss survived. Reduced motion: one still frame, held. No WebGL: the plain ground with the type.
|
|
32
37
|
// No scripts: the composed hero on the plain ground (blocks.css, "--- background-silk ---").
|
|
33
38
|
import { useRef } from "react";
|
|
39
|
+
import { accentFaceReady, accentLines } from "../kit/accent";
|
|
34
40
|
import { gsap, useGSAP } from "../kit/gsap";
|
|
41
|
+
import { whenUncovered } from "../preloaders/preloader-slices-uncovered";
|
|
35
42
|
|
|
36
43
|
export const SILK_VARIANTS = [
|
|
37
44
|
{ id: "fold", title: "Plooi" },
|
|
@@ -45,6 +52,9 @@ type Props = {
|
|
|
45
52
|
variant?: SilkVariant;
|
|
46
53
|
/** The headline, one entry per line. Two short lines read best. */
|
|
47
54
|
lines: string[];
|
|
55
|
+
/** The site's one accent word: where it first stands in the headline it is set in the pairing's italic accent face
|
|
56
|
+
* (--face-accent), as the Statement and the Close set it. A word the headline does not hold changes nothing. */
|
|
57
|
+
accent?: string;
|
|
48
58
|
/** One lead sentence under the headline. */
|
|
49
59
|
lead: string;
|
|
50
60
|
/** One text link. */
|
|
@@ -435,7 +445,9 @@ function silkField(
|
|
|
435
445
|
|
|
436
446
|
// State the loop owns.
|
|
437
447
|
let time = reduce ? STILL_T : 0;
|
|
438
|
-
|
|
448
|
+
/** When the folds started to rise; -1 while a cover is still over the page, so the sheet waits flat under it. */
|
|
449
|
+
let introAt = -1;
|
|
450
|
+
let stopRise = () => {};
|
|
439
451
|
let last = 0;
|
|
440
452
|
let raf = 0;
|
|
441
453
|
let onScreen = true;
|
|
@@ -545,6 +557,7 @@ function silkField(
|
|
|
545
557
|
|
|
546
558
|
const intro = () => {
|
|
547
559
|
if (reduce) return 1;
|
|
560
|
+
if (introAt < 0) return 0;
|
|
548
561
|
const x = Math.min(1, Math.max(0, (time - introAt) / INTRO));
|
|
549
562
|
return 1 - Math.pow(1 - x, 3);
|
|
550
563
|
};
|
|
@@ -778,13 +791,18 @@ function silkField(
|
|
|
778
791
|
document.addEventListener("visibilitychange", onVisibility);
|
|
779
792
|
resizeObserver.observe(holder);
|
|
780
793
|
io.observe(el);
|
|
781
|
-
|
|
782
|
-
|
|
794
|
+
// The accent face too, when a word is marked: the type's box is read as it will stand.
|
|
795
|
+
document.fonts?.ready.then(() => accentFaceReady(el)).then(onFonts);
|
|
796
|
+
// The folds rise once nothing covers the page: at once, or as a preloader or page transition starts to leave.
|
|
797
|
+
stopRise = whenUncovered(() => {
|
|
798
|
+
introAt = time;
|
|
799
|
+
});
|
|
783
800
|
if (reduce) still();
|
|
784
801
|
else kick();
|
|
785
802
|
},
|
|
786
803
|
dispose() {
|
|
787
804
|
disposed = true;
|
|
805
|
+
stopRise();
|
|
788
806
|
cancelAnimationFrame(raf);
|
|
789
807
|
cancelAnimationFrame(colourFrame);
|
|
790
808
|
resizeObserver.disconnect();
|
|
@@ -806,7 +824,7 @@ function silkField(
|
|
|
806
824
|
};
|
|
807
825
|
}
|
|
808
826
|
|
|
809
|
-
export function BackgroundSilk({ variant = "fold", lines, lead, link, className = "" }: Props) {
|
|
827
|
+
export function BackgroundSilk({ variant = "fold", lines, accent, lead, link, className = "" }: Props) {
|
|
810
828
|
const root = useRef<HTMLElement>(null);
|
|
811
829
|
|
|
812
830
|
useGSAP(
|
|
@@ -831,15 +849,21 @@ export function BackgroundSilk({ variant = "fold", lines, lead, link, className
|
|
|
831
849
|
gsap.set(rise, { y: 0, yPercent: shown ? 0 : 115 });
|
|
832
850
|
el.dataset.ready = "";
|
|
833
851
|
|
|
834
|
-
let played = false;
|
|
835
852
|
const play = contextSafe(() => {
|
|
836
|
-
if (played) return;
|
|
837
|
-
played = true;
|
|
838
853
|
if (!shown) gsap.to(rise, { yPercent: 0, duration: 1.2, ease: "expo.out", stagger: 0.08, delay: 0.25 });
|
|
839
854
|
});
|
|
855
|
+
// The type rises with the first frame of the cloth, and only once nothing covers the page (a preloader or page
|
|
856
|
+
// transition starting to leave, or none at all).
|
|
857
|
+
let asked = false;
|
|
858
|
+
let stop = () => {};
|
|
859
|
+
const ready = () => {
|
|
860
|
+
if (asked) return;
|
|
861
|
+
asked = true;
|
|
862
|
+
stop = whenUncovered(play);
|
|
863
|
+
};
|
|
840
864
|
// The type never waits long on a slow shader compile.
|
|
841
|
-
const failsafe = window.setTimeout(
|
|
842
|
-
const field = silkField(el, holder, variant, reduce,
|
|
865
|
+
const failsafe = window.setTimeout(ready, 1400);
|
|
866
|
+
const field = silkField(el, holder, variant, reduce, ready);
|
|
843
867
|
field.start();
|
|
844
868
|
|
|
845
869
|
// The hand-over: the type leaves first; on fold and ripple the cloth drifts slower than the page, on drape it
|
|
@@ -857,6 +881,7 @@ export function BackgroundSilk({ variant = "fold", lines, lead, link, className
|
|
|
857
881
|
|
|
858
882
|
return () => {
|
|
859
883
|
window.clearTimeout(failsafe);
|
|
884
|
+
stop();
|
|
860
885
|
field.dispose();
|
|
861
886
|
};
|
|
862
887
|
},
|
|
@@ -864,7 +889,7 @@ export function BackgroundSilk({ variant = "fold", lines, lead, link, className
|
|
|
864
889
|
);
|
|
865
890
|
|
|
866
891
|
const mask = (child: React.ReactNode, key?: number) => (
|
|
867
|
-
<span key={key} className="struq-line-mask block
|
|
892
|
+
<span key={key} className="struq-line-mask block">
|
|
868
893
|
<span data-rise="" className="block">
|
|
869
894
|
{child}
|
|
870
895
|
</span>
|
|
@@ -884,10 +909,10 @@ export function BackgroundSilk({ variant = "fold", lines, lead, link, className
|
|
|
884
909
|
className={`relative h-svh min-h-[34rem] overflow-hidden bg-surface-0 text-ink ${className}`}
|
|
885
910
|
>
|
|
886
911
|
<div data-field="" aria-hidden="true" className="absolute inset-0" />
|
|
887
|
-
<div className={`relative flex h-full flex-col px-
|
|
912
|
+
<div className={`relative flex h-full flex-col px-sheet-margin pb-[9vh] pt-[12vh] md:pb-[11vh] ${place}`}>
|
|
888
913
|
<div data-copy="" className="max-w-[min(100%,40rem)]">
|
|
889
914
|
<h1 data-head="" className="font-display text-[clamp(2.75rem,12.5vw,4.5rem)] leading-[0.98] tracking-[-0.03em] md:text-[clamp(3.75rem,6.6vw,7.5rem)]">
|
|
890
|
-
{lines.map((line, i) => mask(line, i))}
|
|
915
|
+
{accentLines(lines, accent).map((line, i) => mask(line, i))}
|
|
891
916
|
</h1>
|
|
892
917
|
<div data-small="" className="mt-7 md:mt-9">
|
|
893
918
|
{mask(<span className="block max-w-[34ch] text-[1.0625rem] leading-relaxed text-ink-muted md:text-lg">{lead}</span>)}
|