1/**
2 * Atelier Nord — Scandinavian fashion e-commerce.
3 *
4 * Monochrome high-fashion storefront: giant condensed type, an art-directed
5 * asymmetric grid and procedural garment "photography" (SVG silhouettes with
6 * fold shading, knit/cable/herringbone patterns and a CSS film-grain layer).
7 * No external images or fonts.
8 *
9 * Motion
10 * - Masthead: each letter of ATELIER / NORD scrubs on scroll at its own
11 * speed and tilt; the cover image drifts and scales underneath.
12 * - Zoom-parallax lookbook: a pinned stage starts zoomed into one image and
13 * pulls back until it settles into an asymmetric grid; tiles arrive at
14 * different depths.
15 * - Product cards: swatch changes cross-fade the fabric colour; "Quick add"
16 * morphs (clip-path) into a size picker, then into an "Added" state.
17 * - Cart drawer slides in on a real spring (stiffness 380, damping 30).
18 * - A cursor-following "View" label springs after the pointer over product
19 * imagery (mouse only).
20 * - Editorial press-quote marquee; reveal-on-scroll everywhere else.
21 *
22 * Reduced motion: no scrub, no pinning, no marquee, drawer/label snap.
23 * SSR-safe: no window/document access during render.
24 * Accessibility: real buttons with aria-pressed / aria-label, dialog with
25 * focus management + Escape, aria-live cart status, AA contrast.
26 */
27import { useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from "react";
28
29/* ------------------------------------------------------------------ */
30/* Garments */
31/* ------------------------------------------------------------------ */
32type GKind = "coat" | "knit" | "trouser" | "shirt" | "scarf" | "bag";
33
34function useSvgId() {
35 return "an" + useId().replace(/[^a-zA-Z0-9_-]/g, "");
36}
37
38/** Shared gradient + pattern defs every garment drawing references. */
39function GarmentDefs({ id }: { id: string }) {
40 return (
41 <>
42 <linearGradient id={`${id}-sh`} x1="0" y1="0" x2="1" y2="0">
43 <stop offset="0" stopColor="#000" stopOpacity=".34" />
44 <stop offset=".28" stopColor="#000" stopOpacity="0" />
45 <stop offset=".62" stopColor="#fff" stopOpacity=".08" />
46 <stop offset="1" stopColor="#000" stopOpacity=".38" />
47 </linearGradient>
48 <linearGradient id={`${id}-shv`} x1="0" y1="0" x2="0" y2="1">
49 <stop offset="0" stopColor="#fff" stopOpacity=".1" />
50 <stop offset=".7" stopColor="#000" stopOpacity="0" />
51 <stop offset="1" stopColor="#000" stopOpacity=".22" />
52 </linearGradient>
53 <pattern id={`${id}-rib`} width="6" height="10" patternUnits="userSpaceOnUse">
54 <rect width="3" height="10" fill="#000" opacity=".16" />
55 </pattern>
56 <pattern id={`${id}-cable`} width="44" height="40" patternUnits="userSpaceOnUse">
57 <path d="M8 0 C 22 10 22 10 8 20 C -6 30 -6 30 8 40 M22 0 C 8 10 8 10 22 20 C 36 30 36 30 22 40" fill="none" stroke="#000" strokeOpacity=".16" strokeWidth="3" />
58 <path d="M8 0 C 22 10 22 10 8 20 C -6 30 -6 30 8 40" fill="none" stroke="#fff" strokeOpacity=".1" strokeWidth="1.2" transform="translate(1 -1)" />
59 <rect x="34" width="2" height="40" fill="#000" opacity=".1" />
60 </pattern>
61 <pattern id={`${id}-hb`} width="12" height="12" patternUnits="userSpaceOnUse">
62 <path d="M0 12 L6 0 M6 12 L12 0" stroke="#000" strokeOpacity=".09" strokeWidth="1.4" />
63 <path d="M6 0 L12 12" stroke="#fff" strokeOpacity=".05" strokeWidth="1" />
64 </pattern>
65 <pattern id={`${id}-check`} width="36" height="36" patternUnits="userSpaceOnUse">
66 <rect width="36" height="12" fill="#000" opacity=".1" />
67 <rect width="12" height="36" fill="#000" opacity=".1" />
68 <rect y="24" width="36" height="1.4" fill="#fff" opacity=".2" />
69 </pattern>
70 </>
71 );
72}
73
74function Garment({ kind, color, id }: { kind: GKind; color: string; id: string }) {
75 const fab = { className: "an-fab", style: { fill: color } as CSSProperties };
76 const sh = `url(#${id}-sh)`;
77 const shv = `url(#${id}-shv)`;
78 const line = (d: string, o = 0.24, w = 2) => <path d={d} fill="none" stroke="#000" strokeOpacity={o} strokeWidth={w} strokeLinecap="round" />;
79 const hi = (d: string, o = 0.1, w = 7) => <path d={d} fill="none" stroke="#fff" strokeOpacity={o} strokeWidth={w} strokeLinecap="round" />;
80
81 if (kind === "coat") {
82 const body = "M150 64 L200 86 L250 64 L282 90 C288 160 286 232 292 300 L304 470 L96 470 L108 300 C114 232 112 160 118 90 Z";
83 const sl = "M120 90 C96 104 84 150 80 222 L68 408 L104 412 L120 250 Z";
84 const sr = "M280 90 C304 104 316 150 320 222 L332 408 L296 412 L280 250 Z";
85 return (
86 <g>
87 <path d="M164 60 Q200 76 236 60 L200 90 Z" fill="#1A1A1A" />
88 <path d={sl} {...fab} /><path d={sl} fill={sh} />
89 <path d={sr} {...fab} /><path d={sr} fill={sh} />
90 <path d={body} {...fab} /><path d={body} fill={`url(#${id}-hb)`} /><path d={body} fill={sh} /><path d={body} fill={shv} />
91 <path d="M150 64 L200 88 L188 210 L156 124 L140 76 Z" {...fab} />
92 <path d="M150 64 L200 88 L188 210 L156 124 L140 76 Z" fill="#000" opacity=".16" />
93 <path d="M250 64 L200 88 L212 210 L244 124 L260 76 Z" {...fab} />
94 <path d="M250 64 L200 88 L212 210 L244 124 L260 76 Z" fill="#fff" opacity=".06" />
95 {line("M156 124 L188 210", 0.28, 1.5)}
96 {line("M244 124 L212 210", 0.28, 1.5)}
97 {line("M212 210 L218 470", 0.3)}
98 {line("M126 338 L178 342", 0.34, 2.5)}
99 {line("M222 342 L274 338", 0.34, 2.5)}
100 {line("M104 380 L120 380", 0.2)}
101 {[244, 302, 360].map((y) => (
102 <g key={y}><circle cx="230" cy={y} r="6" fill="#111" opacity=".75" /><circle cx="228.5" cy={y - 1.5} r="2" fill="#fff" opacity=".25" /></g>
103 ))}
104 {hi("M140 150 C150 250 146 360 150 466")}
105 {hi("M264 170 C268 270 270 380 276 466", 0.06, 10)}
106 {hi("M98 210 C94 290 90 350 86 404", 0.08, 5)}
107 {line("M118 90 C120 160 120 210 120 250", 0.18, 1.5)}
108 {line("M282 90 C280 160 280 210 280 250", 0.18, 1.5)}
109 </g>
110 );
111 }
112 if (kind === "knit") {
113 const body = "M134 84 Q200 106 266 84 L290 98 C300 150 300 220 300 300 L302 392 L98 392 L100 300 C100 220 100 150 110 98 Z";
114 const sl = "M112 98 C84 112 72 172 66 250 L58 380 L94 384 L104 262 L110 146 Z";
115 const sr = "M288 98 C316 112 328 172 334 250 L342 380 L306 384 L296 262 L290 146 Z";
116 return (
117 <g>
118 <path d="M156 80 Q200 112 244 80 Q200 92 156 80Z" fill="#1A1A1A" />
119 <path d={sl} {...fab} /><path d={sl} fill={sh} />
120 <path d={sr} {...fab} /><path d={sr} fill={sh} />
121 <path d="M58 360 L94 364 L92 386 L57 382Z" fill={`url(#${id}-rib)`} />
122 <path d="M306 364 L342 360 L343 382 L308 386Z" fill={`url(#${id}-rib)`} />
123 <path d={body} {...fab} />
124 <path d={body} fill={`url(#${id}-cable)`} />
125 <path d={body} fill={sh} /><path d={body} fill={shv} />
126 <path d="M98 364 L302 364 L302 392 L98 392Z" fill={`url(#${id}-rib)`} />
127 {line("M98 364 L302 364", 0.2, 1.5)}
128 <path d="M148 82 Q200 124 252 82" fill="none" style={{ stroke: color }} className="an-fab-stroke" strokeWidth="13" />
129 <path d="M148 82 Q200 124 252 82" fill="none" stroke={`url(#${id}-rib)`} strokeWidth="13" />
130 <path d="M148 82 Q200 124 252 82" fill="none" stroke="#000" strokeOpacity=".14" strokeWidth="13" />
131 {hi("M124 160 C120 240 122 300 120 360", 0.08, 10)}
132 {line("M110 146 C108 200 104 240 104 262", 0.2, 1.5)}
133 {line("M290 146 C292 200 296 240 296 262", 0.2, 1.5)}
134 </g>
135 );
136 }
137 if (kind === "trouser") {
138 const legs = "M118 92 L282 92 L304 474 L216 474 L200 196 L184 474 L96 474 Z";
139 return (
140 <g>
141 <path d={legs} {...fab} /><path d={legs} fill={sh} /><path d={legs} fill={shv} />
142 <path d="M116 62 L284 62 L284 94 L116 94 Z" {...fab} />
143 <path d="M116 62 L284 62 L284 94 L116 94 Z" fill="#000" opacity=".12" />
144 {[134, 176, 224, 266].map((x) => <rect key={x} x={x} y="58" width="7" height="38" rx="2" style={{ fill: color }} className="an-fab" stroke="#000" strokeOpacity=".18" />)}
145 <circle cx="210" cy="78" r="5" fill="#111" opacity=".7" />
146 {line("M200 94 L200 196", 0.34)}
147 {line("M152 94 L140 300", 0.22)}
148 {line("M248 94 L260 300", 0.22)}
149 {line("M128 104 C140 128 150 132 166 128", 0.2, 1.5)}
150 {line("M272 104 C260 128 250 132 234 128", 0.2, 1.5)}
151 {hi("M140 300 L130 470", 0.12, 4)}
152 {hi("M260 300 L270 470", 0.1, 4)}
153 {line("M96 462 L184 462 M216 462 L304 462", 0.14, 1.5)}
154 </g>
155 );
156 }
157 if (kind === "shirt") {
158 const body = "M140 70 L200 94 L260 70 L292 92 C298 170 296 260 298 330 C298 380 300 420 296 452 Q200 472 104 452 C100 420 102 380 102 330 C104 260 102 170 108 92 Z";
159 const sl = "M110 92 C84 108 74 160 70 240 L60 392 L96 396 L104 250 Z";
160 const sr = "M290 92 C316 108 326 160 330 240 L340 392 L304 396 L296 250 Z";
161 return (
162 <g>
163 <path d="M168 66 Q200 78 232 66 L200 94Z" fill="#1A1A1A" />
164 <path d={sl} {...fab} /><path d={sl} fill={sh} />
165 <path d={sr} {...fab} /><path d={sr} fill={sh} />
166 <path d="M60 376 L96 380 L96 396 L60 392Z" fill="#000" opacity=".12" />
167 <path d={body} {...fab} /><path d={body} fill={sh} /><path d={body} fill={shv} />
168 <path d="M156 64 L200 94 L176 124 L146 84 Z" {...fab} />
169 <path d="M156 64 L200 94 L176 124 L146 84 Z" fill="#fff" opacity=".18" />
170 <path d="M244 64 L200 94 L224 124 L254 84 Z" {...fab} />
171 <path d="M244 64 L200 94 L224 124 L254 84 Z" fill="#fff" opacity=".1" />
172 {line("M200 94 L200 462", 0.22, 1.5)}
173 {line("M208 94 L208 462", 0.12, 1)}
174 {[140, 192, 244, 296, 348, 400].map((y) => <circle key={y} cx="204" cy={y} r="3.2" fill="#fff" stroke="#000" strokeOpacity=".3" />)}
175 <path d="M228 154 L266 154 L266 198 Q247 206 228 198 Z" fill="none" stroke="#000" strokeOpacity=".2" strokeWidth="1.5" />
176 {hi("M132 140 C138 240 132 340 136 446", 0.14, 8)}
177 {line("M160 180 C170 230 166 260 172 300", 0.08, 6)}
178 {line("M250 260 C244 300 248 340 244 380", 0.08, 6)}
179 </g>
180 );
181 }
182 if (kind === "scarf") {
183 const left = "M138 66 L198 66 L192 436 L132 436 Z";
184 const right = "M202 66 L264 66 L274 404 L212 404 Z";
185 return (
186 <g>
187 <rect x="100" y="52" width="200" height="6" rx="3" fill="#2A2A2A" />
188 <path d={right} {...fab} /><path d={right} fill={`url(#${id}-check)`} /><path d={right} fill={sh} />
189 <path d={left} {...fab} /><path d={left} fill={`url(#${id}-check)`} /><path d={left} fill={sh} /><path d={left} fill={shv} />
190 <path d="M134 44 L266 44 C272 52 270 62 264 70 L138 70 C132 62 130 52 134 44 Z" {...fab} />
191 <path d="M134 44 L266 44 C272 52 270 62 264 70 L138 70 C132 62 130 52 134 44 Z" fill="#000" opacity=".1" />
192 {Array.from({ length: 14 }).map((_, i) => (
193 <path key={`l${i}`} d={`M${134 + i * 4.3} 436 l${(i % 3) - 1} 26`} style={{ stroke: color }} className="an-fab-stroke" strokeWidth="2" />
194 ))}
195 {Array.from({ length: 14 }).map((_, i) => (
196 <path key={`r${i}`} d={`M${213 + i * 4.4} 404 l${(i % 3) - 1} 24`} style={{ stroke: color }} className="an-fab-stroke" strokeWidth="2" />
197 ))}
198 {hi("M160 90 C164 200 156 320 160 430", 0.1, 8)}
199 {line("M198 66 L192 436", 0.25, 1.5)}
200 </g>
201 );
202 }
203 // bag
204 const bag = "M108 176 L292 176 L310 444 L90 444 Z";
205 return (
206 <g>
207 <path d="M150 180 C146 80 254 80 250 180" fill="none" style={{ stroke: color }} className="an-fab-stroke" strokeWidth="14" />
208 <path d="M150 180 C146 80 254 80 250 180" fill="none" stroke="#000" strokeOpacity=".22" strokeWidth="14" />
209 <path d="M150 180 C146 80 254 80 250 180" fill="none" stroke="#fff" strokeOpacity=".16" strokeWidth="2" transform="translate(-3 0)" />
210 <path d={bag} {...fab} /><path d={bag} fill={sh} /><path d={bag} fill={shv} />
211 <path d="M108 176 L292 176 L292 192 L108 192Z" fill="#000" opacity=".14" />
212 <path d="M116 200 L284 200 L300 432 L100 432Z" fill="none" stroke="#fff" strokeOpacity=".22" strokeWidth="1.2" strokeDasharray="4 5" />
213 <rect x="184" y="214" width="32" height="18" rx="3" fill="#111" opacity=".5" />
214 {hi("M126 220 L114 430", 0.1, 10)}
215 </g>
216 );
217}
218
219function ProductShot({ kind, color, label }: { kind: GKind; color: string; label: string }) {
220 const id = useSvgId();
221 return (
222 <svg className="an-shot" viewBox="0 0 400 500" role="img" aria-label={label}>
223 <defs>
224 <GarmentDefs id={id} />
225 <radialGradient id={`${id}-bg`} cx=".5" cy=".38" r=".75"><stop offset="0" stopColor="#F4F3F0" /><stop offset="1" stopColor="#DEDCD7" /></radialGradient>
226 <radialGradient id={`${id}-fl`}><stop offset="0" stopColor="#000" stopOpacity=".22" /><stop offset="1" stopColor="#000" stopOpacity="0" /></radialGradient>
227 </defs>
228 <rect width="400" height="500" fill={`url(#${id}-bg)`} />
229 <ellipse cx="200" cy="486" rx="150" ry="14" fill={`url(#${id}-fl)`} />
230 <g transform="translate(0 6)"><Garment kind={kind} color={color} id={id} /></g>
231 </svg>
232 );
233}
234
235/* ------------------------------------------------------------------ */
236/* Editorial scenes (monochrome) */
237/* ------------------------------------------------------------------ */
238type SceneK = "figure" | "fjord" | "birch" | "stack" | "detail" | "window" | "still";
239
240function Figure({ id, coat = "#2B2B2B", x = 200, y = 0, s = 1 }: { id: string; coat?: string; x?: number; y?: number; s?: number }) {
241 return (
242 <g transform={`translate(${x} ${y}) scale(${s}) translate(-200 0)`}>
243 <path d="M176 470 L170 690 L190 690 L198 470Z M206 470 L212 690 L232 690 L226 470Z" fill="#161616" />
244 <path d="M164 688 L194 688 L196 700 L160 700Z M208 688 L238 688 L242 700 L206 700Z" fill="#0A0A0A" />
245 <g transform="translate(0 30) scale(1 .95)"><Garment kind="coat" color={coat} id={id} /></g>
246 <path d="M186 36 L214 36 L212 92 L188 92Z" fill="#C9C5BE" />
247 <path d="M186 60 L214 60 L212 92 L188 92Z" fill="#000" opacity=".25" />
248 <ellipse cx="200" cy="20" rx="27" ry="33" fill="#D3CFC8" />
249 <path d="M172 22 C170 -14 230 -14 228 22 C228 34 222 46 220 54 C222 30 212 6 200 6 C186 6 176 24 178 52 C174 44 172 34 172 22Z" fill="#141414" />
250 <ellipse cx="200" cy="20" rx="27" ry="33" fill={`url(#${id}-sh)`} />
251 </g>
252 );
253}
254
255function Scene({ k, label }: { k: SceneK; label: string }) {
256 const id = useSvgId();
257 const u = (n: string) => `url(#${id}-${n})`;
258 let vb = "0 0 600 800";
259 let body: ReactNode = null;
260 if (k === "figure") {
261 body = (
262 <>
263 <rect width="600" height="800" fill={u("studio")} />
264 <path d="M0 640 L600 640 L600 800 L0 800Z" fill="#CFCCC6" />
265 <path d="M0 640 L600 640" stroke="#BDB9B2" strokeWidth="2" />
266 <ellipse cx="300" cy="752" rx="130" ry="12" fill="#000" opacity=".2" />
267 <rect x="0" y="0" width="230" height="800" fill="#fff" opacity=".18" />
268 <Figure id={id} x={300} y={55} s={0.98} />
269 </>
270 );
271 } else if (k === "fjord") {
272 vb = "0 0 1200 800";
273 body = (
274 <>
275 <linearGradient id={`${id}-sky`} x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#9C9C9A" /><stop offset=".6" stopColor="#D9D8D4" /><stop offset="1" stopColor="#E9E8E4" /></linearGradient>
276 <rect width="1200" height="800" fill={u("sky")} />
277 <path d="M0 420 L120 300 L210 360 L330 230 L460 380 L560 330 L700 420 L0 420Z" fill="#8E8D8A" />
278 <path d="M520 430 L680 250 L780 320 L900 200 L1040 330 L1200 280 L1200 440 Z" fill="#7A7976" />
279 <path d="M0 440 L180 360 L320 430 L0 470Z" fill="#5E5D5A" />
280 <path d="M760 450 L960 340 L1200 400 L1200 470Z" fill="#5A5956" />
281 <rect y="460" width="1200" height="340" fill="#B9B8B4" />
282 <path d="M0 460 L120 520 L210 490 L330 560 L460 480 L560 510 L700 460Z" fill="#8E8D8A" opacity=".35" />
283 {Array.from({ length: 18 }).map((_, i) => <rect key={i} x={(i * 173) % 1150} y={480 + ((i * 59) % 300)} width={60 + ((i * 41) % 120)} height="2" fill="#fff" opacity=".35" />)}
284 <path d="M0 690 C 300 670 700 700 1200 660 L1200 800 L0 800Z" fill="#3E3D3B" />
285 <g transform="translate(820 470) scale(.36)"><Figure id={id} coat="#1E1E1E" x={200} y={0} s={1} /></g>
286 </>
287 );
288 } else if (k === "birch") {
289 body = (
290 <>
291 <linearGradient id={`${id}-fog`} x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#E7E6E2" /><stop offset="1" stopColor="#C8C6C1" /></linearGradient>
292 <rect width="600" height="800" fill={u("fog")} />
293 {Array.from({ length: 11 }).map((_, i) => {
294 const x = (i * 131) % 640 - 20;
295 const w = 14 + ((i * 23) % 26);
296 const far = i % 3 === 0;
297 return (
298 <g key={i} opacity={far ? 0.4 : 1}>
299 <rect x={x} y="0" width={w} height="800" fill="#F4F3F0" />
300 <rect x={x + w * 0.7} y="0" width={w * 0.3} height="800" fill="#000" opacity=".12" />
301 {[90, 210, 330, 470, 600, 700].map((y, kk) => (
302 <rect key={kk} x={x + ((kk * 7) % (w / 2))} y={y + ((i * 31) % 50)} width={w * (0.3 + ((kk + i) % 3) * 0.2)} height={3 + ((kk + i) % 3) * 2} fill="#1E1E1E" opacity=".8" />
303 ))}
304 </g>
305 );
306 })}
307 <path d="M0 720 C 200 700 400 730 600 700 L600 800 L0 800Z" fill="#EDECE8" />
308 </>
309 );
310 } else if (k === "stack") {
311 const cols = ["#ECE7DC", "#A9A49B", "#3B3B3B", "#D8D2C6", "#6F6C66"];
312 body = (
313 <>
314 <rect width="600" height="800" fill="#D9D7D2" />
315 <rect y="560" width="600" height="240" fill="#BDBAB4" />
316 <rect x="90" y="560" width="420" height="22" fill="#9E9B95" />
317 {cols.map((c, i) => (
318 <g key={i} transform={`translate(${130 + (i % 2) * 8} ${470 - i * 82})`}>
319 <rect width="340" height="84" rx="14" fill={c} />
320 <rect width="340" height="84" rx="14" fill={u("cable")} />
321 <rect y="60" width="340" height="24" rx="10" fill={u("rib")} />
322 <rect width="340" height="84" rx="14" fill={u("shv")} />
323 <path d="M14 10 L326 10" stroke="#fff" strokeOpacity=".2" strokeWidth="2" />
324 </g>
325 ))}
326 <ellipse cx="300" cy="566" rx="200" ry="8" fill="#000" opacity=".18" />
327 </>
328 );
329 } else if (k === "detail") {
330 body = (
331 <>
332 <rect width="600" height="800" fill="#2E2E2E" />
333 <g transform="translate(-520 -230) scale(3.6)"><Garment kind="coat" color="#2E2E2E" id={id} /></g>
334 <rect width="600" height="800" fill={u("sh")} opacity=".5" />
335 </>
336 );
337 } else if (k === "window") {
338 body = (
339 <>
340 <rect width="600" height="800" fill="#CFCDC8" />
341 <path d="M0 560 L600 560 L600 800 L0 800Z" fill="#B2AFA9" />
342 <path d="M120 600 L330 600 L470 800 L170 800Z" fill="#E9E7E2" opacity=".85" />
343 <path d="M60 80 L300 80 L300 440 L60 440Z" fill="#EFEEEA" />
344 <path d="M180 80 L180 440 M60 260 L300 260" stroke="#9F9C96" strokeWidth="8" />
345 <path d="M60 80 L300 80 L300 440 L60 440Z" fill="none" stroke="#9F9C96" strokeWidth="10" />
346 <g transform="translate(360 330)">
347 <rect x="0" y="120" width="150" height="14" fill="#222" />
348 <rect x="6" y="134" width="8" height="160" fill="#222" />
349 <rect x="136" y="134" width="8" height="160" fill="#222" />
350 <rect x="130" y="-40" width="12" height="174" fill="#222" />
351 </g>
352 <g transform="translate(330 250) scale(.42)"><Garment kind="scarf" color="#3A3A3A" id={id} /></g>
353 </>
354 );
355 } else {
356 body = (
357 <>
358 <rect width="600" height="800" fill="#E3E1DC" />
359 <rect x="140" y="560" width="320" height="240" fill="#F1F0EC" />
360 <rect x="140" y="560" width="320" height="14" fill="#CFCCC6" />
361 <ellipse cx="300" cy="560" rx="140" ry="8" fill="#000" opacity=".16" />
362 <g transform="translate(110 182) scale(.95)"><Garment kind="bag" color="#1F1F1F" id={id} /></g>
363 </>
364 );
365 }
366 return (
367 <svg className="an-scene" viewBox={vb} preserveAspectRatio="xMidYMid slice" role="img" aria-label={label}>
368 <defs>
369 <GarmentDefs id={id} />
370 <linearGradient id={`${id}-studio`} x1="0" y1="0" x2="1" y2="0"><stop offset="0" stopColor="#E6E4DF" /><stop offset="1" stopColor="#C2BFB9" /></linearGradient>
371 </defs>
372 {body}
373 </svg>
374 );
375}
376
377/* ------------------------------------------------------------------ */
378/* Data */
379/* ------------------------------------------------------------------ */
380type Swatch = { name: string; hex: string };
381const SW = {
382 ecru: { name: "Ecru", hex: "#E6E0D3" },
383 stone: { name: "Stone", hex: "#A8A39A" },
384 charcoal: { name: "Charcoal", hex: "#3A3A3A" },
385 black: { name: "Black", hex: "#141414" },
386 fog: { name: "Fog", hex: "#C9C9C6" },
387 moss: { name: "Moss", hex: "#55574C" },
388};
389const PRODUCTS: { name: string; kind: GKind; price: number; tag?: string; swatches: Swatch[]; fabric: string; layout: string }[] = [
390 { name: "Sund Overcoat", kind: "coat", price: 690, tag: "New", swatches: [SW.charcoal, SW.stone, SW.black], fabric: "Wool herringbone, 680 g/m", layout: "an-p-a" },
391 { name: "Vind Cable Crew", kind: "knit", price: 230, swatches: [SW.ecru, SW.fog, SW.charcoal], fabric: "Extrafine merino", layout: "an-p-b" },
392 { name: "Ström Pleated Trouser", kind: "trouser", price: 260, swatches: [SW.black, SW.stone, SW.moss], fabric: "Wool twill", layout: "an-p-c" },
393 { name: "Ljus Poplin Shirt", kind: "shirt", price: 160, tag: "Restocked", swatches: [SW.ecru, SW.fog, SW.black], fabric: "Organic cotton poplin", layout: "an-p-d" },
394 { name: "Frost Check Scarf", kind: "scarf", price: 140, swatches: [SW.fog, SW.charcoal, SW.ecru], fabric: "Lambswool", layout: "an-p-e" },
395 { name: "Båt Leather Tote", kind: "bag", price: 320, swatches: [SW.black, SW.stone], fabric: "Vegetable-tanned leather", layout: "an-p-f" },
396];
397const SIZES = ["XS", "S", "M", "L", "XL"];
398const ONE_SIZE: GKind[] = ["scarf", "bag"];
399
400const LOOKS: { k: SceneK; label: string; cap: string }[] = [
401 { k: "birch", label: "Birch forest in winter fog", cap: "Look 03 — Tyresta" },
402 { k: "stack", label: "Stack of folded cable knits in five colours", cap: "Vind Cable Crew" },
403 { k: "detail", label: "Close-up of the herringbone overcoat lapel and horn buttons", cap: "Hand-set horn" },
404 { k: "fjord", label: "A figure in a dark overcoat on a grey fjord shore", cap: "Look 01 — Lysefjord, 06:40" },
405 { k: "window", label: "A chair by a window with a check scarf draped over it", cap: "Frost Check Scarf" },
406 { k: "figure", label: "Studio portrait of a model in the charcoal overcoat", cap: "Look 07" },
407 { k: "still", label: "Black leather tote on a white plinth", cap: "Båt Tote" },
408];
409const LOOK_CENTER = 3;
410
411const QUOTES = [
412 { q: "The quietest coat of the season — and the one everyone asked about.", s: "Kvarter Magazine" },
413 { q: "Scandinavian restraint, cut with a tailor's arrogance.", s: "The Northern Review" },
414 { q: "Atelier Nord makes clothes you stop noticing, which is the point.", s: "Form & Fabric" },
415 { q: "A knit you will hand down, not throw out.", s: "Studio Weekly" },
416];
417
418const FABRICS = [
419 { name: "Herringbone wool", from: "Kvarnby Spinneri, Halland", spec: "680 g/m · 100% wool", cls: "an-tx-hb" },
420 { name: "Cable merino", from: "Filatura Orsi, Biella", spec: "19.5 µm · RWS certified", cls: "an-tx-knit" },
421 { name: "Bouclé alpaca", from: "Andean co-op, Arequipa", spec: "62% alpaca · 38% wool", cls: "an-tx-boucle" },
422 { name: "Cotton poplin", from: "Tessitura Vela, Como", spec: "120/2 · GOTS organic", cls: "an-tx-poplin" },
423];
424
425const JOURNAL = [
426 { k: "fjord" as SceneK, date: "14 Oct 2026", t: "Six days on Lysefjord: shooting AW26 in four hours of light a day" },
427 { k: "stack" as SceneK, date: "2 Oct 2026", t: "Why our cable crew takes eleven hours to knit — and why we refuse to speed it up" },
428 { k: "window" as SceneK, date: "19 Sep 2026", t: "Inside the Södermalm atelier: repairs, alterations and a very old Pfaff" },
429];
430
431const fmt = (n: number) => `€${n.toLocaleString("en-IE")}`;
432type CartItem = { key: string; name: string; kind: GKind; color: Swatch; size: string; price: number; qty: number };
433
434/* ------------------------------------------------------------------ */
435/* Page */
436/* ------------------------------------------------------------------ */
437export function Page() {
438 const rootRef = useRef<HTMLDivElement>(null);
439 const drawerRef = useRef<HTMLDivElement>(null);
440 const closeRef = useRef<HTMLButtonElement>(null);
441 const lastFocus = useRef<HTMLElement | null>(null);
442 const [small, setSmall] = useState(false);
443 const [menu, setMenu] = useState(false);
444 const [colors, setColors] = useState<number[]>(PRODUCTS.map(() => 0));
445 const [picking, setPicking] = useState<number | null>(null);
446 const [added, setAdded] = useState<number | null>(null);
447 const [cart, setCart] = useState<CartItem[]>([
448 { key: "ström-black-M", name: "Ström Pleated Trouser", kind: "trouser", color: SW.black, size: "M", price: 260, qty: 1 },
449 ]);
450 const [open, setOpen] = useState(false);
451 const [bump, setBump] = useState(0);
452 const [status, setStatus] = useState("");
453 const [email, setEmail] = useState("");
454 const [joined, setJoined] = useState(false);
455
456 const count = cart.reduce((a, c) => a + c.qty, 0);
457 const subtotal = cart.reduce((a, c) => a + c.qty * c.price, 0);
458 const FREE = 400;
459
460 /* Scroll engine, reveals, cursor label. */
461 useEffect(() => {
462 const root = rootRef.current;
463 if (!root) return;
464 const doc = root.ownerDocument;
465 const win = doc.defaultView;
466 if (!win) return;
467 const reduce = win.matchMedia("(prefers-reduced-motion: reduce)").matches;
468 root.classList.add("an-js");
469
470 // Hosts with overflow:hidden on html AND body make <body> a scroll container
471 // that never scrolls, which breaks position:sticky. `clip` looks the same.
472 let restoreBody: (() => void) | null = null;
473 const fixRaf = win.requestAnimationFrame(() => {
474 const b = doc.body;
475 const bs = win.getComputedStyle(b);
476 const hs = win.getComputedStyle(doc.documentElement);
477 if (hs.overflowX !== "visible" && (bs.overflowX === "hidden" || bs.overflowY === "auto")) {
478 const prev = [b.style.overflowX, b.style.overflowY];
479 b.style.overflowX = "clip";
480 b.style.overflowY = "visible";
481 restoreBody = () => {
482 b.style.overflowX = prev[0];
483 b.style.overflowY = prev[1];
484 };
485 }
486 });
487
488 const mast = root.querySelector<HTMLElement>(".an-mast");
489 const look = root.querySelector<HTMLElement>(".an-look");
490 const grid = root.querySelector<HTMLElement>(".an-look-grid");
491 const stage = root.querySelector<HTMLElement>(".an-look-stage");
492 const center = root.querySelector<HTMLElement>(`.an-tile-${LOOK_CENTER}`);
493 let s0 = 1;
494 let ox = 0;
495 let oy = 0;
496 let lookOn = false;
497
498 // Fit the masthead word to the content width whatever condensed face the OS supplies.
499 const word = root.querySelector<HTMLElement>(".an-word");
500 const fitWord = () => {
501 if (!word || !mast) return;
502 const lines = Array.from(word.querySelectorAll<HTMLElement>(".an-word-line"));
503 word.style.fontSize = "100px";
504 const widest = Math.max(...lines.map((l) => l.scrollWidth));
505 const avail = mast.clientWidth - parseFloat(win.getComputedStyle(mast).paddingLeft) * 2;
506 if (widest <= 0) return;
507 const byWidth = ((100 * avail) / widest) * 0.995;
508 // Keep the CTA row above the fold on short desktop viewports.
509 const byHeight = win.innerWidth > 760 ? (win.innerHeight - 330) / 1.56 : Infinity;
510 word.style.fontSize = `${Math.floor(Math.max(64, Math.min(byWidth, byHeight)))}px`;
511 };
512 doc.fonts?.ready.then(() => { fitWord(); }).catch(() => {});
513
514 const measure = () => {
515 fitWord();
516 lookOn = !reduce && win.innerWidth > 760;
517 if (!grid || !center || !stage) return;
518 grid.style.transform = "";
519 if (!lookOn) {
520 look?.classList.remove("is-zoom");
521 return;
522 }
523 look?.classList.add("is-zoom");
524 const cw = center.offsetWidth;
525 const ch = center.offsetHeight;
526 const cx = center.offsetLeft + cw / 2;
527 const cy = center.offsetTop + ch / 2;
528 grid.style.transformOrigin = `${cx}px ${cy}px`;
529 s0 = Math.max(win.innerWidth / cw, win.innerHeight / ch) * 1.02;
530 // Where the centre tile sits relative to the stage centre when settled.
531 ox = stage.clientWidth / 2 - (grid.offsetLeft + cx);
532 oy = stage.clientHeight / 2 - (grid.offsetTop + cy);
533 };
534
535 let raf = 0;
536 let lastSmall = false;
537 const update = () => {
538 raf = 0;
539 const vh = win.innerHeight;
540 const y = win.scrollY || doc.documentElement.scrollTop;
541 const sm = y > 40;
542 if (sm !== lastSmall) {
543 lastSmall = sm;
544 setSmall(sm);
545 }
546 if (mast && !reduce) {
547 const r = mast.getBoundingClientRect();
548 const p = Math.min(1, Math.max(0, -r.top / Math.max(1, r.height)));
549 mast.style.setProperty("--p", p.toFixed(4));
550 }
551 if (look && grid && lookOn) {
552 const r = look.getBoundingClientRect();
553 const p = Math.min(1, Math.max(0, -r.top / Math.max(1, r.height - vh)));
554 const k = Math.min(1, p / 0.78);
555 const e = 1 - Math.pow(1 - k, 3);
556 const s = s0 + (1 - s0) * e;
557 grid.style.transform = `translate3d(${(ox * (1 - e)).toFixed(1)}px, ${(oy * (1 - e)).toFixed(1)}px, 0) scale(${s.toFixed(4)})`;
558 look.style.setProperty("--e", e.toFixed(4));
559 }
560 };
561 const onScroll = () => {
562 if (!raf) raf = win.requestAnimationFrame(update);
563 };
564 const onResize = () => {
565 measure();
566 onScroll();
567 };
568 measure();
569 update();
570 win.addEventListener("scroll", onScroll, { passive: true });
571 win.addEventListener("resize", onResize);
572
573 const io = new win.IntersectionObserver(
574 (es) => es.forEach((e) => {
575 if (e.isIntersecting) {
576 e.target.classList.add("is-in");
577 io.unobserve(e.target);
578 }
579 }),
580 { rootMargin: "0px 0px -10% 0px", threshold: 0.12 },
581 );
582 root.querySelectorAll("[data-an-rv]").forEach((el) => io.observe(el));
583
584 // Cursor "View" label — spring-follow (stiffness 380, damping 30).
585 const label = root.querySelector<HTMLElement>(".an-cursor");
586 let tx = 0, ty = 0, x = 0, y2 = 0, vx = 0, vy = 0, active = false, cRaf = 0, last = 0;
587 const step = (now: number) => {
588 const dt = Math.min(0.032, (now - (last || now)) / 1000) || 0.016;
589 last = now;
590 const ax = 380 * (tx - x) - 30 * vx;
591 const ay = 380 * (ty - y2) - 30 * vy;
592 vx += ax * dt; vy += ay * dt;
593 x += vx * dt; y2 += vy * dt;
594 if (label) label.style.transform = `translate3d(${x.toFixed(1)}px, ${y2.toFixed(1)}px, 0)`;
595 if (Math.abs(tx - x) + Math.abs(ty - y2) + Math.abs(vx) + Math.abs(vy) > 0.3) cRaf = win.requestAnimationFrame(step);
596 else { cRaf = 0; last = 0; }
597 };
598 const onMove = (e: PointerEvent) => {
599 if (e.pointerType !== "mouse" || !label) return;
600 const t = e.target as Element | null;
601 // Show over imagery, but step aside for real controls (quick add, hotspots).
602 const hit = t?.closest?.("[data-an-view]") && !t.closest("button");
603 const rr = root.getBoundingClientRect();
604 tx = e.clientX - rr.left;
605 ty = e.clientY - rr.top;
606 if (hit && !active) {
607 active = true;
608 if (reduce || !label.classList.contains("is-on")) { x = tx; y2 = ty; vx = vy = 0; }
609 label.classList.add("is-on");
610 } else if (!hit && active) {
611 active = false;
612 label.classList.remove("is-on");
613 }
614 if (reduce) {
615 label.style.transform = `translate3d(${tx}px, ${ty}px, 0)`;
616 return;
617 }
618 if (!cRaf) cRaf = win.requestAnimationFrame(step);
619 };
620 root.addEventListener("pointermove", onMove, { passive: true });
621 const onLeave = () => { active = false; label?.classList.remove("is-on"); };
622 root.addEventListener("pointerleave", onLeave);
623
624 return () => {
625 win.cancelAnimationFrame(fixRaf);
626 if (raf) win.cancelAnimationFrame(raf);
627 if (cRaf) win.cancelAnimationFrame(cRaf);
628 win.removeEventListener("scroll", onScroll);
629 win.removeEventListener("resize", onResize);
630 root.removeEventListener("pointermove", onMove);
631 root.removeEventListener("pointerleave", onLeave);
632 io.disconnect();
633 restoreBody?.();
634 root.classList.remove("an-js");
635 };
636 }, []);
637
638 /* Cart drawer — spring slide + focus management. */
639 const springX = useRef({ x: 104, v: 0 });
640 useEffect(() => {
641 const el = drawerRef.current;
642 const win = el?.ownerDocument.defaultView;
643 if (!el || !win) return;
644 const reduce = win.matchMedia("(prefers-reduced-motion: reduce)").matches;
645 const target = open ? 0 : 104;
646 const st = springX.current;
647 let raf = 0;
648 let last = 0;
649 const paint = () => { el.style.transform = `translate3d(${st.x.toFixed(2)}%, 0, 0)`; };
650 if (reduce) {
651 st.x = target; st.v = 0; paint();
652 } else {
653 const tick = (now: number) => {
654 const dt = Math.min(0.032, (now - (last || now)) / 1000) || 0.016;
655 last = now;
656 const a = 380 * (target - st.x) - 30 * st.v;
657 st.v += a * dt;
658 st.x += st.v * dt;
659 paint();
660 if (Math.abs(target - st.x) > 0.05 || Math.abs(st.v) > 0.05) raf = win.requestAnimationFrame(tick);
661 else { st.x = target; st.v = 0; paint(); }
662 };
663 raf = win.requestAnimationFrame(tick);
664 }
665 if (open) {
666 lastFocus.current = el.ownerDocument.activeElement as HTMLElement | null;
667 win.setTimeout(() => closeRef.current?.focus({ preventScroll: true }), 60);
668 } else if (lastFocus.current) {
669 lastFocus.current.focus?.({ preventScroll: true });
670 lastFocus.current = null;
671 }
672 const onKey = (e: KeyboardEvent) => {
673 if (!open) return;
674 if (e.key === "Escape") setOpen(false);
675 if (e.key === "Tab") {
676 const f = Array.from(el.querySelectorAll<HTMLElement>("button, a[href], input")).filter((n) => !n.hasAttribute("disabled"));
677 if (!f.length) return;
678 const first = f[0];
679 const lastEl = f[f.length - 1];
680 const act = el.ownerDocument.activeElement;
681 if (e.shiftKey && act === first) { e.preventDefault(); lastEl.focus(); }
682 else if (!e.shiftKey && act === lastEl) { e.preventDefault(); first.focus(); }
683 }
684 };
685 el.ownerDocument.addEventListener("keydown", onKey);
686 return () => {
687 win.cancelAnimationFrame(raf);
688 el.ownerDocument.removeEventListener("keydown", onKey);
689 };
690 }, [open]);
691
692 const addToCart = (pi: number, size: string) => {
693 const p = PRODUCTS[pi];
694 const c = p.swatches[colors[pi]];
695 const key = `${p.name}-${c.name}-${size}`;
696 setCart((cs) => {
697 const ex = cs.find((x) => x.key === key);
698 if (ex) return cs.map((x) => (x.key === key ? { ...x, qty: x.qty + 1 } : x));
699 return [{ key, name: p.name, kind: p.kind, color: c, size, price: p.price, qty: 1 }, ...cs];
700 });
701 setPicking(null);
702 setAdded(pi);
703 setBump((b) => b + 1);
704 setStatus(`${p.name}, ${c.name}, size ${size} added to your bag.`);
705 const win = rootRef.current?.ownerDocument.defaultView;
706 win?.setTimeout(() => setOpen(true), 650);
707 win?.setTimeout(() => setAdded((a) => (a === pi ? null : a)), 2600);
708 };
709 const changeQty = (key: string, d: number) =>
710 setCart((cs) => cs.map((x) => (x.key === key ? { ...x, qty: x.qty + d } : x)).filter((x) => x.qty > 0));
711
712 const navL = ["Women", "Men", "Knitwear", "Lookbook", "Journal"];
713 const word1 = "ATELIER".split("");
714 const word2 = "NORD".split("");
715 const dyOf = (i: number, w: number) => (w === 0 ? -1 : 1) * (40 + ((i * 53 + w * 31) % 110));
716 const rotOf = (i: number, w: number) => (((i + w) % 3) - 1) * (2 + (i % 2) * 2);
717
718 return (
719 <div ref={rootRef} className="an">
720 <style>{css}</style>
721 <a className="an-skip" href="#an-main">Skip to content</a>
722 <span className="an-cursor" aria-hidden="true">View</span>
723
724 <p className="an-announce">Complimentary shipping & returns across the EU over €400 · Repairs free for life</p>
725
726 {/* NAV */}
727 <header className={`an-nav${small ? " is-small" : ""}${menu ? " is-open" : ""}`}>
728 <div className="an-nav-in">
729 <nav className="an-nav-l" aria-label="Primary">
730 <button type="button" className="an-burger" aria-expanded={menu} aria-label={menu ? "Close menu" : "Open menu"} onClick={() => setMenu((m) => !m)}><span /><span /></button>
731 {navL.map((l) => <a key={l} href={l === "Lookbook" ? "#an-look" : l === "Journal" ? "#an-journal" : "#an-shop"}>{l}</a>)}
732 </nav>
733 <a href="#an-top" className="an-logo" aria-label="Atelier Nord — home">Atelier Nord</a>
734 <div className="an-nav-r">
735 <a href="#an-top" className="an-nav-txt">Search</a>
736 <a href="#an-top" className="an-nav-txt">Account</a>
737 <button type="button" className="an-bag-btn" onClick={() => setOpen(true)} aria-label={`Bag, ${count} ${count === 1 ? "item" : "items"}`}>
738 Bag <span key={bump} className={`an-bag-n${bump ? " is-bump" : ""}`}>{count}</span>
739 </button>
740 </div>
741 </div>
742 <div className="an-sheet" hidden={!menu}>
743 {navL.map((l) => <a key={l} href={l === "Lookbook" ? "#an-look" : l === "Journal" ? "#an-journal" : "#an-shop"} onClick={() => setMenu(false)}>{l}</a>)}
744 </div>
745 </header>
746
747 <main id="an-main">
748 {/* MASTHEAD */}
749 <section id="an-top" className="an-mast" aria-labelledby="an-mast-title">
750 <div className="an-mast-meta">
751 <p>Autumn / Winter 2026<br />Collection 07 — <em>Stillhet</em></p>
752 <p className="an-mast-meta-r">Designed in Stockholm<br />Made in Portugal & Italy</p>
753 </div>
754 <figure className="an-mast-img" data-an-view>
755 <div className="an-mast-img-in an-grain"><Scene k="figure" label="Model in the Sund Overcoat in charcoal herringbone, studio" /></div>
756 <figcaption>Look 07 · Sund Overcoat, Charcoal</figcaption>
757 </figure>
758 <h1 id="an-mast-title" className="an-word" aria-label="Atelier Nord">
759 <span className="an-word-line" aria-hidden="true">
760 {word1.map((c, i) => (
761 <span key={i} className="an-ch" style={{ "--dy": dyOf(i, 0), "--rot": rotOf(i, 0), "--i": i } as CSSProperties}><span>{c}</span></span>
762 ))}
763 </span>
764 <span className="an-word-line an-word-line-2" aria-hidden="true">
765 {word2.map((c, i) => (
766 <span key={i} className="an-ch" style={{ "--dy": dyOf(i, 1), "--rot": rotOf(i, 1), "--i": i + 7 } as CSSProperties}><span>{c}</span></span>
767 ))}
768 </span>
769 </h1>
770 <div className="an-mast-foot">
771 <p>Clothes for long winters and short light. Seven-year coats, knits you repair, nothing you need to explain.</p>
772 <a href="#an-shop" className="an-btn an-btn-ink">Shop the collection <span aria-hidden="true">→</span></a>
773 </div>
774 </section>
775
776 {/* STATEMENT */}
777 <section className="an-statement" aria-label="About the collection">
778 <p className="an-statement-n" data-an-rv>(01)</p>
779 <p className="an-statement-t" data-an-rv>
780 Stillhet is Swedish for <em>stillness</em>. Thirty-one pieces in five colours, cut from cloth woven within
781 <span className="an-inline-img an-grain" aria-hidden="true"><Scene k="detail" label="" /></span>
782 a day of where it was spun — built to be worn hard and mended, not replaced.
783 </p>
784 </section>
785
786 {/* LOOKBOOK ZOOM */}
787 <section id="an-look" className="an-look" aria-labelledby="an-look-title">
788 <div className="an-look-stage">
789 <div className="an-look-head">
790 <h2 id="an-look-title" className="an-h2">Lookbook — AW26</h2>
791 <p>Shot on Lysefjord and in Tyresta, October 2026</p>
792 </div>
793 <ul className="an-look-grid">
794 {LOOKS.map((l, i) => (
795 <li key={l.k} className={`an-tile an-tile-${i}`} style={{ "--k": [140, -90, 200, 0, -160, 120, -60][i] } as CSSProperties} data-an-view>
796 <div className="an-tile-img an-grain"><Scene k={l.k} label={l.label} /></div>
797 <p className="an-tile-cap">{l.cap}</p>
798 </li>
799 ))}
800 </ul>
801 <p className="an-look-over" aria-hidden="true">Stillhet</p>
802 </div>
803 </section>
804
805 {/* SHOP */}
806 <section id="an-shop" className="an-shop" aria-labelledby="an-shop-title">
807 <div className="an-shop-head">
808 <h2 id="an-shop-title" className="an-h2 an-h2-xl" data-an-rv>New in</h2>
809 <p data-an-rv>Six pieces from Collection 07. Every garment ships with a repair card — send it back any time and we'll mend it free.</p>
810 <a href="#an-shop" className="an-link" data-an-rv>View all 31 pieces</a>
811 </div>
812 <p className="an-sr" aria-live="polite">{status}</p>
813 <ul className="an-grid">
814 {PRODUCTS.map((p, i) => {
815 const c = p.swatches[colors[i]];
816 const isPick = picking === i;
817 const isAdded = added === i;
818 const oneSize = ONE_SIZE.includes(p.kind);
819 return (
820 <li key={p.name} className={`an-card ${p.layout}`} data-an-rv>
821 <div className="an-card-media" data-an-view>
822 <a href="#an-shop" className="an-card-img an-grain" aria-label={`${p.name} in ${c.name}, ${fmt(p.price)}`}>
823 <ProductShot kind={p.kind} color={c.hex} label={`${p.name} in ${c.name}`} />
824 </a>
825 {p.tag && <span className="an-tag">{p.tag}</span>}
826 <div className={`an-qa${isPick ? " is-pick" : ""}${isAdded ? " is-added" : ""}`}>
827 <button type="button" className="an-qa-main" aria-expanded={isPick} onClick={() => (oneSize ? addToCart(i, "One size") : setPicking(isPick ? null : i))} disabled={isAdded}>
828 <span className="an-qa-plus" aria-hidden="true" />
829 <span className="an-qa-txt">{isAdded ? "Added" : oneSize ? "Add to bag" : "Quick add"}</span>
830 </button>
831 <div className="an-qa-sizes" role="group" aria-label={`Choose size for ${p.name}`} aria-hidden={!isPick}>
832 {SIZES.map((s, si) => (
833 <button key={s} type="button" tabIndex={isPick ? 0 : -1} disabled={p.kind === "coat" && si === 0} onClick={() => addToCart(i, s)} aria-label={`Size ${s}${p.kind === "coat" && si === 0 ? ", sold out" : ""}`}>{s}</button>
834 ))}
835 </div>
836 </div>
837 </div>
838 <div className="an-card-info">
839 <div>
840 <h3 className="an-card-name">{p.name}</h3>
841 <p className="an-card-fab">{p.fabric} · <span className="an-card-col">{c.name}</span></p>
842 </div>
843 <p className="an-card-price">{fmt(p.price)}</p>
844 </div>
845 <div className="an-sw" role="group" aria-label={`Colour for ${p.name}`}>
846 {p.swatches.map((s, si) => (
847 <button key={s.name} type="button" aria-label={s.name} aria-pressed={colors[i] === si} className={colors[i] === si ? "is-on" : ""} style={{ "--c": s.hex } as CSSProperties} onClick={() => setColors((cs) => cs.map((v, k) => (k === i ? si : v)))} />
848 ))}
849 </div>
850 </li>
851 );
852 })}
853 </ul>
854 </section>
855
856 {/* PRESS MARQUEE */}
857 <section className="an-press" aria-label="Press">
858 <div className="an-marquee">
859 <div className="an-marquee-track">
860 {[0, 1].map((k) => (
861 <ul key={k} className="an-marquee-set" aria-hidden={k === 1}>
862 {QUOTES.map((q) => (
863 <li key={q.s}><blockquote>“{q.q}”</blockquote><cite>— {q.s}</cite><span className="an-star" aria-hidden="true">✳</span></li>
864 ))}
865 </ul>
866 ))}
867 </div>
868 </div>
869 </section>
870
871 {/* FEATURE */}
872 <section className="an-feature" aria-labelledby="an-feature-title">
873 <div className="an-feature-media" data-an-view>
874 <div className="an-feature-img an-grain">
875 <ProductShot kind="coat" color={SW.charcoal.hex} label="Sund Overcoat, charcoal, front view" />
876 {[
877 { x: 47, y: 24, t: "Notch lapel, hand-padded with 1,100 stitches" },
878 { x: 57, y: 55, t: "Corozo & horn buttons, sewn with a shank" },
879 { x: 30, y: 70, t: "Flap pockets, lined in Bemberg cupro" },
880 ].map((h, i) => (
881 <button key={i} type="button" className="an-hot" style={{ left: `${h.x}%`, top: `${h.y}%` }} aria-label={h.t}>
882 <span aria-hidden="true">{i + 1}</span>
883 <em role="tooltip">{h.t}</em>
884 </button>
885 ))}
886 </div>
887 </div>
888 <div className="an-feature-text">
889 <p className="an-eyebrow" data-an-rv>The Sund Overcoat</p>
890 <h2 id="an-feature-title" className="an-h2 an-h2-xl" data-an-rv>Seven winters, minimum.</h2>
891 <p className="an-body" data-an-rv>A single-breasted coat in 680-gram herringbone, woven in Halland from wool shorn forty kilometres away. Canvassed through the chest, cut long enough to cover a blazer and roomy enough for a cable knit underneath.</p>
892 <dl className="an-specs" data-an-rv>
893 <div><dt>Cloth</dt><dd>100% wool herringbone, Kvarnby Spinneri</dd></div>
894 <div><dt>Lining</dt><dd>Bemberg cupro, body & sleeves</dd></div>
895 <div><dt>Fit</dt><dd>Relaxed. Model is 186 cm wearing M</dd></div>
896 <div><dt>Made</dt><dd>Guimarães, Portugal — 14 hours per coat</dd></div>
897 <div><dt>Care</dt><dd>Brush, air, steam. Free repairs for life</dd></div>
898 </dl>
899 <div className="an-feature-cta" data-an-rv>
900 <button type="button" className="an-btn an-btn-ink" onClick={() => addToCart(0, "M")}>Add to bag — {fmt(690)}</button>
901 <a href="#an-shop" className="an-link">Size guide</a>
902 </div>
903 </div>
904 </section>
905
906 {/* MATERIALS */}
907 <section className="an-mat" aria-labelledby="an-mat-title">
908 <div className="an-mat-head">
909 <p className="an-eyebrow" data-an-rv>(04) Materials</p>
910 <h2 id="an-mat-title" className="an-h2" data-an-rv>Four cloths. Four mills. We have visited every one.</h2>
911 </div>
912 <ul className="an-mat-grid">
913 {FABRICS.map((f, i) => (
914 <li key={f.name} className="an-mat-card" data-an-rv style={{ "--d": `${i * 80}ms` } as CSSProperties}>
915 <div className="an-mat-sw"><span className={`an-tx ${f.cls}`} /></div>
916 <h3 className="an-mat-name">{f.name}</h3>
917 <p className="an-mat-from">{f.from}</p>
918 <p className="an-mat-spec">{f.spec}</p>
919 </li>
920 ))}
921 </ul>
922 </section>
923
924 {/* JOURNAL */}
925 <section id="an-journal" className="an-journal" aria-labelledby="an-journal-title">
926 <div className="an-journal-head">
927 <h2 id="an-journal-title" className="an-h2 an-h2-xl" data-an-rv>Journal</h2>
928 <a href="#an-journal" className="an-link" data-an-rv>All stories</a>
929 </div>
930 <ul className="an-journal-grid">
931 {JOURNAL.map((j, i) => (
932 <li key={j.t} className="an-story" data-an-rv style={{ "--d": `${i * 90}ms` } as CSSProperties}>
933 <a href="#an-journal">
934 <div className="an-story-img an-grain"><Scene k={j.k} label="" /></div>
935 <p className="an-story-date">{j.date}</p>
936 <h3 className="an-story-t">{j.t}</h3>
937 </a>
938 </li>
939 ))}
940 </ul>
941 </section>
942 </main>
943
944 {/* FOOTER */}
945 <footer className="an-foot">
946 <div className="an-foot-top">
947 <form className="an-news" onSubmit={(e) => { e.preventDefault(); if (/\S+@\S+\.\S+/.test(email)) setJoined(true); }}>
948 <h2 className="an-news-t">Letters from the atelier</h2>
949 <p className="an-news-s">New collections a week early, sample sales in Stockholm and Copenhagen. Twice a season, never more.</p>
950 <label htmlFor="an-email" className="an-news-l">Email</label>
951 <div className="an-news-row">
952 <input id="an-email" type="email" required autoComplete="email" placeholder="name@domain.com" value={email} onChange={(e) => setEmail(e.target.value)} disabled={joined} />
953 <button type="submit" disabled={joined}>{joined ? "Subscribed ✓" : "Subscribe"}</button>
954 </div>
955 <p className="an-news-note" aria-live="polite">{joined ? `Tack — the next letter goes to ${email}.` : "By subscribing you accept our privacy policy."}</p>
956 </form>
957 <div className="an-foot-cols">
958 <div><p className="an-foot-h">Stores</p><ul><li>Hornsgatan 54, Stockholm</li><li>Ravnsborggade 9, København</li><li>Markveien 35, Oslo</li></ul></div>
959 <div><p className="an-foot-h">Help</p><ul>{["Shipping & returns", "Repairs", "Size guide", "Care", "Contact"].map((x) => <li key={x}><a href="#an-top">{x}</a></li>)}</ul></div>
960 <div><p className="an-foot-h">Atelier</p><ul>{["About", "Mills & makers", "Impact report 2025", "Careers", "Stockists"].map((x) => <li key={x}><a href="#an-top">{x}</a></li>)}</ul></div>
961 <div><p className="an-foot-h">Follow</p><ul>{["Instagram", "Pinterest", "Spotify — Stillhet playlist"].map((x) => <li key={x}><a href="#an-top">{x}</a></li>)}</ul></div>
962 </div>
963 </div>
964 <p className="an-foot-word" aria-hidden="true">ATELIER NORD</p>
965 <div className="an-foot-legal">
966 <span>© 2026 Atelier Nord AB · Org. nr 559301-4471 · Stockholm</span>
967 <span><a href="#an-top">Privacy</a><a href="#an-top">Terms</a><a href="#an-top">Cookies</a><a href="#an-top">EUR € / English</a></span>
968 </div>
969 </footer>
970
971 {/* CART DRAWER */}
972 <div className={`an-scrim${open ? " is-on" : ""}`} onClick={() => setOpen(false)} aria-hidden="true" />
973 <div ref={drawerRef} className={`an-drawer${open ? " is-open" : ""}`} role="dialog" aria-modal="true" aria-labelledby="an-bag-title" aria-hidden={!open} style={{ transform: "translate3d(104%,0,0)" }}>
974 <div className="an-drawer-head">
975 <h2 id="an-bag-title" className="an-drawer-t">Bag <span>({count})</span></h2>
976 <button ref={closeRef} type="button" className="an-x" onClick={() => setOpen(false)} aria-label="Close bag" tabIndex={open ? 0 : -1}><span /><span /></button>
977 </div>
978 <div className="an-ship">
979 <p>{subtotal >= FREE ? "You've unlocked free shipping." : `You're ${fmt(FREE - subtotal)} away from free shipping.`}</p>
980 <span className="an-ship-bar"><i style={{ transform: `scaleX(${Math.min(1, subtotal / FREE)})` }} /></span>
981 </div>
982 <ul className="an-lines">
983 {cart.length === 0 && <li className="an-empty">Your bag is empty.</li>}
984 {cart.map((c) => (
985 <li key={c.key} className="an-line">
986 <div className="an-line-img"><ProductShot kind={c.kind} color={c.color.hex} label="" /></div>
987 <div className="an-line-info">
988 <p className="an-line-name">{c.name}</p>
989 <p className="an-line-meta">{c.color.name} · {c.size}</p>
990 <div className="an-qty">
991 <button type="button" onClick={() => changeQty(c.key, -1)} aria-label={`Remove one ${c.name}`} tabIndex={open ? 0 : -1}>−</button>
992 <span>{c.qty}</span>
993 <button type="button" onClick={() => changeQty(c.key, 1)} aria-label={`Add one ${c.name}`} tabIndex={open ? 0 : -1}>+</button>
994 </div>
995 </div>
996 <p className="an-line-price">{fmt(c.price * c.qty)}</p>
997 </li>
998 ))}
999 </ul>
1000 <div className="an-drawer-foot">
1001 <p className="an-sub"><span>Subtotal</span><span>{fmt(subtotal)}</span></p>
1002 <p className="an-sub-note">Taxes included. Shipping calculated at checkout.</p>
1003 <button type="button" className="an-btn an-btn-ink an-btn-block" disabled={!cart.length} tabIndex={open ? 0 : -1}>Checkout</button>
1004 </div>
1005 </div>
1006 </div>
1007 );
1008}
1009
1010export default Page;
1011
1012/* ------------------------------------------------------------------ */
1013/* Styles */
1014/* ------------------------------------------------------------------ */
1015const GRAIN = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .55 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E")`;
1016
1017const css = String.raw`
1018.an {
1019 --an-paper: #F2F1EE;
1020 --an-white: #FFFFFF;
1021 --an-studio: #E4E2DD;
1022 --an-ink: #0E0E0E;
1023 --an-graphite: #2A2A2A;
1024 --an-grey: #66645F;
1025 --an-line: rgba(14, 14, 14, 0.14);
1026 --an-cond: "HelveticaNeue-CondensedBold", "Helvetica Neue Condensed Bold", "Helvetica Neue Condensed", "Arial Narrow", "Roboto Condensed", "Nimbus Sans Narrow", "TeX Gyre Heros Cn", "Liberation Sans Narrow", ui-sans-serif, sans-serif;
1027 --an-sans: "Helvetica Neue", Helvetica, ui-sans-serif, -apple-system, "Inter", "Segoe UI", Roboto, Arial, sans-serif;
1028 --fd-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
1029 --fd-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
1030 --fd-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
1031 --fd-dur-1: 120ms;
1032 --fd-dur-2: 240ms;
1033 --fd-dur-3: 420ms;
1034 --fd-dur-4: 700ms;
1035 --an-gut: 24px;
1036 position: relative;
1037 display: block;
1038 width: 100%;
1039 margin: 0;
1040 padding: 0;
1041 overflow-x: clip;
1042 background: var(--an-paper);
1043 color: var(--an-ink);
1044 font: 400 14px/1.5 var(--an-sans);
1045 letter-spacing: 0;
1046 text-align: left;
1047 -webkit-font-smoothing: antialiased;
1048 -moz-osx-font-smoothing: grayscale;
1049}
1050.an *, .an *::before, .an *::after {
1051 box-sizing: border-box;
1052 margin: 0;
1053 padding: 0;
1054 border: 0 solid transparent;
1055 font: inherit;
1056 color: inherit;
1057 letter-spacing: inherit;
1058 text-decoration: none;
1059 list-style: none;
1060 background: none;
1061 -webkit-appearance: none;
1062 appearance: none;
1063 min-width: 0;
1064 font-style: normal;
1065}
1066.an :where(svg) { display: block; }
1067.an :where(button) { cursor: pointer; text-align: inherit; line-height: inherit; border-radius: 0; }
1068.an :where(button:disabled) { cursor: not-allowed; }
1069.an :focus-visible { outline: 2px solid rgba(14, 14, 14, 0.7); outline-offset: 2px; }
1070.an [hidden] { display: none !important; }
1071.an-skip { position: absolute; left: 12px; top: -60px; z-index: 200; padding: 12px 16px; background: var(--an-ink); color: #fff; font: 500 13px/1 var(--an-sans); }
1072.an-skip:focus { top: 12px; }
1073.an-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
1074.an-grain { position: relative; }
1075.an-grain::after { content: ""; position: absolute; inset: 0; pointer-events: none; background-image: ${GRAIN}; background-size: 160px 160px; opacity: 0.2; mix-blend-mode: multiply; }
1076.an-shot, .an-scene { width: 100%; height: 100%; }
1077.an-fab { transition: fill var(--fd-dur-4) var(--fd-ease-in-out); }
1078.an-fab-stroke { transition: stroke var(--fd-dur-4) var(--fd-ease-in-out); }
1079
1080.an-eyebrow { font: 500 12px/1.4 var(--an-sans); letter-spacing: 0.1em; text-transform: uppercase; color: var(--an-grey); }
1081.an-h2 { font: 700 clamp(40px, 5vw, 76px)/0.88 var(--an-cond); font-stretch: condensed; letter-spacing: -0.015em; text-transform: uppercase; color: var(--an-ink); }
1082.an-h2-xl { font-size: clamp(64px, 9vw, 150px); line-height: 0.82; letter-spacing: -0.025em; }
1083.an-body { font: 400 16px/1.6 var(--an-sans); color: var(--an-graphite); }
1084.an-link { display: inline-flex; align-items: center; min-height: 44px; font: 500 13px/1 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-ink); background-image: linear-gradient(currentColor, currentColor); background-size: 100% 1px; background-repeat: no-repeat; background-position: 0 calc(50% + 10px); transition: background-size var(--fd-dur-3) var(--fd-ease-out); }
1085.an-link:hover { background-size: 0% 1px; background-position: 100% calc(50% + 10px); }
1086.an-btn { display: inline-flex; align-items: center; justify-content: center; gap: 12px; min-height: 52px; padding: 0 28px; font: 500 13px/1 var(--an-sans); letter-spacing: 0.1em; text-transform: uppercase; white-space: nowrap; transition: background-color var(--fd-dur-2) var(--fd-ease-out), color var(--fd-dur-2) var(--fd-ease-out), transform var(--fd-dur-2) var(--fd-spring); }
1087.an-btn span { transition: transform var(--fd-dur-3) var(--fd-ease-out); }
1088.an-btn:hover span { transform: translateX(4px); }
1089.an-btn:active { transform: scale(0.97); transition-duration: var(--fd-dur-1); }
1090.an-btn-ink { background: var(--an-ink); color: #FFFFFF; }
1091.an-btn-ink:hover { background: #333; }
1092.an-btn-ink:disabled { background: #9A9893; }
1093.an-btn-block { width: 100%; }
1094
1095/* Cursor label */
1096.an-cursor { position: absolute; left: 0; top: 0; z-index: 90; display: grid; place-items: center; width: 84px; height: 84px; margin: -42px 0 0 -42px; border-radius: 50%; background: var(--an-ink); color: #FFFFFF; font: 500 12px/1 var(--an-sans); letter-spacing: 0.12em; text-transform: uppercase; pointer-events: none; scale: 0; transition: scale var(--fd-dur-3) var(--fd-spring); will-change: transform; }
1097.an-cursor.is-on { scale: 1; }
1098
1099/* Announcement + NAV */
1100.an-announce { padding: 10px var(--an-gut); background: var(--an-ink); color: #EDEDEA; font: 400 12px/1.4 var(--an-sans); letter-spacing: 0.06em; text-align: center; }
1101.an-nav { position: sticky; top: 0; z-index: 70; }
1102.an-nav-in { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; height: 72px; padding: 0 var(--an-gut); background: var(--an-paper); border-bottom: 1px solid transparent; transition: height var(--fd-dur-3) var(--fd-ease-out), border-color var(--fd-dur-3) var(--fd-ease-out), background-color var(--fd-dur-3) var(--fd-ease-out); }
1103.an-nav.is-small .an-nav-in { height: 52px; border-bottom-color: var(--an-line); background: rgba(242, 241, 238, 0.9); -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px); }
1104.an-nav-l { display: flex; align-items: center; gap: 22px; }
1105.an-nav-l a, .an-nav-txt { display: inline-flex; align-items: center; min-height: 44px; font: 500 12px/1 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-ink); }
1106.an-nav-l a:hover, .an-nav-txt:hover { text-decoration: underline; text-underline-offset: 4px; }
1107.an-logo { font: 700 30px/1 var(--an-cond); font-stretch: condensed; letter-spacing: 0.04em; text-transform: uppercase; color: var(--an-ink); white-space: nowrap; transition: font-size var(--fd-dur-3) var(--fd-ease-out), letter-spacing var(--fd-dur-3) var(--fd-ease-out); }
1108.an-nav.is-small .an-logo { font-size: 22px; letter-spacing: 0.08em; }
1109.an-nav-r { display: flex; align-items: center; justify-content: flex-end; gap: 22px; }
1110.an-bag-btn { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; font: 500 12px/1 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-ink); }
1111.an-bag-n { display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 9999px; background: var(--an-ink); color: #FFFFFF; font: 500 11px/1 var(--an-sans); letter-spacing: 0; }
1112.an-bag-n.is-bump { animation: an-bump 600ms var(--fd-spring); }
1113@keyframes an-bump { 0% { transform: scale(1); } 30% { transform: scale(1.5); } 100% { transform: scale(1); } }
1114.an-burger { display: none; position: relative; width: 44px; height: 44px; margin-left: -10px; }
1115.an-burger span { position: absolute; left: 11px; right: 11px; height: 1.5px; background: var(--an-ink); transition: transform var(--fd-dur-3) var(--fd-ease-out); }
1116.an-burger span:first-child { top: 18px; }
1117.an-burger span:last-child { top: 25px; }
1118.an-nav.is-open .an-burger span:first-child { transform: translateY(3.5px) rotate(45deg); }
1119.an-nav.is-open .an-burger span:last-child { transform: translateY(-3.5px) rotate(-45deg); }
1120.an-sheet { display: flex; flex-direction: column; padding: 8px var(--an-gut) 24px; background: var(--an-paper); border-bottom: 1px solid var(--an-line); }
1121.an-sheet a { padding: 10px 0; font: 700 44px/1 var(--an-cond); font-stretch: condensed; text-transform: uppercase; color: var(--an-ink); }
1122
1123/* MASTHEAD */
1124.an-mast { position: relative; display: grid; grid-template-columns: 1fr; grid-template-rows: auto 1fr auto auto; min-height: calc(100vh - 110px); min-height: calc(100svh - 110px); padding: 20px var(--an-gut) 28px; --p: 0; overflow: hidden; }
1125.an-mast-meta { display: flex; justify-content: space-between; gap: 24px; font: 500 12px/1.5 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-ink); position: relative; z-index: 3; }
1126.an-mast > .an-word { isolation: auto; }
1127.an-mast-meta em { font-style: italic; }
1128.an-mast-meta-r { text-align: right; color: var(--an-grey); }
1129.an-mast-img { position: absolute; right: 9vw; top: 84px; z-index: 2; width: 24vw; margin: 0; transform: translate3d(0, calc(var(--p) * 120px), 0); }
1130.an-mast-img-in { aspect-ratio: 3 / 4; overflow: hidden; background: var(--an-studio); }
1131.an-mast-img-in .an-scene { transform: scale(calc(1.12 + var(--p) * 0.12)); transform-origin: 50% 30%; }
1132.an-mast-img figcaption { margin-top: 10px; font: 500 11px/1.4 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-grey); }
1133.an-word { grid-row: 3; position: relative; z-index: 3; mix-blend-mode: difference; color: #FFFFFF; display: flex; flex-direction: column; font: 700 22.6vw/0.78 var(--an-cond); font-stretch: condensed; letter-spacing: -0.02em; text-transform: uppercase; margin: 0; }
1134.an-word-line { display: flex; width: max-content; white-space: nowrap; }
1135.an-word-line-2 { justify-content: flex-start; padding-left: 0; }
1136.an-ch { display: inline-block; overflow: hidden; padding: 0.04em 0 0.02em; margin: -0.04em 0 -0.02em; transform: translate3d(0, calc(var(--p) * var(--dy) * 1px), 0) rotate(calc(var(--p) * var(--rot) * 1deg)); will-change: transform; }
1137.an-ch > span { display: inline-block; animation: an-rise 1.1s var(--fd-ease-out) both; animation-delay: calc(var(--i) * 45ms + 120ms); }
1138@keyframes an-rise { from { transform: translateY(102%); } to { transform: none; } }
1139.an-mast-foot { grid-row: 4; position: relative; z-index: 3; display: flex; align-items: flex-end; justify-content: space-between; gap: 32px; margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--an-ink); }
1140.an-mast-foot p { max-width: 420px; font: 400 16px/1.5 var(--an-sans); color: var(--an-graphite); }
1141
1142/* STATEMENT */
1143.an-statement { display: grid; grid-template-columns: 1fr 3fr; gap: var(--an-gut); padding: 160px var(--an-gut) 180px; }
1144.an-statement-n { font: 500 12px/1.6 var(--an-sans); letter-spacing: 0.08em; color: var(--an-grey); }
1145.an-statement-t { font: 400 clamp(28px, 3.4vw, 52px)/1.12 var(--an-sans); letter-spacing: -0.03em; color: var(--an-ink); text-indent: 12%; }
1146.an-statement-t em { font-style: italic; }
1147.an-inline-img { display: inline-block; width: 1.9em; height: 0.82em; margin: 0 0.12em; vertical-align: -0.06em; overflow: hidden; border-radius: 9999px; background: #2E2E2E; }
1148.an-inline-img .an-scene { width: 100%; height: 100%; }
1149
1150/* REVEALS */
1151.an-js [data-an-rv] { opacity: 0; transform: translate3d(0, 32px, 0); transition: opacity 900ms var(--fd-ease-out), transform 1s var(--fd-ease-out); transition-delay: var(--d, 0ms); }
1152.an-js [data-an-rv].is-in { opacity: 1; transform: none; }
1153
1154/* LOOKBOOK */
1155.an-look { position: relative; background: var(--an-paper); --e: 1; }
1156.an-look.is-zoom { height: 320vh; height: 320svh; --e: 0; }
1157.an-look-stage { position: relative; padding: 80px var(--an-gut); }
1158.an-look.is-zoom .an-look-stage { position: sticky; top: 0; height: 100vh; height: 100svh; padding: 0 var(--an-gut); overflow: hidden; display: flex; align-items: center; justify-content: center; }
1159.an-look-head { position: relative; z-index: 3; display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 32px; font: 500 12px/1.4 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-grey); }
1160.an-look.is-zoom .an-look-head { position: absolute; left: var(--an-gut); right: var(--an-gut); top: 72px; margin: 0; opacity: calc((var(--e) - 0.75) * 4); transform: translate3d(0, calc((1 - var(--e)) * 30px), 0); }
1161.an-look-grid { position: relative; display: grid; grid-template-columns: repeat(12, 1fr); grid-template-rows: repeat(6, minmax(0, 1fr)); gap: 14px; width: 100%; max-width: 1400px; height: min(66vh, 640px); will-change: transform; }
1162.an-tile { position: relative; margin: 0; transform: translate3d(0, calc((1 - var(--e)) * var(--k) * 1px), 0); }
1163.an-tile-img { width: 100%; height: 100%; overflow: hidden; background: var(--an-studio); }
1164.an-tile-cap { position: absolute; left: 0; bottom: -22px; font: 500 11px/1.4 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-grey); white-space: nowrap; opacity: calc((var(--e) - 0.8) * 5); }
1165.an-tile-0 { grid-column: 1 / 3; grid-row: 1 / 4; }
1166.an-tile-1 { grid-column: 1 / 4; grid-row: 4 / 7; }
1167.an-tile-2 { grid-column: 3 / 5; grid-row: 1 / 4; }
1168.an-tile-3 { grid-column: 5 / 9; grid-row: 2 / 6; }
1169.an-tile-4 { grid-column: 9 / 11; grid-row: 1 / 4; }
1170.an-tile-5 { grid-column: 11 / 13; grid-row: 1 / 5; }
1171.an-tile-6 { grid-column: 9 / 11; grid-row: 4 / 7; }
1172.an-tile-1, .an-tile-6 { margin-top: 18px; }
1173.an-look-over { position: absolute; z-index: 2; left: 50%; top: 50%; font: 700 15vw/0.8 var(--an-cond); font-stretch: condensed; text-transform: uppercase; letter-spacing: -0.02em; color: #FFFFFF; mix-blend-mode: difference; pointer-events: none; white-space: nowrap; transform: translate3d(-50%, -50%, 0) scale(calc(1 + (1 - var(--e)) * 0.25)); opacity: calc(1 - var(--e) * 2.4); }
1174.an-look:not(.is-zoom) .an-look-over { display: none; }
1175
1176/* SHOP */
1177.an-shop { padding: 140px var(--an-gut) 120px; background: var(--an-white); }
1178.an-shop-head { display: grid; grid-template-columns: 1.4fr 1fr auto; align-items: end; gap: var(--an-gut); margin-bottom: 72px; }
1179.an-shop-head p { max-width: 380px; font: 400 15px/1.55 var(--an-sans); color: var(--an-graphite); }
1180.an-grid { display: grid; grid-template-columns: repeat(12, 1fr); column-gap: var(--an-gut); row-gap: 72px; align-items: start; }
1181.an-p-a { grid-column: 1 / 6; }
1182.an-p-b { grid-column: 7 / 10; margin-top: 160px; }
1183.an-p-c { grid-column: 10 / 13; margin-top: 40px; }
1184.an-p-d { grid-column: 2 / 5; }
1185.an-p-e { grid-column: 6 / 9; margin-top: 120px; }
1186.an-p-f { grid-column: 9 / 13; margin-top: -60px; }
1187.an-card-media { position: relative; overflow: hidden; background: var(--an-studio); cursor: none; }
1188.an-card-img { display: block; aspect-ratio: 4 / 5; overflow: hidden; }
1189.an-card-img .an-shot { transition: transform 1.2s var(--fd-ease-out); }
1190.an-card-media:hover .an-card-img .an-shot { transform: scale(1.04); }
1191.an-tag { position: absolute; left: 14px; top: 14px; padding: 6px 10px; background: var(--an-white); font: 500 11px/1 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-ink); }
1192.an-card-info { display: flex; justify-content: space-between; gap: 16px; margin-top: 14px; }
1193.an-card-name { font: 500 14px/1.35 var(--an-sans); letter-spacing: 0.04em; text-transform: uppercase; color: var(--an-ink); }
1194.an-card-fab { margin-top: 3px; font: 400 13px/1.4 var(--an-sans); color: var(--an-grey); }
1195.an-card-col { display: inline-block; animation: an-fadein var(--fd-dur-3) var(--fd-ease-out); }
1196.an-card-price { font: 500 14px/1.35 var(--an-sans); color: var(--an-ink); font-variant-numeric: tabular-nums; }
1197.an-sw { display: flex; gap: 4px; margin-top: 8px; margin-left: -8px; }
1198.an-sw button { position: relative; width: 36px; height: 36px; border-radius: 50%; }
1199.an-sw button::before { content: ""; position: absolute; inset: 10px; border-radius: 50%; background: var(--c); box-shadow: inset 0 0 0 1px rgba(14, 14, 14, 0.18); transition: inset var(--fd-dur-2) var(--fd-spring); }
1200.an-sw button::after { content: ""; position: absolute; inset: 6px; border-radius: 50%; box-shadow: 0 0 0 1px var(--an-ink); opacity: 0; transform: scale(0.7); transition: opacity var(--fd-dur-2) var(--fd-ease-out), transform var(--fd-dur-3) var(--fd-spring); }
1201.an-sw button.is-on::after { opacity: 1; transform: none; }
1202.an-sw button:hover::before { inset: 9px; }
1203@keyframes an-fadein { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
1204
1205/* Quick add morph */
1206.an-qa { position: absolute; left: 12px; right: 12px; bottom: 12px; height: 48px; display: flex; align-items: stretch; background: var(--an-white); clip-path: inset(0 calc(100% - 132px) 0 0); transform: translateY(8px); opacity: 0; transition: clip-path var(--fd-dur-3) var(--fd-ease-out), transform var(--fd-dur-3) var(--fd-ease-out), opacity var(--fd-dur-2) var(--fd-ease-out), background-color var(--fd-dur-3) var(--fd-ease-out); }
1207.an-card-media:hover .an-qa, .an-qa:focus-within, .an-qa.is-pick, .an-qa.is-added { opacity: 1; transform: none; }
1208.an-qa.is-pick { clip-path: inset(0 0 0 0); }
1209.an-qa.is-added { clip-path: inset(0 calc(100% - 132px) 0 0); background: var(--an-ink); color: #FFFFFF; }
1210.an-qa-main { flex: none; display: flex; align-items: center; gap: 10px; width: 132px; padding: 0 16px; font: 500 12px/1 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: inherit; }
1211.an-qa-main:disabled { cursor: default; }
1212.an-qa-plus { position: relative; width: 12px; height: 12px; flex: none; transition: transform var(--fd-dur-3) var(--fd-ease-out); }
1213.an-qa-plus::before, .an-qa-plus::after { content: ""; position: absolute; left: 0; top: 5.25px; width: 12px; height: 1.5px; background: currentColor; transition: transform var(--fd-dur-3) var(--fd-ease-out), width var(--fd-dur-3) var(--fd-ease-out); }
1214.an-qa-plus::after { transform: rotate(90deg); }
1215.an-qa.is-pick .an-qa-plus { transform: rotate(45deg); }
1216.an-qa.is-added .an-qa-plus { transform: rotate(-45deg) translate(1px, -1px); }
1217.an-qa.is-added .an-qa-plus::before { width: 6px; top: 8px; left: 0; transform: rotate(90deg) translateX(-3px); transform-origin: left; }
1218.an-qa.is-added .an-qa-plus::after { width: 12px; top: 8px; transform: none; }
1219.an-qa-sizes { flex: 1; display: grid; grid-template-columns: repeat(5, 1fr); border-left: 1px solid var(--an-line); }
1220.an-qa-sizes button { font: 500 12px/1 var(--an-sans); letter-spacing: 0.06em; color: var(--an-ink); text-align: center; opacity: 0; transform: translateX(-8px); transition: opacity var(--fd-dur-2) var(--fd-ease-out), transform var(--fd-dur-3) var(--fd-ease-out), background-color var(--fd-dur-1) var(--fd-ease-out); }
1221.an-qa.is-pick .an-qa-sizes button { opacity: 1; transform: none; }
1222.an-qa.is-pick .an-qa-sizes button:nth-child(2) { transition-delay: 30ms; }
1223.an-qa.is-pick .an-qa-sizes button:nth-child(3) { transition-delay: 60ms; }
1224.an-qa.is-pick .an-qa-sizes button:nth-child(4) { transition-delay: 90ms; }
1225.an-qa.is-pick .an-qa-sizes button:nth-child(5) { transition-delay: 120ms; }
1226.an-qa-sizes button:hover:not(:disabled) { background: var(--an-paper); }
1227.an-qa-sizes button:disabled { color: #8D8B86; text-decoration: line-through; }
1228
1229/* PRESS */
1230.an-press { padding: 56px 0; background: var(--an-ink); color: #F2F1EE; overflow: hidden; }
1231.an-marquee-track { display: flex; width: max-content; animation: an-marq 60s linear infinite; }
1232.an-press:hover .an-marquee-track { animation-play-state: paused; }
1233.an-marquee-set { display: flex; }
1234.an-marquee-set li { display: flex; align-items: baseline; gap: 18px; padding-right: 18px; white-space: nowrap; }
1235.an-marquee-set blockquote { font: 700 clamp(40px, 5vw, 72px)/1 var(--an-cond); font-stretch: condensed; letter-spacing: -0.01em; text-transform: uppercase; }
1236.an-marquee-set cite { font: 500 12px/1 var(--an-sans); letter-spacing: 0.1em; text-transform: uppercase; color: #B9B7B2; }
1237.an-star { font: 400 36px/1 var(--an-sans); color: #8C8A85; margin-left: 40px; }
1238@keyframes an-marq { to { transform: translateX(-50%); } }
1239
1240/* FEATURE */
1241.an-feature { display: grid; grid-template-columns: 7fr 5fr; gap: var(--an-gut); padding: 140px var(--an-gut); background: var(--an-paper); }
1242.an-feature-media { position: sticky; top: 76px; align-self: start; cursor: none; }
1243.an-feature-img { aspect-ratio: 4 / 5; overflow: hidden; background: var(--an-studio); max-height: calc(100vh - 100px); margin: 0 auto; }
1244.an-hot { position: absolute; z-index: 2; display: grid; place-items: center; width: 44px; height: 44px; margin: -22px 0 0 -22px; cursor: pointer; }
1245.an-hot span { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: #FFFFFF; color: var(--an-ink); font: 500 12px/1 var(--an-sans); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.6); animation: an-ping 2.4s var(--fd-ease-out) infinite; }
1246@keyframes an-ping { 0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.6); } 80%, 100% { box-shadow: 0 0 0 14px rgba(255, 255, 255, 0); } }
1247.an-hot em { position: absolute; left: 50%; bottom: calc(100% + 4px); width: 220px; padding: 10px 12px; background: #FFFFFF; color: var(--an-ink); font: 400 13px/1.4 var(--an-sans); text-align: left; opacity: 0; transform: translate(-50%, 6px); pointer-events: none; transition: opacity var(--fd-dur-2) var(--fd-ease-out), transform var(--fd-dur-3) var(--fd-ease-out); }
1248.an-hot:hover em, .an-hot:focus-visible em { opacity: 1; transform: translate(-50%, 0); }
1249.an-feature-text { padding: 40px 0 0 4vw; }
1250.an-feature-text .an-h2 { margin: 20px 0 32px; }
1251.an-feature-text .an-body { max-width: 460px; }
1252.an-specs { margin-top: 48px; border-top: 1px solid var(--an-ink); }
1253.an-specs div { display: grid; grid-template-columns: 110px 1fr; gap: 16px; padding: 16px 0; border-bottom: 1px solid var(--an-line); }
1254.an-specs dt { font: 500 12px/1.5 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-grey); }
1255.an-specs dd { font: 400 15px/1.5 var(--an-sans); color: var(--an-ink); }
1256.an-feature-cta { display: flex; align-items: center; gap: 28px; margin-top: 40px; }
1257
1258/* MATERIALS */
1259.an-mat { padding: 140px var(--an-gut); background: var(--an-white); }
1260.an-mat-head { display: grid; grid-template-columns: 1fr 2fr; gap: var(--an-gut); margin-bottom: 64px; }
1261.an-mat-head .an-h2 { max-width: 14ch; }
1262.an-mat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--an-gut); }
1263.an-mat-sw { aspect-ratio: 1; overflow: hidden; background: var(--an-studio); }
1264.an-tx { display: block; width: 100%; height: 100%; transition: transform 1.2s var(--fd-ease-out); }
1265.an-mat-card:hover .an-tx { transform: scale(1.5); }
1266.an-tx-hb { background-color: #3A3A3A; }
1267.an-tx-hb { background-image: linear-gradient(90deg, transparent 50%, rgba(0,0,0,.06) 50%), repeating-linear-gradient(45deg, rgba(255,255,255,.1) 0 2px, transparent 2px 6px), repeating-linear-gradient(-45deg, rgba(255,255,255,.1) 0 2px, transparent 2px 6px); background-size: 28px 100%, 28px 28px, 28px 28px; }
1268.an-tx-knit { background-color: #E6E0D3; background-image: radial-gradient(ellipse 5px 9px at 50% 50%, rgba(0,0,0,.14) 60%, transparent 62%), radial-gradient(ellipse 5px 9px at 50% 50%, rgba(255,255,255,.5) 40%, transparent 42%); background-size: 12px 14px, 12px 14px; background-position: 0 0, 6px 7px; }
1269.an-tx-boucle { background-color: #C9C9C6; background-image: radial-gradient(circle at 30% 30%, rgba(255,255,255,.7) 0 2px, transparent 3px), radial-gradient(circle at 70% 60%, rgba(0,0,0,.18) 0 3px, transparent 4px), radial-gradient(circle at 50% 80%, rgba(255,255,255,.5) 0 2.5px, transparent 3.5px), radial-gradient(circle at 20% 70%, rgba(0,0,0,.12) 0 2px, transparent 3px); background-size: 11px 11px, 13px 13px, 9px 9px, 15px 15px; }
1270.an-tx-poplin { background-color: #F1EEE7; background-image: repeating-linear-gradient(0deg, rgba(0,0,0,.05) 0 1px, transparent 1px 3px), repeating-linear-gradient(90deg, rgba(0,0,0,.04) 0 1px, transparent 1px 3px), linear-gradient(120deg, rgba(255,255,255,.6), rgba(0,0,0,.06)); }
1271.an-mat-name { margin-top: 18px; font: 500 14px/1.35 var(--an-sans); letter-spacing: 0.04em; text-transform: uppercase; color: var(--an-ink); }
1272.an-mat-from { margin-top: 4px; font: 400 14px/1.4 var(--an-sans); color: var(--an-graphite); }
1273.an-mat-spec { margin-top: 2px; font: 400 13px/1.4 var(--an-sans); color: var(--an-grey); }
1274
1275/* JOURNAL */
1276.an-journal { padding: 140px var(--an-gut); background: var(--an-paper); }
1277.an-journal-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 56px; }
1278.an-journal-grid { display: grid; grid-template-columns: 5fr 3fr 4fr; gap: var(--an-gut); align-items: start; }
1279.an-story:nth-child(2) { margin-top: 120px; }
1280.an-story:nth-child(3) { margin-top: 40px; }
1281.an-story a { display: block; color: var(--an-ink); }
1282.an-story-img { aspect-ratio: 4 / 5; overflow: hidden; background: var(--an-studio); }
1283.an-story:first-child .an-story-img { aspect-ratio: 5 / 4; }
1284.an-story-img .an-scene { transition: transform 1.2s var(--fd-ease-out); }
1285.an-story a:hover .an-story-img .an-scene { transform: scale(1.05); }
1286.an-story-date { margin-top: 16px; font: 500 12px/1.4 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--an-grey); }
1287.an-story-t { margin-top: 8px; font: 400 22px/1.25 var(--an-sans); letter-spacing: -0.02em; color: var(--an-ink); max-width: 30ch; }
1288.an-story a:hover .an-story-t { text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; }
1289
1290/* FOOTER */
1291.an-foot { padding: 96px var(--an-gut) 28px; background: var(--an-ink); color: #EDEDEA; overflow: hidden; }
1292.an-foot-top { display: grid; grid-template-columns: 5fr 7fr; gap: 64px; }
1293.an-news-t { font: 700 44px/0.9 var(--an-cond); font-stretch: condensed; text-transform: uppercase; color: #FFFFFF; }
1294.an-news-s { margin-top: 14px; max-width: 420px; font: 400 15px/1.55 var(--an-sans); color: #BDBBB6; }
1295.an-news-l { display: block; margin-top: 28px; font: 500 12px/1 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: #BDBBB6; }
1296.an-news-row { display: flex; margin-top: 10px; border-bottom: 1px solid #EDEDEA; }
1297.an-news-row input { flex: 1; height: 52px; font: 400 18px/1 var(--an-sans); color: #FFFFFF; outline: none; }
1298.an-news-row input::placeholder { color: #8C8A85; }
1299.an-news-row button { padding: 0 4px 0 16px; font: 500 12px/1 var(--an-sans); letter-spacing: 0.1em; text-transform: uppercase; color: #FFFFFF; min-height: 52px; }
1300.an-news-row:focus-within { border-bottom-color: #FFFFFF; box-shadow: 0 1px 0 #FFFFFF; }
1301.an-news-note { margin-top: 10px; font: 400 12px/1.5 var(--an-sans); color: #A7A5A0; }
1302.an-foot-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--an-gut); }
1303.an-foot-h { margin-bottom: 14px; font: 500 12px/1 var(--an-sans); letter-spacing: 0.08em; text-transform: uppercase; color: #A7A5A0; }
1304.an-foot-cols li { margin-bottom: 8px; font: 400 14px/1.45 var(--an-sans); color: #EDEDEA; }
1305.an-foot-cols a:hover { text-decoration: underline; text-underline-offset: 3px; }
1306.an-foot-word { margin: 96px -0.02em 0; font: 700 16.2vw/0.78 var(--an-cond); font-stretch: condensed; letter-spacing: -0.02em; color: #FFFFFF; white-space: nowrap; text-align: center; }
1307.an-foot-legal { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; margin-top: 28px; padding-top: 18px; border-top: 1px solid rgba(237, 237, 234, 0.2); font: 400 12px/1.5 var(--an-sans); color: #A7A5A0; }
1308.an-foot-legal span:last-child { display: flex; flex-wrap: wrap; gap: 18px; }
1309.an-foot-legal a:hover { color: #FFFFFF; }
1310
1311/* DRAWER */
1312.an-scrim { position: fixed; inset: 0; z-index: 95; background: rgba(14, 14, 14, 0.4); opacity: 0; pointer-events: none; transition: opacity var(--fd-dur-3) var(--fd-ease-out); }
1313.an-scrim.is-on { opacity: 1; pointer-events: auto; }
1314.an-drawer { position: fixed; top: 0; right: 0; bottom: 0; z-index: 100; display: flex; flex-direction: column; width: min(440px, 100vw); background: var(--an-white); box-shadow: -30px 0 60px -30px rgba(0, 0, 0, 0.35); will-change: transform; visibility: hidden; transition: visibility 0s linear 600ms; }
1315.an-drawer.is-open { visibility: visible; transition: none; }
1316.an-drawer-head { display: flex; align-items: center; justify-content: space-between; height: 72px; padding: 0 var(--an-gut); border-bottom: 1px solid var(--an-line); }
1317.an-drawer-t { font: 700 32px/1 var(--an-cond); font-stretch: condensed; text-transform: uppercase; color: var(--an-ink); }
1318.an-drawer-t span { color: var(--an-grey); }
1319.an-x { position: relative; width: 44px; height: 44px; margin-right: -10px; }
1320.an-x span { position: absolute; left: 12px; right: 12px; top: 21px; height: 1.5px; background: var(--an-ink); transform: rotate(45deg); }
1321.an-x span:last-child { transform: rotate(-45deg); }
1322.an-ship { padding: 16px var(--an-gut); border-bottom: 1px solid var(--an-line); font: 400 13px/1.4 var(--an-sans); color: var(--an-graphite); }
1323.an-ship-bar { position: relative; display: block; height: 2px; margin-top: 10px; background: var(--an-line); }
1324.an-ship-bar i { position: absolute; inset: 0; background: var(--an-ink); transform-origin: left; transition: transform var(--fd-dur-4) var(--fd-ease-out); }
1325.an-lines { flex: 1; overflow-y: auto; padding: 8px var(--an-gut); }
1326.an-empty { padding: 40px 0; font: 400 15px/1.5 var(--an-sans); color: var(--an-grey); }
1327.an-line { display: grid; grid-template-columns: 84px 1fr auto; gap: 16px; padding: 16px 0; border-bottom: 1px solid var(--an-line); animation: an-fadein var(--fd-dur-3) var(--fd-ease-out); }
1328.an-line-img { aspect-ratio: 4 / 5; overflow: hidden; background: var(--an-studio); }
1329.an-line-name { font: 500 13px/1.35 var(--an-sans); letter-spacing: 0.04em; text-transform: uppercase; color: var(--an-ink); }
1330.an-line-meta { margin-top: 4px; font: 400 13px/1.4 var(--an-sans); color: var(--an-grey); }
1331.an-qty { display: inline-flex; align-items: center; margin-top: 12px; border: 1px solid var(--an-line); }
1332.an-qty button { width: 36px; height: 36px; font: 400 16px/1 var(--an-sans); color: var(--an-ink); text-align: center; }
1333.an-qty button:hover { background: var(--an-paper); }
1334.an-qty span { min-width: 24px; font: 500 13px/1 var(--an-sans); text-align: center; font-variant-numeric: tabular-nums; }
1335.an-line-price { font: 500 13px/1.35 var(--an-sans); color: var(--an-ink); font-variant-numeric: tabular-nums; }
1336.an-drawer-foot { padding: 20px var(--an-gut) 24px; border-top: 1px solid var(--an-line); }
1337.an-sub { display: flex; justify-content: space-between; font: 500 14px/1 var(--an-sans); letter-spacing: 0.06em; text-transform: uppercase; color: var(--an-ink); }
1338.an-sub-note { margin: 8px 0 16px; font: 400 12px/1.4 var(--an-sans); color: var(--an-grey); }
1339
1340/* RESPONSIVE */
1341@media (max-width: 1080px) {
1342 .an-nav-l a, .an-nav-txt { display: none; }
1343 .an-burger { display: block; }
1344 .an-mast-img { width: 32vw; right: 6vw; }
1345 .an-shop-head { grid-template-columns: 1fr 1fr; }
1346 .an-shop-head .an-link { grid-column: 2; }
1347 .an-mat-grid { grid-template-columns: repeat(2, 1fr); row-gap: 40px; }
1348 .an-foot-top { grid-template-columns: 1fr; }
1349}
1350@media (max-width: 760px) {
1351 .an { --an-gut: 16px; }
1352 .an-announce { font-size: 11px; }
1353 .an-nav-in { height: 60px; }
1354 .an-logo { font-size: 24px; }
1355 .an-nav.is-small .an-logo { font-size: 20px; }
1356 .an-mast { min-height: 0; padding-top: 16px; grid-template-rows: auto auto auto auto; }
1357 .an-mast-meta-r { display: none; }
1358 .an-mast-img { position: relative; right: auto; top: auto; grid-row: 2; width: 72%; margin: 20px 0 0 auto; transform: translate3d(0, calc(var(--p) * 60px), 0); }
1359 .an-word { font-size: 25.4vw; margin-top: -18vw; }
1360 .an-mast-foot { flex-direction: column; align-items: flex-start; gap: 18px; }
1361 .an-mast-foot .an-btn { width: 100%; }
1362 .an-statement { grid-template-columns: 1fr; padding: 96px var(--an-gut); }
1363 .an-statement-t { text-indent: 0; font-size: 28px; }
1364 .an-look-stage { padding: 72px var(--an-gut); }
1365 .an-look-head { flex-direction: column; align-items: flex-start; gap: 8px; }
1366 .an-look-grid { grid-template-columns: repeat(2, 1fr); grid-template-rows: none; grid-auto-rows: 200px; height: auto; gap: 10px; }
1367 .an-tile { grid-column: auto !important; grid-row: auto !important; margin: 0 !important; transform: none; }
1368 .an-tile-3 { grid-column: 1 / -1 !important; grid-row: span 2 !important; order: -1; }
1369 .an-tile-cap { position: static; margin-top: 6px; opacity: 1; }
1370 .an-look-grid { row-gap: 34px; }
1371 .an-shop { padding: 96px var(--an-gut); }
1372 .an-shop-head { grid-template-columns: 1fr; gap: 16px; margin-bottom: 48px; }
1373 .an-shop-head .an-link { grid-column: auto; }
1374 .an-grid { grid-template-columns: 1fr 1fr; column-gap: 10px; row-gap: 40px; }
1375 .an-card { grid-column: auto !important; margin-top: 0 !important; }
1376 .an-p-a, .an-p-f { grid-column: 1 / -1 !important; }
1377 .an-card-name { font-size: 12px; }
1378 .an-card-fab { font-size: 12px; }
1379 .an-card-info { flex-direction: column; gap: 4px; }
1380 .an-qa { opacity: 1; transform: none; left: 8px; right: 8px; bottom: 8px; height: 44px; clip-path: inset(0 calc(100% - 44px) 0 0); }
1381 .an-qa-main { width: 44px; padding: 0 16px; }
1382 .an-qa-txt { display: none; }
1383 .an-qa.is-pick { clip-path: inset(0 0 0 0); height: auto; flex-wrap: wrap; }
1384 .an-qa.is-pick .an-qa-sizes { flex-basis: 100%; grid-template-columns: repeat(3, 1fr); border-left: 0; border-top: 1px solid var(--an-line); }
1385 .an-qa.is-pick .an-qa-sizes button { min-height: 44px; }
1386 .an-qa.is-added { clip-path: inset(0 calc(100% - 44px) 0 0); }
1387 .an-mast-img figcaption { display: none; }
1388 .an-journal-head .an-link { white-space: nowrap; }
1389 .an-card-media { cursor: auto; }
1390 .an-feature { grid-template-columns: 1fr; padding: 96px var(--an-gut); }
1391 .an-feature-media { position: relative; top: auto; }
1392 .an-feature-text { padding: 32px 0 0; }
1393 .an-feature-cta { flex-direction: column; align-items: stretch; gap: 8px; }
1394 .an-hot em { width: 180px; }
1395 .an-mat { padding: 96px var(--an-gut); }
1396 .an-mat-head { grid-template-columns: 1fr; gap: 16px; margin-bottom: 40px; }
1397 .an-mat-grid { gap: 10px; row-gap: 32px; }
1398 .an-journal { padding: 96px var(--an-gut); }
1399 .an-journal-grid { grid-template-columns: 1fr; gap: 40px; }
1400 .an-story:nth-child(n) { margin-top: 0; }
1401 .an-story-t { font-size: 20px; }
1402 .an-foot { padding-top: 72px; }
1403 .an-foot-cols { grid-template-columns: 1fr 1fr; row-gap: 32px; }
1404 .an-foot-word { margin-top: 64px; }
1405 .an-specs div { grid-template-columns: 90px 1fr; }
1406}
1407@media (hover: none) {
1408 .an-cursor { display: none; }
1409 .an-card-media, .an-feature-media { cursor: auto; }
1410 .an-qa { opacity: 1; transform: none; }
1411}
1412@media (prefers-reduced-motion: reduce) {
1413 .an *, .an *::before, .an *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; animation-delay: 0ms !important; transition-duration: 120ms !important; transition-delay: 0ms !important; }
1414 .an-ch, .an-mast-img, .an-mast-img-in .an-scene, .an-tile { transform: none !important; }
1415 .an-js [data-an-rv] { transform: none; }
1416 .an-marquee-track { animation: none !important; width: auto; }
1417 .an-marquee-set[aria-hidden="true"] { display: none; }
1418 .an-marquee-set { flex-direction: column; gap: 24px; padding: 0 var(--an-gut); }
1419 .an-marquee-set li { white-space: normal; flex-wrap: wrap; }
1420 .an-star { display: none; }
1421}
1422@media (prefers-contrast: more) {
1423 .an-nav.is-small .an-nav-in { background: var(--an-paper); -webkit-backdrop-filter: none; backdrop-filter: none; border-bottom-color: var(--an-ink); }
1424 .an-grain::after { display: none; }
1425}
1426@media (prefers-reduced-transparency: reduce) {
1427 .an-nav.is-small .an-nav-in { background: var(--an-paper); -webkit-backdrop-filter: none; backdrop-filter: none; }
1428}
1429`;