@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,330 +0,0 @@
|
|
|
1
|
-
# Anti-Pattern Gallery: the Cheap Reflex vs. the Agency Version
|
|
2
|
-
|
|
3
|
-
Each pair below names the **tell**, the specific detail that exposes the default. Use this file as a pre-ship checklist: scan for tells, replace with the paired fix. Cross-references point to the full technique.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Elevation
|
|
8
|
-
|
|
9
|
-
**Reflex:**
|
|
10
|
-
```tsx
|
|
11
|
-
<div className="rounded-xl bg-white p-6 shadow-lg border border-gray-200">
|
|
12
|
-
```
|
|
13
|
-
**The tell:** `shadow-md`/`shadow-lg` doing elevation on a light card, gray-200 border, on a `bg-gray-50` page. Component-library depth model.
|
|
14
|
-
|
|
15
|
-
**Agency:**
|
|
16
|
-
```tsx
|
|
17
|
-
// Dark: elevation = lighter surface. Light: elevation = hairline + tone.
|
|
18
|
-
<div className="rounded-sm bg-surface-2 p-8 border border-line">
|
|
19
|
-
```
|
|
20
|
-
Surface ladder steps (`surface-0 → 1 → 2`) signal depth tonally. Real shadows are reserved for true floaters, and then: `shadow-[0_24px_80px_-12px_rgb(0_0_0/0.4)]`. ([baseline-prompt.md](baseline-prompt.md))
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## 2. The feature section
|
|
25
|
-
|
|
26
|
-
**Reflex:**
|
|
27
|
-
```tsx
|
|
28
|
-
<section className="py-16">
|
|
29
|
-
<h2 className="text-center text-3xl font-bold">Why Choose Us</h2>
|
|
30
|
-
<p className="mt-2 text-center text-gray-500">Everything you need to succeed</p>
|
|
31
|
-
<div className="mt-12 grid grid-cols-3 gap-8">
|
|
32
|
-
{features.map(f => (
|
|
33
|
-
<div className="rounded-xl border p-6 shadow-sm text-center">
|
|
34
|
-
<Icon className="mx-auto h-8 w-8 text-blue-600" />
|
|
35
|
-
<h3 className="mt-4 font-semibold">{f.title}</h3>
|
|
36
|
-
<p className="mt-2 text-sm text-gray-500">{f.body}</p>
|
|
37
|
-
</div>
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
</section>
|
|
41
|
-
```
|
|
42
|
-
**The tell:** centered h2 + subhead + 3 identical icon-cards. The single most recognizable AI/template section on the internet.
|
|
43
|
-
|
|
44
|
-
**Agency:** kill the cards; let type and rhythm carry it:
|
|
45
|
-
```tsx
|
|
46
|
-
<section className="content-grid py-40 gap-y-0">
|
|
47
|
-
<h2 className="font-display text-h2 max-w-[18ch]">What we take on, start to finish</h2>
|
|
48
|
-
<div className="mt-16 divide-y divide-line">
|
|
49
|
-
{features.map((f) => (
|
|
50
|
-
<div key={f.title} className="group grid grid-cols-12 items-baseline gap-4 py-10">
|
|
51
|
-
<h3 className="type-h3 text-h3 col-span-6">{f.title}</h3>
|
|
52
|
-
<p className="text-body col-span-5 col-start-8 max-w-[44ch] text-ink-muted">{f.body}</p>
|
|
53
|
-
</div>
|
|
54
|
-
))}
|
|
55
|
-
</div>
|
|
56
|
-
</section>
|
|
57
|
-
```
|
|
58
|
-
Editorial rows, left-aligned, divided by hairlines, asymmetric columns. Hierarchy comes from type scale and the hairlines, not from a label or a number on every row (see §16). ([composition.md](composition.md))
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## 3. Scroll reveal
|
|
63
|
-
|
|
64
|
-
**Reflex:**
|
|
65
|
-
```tsx
|
|
66
|
-
<motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} transition={{ duration: 0.3 }}>
|
|
67
|
-
```
|
|
68
|
-
**The tell:** opacity-only, no transform, no stagger, 300ms default ease. Content blinks into existence, all siblings simultaneously.
|
|
69
|
-
|
|
70
|
-
**Agency:**
|
|
71
|
-
```tsx
|
|
72
|
-
gsap.from("[data-reveal]", {
|
|
73
|
-
y: 48, autoAlpha: 0, duration: 0.9, ease: "expo.out",
|
|
74
|
-
stagger: 0.08,
|
|
75
|
-
scrollTrigger: { trigger: container.current, start: "top 78%", once: true },
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
Transform + opacity, expo-out tail, 80ms rhythm between siblings, inside the reduced-motion branch. ([scroll-reveals.md](scroll-reveals.md))
|
|
79
|
-
|
|
80
|
-
**The silent failure:** a `[data-reveal]` element that also carries `transition-opacity` (a sibling-dim hover) or a `translate-y-*` offset. The first leaves it stuck at `opacity: 0` with the tween at progress 1; the second is erased because GSAP sets `translate: none`. Reveal the wrapper, transition and offset the child ([pitfalls.md](pitfalls.md) §3).
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 4. Headline entrance
|
|
85
|
-
|
|
86
|
-
**Reflex:** the whole `<h1>` fades up as one block, or worse, a per-character typewriter with uniform linear timing.
|
|
87
|
-
**The tell:** text treated as a rectangle (or a teletype), not as lines.
|
|
88
|
-
|
|
89
|
-
**Agency:**
|
|
90
|
-
```tsx
|
|
91
|
-
SplitText.create(el, {
|
|
92
|
-
type: "lines", mask: "lines", autoSplit: true,
|
|
93
|
-
onSplit: (self) => gsap.from(self.lines, {
|
|
94
|
-
yPercent: 110, duration: 1.1, ease: "expo.out", stagger: 0.09,
|
|
95
|
-
scrollTrigger: { trigger: el, start: "top 80%", once: true },
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
98
|
-
```
|
|
99
|
-
Lines rising from individual overflow masks. ([scroll-reveals.md](scroll-reveals.md) §3; the typographic side of SplitText lives in [typography.md](typography.md))
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## 5. Hero
|
|
104
|
-
|
|
105
|
-
**Reflex:** centered h1, gray subhead, two buttons (one blue, one outline), then a product screenshot in a browser-chrome mockup, possibly tilted with a purple glow.
|
|
106
|
-
**The tell:** the symmetric "SaaS hero stack," gradient text on the keyword, floating screenshot.
|
|
107
|
-
|
|
108
|
-
**Agency (structure, not just styling):** left-aligned or compositionally split; h1 as `font-display text-display` (the pairing's display clamp, [typography.md](typography.md) §2.4) doing the visual work; media bleeding off-grid or punching into the next section; meta-information (location, availability, scroll cue) placed at the hero's edges like a poster; scrub-tied departure ([pinning-scrub.md](pinning-scrub.md) §3). A hero is a *composition*, not a stack. For ready-made hero showpieces, see [showcase.md](showcase.md).
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## 6. Parallax
|
|
113
|
-
|
|
114
|
-
**Reflex:** `background-attachment: fixed` banner, or one layer drifting at 0.5× scroll.
|
|
115
|
-
**The tell:** single-layer movement (and iOS breakage). Depth needs *relative* rates.
|
|
116
|
-
|
|
117
|
-
**Agency:** three siblings at `data-speed` 0.9 / 1.0 / 1.12 within one section, scrubbed, symmetric around natural position, plus photos panning inside `overflow-hidden` frames (`scale(1.18)`, `yPercent` from -8 to 8). ([depth-parallax.md](depth-parallax.md))
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## 7. Sticky stacking
|
|
122
|
-
|
|
123
|
-
**Reflex:** bare `sticky top-0` cards papering over each other.
|
|
124
|
-
**The tell:** the covered card sits frozen at full scale/brightness. It looks glitched, not layered.
|
|
125
|
-
|
|
126
|
-
**Agency:** each covered card recedes (`scale: 0.92`, surface-colored veil to 50%), scrubbed to the *next* card's approach. ([depth-parallax.md](depth-parallax.md) §3)
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## 7b. Pinned / horizontal-scroll handoff
|
|
131
|
-
|
|
132
|
-
**Reflex:** reach for `anticipatePin: 1` (and/or `scrub: true`) to "smooth" the moment a pinned or horizontal section engages.
|
|
133
|
-
**The tell:** a jerk/jump right at the vertical-to-horizontal (or pin-on) handoff, worst on a fast scroll-in. It snaps then corrects instead of easing in. With smooth scroll (Lenis), `anticipatePin` predicts from a *smoothed* velocity, overshoots, and pins a frame early; `scrub: true` then passes that spike straight through 1:1.
|
|
134
|
-
|
|
135
|
-
**Agency:** **no `anticipatePin`** with smooth scroll + `scrub: 1` (pairs with Lenis lerp ~0.09 so the track settles softly). If only the *first* scroll-in snaps, the cause is stale pin positions, so add the delayed initial `lenis.resize()` + `ScrollTrigger.refresh()` from [setup.md](setup.md) §2. Tuning `scrub` is not the fix; removing `anticipatePin` is. ([pinning-scrub.md](pinning-scrub.md) §2; every other pin cause, symptom first: [pitfalls.md](pitfalls.md) §2)
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## 8. Section transitions
|
|
140
|
-
|
|
141
|
-
**Reflex:** every section a watertight `py-16` box; boundaries are just background-color changes at a hard line.
|
|
142
|
-
**The tell:** uniform vertical rhythm + zero cross-boundary elements. The page is a stack of slides.
|
|
143
|
-
|
|
144
|
-
**Agency:** padding varies by section role (8/16/24rem); at least one element trespasses each major seam (negative margin + z); one act-break uses a scrubbed curtain sweep. ([composition.md](composition.md), [transitions.md](transitions.md) §2)
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## 9. Page navigation
|
|
149
|
-
|
|
150
|
-
**Reflex:** hard cut between routes, or a 200ms full-page opacity crossfade.
|
|
151
|
-
**The tell:** navigation is an event, not a moment. No surface moves, content just swaps.
|
|
152
|
-
|
|
153
|
-
**Agency:** quint-in-out curtain sweep (`scaleY` with flipping `transform-origin`) + incoming content rising with expo-out after a 0.25s overlap. Total ≤ 900ms. ([transitions.md](transitions.md) §1; for full-screen route transitions on multi-page sites see [route-transitions.md](route-transitions.md))
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## 10. Buttons & hovers
|
|
158
|
-
|
|
159
|
-
**Reflex:**
|
|
160
|
-
```tsx
|
|
161
|
-
<button className="rounded-lg bg-blue-600 px-4 py-2 text-white transition hover:bg-blue-700 hover:scale-105">
|
|
162
|
-
```
|
|
163
|
-
**The tell:** color-darken + scale-up + `transition-all duration-300`, identical on every interactive element.
|
|
164
|
-
|
|
165
|
-
**Agency:** one primary CTA with a *scene*: fill swelling from the bottom edge, label rolling through a mask or trailing magnetically, elastic return. Secondary links get the origin-flipping underline. Cards get three-clock hovers + sibling dimming. Press = `scale 0.97` at 150ms. ([micro-interactions.md](micro-interactions.md))
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## 11. Color & accent
|
|
170
|
-
|
|
171
|
-
**Reflex:** `bg-gradient-to-r from-blue-600 to-purple-600` (buttons, hero glow, `bg-clip-text` headline), accents in 4 hues, pure white page with gray-500 text.
|
|
172
|
-
**The tell:** the blue-purple gradient is the watermark of generated design; multiple accents mean no hierarchy decision was made.
|
|
173
|
-
|
|
174
|
-
**Agency:** temperature-shifted neutrals doing 95% of the work; **one** accent appearing at most once per viewport; headline color = ink, with hierarchy from scale. If a gradient exists at all it is a subtle same-hue tonal ramp on a surface, never on text. ([vibe-presets.md](vibe-presets.md))
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## 12. Type scale
|
|
179
|
-
|
|
180
|
-
**Reflex:** h1 `text-4xl`, h2 `text-3xl`, h3 `text-2xl`, h4 `text-xl`: five steps between 20 and 36px, all `font-bold`, default tracking.
|
|
181
|
-
**The tell:** compressed mid-range hierarchy. Nothing leads, everything is "kind of big."
|
|
182
|
-
|
|
183
|
-
**Agency:** two extremes, set through the role tokens so nothing is improvised: `font-display text-display` for the hero (the token carries the pairing's clamp, tracking and leading), `font-display text-h2` per section, `type-h3 text-h3` for items, `text-body` at 1 to 1.0625rem, and quiet `text-small text-ink-muted` meta lines (sentence case) for captions and dates. No label above a heading (§19). No `tracking-tight`: tracking belongs to the face and the role ([typography.md](typography.md) §3.1). **Hero/display runs light (300 to 400)**. Large + light is the agency signal; heavy display (600+) is a deliberate genre choice only, never the reflex. **Floor:** body ≥1rem, muted ≥0.875rem, nothing below 12px. **Contrast:** body ≥4.5:1 (WCAG AA), large/≥24px ≥3:1; `ink-faint` is never text. ([baseline-prompt.md](baseline-prompt.md); the Signature pairings, roles and fluid scale are in [typography.md](typography.md))
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## 13. Iconography & ornament
|
|
188
|
-
|
|
189
|
-
**Reflex:** 🚀✨💡 as feature icons; the AI-cliché trio (`Sparkles`, `Rocket`, `Zap`/lightning, in any set) standing in for "magic / launch / fast"; reflexive Lucide for everything; icons at 5 sizes/strokes in brand-blue circles with light-blue backgrounds; an animated icon looping forever in a dashboard, or a 500ms+ animation lagging a click; a complex decorative icon turned to mud at 16px.
|
|
190
|
-
**The tell:** icon-as-filler. `Sparkles`/`Rocket`/`Zap` are the visual signature of generated UI; the icon circle chip is the card-grid's sidekick; constant-loop animation reads as distraction, not delight.
|
|
191
|
-
|
|
192
|
-
**Agency:** icons only where they inform (UI affordances, scannability, button anchors, never decoration); **one coherent family chosen for the brand** (its own set, else Struq Icons, else Phosphor light; never reflexive Lucide, never two families), one size, `strokeWidth={1.5}`, ink-colored; system icons small, decorative/animated big. Animated icons: ≤500ms on click, 1.5 to 3s on hover, never loop in standard UI, natural physics, reduced-motion falls back to static. Decoration jobs otherwise go to hairline rules, ghost type, a single oversized numeral as an accent (not a per-row counter, see §16), real photography/render. Often the strongest move is **no icon at all**. (Full doctrine: [iconography.md](iconography.md); drawn or morphing line art belongs to [svg-motion.md](svg-motion.md))
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
## 14. The exit problem
|
|
197
|
-
|
|
198
|
-
**Reflex:** choreographed entrances, then `display: none` exits: accordions snapping shut, modals vanishing, filtered items teleporting.
|
|
199
|
-
**The tell:** asymmetry of care. The system only performs in one direction.
|
|
200
|
-
|
|
201
|
-
**Agency:** every exit designed at ~60% of entrance duration with `power2.in` / `easeIn`, reverse stagger for groups, `AnimatePresence` for unmounts. ([transitions.md](transitions.md) §4)
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## 15. Loading & lorem
|
|
206
|
-
|
|
207
|
-
**Reflex:** lorem ipsum, "John Doe, CEO at Acme", `https://via.placeholder.com/600x400`, spinner gif while content pops in.
|
|
208
|
-
**The tell:** placeholder *anything*. It signals nobody made content decisions, and layout was never tested against reality.
|
|
209
|
-
|
|
210
|
-
**Agency:** real (or convincingly written) copy with opinions; image areas with explicit aspect ratios; entrances double as load choreography (content reveals when ready, no spinner, no CLS).
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## 16. Numbered everything
|
|
215
|
-
|
|
216
|
-
**Reflex:** `01 / 02 / 03 / 04` stamped on every section header, every feature, every list row (often oversized in a corner) as the page's wayfinding system.
|
|
217
|
-
**The tell:** uniform sequencing is reflexive, not editorial. No art director numbers *everything*; it is the same impulse as `py-16` on every section, applied to labels. It signals "I needed a decoration and reached for the nearest one."
|
|
218
|
-
|
|
219
|
-
**Agency:** wayfinding through variety: hairline rules, scale jumps, surface changes, the headings themselves. Never tracked uppercase eyebrows: they are a tell of their own (§19). A numeral earns its place only as a **single deliberate accent** (one oversized `03` against a manifesto line), never as a per-row counter. If you can delete the numbers and lose no meaning, they were filler.
|
|
220
|
-
|
|
221
|
-
**The boundary:** ordinal numbering *inside one genuinely stepped component* (the phase kickers of a pinned sequence, see [pinning-scrub.md](pinning-scrub.md) §1, or a numbered process that really is ordered) is legitimate content, not this tell. The tell is *page-wide* stamping: numerals as the default decoration on every section, feature, and row.
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## 17. Monospace as flavor
|
|
226
|
-
|
|
227
|
-
**Reflex:**
|
|
228
|
-
```tsx
|
|
229
|
-
<p className="font-mono text-xs uppercase tracking-widest">// System online</p>
|
|
230
|
-
<span className="font-mono text-sm">[ 2024 ]</span>
|
|
231
|
-
```
|
|
232
|
-
**The tell:** monospace on labels, stats, badges, timestamps, or body to look "technical / engineered / terminal." It is the aesthetic of a code editor, not a designed page, and it has become a default reach for "techy," which makes it read as generated.
|
|
233
|
-
|
|
234
|
-
**Agency:** a real text face carries all UI and copy; "precision" comes from tracking, `tabular-nums` on figures, and tight type, not from a typewriter font. **Mono is reserved for literal code** (snippets, inline `code`, terminal output on a dev-tool site where it is genuinely the subject). Anywhere else, it is a tell. The same logic kills the scramble/decode "terminal" loader ([loaders.md](loaders.md)).
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## 18. Per-viewport pixel-fixing
|
|
239
|
-
|
|
240
|
-
**Reflex:**
|
|
241
|
-
```tsx
|
|
242
|
-
<h2 className="text-2xl md:text-4xl lg:text-5xl xl:text-6xl">
|
|
243
|
-
<section className="px-6 py-12 md:px-12 md:py-20 lg:px-24 lg:py-32">
|
|
244
|
-
// fixed px widths everywhere; a fresh override added each time a new screen breaks
|
|
245
|
-
```
|
|
246
|
-
**The tell:** every size hand-tuned per breakpoint, layout authored in px, and it *still* drifts at 1440 and 2560, so each new viewport means another round of element-by-element fixes. The hours sink is real and it never converges.
|
|
247
|
-
|
|
248
|
-
**Agency:** one fluid root-rem knob (an a11y-safe `clamp` that keeps a rem term so zoom works), everything authored in rem, breakpoints reserved for **layout changes** (column count, stack/row) not resizing; display type carries its own `clamp()` on top. The whole UI scales proportionally at every width with zero per-element overrides. ([scaling.md](scaling.md))
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## 19. Eyebrows
|
|
253
|
-
|
|
254
|
-
**Reflex:**
|
|
255
|
-
```tsx
|
|
256
|
-
<p className="text-xs font-medium uppercase tracking-[0.18em] text-ink-muted">Restaurant in Amsterdam-Noord</p>
|
|
257
|
-
<h1 className="font-display text-display">Vis en groente, van het seizoen.</h1>
|
|
258
|
-
```
|
|
259
|
-
**The tell:** a small uppercase tracked label above every heading. Generated pages put one on every section because it looks "designed" at no cost; the result is the most recognisable AI marketing page there is, and it competes with the heading it sits on.
|
|
260
|
-
|
|
261
|
-
**Agency:** the heading carries the section alone. Wayfinding comes from a hairline rule (`border-t border-line`), a change of surface step, a jump in scale, an image. A place or a date the reader needs goes in the lead line under the heading, in sentence case. `check.mjs` fails on `uppercase` with `tracking-`.
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## 20. Gradients, tinted fills and glass
|
|
266
|
-
|
|
267
|
-
**Reflex:** `bg-linear-to-b from-surface-0 to-surface-2` on a section, `bg-accent/10` behind a label, `bg-white/5 backdrop-blur` on a card, a radial glow behind the hero.
|
|
268
|
-
|
|
269
|
-
**The tell:** colour that is neither a surface nor the accent. Translucent tints and gradients are the generator's way to make a flat page "rich"; they muddy the palette and read as a template on sight.
|
|
270
|
-
|
|
271
|
-
**Agency:** flat, opaque surface steps (`bg-surface-1`, `bg-surface-2`) and hairlines. The only translucent fill is a flat black scrim over a photo so type in `text-on-photo` reads. Depth comes from the WebGL hero, the photography and the overlap of sections, not from washes.
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## 21. Pills
|
|
276
|
-
|
|
277
|
-
**Reflex:** `rounded-full px-4 py-1.5 bg-accent/10 text-accent` badges, a "New" pill with a pulsing dot, pill-shaped buttons.
|
|
278
|
-
|
|
279
|
-
**The tell:** the pill with a dot is the badge of every generated launch page. Pills everywhere also break the site's radius system.
|
|
280
|
-
|
|
281
|
-
**Agency:** the site radius (0 to 4px) on every button and frame; status as words in the lead line; no badges. The primary CTA is a `MagneticButton`, square-cornered, its fill rising on hover.
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## 22. Empty frames
|
|
286
|
-
|
|
287
|
-
**Reflex:** an `aspect-[16/9] bg-surface-2` box with "Foto volgt" in it, or a placeholder component standing in for a photo that never came.
|
|
288
|
-
|
|
289
|
-
**The tell:** the reader sees the site was not finished, and a plain page with no images at all beats it.
|
|
290
|
-
|
|
291
|
-
**Agency:** every frame holds a real image: the client's photo, or the Struq beeldbank pack (`scripts/images.mjs`, automatic, CC0), or, when struq.nl is unreachable, the graded fallback plate the script writes. Never ship an empty frame ([struq-imagery.md](struq-imagery.md)).
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
## 23. The smaller tells
|
|
296
|
-
|
|
297
|
-
Each is cheap on its own; three of them together read as generated.
|
|
298
|
-
|
|
299
|
-
| Tell | Reflex | Instead |
|
|
300
|
-
|---|---|---|
|
|
301
|
-
| Side-stripe borders | `border-l-4 border-accent` down a card or a quote | a hairline, or nothing |
|
|
302
|
-
| Status badges | `bg-green-100 text-green-700` "Available" chips | the fact in the lead line |
|
|
303
|
-
| Live dots | an `animate-ping` dot beside "Now open" | the words alone |
|
|
304
|
-
| Uppercase marquees | a scrolling band of tracked capitals | a large display line in sentence case, if any marquee at all |
|
|
305
|
-
| Generic CTAs | "Learn more", "Get started", "Lees meer" | a verb and the thing: "Reserveer een tafel", "Bekijk de projecten" |
|
|
306
|
-
| Generic headings | "Our Services", "Why Choose Us", "Over ons" | a heading only this business could write |
|
|
307
|
-
| Hype copy | "seamless", "elevate", "next-level", "naadloos", "revolutionair" | the plain, specific claim from the brief |
|
|
308
|
-
| Hero-metric template | a hero followed by a strip of four big numbers | numbers only when the brief gives them, set inside the copy |
|
|
309
|
-
| Category-reflex colours | green for every eco brand, blue for every bank, purple for every AI | the canon palette that fits the mood; the reflex hue only when it is the brand's own |
|
|
310
|
-
| Restated headings | a paragraph that repeats the heading in more words | the paragraph adds the next fact |
|
|
311
|
-
|
|
312
|
-
---
|
|
313
|
-
|
|
314
|
-
## The 30-second tell scan
|
|
315
|
-
|
|
316
|
-
The script does this scan for you: `node <skill-dir>/scripts/check.mjs` prints every hit with its file and line. By hand:
|
|
317
|
-
|
|
318
|
-
Before shipping, run these grep-level checks:
|
|
319
|
-
- `shadow-md|shadow-lg` on cards → §1 · `from-blue|to-purple|bg-clip-text` → §11
|
|
320
|
-
- `py-16` ≥3 times → §8 · `grid-cols-3` of identical children → §2
|
|
321
|
-
- `hover:scale-105|transition-all` → §10 · `duration-300` as the only duration → [feel-layer.md](feel-layer.md)
|
|
322
|
-
- `opacity` tweens with no transform partner → §3 · emoji in JSX → §13
|
|
323
|
-
- `anticipatePin` anywhere while Lenis/smooth scroll is mounted → §7b (jagged pin handoff)
|
|
324
|
-
- `Sparkles|Rocket|Zap` icon imports (any set) / icon in a colored chip / looping animated icon → §13 · `font-mono` outside a code block → §17
|
|
325
|
-
- `grep -P '0\d\s*[/.\-\u2014\u2013]'` (catches `01 /`, `01.`, `01 -` and the dash variants) / `0{i + 1}` numbering every section or row → §16
|
|
326
|
-
- `font-bold` on the hero/`h1` / largest size = `text-4xl` → §12 · faint text on body copy / contrast below AA → §12
|
|
327
|
-
- size ladders like `text-2xl md:text-4xl lg:text-6xl`, `px-`/`py-` tuned per breakpoint, fixed-`px` layout widths, or no fluid root font-size → §18
|
|
328
|
-
- `text-4xl` as the largest size on the page → §12 · "Lorem|Acme|John Doe" → §15
|
|
329
|
-
- `uppercase` with `tracking-`, or an `eyebrow`/`kicker` class → §19 · `gradient|bg-linear-|backdrop-blur|bg-accent/` → §20 · `rounded-full` with `px-` → §21 · `Placeholder`, an empty `aspect-` div → §22
|
|
330
|
-
- `tracking-tight`, `text-ink-faint`, or `uppercase` with `tracking-` anywhere (the eyebrow tell, §19), or `text-xs` on headings (role tokens bypassed) → §12 · `Inter|Roboto|Open_Sans|Poppins|Montserrat|Lato|Space_Grotesk|Geist` in `next/font` imports → [typography.md](typography.md) §3.4
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
# The Minimal Baseline: rationale + standalone paste block
|
|
2
|
-
|
|
3
|
-
**SKILL.md carries the short form of this baseline ("The taste baseline") and the Build checklist that enforces it.** This file does two jobs. It explains **why each rule earns its place** (the rationale below), and it carries the one **standalone copy-paste block** for agents that do not load Struq as a skill.
|
|
4
|
-
|
|
5
|
-
<!-- The paste block below must agree with SKILL.md ("The taste baseline" and "Three rules that never bend"). When a rule changes there, change it here in the same commit. -->
|
|
6
|
-
|
|
7
|
-
## The paste block (for any LLM, any stack)
|
|
8
|
-
|
|
9
|
-
Paste this at the top of any project prompt to lift the output into the top tier. Include the rationale section only when the agent has room.
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
Act as a senior design engineer at an award-winning studio (Awwwards SOTD
|
|
13
|
-
caliber). Apply every rule below; they override your defaults.
|
|
14
|
-
|
|
15
|
-
STRUQ BASELINE: NON-NEGOTIABLES
|
|
16
|
-
|
|
17
|
-
TYPE: Signature first: one pairing id from typography.md §3.1 (S1 to S7;
|
|
18
|
-
Extended E1 to E6 only when no Signature temperament fits), two faces max.
|
|
19
|
-
Never Inter, Roboto, Open Sans, Poppins, Montserrat or Lato; never Space
|
|
20
|
-
Grotesk or Geist as a reflex. Scale 1.333 with skipped steps (body 1, lead
|
|
21
|
-
1.25, h3 1.78, h2 3.16, display 7 to 8). Hero h1 = text-display,
|
|
22
|
-
clamp(2.875rem, 1.2rem + 6.86vw, 8rem), leading 0.92 to 1.0; S4 and S5 set
|
|
23
|
-
larger, S7 smaller (typography.md §3.1). Display runs LIGHT (300 to 400);
|
|
24
|
-
heavy display only as a declared genre (the Brutalist appendix), never a
|
|
25
|
-
reflex. Tracking per face, never one tracking-tight on every size: sans
|
|
26
|
-
display about -0.025 to -0.03em, serif display about -0.01 to -0.025em, h2
|
|
27
|
-
about half that. Body 1rem to 1.0625rem at 400, leading 1.6+, 55 to 75ch;
|
|
28
|
-
emphasis 500, never 700. Hierarchy from scale contrast, not medium sizes.
|
|
29
|
-
ROLES: every string takes a role token, never text-xs, text-lg or text-5xl:
|
|
30
|
-
font-display text-display, font-display text-h2, type-h3 text-h3,
|
|
31
|
-
text-lead, text-body, text-small, meta text-meta, font-ui text-ui,
|
|
32
|
-
stat text-stat, and font-display text-statement for the one Orient line
|
|
33
|
-
or manifesto. NO EYEBROWS: no small uppercase tracked label above a
|
|
34
|
-
heading, no uppercase marquee. The heading, a hairline and scale do the
|
|
35
|
-
wayfinding; captions and dates are meta in sentence case, ink-muted.
|
|
36
|
-
FLOOR: body at least 1rem, muted at least 0.875rem, nothing under 12px.
|
|
37
|
-
CONTRAST: body at least 4.5:1, large text at least 3:1. ink-faint is never
|
|
38
|
-
text: hairlines and disabled states only.
|
|
39
|
-
SIGNATURE: every STRUQ.md declares one typographic signature move
|
|
40
|
-
(typography.md §5: weight-in, soft-scrub, type-mask-media,
|
|
41
|
-
fit-width-wordmark, a cropped display). The masked line rise is a
|
|
42
|
-
technique, not the default hero. Mono only for literal code. Headings get
|
|
43
|
-
text-wrap: balance; body gets text-wrap: pretty.
|
|
44
|
-
|
|
45
|
-
SPACING: Base-8 rhythm with deliberate asymmetry. Section padding varies by
|
|
46
|
-
role: hero 12 to 20rem, dense content 6 to 8rem, breathers 16 to 24rem. NEVER
|
|
47
|
-
uniform py-16 on every section. Whitespace is a material: at least one section
|
|
48
|
-
should feel "too empty" by SaaS standards.
|
|
49
|
-
|
|
50
|
-
SCALING: One fluid root-rem knob, not per-breakpoint size ladders:
|
|
51
|
-
html { font-size: clamp(1rem, 0.92rem + 0.2vw, 1.125rem); }
|
|
52
|
-
16px at 390, about 17.6px at 1440, frozen at 18px from about 1640. Every
|
|
53
|
-
clamp() keeps a rem term in its middle so zoom works; a vw-only middle is a
|
|
54
|
-
bug. Author everything in rem; px only for 1px hairlines. Breakpoints change
|
|
55
|
-
LAYOUT (columns, stack or row), never size. Display roles carry their own
|
|
56
|
-
clamp() on top.
|
|
57
|
-
|
|
58
|
-
SURFACE: Elevation is a tonal step, not a drop shadow. Dark mode: each layer
|
|
59
|
-
steps LIGHTER (#0A0A0B, #111113, #19191C, #232328). Light mode: pick one
|
|
60
|
-
direction and keep it (panels a shade deeper than a toned page, or white lifted
|
|
61
|
-
off a toned page). Hairline borders about 8 to 12% off the surface; in light
|
|
62
|
-
mode a border is the ink at low alpha (rgb(<ink> / 0.08 to 0.14)), never a
|
|
63
|
-
flat gray hex. Shadows only for true floating elements, then large, soft and faint
|
|
64
|
-
(0 24px 80px -12px rgb(0 0 0 / 0.4)). Never shadow-md or shadow-lg.
|
|
65
|
-
|
|
66
|
-
COLOR: One accent, used sparingly (links, the key CTA, one highlight per
|
|
67
|
-
viewport). Neutrals are temperature-shifted (warm cream and off-black, or cool
|
|
68
|
-
charcoal), never pure #FFF and #000 with gray-500 text. A second accent is a
|
|
69
|
-
declared genre choice with its own token and the same budget. NO gradients
|
|
70
|
-
of any kind, no gradient text, no glass, no tinted translucent fills (a
|
|
71
|
-
flat black scrim over a photo is the one exception), no pills, no pulsing
|
|
72
|
-
dots. No blue or purple unless the brand is that colour.
|
|
73
|
-
|
|
74
|
-
MOTION: Lenis smooth scroll (lerp 0.09 to 0.11) on one gsap.ticker clock,
|
|
75
|
-
unless the genre calls for native scroll. Every entrance animates opacity AND
|
|
76
|
-
transform (y 24 to 80px, or a clip or overflow mask). Opacity-only fades are
|
|
77
|
-
forbidden. Stagger siblings 0.06 to 0.1s. Durations: micro 0.2 to 0.3s,
|
|
78
|
-
standard UI 0.3 to 0.6s, reveals 0.6 to 1.0s, hero and cinematic 1.0 to 1.6s;
|
|
79
|
-
exits run about 60% of their entrance. Eases: entrances expo-out
|
|
80
|
-
cubic-bezier(0.16, 1, 0.3, 1) (expo.out); exits power2.in; position swaps
|
|
81
|
-
quart-in-out cubic-bezier(0.76, 0, 0.24, 1) (power3.inOut); curtains and
|
|
82
|
-
panels quint-in-out cubic-bezier(0.83, 0, 0.17, 1) (power4.inOut). Never
|
|
83
|
-
default ease-in-out everywhere.
|
|
84
|
-
Scroll-tied effects scrub; they do not fire time-based tweens at a threshold.
|
|
85
|
-
prefers-reduced-motion: gate transforms, keep opacity.
|
|
86
|
-
|
|
87
|
-
COMPOSITION: Break the grid at least twice per page: an image bleeding off
|
|
88
|
-
the viewport, an element overlapping two sections, oversized type running off
|
|
89
|
-
canvas, a sticky element. No three identical icon cards in a row. Vary section
|
|
90
|
-
layout: after a 50/50 split comes full-bleed or an asymmetric 1/3 and 2/3.
|
|
91
|
-
Icons: ONE coherent family chosen for the brand, one stroke and size,
|
|
92
|
-
ink-colored. Never emoji, never the AI-cliche icons (Sparkles, Rocket, Zap),
|
|
93
|
-
never icons in colored chips. Animated icons run under 500ms on click and
|
|
94
|
-
never loop in standard UI.
|
|
95
|
-
|
|
96
|
-
DETAILS THAT SELL IT: Real running copy, never invented facts. Never
|
|
97
|
-
01/02/03 stamped on every section (one deliberate oversized numeral is
|
|
98
|
-
fine). Never an empty image frame. The footer is designed as hard as the
|
|
99
|
-
hero. Custom
|
|
100
|
-
::selection color. Visible, designed focus-visible states. Every route change
|
|
101
|
-
is a designed transition, not a hard cut.
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
The block is the floor, not the ceiling. For the full recipe stack (Struq's canon, the beeldbank, the Lenis + GSAP single clock, the SplitText hero, a showpiece, the WebGL hero layer) run the Build checklist in [SKILL.md](../SKILL.md).
|
|
105
|
-
|
|
106
|
-
## Why each rule matters
|
|
107
|
-
|
|
108
|
-
**Spacing asymmetry.** Uniform `py-16` is the single most reliable template tell. Real art direction treats vertical rhythm as pacing: a hero breathes, a feature block is dense, a manifesto line sits in 20rem of air. Varying section padding by role is free and reads instantly as "someone decided this." The "too empty" instruction exists because LLMs systematically under-use whitespace; what feels wasteful in code feels expensive in the browser.
|
|
109
|
-
|
|
110
|
-
**Type scale contrast.** Templates use five sizes between 16 and 36px, so nothing leads. Agencies compress hierarchy into two extremes: enormous display type (it is the visual, not decoration around the visual) and small, quiet everything else. Negative tracking matters because text faces are spaced for 16px, and at 96px+ the gaps look broken; how much depends on the face (a geometric sans takes about -0.03em, a hairline serif about -0.01em), which is why tracking lives in the pairing row and not in one `tracking-tight`. The fix for the "medium subhead" that flattens hierarchy is not a tracked-uppercase eyebrow (that is a tell of its own) but a bigger gap between display and body. Role tokens (`text-display`, `text-h2`, `meta text-meta`) exist so no component improvises a size, and the one signature move exists so two Struq sites never open the same way. The deeper type system (Signature pairings, roles, fluid scale, SplitText typography) lives in [typography.md](typography.md).
|
|
111
|
-
|
|
112
|
-
**Tonal elevation.** Drop shadows on light cards are the Bootstrap-era elevation model; design-led products (Linear, Vercel, Arc) moved to tonal ladders, where closer surfaces are lighter (dark mode) or distinguished by hairline borders and a subtle tone step (light mode). The light-mode direction is a preset decision: editorial/luxury pages are the lightest layer with panels stepping deeper; brutalist pages are toned with white panels lifting off them. One step per layer keeps depth legible either way. When you do shadow a floating element, the large/soft/low-opacity recipe reads as ambient light, while `shadow-lg` reads as a 2015 component library.
|
|
113
|
-
|
|
114
|
-
**One accent.** Restraint is the strongest single luxury signal. Multiple accents (or a gradient) means no decision was made. Temperature-shifted neutrals are why agency sites feel "warm" or "cinematic" while AI output feels like a wireframe: pure white + gray-500 is the absence of a palette, not a palette. The second-accent escape hatch exists because playful/consumer genres legitimately run two voices, but it is a *declared token with the same budget*, mirroring how heavy display weight is allowed only as a genre choice, never a reflex.
|
|
115
|
-
|
|
116
|
-
**Transform + mask in every entrance.** An opacity-only fade has no physicality: nothing arrives from anywhere. Adding y-travel or a mask (the element slides out from behind a clipped edge) gives motion a direction and a surface to interact with, which is what reads as "crafted." Stagger matters because simultaneous arrival is visual noise; 0.06 to 0.1s offsets create reading order. Ease asymmetry (fast-out entrances, accelerating exits) mirrors how physical objects behave; symmetric ease-in-out everywhere is the uncanny valley of motion.
|
|
117
|
-
|
|
118
|
-
**Scrub over trigger.** Effects that progress with scroll position feel connected to the user's hand; effects that fire once at a threshold feel like a slideshow. This one distinction separates most award sites from most marketing sites.
|
|
119
|
-
|
|
120
|
-
**Grid-breaking.** A layout where every element respects the container communicates "theme." Bleed, overlap, and stickiness communicate "art direction" because they require coordinating elements across section boundaries, which is exactly what templates cannot do. Twice per page is the floor; it should look intentional, not chaotic.
|
|
121
|
-
|
|
122
|
-
**Details.** Real copy forces real layout decisions (lorem hides widow/orphan and width problems). Long pages get their editorial spine from hairline rules, surface steps and headings that say something, never from tracked eyebrow labels. Note what is *removed* here: stamping `01/02/03` on every section/feature/row is one of the most reliable AI tells. It is reflexive wayfinding that no editor would apply uniformly. One deliberate oversized numeral can be a strong accent; sequencing the whole page is filler. (Ordinal numbering *inside* one genuinely stepped component, such as a pinned phase sequence or a numbered process, is legitimate; page-wide stamping is the tell.) The same logic bans two other reflexes: **monospace as flavor** (it is the look of a code editor, not a designed page, and belongs only around literal code) and the **cliché icon set** (`Sparkles`/`Rocket`/`Zap` and their emoji forms 🚀✨💡 are the visual signature of generated UI). The footer is where everyone quits, so finishing it signals the whole build is finished. Selection color and focus states are tiny, but they are the difference between "surface-level styling" and "someone touched every state."
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Brand: name, logo, mark and the files a site needs
|
|
2
|
-
|
|
3
|
-
Run this when the project has no name or no logo. When a brand exists, do not redesign it; extract it ([direction.md](direction.md) §2). The output is small on purpose: a name, a wordmark, a mark, the favicon set, and one voice line. A brand guideline PDF is not the job. A site that carries the brand is.
|
|
4
|
-
|
|
5
|
-
Order matters: **positioning, then name, then type, then logo.** A name chosen first forces the positioning to be bent around it. A logo drawn before the typefaces are chosen never sits right in the site.
|
|
6
|
-
|
|
7
|
-
## 1. Positioning in two lines
|
|
8
|
-
|
|
9
|
-
Before naming anything, write:
|
|
10
|
-
|
|
11
|
-
- **For** <who, specifically> **who** <the need or the frustration>, **<name> is** <the category> **that** <the one thing it does differently>.
|
|
12
|
-
- **Unlike** <the real alternative, including "do it yourself"> **it** <the difference that matters to the buyer>.
|
|
13
|
-
|
|
14
|
-
If these two lines cannot be written, the brief is missing the facts the name depends on. Ask for them (one round) instead of naming in the dark.
|
|
15
|
-
|
|
16
|
-
## 2. Naming
|
|
17
|
-
|
|
18
|
-
**Skip rule.** Sites for a person, a practice or a founder-named business (portfolios, studios named after people, clinics) use the person's name. If the name is unknown, use a placeholder, record it under ASSUMPTIONS in STRUQ.md, skip this section and go straight to the wordmark (§3).
|
|
19
|
-
|
|
20
|
-
### Territories
|
|
21
|
-
|
|
22
|
-
Generate across all four, three to four candidates each, so the shortlist is a real choice and not twelve variations of one idea:
|
|
23
|
-
|
|
24
|
-
| Territory | What it is | Strength | Risk |
|
|
25
|
-
|---|---|---|---|
|
|
26
|
-
| **Descriptive** | Says what it does (a real word or two) | Instantly clear, good for local services | Hard to own, hard to trademark |
|
|
27
|
-
| **Evocative** | A real word that carries the feeling or the metaphor from the concept | Memorable, ownable, carries the motif | Needs the site to explain it |
|
|
28
|
-
| **Invented** | A coined word built from meaningful roots or sounds | Fully ownable, domain-friendly | Empty until the brand fills it |
|
|
29
|
-
| **Founder or place** | A surname, a street, a landmark, a region | Credible, personal, honest | Hard to sell later |
|
|
30
|
-
|
|
31
|
-
### The filter
|
|
32
|
-
|
|
33
|
-
Score every candidate, drop anything that fails a hard rule:
|
|
34
|
-
|
|
35
|
-
- **Hard:** easy to say on first sight, easy to spell after hearing it once, no obvious clash with a major brand in the same category, no unfortunate meaning in the client's main languages.
|
|
36
|
-
- **Soft (rank by):** one or two syllables beats three; a distinctive first letter; works as a wordmark (letter shapes with character, no awkward pairs); a plausible domain or handle pattern (`name.com`, `getname.com`, `name.studio`, the country TLD).
|
|
37
|
-
|
|
38
|
-
**You cannot verify trademarks or domain availability.** Say so plainly next to the shortlist and tell the user what to check before committing.
|
|
39
|
-
|
|
40
|
-
### The deliverable
|
|
41
|
-
|
|
42
|
-
A shortlist of five with one line each (territory, why it fits the positioning, the risk), and one recommendation with the reason. Write it to `brand/NAMING.md`. In a non-interactive run, take the recommendation and record it under ASSUMPTIONS.
|
|
43
|
-
|
|
44
|
-
## 3. The wordmark
|
|
45
|
-
|
|
46
|
-
The wordmark is the name set in type, then drawn into something ownable. Start from the **display face chosen in the SYSTEM phase** ([typography.md](typography.md)), so the logo and the headlines are one family of shapes.
|
|
47
|
-
|
|
48
|
-
1. Set the name in the display face at the weight the site uses (light, 300 to 400) and one step heavier. Pick the one that holds at 24px tall.
|
|
49
|
-
2. Tune it as a logo, not as text: tracking a little tighter than headline tracking, kern every pair by eye, decide on case (lowercase reads friendly, caps read institutional, title case reads like a person's name).
|
|
50
|
-
3. Make **one** deliberate modification. That single change turns a font into a logo: a cut terminal, a joined pair (ligature), a letter replaced by the mark, a counter filled, a stroke extended into a line that the site's motif can continue. Never more than one.
|
|
51
|
-
4. Convert to outlines, so the logo does not depend on the font loading:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
npm i -D opentype.js
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
// scripts/wordmark.mjs:
|
|
59
|
-
// node scripts/wordmark.mjs "Name" path/to/Display-Light.ttf -0.02 > brand/logo.svg
|
|
60
|
-
// Third argument: tracking in em (the tuned value from step 2). Default -0.02.
|
|
61
|
-
import { readFile } from "node:fs/promises";
|
|
62
|
-
import opentype from "opentype.js";
|
|
63
|
-
|
|
64
|
-
const [text, fontPath, trackingEm = "-0.02"] = process.argv.slice(2);
|
|
65
|
-
// Newer opentype.js majors removed load(); parse() works on every version.
|
|
66
|
-
const font = opentype.parse(await readFile(fontPath));
|
|
67
|
-
const escapeAttr = (s) =>
|
|
68
|
-
s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
69
|
-
const size = 100;
|
|
70
|
-
const tracking = Number(trackingEm) * size;
|
|
71
|
-
|
|
72
|
-
// One glyph per character, no shaping: font.stringToGlyphs() runs the ccmp feature
|
|
73
|
-
// and throws on GSUB lookups opentype.js does not support yet (seen on variable fonts).
|
|
74
|
-
const glyphs = Array.from(text, (ch) => font.charToGlyph(ch));
|
|
75
|
-
if (glyphs.some((g) => g.index === 0)) console.error("warning: a character is missing from the font");
|
|
76
|
-
|
|
77
|
-
let x = 0;
|
|
78
|
-
const parts = [];
|
|
79
|
-
const box = { x1: Infinity, y1: Infinity, x2: -Infinity, y2: -Infinity };
|
|
80
|
-
glyphs.forEach((glyph, i) => {
|
|
81
|
-
const path = glyph.getPath(x, 0, size); // baseline at y = 0
|
|
82
|
-
const b = path.getBoundingBox();
|
|
83
|
-
if (!b.isEmpty()) {
|
|
84
|
-
box.x1 = Math.min(box.x1, b.x1); box.y1 = Math.min(box.y1, b.y1);
|
|
85
|
-
box.x2 = Math.max(box.x2, b.x2); box.y2 = Math.max(box.y2, b.y2);
|
|
86
|
-
}
|
|
87
|
-
parts.push(path.toPathData(2)); // a number keeps y unflipped
|
|
88
|
-
const kern = glyphs[i + 1] ? font.getKerningValue(glyph, glyphs[i + 1]) : 0;
|
|
89
|
-
x += ((glyph.advanceWidth + kern) / font.unitsPerEm) * size + tracking;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// The viewBox hugs the ink, descenders included, so the logo aligns by its real edges.
|
|
93
|
-
const vx = Math.floor(box.x1), vy = Math.floor(box.y1);
|
|
94
|
-
const vw = Math.ceil(box.x2) - vx, vh = Math.ceil(box.y2) - vy;
|
|
95
|
-
console.log(
|
|
96
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${vx} ${vy} ${vw} ${vh}" fill="currentColor" role="img" aria-label="${escapeAttr(text)}"><path d="${parts.join(" ")}"/></svg>`
|
|
97
|
-
);
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Use a **static** file of the chosen weight. A variable file outlines its default instance, usually 400, not the light weight the site uses. The reliable source an agent can fetch without downloading archives is Fontsource on npm, which ships one static file per weight, including for families Google serves as variable only (Sora, for one): `npm i -D @fontsource/<family>`, then use `node_modules/@fontsource/<family>/files/<family>-latin-300-normal.woff`. opentype.js parses WOFF (not WOFF2), and `next/og` reads the same WOFF ([seo.md](seo.md) §3), so copy it once to `assets/fonts/` (outside `public/`) and point both scripts at it. A static TTF or OTF from the foundry works the same way. The script applies the font's own pair kerning plus your tracking; for a hand-kerned result, export each glyph as its own `<path>` and adjust `x` per pair, or finish the outline in a vector editor. Apply the one modification from step 3 on the outlined paths.
|
|
101
|
-
|
|
102
|
-
5. Use `fill="currentColor"`, so the logo takes the ink color in light and dark surfaces without a second file.
|
|
103
|
-
6. **Test the modification at nav size** (about 1.25 to 1.5rem tall). A thin full-width rule under a word reads as a link underline there, and a cut terminal can vanish. If it fails at nav size, make it thicker, shorter or part of a letter. The modification goes into the SVG as its own element after the outlined path (a `<rect>`, a `<path>`), so it can be tuned without re-outlining.
|
|
104
|
-
|
|
105
|
-
## 4. The mark
|
|
106
|
-
|
|
107
|
-
The mark is the logo reduced to one shape that works at 16px: the favicon, the loader, the menu button, the social avatar. Draw it as SVG by construction, never by freehand path guessing:
|
|
108
|
-
|
|
109
|
-
- **Monogram:** the first letter from the wordmark outlines, with the same modification, sitting in a deliberate bounding shape (or none).
|
|
110
|
-
- **Symbol:** built from the concept's motif with primitives (`circle`, `rect`, `line`, arcs) on a 24 or 48 unit grid. Whole or half units only. One stroke weight or one fill, never both.
|
|
111
|
-
- **Test** at 16, 32 and 512px, on the darkest and lightest surface in the palette. If it turns to mush at 16px, simplify.
|
|
112
|
-
|
|
113
|
-
Build it so it can move. Strokes (not fills) can be drawn on with DrawSVG for the `draw-mark-loader`; a mark built from the same number of points as a second shape can morph into it for `logo-morph` ([svg-motion.md](svg-motion.md), [showcase.md](showcase.md)).
|
|
114
|
-
|
|
115
|
-
## 5. The file set
|
|
116
|
-
|
|
117
|
-
Write these, wired into the framework's conventions (Next.js App Router shown):
|
|
118
|
-
|
|
119
|
-
| File | What |
|
|
120
|
-
|---|---|
|
|
121
|
-
| `brand/logo.svg` | Wordmark, outlined, `currentColor` |
|
|
122
|
-
| `brand/mark.svg` | Mark, outlined, `currentColor` |
|
|
123
|
-
| `components/brand/Logo.tsx` | Both inlined as components with a `title` for accessibility, so they can animate and take color |
|
|
124
|
-
| `app/icon.svg` | The mark on a solid surface tile; check it at 16px |
|
|
125
|
-
| `app/apple-icon.png` | 180 by 180, the mark on the surface color with padding |
|
|
126
|
-
| `app/opengraph-image.tsx` | Owned by [seo.md](seo.md) §3, and built in the SEO workflow step once the routes exist. The brand supplies the wordmark, surface color and display face it uses. |
|
|
127
|
-
|
|
128
|
-
Vite: the same SVGs in `public/` and `<link rel="icon" type="image/svg+xml">`; the static OG PNGs are covered in [seo.md](seo.md) §7.
|
|
129
|
-
|
|
130
|
-
## 6. Voice
|
|
131
|
-
|
|
132
|
-
One line in STRUQ.md that every piece of copy is checked against: register, sentence length, what it never does. For example: "plain and certain; short sentences; numbers instead of adjectives; never exclamation marks, never 'unlock' or 'elevate'." The copy rules in [page-architecture.md](page-architecture.md) §5 apply on top.
|
|
133
|
-
|
|
134
|
-
## Tells
|
|
135
|
-
|
|
136
|
-
- A name chosen before the positioning exists
|
|
137
|
-
- Twelve candidates that are one idea in twelve spellings
|
|
138
|
-
- A wordmark in a different family from the site's display face, for no reason
|
|
139
|
-
- Three modifications on one wordmark
|
|
140
|
-
- A mark that is a generic icon (a leaf, a lightbulb, a rocket, an abstract swoosh, a hexagon network)
|
|
141
|
-
- A gradient logo, a logo that only works on one background
|
|
142
|
-
- A favicon that is the full wordmark at 16px
|
|
143
|
-
- Claiming a name is "available" without having checked
|