cinematic-scroll-skill 2.1.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/COMPATIBILITY.md +244 -0
- package/LICENSE +21 -0
- package/MODELS.md +92 -0
- package/README.md +250 -0
- package/SKILL.md +1003 -0
- package/audit-mode.md +497 -0
- package/bin/install.mjs +91 -0
- package/compile-choreography.mjs +296 -0
- package/decision-log.md +241 -0
- package/examples/GETTING_STARTED.md +279 -0
- package/examples/KNOWN_ISSUES.md +50 -0
- package/examples/PROMPTS.md +166 -0
- package/examples/luxe/README.md +88 -0
- package/examples/luxe/index.html +662 -0
- package/examples/noir/README.md +72 -0
- package/examples/noir/index.html +634 -0
- package/examples/pop/README.md +81 -0
- package/examples/pop/index.html +711 -0
- package/examples/renaissance/README.md +39 -0
- package/examples/renaissance/index.html +648 -0
- package/examples/studio/README.md +77 -0
- package/examples/studio/chapters.js +105 -0
- package/examples/studio/index.html +520 -0
- package/manifest.json +92 -0
- package/manifest.md +136 -0
- package/package.json +56 -0
- package/references/film-archetypes.md +211 -0
- package/references/performance-budget.md +499 -0
- package/references/scroll-patterns.md +693 -0
- package/scroll-choreography-compilation.md +543 -0
- package/scroll-choreography.json +1512 -0
- package/taste-guardrails.md +164 -0
- package/templates/nextjs/.env.example +41 -0
- package/templates/nextjs/app/api/fal/proxy/route.ts +33 -0
- package/templates/nextjs/app/api/fal/webhook/route.ts +132 -0
- package/templates/nextjs/app/api/generate-edition-asset/route.ts +66 -0
- package/templates/nextjs/app/globals.css +80 -0
- package/templates/nextjs/app/layout.tsx +21 -0
- package/templates/nextjs/app/page.tsx +10 -0
- package/templates/nextjs/components/ChapterDemoVisual.tsx +212 -0
- package/templates/nextjs/components/ChapterScene.tsx +373 -0
- package/templates/nextjs/components/EditionsPage.tsx +116 -0
- package/templates/nextjs/components/SmoothScrollProvider.tsx +8 -0
- package/templates/nextjs/lib/api-guard.ts +110 -0
- package/templates/nextjs/lib/editions-manifest.ts +224 -0
- package/templates/nextjs/lib/fal-client.ts +12 -0
- package/templates/nextjs/lib/fal-generate.ts +86 -0
- package/templates/nextjs/lib/fal-models.ts +213 -0
- package/templates/nextjs/lib/prompt-contract.ts +97 -0
- package/templates/nextjs/lib/use-device.ts +42 -0
- package/templates/nextjs/lib/use-lenis.ts +35 -0
- package/templates/nextjs/next.config.ts +29 -0
- package/templates/nextjs/package-lock.json +6455 -0
- package/templates/nextjs/package.json +41 -0
- package/templates/nextjs/package.patch.json +28 -0
- package/templates/nextjs/postcss.config.js +6 -0
- package/templates/nextjs/scripts/generate-chapter-assets.mjs +243 -0
- package/templates/nextjs/scripts/setup.mjs +170 -0
- package/templates/nextjs/tailwind.config.ts +37 -0
- package/templates/nextjs/tsconfig.json +23 -0
- package/troubleshooting.md +1284 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
|
+
<title>Classic Touch — The Renaissance Edition · cinematic-scroll example</title>
|
|
7
|
+
<meta name="description" content="A self-contained, build-free Renaissance cinematic-scroll page — the flagship example for the cinematic-scroll Agent Skill. Mirrors w230.net/reinassence with zero framework." />
|
|
8
|
+
|
|
9
|
+
<!-- Open Graph / preview -->
|
|
10
|
+
<meta property="og:title" content="Classic Touch — The Renaissance Edition" />
|
|
11
|
+
<meta property="og:description" content="Eight chapters on the firm that builds the taste layer for agentic AI. Built with the cinematic-scroll skill — vanilla, no build step." />
|
|
12
|
+
<meta property="og:type" content="website" />
|
|
13
|
+
|
|
14
|
+
<link rel="preload" as="image" href="./assets/0-prologue.jpg" />
|
|
15
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
16
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
17
|
+
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500;1,600&family=EB+Garamond:ital@0;1&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet" />
|
|
18
|
+
|
|
19
|
+
<!-- GSAP + ScrollTrigger (now 100% free) — powers the dedicated "The Unveiling" showcase:
|
|
20
|
+
a GOD-SHOT / OVERHEAD pull-back (taste-guardrails §2) + a SCRUBBED SVG FLOURISH
|
|
21
|
+
(an illuminated gold rule drawn via stroke-dashoffset, pattern #2). Deferred +
|
|
22
|
+
feature-detected: if the CDN fails, the hand-rolled rAF engine below still runs and
|
|
23
|
+
the beat stays at its complete static state. -->
|
|
24
|
+
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/gsap.min.js"></script>
|
|
25
|
+
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/ScrollTrigger.min.js"></script>
|
|
26
|
+
|
|
27
|
+
<style>
|
|
28
|
+
/* ───────────────────────────────────────────────────────────────
|
|
29
|
+
cinematic-scroll · Renaissance example
|
|
30
|
+
Single-file, no build, GitHub-Pages friendly.
|
|
31
|
+
Motion grammar = the skill's vanilla fallback:
|
|
32
|
+
sticky pin + rAF-throttled scroll + depth-multiplied parallax
|
|
33
|
+
+ IntersectionObserver scroll-spy + background morph.
|
|
34
|
+
Reduced-motion + touch + mobile all degrade gracefully.
|
|
35
|
+
─────────────────────────────────────────────────────────────── */
|
|
36
|
+
|
|
37
|
+
:root {
|
|
38
|
+
--paper: #F5EFE3;
|
|
39
|
+
--paper-2: #FAF6EC;
|
|
40
|
+
--ink: #2C2A26;
|
|
41
|
+
--body: #4A463D;
|
|
42
|
+
--muted: #8B8579;
|
|
43
|
+
--gold: #B8893A;
|
|
44
|
+
--oxblood: #6B2C2C;
|
|
45
|
+
--oxblood-2:#8F4040;
|
|
46
|
+
--sage: #6f7a5a;
|
|
47
|
+
--accent: var(--gold);
|
|
48
|
+
|
|
49
|
+
--serif: "EB Garamond", Georgia, "Times New Roman", serif;
|
|
50
|
+
--display: "Cormorant Garamond", Georgia, serif;
|
|
51
|
+
--mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
55
|
+
html { scroll-behavior: smooth; }
|
|
56
|
+
body {
|
|
57
|
+
background: var(--paper);
|
|
58
|
+
color: var(--ink);
|
|
59
|
+
font-family: var(--serif);
|
|
60
|
+
line-height: 1.6;
|
|
61
|
+
-webkit-font-smoothing: antialiased;
|
|
62
|
+
overflow-x: hidden;
|
|
63
|
+
}
|
|
64
|
+
a { color: inherit; }
|
|
65
|
+
img { display: block; max-width: 100%; }
|
|
66
|
+
|
|
67
|
+
/* ── Fixed atmosphere layers ─────────────────────────────────── */
|
|
68
|
+
.morph {
|
|
69
|
+
position: fixed; inset: 0; z-index: -2;
|
|
70
|
+
background: radial-gradient(120% 80% at 20% 10%, rgba(184,137,58,0.10), rgba(184,137,58,0) 60%),
|
|
71
|
+
radial-gradient(90% 70% at 90% 90%, rgba(107,44,44,0.08), rgba(107,44,44,0) 62%),
|
|
72
|
+
var(--paper);
|
|
73
|
+
transition: background 1.1s cubic-bezier(0.22,1,0.36,1);
|
|
74
|
+
}
|
|
75
|
+
/* Paper grain (inline SVG noise) */
|
|
76
|
+
.grain {
|
|
77
|
+
position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: 0.5;
|
|
78
|
+
mix-blend-mode: multiply;
|
|
79
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ── Masthead ────────────────────────────────────────────────── */
|
|
83
|
+
.masthead {
|
|
84
|
+
position: fixed; top: 0; left: 0; right: 0; z-index: 50;
|
|
85
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
86
|
+
padding: clamp(14px, 2.4vw, 26px) clamp(18px, 4vw, 48px);
|
|
87
|
+
padding-top: max(clamp(14px,2.4vw,26px), env(safe-area-inset-top));
|
|
88
|
+
background: linear-gradient(to bottom, var(--paper) 14%, rgba(245,239,227,0.72) 70%, rgba(245,239,227,0));
|
|
89
|
+
backdrop-filter: blur(4px);
|
|
90
|
+
}
|
|
91
|
+
.masthead .mark { display: flex; align-items: baseline; gap: 14px; }
|
|
92
|
+
.masthead .mark b {
|
|
93
|
+
font-family: var(--display); font-weight: 600; font-size: clamp(16px, 2.2vw, 22px);
|
|
94
|
+
letter-spacing: -0.01em; color: var(--ink);
|
|
95
|
+
}
|
|
96
|
+
.masthead .mark span {
|
|
97
|
+
font-family: var(--mono); font-size: 9px; letter-spacing: 0.32em; text-transform: uppercase;
|
|
98
|
+
color: var(--gold);
|
|
99
|
+
}
|
|
100
|
+
.masthead nav { display: flex; gap: clamp(12px, 2vw, 26px); }
|
|
101
|
+
.masthead nav a {
|
|
102
|
+
font-family: var(--mono); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase;
|
|
103
|
+
color: var(--muted); text-decoration: none; transition: color 0.3s;
|
|
104
|
+
}
|
|
105
|
+
.masthead nav a:hover { color: var(--ink); }
|
|
106
|
+
@media (max-width: 720px){ .masthead nav { display: none; } }
|
|
107
|
+
|
|
108
|
+
/* ── Right-rail chapter index ────────────────────────────────── */
|
|
109
|
+
.rail {
|
|
110
|
+
position: fixed; top: 50%; right: clamp(14px, 2.6vw, 34px); transform: translateY(-50%);
|
|
111
|
+
z-index: 40; display: flex; flex-direction: column; gap: 13px;
|
|
112
|
+
padding-right: env(safe-area-inset-right);
|
|
113
|
+
}
|
|
114
|
+
.rail button {
|
|
115
|
+
display: flex; align-items: center; gap: 10px; justify-content: flex-end;
|
|
116
|
+
background: none; border: none; cursor: pointer; color: var(--muted);
|
|
117
|
+
font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
|
|
118
|
+
transition: color 0.3s; padding: 2px 0;
|
|
119
|
+
}
|
|
120
|
+
.rail button .nm { opacity: 0; transform: translateX(6px); transition: opacity 0.3s, transform 0.3s; max-width: 0; overflow: hidden; white-space: nowrap; }
|
|
121
|
+
.rail:hover button .nm { opacity: 0.8; transform: translateX(0); max-width: 160px; }
|
|
122
|
+
.rail button .tick { width: 20px; height: 1px; background: currentColor; opacity: 0.5; transition: width 0.35s, opacity 0.35s; flex: none; }
|
|
123
|
+
.rail button[aria-current="true"] { color: var(--ink); }
|
|
124
|
+
.rail button[aria-current="true"] .tick { width: 36px; opacity: 1; background: var(--accent); }
|
|
125
|
+
.rail button[aria-current="true"] .nm { opacity: 1; transform: translateX(0); max-width: 160px; }
|
|
126
|
+
@media (max-width: 860px){ .rail { display: none; } }
|
|
127
|
+
|
|
128
|
+
/* ── Chapters ────────────────────────────────────────────────── */
|
|
129
|
+
.chapter { position: relative; height: 220vh; }
|
|
130
|
+
.chapter .stage {
|
|
131
|
+
position: sticky; top: 0; height: 100vh; overflow: hidden;
|
|
132
|
+
display: grid; align-items: center;
|
|
133
|
+
grid-template-columns: 1fr;
|
|
134
|
+
}
|
|
135
|
+
.stage .inner {
|
|
136
|
+
width: min(1180px, 92vw); margin: 0 auto;
|
|
137
|
+
display: grid; grid-template-columns: 1.02fr 0.98fr; gap: clamp(28px, 5vw, 72px);
|
|
138
|
+
align-items: center;
|
|
139
|
+
padding: 0 clamp(8px, 2vw, 24px);
|
|
140
|
+
}
|
|
141
|
+
.chapter[data-side="left"] .inner { grid-template-columns: 0.98fr 1.02fr; }
|
|
142
|
+
.chapter[data-side="left"] .copy { order: 2; }
|
|
143
|
+
.chapter[data-side="left"] .figure { order: 1; }
|
|
144
|
+
|
|
145
|
+
/* layers */
|
|
146
|
+
.layer { will-change: transform, opacity; }
|
|
147
|
+
|
|
148
|
+
/* copy column */
|
|
149
|
+
.copy .eyebrow {
|
|
150
|
+
font-family: var(--mono); font-size: 10px; letter-spacing: 0.34em; text-transform: uppercase;
|
|
151
|
+
color: var(--accent); margin-bottom: clamp(16px, 2.4vw, 26px); display: block;
|
|
152
|
+
}
|
|
153
|
+
.copy h2 {
|
|
154
|
+
font-family: var(--display); font-weight: 500; font-style: italic;
|
|
155
|
+
font-size: clamp(2.3rem, 5.2vw, 4.4rem); line-height: 1.02; letter-spacing: -0.01em;
|
|
156
|
+
color: var(--ink); margin-bottom: clamp(18px, 2.4vw, 26px);
|
|
157
|
+
}
|
|
158
|
+
.copy h2 .w { display: inline-block; }
|
|
159
|
+
.copy h2 .accent { color: var(--oxblood); }
|
|
160
|
+
.copy .rule { height: 1px; width: 128px; background: linear-gradient(to right, var(--accent), rgba(184,137,58,0)); margin: 4px 0 22px; }
|
|
161
|
+
.copy p.lede { font-family: var(--serif); font-size: clamp(1.02rem, 1.5vw, 1.22rem); line-height: 1.66; color: var(--body); max-width: 46ch; }
|
|
162
|
+
.copy .meta { margin-top: 18px; font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--muted); }
|
|
163
|
+
.copy .cta {
|
|
164
|
+
margin-top: clamp(24px, 3vw, 38px); display: inline-flex; align-items: center; gap: 12px;
|
|
165
|
+
padding: 13px 26px; border-radius: 999px; background: var(--ink); color: var(--paper);
|
|
166
|
+
font-family: var(--mono); font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
|
|
167
|
+
text-decoration: none; transition: transform 0.3s;
|
|
168
|
+
}
|
|
169
|
+
.copy .cta:hover { transform: scale(1.03); }
|
|
170
|
+
|
|
171
|
+
/* figure column */
|
|
172
|
+
.figure { position: relative; }
|
|
173
|
+
.frame {
|
|
174
|
+
position: relative; width: 100%; max-width: 460px; margin-inline: auto;
|
|
175
|
+
aspect-ratio: 4 / 5; perspective: 1200px;
|
|
176
|
+
}
|
|
177
|
+
.chapter[data-side="left"] .frame { margin-left: auto; }
|
|
178
|
+
.frame .pic {
|
|
179
|
+
position: relative; width: 100%; height: 100%; transform-style: preserve-3d;
|
|
180
|
+
transition: transform 0.18s ease-out;
|
|
181
|
+
}
|
|
182
|
+
.frame img {
|
|
183
|
+
width: 100%; height: 100%; object-fit: cover; object-position: center;
|
|
184
|
+
border-radius: 2px; box-shadow: 0 30px 60px -28px rgba(44,42,38,0.55);
|
|
185
|
+
}
|
|
186
|
+
.frame .gild { position: absolute; inset: -10px; pointer-events: none;
|
|
187
|
+
background:
|
|
188
|
+
linear-gradient(135deg, rgba(184,137,58,0.5) 0, rgba(184,137,58,0) 13%),
|
|
189
|
+
linear-gradient(225deg, rgba(184,137,58,0.5) 0, rgba(184,137,58,0) 13%),
|
|
190
|
+
linear-gradient(315deg, rgba(184,137,58,0.5) 0, rgba(184,137,58,0) 13%),
|
|
191
|
+
linear-gradient(45deg, rgba(184,137,58,0.5) 0, rgba(184,137,58,0) 13%);
|
|
192
|
+
}
|
|
193
|
+
.frame .corner { position: absolute; width: 38px; height: 38px; border-color: var(--gold); opacity: 0.85; }
|
|
194
|
+
.frame .corner.tl { top: -12px; left: -12px; border-top: 2px solid; border-left: 2px solid; }
|
|
195
|
+
.frame .corner.br { bottom: -12px; right: -12px; border-bottom: 2px solid; border-right: 2px solid; }
|
|
196
|
+
|
|
197
|
+
/* floating mono "AI" card — the modern contrast layer */
|
|
198
|
+
.aicard {
|
|
199
|
+
position: absolute; z-index: 5; left: -28px; top: 38%;
|
|
200
|
+
background: rgba(250,246,236,0.9); backdrop-filter: blur(8px);
|
|
201
|
+
border: 1px solid rgba(184,137,58,0.32); border-radius: 2px;
|
|
202
|
+
padding: 13px 15px; max-width: 188px; box-shadow: 0 16px 30px -16px rgba(44,42,38,0.5);
|
|
203
|
+
font-family: var(--mono); font-size: 9px; color: var(--ink);
|
|
204
|
+
}
|
|
205
|
+
.chapter[data-side="left"] .aicard { left: auto; right: -28px; }
|
|
206
|
+
.aicard .tag { color: var(--oxblood); font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
|
|
207
|
+
.aicard .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--gold); animation: pulse 2.2s infinite; }
|
|
208
|
+
.aicard p { opacity: 0.82; line-height: 1.55; }
|
|
209
|
+
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.35} }
|
|
210
|
+
|
|
211
|
+
/* fig caption */
|
|
212
|
+
.figcap { margin-top: 14px; display: flex; align-items: baseline; gap: 12px;
|
|
213
|
+
font-family: var(--mono); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--muted); }
|
|
214
|
+
.figcap .n { color: var(--gold); }
|
|
215
|
+
.figcap .ln { height: 1px; flex: 1; background: currentColor; opacity: 0.3; }
|
|
216
|
+
|
|
217
|
+
/* scroll cue (prologue only) */
|
|
218
|
+
.cue { position: absolute; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 30;
|
|
219
|
+
display: flex; flex-direction: column; align-items: center; gap: 8px; color: var(--muted);
|
|
220
|
+
font-family: var(--mono); font-size: 9px; letter-spacing: 0.28em; text-transform: uppercase; }
|
|
221
|
+
.cue .bar { width: 1px; height: 40px; background: currentColor; opacity: 0.45; transform-origin: top; animation: cuebar 2.4s infinite ease-in-out; }
|
|
222
|
+
@keyframes cuebar { 0%,100%{transform:scaleY(1)} 50%{transform:scaleY(0.35)} }
|
|
223
|
+
|
|
224
|
+
/* colophon */
|
|
225
|
+
.colophon { position: relative; padding: clamp(60px,12vh,140px) 24px; text-align: center; }
|
|
226
|
+
.colophon .vine { width: 120px; height: 1px; margin: 0 auto 28px; background: linear-gradient(to right, transparent, var(--gold), transparent); }
|
|
227
|
+
.colophon h3 { font-family: var(--display); font-style: italic; font-weight: 500; font-size: clamp(1.6rem, 3.4vw, 2.6rem); color: var(--ink); margin-bottom: 14px; }
|
|
228
|
+
.colophon p { font-family: var(--serif); color: var(--body); max-width: 52ch; margin: 0 auto 10px; }
|
|
229
|
+
.colophon .src { font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--muted); margin-top: 22px; }
|
|
230
|
+
.colophon .src a { color: var(--oxblood); text-decoration: none; border-bottom: 1px solid rgba(107,44,44,0.3); }
|
|
231
|
+
|
|
232
|
+
/* ── Mobile: no pin, stacked, fade-up ───────────────────────── */
|
|
233
|
+
@media (max-width: 860px) {
|
|
234
|
+
.chapter { height: auto; }
|
|
235
|
+
.chapter .stage { position: relative; height: auto; min-height: 92vh; padding: clamp(70px,12vh,110px) 0; }
|
|
236
|
+
.stage .inner { grid-template-columns: 1fr; gap: 30px; width: 90vw; }
|
|
237
|
+
.chapter[data-side="left"] .copy,
|
|
238
|
+
.chapter[data-side="left"] .figure { order: initial; }
|
|
239
|
+
.figure { order: 2 !important; } .copy { order: 1 !important; }
|
|
240
|
+
.layer { transform: none !important; }
|
|
241
|
+
.aicard { position: relative; left: 0 !important; right: auto !important; top: 0; margin: 18px auto 0; }
|
|
242
|
+
.frame { perspective: none; }
|
|
243
|
+
.reveal { opacity: 0; transform: translateY(26px); transition: opacity 0.8s ease, transform 0.8s cubic-bezier(0.16,1,0.3,1); }
|
|
244
|
+
.reveal.in { opacity: 1; transform: none; }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* ── Reduced motion: snap to readable mid-state ─────────────── */
|
|
248
|
+
@media (prefers-reduced-motion: reduce) {
|
|
249
|
+
html { scroll-behavior: auto; }
|
|
250
|
+
.chapter { height: auto; }
|
|
251
|
+
.chapter .stage { position: relative; height: auto; min-height: 90vh; padding: 100px 0; }
|
|
252
|
+
.stage .inner { grid-template-columns: 1fr; }
|
|
253
|
+
.layer { transform: none !important; opacity: 1 !important; }
|
|
254
|
+
.copy h2 .w { opacity: 1 !important; transform: none !important; }
|
|
255
|
+
.cue .bar, .aicard .dot { animation: none; }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ── GSAP SHOWCASE — "THE UNVEILING" ────────────────────────────────
|
|
259
|
+
A dedicated viewport demonstrating two techniques the hand-rolled
|
|
260
|
+
engine doesn't: a GOD-SHOT / OVERHEAD pull-back (taste-guardrails §2 —
|
|
261
|
+
starts tight on a detail then pulls back, scale 1.5→1 + translateY 20%→0)
|
|
262
|
+
and a SCRUBBED SVG FLOURISH (an illuminated-manuscript gold rule that
|
|
263
|
+
draws via stroke-dashoffset, pattern #2). GSAP pins .unveil-stage; the
|
|
264
|
+
STATIC DEFAULTS below keep the beat whole when GSAP is absent / reduced-
|
|
265
|
+
motion / mobile (plate at scale 1, flourish fully drawn — no dasharray). */
|
|
266
|
+
.chapter.showcase { height: auto; } /* GSAP pin owns the scroll room */
|
|
267
|
+
.unveil-stage { position: relative; height: 100vh; overflow: hidden;
|
|
268
|
+
display: grid; place-items: center;
|
|
269
|
+
background: radial-gradient(120% 90% at 50% 12%, rgba(184,137,58,0.12), rgba(184,137,58,0) 60%),
|
|
270
|
+
radial-gradient(90% 70% at 50% 100%, rgba(107,44,44,0.08), rgba(107,44,44,0) 64%); }
|
|
271
|
+
/* the "plate" the god-shot pulls back from — illuminated initial + leaf border */
|
|
272
|
+
.unveil-plate { position: relative; z-index: 2; width: min(78vw, 560px);
|
|
273
|
+
transform-origin: 50% 30%; will-change: transform; /* god-shot pivot */
|
|
274
|
+
text-align: center; padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 44px); }
|
|
275
|
+
.unveil-plate .initial {
|
|
276
|
+
font-family: var(--display); font-weight: 600; font-style: italic;
|
|
277
|
+
font-size: clamp(7rem, 22vw, 14rem); line-height: 0.8; color: var(--oxblood);
|
|
278
|
+
letter-spacing: -0.02em; }
|
|
279
|
+
.unveil-plate .gloss {
|
|
280
|
+
font-family: var(--display); font-style: italic; font-weight: 500;
|
|
281
|
+
font-size: clamp(1.5rem, 4vw, 2.8rem); line-height: 1.04; color: var(--ink);
|
|
282
|
+
margin-top: clamp(10px, 1.6vw, 18px); }
|
|
283
|
+
.unveil-plate .gloss em { color: var(--oxblood); font-style: italic; }
|
|
284
|
+
/* the scrubbed flourish — fully drawn by default (no dasharray set in CSS) */
|
|
285
|
+
.unveil-flourish { display: block; width: min(72vw, 520px); height: 64px;
|
|
286
|
+
margin: clamp(14px, 2.2vw, 26px) auto 0; overflow: visible; }
|
|
287
|
+
.unveil-flourish path { fill: none; stroke: var(--gold); stroke-width: 1.6;
|
|
288
|
+
stroke-linecap: round; stroke-linejoin: round; }
|
|
289
|
+
.unveil-cap { position: absolute; z-index: 3; bottom: clamp(28px, 8vh, 80px);
|
|
290
|
+
left: 0; right: 0; text-align: center; }
|
|
291
|
+
.unveil-cap .eyebrow {
|
|
292
|
+
font-family: var(--mono); font-size: 10px; letter-spacing: 0.34em;
|
|
293
|
+
text-transform: uppercase; color: var(--gold); display: block; margin-bottom: 8px; }
|
|
294
|
+
.unveil-cap span:last-child {
|
|
295
|
+
font-family: var(--serif); font-style: italic; font-size: clamp(0.95rem, 1.6vw, 1.15rem);
|
|
296
|
+
color: var(--body); }
|
|
297
|
+
@media (max-width: 860px) {
|
|
298
|
+
.unveil-stage { height: auto; padding: clamp(70px,12vh,110px) 22px; }
|
|
299
|
+
.unveil-plate { transform: none !important; width: 100%; }
|
|
300
|
+
.unveil-cap { position: relative; bottom: auto; margin-top: 30px; }
|
|
301
|
+
}
|
|
302
|
+
@media (prefers-reduced-motion: reduce) {
|
|
303
|
+
.unveil-stage { height: auto; padding: 100px 22px; }
|
|
304
|
+
.unveil-plate { transform: none !important; }
|
|
305
|
+
.unveil-cap { position: relative; bottom: auto; margin-top: 30px; }
|
|
306
|
+
}
|
|
307
|
+
</style>
|
|
308
|
+
</head>
|
|
309
|
+
<body>
|
|
310
|
+
<div class="morph" id="morph" aria-hidden="true"></div>
|
|
311
|
+
<div class="grain" aria-hidden="true"></div>
|
|
312
|
+
|
|
313
|
+
<header class="masthead">
|
|
314
|
+
<div class="mark">
|
|
315
|
+
<b>Classic Touch</b>
|
|
316
|
+
<span>The Renaissance Edition</span>
|
|
317
|
+
</div>
|
|
318
|
+
<nav>
|
|
319
|
+
<a href="#manifesto">Treatise</a>
|
|
320
|
+
<a href="#commissions">Commissions</a>
|
|
321
|
+
<a href="#audience">Request an audience</a>
|
|
322
|
+
</nav>
|
|
323
|
+
</header>
|
|
324
|
+
|
|
325
|
+
<nav class="rail" id="rail" aria-label="Chapters"></nav>
|
|
326
|
+
|
|
327
|
+
<main id="book"></main>
|
|
328
|
+
|
|
329
|
+
<footer class="colophon">
|
|
330
|
+
<div class="vine"></div>
|
|
331
|
+
<h3>The catalogue is a side-effect of the work.</h3>
|
|
332
|
+
<p>Eight chapters on the firm that builds the taste layer for agentic AI — rendered here as a single, build-free HTML page.</p>
|
|
333
|
+
<p class="src">
|
|
334
|
+
Built with the <a href="../../README.md">cinematic-scroll</a> skill · Live React/Next.js edition:
|
|
335
|
+
<a href="https://www.w230.net/reinassence">w230.net/reinassence</a>
|
|
336
|
+
</p>
|
|
337
|
+
</footer>
|
|
338
|
+
|
|
339
|
+
<script>
|
|
340
|
+
/* ───────────────────────────────────────────────────────────────
|
|
341
|
+
Manifest — drive the page from data, exactly as the skill teaches.
|
|
342
|
+
Edit this array to retheme; the DOM + motion are generated from it.
|
|
343
|
+
─────────────────────────────────────────────────────────────── */
|
|
344
|
+
const CHAPTERS = [
|
|
345
|
+
{ id:'prologue', roman:'0', it:'Prologo', en:'Prologue',
|
|
346
|
+
eyebrow:'Prologue · MMXXVI', accent:'gold', img:'0-prologue.jpg', fig:'0.1', figLabel:'Frontispiece',
|
|
347
|
+
title:[['A',0],['new',0],['world',0],['of',0],['commerce',0],['of',1],['taste.',1]],
|
|
348
|
+
lede:'Eight chapters on the firm that builds the taste layer for agentic AI — the house rules, evaluation constraints, and curation logic that let autonomous systems act on behalf of brands that cannot afford a wrong word.',
|
|
349
|
+
card:{ tag:'/w230-system', body:'Judgment harness online. Core constitution compiled with 0 issues.' },
|
|
350
|
+
cue:true },
|
|
351
|
+
{ id:'manifesto', roman:'I', it:'Manifesto', en:'Manifesto',
|
|
352
|
+
eyebrow:'Chapter I', accent:'gold', img:'1-manifesto.jpg', fig:'1.1', figLabel:'The thesis',
|
|
353
|
+
title:[['House',0],['rules,',0],['not',1],['guardrails.',1]],
|
|
354
|
+
lede:'Why autonomous systems need a constitution, not a fence. Guardrails say no at the edge; house rules encode judgment at the centre — versioned, observable, and arguable.',
|
|
355
|
+
card:{ tag:'/constitution', body:'42 rules compiled. Plan-time + action-time hooks live. Rationale attached to every verdict.' } },
|
|
356
|
+
{ id:'atelier', roman:'II', it:'Atelier', en:'Atelier',
|
|
357
|
+
eyebrow:'Chapter II', accent:'gold', img:'2-atelier.jpg', fig:'2.1', figLabel:'The three bays of the workshop',
|
|
358
|
+
title:[['The',0],['workshop.',0]],
|
|
359
|
+
lede:'MCP servers, skill packs, agent templates — the instruments the apprentices reach for first. The instruments are the catalogue; the catalogue is a side-effect of the work.',
|
|
360
|
+
card:{ tag:'/product-sync', body:'Status: Active · Synced 42 objects · Dry-run verified across 1,200 instances.' } },
|
|
361
|
+
{ id:'commissions', roman:'III', it:'Commissioni', en:'Commissions',
|
|
362
|
+
eyebrow:'Chapter III', accent:'gold', img:'3-commissions.jpg', fig:'3.1', figLabel:'Six panels, anonymised',
|
|
363
|
+
title:[['Six',0],['case',0],['studies',1],['under',1],['NDA.',1]],
|
|
364
|
+
lede:'Each commission a panel: a brand that could not afford a wrong word, an autonomous system taught to earn the right to say it. Names redacted; patterns intact.',
|
|
365
|
+
card:{ tag:'/commission-07', body:'Veto agent rejected 3 of 200 drafts. Each rejection routed to the exception queue with rationale.' } },
|
|
366
|
+
{ id:'patrons', roman:'IV', it:'Mecenati', en:'Patrons',
|
|
367
|
+
eyebrow:'Chapter IV', accent:'oxblood', img:'4-patrons.jpg', fig:'4.1', figLabel:'The board of taste',
|
|
368
|
+
title:[['Where',0],['judgment',0],['was',1],['trained.',1]],
|
|
369
|
+
lede:'Every taste system is somebody’s taste, made legible. The patrons are where the judgment was sourced, scored, and turned into infrastructure the machines could consult.',
|
|
370
|
+
card:{ tag:'/taste-gate', body:'Source-class registry online. Trade-press blocked as primary in legal + financial verticals.' } },
|
|
371
|
+
{ id:'studies', roman:'V', it:'Studi', en:'Studies',
|
|
372
|
+
eyebrow:'Chapter V', accent:'gold', img:'5-studies.jpg', fig:'5.1', figLabel:'Essays in the margin',
|
|
373
|
+
title:[['Taste',0],['as',0],['infrastructure.',1]],
|
|
374
|
+
lede:'Essays on the unglamorous claim at the centre of the practice: that taste is not decoration but plumbing — a layer you build, version, and depend on like any other.',
|
|
375
|
+
card:{ tag:'/folio', body:'Seven essays compiled. Citations validated. Unsupported claims refused at synthesis time.' } },
|
|
376
|
+
{ id:'apprentices', roman:'VI', it:'Apprendisti', en:'Apprentices',
|
|
377
|
+
eyebrow:'Chapter VI', accent:'gold', img:'6-apprentices.jpg', fig:'6.1', figLabel:'Standalone builds',
|
|
378
|
+
title:[['What',0],['the',0],['workshop',1],['ships.',1]],
|
|
379
|
+
lede:'Standalone builds graduated from the atelier — MindGleam, AutoSplat, and what comes next. The apprentices leave with the house rules sewn into the lining.',
|
|
380
|
+
card:{ tag:'/mindgleam', body:'Build promoted to live after one supervised week. Rollback hooks armed; every action auditable.' } },
|
|
381
|
+
{ id:'audience', roman:'VII', it:'Udienza', en:'Audience',
|
|
382
|
+
eyebrow:'Chapter VII', accent:'oxblood', img:'7-audience.jpg', fig:'7.1', figLabel:'The petition',
|
|
383
|
+
title:[['Request',0],['an',0],['audience.',1]],
|
|
384
|
+
lede:'Six weeks from first conversation to a deployed, governed, observable system. Bring a brand that cannot afford a wrong word. We will teach the machine to earn it.',
|
|
385
|
+
card:{ tag:'/audience', body:'Queue open. Six weeks to deployment. One supervised week before any agent goes live.' },
|
|
386
|
+
cta:'Begin the petition' },
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const ACCENTS = {
|
|
390
|
+
gold: { c:'#B8893A', morph:'radial-gradient(120% 80% at 18% 8%, rgba(184,137,58,0.16), rgba(184,137,58,0) 58%), radial-gradient(90% 70% at 88% 92%, rgba(107,44,44,0.06), rgba(107,44,44,0) 62%), #F5EFE3' },
|
|
391
|
+
oxblood: { c:'#6B2C2C', morph:'radial-gradient(120% 80% at 20% 10%, rgba(107,44,44,0.18), rgba(107,44,44,0) 58%), radial-gradient(90% 70% at 86% 90%, rgba(184,137,58,0.08), rgba(184,137,58,0) 60%), #F1E9DC' },
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const clamp = (n,a,b)=>Math.max(a,Math.min(b,n));
|
|
395
|
+
const smooth = (a,b,t)=>{ const x=clamp((t-a)/(b-a),0,1); return x*x*(3-2*x); };
|
|
396
|
+
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
397
|
+
const isTouch = window.matchMedia('(hover: none) and (pointer: coarse)').matches;
|
|
398
|
+
const isMobile = () => window.matchMedia('(max-width: 860px)').matches;
|
|
399
|
+
|
|
400
|
+
/* ── Build chapters from the manifest ───────────────────────── */
|
|
401
|
+
const book = document.getElementById('book');
|
|
402
|
+
const rail = document.getElementById('rail');
|
|
403
|
+
|
|
404
|
+
CHAPTERS.forEach((ch, i) => {
|
|
405
|
+
const side = i % 2 === 0 ? 'right' : 'left';
|
|
406
|
+
const accentC = ACCENTS[ch.accent].c;
|
|
407
|
+
|
|
408
|
+
const titleHTML = ch.title.map(([w,acc]) =>
|
|
409
|
+
`<span class="w"${acc?' style="--ax:1"':''}>${acc?`<span class="accent">${w}</span>`:w}</span>`
|
|
410
|
+
).join(' ');
|
|
411
|
+
|
|
412
|
+
const sec = document.createElement('section');
|
|
413
|
+
sec.className = 'chapter';
|
|
414
|
+
sec.id = ch.id;
|
|
415
|
+
sec.dataset.side = side;
|
|
416
|
+
sec.dataset.index = i;
|
|
417
|
+
sec.style.setProperty('--accent', accentC);
|
|
418
|
+
sec.innerHTML = `
|
|
419
|
+
<div class="stage">
|
|
420
|
+
<div class="inner">
|
|
421
|
+
<div class="copy">
|
|
422
|
+
<div class="layer copytext" data-depth="1.0">
|
|
423
|
+
<span class="eyebrow reveal">${ch.eyebrow}</span>
|
|
424
|
+
<h2>${titleHTML}</h2>
|
|
425
|
+
<div class="rule reveal" style="background:linear-gradient(to right, ${accentC}, rgba(184,137,58,0))"></div>
|
|
426
|
+
<p class="lede reveal">${ch.lede}</p>
|
|
427
|
+
${ch.cta ? `<a class="cta reveal" href="https://www.w230.net/reinassence">${ch.cta} →</a>`
|
|
428
|
+
: `<p class="meta reveal">${ch.it} · ${ch.en}</p>`}
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
<div class="figure">
|
|
432
|
+
<div class="layer figwrap" data-depth="${side==='right'?0.62:0.66}">
|
|
433
|
+
<div class="frame">
|
|
434
|
+
<span class="corner tl"></span><span class="corner br"></span>
|
|
435
|
+
<div class="gild"></div>
|
|
436
|
+
<div class="pic">
|
|
437
|
+
<img src="./assets/${ch.img}" alt="${ch.en} — Renaissance scene with halftone pop-art artifacts"
|
|
438
|
+
loading="${i===0?'eager':'lazy'}" decoding="async" />
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
<div class="layer aicard" data-depth="1.32">
|
|
442
|
+
<div class="tag"><span class="dot"></span>${ch.card.tag}</div>
|
|
443
|
+
<p>${ch.card.body}</p>
|
|
444
|
+
</div>
|
|
445
|
+
<div class="figcap reveal">
|
|
446
|
+
<span class="n">Fig. ${ch.fig}</span><span class="ln"></span><span>${ch.figLabel}</span>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
${ch.cue ? `<button class="cue" type="button" data-goto="manifesto" aria-label="Scroll to Manifesto">
|
|
452
|
+
<span>Turn the page</span><span class="bar"></span></button>` : ''}
|
|
453
|
+
</div>`;
|
|
454
|
+
book.appendChild(sec);
|
|
455
|
+
|
|
456
|
+
const b = document.createElement('button');
|
|
457
|
+
b.type = 'button';
|
|
458
|
+
b.dataset.goto = ch.id;
|
|
459
|
+
b.style.setProperty('--accent', accentC);
|
|
460
|
+
b.innerHTML = `<span class="nm">${ch.it}</span><span style="font-family:var(--mono)">${ch.roman}</span><span class="tick"></span>`;
|
|
461
|
+
rail.appendChild(b);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
/* ── INSERT THE GSAP SHOWCASE BEAT ("THE UNVEILING") ────────────────
|
|
465
|
+
Placed right after the prologue (DOM/rail/CHAPTERS index 1) so it reads:
|
|
466
|
+
prologue → the unveiling → manifesto. The scroll-spy keys off
|
|
467
|
+
`+target.dataset.index` and then reads CHAPTERS[idx].accent / railBtns[idx],
|
|
468
|
+
so we splice a matching CHAPTERS entry AND a rail button at the same index,
|
|
469
|
+
then re-stamp every section's data-index — all BEFORE `sections`/`railBtns`
|
|
470
|
+
are collected, keeping the three arrays perfectly aligned. Pure markup here;
|
|
471
|
+
the god-shot + flourish motion is wired at the end (desktop-only, GSAP-gated). */
|
|
472
|
+
const showAccentC = ACCENTS.oxblood.c;
|
|
473
|
+
CHAPTERS.splice(1, 0, { id:'unveiling', roman:'·', it:'Lo Svelamento', en:'The Unveiling', accent:'oxblood' });
|
|
474
|
+
|
|
475
|
+
const show = document.createElement('section');
|
|
476
|
+
show.className = 'chapter showcase';
|
|
477
|
+
show.id = 'unveiling';
|
|
478
|
+
show.style.setProperty('--accent', showAccentC);
|
|
479
|
+
show.innerHTML = `
|
|
480
|
+
<div class="unveil-stage">
|
|
481
|
+
<div class="unveil-plate" aria-hidden="false">
|
|
482
|
+
<div class="initial">Q</div>
|
|
483
|
+
<div class="gloss">Every house rule begins as a <em>single illuminated letter.</em></div>
|
|
484
|
+
<!-- SCRUBBED SVG FLOURISH (pattern #2): an elegant gold rule drawn via
|
|
485
|
+
stroke-dashoffset; getTotalLength() read once on init, not per-frame. -->
|
|
486
|
+
<svg class="unveil-flourish" viewBox="0 0 520 64" preserveAspectRatio="xMidYMid meet" aria-hidden="true">
|
|
487
|
+
<path d="M10,32 C70,8 110,8 170,32 C210,50 230,50 260,18 C290,50 310,50 350,32 C410,8 450,8 510,32 M260,18 C260,10 254,6 246,6 M260,18 C260,10 266,6 274,6"/>
|
|
488
|
+
</svg>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="unveil-cap">
|
|
491
|
+
<span class="eyebrow">Interludio · The Unveiling</span>
|
|
492
|
+
<span>From a tight detail, the whole page draws back into view.</span>
|
|
493
|
+
</div>
|
|
494
|
+
</div>`;
|
|
495
|
+
book.insertBefore(show, book.children[1] || null); // after prologue (DOM index 0)
|
|
496
|
+
|
|
497
|
+
const showBtn = document.createElement('button');
|
|
498
|
+
showBtn.type = 'button';
|
|
499
|
+
showBtn.dataset.goto = 'unveiling';
|
|
500
|
+
showBtn.style.setProperty('--accent', showAccentC);
|
|
501
|
+
showBtn.innerHTML = `<span class="nm">Lo Svelamento</span><span style="font-family:var(--mono)">·</span><span class="tick"></span>`;
|
|
502
|
+
rail.insertBefore(showBtn, rail.children[1] || null);
|
|
503
|
+
|
|
504
|
+
/* re-stamp data-index across all chapters so the spy's idx stays aligned */
|
|
505
|
+
[...document.querySelectorAll('.chapter')].forEach((s, i) => { s.dataset.index = i; });
|
|
506
|
+
|
|
507
|
+
/* ── Navigation clicks ──────────────────────────────────────── */
|
|
508
|
+
document.querySelectorAll('[data-goto]').forEach(el => {
|
|
509
|
+
el.addEventListener('click', () => {
|
|
510
|
+
document.getElementById(el.dataset.goto)?.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth' });
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
/* ── Scroll-spy → active rail + background morph ─────────────── */
|
|
515
|
+
const sections = [...document.querySelectorAll('.chapter')];
|
|
516
|
+
const railBtns = [...rail.children];
|
|
517
|
+
const morph = document.getElementById('morph');
|
|
518
|
+
let activeIdx = -1;
|
|
519
|
+
|
|
520
|
+
const spy = new IntersectionObserver((entries) => {
|
|
521
|
+
entries.forEach(e => {
|
|
522
|
+
if (!e.isIntersecting) return;
|
|
523
|
+
const idx = +e.target.dataset.index;
|
|
524
|
+
if (idx === activeIdx) return;
|
|
525
|
+
activeIdx = idx;
|
|
526
|
+
railBtns.forEach((b,j) => b.setAttribute('aria-current', j===idx ? 'true':'false'));
|
|
527
|
+
morph.style.background = ACCENTS[CHAPTERS[idx].accent].morph;
|
|
528
|
+
document.body.style.setProperty('--accent', ACCENTS[CHAPTERS[idx].accent].c);
|
|
529
|
+
});
|
|
530
|
+
}, { threshold: 0.01, rootMargin: '-45% 0px -45% 0px' });
|
|
531
|
+
sections.forEach(s => spy.observe(s));
|
|
532
|
+
|
|
533
|
+
/* ── Mobile / reduced-motion: simple fade-up, no parallax ───── */
|
|
534
|
+
if (reduce || isMobile()) {
|
|
535
|
+
const up = new IntersectionObserver((entries) => {
|
|
536
|
+
entries.forEach(e => { if (e.isIntersecting){ e.target.classList.add('in'); up.unobserve(e.target);} });
|
|
537
|
+
}, { threshold: 0.18 });
|
|
538
|
+
document.querySelectorAll('.reveal').forEach(el => up.observe(el));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/* ── Desktop parallax engine — rAF-throttled, transform/opacity only ──
|
|
542
|
+
T = unified progress over a section's WHOLE pass (approach → pin → exit),
|
|
543
|
+
so content is fully composed when the chapter is centered — including the
|
|
544
|
+
first chapter at load (T ≈ 0.31). Never starts invisible. */
|
|
545
|
+
function parallax() {
|
|
546
|
+
if (reduce || isMobile()) return;
|
|
547
|
+
const vh = window.innerHeight;
|
|
548
|
+
for (const sec of sections) {
|
|
549
|
+
const rect = sec.getBoundingClientRect();
|
|
550
|
+
const total = sec.offsetHeight + vh;
|
|
551
|
+
const T = clamp((vh - rect.top) / total, 0, 1); // 0 just below → 1 fully past
|
|
552
|
+
const enter = smooth(0.08, 0.30, T); // composed by the time it pins
|
|
553
|
+
const exit = 1 - smooth(0.66, 0.84, T); // fades as it scrolls away
|
|
554
|
+
const live = enter * exit; // 0 → 1 → 0
|
|
555
|
+
const drift = 0.46 - T; // signed, ~0 at hold
|
|
556
|
+
|
|
557
|
+
// image / figure layer: subtle parallax + scale swell
|
|
558
|
+
const fig = sec.querySelector('.figwrap');
|
|
559
|
+
if (fig) {
|
|
560
|
+
const depth = +fig.dataset.depth;
|
|
561
|
+
const sc = 1.035 - 0.035 * enter;
|
|
562
|
+
fig.style.transform = `translateY(${drift * 150 * depth}px) scale(${sc.toFixed(3)})`;
|
|
563
|
+
fig.style.opacity = (0.12 + 0.88 * live).toFixed(3);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// copy / title block + per-word stagger
|
|
567
|
+
const copy = sec.querySelector('.copytext');
|
|
568
|
+
if (copy) {
|
|
569
|
+
copy.style.transform = `translateY(${(drift * 80).toFixed(1)}px)`;
|
|
570
|
+
copy.style.opacity = (0.08 + 0.92 * live).toFixed(3);
|
|
571
|
+
const words = copy.querySelectorAll('h2 .w');
|
|
572
|
+
const n = words.length;
|
|
573
|
+
words.forEach((w, k) => {
|
|
574
|
+
const t0 = 0.05 + (k / n) * 0.14; // staggered, all in by ~T=0.29
|
|
575
|
+
const wp = smooth(t0, t0 + 0.10, T);
|
|
576
|
+
w.style.opacity = wp.toFixed(3);
|
|
577
|
+
w.style.transform = `translateY(${((1 - wp) * 22).toFixed(1)}px)`;
|
|
578
|
+
w.style.letterSpacing = `${((1 - wp) * 0.18).toFixed(3)}em`;
|
|
579
|
+
});
|
|
580
|
+
copy.querySelectorAll('.reveal').forEach(r => { r.style.opacity = (smooth(0.10, 0.30, T) * exit).toFixed(3); });
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// foreground AI card — faster drift (depth > 1)
|
|
584
|
+
const card = sec.querySelector('.aicard');
|
|
585
|
+
if (card) {
|
|
586
|
+
const depth = +card.dataset.depth;
|
|
587
|
+
card.style.transform = `translateY(${(drift * 120 * (depth - 1) * 2.6).toFixed(1)}px)`;
|
|
588
|
+
card.style.opacity = (0.95 * smooth(0.16, 0.36, T) * (1 - smooth(0.62, 0.82, T))).toFixed(3);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
ticking = false;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
let ticking = false;
|
|
595
|
+
function onScroll(){ if (!ticking){ ticking = true; requestAnimationFrame(parallax); } }
|
|
596
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
597
|
+
window.addEventListener('resize', () => { ticking = true; requestAnimationFrame(parallax); }, { passive: true });
|
|
598
|
+
parallax(); // initial paint so nothing starts invisible if scroll never fires
|
|
599
|
+
|
|
600
|
+
/* ── 3D tilt on hero images (pointer, non-touch) ────────────── */
|
|
601
|
+
if (!isTouch && !reduce) {
|
|
602
|
+
document.querySelectorAll('.frame').forEach(frame => {
|
|
603
|
+
const pic = frame.querySelector('.pic');
|
|
604
|
+
frame.addEventListener('pointermove', (ev) => {
|
|
605
|
+
const r = frame.getBoundingClientRect();
|
|
606
|
+
const px = (ev.clientX - r.left) / r.width - 0.5;
|
|
607
|
+
const py = (ev.clientY - r.top) / r.height - 0.5;
|
|
608
|
+
pic.style.transform = `rotateY(${px * 8}deg) rotateX(${-py * 6}deg)`;
|
|
609
|
+
});
|
|
610
|
+
frame.addEventListener('pointerleave', () => { pic.style.transform = 'rotateY(0) rotateX(0)'; });
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* ── GSAP SHOWCASE WIRING — "THE UNVEILING" ─────────────────────────
|
|
615
|
+
Polls for GSAP (~120 rAF tries); if the CDN never loads, #unveiling stays
|
|
616
|
+
at its complete static state. matchMedia re-guards desktop + no-reduced-motion
|
|
617
|
+
so it auto-kills on resize into a small/reduced context. Two cinematic
|
|
618
|
+
techniques (taste-guardrails §2), each commented at its tween:
|
|
619
|
+
• GOD-SHOT / OVERHEAD pull-back — plate scale 1.5→1 + translateY 20%→0
|
|
620
|
+
• SCRUBBED SVG FLOURISH — the gold rule draws via stroke-dashoffset (pattern #2)
|
|
621
|
+
Only transform + opacity (+ stroke-dashoffset) are animated. */
|
|
622
|
+
(function wireShowcase(tries){
|
|
623
|
+
if (!window.gsap || !window.ScrollTrigger) {
|
|
624
|
+
if (tries > 0) requestAnimationFrame(() => wireShowcase(tries - 1));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
628
|
+
ScrollTrigger.defaults({ scrub: 0.5, fastScrollEnd: true, invalidateOnRefresh: true });
|
|
629
|
+
const mm = gsap.matchMedia();
|
|
630
|
+
mm.add('(min-width:769px) and (prefers-reduced-motion:no-preference)', () => {
|
|
631
|
+
const stage = document.querySelector('#unveiling .unveil-stage');
|
|
632
|
+
if (!stage) return;
|
|
633
|
+
const plate = stage.querySelector('.unveil-plate');
|
|
634
|
+
const flourish = stage.querySelector('.unveil-flourish path');
|
|
635
|
+
const cap = stage.querySelector('.unveil-cap');
|
|
636
|
+
const len = flourish.getTotalLength(); // read length once, on init
|
|
637
|
+
gsap.set(flourish, { strokeDasharray: len, strokeDashoffset: len, willChange: 'stroke-dashoffset' });
|
|
638
|
+
const tl = gsap.timeline({ scrollTrigger: {
|
|
639
|
+
trigger: '#unveiling', start: 'top top', end: '+=140%', pin: stage, anticipatePin: 1 } });
|
|
640
|
+
tl.fromTo(plate, { scale: 1.5, yPercent: 20 }, { scale: 1, yPercent: 0, ease: 'none' }, 0) // GOD-SHOT pull-back
|
|
641
|
+
.to(flourish, { strokeDashoffset: 0, ease: 'none' }, 0) // SCRUBBED SVG FLOURISH
|
|
642
|
+
.fromTo(cap, { opacity: 0, y: 18 }, { opacity: 1, y: 0, ease: 'power2.out', duration: 0.4 }, 0.1);
|
|
643
|
+
return () => gsap.set([plate, flourish, cap], { clearProps: 'all' });
|
|
644
|
+
});
|
|
645
|
+
})(120);
|
|
646
|
+
</script>
|
|
647
|
+
</body>
|
|
648
|
+
</html>
|