@struq/skill 0.2.1 → 0.3.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/README.md +15 -5
- package/package.json +4 -4
- package/skills/struq/SKILL.md +304 -200
- package/skills/struq/cinematic/showpieces/README.md +18 -0
- package/skills/struq/cinematic/showpieces/toonstuk-fill-rows.tsx +74 -0
- package/skills/struq/cinematic/showpieces/toonstuk-hero-opener.tsx +128 -0
- package/skills/struq/cinematic/showpieces/toonstuk-image-to-fullbleed.tsx +78 -0
- package/skills/struq/cinematic/showpieces/toonstuk-manifesto-scrub.tsx +81 -0
- package/skills/struq/cinematic/showpieces/toonstuk-panel-wipe.tsx +89 -0
- package/skills/struq/cinematic/showpieces/toonstuk-pixel-resolve.tsx +107 -0
- package/skills/struq/cinematic/showpieces/toonstuk-project-stack.tsx +86 -0
- package/skills/struq/cinematic/showpieces/toonstuk-scrub-connector.tsx +78 -0
- package/skills/struq/cinematic/showpieces/toonstuk-wordmark-finale.tsx +105 -0
- package/skills/struq/recipes/app/fonts.ts +22 -0
- package/skills/struq/recipes/app/globals.css +185 -0
- package/skills/struq/recipes/app/layout.tsx +28 -0
- package/skills/struq/recipes/components/struq/Cursor.tsx +117 -0
- package/skills/struq/recipes/components/struq/HeroCanvas.tsx +278 -0
- package/skills/struq/recipes/components/struq/HorizontalGallery.tsx +106 -0
- package/skills/struq/recipes/components/struq/MagneticButton.tsx +92 -0
- package/skills/struq/recipes/components/struq/MediaFrame.tsx +96 -0
- package/skills/struq/recipes/components/struq/PinnedZoom.tsx +79 -0
- package/skills/struq/recipes/components/struq/Reveal.tsx +64 -0
- package/skills/struq/recipes/components/struq/SmoothScroll.tsx +57 -0
- package/skills/struq/recipes/components/struq/SplitHeadline.tsx +68 -0
- package/skills/struq/recipes/components/struq/scenes/contours.ts +161 -0
- package/skills/struq/recipes/components/struq/scenes/dots.ts +110 -0
- package/skills/struq/recipes/components/struq/scenes/ripple.ts +99 -0
- package/skills/struq/recipes/components/struq/scenes/types.ts +114 -0
- package/skills/struq/recipes/lib/gsap.ts +21 -0
- package/skills/struq/references/anti-patterns.md +71 -6
- package/skills/struq/references/baseline-prompt.md +18 -14
- package/skills/struq/references/brief-router.md +7 -8
- package/skills/struq/references/composition.md +0 -1
- package/skills/struq/references/depth-parallax.md +1 -1
- package/skills/struq/references/direction.md +5 -5
- package/skills/struq/references/hero-scenes.md +107 -0
- package/skills/struq/references/iconography.md +1 -1
- package/skills/struq/references/imagery.md +10 -29
- package/skills/struq/references/micro-interactions.md +1 -1
- package/skills/struq/references/mobile-composition.md +1 -1
- package/skills/struq/references/niche-packets.md +338 -0
- package/skills/struq/references/page-architecture.md +2 -2
- package/skills/struq/references/pinning-scrub.md +3 -4
- package/skills/struq/references/scroll-reveals.md +1 -2
- package/skills/struq/references/sections.md +141 -0
- package/skills/struq/references/seo.md +2 -2
- package/skills/struq/references/showcase.md +2 -2
- package/skills/struq/references/svg-motion.md +390 -390
- package/skills/struq/references/typography.md +39 -39
- package/skills/struq/references/vibe-presets.md +5 -5
- package/skills/struq/references/webgl-scenes.md +593 -593
- package/skills/struq/scripts/canon.mjs +309 -0
- package/skills/struq/scripts/check.mjs +547 -0
- package/skills/struq/scripts/font-names.mjs +40 -0
- package/skills/struq/scripts/images.mjs +360 -0
- package/skills/struq/scripts/log.mjs +92 -0
- package/skills/struq/scripts/packs.mjs +156 -0
- package/skills/struq/scripts/recipes.mjs +135 -0
- /package/{bin → cli}/struq.mjs +0 -0
package/skills/struq/SKILL.md
CHANGED
|
@@ -1,237 +1,341 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: struq
|
|
3
|
-
description: "Award-tier web design for coding agents. Builds cinematic, agency-grade marketing sites, landing pages and portfolios
|
|
3
|
+
description: "Award-tier web design for coding agents. Builds cinematic, agency-grade marketing sites, landing pages and portfolios: two-face typography with light display weights, curated palettes, art-directed CC0 photography from the Struq beeldbank, Lenis smooth scroll on one GSAP clock, SplitText headlines, masked reveals, a pinned or horizontal showpiece, a WebGL hero layer with a poster fallback, and a magnetic CTA. Ships tested copy-in components and a self-check script. Use when designing, building, redesigning, animating or auditing a marketing or narrative site, or when a page must feel premium, custom, cinematic or award-winning instead of templated. Stack: Next.js App Router + Tailwind v4 (Vite supported)."
|
|
4
4
|
argument-hint: "[brief] | redesign <path> | audit <path-or-url> | direct <brief>"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Struq
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A site looks like a template because every choice in it is the likeliest one. Struq replaces those choices with the ones an award-winning studio makes, and ships them as **tested components and two scripts**, so the build spends its effort on the page, not on plumbing. Follow the Build checklist below in order. The references are depth for when a step needs it; you do not need to read them to finish a good build.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
`<skill-dir>` below is the folder this SKILL.md is in (Claude Code prints it as "Base directory for this skill"). Typical paths: `.claude/skills/struq`, `~/.claude/skills/struq`, `.agents/skills/struq`.
|
|
12
12
|
|
|
13
13
|
## Modes
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
| Invocation | Mode |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `/struq <brief>` | **Build**: the Build checklist below, start to finish. |
|
|
18
|
+
| `/struq redesign <path>` | **Redesign**: keep the brand, copy and components; read [references/triage.md](references/triage.md), then run the checklist from step 3 on the existing project. The log starts with what you found. |
|
|
19
|
+
| `/struq audit <path-or-url>` | **Audit**: change nothing. Run `node <skill-dir>/scripts/check.mjs` on the project and report its FAIL and WARN lines, a score (100, minus 5 per FAIL, minus 2 per WARN) and the three fixes with the most visual payoff. The check appends its result to the project's STRUQ-LOG.md; log the audit itself there too. |
|
|
20
|
+
| `/struq direct <brief>` | **Directed**: one continuous WebGL experience across chapters. Run the checklist through step 5, then [cinematic/README.md](cinematic/README.md), logging every chapter to the same STRUQ-LOG.md. |
|
|
21
|
+
|
|
22
|
+
Register: this is for marketing, landing, portfolio and narrative sites. For dashboards and product UI keep the type, palette and surface rules and drop Lenis, showpieces and the hero canvas.
|
|
23
|
+
|
|
24
|
+
## Three rules that never bend
|
|
25
|
+
|
|
26
|
+
1. **Never an empty frame.** Every image slot holds a real image. Without photos from the user, the Struq beeldbank pack is used automatically: it is free, CC0, made for this, and needs no permission. If struq.nl is unreachable, the script writes graded fallback plates in the site palette. A grey box, a "photo to come" caption or a placeholder component never ships.
|
|
27
|
+
2. **Never invent facts.** No testimonials, client logos, stats, awards, prices, addresses, opening hours, bios or quotes the brief did not give. Write real, specific running copy from what the brief says; anything the layout needs that is not known (an address, hours) is left out or written as a plain invitation ("Bel of mail voor een tafel"), and listed under Assumptions in STRUQ.md. A beeldbank photo shows the world around the client, never their own room, team or product: never caption it as theirs.
|
|
28
|
+
3. **Never the AI tells.** The one combined list (Struq's tells, the slop audit and Roy's rules), each with its fix in [references/anti-patterns.md](references/anti-patterns.md) and each detected by `check.mjs`:
|
|
29
|
+
- **Type and labels:** eyebrows and any uppercase label or marquee; monospace as a design face; Inter, Roboto, Arial, Helvetica, Open Sans, Poppins, Montserrat, Lato, Geist, Space Grotesk; heavy display; 01 / 02 / 03 numbering; raw text sizes and breakpoint size ladders.
|
|
30
|
+
- **Colour and surface:** gradients of any kind; gradient text; tinted translucent fills and glass; pastel status badges; pills, above all a pill with a dot; pulsing live dots; blue or purple unless the brand is that colour; drop-shadow elevation; side-stripe borders.
|
|
31
|
+
- **Layout and motion:** three identical cards; uniform section padding; opacity-only entrances; the same hover on everything; single-layer parallax; `anticipatePin` with smooth scroll; empty or placeholder frames.
|
|
32
|
+
- **Copy and icons:** emoji and the cliche icons (sparkles, rocket, lightning, bulb, wand, flame); lorem, Acme and leftover template text; em and en dashes in any form; generic CTAs ("Learn more", "Lees meer"); generic headings ("Our Services", "Over ons"); hype words ("seamless", "naadloos"). Judged by eye, not by the script: the hero-metric template, category-reflex colours, a heading the paragraph restates.
|
|
33
|
+
|
|
34
|
+
## The Build checklist
|
|
35
|
+
|
|
36
|
+
**Struq MCP connected (`struq_*` tools)?** Then the MCP build line (see "With the Struq MCP" below) replaces steps 2, 4 and 7: bouwplan, the one stop, projectschil, motion, achtergrond, and one sectiespec per arc row. Copy its block into STRUQ.md too.
|
|
37
|
+
|
|
38
|
+
Copy this block into `STRUQ.md` in the project root (under the short plan, step 2) and tick each line (`- [x]`) as you finish it. `check.mjs` fails while any line is open.
|
|
39
|
+
|
|
40
|
+
**The site log.** Every site also keeps `STRUQ-LOG.md` in the project root, from the first step on: append only, one entry per step, a heading with the step name and the local time, then two to five short lines of what and why. Log the brief received, the canon picks and why, the approval asked and the answer, each section built (files, recipe), every check run, every fix after a failure, every deviation and assumption, and the final state. `canon.mjs`, `images.mjs`, `recipes.mjs` and `check.mjs` append their own entries; you add the rest with `log.mjs`. `check.mjs` fails when the log is missing or a ticked step has no entry. STRUQ.md stays the plan; the log is what happened. Do not report the build as done until every line is ticked and `check.mjs` prints `RESULT: PASS`.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
## Build checklist
|
|
44
|
+
Rule: keep STRUQ-LOG.md at every step: node <skill-dir>/scripts/log.mjs "Step N Name" "what" "why" (canon, images, recipes and check log themselves)
|
|
45
|
+
- [ ] 1 Project: Next.js App Router + Tailwind v4 in place (scaffolded if the folder was empty)
|
|
46
|
+
- [ ] 2 Plan and approval: palette, pairing, design system and pack picked from the canon (canon.mjs list); STRUQ.md written; the one stop for approval sent and answered
|
|
47
|
+
- [ ] 3 Recipes: node <skill-dir>/scripts/recipes.mjs printed "OK recipes ready"
|
|
48
|
+
- [ ] 4 Canon: canon.mjs palette and canon.mjs type wrote the tokens and the fonts of the approved picks
|
|
49
|
+
- [ ] 5 Images: images.mjs --brief printed "OK"; only photos whose alt fits the client are used, captions written from the alts; title and lang set in app/layout.tsx
|
|
50
|
+
- [ ] 6 Hero: the full-bleed HeroOpener (photo edge to edge, line at 14 to 20vw, lead, large CTA, header with the data-wordmark name), or a hand-built hero under the same rules
|
|
51
|
+
- [ ] 7 Sections: 5 to 7 sections on the arc, every image in a MediaFrame, text groups in Reveal, two grid breaks; one contrast scene (scene-inverse or scene-field), at most two
|
|
52
|
+
- [ ] 8 Showpieces: the lead toonstuk from the picker (or the one the user named) and at most one supporting move
|
|
53
|
+
- [ ] 9 Footer: toonstuk-wordmark-finale, with the image credit
|
|
54
|
+
- [ ] 10 Build: npm run build passes
|
|
55
|
+
- [ ] 11 Look: screenshots at 390 and 1440 and one full scroll, fixes made (or "not seen: no browser" written under Verify)
|
|
56
|
+
- [ ] 12 Check: node <skill-dir>/scripts/check.mjs prints RESULT: PASS
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Step 1: Project
|
|
60
|
+
|
|
61
|
+
Empty folder: `npx create-next-app@latest . --ts --tailwind --app --eslint --yes`. Existing project: read `package.json`; when the installed version's types disagree with a recipe, the installed version wins (adapt the recipe, keep its intent). Vite: translate each step through [references/stack-targets.md](references/stack-targets.md).
|
|
62
|
+
|
|
63
|
+
### Step 2: Plan, then the one stop for approval
|
|
64
|
+
|
|
65
|
+
Run `node <skill-dir>/scripts/canon.mjs list` (it reads the canon and changes nothing) and pick the palette, the pairing, the design system and the beeldbank pack as step 4 and step 5 describe. Then write this at the top of `STRUQ.md`, with the checklist under it:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
# STRUQ.md: <name>
|
|
69
|
+
Brief: <one line: what it is, for whom, where>
|
|
70
|
+
Idea: <the one idea the site is about, and the one visual motif that carries it>
|
|
71
|
+
Palette: <canon palette slug> (<its mood>)
|
|
72
|
+
Type: <canon pairing slug>: <display face> + <text face>
|
|
73
|
+
System: <canon design system slug> (<its section order>)
|
|
74
|
+
Images: pack <slug> (or: the client's photos, then pack <slug>)
|
|
75
|
+
Hero: <contours | ripple | dots | the MCP background>
|
|
76
|
+
Showpiece: <HorizontalGallery | PinnedZoom>, after section <n>
|
|
77
|
+
Arc: 1 hero, 2 <...>, 3 <...>, 4 showpiece, 5 <...>, 6 invite, footer
|
|
78
|
+
Assumptions: <every drafted line and every fact left out>
|
|
79
|
+
Verify: <filled in at step 11>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then **stop, once**: send the plan in five lines (idea, palette, type, pack, arc with the showpiece) and the materials request, in the user's language:
|
|
83
|
+
|
|
84
|
+
> Akkoord? Heb je een logo, foto's of echte cijfers, stuur ze nu.
|
|
85
|
+
|
|
86
|
+
(English: "Approve? If you have a logo, photos or real numbers, send them now.") Wait for the answer. Any yes, or "bouw maar" with nothing attached, builds with the canon picks as planned; what the user sends replaces the matching pick or pack images; a veto changes only what it names. **This is the only stop: never ask a second time**, not for permission to download the images (they are free and CC0) and not to confirm a build step. Only a brief that does not say what the business is gets one question before the plan, instead of this stop.
|
|
87
|
+
|
|
88
|
+
**No photos that pretend.** When the brief's own sector words point at no pack (a product launch: an e-bike, a device, a drink), no beeldbank photo shows the client's product. Then add this line to the stop: "Ik heb geen foto's van je <product>: stuur ze, dan komen ze in de hero." (English: "I have no photos of your <product>: send them and they go in the hero.")
|
|
89
|
+
|
|
90
|
+
**The build request is not the approval.** "Build me a site for ..." is the brief; the approval is the user's answer to this stop, and nothing else counts as one. Log it word for word, then go on: `node <skill-dir>/scripts/log.mjs "Step 2 Approval" "Asked: Akkoord? Heb je een logo, foto's of echte cijfers, stuur ze nu." "Reply: \"<the user's own words>\""`. `check.mjs` fails without that entry and its Reply line.
|
|
91
|
+
|
|
92
|
+
### Step 3: Recipes
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
node <skill-dir>/scripts/recipes.mjs
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Copies the tested components into the project and installs `gsap @gsap/react lenis three @types/three`. On a fresh scaffold it also writes `app/globals.css`, `app/layout.tsx` and `app/fonts.ts`; on your own files it prints what to merge. The components (all in `components/struq/`):
|
|
99
|
+
|
|
100
|
+
| Component | What it gives the page |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `SmoothScroll` | Lenis on `gsap.ticker`, one clock for the whole page. Already in the layout. `getLenis()?.stop()` / `.start()` for menus. |
|
|
103
|
+
| `SplitHeadline` | SplitText line-mask rise. `as="h1"` for the hero, `on="scroll"` for a statement further down. Plain text children. |
|
|
104
|
+
| `Reveal` | Staggered rise of its direct children on scroll (`on="load"` in the hero). |
|
|
105
|
+
| `MediaFrame` | The only way an image goes on the page: curtain reveal, photo settle, scrubbed parallax. Props `image`, `ratio`, `sizes`, `priority` (the hero image only), `reveal`. |
|
|
106
|
+
| `HeroCanvas` | The WebGL hero layer: `scene="contours"`, `"ripple"` (needs `image`) or `"dots"`. Lazy three.js chunk, poster first, DPR cap, pixel budget, pause offscreen, still frame under reduced motion, colours read from the tokens. |
|
|
107
|
+
| `HorizontalGallery` | Showpiece: pinned section, a track of photos sliding sideways, each photo panning. Native swipe row on phones. |
|
|
108
|
+
| `PinnedZoom` | Showpiece: a small framed photo opens to full bleed while the statement lifts away. |
|
|
109
|
+
| `MagneticButton` | The primary CTA: leans to the pointer, ink fill rises on hover. `variant="line"` for the secondary. |
|
|
110
|
+
| `Cursor` | Dot and trailing ring, inverted against the page. Already in the layout; delete it for a booking tool or a dense shop. |
|
|
111
|
+
|
|
112
|
+
### Step 4: Canon (Struq's own palette, type and system, every build)
|
|
113
|
+
|
|
114
|
+
A generic AI falls back on its standard palette and fonts. A Struq build starts from Struq's canon: curated palettes, pairings and design systems, public, no account. **Never invent a palette or a pairing while the canon has a fit.**
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
node <skill-dir>/scripts/canon.mjs list # every palette (slug, mood, light or dark), pairing, system, pack
|
|
118
|
+
node <skill-dir>/scripts/canon.mjs palette <slug> # writes the STRUQ TOKENS block in app/globals.css
|
|
119
|
+
node <skill-dir>/scripts/canon.mjs type <slug> # writes app/fonts.ts (Google via next/font, Fontshare as local woff2)
|
|
120
|
+
node <skill-dir>/scripts/canon.mjs system <slug> # prints the section order
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Each command prints the line to paste into STRUQ.md (`Palette:`, `Type:`, `System:`). The raw API, if you want to read it yourself:
|
|
16
124
|
|
|
17
|
-
|
|
|
125
|
+
| Endpoint | Read |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `curl -s "https://struq.nl/api/assets?filter=palette"` | `items[].slug`, `items[].data.mood`, `items[].data.colors` with `background`, `surface`, `text`, `accent`, `accentAlt` (OKLCH or hex), `items[].tags` (`sfeer:licht`, `sfeer:donker`, `sfeer:warm`, `sfeer:koel`, `sfeer:redactioneel`, `sfeer:rustig`, `sfeer:gedurfd`, `sfeer:technisch`) |
|
|
128
|
+
| `curl -s "https://struq.nl/api/assets?filter=typography"` | `items[].slug`, `items[].data.display.name`, `items[].data.body.name`, their `stack`, `items[].data.intent` |
|
|
129
|
+
| `curl -s "https://struq.nl/api/assets?filter=design_system"` | `items[].slug`, `items[].data.silhouette.sectionOrder`, `items[].data.paletteRef`, `items[].data.typographyRef` |
|
|
130
|
+
| `curl -s "https://struq.nl/api/media/packs"` | `packs[].slug`, `packs[].description` (step 5) |
|
|
131
|
+
|
|
132
|
+
Testing only: `STRUQ_API_BASE=https://staging.struq.nl` points `canon.mjs` and `images.mjs` (API and image downloads) at another Struq environment. Users never set it.
|
|
133
|
+
|
|
134
|
+
**How to pick.** Palette by the brief's mood first (`data.mood` and the `sfeer:` tags), then by sector: a restaurant by a wood fire reads `frontier-olea-flour-terracotta` or `frontier-lumen-firelit-flour`; an architect `graphite-ivory` or `midnight-brass`; a mobility launch `frontier-aperture-technical-dark` or `frontier-velar-graphite-scarlet`; care `frontier-thuispost-oat-petrol` or `frontier-stillwater-oat-sage`; fashion `frontier-seve-bone-oxblood`. A palette whose accent is blue or purple (`canon.mjs list` marks it) only when the brand is that colour, declared in STRUQ.md as `Cool hue: declared because <the brand colour>`. The brand's own colours, when the brief gives them, beat the canon: write them into the same token names.
|
|
135
|
+
|
|
136
|
+
Pairing by temperament: `serif-editorial` (Fraunces, warm and premium), `newsreader-classic` (Newsreader, literate, long-form), `editorial-sans-precision` (Sora, architectural, calm), `geometric-modern` (Manrope, self-assured), `display-drama` (Syne, expressive campaigns). `canon.mjs type` applies Struq's overrides for you: Inter is replaced (Fraunces gets Urbanist), a commercial face gets its free stand-in (Neue Haas Grotesk Text becomes General Sans from Fontshare), and `grotesk-workhorse` (Space Grotesk + Inter) is refused. When no canon pairing fits the temperament (quiet luxury, a fashion house, a poster voice), use the Signature set in [references/typography.md](references/typography.md) §3.1 through `canon.mjs type --display "Cormorant Garamond" --body "Jost"`.
|
|
137
|
+
|
|
138
|
+
Design system by the business: `gallery-first` when the work is visual (restaurant, architecture, shop), `proof-forward` when the brief has real facts to show, `story-led` for founder-led and care, `loud-campaign` for a launch, `editorial-restrained` for professional services. Use its section order for the arc; drop any section that would need invented content (`proof` and `story-proof` only with real facts, `faq` only with answers the brief gives), and add the Struq floor: the hero canvas, one showpiece at the act break, the designed footer.
|
|
139
|
+
|
|
140
|
+
Every string then takes a role: `font-display text-display` (the h1), `font-display text-h2`, `font-display text-h3`, `text-lead`, `text-body`, `text-small`. Never `text-xl` or `text-5xl`, never `font-bold` on a heading: hierarchy comes from scale. The accent move is one word in the display italic, or the accent colour on one word.
|
|
141
|
+
|
|
142
|
+
No network: keep the `Paper` tokens the recipe ships, set `Palette: offline default (paper)` in STRUQ.md, and run `canon.mjs` again when struq.nl answers.
|
|
143
|
+
|
|
144
|
+
### Step 5: Images (automatic, every build)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
node <skill-dir>/scripts/images.mjs --brief "<the brief, in the user's words>"
|
|
148
|
+
node <skill-dir>/scripts/images.mjs <pack-slug> --brief "<the brief>" # when you picked the pack yourself
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Always pass `--brief`. When the brief's sector words do not point at the pack (a product launch handed `vitrine`, a pack you named yourself), the script keeps only the photos whose alt names something the brief is about (the material, the city, the street), and none when nothing does: `lib/struq-images.ts` then says `imageFit = "none"`. **An off-topic photo is worse than no photo:** with `imageFit` "none" use no photos at all, and the opener is typographic over the WebGL scene (`<HeroOpener>` without `image`); with "partial" use the few that fit, only where their alt fits. `check.mjs` fails a page whose photos share nothing with the brief. With the brief's own sector pack, the script picks the pack by Struq's rule when you name none, and **leaves out the photos whose alt names a subject this client does not do** (a fish restaurant gets the dining room and the wood fire of `proof`, not its flour and dough); the best fits come first in each slot. It downloads what is left to `public/images/`, verifies every file is a real image, and writes `lib/struq-images.ts` with each photo's alt and description. Read every alt before you place a photo: use only photos whose alt fits what this client actually does, and **write every caption from what the alt says**. Never caption a photo with a subject it does not show (no "Vis van het seizoen" under an apron with flour on it). Use them as `images.hero[0]`, `images.card[3]`, and so on; `imageCredit` goes in the footer. With the user's own photos: put them in `public/images/` and use them first; fill the rest from the pack.
|
|
152
|
+
|
|
153
|
+
The pack rule, the same one Struq's MCP uses (`scripts/packs.mjs`): a sector word in the brief picks that sector's pack, matched as whole words; a product launch with no sector word gets `vitrine` (product photography) and the hero scene carries the product; vehicle and mobility words never pull a brief to freight; anything else gets `halide`. The table, by what the photos show:
|
|
154
|
+
|
|
155
|
+
| The brief is | Pack | The photos |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| Restaurant, bistro, bakery, café, wine bar, catering | `proof` | wood-fire kitchen, dining room before service, bread, plates |
|
|
158
|
+
| Shop, ceramics, cosmetics, homeware, product brand | `vitrine` | objects on stone under soft light |
|
|
159
|
+
| Architect, real estate, makelaar, property, interiors studio | `datum` | concrete, glass and timber, corrected verticals, no people |
|
|
160
|
+
| Construction, trades, installation, renovation | `mortar` | scaffold, timber frame, tools, backs and hands |
|
|
161
|
+
| Hotel, holiday home, travel, terrace, pool | `portico` | limewash, shutters, still water, late warm light |
|
|
162
|
+
| Law, accountancy, consultancy, finance, notary | `ledger` | oak desks, closed volumes, brass |
|
|
163
|
+
| Design studio, agency, print, illustration, writer | `quire` | studio desk mid-project, paper, ink, swatches |
|
|
164
|
+
| Software, SaaS, AI, anything with nothing to photograph | `halide` | glass, machined metal and light in darkness |
|
|
165
|
+
| Electronics, hardware, devices, engineering, deep tech | `circuit` | copper, solder and connectors at macro range |
|
|
166
|
+
| Logistics, freight, distribution, warehousing (vracht, distributie, magazijn) | `transit` | depots, wet road, containers under sodium light |
|
|
167
|
+
| A product launch with no sector of its own: an e-bike, a cargo bike, a device, a drink | `vitrine` | the product's world on stone under one light; the hero scene carries the product |
|
|
168
|
+
| Sport, gym, running, coaching for performance | `cadence` | training spaces after dark, chalk and steel |
|
|
169
|
+
| Yoga, physio, spa, wellness, movement | `vessel` | studio floor, still water, linen, sand tones |
|
|
170
|
+
| Care, therapy, coaching, psychology, a small B&B | `linen` | calm rooms, linen, quiet hands |
|
|
171
|
+
| Hair salon, beauty, barber, nails | `sable` | salon light in taupe and brass |
|
|
172
|
+
| Fashion, textile, tailoring, clothing | `bolt` | cloth, rails, folds and seams, no bodies |
|
|
173
|
+
| Farm, growers, farm shop, food provenance | `loam` | soil, crops, greenhouse in low morning light |
|
|
174
|
+
| Nature, outdoor, coast, landscape, sustainability | `brume` | Atlantic coast under overcast, fog, stone |
|
|
175
|
+
| Crafts, metalwork, furniture making, workshop | `forge` | hot steel, worn tools, warm darkness |
|
|
176
|
+
| School, childcare, education | `primer` | empty rooms at child height, birch and felt |
|
|
177
|
+
| Events, venues, theatre, conferences | `assembly` | the room before it fills, chairs and stage light |
|
|
178
|
+
|
|
179
|
+
The table reads the photos, never the API's `sector` field: production still carries old labels that do not match the pictures (`vessel` is wellness, not kitchen; `datum` is architecture, not data), so the skill never chooses by that field. Nothing fits closely: take the pack whose light matches the palette (warm and light `linen`, warm and dark `forge`, cool and dark `halide`, cool and light `datum`). Never skip images. One pack per site. Translate the alts in `lib/struq-images.ts` when the site is not Dutch. Then set the title, description and `lang` in `app/layout.tsx`.
|
|
180
|
+
|
|
181
|
+
### Step 6: The hero: a full-bleed cinematic opener
|
|
182
|
+
|
|
183
|
+
The first screen is the photo, edge to edge, with giant type set across it. Not a text block beside a picture. Use `toonstuk-hero-opener` (copied to `components/struq/showpieces/`):
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
<HeroOpener
|
|
187
|
+
image={images.hero[0]}
|
|
188
|
+
title="Koken op"
|
|
189
|
+
accent="vuur."
|
|
190
|
+
lead="Een houtoven, een wijnbar en een zaal aan het water."
|
|
191
|
+
cta={{ href: "#reserveren", label: "Reserveer een tafel" }}
|
|
192
|
+
header={<SiteHeader />}
|
|
193
|
+
/>
|
|
194
|
+
// or the name split to both edges with the photo between:
|
|
195
|
+
<HeroOpener image={images.hero[0]} split={["Vuur", "Noord"]} lead="..." cta={{ href: "#reserveren", label: "Reserveer een tafel" }} header={<SiteHeader />} />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- **The photo fills the viewport,** the ripple WebGL layer living in it, with a flat scrim at a fixed opacity so the copy reads at 4.5:1. Never a gradient.
|
|
199
|
+
- **The display line runs at 14 to 20vw** (the opener sets 19vw on phones, 16vw on desktop) along the held-clear bottom, or as a split wordmark. Eight words at most; one word may be the italic accent. `check.mjs` fails a hand-built hero line under 10vw.
|
|
200
|
+
- **One short lead and one confident rectangular CTA** above the line (`MagneticButton size="lg"`): one clean button, never a pill, never inside a container card or a box of its own.
|
|
201
|
+
- **The lead sits on the photo's calm side.** The opener reads the photo's `negativeSpace` from the beeldbank (written into lib/struq-images.ts) and puts the lead there, the CTA on the other side; pass `calm="left"` or `calm="right"` for your own photo. Where there is no calm side, the lead gets a flat backing at a fixed opacity and is set a step larger (text-h3). Never a lead on bright, busy detail without one.
|
|
202
|
+
- **Display lines get word spacing in step with their size:** 0.06 to 0.1em at poster sizes (the opener uses 0.09em), the italic accent word included, so tight tracking never runs two words into one.
|
|
203
|
+
- **The handover is a move:** on scroll the opener pins, scales down and darkens while the next section slides up over it. The section after it needs nothing extra: globals.css gives it a ground and the layer above.
|
|
204
|
+
- **The header** with the wordmark (`data-wordmark`) goes in the `header` prop, set over the photo in `text-on-photo`.
|
|
205
|
+
- **Never model the client's product from primitives:** a launch uses the `vitrine` product photography in the opener. For a portfolio brief ("laat ons werk zien"), the opener photo is a real project of the client.
|
|
206
|
+
- Phones and reduced motion get the composed still: the same full-bleed photo and line, no pin.
|
|
207
|
+
|
|
208
|
+
A hand-built hero is allowed when the brief needs something the opener does not do, under the same rules: a pack photo in the hero (`check.mjs` fails one without), the copy marked `data-quiet` (the `SplitHeadline` h1 marks itself), the line at 10vw or more, a header with the wordmark.
|
|
209
|
+
|
|
210
|
+
**One contrast scene.** Every site has one dark or full-colour scene, at most two, so the page does not stay on one ground: wrap one section in `scene-inverse` (the palette's dark ground with light type) or `scene-field` (a full-strength colour field). The tokens turn inside, so any recipe in it follows. Good places: the agency's scrub connector, the services rows, the project stack, the invitation. `canon.mjs palette` writes the `inverse` and `field` roles from the canon palette when it has them, and swaps text and ground when it does not. `check.mjs` fails a site with none, or with more than two.
|
|
211
|
+
|
|
212
|
+
### Step 7: Sections on the arc
|
|
213
|
+
|
|
214
|
+
Five to seven sections after the hero, in this order of beats: **orient** (what it is, one statement beside a big image), **prove** (the real specifics: the menu, the services, the projects, typeset), the **showpiece**, **deepen** (detail photos and short text), **invite** (the one CTA again). Then the footer. Patterns with code: [references/sections.md](references/sections.md).
|
|
215
|
+
|
|
216
|
+
- Every image in a `MediaFrame` from `lib/struq-images.ts`, declared ratios only (16/9, 3/2, 4/5, 1/1, 21/9), never the same photo twice.
|
|
217
|
+
- Text groups in `<Reveal>`: a heading and its paragraph rise together.
|
|
218
|
+
- Break the grid at least twice: an image bleeding off the viewport edge, an image overlapping the next section (`-mb-[12vh] relative z-10`), oversized type running off the canvas.
|
|
219
|
+
- Vary the layouts: after a 5/7 split comes a full-bleed band or hairline rows, never three equal cards.
|
|
220
|
+
- Section padding varies by role: dense 6 to 8rem, breathers 12 to 20rem. One section should feel too empty by SaaS standards.
|
|
221
|
+
- Wayfinding without eyebrows: the heading itself, a hairline rule (`border-t border-line`), a change of surface step, a jump in scale.
|
|
222
|
+
- **Captions on media are allowed:** a metadata line directly under or on an image or frame, describing it ("Klant · Woningbouw · 2025"), may be small and tracked. Set it as `<figcaption>` or mark it `data-caption`. A label above a section heading stays an eyebrow and stays banned, and so do numbered steps.
|
|
223
|
+
- **A display heading never sits under or behind an image box,** and never inside an `overflow-hidden` box that would clip its letters: give it its own grid cell beside the image. `check.mjs` fails a clipped display heading and an image reference past what the pack holds (`images.card[5]` when the pack kept four), which renders as an empty swatch. The inline photos of `toonstuk-manifesto-scrub` are a word's size: give them a subject (card, column or hero photos), never a texture (detail, backdrop), which reads as a grey swatch.
|
|
224
|
+
|
|
225
|
+
### Step 8: The showpieces (toonstuk)
|
|
226
|
+
|
|
227
|
+
Struq builds impressive agency, brand, product and company sites, not simple conversion pages. The showpieces are what make them so: directed, pinned, scroll-scrubbed scenes on the one clock. `recipes.mjs` copies them to `components/struq/showpieces/`, one file per stable slug; each has a composed still for phones and reduced motion built in.
|
|
228
|
+
|
|
229
|
+
**Pick one lead showpiece and at most one supporting move,** from this table, by the kind of client. Do not improvise a third. When the user names one in the brief ("met de project-stack", "with the panel wipe"), use that one as the lead, whatever the table says.
|
|
230
|
+
|
|
231
|
+
| The client is | Lead showpiece | Supporting move (at most one) |
|
|
18
232
|
|---|---|---|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
233
|
+
| Architect, studio or portfolio with real project photos | `toonstuk-project-stack` | `toonstuk-image-to-fullbleed` |
|
|
234
|
+
| Architect, studio or portfolio without project photos | `toonstuk-image-to-fullbleed` | `toonstuk-manifesto-scrub` |
|
|
235
|
+
| Product launch | `toonstuk-image-to-fullbleed`, with the hero scene carrying the product | `toonstuk-pixel-resolve` in the hero |
|
|
236
|
+
| Restaurant, hospitality, hotel | `toonstuk-panel-wipe` | `toonstuk-manifesto-scrub` |
|
|
237
|
+
| Agency or creative brand | `toonstuk-scrub-connector` | `toonstuk-fill-rows` for the services |
|
|
238
|
+
| Editorial, a writer, a publication | `toonstuk-manifesto-scrub` | `toonstuk-image-to-fullbleed` |
|
|
239
|
+
| Anything else | `toonstuk-image-to-fullbleed` | none |
|
|
23
240
|
|
|
24
|
-
|
|
241
|
+
Every site closes with `toonstuk-wordmark-finale` as its footer; it does not count as the supporting move.
|
|
242
|
+
|
|
243
|
+
| Slug | Component | What it does |
|
|
244
|
+
|---|---|---|
|
|
245
|
+
| `toonstuk-image-to-fullbleed` | `ImageToFullbleed` | A small centred photo unclips to the full viewport while pinned, the name split to either side and travelling off the edges, one line rising over the open photo. |
|
|
246
|
+
| `toonstuk-panel-wipe` | `PanelWipe` | Two ground-colour panels, each carrying one word, part to reveal a full-bleed photo and close again over it, carrying the headline into the next scene. |
|
|
247
|
+
| `toonstuk-scrub-connector` | `ScrubConnector` | One accent bar rotates, stretches and travels across a pinned stretch until it lands as a level line between two words. |
|
|
248
|
+
| `toonstuk-project-stack` | `ProjectStack` | Pinned full-bleed project cards slide over each other, the one beneath receding; the cursor becomes a round "Bekijk" label over them. Only with the client's own project photos. |
|
|
249
|
+
| `toonstuk-manifesto-scrub` | `ManifestoScrub` | One large statement fills word by word on scroll, with small photos opening inside the line (`{0}`, `{1}` in the text). |
|
|
250
|
+
| `toonstuk-fill-rows` | `FillRows` | Large hairline rows of services or chapters; on hover and focus a row floods with the accent and its detail slides in. |
|
|
251
|
+
| `toonstuk-wordmark-finale` | `WordmarkFinale` | The footer: the name fitted edge to edge, letters rising out of a mask, one closing line and a live local clock. |
|
|
252
|
+
| `toonstuk-pixel-resolve` | `PixelResolve` | The hero photo arrives as coarse pixels and resolves to sharp in held steps, then the plain image stays. |
|
|
25
253
|
|
|
26
|
-
**
|
|
254
|
+
**The direction these showpieces need** (they look cheap without it):
|
|
255
|
+
- Two or three colours from the canon palette, at full strength: the ground, the ink, the accent. No tints.
|
|
256
|
+
- Display type at 12 to 20vw against small body copy. The display type is the composition, not a heading above it.
|
|
257
|
+
- One italic serif accent at most, on one word.
|
|
258
|
+
- Full-bleed beeldbank photography (or the client's own), never framed small in a box when a scene can carry it.
|
|
259
|
+
- One idea per screen. Hold on it.
|
|
260
|
+
- Transitions are scale, mask and clip moves between full-bleed scenes, never fades between boxes.
|
|
261
|
+
- SplitText on display lines, long eases, holds on every key frame; nothing bounces.
|
|
262
|
+
- Struq's rules still win over any reference: no eyebrow or dot labels, no pills (the round cursor label is not a pill), no stat rows ("20+", "51+"), no three identical cards, no numbered sections, no mono flavour, no Inter, no dashes, no blue or purple by default, and never a reference's layout, copy or images.
|
|
27
263
|
|
|
28
|
-
|
|
264
|
+
Never two pinned showpieces back to back: put a breathing section between them. The Struq MCP or a brief may budget more; never fewer than one lead.
|
|
29
265
|
|
|
30
|
-
|
|
266
|
+
### Step 9: The footer
|
|
31
267
|
|
|
32
|
-
|
|
33
|
-
|---|---|---|---|
|
|
34
|
-
| Brand | Name, SVG wordmark and mark, favicon set | Positioning before name; the wordmark is drawn from the display face | [brand.md](references/brand.md) |
|
|
35
|
-
| Typography | Signature first: one pairing id (S1 to S7), one display face and one text face, set through role tokens | Display runs **large and light (300 to 400)**. Hierarchy from scale contrast, not weight. One signature move per site. | [typography.md](references/typography.md) |
|
|
36
|
-
| Palette | Tokens on `:root` via Tailwind `@theme` (v4 or later) | Temperature-shifted neutrals do 95% of the work, one accent | [vibe-presets.md](references/vibe-presets.md) |
|
|
37
|
-
| Scale | One fluid root-rem knob | Everything in rem; breakpoints change layout, never size | [scaling.md](references/scaling.md) |
|
|
38
|
-
| Smooth scroll | Lenis, driven by `gsap.ticker` | One requestAnimationFrame for the whole page | [setup.md](references/setup.md), [feel-layer.md](references/feel-layer.md) |
|
|
39
|
-
| Scroll motion | GSAP + ScrollTrigger | Scrub for scroll-tied values; masked reveals over fades | [scroll-reveals.md](references/scroll-reveals.md) |
|
|
40
|
-
| Text motion | SplitText | Masked line rise, char stagger, scroll color fill | [typography.md](references/typography.md) |
|
|
41
|
-
| SVG motion | DrawSVG, MorphSVG, MotionPath | Draw-on-scroll paths, logo morphs, signature marks | [svg-motion.md](references/svg-motion.md) |
|
|
42
|
-
| Showpieces | Pinned scrub, horizontal galleries, sticky stacks | One or two per site, placed at act breaks | [showcase.md](references/showcase.md), [pinning-scrub.md](references/pinning-scrub.md) |
|
|
43
|
-
| Icons | Struq Icons (`@struq/icons`) by default, after the brand's own set; else Phosphor (light), else Iconoir or Tabler | One icon family per site, never mixed, never Lucide by reflex | [iconography.md](references/iconography.md) |
|
|
44
|
-
| Hover and pointer | GSAP `quickTo` (magnetic, cursor), CSS + GSAP hover scenes | Garnish, applied last | [micro-interactions.md](references/micro-interactions.md) |
|
|
45
|
-
| React lifecycle | Motion (`motion/react`) | Mount/unmount, `layoutId`, gesture state | GSAP vs Motion below |
|
|
46
|
-
| Overlays | Radix primitives (Dialog for menus and modals) | Focus trap, Escape, scroll lock paired with `lenis.stop()` | [nav-header.md](references/nav-header.md) |
|
|
47
|
-
| Page flow | Full-screen cover, swap, reveal transitions; session-gated loader | Every route change is a designed moment | [route-transitions.md](references/route-transitions.md), [loaders.md](references/loaders.md) |
|
|
48
|
-
| 3D moments | Three.js, R3F, Drei, postprocessing | Lazy island, poster fallback, driven from the same ticker | [webgl-r3f.md](references/webgl-r3f.md), [webgl-scenes.md](references/webgl-scenes.md) |
|
|
49
|
-
| Findability | Metadata API, an OG image per route, sitemap, robots, JSON-LD | One h1 per page; loaders and reveals never hide content from the HTML | [seo.md](references/seo.md) |
|
|
50
|
-
| Performance | ffmpeg, next/image, gltf-transform, next/font | Assets first: budgets per role, posters always, WebGL never in the first-load bundle | [performance.md](references/performance.md) |
|
|
268
|
+
`<WordmarkFinale name="<the name>" line="<one closing line>" place="<the city>" timeZone="Europe/Amsterdam">` with the one CTA, the contact facts the brief gave (and only those) and the image credit as children: `<a href={imageCredit.href}>{imageCredit.label}</a>`. The name is fitted to the full width and marked `data-wordmark`.
|
|
51
269
|
|
|
52
|
-
|
|
270
|
+
### Steps 10 to 12: Build, look, check
|
|
53
271
|
|
|
54
|
-
|
|
272
|
+
- `npm run build` must pass. Fix type errors in your own code; never delete a recipe to get green.
|
|
273
|
+
- **Look**, when you have a browser tool: serve the production build (`npx next start -p 4321`), screenshot 390 and 1440, scroll the page once top to bottom. Check: the headline reads, no frame is blank after its reveal, the showpiece engages and releases without a jump, nothing overflows sideways at 390. No browser tool: write `Verify: not seen, no browser` in STRUQ.md. Never claim you saw what you did not.
|
|
274
|
+
- **Check**: `node <skill-dir>/scripts/check.mjs`. Fix every FAIL at its file and line, run it again, and repeat until `RESULT: PASS`. Then tick line 12 and report.
|
|
55
275
|
|
|
56
|
-
|
|
276
|
+
## With the Struq MCP (Pro): the build line
|
|
277
|
+
|
|
278
|
+
When the `struq_*` tools are connected, the build runs on Struq's build line, and this checklist replaces steps 2, 4 and 7 of the Build checklist (the others stay). Every call is logged with `log.mjs mcp`; `check.mjs` fails a build whose log shows a bouwplan but fewer sectiespec calls than the plan's arc rows, and an approval stop that is not between the bouwplan and the projectschil.
|
|
57
279
|
|
|
58
280
|
```
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
font-display text-display, font-display text-h2, type-h3 text-h3,
|
|
75
|
-
text-lead, text-body, text-small, eyebrow text-eyebrow, font-ui text-ui,
|
|
76
|
-
stat text-stat, and font-display text-statement for the one Orient line
|
|
77
|
-
or manifesto. Eyebrows: uppercase 0.75rem (the 12px floor), tracked 0.12
|
|
78
|
-
to 0.18em per face, ink-muted, one per section, never ink-faint.
|
|
79
|
-
FLOOR: body at least 1rem, muted at least 0.875rem, nothing under 12px.
|
|
80
|
-
CONTRAST: body at least 4.5:1, large text at least 3:1. ink-faint is never
|
|
81
|
-
text: hairlines and disabled states only.
|
|
82
|
-
SIGNATURE: every STRUQ.md declares one typographic signature move
|
|
83
|
-
(typography.md §5: weight-in, soft-scrub, type-mask-media,
|
|
84
|
-
fit-width-wordmark, a cropped display). The masked line rise is a
|
|
85
|
-
technique, not the default hero. Mono only for literal code. Headings get
|
|
86
|
-
text-wrap: balance; body gets text-wrap: pretty.
|
|
87
|
-
|
|
88
|
-
SPACING: Base-8 rhythm with deliberate asymmetry. Section padding varies by
|
|
89
|
-
role: hero 12 to 20rem, dense content 6 to 8rem, breathers 16 to 24rem. NEVER
|
|
90
|
-
uniform py-16 on every section. Whitespace is a material: at least one section
|
|
91
|
-
should feel "too empty" by SaaS standards.
|
|
92
|
-
|
|
93
|
-
SCALING: One fluid root-rem knob, not per-breakpoint size ladders:
|
|
94
|
-
html { font-size: clamp(1rem, 0.92rem + 0.2vw, 1.125rem); }
|
|
95
|
-
16px at 390, about 17.6px at 1440, frozen at 18px from about 1640. Every
|
|
96
|
-
clamp() keeps a rem term in its middle so zoom works; a vw-only middle is a
|
|
97
|
-
bug. Author everything in rem; px only for 1px hairlines. Breakpoints change
|
|
98
|
-
LAYOUT (columns, stack or row), never size. Display roles carry their own
|
|
99
|
-
clamp() on top.
|
|
100
|
-
|
|
101
|
-
SURFACE: Elevation is a tonal step, not a drop shadow. Dark mode: each layer
|
|
102
|
-
steps LIGHTER (#0A0A0B, #111113, #19191C, #232328). Light mode: pick one
|
|
103
|
-
direction and keep it (panels a shade deeper than a toned page, or white lifted
|
|
104
|
-
off a toned page). Hairline borders about 8 to 12% off the surface; in light
|
|
105
|
-
mode a border is the ink at low alpha (rgb(<ink> / 0.08 to 0.14)), never a
|
|
106
|
-
flat gray hex. Shadows only for true floating elements, then large, soft and faint
|
|
107
|
-
(0 24px 80px -12px rgb(0 0 0 / 0.4)). Never shadow-md or shadow-lg.
|
|
108
|
-
|
|
109
|
-
COLOR: One accent, used sparingly (links, the key CTA, one highlight per
|
|
110
|
-
viewport). Neutrals are temperature-shifted (warm cream and off-black, or cool
|
|
111
|
-
charcoal), never pure #FFF and #000 with gray-500 text. A second accent is a
|
|
112
|
-
declared genre choice with its own token and the same budget. No blue to
|
|
113
|
-
purple gradients, no gradient text, no glassmorphism cards.
|
|
114
|
-
|
|
115
|
-
MOTION: Lenis smooth scroll (lerp 0.09 to 0.11) on one gsap.ticker clock,
|
|
116
|
-
unless the genre calls for native scroll. Every entrance animates opacity AND
|
|
117
|
-
transform (y 24 to 80px, or a clip or overflow mask). Opacity-only fades are
|
|
118
|
-
forbidden. Stagger siblings 0.06 to 0.1s. Durations: micro 0.2 to 0.3s,
|
|
119
|
-
standard UI 0.3 to 0.6s, reveals 0.6 to 1.0s, hero and cinematic 1.0 to 1.6s;
|
|
120
|
-
exits run about 60% of their entrance. Eases: entrances expo-out
|
|
121
|
-
cubic-bezier(0.16, 1, 0.3, 1) (expo.out); exits power2.in; position swaps
|
|
122
|
-
quart-in-out cubic-bezier(0.76, 0, 0.24, 1) (power3.inOut); curtains and
|
|
123
|
-
panels quint-in-out cubic-bezier(0.83, 0, 0.17, 1) (power4.inOut). Never
|
|
124
|
-
default ease-in-out everywhere.
|
|
125
|
-
Scroll-tied effects scrub; they do not fire time-based tweens at a threshold.
|
|
126
|
-
prefers-reduced-motion: gate transforms, keep opacity.
|
|
127
|
-
|
|
128
|
-
COMPOSITION: Break the grid at least twice per page: an image bleeding off
|
|
129
|
-
the viewport, an element overlapping two sections, oversized type running off
|
|
130
|
-
canvas, a sticky element. No three identical icon cards in a row. Vary section
|
|
131
|
-
layout: after a 50/50 split comes full-bleed or an asymmetric 1/3 and 2/3.
|
|
132
|
-
Icons: ONE coherent family chosen for the brand, one stroke and size,
|
|
133
|
-
ink-colored. Never emoji, never the AI-cliche icons (Sparkles, Rocket, Zap),
|
|
134
|
-
never icons in colored chips. Animated icons run under 500ms on click and
|
|
135
|
-
never loop in standard UI.
|
|
136
|
-
|
|
137
|
-
DETAILS THAT SELL IT: Real running copy. Tracked uppercase eyebrows as
|
|
138
|
-
wayfinding, not 01/02/03 stamped on every section (one deliberate oversized
|
|
139
|
-
numeral is fine). The footer is designed as hard as the hero. Custom
|
|
140
|
-
::selection color. Visible, designed focus-visible states. Every route change
|
|
141
|
-
is a designed transition, not a hard cut.
|
|
281
|
+
## MCP build line
|
|
282
|
+
- [ ] M1 struq_bouwplan with the brief; keep its planToken; count its arc rows
|
|
283
|
+
node <skill-dir>/scripts/log.mjs mcp struq_bouwplan --rows <arc rows> --token <planToken>
|
|
284
|
+
- [ ] M2 The one approval stop: show the plan (palette, type, arc, showpiece) and stop once with
|
|
285
|
+
"Akkoord? Heb je een logo, foto's of echte cijfers, stuur ze nu." The plan's own needs (its
|
|
286
|
+
`needs` questions, missing photos) go into that same question. Log the reply as Step 2 Approval.
|
|
287
|
+
- [ ] M3 struq_projectschil with the planToken: build the files it returns
|
|
288
|
+
node <skill-dir>/scripts/log.mjs mcp struq_projectschil --token <planToken>
|
|
289
|
+
- [ ] M4 struq_motion with the planToken (a marketing site)
|
|
290
|
+
node <skill-dir>/scripts/log.mjs mcp struq_motion --token <planToken>
|
|
291
|
+
- [ ] M5 struq_achtergrond: build the background it returns as the hero scene (below)
|
|
292
|
+
node <skill-dir>/scripts/log.mjs mcp struq_achtergrond --token <planToken>
|
|
293
|
+
- [ ] M6 For EVERY arc row, in order: struq_sectiespec with the planToken and the row, then write
|
|
294
|
+
that row's code from the spec, then log it. Never write a row without its spec.
|
|
295
|
+
node <skill-dir>/scripts/log.mjs mcp struq_sectiespec --row <n> --id <arc id> --token <planToken>
|
|
142
296
|
```
|
|
143
297
|
|
|
144
|
-
|
|
298
|
+
The plan wins for palette, type, arc, copy budget and motion values. The floor in this file still holds on top of it: the hero opener, one showpiece at least, the contrast scene, every check passing. A refusal from `struq_sectiespec` names the step that comes first: do that step, never work around it.
|
|
145
299
|
|
|
146
|
-
|
|
300
|
+
**Photos: the skill's rule wins.** When the plan names a pack or photos that do not fit the brief (a cargo bike handed product shots of vases), run `images.mjs <that pack> --brief "<the brief>"` anyway: it keeps only the photos whose alt names the brief, and none when nothing fits. No off-topic photo ever ships because a plan named it; the opener then goes typographic over the WebGL scene, and the approval stop asks the client for photos.
|
|
147
301
|
|
|
148
|
-
|
|
302
|
+
**When `struq_achtergrond` returns a background, build that one.** It is the hero layer. Write it as `components/struq/scenes/<name>.ts` with the same `createScene` contract as the three shipped scenes ([references/hero-scenes.md](references/hero-scenes.md)), take its colours from the palette passed in (so it is recoloured to the site's tokens), add it to the `SCENES` map in `HeroCanvas.tsx`, and use `<HeroCanvas scene="<name>" />`. Never skip it because it is hard to check without a browser: HeroCanvas keeps the poster when the scene fails, so building it can never leave the hero empty.
|
|
149
303
|
|
|
150
|
-
|
|
151
|
-
2. **Typography.** The Signature pairing, the light display weight, the role tokens, the eyebrow system, the one signature move. This is 70% of "looks designed". Set the root-rem knob ([references/scaling.md](references/scaling.md)) first, then the type system ([references/typography.md](references/typography.md)).
|
|
152
|
-
3. **Palette and surfaces.** Kills the "Tailwind default" look instantly. [references/vibe-presets.md](references/vibe-presets.md)
|
|
153
|
-
4. **Masked, staggered scroll reveals.** The single highest-payoff motion technique, including the curtain section reveal. [references/scroll-reveals.md](references/scroll-reveals.md)
|
|
154
|
-
5. **The feel layer.** Lenis lerp, scrub smoothing, ease selection. Wrong values make good techniques feel cheap. [references/feel-layer.md](references/feel-layer.md)
|
|
155
|
-
6. **Nav and header.** On screen for the whole session; the fullscreen menu is often the most art-directed moment on the site. [references/nav-header.md](references/nav-header.md)
|
|
156
|
-
7. **Imagery and art direction.** One grade, declared ratios, treated assets. Ungraded mixed stock undoes everything above it. [references/imagery.md](references/imagery.md). No photos of their own? Struq's beeldbank comes before stock: [references/struq-imagery.md](references/struq-imagery.md)
|
|
157
|
-
8. **Composition: overlap, bleed, z-choreography.** [references/composition.md](references/composition.md), and how each technique translates at 360 to 430px: [references/mobile-composition.md](references/mobile-composition.md)
|
|
158
|
-
9. **Showpieces.** Pinned scrub timelines, horizontal galleries, sticky stacks, path journeys. High cost; one or two per site at act breaks. Pick by id from [references/showcase.md](references/showcase.md).
|
|
159
|
-
10. **Page flow.** Full-screen route transitions ([references/route-transitions.md](references/route-transitions.md)), a session-gated first-load loader ([references/loaders.md](references/loaders.md)), per-zone entrances ([references/page-entrances.md](references/page-entrances.md)), section curtains and match cuts ([references/transitions.md](references/transitions.md)). Loader invariant: the pre-paint revisit gate and the live animation phase are separate state machines. A persistent `seen` flag must never hide a curtain that is still playing.
|
|
160
|
-
11. **Text and SVG choreography.** SplitText beyond the line reveal, DrawSVG, MorphSVG. [references/typography.md](references/typography.md), [references/svg-motion.md](references/svg-motion.md)
|
|
161
|
-
12. **Hover and pointer.** Magnetic buttons, cursor followers, hover scenes. Garnish, not the meal. [references/micro-interactions.md](references/micro-interactions.md)
|
|
162
|
-
13. **WebGL moments.** Highest cost of all; only when a section genuinely calls for it. Integration first ([references/webgl-r3f.md](references/webgl-r3f.md)), then Drei, postprocessing and interaction ([references/webgl-scenes.md](references/webgl-scenes.md)). If the whole page is one 3D experience, switch to Directed mode.
|
|
304
|
+
## The taste baseline
|
|
163
305
|
|
|
164
|
-
|
|
306
|
+
What the recipes and the check enforce, and what you keep when you write your own code:
|
|
165
307
|
|
|
166
|
-
|
|
308
|
+
- **Type:** two faces; display large and light (300 to 400), leading 0.92 to 1.0, tracking tighter at size; body 1 to 1.0625rem at 1.6+; nothing under 12px; body at 4.5:1. Headings `text-wrap: balance`, body `pretty`. Mono only for literal code.
|
|
309
|
+
- **Scale:** one fluid root-rem knob (in globals.css). Author in rem; breakpoints change layout, never size.
|
|
310
|
+
- **Surface:** elevation is a tonal step plus a hairline (`border-line`), never a drop shadow. Radius 0 to 4px.
|
|
311
|
+
- **Colour:** temperature-shifted neutrals, one accent, flat fills. No gradient anywhere, no gradient text, no glass.
|
|
312
|
+
- **Motion:** every entrance moves and fades (never opacity alone), expo.out, stagger 0.06 to 0.1s, reveals 0.6 to 1.0s, hero 1.0 to 1.6s; scroll-tied effects scrub; one clock; reduced motion keeps opacity and drops transforms.
|
|
313
|
+
- **Composition:** break the grid twice, vary section layouts, one too-empty section, the footer designed as hard as the hero.
|
|
314
|
+
- **Details:** real running copy, custom `::selection`, designed `:focus-visible`, one icon family if any icons at all ([references/iconography.md](references/iconography.md)), never Sparkles, Rocket or Zap.
|
|
167
315
|
|
|
168
|
-
|
|
316
|
+
Before and after pairs for every tell: [references/anti-patterns.md](references/anti-patterns.md).
|
|
169
317
|
|
|
170
|
-
##
|
|
318
|
+
## The canonical references, and where Struq overrides them
|
|
171
319
|
|
|
172
|
-
|
|
320
|
+
For craft (layout, type, motion, Lenis, SplitText, reveals, pinned and horizontal showpieces, WebGL heroes, custom cursors) the canonical sources are Roy's `deck-design` skill and the `cinematic-web-experience` skill; this skill's references and recipes follow them. Where Struq deliberately differs, Struq wins, and these are all the differences:
|
|
173
321
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
- `bg-white` page, `text-gray-500` body, `border-gray-200` everywhere
|
|
183
|
-
- Emoji as icons, the cliche trio (Sparkles, Rocket, Zap) as decoration, icons in colored circles
|
|
184
|
-
- Monospace as decoration on eyebrows, labels, stats or badges
|
|
185
|
-
- `01 / 02 / 03` stamped on every section or row
|
|
186
|
-
- Hero = centered h1, subhead, two buttons and a dashboard screenshot in browser chrome
|
|
187
|
-
- Untreated mixed-grade stock at random ratios, glossy AI-render blobs, floating purple spheres
|
|
188
|
-
- `backdrop-blur` glass cards on a mesh-gradient background
|
|
189
|
-
- Hard cuts between routes on a multi-page site
|
|
190
|
-
- Lorem ipsum, "John Doe", "Acme Inc.", "Your journey starts here"
|
|
322
|
+
1. **No eyebrows.** deck-design recommends tracked uppercase labels as wayfinding; Struq treats them as a tell. The heading, a hairline and scale do the wayfinding.
|
|
323
|
+
2. **No gradients and no tinted fills at all,** not even the subtle same-hue ramps deck-design allows. A flat black scrim over a photo is the only translucent layer.
|
|
324
|
+
3. **No pills, no live dots, no uppercase marquees.**
|
|
325
|
+
4. **The canon first.** Palette, pairing and design system come from Struq's canon (step 4), not from a preset you write yourself; the brand's own colours and faces beat the canon.
|
|
326
|
+
5. **Never an empty frame.** deck-design allows a graded placeholder frame; Struq fills every slot from the beeldbank, or a generated fallback plate.
|
|
327
|
+
6. **The cinematic floor is mandatory, not "when the page calls for it":** Lenis on one GSAP clock, a SplitText hero, masked reveals, one showpiece, a WebGL hero layer with poster, a magnetic CTA, on every marketing site.
|
|
328
|
+
7. **No Inter anywhere,** including the two canon pairings that name it (`canon.mjs` swaps it), and no commercial face without a licence (its free stand-in instead).
|
|
329
|
+
8. **Exactly one stop.** The plan stops once for approval with the materials request, and never again; the canon picks are the defaults when the answer is a plain yes. deck-design's longer planning stays available in [references/direction.md](references/direction.md) for a long interactive session.
|
|
191
330
|
|
|
192
|
-
##
|
|
331
|
+
## When something goes wrong
|
|
193
332
|
|
|
194
|
-
|
|
333
|
+
- **The page does not scroll:** `<html>` has `lenis-stopped`? An overlay called `stop()` without `start()`. Otherwise the ticker is not driving Lenis: keep `SmoothScroll` as shipped. [references/setup.md](references/setup.md) §2.
|
|
334
|
+
- **A pin jerks on engage:** never `anticipatePin` with smooth scroll; `scrub: 1`. [references/pinning-scrub.md](references/pinning-scrub.md) §2.
|
|
335
|
+
- **Descenders clipped in the split headline:** the `.struq-line-mask` rule in globals.css gives them room; keep it.
|
|
336
|
+
- **Content stays invisible:** something removed `data-ready` handling or the `js` class script. Restore the layout and globals.css from `<skill-dir>/recipes/app/`.
|
|
337
|
+
- **Anything else in motion:** [references/pitfalls.md](references/pitfalls.md), symptom first.
|
|
195
338
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
| Multi-step timelines and orchestration | Layout and shared-element swaps from React state (`layout`, `layoutId`) |
|
|
200
|
-
| SplitText, DrawSVG, MorphSVG, MotionPath | `whileHover`, `whileTap`, one-off `whileInView` |
|
|
201
|
-
| Flip morphs that coordinate with scroll or a timeline | Press and toggle micro-feedback |
|
|
202
|
-
| Magnetic buttons, custom cursor (`quickTo`) | Local stateful overlays that are not part of a timeline |
|
|
203
|
-
| Horizontal scroll, sticky stacks, R3F uniforms from scroll | |
|
|
204
|
-
|
|
205
|
-
**Before writing any Flip,** read [references/gsap-api.md](references/gsap-api.md) §7.1 to 7.4. Flip has four failure modes that compound and that no typecheck catches. Flip is verified by watching it, never by a green build.
|
|
206
|
-
|
|
207
|
-
## Workflow (Build and Redesign)
|
|
208
|
-
|
|
209
|
-
These step names are the one numbering scheme; [references/brief-router.md](references/brief-router.md) §4 keys its load manifest to them.
|
|
210
|
-
|
|
211
|
-
0. **Detect versions.** Scaffold with `@latest` if the folder is empty, then read the installed `package.json` and record the majors on the `Stack:` line. When unsure of an API, check the installed package's types, `npm view <pkg> version` and the official docs for that major. **When the skill's code disagrees with the installed version's types or docs, the installed version wins:** adapt the code and log the adaptation under LOG in STRUQ.md. Strip the scaffold's starter fonts, page and CSS. The known shifts and how to tell which side a project is on: [references/setup.md](references/setup.md) §0.
|
|
212
|
-
1. **Direction.** Run [references/direction.md](references/direction.md) phases 0 to 4: intake (the brief extracted silently, stack detected and recorded as `Stack:`), brand ([references/brand.md](references/brand.md) when there is none), concept, system, and the site plan via [references/brief-router.md](references/brief-router.md) §2. No usable brief? The router's wizard asks at most three plain-language rounds; that is the only early stop. Write `STRUQ.md`, then send the one gate message: summary, materials request, "Veto anything?" Vite? Every later step translates through [references/stack-targets.md](references/stack-targets.md). Redesigning? First run the asset audit in [references/performance.md](references/performance.md) §2 and read [references/triage.md](references/triage.md).
|
|
213
|
-
2. **Brand files and tokens.** Write the logo, mark and favicon set ([references/brand.md](references/brand.md) §5). Set the root-rem knob, the type system ([references/typography.md](references/typography.md)) and the palette tokens exactly as STRUQ.md records them.
|
|
214
|
-
3. **Structure and SEO.** Routes, metadata, sitemap, robots and JSON-LD from the PLAN's page list ([references/seo.md](references/seo.md)), before any section is styled. Then, with the routes in place, the OG image per shared route ([references/seo.md](references/seo.md) §3).
|
|
215
|
-
4. **Wire providers.** Lenis on the GSAP ticker with the reduced-motion guard ([references/setup.md](references/setup.md)). For multi-page sites, the transition provider ([references/route-transitions.md](references/route-transitions.md)) and optionally a loader ([references/loaders.md](references/loaders.md)).
|
|
216
|
-
5. **Static layout.** Authored in rem, designed at 1440 and 390 at the same time, and checked at 768 wherever a breakpoint changes the column count ([references/mobile-composition.md](references/mobile-composition.md) §4). Type, spacing and surfaces right **before any motion**; motion cannot rescue a flat layout. Nav per [references/nav-header.md](references/nav-header.md), icons per [references/iconography.md](references/iconography.md).
|
|
217
|
-
6. **Motion in layers.** Reveals, then composition, then the one or two showpieces, then text and SVG choreography, then hover and pointer. WebGL last, as a lazy island with a poster. Read [references/pitfalls.md](references/pitfalls.md) before the first tween.
|
|
218
|
-
7. **Optimize and sweep.** Every asset through the budgets in [references/performance.md](references/performance.md); then [references/anti-patterns.md](references/anti-patterns.md) as a checklist and the [references/seo.md](references/seo.md) checklist.
|
|
219
|
-
8. **Verify.** Against a production server only: `next build && next start` (or `vite build && vite preview`), never the dev server, and confirm the port you shoot is owned by that production process (a leftover dev server on the same port answers silently). Then:
|
|
220
|
-
- **Layout:** screenshot at **390, 768, 1440 and 1920** with whatever browser tool the harness has (Playwright or similar). Full-page shots do not show a fixed footer-reveal (the viewport becomes the document height); verify the close from the scroll pass.
|
|
221
|
-
- **Scans:** the 30-second tell scan ([references/anti-patterns.md](references/anti-patterns.md)) and the 60-second feel audit ([references/feel-layer.md](references/feel-layer.md) §5) against the screenshots, not the code.
|
|
222
|
-
- **Motion:** scroll the page end to end with motion on: no dead wheel, no flash on route change, and **every pinned section** engaged and released at a slow and a fast scroll with no jump or snap. Run the descender test ("Gyp, jq." in every masked component at 390 and 1440) and the pin checks in [references/pitfalls.md](references/pitfalls.md) §1 and §2.
|
|
223
|
-
- **No empty regions:** ambitious motion loses to plain pages when a reveal leaves a region blank. Capture a full page with reduced motion, a full page with JavaScript off, and the scroll sequence after each reveal settles; any blank footer, frame, CTA or row that should hold content fails verify ([references/pitfalls.md](references/pitfalls.md) §3.6, §3.7).
|
|
224
|
-
- **Timing:** static PNGs cannot show timing bugs. Take a frame trace of the first 3 seconds (a screenshot every 200 to 250ms from navigation) and confirm each entrance does what its recipe says: the hold frame of a match cut is the photo, a split hero is never shown unsplit, nothing stays at opacity 0.
|
|
225
|
-
- **Vitals:** LCP (with the element it names) and CLS from an in-page `PerformanceObserver` probe under 4x CPU and a throttled network, plus INP from field data or a manual interaction trace, and the first-load JS measured from the served HTML (Turbopack prints no table). Port check, probe, trace and first-load scripts: [references/performance.md](references/performance.md) §8.
|
|
226
|
-
Fix, re-shoot, and write the score under VERIFY in STRUQ.md.
|
|
227
|
-
|
|
228
|
-
## Audit
|
|
229
|
-
|
|
230
|
-
`/struq audit` changes nothing. It returns:
|
|
231
|
-
|
|
232
|
-
1. **Score out of 100**, starting at 100: minus 5 per blocklist tell found, minus 3 per baseline violation (type, spacing, surface, color, motion, composition), minus 10 if the page fails to scroll smoothly or a pin jerks, minus 10 if there is no designed mobile layout, minus 5 per asset over budget ([references/performance.md](references/performance.md) §1, capped at minus 15), minus 5 if social images, the sitemap or page metadata are missing ([references/seo.md](references/seo.md)).
|
|
233
|
-
2. **The tells**, grouped by category, each with the file and line (or screenshot region) and the one-line fix.
|
|
234
|
-
3. **Feel audit** result from [references/feel-layer.md](references/feel-layer.md) §5.
|
|
235
|
-
4. **The three changes with the most visual payoff**, ordered by the leverage ranking above.
|
|
236
|
-
|
|
237
|
-
Keep the score honest. An award-tier page lands at 90+; a typical AI-generated page lands under 50.
|
|
339
|
+
## Depth, when a step needs it
|
|
340
|
+
|
|
341
|
+
Per client type (tension, type, palette fallback, motion, arc, the niche tells, the pack) [niche-packets.md](references/niche-packets.md) · Typography [typography.md](references/typography.md) · palettes and presets [vibe-presets.md](references/vibe-presets.md) · page arc and footer [page-architecture.md](references/page-architecture.md) · composition [composition.md](references/composition.md), on phones [mobile-composition.md](references/mobile-composition.md) · imagery and grading [imagery.md](references/imagery.md), the beeldbank [struq-imagery.md](references/struq-imagery.md) · reveals [scroll-reveals.md](references/scroll-reveals.md) · more showpieces [showcase.md](references/showcase.md), [pinning-scrub.md](references/pinning-scrub.md) · the feel layer [feel-layer.md](references/feel-layer.md) · nav and menus [nav-header.md](references/nav-header.md) · route transitions [route-transitions.md](references/route-transitions.md), loaders [loaders.md](references/loaders.md) · SVG motion [svg-motion.md](references/svg-motion.md) · pointer [micro-interactions.md](references/micro-interactions.md) · WebGL beyond the hero [webgl-r3f.md](references/webgl-r3f.md), [webgl-scenes.md](references/webgl-scenes.md), hero scenes [hero-scenes.md](references/hero-scenes.md) · brand from nothing [brand.md](references/brand.md) · SEO [seo.md](references/seo.md) · performance [performance.md](references/performance.md) · a long, interactive planning session [direction.md](references/direction.md).
|