@struq/skill 0.2.2 → 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.
Files changed (122) hide show
  1. package/README.md +23 -35
  2. package/package.json +3 -2
  3. package/skills/struq/SKILL.md +148 -212
  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/blocks/kit/Cursor.tsx +117 -0
  19. package/skills/struq/blocks/kit/HeroCanvas.tsx +278 -0
  20. package/skills/struq/blocks/kit/HorizontalGallery.tsx +106 -0
  21. package/skills/struq/blocks/kit/MagneticButton.tsx +92 -0
  22. package/skills/struq/blocks/kit/MediaFrame.tsx +96 -0
  23. package/skills/struq/blocks/kit/PinnedZoom.tsx +89 -0
  24. package/skills/struq/blocks/kit/Reveal.tsx +64 -0
  25. package/skills/struq/blocks/kit/SmoothScroll.tsx +57 -0
  26. package/skills/struq/blocks/kit/SplitHeadline.tsx +68 -0
  27. package/skills/struq/blocks/kit/blocks.css +2102 -0
  28. package/skills/struq/blocks/kit/gsap.ts +21 -0
  29. package/skills/struq/blocks/kit/images.ts +17 -0
  30. package/skills/struq/blocks/kit/photo-read.ts +102 -0
  31. package/skills/struq/blocks/kit/scenes/contours.ts +161 -0
  32. package/skills/struq/blocks/kit/scenes/dots.ts +110 -0
  33. package/skills/struq/blocks/kit/scenes/ripple.ts +99 -0
  34. package/skills/struq/blocks/kit/scenes/types.ts +114 -0
  35. package/skills/struq/blocks/page-transitions/page-transition-gate.tsx +706 -0
  36. package/skills/struq/blocks/page-transitions/page-transition-slices.tsx +469 -0
  37. package/skills/struq/blocks/page-transitions/page-transition-stack.tsx +583 -0
  38. package/skills/struq/blocks/preloaders/preloader-gate.tsx +358 -0
  39. package/skills/struq/blocks/preloaders/preloader-slices-uncovered.ts +50 -0
  40. package/skills/struq/blocks/preloaders/preloader-slices.tsx +289 -0
  41. package/skills/struq/blocks/preloaders/preloader-stack.tsx +367 -0
  42. package/skills/struq/blocks/preloaders/preloader-wordmark.tsx +568 -0
  43. package/skills/struq/blocks/sections/about-studio.tsx +436 -0
  44. package/skills/struq/blocks/sections/approach.tsx +661 -0
  45. package/skills/struq/blocks/sections/contact-form.tsx +664 -0
  46. package/skills/struq/blocks/sections/cta-closing.tsx +763 -0
  47. package/skills/struq/blocks/sections/fanned-cards.tsx +547 -0
  48. package/skills/struq/blocks/sections/film-strip.tsx +385 -0
  49. package/skills/struq/blocks/sections/grid-assembly.tsx +616 -0
  50. package/skills/struq/blocks/sections/hover-list.tsx +467 -0
  51. package/skills/struq/blocks/sections/image-trail.tsx +432 -0
  52. package/skills/struq/blocks/sections/manifesto.tsx +691 -0
  53. package/skills/struq/blocks/sections/panel-accordion.tsx +380 -0
  54. package/skills/struq/blocks/sections/reveals.tsx +416 -0
  55. package/skills/struq/blocks/sections/split-pair.tsx +670 -0
  56. package/skills/struq/blocks/sections/sticky-chapters.tsx +256 -0
  57. package/skills/struq/blocks/sections/velocity-type.tsx +269 -0
  58. package/skills/struq/blocks/showpieces/fill-rows.tsx +74 -0
  59. package/skills/struq/blocks/showpieces/hero-opener.tsx +128 -0
  60. package/skills/struq/blocks/showpieces/image-to-fullbleed.tsx +89 -0
  61. package/skills/struq/blocks/showpieces/manifesto-scrub.tsx +48 -0
  62. package/skills/struq/blocks/showpieces/panel-wipe.tsx +89 -0
  63. package/skills/struq/blocks/showpieces/pixel-resolve.tsx +107 -0
  64. package/skills/struq/blocks/showpieces/project-stack.tsx +86 -0
  65. package/skills/struq/blocks/showpieces/scrub-connector.tsx +307 -0
  66. package/skills/struq/blocks/showpieces/wordmark-finale.tsx +105 -0
  67. package/skills/struq/blocks/transitions/zoom-through.tsx +613 -0
  68. package/skills/struq/canon/canon.json +627 -0
  69. package/skills/struq/cinematic/README.md +7 -18
  70. package/skills/struq/cinematic/examples/code/nextjs/app/components/LenisRoot.tsx +1 -1
  71. package/skills/struq/cinematic/examples/code/shared/styles.css +1 -1
  72. package/skills/struq/cinematic/examples/code/vite/src/LenisRoot.tsx +1 -1
  73. package/skills/struq/cinematic/references/01-core-mental-model.md +1 -1
  74. package/skills/struq/cinematic/references/02-vocabulary.md +1 -1
  75. package/skills/struq/cinematic/references/07-stack-integration.md +2 -2
  76. package/skills/struq/recipes/app/fonts.ts +12 -0
  77. package/skills/struq/recipes/app/globals.css +42 -0
  78. package/skills/struq/recipes/app/layout.tsx +30 -0
  79. package/skills/struq/references/craft.md +77 -0
  80. package/skills/struq/references/iconography.md +28 -230
  81. package/skills/struq/references/stack-targets.md +23 -74
  82. package/skills/struq/references/tells.md +52 -0
  83. package/skills/struq/references/triage.md +6 -7
  84. package/skills/struq/scripts/blocks.mjs +156 -0
  85. package/skills/struq/scripts/canon.mjs +134 -0
  86. package/skills/struq/scripts/check.mjs +513 -0
  87. package/skills/struq/scripts/images.mjs +360 -0
  88. package/skills/struq/scripts/log.mjs +75 -0
  89. package/skills/struq/scripts/packs.mjs +156 -0
  90. package/skills/struq/scripts/tokens.mjs +222 -0
  91. package/skills/struq/references/anti-patterns.md +0 -265
  92. package/skills/struq/references/baseline-prompt.md +0 -118
  93. package/skills/struq/references/brand.md +0 -143
  94. package/skills/struq/references/brief-router.md +0 -219
  95. package/skills/struq/references/composition.md +0 -191
  96. package/skills/struq/references/depth-parallax.md +0 -235
  97. package/skills/struq/references/direction.md +0 -188
  98. package/skills/struq/references/feel-layer.md +0 -107
  99. package/skills/struq/references/gsap-api.md +0 -293
  100. package/skills/struq/references/imagery.md +0 -144
  101. package/skills/struq/references/loaders.md +0 -287
  102. package/skills/struq/references/micro-interactions.md +0 -270
  103. package/skills/struq/references/mobile-composition.md +0 -70
  104. package/skills/struq/references/nav-header.md +0 -296
  105. package/skills/struq/references/page-architecture.md +0 -134
  106. package/skills/struq/references/page-entrances.md +0 -198
  107. package/skills/struq/references/performance.md +0 -235
  108. package/skills/struq/references/pinning-scrub.md +0 -341
  109. package/skills/struq/references/pitfalls.md +0 -290
  110. package/skills/struq/references/route-transitions.md +0 -281
  111. package/skills/struq/references/scaling.md +0 -151
  112. package/skills/struq/references/scroll-reveals.md +0 -280
  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
@@ -1,235 +0,0 @@
1
- # Layered Depth & Parallax
2
-
3
- Parallax has a deserved bad reputation because the cheap version (one background image crawling at 0.5×) was strip-mined by 2014 WordPress themes. The agency version is about **relative rates**: multiple surfaces moving at *different* speeds, which is how human vision infers depth. One moving layer is a gimmick; three rates is a world. For named, ready-made showpiece recipes with ids, see [showcase.md](showcase.md).
4
-
5
- Rules of thumb before the techniques:
6
- - Speed deltas are small: ±10 to 20% of scroll rate. ±50% reads as broken, not deep.
7
- - Foreground moves *faster* than scroll, background *slower*. Match physical optics or it feels wrong without the viewer knowing why.
8
- - Everything scrub-tied (`ease: "none"`), transforms only.
9
- - Disable under `prefers-reduced-motion` (parallax is the #1 vestibular-trigger complaint).
10
-
11
- ---
12
-
13
- ## 1. Multi-speed layers (`data-speed`)
14
-
15
- **Term:** Rate-layered section / multiplane.
16
- **Mechanic:** Siblings in one section carry a `data-speed` attribute; a single loop gives each a scrubbed y-drift proportional to `(speed - 1)`. Speed 1 = normal flow, 0.85 = lags (far), 1.15 = leads (near).
17
-
18
- **When:** Heroes, full-bleed image sections, any composition from [composition.md](composition.md) with overlapping layers: depth makes overlap legible.
19
- **Overkill when:** dense text/UI sections. Moving reading material is hostile.
20
- **Cost:** Trivial (transforms). Just don't exceed ~6 layers per viewport.
21
-
22
- ```tsx
23
- "use client";
24
-
25
- import { useRef } from "react";
26
- import { gsap, useGSAP } from "@/lib/gsap";
27
-
28
- export function ParallaxSection({ children }: { children: React.ReactNode }) {
29
- const container = useRef<HTMLElement>(null);
30
-
31
- useGSAP(
32
- () => {
33
- gsap.utils.toArray<HTMLElement>("[data-speed]").forEach((el) => {
34
- const speed = parseFloat(el.dataset.speed ?? "1");
35
- gsap.to(el, {
36
- y: () => (1 - speed) * 0.4 * window.innerHeight, // drift over one viewport of travel
37
- ease: "none",
38
- scrollTrigger: {
39
- trigger: container.current,
40
- start: "top bottom", // animate across the section's entire visibility
41
- end: "bottom top",
42
- scrub: true,
43
- invalidateOnRefresh: true,
44
- },
45
- });
46
- });
47
- },
48
- { scope: container }
49
- );
50
-
51
- return <section ref={container} className="relative">{children}</section>;
52
- }
53
-
54
- // Usage: three rates = depth:
55
- // <ParallaxSection>
56
- // <span data-speed="0.9" className="...ghost numeral, far"/>
57
- // <img data-speed="1" className="...main media, normal"/>
58
- // <div data-speed="1.12" className="...caption card overlapping the image, near"/>
59
- // </ParallaxSection>
60
- ```
61
-
62
- `start: "top bottom", end: "bottom top"` makes the drift symmetric around the section's centered position. Elements are at their natural layout spot when the section is mid-viewport. That's why this doesn't break layout the way naive offset parallax does.
63
-
64
- **Cheap tell:** one `background-attachment: fixed` image (also broken on iOS), or a single layer at 0.5×. **Instead:** Three-plus siblings at 0.9 / 1.0 / 1.12, symmetric drift.
65
-
66
- ---
67
-
68
- ## 2. Image parallax inside a mask
69
-
70
- **Term:** Window parallax / internal pan.
71
- **Mechanic:** Container is the "window" (`overflow-hidden`, fixed aspect); image inside is oversized via `scale(1.15)` and pans vertically as the window crosses the viewport. The frame holds still while the photo moves behind it, the signature texture of editorial agency sites.
72
-
73
- **When:** Every significant content image. This is a default treatment, not a feature.
74
- **Cost:** Cheap. The scale provides the pan headroom. Never load a larger asset for this. ~15% scale on a properly-sized responsive image is invisible quality-wise.
75
-
76
- ```tsx
77
- "use client";
78
-
79
- import { useRef } from "react";
80
- import { gsap, useGSAP } from "@/lib/gsap";
81
-
82
- export function ParallaxImage({ src, alt, ratio = "4/3" }: { src: string; alt: string; ratio?: string }) {
83
- const frame = useRef<HTMLDivElement>(null);
84
-
85
- useGSAP(
86
- () => {
87
- gsap.fromTo(
88
- frame.current!.querySelector("img"),
89
- { yPercent: -8 },
90
- {
91
- yPercent: 8,
92
- ease: "none",
93
- scrollTrigger: { trigger: frame.current, start: "top bottom", end: "bottom top", scrub: true },
94
- }
95
- );
96
- },
97
- { scope: frame }
98
- );
99
-
100
- return (
101
- <div ref={frame} className="overflow-hidden" style={{ aspectRatio: ratio }}>
102
- <img src={src} alt={alt} className="h-full w-full scale-[1.18] object-cover" />
103
- </div>
104
- );
105
- }
106
- ```
107
-
108
- Arithmetic that must hold: pan range (-8% → +8% = 16%) < scale headroom (18%). If edges ever show, scale is too low for the range. Combine freely with the masked-entrance reveal from [scroll-reveals.md](scroll-reveals.md): the entrance plays once and the pan scrubs forever; they don't conflict because the entrance animates the same `yPercent` through GSAP's tween overwrite.
109
-
110
- **Cheap tell:** flat `<img>` in a `rounded-xl shadow-lg` card. **Instead:** Still frame, breathing photo.
111
-
112
- ---
113
-
114
- ## 3. Sticky-stacking cards
115
-
116
- **Term:** Stacking deck / sticky pile.
117
- **Mechanic:** Consecutive full-ish-height cards each get `position: sticky; top: 0`, so each pins while the next slides over it. CSS does 90% of the work; the agency layer is the scrub that *recedes* the covered card: scaling it down and dimming it so it visibly drops into the background instead of just being papered over.
118
-
119
- **When:** Sequential content with weight: case studies, principles, chapters. 3 to 5 cards.
120
- **Overkill when:** >6 cards (the gesture wears out), or content users need to compare side-by-side.
121
- **Cost:** Sticky is free. The recede uses `scale` + an opacity veil (cheap). Avoid scrubbed `filter: brightness()`/`blur()` on large cards; that's the expensive variant. If you want blur-recede, budget it as your one filter effect per page ([setup.md](setup.md) §5).
122
-
123
- ```tsx
124
- "use client";
125
-
126
- import { useRef } from "react";
127
- import { gsap, useGSAP } from "@/lib/gsap";
128
-
129
- const SURFACES = ["bg-surface-1", "bg-surface-2", "bg-surface-1", "bg-surface-2"];
130
-
131
- export function StackingCards({ items }: { items: { kicker: string; title: string; body: string }[] }) {
132
- const container = useRef<HTMLDivElement>(null);
133
-
134
- useGSAP(
135
- () => {
136
- const mm = gsap.matchMedia();
137
- // The recede is decoration: reduced motion keeps the plain CSS sticky stack.
138
- mm.add("(prefers-reduced-motion: no-preference)", () => {
139
- gsap.utils.toArray<HTMLElement>("[data-card]").forEach((card, i, all) => {
140
- if (i === all.length - 1) return; // last card never gets covered
141
- gsap.to(card, {
142
- scale: 0.92,
143
- yPercent: -4,
144
- ease: "none",
145
- scrollTrigger: {
146
- trigger: all[i + 1], // driven by the NEXT card's approach
147
- start: "top bottom",
148
- end: "top top",
149
- scrub: true,
150
- },
151
- });
152
- gsap.fromTo(card.querySelector("[data-veil]"), { autoAlpha: 0 }, {
153
- autoAlpha: 0.5,
154
- ease: "none",
155
- scrollTrigger: { trigger: all[i + 1], start: "top bottom", end: "top top", scrub: true },
156
- });
157
- });
158
- });
159
- },
160
- { scope: container }
161
- );
162
-
163
- return (
164
- <div ref={container}>
165
- {items.map((item, i) => (
166
- // One screen tall from md up (svh, so mobile browser chrome never resizes it);
167
- // content height below md, per mobile-composition.md §1.
168
- <section key={item.title} data-card
169
- className={`sticky top-0 flex flex-col justify-between gap-16 overflow-hidden
170
- px-8 py-12 md:min-h-[100svh] ${SURFACES[i % SURFACES.length]}`}>
171
- <span className="eyebrow text-eyebrow text-ink-muted">{item.kicker}</span>
172
- <div className="max-w-3xl">
173
- <h3 className="font-display text-h2">{item.title}</h3>
174
- <p className="text-lead mt-6 max-w-[48ch] text-ink-muted">{item.body}</p>
175
- </div>
176
- <div data-veil className="pointer-events-none absolute inset-0 bg-surface-0 opacity-0" />
177
- </section>
178
- ))}
179
- </div>
180
- );
181
- }
182
- ```
183
-
184
- Load-bearing details:
185
- - **Trigger is the *next* card**: each recede is driven by its coverer's approach, so timing is exact regardless of card heights.
186
- - The `data-veil` div dims with surface color (not black) so the receding card recedes *into the page's own depth*, and it's opacity, which is cheap where `brightness()` isn't.
187
- - Alternating `bg-surface-1/2` makes each new edge visible the moment it enters.
188
- - `transform-origin` defaults to center, which is correct here: the card shrinks toward the page's vanishing point.
189
-
190
- **Cheap tell:** bare `sticky top-0` cards papering over each other with no recede; covered cards look frozen/glitched rather than layered. **Instead:** Covered cards scale to 0.92 and dim under a surface veil, scrubbed to the coverer's approach.
191
-
192
- ---
193
-
194
- ## 4. Scrub-drift marquee (ghost type)
195
-
196
- **Term:** Scroll-rate marquee.
197
- **Mechanic:** The off-canvas ghost type from [composition.md](composition.md) §4, driven sideways by scroll position (not a CSS loop). Direction can alternate per instance; speed is the parallax delta again, applied on x.
198
-
199
- ```tsx
200
- useGSAP(() => {
201
- const mm = gsap.matchMedia();
202
- mm.add("(prefers-reduced-motion: no-preference)", () => {
203
- gsap.to("[data-marquee]", {
204
- xPercent: -18,
205
- ease: "none",
206
- scrollTrigger: { trigger: container.current, start: "top bottom", end: "bottom top", scrub: true },
207
- });
208
- });
209
- }, { scope: container });
210
-
211
- // <div aria-hidden className="overflow-clip">
212
- // <p data-marquee className="font-display text-ghost whitespace-nowrap font-light text-ink/[0.05]">
213
- // Studio Nord · Studio Nord · Studio Nord ·
214
- // </p>
215
- // </div>
216
- ```
217
-
218
- The type is muted solid: `text-ghost` in the display face at its lightest loaded weight, `text-ink/[0.05]` (0.04 to 0.06), the phrase repeated three times ([typography.md](typography.md) §5, Ghost marquee). Never outlined (`-webkit-text-stroke` with a transparent fill): that is a dated template move.
219
-
220
- Scroll-tied drift beats an autonomous CSS `@keyframes` marquee for section texture: it's still when the user is still (calmer, and free under reduced motion if you gate it). Use the looping CSS marquee only for genuinely continuous content like logo walls.
221
-
222
- ---
223
-
224
- ## Depth recipe (composing the file)
225
-
226
- For a hero or any showpiece section, stack the techniques as planes:
227
-
228
- | Plane | Element | Treatment |
229
- |---|---|---|
230
- | -2 | ghost numeral / marquee type | `data-speed 0.9` or x-drift, `text-ghost text-ink/[0.05]`, muted solid |
231
- | -1 | full-bleed media | window parallax (§2) |
232
- | 0 | headline, body | normal flow, text never parallaxes |
233
- | +1 | caption card, badge overlapping media | `data-speed 1.1`, `z-20` |
234
-
235
- Text at plane 0 always. The moment copy drifts at a different rate than the reading eye expects, the whole effect flips from depth to seasickness.
@@ -1,188 +0,0 @@
1
- # Direction: plan everything before the first component
2
-
3
- Award-tier sites are decided before they are built. The logo, the one idea the site is about, the two typefaces, the palette, the icon family, the arc and the showpieces are made once, together, so every later choice can be checked against them. A build that discovers its direction while coding ends up with a Tailwind default and a nice hero.
4
-
5
- This file is the orchestrator for **Build**, **Redesign** and the planning half of **Directed**. It runs seven phases, numbered 0 to 6, with one gate between planning and building. It writes one contract file, `STRUQ.md` in the project root, and that file is the only plan artifact: there is no second plan block, no separate brief document, no parallel folder for a normal build.
6
-
7
- ## The phases
8
-
9
- | # | Phase | Output (a section of STRUQ.md) | Procedure | Skip when |
10
- |---|---|---|---|---|
11
- | 0 | **Intake** | header (Mode, Stack), MATERIALS | this file §1, with [brief-router.md](brief-router.md) §1 and §1b | never |
12
- | 1 | **Brand** | BRAND | [brand.md](brand.md) | a brand exists (extract it, §2) |
13
- | 2 | **Concept** | CONCEPT | this file §3; the tension from [brief-router.md](brief-router.md) §1 | never |
14
- | 3 | **System** | SYSTEM | this file §4 (sole owner of type, palette, icons, imagery, motion) | never |
15
- | 4 | **Site plan** | PLAN | [brief-router.md](brief-router.md) §2 | never |
16
- | Gate | **Veto** | one message: summary, materials request, "Veto anything?" | this file §5 | the user said "just build it", or the run is non-interactive |
17
- | 5 | **Build** | the site | [../SKILL.md](../SKILL.md) workflow steps 2 to 7 | |
18
- | 6 | **Verify** | VERIFY | [../SKILL.md](../SKILL.md) workflow step 8 | never |
19
-
20
- Phases 0 to 4 together are workflow step 1, **Direction**, in [../SKILL.md](../SKILL.md). Directed mode (`/struq direct`) runs phases 0 to 3 here, then hands over to [../cinematic/README.md](../cinematic/README.md), whose lean outputs are appended to the same STRUQ.md as a DIRECTION section.
21
-
22
- **Scale the ceremony to the job.** A one-page site for an existing brand runs phases 0, 2, 3 and 4 in a few minutes. A new company with no name runs all of them. Never ask a question the brief already answers; never skip a phase because it feels obvious. Write the obvious answer down, because the later phases read it.
23
-
24
- ## 1. Intake
25
-
26
- **One stop, not two.** With a usable brief, do not block here. Run the extraction in [brief-router.md](brief-router.md) §1 silently, record what exists under MATERIALS, record what is missing (and how each gap is handled) under MATERIALS and ASSUMPTIONS, and carry on to the gate. The request for materials goes inside the single gate message (§5). Block early only when the wizard trigger conditions in [brief-router.md](brief-router.md) §1b hold: no usable brief, a request to be guided, or no answer to "who buys" and "what are they afraid of".
27
-
28
- What MATERIALS tracks:
29
-
30
- - Name and logo (files, not descriptions)
31
- - Brand colors and fonts, if any are fixed
32
- - Real copy, or at least the facts: what it is, who it is for, what it costs, where it is
33
- - Photos, product shots, video, 3D models
34
- - Real numbers (clients, years, results) and real quotes, with permission
35
- - An existing site (switch to Redesign: [triage.md](triage.md))
36
- - One or two sites whose feel they envy
37
-
38
- **Never invent proof.** Testimonials, client logos, stats and awards that do not exist are left out, not faked.
39
-
40
- **Never write in a real person's or brand's voice.** On a site named after a real person or a real business:
41
-
42
- - No first person attributed to them ("I make those decisions...") and no quotes in their name. Write in the studio's or the site's third-person voice.
43
- - No biography, training, process or sourcing stated as fact ("trained as a production thrower", "eats at the restaurant before drawing", "clay dug in Alentejo") unless the brief says it.
44
- - No catalogue facts: invented works, sizes, glazes, materials, prices, editions, sold-out states, stockists or client names.
45
-
46
- When the layout needs that copy to exist, draft it as a labelled placeholder and list every drafted claim under ASSUMPTIONS ("Studio paragraph: drafted, replace with Ana's words"; "Work index: 8 entries drafted as layout copy, no prices, no client names"). A portfolio with no work list gets its entries drafted the same way, titled as layout copy, never as the record. The gate message names the drafts so the client can replace them.
47
-
48
- **No real imagery yet.** Plan for it instead of papering over it:
49
-
50
- - Prefer type-led showpieces (`manifesto-fill`, `scrub-headline`, `ghost-marquee`) over media-led ones; a horizontal gallery of placeholders is worse than a static grid ([showcase.md](showcase.md) Choosing).
51
- - Every missing image becomes a shot-list line (subject, grade, ratio) under PLAN `Assets to produce`, so it can be shot, sourced or generated later ([imagery.md](imagery.md) §4).
52
- - In the build, a missing image renders as a placeholder frame per [imagery.md](imagery.md) §4b: a graded tonal frame at the declared ratio, with the shot-list caption set in the eyebrow style. Never grey boxes, never random stock.
53
-
54
- ## 2. Brand
55
-
56
- If there is no name or no logo, run [brand.md](brand.md). It produces a name shortlist with one recommendation, a wordmark and a mark as SVG, a favicon set, and one voice line; the OG image is built later, once routes exist ([seo.md](seo.md) §3). If a brand exists, extract it instead: logo file, the exact colors, the fonts, how they write. Existing brand assets always beat the preset; the preset still supplies the token structure they pour into.
57
-
58
- | What exists | Run |
59
- |---|---|
60
- | Nothing | [brand.md](brand.md) in full |
61
- | A name, no logo, no brand | [brand.md](brand.md) §1 and §3 to §5: skip naming |
62
- | **A named brand (colours, a font, a voice) but no logo file** | Skip [brand.md](brand.md) §1 and §2: never rename, never reposition. Draw a **provisional** wordmark from the brand's own face ([brand.md](brand.md) §3), write it to the same paths (`brand/logo.svg`, `brand/mark.svg`) so the real logo drops in, and record it under ASSUMPTIONS: "Logo: provisional wordmark from <face>, replace with the brand file" |
63
- | A full brand with logo files | Extract only; no [brand.md](brand.md) |
64
-
65
- **Person-named sites skip naming.** Portfolios, studios named after a person, practices and clinics use the person's name. If the name is unknown, write a placeholder, record it under ASSUMPTIONS, skip [brand.md](brand.md) §2 and go straight to the wordmark (§3 there).
66
-
67
- ## 3. Concept
68
-
69
- The concept is what makes the site feel authored instead of assembled. Write three lines:
70
-
71
- - **Idea:** one sentence the whole site is about, specific to this client. Not "modern and clean"; "every page is a quiet room with one object in it" or "the site moves like a slow pan across a construction drawing".
72
- - **Tension:** what the visitor needs to feel (convince) and what the site must never signal (never). From [brief-router.md](brief-router.md) §1 question 2.
73
- - **Motif:** the one recurring visual device that carries the idea through the page. A line that draws through every section, a frame that every image sits in, a numeral system, a crop direction, a mark that morphs. One motif, used everywhere, beats five effects used once.
74
-
75
- Every later decision gets checked against these three lines. If a showpiece or effect does not serve the idea or the motif, it goes.
76
-
77
- ## 4. System
78
-
79
- This section is the only place SYSTEM is decided. Other files point here.
80
-
81
- **Read budget.** During planning, read only the Decide-here section at the top of [typography.md](typography.md) and of [iconography.md](iconography.md), the token contract and the one chosen preset in [vibe-presets.md](vibe-presets.md), and the grade table in [imagery.md](imagery.md) §1. Two more reads are planning reads, not build reads, when they apply: the preset's **hero entrance** recipe ([page-entrances.md](page-entrances.md) for `match-cut-hero` and `clip-line-hero`, on any site), and, when the brief asks for 3D, the **WebGL plan** ([webgl-r3f.md](webgl-r3f.md) §1 and [webgl-scenes.md](webgl-scenes.md) §5: poster, gate, touch fallback, and the "No model file yet" ladder in §4). Read the full files during the build (workflow step 2), not now.
82
-
83
- Decide in this order:
84
-
85
- 0. **Niche.** Buyer, genre and preset from [brief-router.md](brief-router.md) §2 step 1, plus the one borrowed field (TYPE or PALETTE) if any.
86
- 1. **Type.** Exactly two faces: display (run light, 300 to 400, unless the concept is a declared heavy genre) and text. Take a pairing id from the brand's temperament per [typography.md](typography.md) Decide here: Signature (S1 to S7) first, Extended (E1 to E6) only when no Signature temperament fits. "Unsure" is not a temperament and never a reason to take S1. Name the faces, the role weights, the source and the license, the **one signature move** ([typography.md](typography.md) §5) and the **hero entrance** (the preset's named entrance, or a declared different one).
87
- 2. **Palette.** Surface ladder (four steps), ink tiers (ink, muted, faint), one accent and the line color, written as tokens. Existing brand colors pour into this structure. Add the radius. [vibe-presets.md](vibe-presets.md)
88
-
89
- **Brand colours onto the ladder.** A brand's colour names are not token names: brand "ink" usually means the brand's dark, while `--color-ink` is the *text* colour. Map by role, never by name:
90
-
91
- | Brand colour | On a light preset | On a dark preset |
92
- |---|---|---|
93
- | The brand's dark | `ink` (text), and the inverted surfaces | `surface-0` (the page); `ink` becomes an off-white |
94
- | The brand's light | `surface-0` (the page) | `ink` (text) |
95
- | The brand's accent | `accent` | `accent` |
96
-
97
- Derive the rest of the ladder from the mapped pair (surfaces step toward the ink, muted and faint tiers mixed from ink). Check the accent against `surface-0`: 4.5:1 or better and it may be text (links); 3:1 or better and it may set large type and UI; below that it is fills and rules only. Write which under SYSTEM.
98
- 3. **Icons.** One icon family for the whole site, chosen by the order in [iconography.md](iconography.md) §2: the brand's own set, else Struq Icons. Name the package, the stroke and how its accent maps to the palette.
99
- 4. **Imagery.** One grade, the declared ratio set (2 to 3), grain yes or no. [imagery.md](imagery.md)
100
- 5. **Scroll and motion.** Lenis lerp (or native, with the reason). Motion is the baseline MOTION line in [../SKILL.md](../SKILL.md) by default: write "baseline". Any other duration band or ease pair, including the alternative pairs in [feel-layer.md](feel-layer.md) §3, is an allowed deviation only when it is written here with its reason.
101
-
102
- ## 5. The gate
103
-
104
- Write `STRUQ.md` (template below), then send one message with: the name, the idea line, the two typefaces and pairing id, the signature move and hero entrance, the palette as hex, the icon family, the arc as one line, the showpiece ids, the open assumptions, and the materials request. End it with exactly this:
105
-
106
- > **Veto anything? If you have a logo, photos or real numbers, send them now.**
107
-
108
- Apply edits and new materials to the file; do not re-interview. This is the only stop between the brief and the build (the wizard in [brief-router.md](brief-router.md) §1b is the one exception, and it runs before this gate, not instead of it).
109
-
110
- If the user said "just build it", or the run is non-interactive, write the file, state the assumptions at the top and continue.
111
-
112
- ## 6. During the build
113
-
114
- `STRUQ.md` is the contract. Before each workflow step, re-read the section it depends on. If a decision has to change during the build (a font does not have the weight, an asset is missing), change it **in the file first**, with one line under LOG saying why, then change the code. A site that drifts from its own plan is how a directed build turns back into a template.
115
-
116
- After verify, append the score and the open issues under VERIFY.
117
-
118
- ## STRUQ.md template
119
-
120
- ```markdown
121
- # STRUQ.md: <project name>
122
-
123
- Mode: build | redesign | directed
124
- Stack: <Next.js | Vite> <installed major> · React <major> · Tailwind <major> · GSAP <x.y> · Lenis <major> · three <0.x> (if used), read from package.json (setup.md §0); Vite translations per stack-targets.md
125
-
126
- ## MATERIALS
127
- Have: <logo, copy, photos, numbers, quotes>
128
- Missing: <list, with how each gap is handled>
129
- References: <sites the client envies, and what to take from each>
130
-
131
- ## BRAND
132
- Name: <name> (shortlist and rationale in brand/NAMING.md, if generated)
133
- Logo: brand/logo.svg (wordmark), brand/mark.svg (mark), app/icon.svg, app/apple-icon.png
134
- OG: app/opengraph-image.tsx, plus one per shared route (seo.md §3)
135
- Voice: <one line, e.g. "plain, certain, a little dry; short sentences; no exclamation marks">
136
-
137
- ## CONCEPT
138
- Idea: <one sentence>
139
- Tension: convince <...> / never signal <...>
140
- Motif: <the one recurring device>
141
-
142
- ## SYSTEM
143
- Niche: <buyer + business model> · genre <Portfolio | Studio | Product launch | Campaign>
144
- · preset <Soft Modern | Warm Editorial | Dark Cinematic | Luxury Minimal | Brutalist (genre appendix, declared genre only)>
145
- [+ borrowed TYPE or PALETTE from <preset>]
146
- Display: <face> <weight> (<source>, <license>) · pairing <S1 to S7 | E1 to E6>
147
- Text: <face> <weights> (<source>, <license>)
148
- Signature move: <weight-in | soft-scrub | type-mask-media | fit-width-wordmark | cropped display>
149
- Hero entrance: <the preset's named entrance, or a declared different one>
150
- Palette: <light | dark> · --surface-0 #... --surface-1 #... --surface-2 #... --surface-3 #...
151
- --ink #... --ink-muted #... --ink-faint #... (non-text only) --accent #... --line <...>
152
- Radius: <n>px
153
- Icons: <family, package, stroke> (default: Struq Icons, @struq/icons, strokeWidth 1.5, accent = --color-accent)
154
- Imagery: <grade>, ratios <2 to 3>, grain <opacity | none>
155
- Scroll: Lenis lerp <the preset's stated value | native, because ...> on gsap.ticker
156
- Motion: baseline | deviations: <each one, with its reason>
157
-
158
- ## PLAN
159
- Pages: <list with slugs>
160
- Arc (home): <numbered rows: section [beat] note, spacing role>; proof form; grid-breaks
161
- Reveals: headlines <...> · blocks <...> · images <...> · svg <moment | none>
162
- Showpieces: <showpiece ids, arc row, mobile fallback> (1 to 2, breathers on both sides; a showpiece is its own density class, page-architecture.md §3)
163
- System recipes: <system ids: line-rise-hero, route-curtain, fullscreen-menu ...>
164
- Transitions: <route-curtain variant | none>, loader <yes | no>
165
- WebGL: <none | the moment, its poster, its fallback>
166
- Copy budget: <per section>
167
- Assets to produce: <shot list: subject, grade, ratio, source>
168
-
169
- ## PRESERVED (redesign only)
170
- - <what will not be touched: brand color, copy, component primitives>
171
-
172
- ## ASSUMPTIONS
173
- - <every decision made without the user's input>
174
- - <every drafted claim: copy in a real person's or brand's name, catalogue entries, a provisional logo, a 3D stand-in>
175
-
176
- ## LOG
177
- - <date> <decision changed and why>
178
- - <date> <API>: installed <pkg major> differs from the skill's code; adapted to <change> (setup.md §0)
179
-
180
- ## VERIFY
181
- Server: next start on :<port>, port owner confirmed
182
- Viewports: 390, 768, 1440, 1920
183
- Score: <n>/100 · LCP <s> (element) CLS <n> INP <ms | field data pending> · first-load JS <n> kB gzip
184
- Motion: pins engaged slow and fast <ok>, descender test <ok>, frame trace <ok>
185
- Open: <issues>
186
- ```
187
-
188
- Directed mode appends `## DIRECTION` after PLAN, per [../cinematic/README.md](../cinematic/README.md).
@@ -1,107 +0,0 @@
1
- # The Feel Layer: Values You'd Actually Ship
2
-
3
- Techniques are copyable; feel is in the numbers. Two builds can run the same code and one feels like Locomotive while the other feels like a tutorial. The delta is lerp values, scrub smoothing, ease pairing, and duration scale. This file is the tuning reference for everything else in the skill.
4
-
5
- ---
6
-
7
- ## 1. Lenis: the scroll's personality
8
-
9
- Lenis interpolates real scroll position toward the target each frame. `lerp` is the fraction covered per frame: **lower = floatier, heavier; higher = tighter, more direct.** This single number sets the personality of the entire site, because every scrub-tied effect inherits it.
10
-
11
- | lerp | Feel | Ship it for |
12
- |---|---|---|
13
- | 0.05-0.07 | syrup-heavy, luxurious, slightly defiant | luxury/fashion one-pagers, short narrative scrolls. Risky: long pages feel slow to operate |
14
- | **0.09-0.11** | **weighted but obedient (the default)** | **portfolios, studios, most marketing** |
15
- | 0.12-0.16 | light glide, barely-smoothed | content-heavy marketing, editorial with lots of reading |
16
- | (no Lenis) | native | dashboards, docs, apps: smoothing a tool insults the user |
17
-
18
- Decision rule: the more *reading* per viewport, the higher the lerp; the more *imagery*, the more floor you can afford. Alternative: `duration: 1.2` + `easing` (Lenis's time-based mode) instead of `lerp`. Same idea, easier to reason about as "a scroll impulse takes ~1.2s to settle." Pick one mode, don't set both.
19
-
20
- Always:
21
- - `autoRaf: false` + GSAP ticker + `lagSmoothing(0)` ([setup.md](setup.md)): two clocks = micro-jitter on every scrub.
22
- - `anchors: true` so in-page links still work.
23
- - Under `prefers-reduced-motion`, keep Lenis mounted and gate the motion it drives (pins, parallax, scrub showpieces) in `matchMedia` branches, as [setup.md](setup.md) §2 and §4 wire it. Returning early from the provider makes native scroll look like a broken setup.
24
-
25
- ---
26
-
27
- ## 2. Scrub: direct drive vs. smoothed
28
-
29
- `scrub: true` = value locks to scroll position exactly. `scrub: <n>` = value *chases* scroll position over ~n seconds: scroll stops, animation eases into place.
30
-
31
- | Value | Feel | Use for |
32
- |---|---|---|
33
- | `true` | mechanical 1:1, instant stop | things that must feel *attached*: hero departure, progress bars, curtains the user "drags," marquee drift |
34
- | `0.5-1` | weighted, settles after the hand stops | **default for transforms**: parallax layers, pinned sequences, horizontal scroll |
35
- | `1.5-2.5` | cinematic float, noticeable lag | large background media, ambient/atmospheric layers |
36
- | `>3` | detached, reads as broken | nothing |
37
-
38
- Two interacting smoothings: Lenis already smooths the scroll *input*; scrub smooths the *output* on top. Lenis at 0.1 + `scrub: 1` is the classic pairing. If you stack Lenis 0.06 + scrub 2.5, the page feels drunk. Rule: **lower Lenis lerp → lower scrub numbers.**
39
-
40
- Mixed rates in one section are the pro move: hero title at `scrub: true` (attached), background media at `scrub: 1.5` (floating behind it). The differential *is* the depth.
41
-
42
- ---
43
-
44
- ## 3. Ease pairing: the system, not the menu
45
-
46
- Full curve table lives in [transitions.md](transitions.md). The shipping system:
47
-
48
- **The default system is the baseline MOTION line in [../SKILL.md](../SKILL.md), and these are the same numbers:**
49
-
50
- | Role | Ease | GSAP name | CSS |
51
- |---|---|---|---|
52
- | Entrances | expo-out | `expo.out` | `cubic-bezier(0.16, 1, 0.3, 1)` |
53
- | Exits | power2-in (cubic-in) | `power2.in` | `cubic-bezier(0.32, 0, 0.67, 0)` |
54
- | Position swaps (A to B moves) | quart-in-out | `power3.inOut` | `cubic-bezier(0.76, 0, 0.24, 1)` |
55
- | Curtains and panels | quint-in-out | `power4.inOut` | `cubic-bezier(0.83, 0, 0.17, 1)` |
56
-
57
- GSAP's names are offset by one from the classic curve names: `power1` = quad, `power2` = cubic, `power3` = quart, `power4` = quint.
58
-
59
- **Allowed deviations, recorded in STRUQ.md SYSTEM with a reason.** A preset or concept may swap the pair; it is never a silent per-component choice:
60
- - *Kinetic*: `power4.out` (quint-out) entrances + `back.out(1.4)` accents (crisp, springy)
61
- - *Editorial-calm*: `power3.out` (quart-out) entrances + `power2.inOut` moves (nothing shouts)
62
-
63
- Then three rules:
64
- 1. Entrances out-ease, exits in-ease (`power2.in`), A→B moves in-out. Never the reverse.
65
- 2. Scrubbed tweens: `ease: "none"` for tracking values; segment eases (power2) only *inside* scrubbed timelines for phase swaps.
66
- 3. `back`/`elastic` are accent spices: ≤1 element type per page (toast, badge, magnetic return). Two springy things = toy.
67
-
68
- ---
69
-
70
- ## 4. Duration & stagger scale
71
-
72
- Durations scale with **visual mass** (size × contrast of the change), not importance. A 90vw image takes 1.2s; a 14px tag takes 0.25s; same duration on both makes one feel sluggish and the other twitchy.
73
-
74
- | Tier | Duration | Examples |
75
- |---|---|---|
76
- | Micro | 0.2 to 0.3s | press, toggles, underline, tag slide |
77
- | Standard UI | 0.3 to 0.6s | dropdowns, card hovers, accordion, text swap |
78
- | Reveal | 0.6 to 1.0s | section entrances, split lines, masked images |
79
- | Hero and cinematic | 1.0 to 1.6s | hero entrance, page curtain + content rise, full-bleed media |
80
- | Over 1.6s | n/a | only multi-part timelines; a single tween this long = user waiting |
81
-
82
- These bands are the baseline's, number for number. A different band is a deviation and goes into STRUQ.md SYSTEM with its reason.
83
-
84
- Exits run about 60% of their entrance's duration (leaving is decisive; see [transitions.md](transitions.md) §4).
85
-
86
- Stagger:
87
-
88
- | each | Feel | Use |
89
- |---|---|---|
90
- | 0.02-0.04 | texture: perceived as one rich motion | chars, long lists (12+) |
91
- | **0.06-0.1** | **readable rhythm (the default)** | cards, lines, nav items |
92
- | 0.12-0.18 | deliberate, editorial | 2-4 big blocks |
93
- | >0.2 | queueing: last item enters embarrassingly late | avoid; for n items keep `each × n ≤ ~0.8s` |
94
-
95
- `stagger: { each: 0.08, from: "center" | "edges" | "random" }`: vary the origin by composition (centered logo walls from `"center"`, grids from `"start"`, particle-ish textures `"random"`).
96
-
97
- ---
98
-
99
- ## 5. The 60-second feel audit
100
-
101
- Scroll the build top to bottom once, then:
102
-
103
- 1. **Stop scrolling abruptly mid-section.** Do scrubbed layers settle softly (good) or freeze instantly (raise scrub) or keep drifting awkwardly (lower it)?
104
- 2. **Watch one entrance.** Can you see it *end*? If you can't perceive the deceleration tail, the ease or duration is too timid. This is the #1 "feels like a template" cause even when the technique is right.
105
- 3. **Count layers in a hover.** One moving layer = cheap, two or three = choreography.
106
- 4. **Flick-scroll the whole page.** Does the site stay composed (Lenis + scrub doing their job) or strobe with triggered animations firing everywhere? If strobing: too many `once` reveals triggered too late (move starts earlier, e.g. `top 85%`).
107
- 5. **Is anything moving that the user didn't cause?** Autonomous looping motion (except one deliberate marquee/ambient element) drains the premium feel. Agency motion is overwhelmingly *response*, not *activity*.