@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
@@ -1,255 +1,53 @@
1
- # Iconography: one icon family + craft doctrine
1
+ # Icons
2
2
 
3
- ## Decide here
3
+ Struq Icons is the default: `npm i @struq/icons` (MIT), the Iconify set `struq` for non-React stacks, browsable at
4
+ [struq.nl/iconen](https://struq.nl/iconen). A two-tone set on one 24 grid, the base tone for the shape and the accent
5
+ on the part that carries the meaning, each icon with its own hover motion.
4
6
 
5
- Planning reads only this section ([direction.md](direction.md) §4); the rest is for the build.
7
+ ## Whether an icon earns its place
6
8
 
7
- - **Family, in this order, stop at the first that fits:** (1) the brand's own drawn set; (2) **Struq Icons**, the default (`npm i @struq/icons`, MIT, 182 icons: 118 two-tone with a hover motion, 14 status, 50 glyphs; browse at [struq.nl/iconen](https://struq.nl/iconen)); (3) **Phosphor, weight `light`** (`@phosphor-icons/react`, `thin` for luxury) when the set lacks what the site needs and drawing it is not worth it, or the concept wants a softer, rounder line; (4) **Iconoir or Tabler** at stroke 1.25 when the concept wants a sharper, technical line.
8
- - **One icon family per site, never mixed.** Never two, never Lucide by reflex. If the chosen family lacks an icon, draw it to that family's rules or move the whole site down the list (§2).
9
- - **Struq Icons on a Struq site:** `strokeWidth={1.5}` next to light display type (`1.25` for luxury); the accent mapped to the site's own `--color-accent` (§2), not the set's default flame; `states.css` on, so icons rest in one tone and the accent shows where you act.
10
- - **Size and color:** one size per context (`size={20}` inline, `size={24}` nav, or a `size-5` / `size-6` class so the icon rides the root-rem knob), ink-colored; color only for state and semantics.
11
- - **Budget:** functional icons only (nav, close, chevrons, form states, CTA anchors). No 3-up feature-card icons, no Sparkles, Rocket or Zap, no emoji, no colored chips.
12
- - **Motion:** the set's own hover motion (200ms, hover and focus only, never on load, never looping, off under reduced motion) is inside the budget. Other animated icons under 500ms on click, never looping in standard UI, static under reduced motion.
13
- - **Record in STRUQ.md SYSTEM:** `Icons: Struq Icons, @struq/icons, strokeWidth 1.5, accent = --color-accent, states.css on`.
9
+ Functional only: navigation, close, chevrons, an arrow on a text link, form states. Never a row of feature-card icons,
10
+ never an icon in a coloured chip, never sparkles, rocket, lightning, bulb, wand or emoji. Most studio sections need
11
+ none: the blocks carry their links as type.
14
12
 
15
- ---
16
-
17
-
18
- Two separate decisions, never collapse them:
19
-
20
- 1. **Whether an icon earns its place**: the Struq taste gate. Most marketing
21
- sections need *fewer* icons than the reflex wants; icon-as-filler is an AI tell.
22
- 2. **How the icon behaves once it's in**: the craft doctrine below (family, register,
23
- color, state, animation timing, accessibility).
24
-
25
- The gate (§1) governs *where*; the doctrine (§2 to §8) governs *how*. A perfectly-crafted
26
- icon in the wrong place is still wrong.
27
-
28
- ---
29
-
30
- ## 1. The gate: does it earn its place?
31
-
32
- Keep the existing bans hard:
33
-
34
- - **Never** as 3-up feature-card filler (centered icon + h3 + two gray lines), the single
35
- most recognizable generated section ([anti-patterns.md](anti-patterns.md) §2, §13).
36
- - **Never** Sparkles / Rocket / Zap / lightning, or emoji (🚀✨💡). They are the visual signature
37
- of generated UI.
38
- - **Never** in a colored circle/chip background.
39
- - **Never** five sizes / five strokes scattered across a page.
40
-
41
- Icons **earn their place** when they do real work:
42
-
43
- - **Functional affordances**: nav, search, close, chevrons, form-field states, toggles.
44
- - **Scannability** in menus and dashboards: a per-row icon lets the eye decode the list
45
- faster than text alone (humans parse a glyph in ~1/10s).
46
- - **Button visual-anchors**: an icon on a CTA gives a fast hint of the action.
47
- - **Paragraph / feature separators**: between blocks of a "wall of text," an icon marks a
48
- visual point of separation. It must *reinforce* the message, never *replace* it.
49
-
50
- When in doubt, **no icon**. Hairline rules, ghost type, a single oversized numeral as an
51
- accent, or real imagery often carry decoration better in a marketing context.
52
-
53
- ---
54
-
55
- ## 2. Source: one coherent family per project, chosen in this order
56
-
57
- The family is a SYSTEM decision, recorded in STRUQ.md ([direction.md](direction.md) §4), not
58
- something picked per component. Resolve it top to bottom and stop at the first that fits:
59
-
60
- | # | Family | When | Install | Weight to match light display type |
61
- |---|---|---|---|---|
62
- | 1 | **The brand's own icons** | The client already has a drawn set | their files, inlined as components | as drawn |
63
- | 2 | **Struq Icons** | The default for every Struq build: a two-tone set on one 24 grid (a base tone for the shape, an accent on the part that carries the meaning), each icon with its own hover motion and a draw-in variant | `npm i @struq/icons`; sprite, plain SVG and Iconify JSON (prefix `struq`) for non-React stacks | `strokeWidth={1.5}` (the set's default 2 is Lucide weight; `1.25` for luxury) |
64
- | 3 | **Phosphor** | A softer, rounder line, or a site that needs icons the Struq set does not have | `npm i @phosphor-icons/react`, or Iconify `ph:` | `weight="light"` (or `"thin"` for luxury) |
65
- | 4 | **Iconoir** or **Tabler** | A sharper, more technical or architectural line | `npm i iconoir-react` / `@tabler/icons-react`, or Iconify `iconoir:` / `tabler:` | Tabler `stroke={1.25}`; Iconoir `strokeWidth={1.25}` |
66
-
67
- ### Struq Icons: the wiring
68
-
69
- Import the stylesheets once, at the root, before the site's own stylesheet; then import icons by name. Every icon is a named PascalCase export (`Search`, `ArrowRight`, `External`, `ChevronDown`, `Close`); the barrel tree-shakes. Check a name against the installed `dist/index.d.ts` or [struq.nl/iconen](https://struq.nl/iconen) before using it: the set names icons by meaning, so the up-right arrow is `External`, not `ArrowUpRight`.
13
+ ## Wiring (React)
70
14
 
71
15
  ```tsx
72
- // app/layout.tsx (Next) or src/main.tsx (Vite): once, before globals.css
16
+ // app/layout.tsx, once, before globals.css
73
17
  import "@struq/icons/tokens.css";
74
18
  import "@struq/icons/icons.css";
75
- import "@struq/icons/states.css"; // optional: one tone at rest, the accent on hover, focus and current
19
+ import "@struq/icons/states.css"; // one tone at rest, the accent on hover, focus and current
76
20
  ```
77
21
 
78
22
  ```css
79
- /* globals.css: the set's accent follows the site's palette, not its default flame.
80
- On body, so it wins over tokens.css's :root and .dark rules by inheritance. */
23
+ /* globals.css: the set's accent follows the palette, not its default. On body, so it wins by inheritance. */
81
24
  body {
82
25
  --icon-base: currentColor;
83
- --icon-accent: var(--color-accent);
84
- --icon-accent-live: var(--color-accent);
26
+ --icon-accent: var(--accent);
27
+ --icon-accent-live: var(--accent);
85
28
  }
86
29
  ```
87
30
 
88
31
  ```tsx
89
32
  import { ArrowRight, Search } from "@struq/icons";
90
33
 
91
- <a href="/work" className="font-ui text-ui inline-flex items-center gap-2">
92
- Selected work <ArrowRight size={20} strokeWidth={1.5} />
93
- </a>
94
-
95
- <button type="button" className="grid size-12 place-items-center">
96
- <Search size={24} strokeWidth={1.5} label="Search" /> {/* the only content: give it a label */}
97
- </button>
98
- ```
99
-
100
- - **Props:** `size` (16, 20, 24, 32 or 48 px; a `size-5` class overrides it in rem), `strokeWidth`, `label` (names it for screen readers; without one it is `aria-hidden` decoration), `animate` (`"hover"` default, `"draw"`, `"none"`), `color` and `accentColor` (any CSS colour, set per icon; they beat the variables). Other SVG props pass through.
101
- - **Server components.** The icons use no hooks, so they render in server components. Only the draw-in entry is a client component.
102
- - **Draw-in** for one empty state, first run or hero moment, never a list: `import { StruqIconDraw } from "@struq/icons/draw"`, `<StruqIconDraw icon={Inbox} size={48} delay={0.2} />`. It needs `motion` (already in the stack), draws once, never loops, and shows the finished icon on the server, without JS and under reduced motion.
103
- - **Name clashes.** `Link`, `Image`, `File` and `Text` collide with `next/link`, `next/image` and DOM globals; import the `LinkIcon`, `ImageIcon`, `FileIcon` and `TextIcon` aliases instead.
104
- - **Accent off in one area:** `data-icon-accent="off"` on a parent paints the accent in the base colour (needs `icons.css`). `data-icons="live"` keeps the accent on at rest in an area when `states.css` is loaded.
105
- - **Status icons** (one tone) take their colour from the place they sit in: they follow `currentColor`, or a token through `color="var(--color-...)"`, never a hex.
106
- - **Without React:** `sprite.svg` (copy it to `public/`, `<use href="/sprite.svg#search" />`, two-tone, no hover motion), plain SVG files under `svg/`, or `iconify.json` (prefix `struq`, for Iconify loaders in Vue, Svelte, Astro or Tailwind: the icon in the text colour with the accent at half strength, no hover motion, and `struq:<name>-draw` for a draw-in that plays once and ignores reduced motion). Same family, same rules.
107
- - **A missing icon** is drawn to the set's rules (24 grid, round caps and joins, the site's stroke, base and accent as two parts) or the whole site moves to row 3. Never patch the gap with a Phosphor glyph.
108
-
109
- **Never Lucide as the reflex.** It is the default of every generated UI, so it reads as
110
- generated. **Never two families on one site.** If the chosen family is missing an icon, either
111
- draw the missing icon to its rules (its grid, its stroke, its caps and joins) or move the whole
112
- site to the next family. Patching gaps with a second set is the icon version of three display
113
- fonts on one page.
114
-
115
- **Iconify** is an optional transport for families 3 and 4 (and for Struq Icons outside React) when you want any family through one
116
- component: one API over about 200k icons across every major set. It is a delivery method, not a
117
- second family.
118
-
119
- ```bash
120
- # Next / any React # Vite (compile-time, zero runtime, tree-shaken)
121
- npm i @iconify/react npm i -D unplugin-icons @iconify/json
122
- ```
123
-
124
- ```tsx
125
- // @iconify/react: works in both Next and Vite
126
- import { Icon } from "@iconify/react";
127
- <Icon icon="ph:arrow-up-right" className="size-5 text-ink" /> // Phosphor
128
- <Icon icon="iconoir:search" className="size-5 text-ink" /> // Iconoir
129
- ```
130
-
131
- ```ts
132
- // Vite alternative: unplugin-icons, imported as components (no runtime fetch)
133
- import ArrowUpRight from "~icons/ph/arrow-up-right";
134
- <ArrowUpRight className="size-5 text-ink" />
34
+ <a href="/werk">Bekijk het werk <ArrowRight size={20} strokeWidth={1.5} /></a>
35
+ <button type="button"><Search size={24} strokeWidth={1.5} label="Zoeken" /></button>
135
36
  ```
136
37
 
137
- **Pick one family and stay in it.** Consistency in *style, weight, corner treatment, and
138
- fill-vs-stroke* is what makes an icon set read as designed. Lock the chosen family in the
139
- project's icon component so nothing else can be imported by accident.
140
-
141
- ---
142
-
143
- ## 3. Two registers: system vs decorative
144
-
145
- | Register | Use | Traits |
146
- |---|---|---|
147
- | **System** | menus, buttons, inputs, table rows, anything **≤24px** | minimal, reduced to essentials, legible at small size, single stroke |
148
- | **Decorative / expressive** | bigger explanatory moments, feature anchors, animated (Lordicon/Lottie) | richer, more detail, only at **larger** sizes |
149
-
150
- **Complex/decorative icons die at small sizes**: their detail turns to mud below ~24px.
151
- If it's small, it's a system icon. If it's expressive, it's big. Don't mix the registers
152
- within one role.
153
-
154
- One stroke across the set, as the §2 table sets it (Struq Icons `strokeWidth={1.5}`,
155
- Phosphor `weight="light"`, Tabler `stroke={1.25}`, Iconoir `strokeWidth={1.25}`), one size per
156
- context (e.g. `size-5` inline, `size-6` nav), ink-colored by default. Struq Icons' two tones
157
- are not two colours in the §4 sense: at rest under `states.css` they read as one tone, and the
158
- accent is the site's one accent, shown only where the visitor acts.
159
-
160
- ---
161
-
162
- ## 4. Color
163
-
164
- - **One color across the set** by default: ink. An icon set in four hues reads as no
165
- decision made (same rule as the one-accent color system).
166
- - Color earns its place only for **state** (hover/pressed/active) and **semantics**:
167
- red/warning for errors, a success check, etc.
168
- - Never rely on color *alone* to carry meaning (accessibility, see §7).
169
-
170
- ---
171
-
172
- ## 5. States & feedback
173
-
174
- An icon the user can act on must *look* actionable and must confirm the action:
175
-
176
- - Distinct **default / hover / pressed**, via motion, color, or both. A clickable icon
177
- that never changes reads as decoration.
178
- - **Feedback through the icon itself:** a checkmark confirms success, a warning sign flags
179
- an error, a state-swap (empty→full trash) signals what happened. Digital UI lacks the
180
- physical click of a real switch, so this feedback matters *more*, not less.
181
-
182
- ---
183
-
184
- ## 6. Animated icons (Lordicon / Lottie): timing is locked
185
-
186
- Animated icons convey more meaning than static ones and drive engagement, but only with
187
- discipline. The same budget as micro-interactions applies: **2 to 3 per view**, never a field
188
- of looping motion.
189
-
190
- **Timing (hard limits):**
191
-
192
- | Trigger | Duration | Notes |
193
- |---|---|---|
194
- | **Click / tap** | **≤ 500ms** | must keep up with repeat clicks; longer feels out of sync and becomes a distraction by the 3rd press |
195
- | **Hover (decorative)** | **1500-3000ms** | the delight sweet spot; more complex symbol → longer end of the range |
196
- | GIF fallback loop | **2000-5000ms delay between loops** | only if tech forces a looping GIF |
197
-
198
- **Rules:**
199
-
200
- - **No constant loops in dashboards / standard UI.** They become an unwelcome
201
- distraction (especially for attention-sensitive users). Trigger on hover instead.
202
- A looping animation is fine *as a page loader*, not as ambient dashboard decoration.
203
- (This rhymes with the skill-wide "nothing moves that the user didn't cause" principle,
204
- [feel-layer.md](feel-layer.md) §5.)
205
- - **No extreme flashing or flickering**: a seizure/motion-sensitivity hazard. Smooth the
206
- frequency; never strobe.
207
- - **Natural physics only.** Motion that breaks real-world dynamics (unnatural bounce,
208
- impossible 3D, irregular movement) leaves users unsettled and confused. Smooth, weighted,
209
- accurate. Every movement must serve a purpose and match the icon's meaning (a security
210
- icon shouldn't "wave and wiggle").
211
- - **Don't overload.** Too many animated icons, especially on loop, drown the message.
212
-
213
- Animated icons are a micro-interaction: they obey the same library split and gating as
214
- everything in [micro-interactions.md](micro-interactions.md). Lottie/Lordicon players are
215
- state/lifecycle-driven (Motion territory for triggering), hover effects are
216
- `@media (hover: hover)`-gated, and **reduced-motion → static icon** (render the still
217
- frame, never the animation).
218
-
219
- ---
220
-
221
- ## 7. Accessibility
222
-
223
- - **Meaningful icon:** give it an accessible name via `aria-label` (or visible label, §8).
224
- An icon that carries content needs alt text so screen readers convey it.
225
- - **Decorative icon:** `aria-hidden`, which keeps it out of the a11y tree entirely.
226
- - **Never color-alone** for meaning (color-vision-deficient and low-vision users). Pair
227
- with shape, label, or motion.
228
- - **Touch target ≥ 48×48px**: the tappable area can exceed the visual glyph. Keep spacing
229
- between grouped tappable icons so they don't misfire.
230
- - **Reduced motion** → static icon (see §6).
231
-
232
- ---
38
+ - `strokeWidth={1.5}` beside light display type (1.25 for a luxury register); one size per context (20 inline, 24 in
39
+ the bar). `label` names an icon that is a control's only content; without it the icon is decoration.
40
+ - Names are by meaning: the up-right arrow is `External`. Check a name in `dist/index.d.ts` before using it.
41
+ `Link`, `Image`, `File` and `Text` clash with Next: import `LinkIcon`, `ImageIcon`, `FileIcon`, `TextIcon`.
42
+ - The hover motion is the set's own (hover and focus only, never on load, off under reduced motion).
233
43
 
234
- ## 8. Labels, universal icons, conflicts
44
+ ## Without React
235
45
 
236
- - **Labels:** before users click, they need to know what to expect. Universal icons (home,
237
- play, stop, pin, search) are self-evident; anything ambiguous needs a **1 to 3 word label**
238
- using an action word (Buy, Continue, Subscribe). Icons reinforce; labels disambiguate.
239
- - **Universal icons reduce cognitive load.** Lean on the globally-recognized set where it
240
- fits, beyond cultural differences.
241
- - **Conflicting icons** (heart vs star both mean "favorite/save/rate"): don't avoid them,
242
- **consciously pick one** and label its function clearly. Don't use one icon for two
243
- purposes or two icons for one purpose.
244
- - **Cultural context** matters for unique/symbolic icons, so consider the audience.
245
- - **Test** new icons with outsiders for *recognizability* (do they understand it?) and
246
- *memorability* (do they recall it a week later?). Re-design or add a label if not.
46
+ `iconify.json` with the prefix `struq` (`struq:search`, `struq:<name>-draw` for a draw-in), `sprite.svg` with
47
+ `<use href="/sprite.svg#search" />`, or the plain SVG files. Same rules.
247
48
 
248
- ---
49
+ ## The one fallback
249
50
 
250
- **Cheap tell:** Lucide `Sparkles`/`Rocket`/`Zap` in colored circle chips, 3-up, as
251
- feature decoration; an animated icon looping forever in a dashboard; a 500ms+ click
252
- animation that lags the cursor; a tiny decorative icon turned to mud at 16px.
253
- **Instead:** One deliberately chosen icon family, system vs decorative registers kept separate,
254
- ink-colored with color only for state/semantics, animated only where it informs and
255
- within the timing limits, labeled where not universal, `aria` correct, ≥48px targets.
51
+ When the set lacks an icon and drawing it to the set's rules (24 grid, round caps and joins, the site's stroke, base
52
+ and accent as two parts) is not worth it, move the whole site to **Phosphor at `weight="light"`**
53
+ (`@phosphor-icons/react`, or Iconify `ph:`). One family per site, never mixed, never Lucide by reflex.
@@ -1,78 +1,27 @@
1
- # Stack Targets: detect Vite vs Next.js, then adapt
1
+ # Vite and other React stacks
2
2
 
3
- Every other reference file is written for **Next.js App Router** (Struq's default
4
- stack). Most of the doctrine is framework-agnostic, but a handful of primitives are
5
- Next-specific and will break or mislead in a Vite project. **Detect the stack before
6
- writing any wiring code**, then translate the Next-only pieces.
3
+ The blocks are written for Next.js App Router. Only four things tie them to it; everything else (GSAP, Lenis, the
4
+ kit, the CSS, the tokens) is plain React and Tailwind v4. Detect the stack first: `next` in package.json and an `app/`
5
+ folder is Next; `vite.config.*` with `src/main.tsx` is Vite.
7
6
 
8
- ---
7
+ `blocks.mjs` refuses a project without `next`: copy the files by hand instead, keeping the layout of `blocks/` under
8
+ `src/components/struq/` so the relative imports hold (each block's files are listed in `blocks/catalog.json`).
9
9
 
10
- ## 1. Detect first
11
-
12
- | Signal | Stack |
10
+ | Next.js | Vite |
13
11
  |---|---|
14
- | `next.config.{js,ts,mjs}` present, or an `app/` directory with `layout.tsx` | **Next.js App Router**: use the reference files as written |
15
- | `next.config` present but `pages/` not `app/` | **Next Pages Router**: providers wrap in `_app.tsx`; `template.tsx` patterns use route events instead |
16
- | `vite.config.{js,ts}` present | **Vite**: translate per §2 (React assumed; Vue/Svelte share the concepts, different syntax) |
17
- | neither (CRA, Astro island, Webflow, plain Vite-less) | framework-agnostic core only: Lenis/GSAP provider + CSS; skip Next primitives |
18
-
19
- Check `package.json` deps to confirm: `next` vs `vite` + `react-dom`. If a router is
20
- present in Vite, it is almost always `react-router-dom`, and that is what the transition
21
- patterns below assume.
22
-
23
- Then read the installed majors (`vite`, `react`, `tailwindcss`, `gsap`, `lenis`, the router) and record them on the `Stack:` line; the installed version beats this file whenever they disagree ([setup.md](setup.md) §0, Known API shifts). Scaffold an empty folder with `npm create vite@latest`.
24
-
25
- ---
26
-
27
- ## 2. Translation table: Next App Router → Vite
28
-
29
- | Concern | Next App Router | Vite (React SPA) |
30
- |---|---|---|
31
- | **Fonts** | `next/font/google` / `next/font/local` exposing CSS vars | `@fontsource/<face>` (npm) **or** self-hosted `@font-face` in CSS; `<link rel="preload">` the woff2 in `index.html`. Map the same `--font-display-face` / `--font-body-face` vars. (Pairing and weight decisions: [typography.md](typography.md).) |
32
- | **Lazy / SSR-free island** (WebGL) | `next/dynamic(() => import("./X"), { ssr: false })` | `React.lazy(() => import("./X"))` + `<Suspense fallback={poster}>`. A Vite SPA is already client-only, so there is no SSR tree to exclude; the lazy boundary is purely for bundle-splitting. |
33
- | **Client/server boundary** | `"use client"` directive on leaf components | **Drop it.** It is a no-op in Vite; everything is a client component. Remove the directive when porting snippets. |
34
- | **Providers mount point** | `app/layout.tsx` wrapping `{children}` | `src/main.tsx` (or `App.tsx`) wrapping `<App/>`: `<SmoothScrollProvider>`, etc. Same component, different host file. |
35
- | **Per-navigation remount** (page-transition entrance) | `app/template.tsx` (re-mounts each navigation) | a wrapper keyed on `useLocation().pathname`, e.g. `<motion.div key={pathname}>` or an `<AnimatePresence mode="wait">` around the routed outlet. Same effect: fresh mount per route. |
36
- | **Route-change transitions** | `template.tsx` + `PageTransitionProvider` ([route-transitions.md](route-transitions.md)) | `AnimatePresence` driven by the router location; the curtain/overlay logic is identical, only the trigger source changes. |
37
- | **Images** | `next/image` (auto sizing, lazy, AVIF/WebP) | `<img>` with **explicit `width`/`height`** (or aspect-ratio box) + `loading="lazy"` + `decoding="async"`; for responsive sources use `srcset` or `vite-imagetools`. Keep explicit dims for CLS regardless. |
38
- | **Metadata / head** | `metadata` export / `generateMetadata` | `react-helmet-async` or set tags in `index.html`. |
39
- | **Icons** | `@struq/icons` (the default; Phosphor light the fallback) | The same React package and root CSS imports in `src/main.tsx`; non-React or Iconify-based stacks use its `iconify.json` (prefix `struq`) or `sprite.svg` ([iconography.md](iconography.md) §2). |
40
- | **Env / public assets** | `/public`, `process.env.NEXT_PUBLIC_*` | `/public`, `import.meta.env.VITE_*`. |
41
-
42
- ---
43
-
44
- ## 3. What is identical on both (do NOT re-derive)
45
-
46
- These carry over unchanged. Only the *mount file* differs:
47
-
48
- - **The smooth-scroll provider**: Lenis driven by `gsap.ticker` with `autoRaf: false` +
49
- `lagSmoothing(0)` ([setup.md](setup.md) §2). One RAF clock. Identical code; mount it in
50
- `app/layout.tsx` (Next) or `main.tsx` (Vite).
51
- - **The GSAP registry module** (`lib/gsap.ts`), `gsap.defaults`, and **all `useGSAP`
52
- rules**: scope ref, `contextSafe`, `autoAlpha`, animate `from` so the SSR/initial HTML
53
- is the final state ([setup.md](setup.md) §1, §3). The same registry covers the plugins used by [svg-motion.md](svg-motion.md) (DrawSVG, MorphSVG).
54
- - **`gsap.matchMedia` reduced-motion guard** ([setup.md](setup.md) §4).
55
- - **The WebGL island's `frameloop:"never"` + GSAP-ticker bridge** ([webgl-r3f.md](webgl-r3f.md) §2,
56
- scene recipes in [webgl-scenes.md](webgl-scenes.md)). R3F is router-agnostic; the single-clock
57
- wiring is the same. Only the lazy-boundary primitive changes (`next/dynamic` → `React.lazy`, per §2).
58
- - **The scaling knob**: one CSS rule in the global stylesheet ([scaling.md](scaling.md)),
59
- `app/globals.css` vs `src/index.css`.
60
- - **All Iconify usage**, the token/`@theme` system, every ScrollTrigger/scrub/pin
61
- technique (including every recipe in [showcase.md](showcase.md)), and all motion *values* ([feel-layer.md](feel-layer.md)).
62
-
63
- The rule of thumb: **anything that touches the DOM, the GSAP ticker, or CSS is portable;
64
- anything that says `next/...` or `"use client"` needs the §2 translation.**
65
-
66
- ---
67
-
68
- ## 4. Vite gotchas worth flagging
69
-
70
- - **No SSR safety net by default.** Code that reads `window` at module scope is fine in a
71
- Vite SPA (no server render), but keep it in effects anyway for correctness and so the
72
- component stays portable back to Next.
73
- - **`ScrollTrigger.refresh()`** is still needed after route changes and after fonts/images
74
- settle. Wire it to the router's location change (the App Router note in
75
- [setup.md](setup.md) §6 applies, just via `react-router` instead of `template.tsx`).
76
- - **StrictMode double-invoke** in dev re-runs effects twice. `useGSAP` cleanup handles
77
- it, but raw `useEffect` GSAP setup will leak, which is another reason never to write raw
78
- `useEffect` for GSAP ([setup.md](setup.md) §3).
12
+ | `next/image` (`<Image fill sizes priority>`) | `<img>` with `width`, `height`, `loading`, `decoding="async"`, and `object-cover` on a positioned box for `fill`. `getImageProps` (page-transition-stack) becomes the plain `src`. |
13
+ | `next/link` | `<a>`, or your router's `Link`. |
14
+ | `next/navigation` in the page transitions (`usePathname`, `useRouter`, `useSearchParams`) | Your router's location and navigate (React Router `useLocation`, `useNavigate`). The transitions only need the current path and a push. |
15
+ | `"use client"` | Delete it, or leave it: Vite ignores it. |
16
+
17
+ The shell, in Vite terms:
18
+
19
+ - `index.html`: in `<head>`, the gate script `<script>document.documentElement.classList.add('sq-js')</script>` before
20
+ any stylesheet, and one `<link rel="stylesheet">` per URL that `canon.mjs print <palette> <pairing>` prints;
21
+ `<body data-blocks>`.
22
+ - `src/index.css`: `@import "tailwindcss";`, the kit import, the `@theme inline` names and the STRUQ TOKENS block from
23
+ `recipes/app/globals.css` (the block comes from `canon.mjs print`).
24
+ - `src/main.tsx`: wrap the app in `SmoothScroll` from the kit.
25
+
26
+ `check.mjs` reads `app/layout.tsx` for the gate; on Vite it reports that line as missing. Check `index.html` by eye
27
+ and note it under Verify in STRUQ.md.
@@ -0,0 +1,52 @@
1
+ # The tell list
2
+
3
+ One list: Struq's tells, the slop audit and Roy's rules. Each is something a generator does by reflex and a studio
4
+ never does. `check.mjs` detects the marked ones (and `struq_controle` returns the same rules when the MCP is
5
+ connected); the rest are judged by eye. The fix is beside each.
6
+
7
+ ## Type and labels
8
+
9
+ - **Eyebrows:** a small label above a heading ("OUR APPROACH", "PREMIUM SERVICE"), any uppercase tracked label or
10
+ marquee. Fix: the heading carries itself; wayfinding is a hairline, a change of surface step, a jump in scale. A
11
+ caption under or on an image (`<figcaption>` or `data-caption`) is media metadata and allowed. (check)
12
+ - **Numbered steps** (01 / 02 / 03) and section numbers. Fix: hairline rows or a sequence the layout makes obvious. (check)
13
+ - **Mono on marketing:** monospace as a design face. Fix: mono only for literal code. (check)
14
+ - **The generator faces:** Inter, Roboto, Arial, Helvetica, Open Sans, Poppins, Montserrat, Lato, Geist, Space
15
+ Grotesk, and Playfair, Anton, Oswald, Cormorant or Manrope as a display. Fix: a canon pairing. (check)
16
+ - **Heavy display** outside the poster pairings, `font-bold` on headings, raw sizes (`text-5xl`) and breakpoint size
17
+ ladders. Fix: the type roles and `--display-weight`. (check)
18
+
19
+ ## Colour and surface
20
+
21
+ - **Gradients of any kind,** gradient text, glass, tinted translucent fills. Fix: flat tokens; a flat veil over a
22
+ photo is the only translucent layer. (check)
23
+ - **Glow:** coloured shadows, neon edges, blurred light behind a card. Fix: none. (check)
24
+ - **Default purple or blue:** an indigo or violet accent nobody chose. Fix: a canon palette; blue only when the brand
25
+ is blue, declared in STRUQ.md as `Cool hue: declared because <the brand colour>`. (check)
26
+ - **Pills,** above all a pill with a dot; pulsing live dots; pastel status badges. Fix: a text link or a rectangular
27
+ button. (check)
28
+ - **Drop-shadow elevation** and side-stripe borders. Fix: a surface step and a hairline.
29
+
30
+ ## Layout and motion
31
+
32
+ - **Icon grids:** three or six identical cards, each an icon, a heading and two grey lines. Fix: hairline rows,
33
+ `hover-list`, `panel-accordion`, or one statement. (check)
34
+ - **Stat banners** ("20+", "99%", "51+") the brief did not give. Fix: leave them out. (check)
35
+ - Uniform section padding, the same hover on everything, opacity-only entrances, single-layer parallax. Fix: the
36
+ craft rules in [craft.md](craft.md).
37
+ - An empty or placeholder frame, a grey box, "photo to come". Fix: a beeldbank photo. (check)
38
+
39
+ ## Copy and icons
40
+
41
+ - **Em and en dashes** in any form, in copy, comments and alt text. Fix: restructure the sentence: a comma, a colon, a
42
+ full stop. (check)
43
+ - Generic CTAs ("Learn more", "Get started", "Lees meer"), generic headings ("Our Services", "Over ons"), hype words
44
+ ("seamless", "naadloos", "next-level"), lorem, Acme, leftover template text. Fix: specific words from the brief. (check)
45
+ - Emoji and the cliche icons (sparkles, rocket, lightning, bulb, wand). Fix: Struq Icons, functional only. (check)
46
+ - Invented facts: testimonials, client logos, stats, awards, prices, addresses, hours, bios, quotes the brief did not
47
+ give. Fix: leave them out and list them under Assumptions.
48
+
49
+ ## Judged by eye
50
+
51
+ The hero-metric template (big number, small label, three times), category-reflex colours (green for eco, blue for
52
+ finance), a heading the paragraph restates, a page that reads as a SaaS template in a studio's palette.
@@ -37,25 +37,25 @@ change its dress.
37
37
  ## Fair game: fix freely (this is what you were called for)
38
38
 
39
39
  - **Layout / structure / IA**: restructure sections, reorder, break the grid, fix the
40
- symmetric "SaaS stack." Layout is execution, not identity. ([page-architecture.md](page-architecture.md))
40
+ symmetric "SaaS stack." Layout is execution, not identity: swap in blocks where the page is weakest (the hero, the work, the footer first).
41
41
  - **Spacing rhythm**: kill uniform `py-16`, vary padding by section role.
42
42
  - **Type scale + weight**: fix the five-mid-sizes problem, lighten hero weights, enforce
43
- the readable floor and contrast minimums ([anti-patterns.md](anti-patterns.md) §12). If the
43
+ the readable floor and contrast minimums. If the
44
44
  brand already owns its typefaces, keep them and change how they are set, not which they are
45
- ([typography.md](typography.md)).
45
+ (their faces go into --face-display and --face-body).
46
46
  - **Surfaces / elevation**: replace `shadow-lg` cards with the tonal ladder.
47
47
  - **Motion / reveals**: replace opacity-only fades, add transform+stagger, fix easing.
48
48
  - **Scaling**: convert px-everything + per-breakpoint size ladders to the fluid root-rem
49
- system ([scaling.md](scaling.md)).
49
+ system ([craft.md](craft.md)).
50
50
  - **Icon misuse**: remove Sparkles/Rocket/Zap/emoji, colored chips, decorative filler;
51
51
  consolidate to one icon family ([iconography.md](iconography.md)): the brand's own set if it has one, else Struq Icons (`@struq/icons`). A redesign that already uses one coherent non-Lucide family keeps it (preserve by default); one family per site, never mixed.
52
- - **Everything in [anti-patterns.md](anti-patterns.md)**: that gallery is the fix list.
52
+ - **Everything in [tells.md](tells.md)**: that list is the fix list.
53
53
 
54
54
  ---
55
55
 
56
56
  ## Workflow
57
57
 
58
- 1. **Scan** with [anti-patterns.md](anti-patterns.md) and collect every tell.
58
+ 1. **Scan** with check.mjs (and struq_controle when connected) and [tells.md](tells.md); collect every tell.
59
59
  2. **Filter** the findings through the preserve-list above. A "fix" that reassigns brand
60
60
  color, rewrites copy, or replaces a component primitive is dropped (or downgraded to a
61
61
  *suggestion* the user can opt into), not applied.
@@ -68,4 +68,3 @@ change its dress.
68
68
  When unsure whether something is identity or execution, **ask** rather than assume, but
69
69
  default to preserving the three categories above.
70
70
 
71
- If the work started from a brief, [brief-router.md](brief-router.md) still runs, with this file's rules overriding it: identity is preserved and the router only re-decides execution.