@struq/skill 0.3.0 → 0.4.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 +22 -36
- package/package.json +3 -2
- package/skills/struq/SKILL.md +139 -307
- package/skills/struq/blocks/CATALOG.md +426 -0
- package/skills/struq/blocks/backgrounds/background-aurora.tsx +818 -0
- package/skills/struq/blocks/backgrounds/background-facets.tsx +1283 -0
- package/skills/struq/blocks/backgrounds/background-silk.tsx +906 -0
- package/skills/struq/blocks/catalog.json +1302 -0
- package/skills/struq/blocks/footers/footer-contact.tsx +789 -0
- package/skills/struq/blocks/footers/footer-reveal.tsx +313 -0
- package/skills/struq/blocks/headers/header-bar.tsx +652 -0
- package/skills/struq/blocks/headers/menu-curtain.tsx +790 -0
- package/skills/struq/blocks/heroes/hero-framed.tsx +627 -0
- package/skills/struq/blocks/heroes/hero-index.tsx +855 -0
- package/skills/struq/blocks/heroes/hero-poster.tsx +1086 -0
- package/skills/struq/blocks/heroes/hero-split.tsx +685 -0
- package/skills/struq/blocks/heroes/hero-still.tsx +862 -0
- package/skills/struq/{recipes/components/struq → blocks/kit}/Cursor.tsx +1 -1
- package/skills/struq/{recipes/components/struq → blocks/kit}/HeroCanvas.tsx +2 -2
- package/skills/struq/{recipes/components/struq → blocks/kit}/HorizontalGallery.tsx +2 -2
- package/skills/struq/{recipes/components/struq → blocks/kit}/MagneticButton.tsx +1 -1
- package/skills/struq/{recipes/components/struq → blocks/kit}/MediaFrame.tsx +2 -2
- package/skills/struq/{recipes/components/struq → blocks/kit}/PinnedZoom.tsx +14 -4
- package/skills/struq/{recipes/components/struq → blocks/kit}/Reveal.tsx +1 -1
- package/skills/struq/{recipes/components/struq → blocks/kit}/SmoothScroll.tsx +1 -1
- package/skills/struq/{recipes/components/struq → blocks/kit}/SplitHeadline.tsx +1 -1
- package/skills/struq/blocks/kit/blocks.css +2102 -0
- package/skills/struq/blocks/kit/images.ts +17 -0
- package/skills/struq/blocks/kit/photo-read.ts +102 -0
- package/skills/struq/blocks/page-transitions/page-transition-gate.tsx +706 -0
- package/skills/struq/blocks/page-transitions/page-transition-slices.tsx +469 -0
- package/skills/struq/blocks/page-transitions/page-transition-stack.tsx +583 -0
- package/skills/struq/blocks/preloaders/preloader-gate.tsx +358 -0
- package/skills/struq/blocks/preloaders/preloader-slices-uncovered.ts +50 -0
- package/skills/struq/blocks/preloaders/preloader-slices.tsx +289 -0
- package/skills/struq/blocks/preloaders/preloader-stack.tsx +367 -0
- package/skills/struq/blocks/preloaders/preloader-wordmark.tsx +568 -0
- package/skills/struq/blocks/sections/about-studio.tsx +436 -0
- package/skills/struq/blocks/sections/approach.tsx +661 -0
- package/skills/struq/blocks/sections/contact-form.tsx +664 -0
- package/skills/struq/blocks/sections/cta-closing.tsx +763 -0
- package/skills/struq/blocks/sections/fanned-cards.tsx +547 -0
- package/skills/struq/blocks/sections/film-strip.tsx +385 -0
- package/skills/struq/blocks/sections/grid-assembly.tsx +616 -0
- package/skills/struq/blocks/sections/hover-list.tsx +467 -0
- package/skills/struq/blocks/sections/image-trail.tsx +432 -0
- package/skills/struq/blocks/sections/manifesto.tsx +691 -0
- package/skills/struq/blocks/sections/panel-accordion.tsx +380 -0
- package/skills/struq/blocks/sections/reveals.tsx +416 -0
- package/skills/struq/blocks/sections/split-pair.tsx +670 -0
- package/skills/struq/blocks/sections/sticky-chapters.tsx +256 -0
- package/skills/struq/blocks/sections/velocity-type.tsx +269 -0
- package/skills/struq/{cinematic/showpieces/toonstuk-fill-rows.tsx → blocks/showpieces/fill-rows.tsx} +1 -1
- package/skills/struq/{cinematic/showpieces/toonstuk-hero-opener.tsx → blocks/showpieces/hero-opener.tsx} +5 -5
- package/skills/struq/{cinematic/showpieces/toonstuk-image-to-fullbleed.tsx → blocks/showpieces/image-to-fullbleed.tsx} +16 -5
- package/skills/struq/blocks/showpieces/manifesto-scrub.tsx +48 -0
- package/skills/struq/{cinematic/showpieces/toonstuk-panel-wipe.tsx → blocks/showpieces/panel-wipe.tsx} +2 -2
- package/skills/struq/{cinematic/showpieces/toonstuk-pixel-resolve.tsx → blocks/showpieces/pixel-resolve.tsx} +1 -1
- package/skills/struq/{cinematic/showpieces/toonstuk-project-stack.tsx → blocks/showpieces/project-stack.tsx} +2 -2
- package/skills/struq/blocks/showpieces/scrub-connector.tsx +307 -0
- package/skills/struq/{cinematic/showpieces/toonstuk-wordmark-finale.tsx → blocks/showpieces/wordmark-finale.tsx} +1 -1
- package/skills/struq/blocks/transitions/zoom-through.tsx +613 -0
- package/skills/struq/canon/canon.json +627 -0
- package/skills/struq/cinematic/README.md +7 -18
- package/skills/struq/cinematic/examples/code/nextjs/app/components/LenisRoot.tsx +1 -1
- package/skills/struq/cinematic/examples/code/shared/styles.css +1 -1
- package/skills/struq/cinematic/examples/code/vite/src/LenisRoot.tsx +1 -1
- package/skills/struq/cinematic/references/01-core-mental-model.md +1 -1
- package/skills/struq/cinematic/references/02-vocabulary.md +1 -1
- package/skills/struq/cinematic/references/07-stack-integration.md +2 -2
- package/skills/struq/recipes/app/fonts.ts +12 -22
- package/skills/struq/recipes/app/globals.css +34 -177
- package/skills/struq/recipes/app/layout.tsx +13 -11
- package/skills/struq/references/craft.md +77 -0
- package/skills/struq/references/iconography.md +28 -230
- package/skills/struq/references/stack-targets.md +23 -74
- package/skills/struq/references/tells.md +52 -0
- package/skills/struq/references/triage.md +6 -7
- package/skills/struq/scripts/blocks.mjs +156 -0
- package/skills/struq/scripts/canon.mjs +106 -281
- package/skills/struq/scripts/check.mjs +92 -126
- package/skills/struq/scripts/images.mjs +2 -2
- package/skills/struq/scripts/log.mjs +6 -23
- package/skills/struq/scripts/tokens.mjs +222 -0
- package/skills/struq/cinematic/showpieces/README.md +0 -18
- package/skills/struq/cinematic/showpieces/toonstuk-manifesto-scrub.tsx +0 -81
- package/skills/struq/cinematic/showpieces/toonstuk-scrub-connector.tsx +0 -78
- package/skills/struq/references/anti-patterns.md +0 -330
- package/skills/struq/references/baseline-prompt.md +0 -122
- package/skills/struq/references/brand.md +0 -143
- package/skills/struq/references/brief-router.md +0 -218
- package/skills/struq/references/composition.md +0 -190
- package/skills/struq/references/depth-parallax.md +0 -235
- package/skills/struq/references/direction.md +0 -188
- package/skills/struq/references/feel-layer.md +0 -107
- package/skills/struq/references/gsap-api.md +0 -293
- package/skills/struq/references/hero-scenes.md +0 -107
- package/skills/struq/references/imagery.md +0 -125
- package/skills/struq/references/loaders.md +0 -287
- package/skills/struq/references/micro-interactions.md +0 -270
- package/skills/struq/references/mobile-composition.md +0 -70
- package/skills/struq/references/nav-header.md +0 -296
- package/skills/struq/references/niche-packets.md +0 -338
- package/skills/struq/references/page-architecture.md +0 -134
- package/skills/struq/references/page-entrances.md +0 -198
- package/skills/struq/references/performance.md +0 -235
- package/skills/struq/references/pinning-scrub.md +0 -340
- package/skills/struq/references/pitfalls.md +0 -290
- package/skills/struq/references/route-transitions.md +0 -281
- package/skills/struq/references/scaling.md +0 -151
- package/skills/struq/references/scroll-reveals.md +0 -279
- package/skills/struq/references/sections.md +0 -141
- package/skills/struq/references/seo.md +0 -219
- package/skills/struq/references/setup.md +0 -397
- package/skills/struq/references/showcase.md +0 -87
- package/skills/struq/references/struq-imagery.md +0 -107
- package/skills/struq/references/svg-motion.md +0 -390
- package/skills/struq/references/transitions.md +0 -160
- package/skills/struq/references/typography.md +0 -720
- package/skills/struq/references/vibe-presets.md +0 -287
- package/skills/struq/references/webgl-r3f.md +0 -274
- package/skills/struq/references/webgl-scenes.md +0 -593
- package/skills/struq/scripts/font-names.mjs +0 -40
- package/skills/struq/scripts/recipes.mjs +0 -135
- /package/skills/struq/{recipes/lib → blocks/kit}/gsap.ts +0 -0
- /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/contours.ts +0 -0
- /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/dots.ts +0 -0
- /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/ripple.ts +0 -0
- /package/skills/struq/{recipes/components/struq → blocks/kit}/scenes/types.ts +0 -0
|
@@ -1,255 +1,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Icons
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
7
|
+
## Whether an icon earns its place
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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"; //
|
|
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
|
|
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(--
|
|
84
|
-
--icon-accent-live: var(--
|
|
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="/
|
|
92
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
##
|
|
44
|
+
## Without React
|
|
235
45
|
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
#
|
|
1
|
+
# Vite and other React stacks
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
| Signal | Stack |
|
|
10
|
+
| Next.js | Vite |
|
|
13
11
|
|---|---|
|
|
14
|
-
| `next
|
|
15
|
-
| `next
|
|
16
|
-
| `
|
|
17
|
-
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
|
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
|
-
(
|
|
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 ([
|
|
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 [
|
|
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 [
|
|
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.
|