SEVRA Luxury Fashion House AI website prompt
A premium fashion e-commerce landing page for SEVRA’s Autumn/Winter 2026 collection, showcasing ready-to-wear clothing, tailoring, outerwear, knitwear, and dresses. The page combines editorial campaign visuals with product browsing, collection details, brand/atelier storytelling, and made-to-order services.
Now put it in WordPress
You have a site. Now make it yours.
An AI build hands you a front end and stops there. No CMS, no client login, no real ownership: you cannot hand it over, and you cannot change it without going back and re-prompting. UiChemy turns that build into native, editable WordPress you own, in the builder you already use.
Bolt to WordPressClaude to WordPressCodex to WordPressCursor to WordPressLovable to WordPressv0 to WordPressAI to WordPress
Get started free
More prompts
Showing 2 prompts
Frequently asked questions
What does the SEVRA Luxury Fashion House prompt build?
A premium fashion e-commerce landing page for SEVRA’s Autumn/Winter 2026 collection, showcasing ready-to-wear clothing, tailoring, outerwear, knitwear, and dresses. The page combines editorial campaign visuals with product browsing, collection details, brand/atelier storytelling, and made-to-order services.
Which AI tools does the SEVRA Luxury Fashion House prompt work with?
It's written to work with . Paste it into your AI tool and it builds the site, then move the result into WordPress with UiChemy.
How do I turn the result into a WordPress site?
After the AI builds your site, UiChemy Composer rebuilds it as native, editable WordPress in Elementor or Gutenberg (Bricks coming soon) — you own it and edit it in your builder. See AI to WordPress.
Is the SEVRA Luxury Fashion House prompt free?
Yes — it's free to copy, no account needed.
Can I edit the website after it's built?
Yes. Once it's in WordPress with UiChemy, every section is native and editable — no re-prompting, no lock-in.
Copy prompt
Copy code
Coming soon
Copy SKILL.md
Coming soon
SEVRA Luxury Fashion House · Section prompt
# MASTER PROMPT — SEVRA — Autumn/Winter 26 (fully self-contained)
## ▶ HOW TO USE THIS PROMPT (do this first)
**Paste this entire document into Claude — nothing to attach.** Every asset (images, models, fonts) loads from a public URL baked into the code below.
This document has **two halves:**
- **PART A — the specification**: describes the page as documentation.
- **PART B — the appendix** (`APPENDIX · VERBATIM SOURCE`): the **complete source of every file**. **This is the half that guarantees the identical page.**
**If you want the exact same result: go to the appendix and write those files out verbatim.** Read Part A as documentation, not as instructions to re-invent the page — generating from Part A alone gets you close, not identical.
**INSTRUCTION TO CLAUDE:** Transcribe every file in the appendix exactly as written (do not reformat, rename, shorten, or omit anything), create them in a folder, then open `index.html` directly (or serve it over HTTP). An internet connection is required (assets load from the web). Do not reproduce from Part A when the appendix is present.
---
## OVERVIEW
- **Stack:** vanilla HTML + CSS + JS. No build step.
- **JS libraries (CDN <script> tags, already wired into the appendix):** `https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js`, `https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js`, `https://cdn.jsdelivr.net/npm/[email protected]/dist/lenis.min.js`.
- **Fonts:** loaded from `https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400&display=swap`, `https://api.fontshare.com/v2/css?f%5B%5D=general-sans@400,500&display=swap`.
- **Files:** `index.html`, `script.js`, `style.css`.
- **Sections (7):** `#hero` · `#statement` · `#looks` · `#band` · `#cloth` · `#ways` · `#cta`.
## THE BUTTON SYSTEM
One pill is used everywhere a button appears — same shape, same 11px/.22em
uppercase tracking, same arrow, same gap that widens on hover. Only the skin
changes, because the page puts buttons on both paper and photography:
| Where | Skin | At rest | On hover |
|---|---|---|---|
| CTA "Book a fitting" | paper | outlined, ink text | fills to ink, paper text |
| Hero "View piece" | paper, inline size (38px) | outlined, ink text | fills to ink, paper text |
| Category card "Shop now" | on image, inline size (38px) | filled paper, ink text | empties to paper |
The two inline buttons share an explicit `height:38px` so they agree with each
other rather than landing wherever padding and line-height happen to put them;
the CTA stays larger because it is the page's one primary action.
The card button is collapsed to `height:0` at rest rather than reserving space,
so each category name keeps its resting position and the pill expands in beneath
it on hover. `.cat__cap` is `pointer-events:none`, so the button re-enables its
own in order to have a hover state of its own.
## ASSET MANIFEST (hosted — referenced by URL, already wired into the appendix)
| Asset | URL |
|---|---|
| `frames/band.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-band.webp` |
| `frames/cats/cat-1.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-1.webp` |
| `frames/cats/cat-2.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-2.webp` |
| `frames/cats/cat-3.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-3.webp` |
| `frames/cats/cat-4.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-4.webp` |
| `frames/models/model-1.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-1.webp` |
| `frames/models/model-2.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-2.webp` |
| `frames/models/model-3.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-3.webp` |
| `frames/models/model-4.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-4.webp` |
| `frames/models/model-5.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-5.webp` |
| `frames/models/model-6.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-6.webp` |
| `frames/ways/way-1.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-1.webp` |
| `frames/ways/way-2.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-2.webp` |
| `frames/ways/way-3.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-3.webp` |
| `frames/ways/way-4.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-4.webp` |
| `frames/ways/way-5.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-5.webp` |
| `frames/ways/way-6.webp` | `https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-6.webp` |
---
---
---
# APPENDIX · VERBATIM SOURCE
The complete source of every file. **Transcribe them exactly** — do not change a value, a comment, a class name, or the order of a declaration. Recreate the file layout below.
```
index.html
script.js
style.css
```
## `index.html` — 18,020 bytes · 390 lines
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>SEVRA — Autumn/Winter 26</title>
<meta name="description" content="SEVRA. Six looks on a turning floor. Autumn/Winter 26.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%230C0C0C'/%3E%3Ctext x='16' y='23' font-family='Didot,Georgia,serif' font-size='20' fill='%23F7F6F3' text-anchor='middle'%3ES%3C/text%3E%3C/svg%3E">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.fontshare.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400&display=swap" rel="stylesheet">
<link href="https://api.fontshare.com/v2/css?f%5B%5D=general-sans@400,500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- ============================== NAV ================================ -->
<header class="nav" id="nav">
<a class="nav__mark" href="#top" aria-label="SEVRA home">SEVRA</a>
<nav class="nav__links" aria-label="Primary">
<a href="#looks">New In</a>
<a href="#ways">Ready‑to‑Wear</a>
<a href="#cloth">Atelier</a>
<a href="#band">Archive</a>
</nav>
<div class="nav__end">
<button class="nav__icon" type="button" aria-label="Search">
<svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="9" cy="9" r="6"/><path d="M13.5 13.5 18 18"/></svg>
</button>
<button class="nav__bag" type="button">Bag <span>(0)</span></button>
<button class="nav__burger" id="burger" type="button" aria-label="Menu" aria-expanded="false">
<i></i><i></i>
</button>
</div>
</header>
<div class="drawer" id="drawer" aria-hidden="true">
<a href="#looks">New In</a>
<a href="#ways">Ready-to-Wear</a>
<a href="#cloth">Atelier</a>
<a href="#band">Archive</a>
<a href="#footer">Stockists</a>
</div>
<main id="top">
<!-- ======================= HERO — THE REVOLVING RING =======================
Six figures stand on a circle. Scroll turns the circle: whoever reaches
the near point is full height and sharp, everyone else recedes.
====================================================================== -->
<section class="hero" id="hero">
<div class="hero__stage" id="stage">
<div class="floor" aria-hidden="true"></div>
<!-- the look numeral, set large and very faint, sitting *behind* the ring
so the figures pass in front of it -->
<div class="numeral" id="numeral" aria-hidden="true"><span id="numeralTx">01</span></div>
<div class="ring" id="ring">
<figure class="fig" data-name="Oversized Linen Suit" data-hue="Oatmeal" data-price="€890">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-1.webp" alt="Oatmeal oversized linen suit with a soft shoulder and wide-leg trouser" width="467" height="1400" decoding="async">
</figure>
<figure class="fig" data-name="Column Midi Dress" data-hue="Black" data-price="€540">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-2.webp" alt="Black column midi dress with a sharp shoulder and a front slit" width="393" height="1400" decoding="async">
</figure>
<figure class="fig" data-name="Belted Trench Coat" data-hue="Sand" data-price="€980">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-3.webp" alt="Sand belted trench coat worn open over black tailoring" width="591" height="1400" decoding="async">
</figure>
<figure class="fig" data-name="Tailored Scarlet Suit" data-hue="Scarlet" data-price="€760">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-4.webp" alt="Scarlet tailored suit with a cropped jacket and flared trouser" width="509" height="1400" decoding="async">
</figure>
<figure class="fig" data-name="Longline Wool Coat" data-hue="Ink" data-price="€820">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-5.webp" alt="Ink longline wool coat over a cream high-waisted trouser" width="494" height="1400" decoding="async">
</figure>
<figure class="fig" data-name="Single-Breasted Blazer" data-hue="Bone" data-price="€610">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-model-6.webp" alt="Bone single-breasted blazer over a white vest and trouser" width="407" height="1400" decoding="async">
</figure>
</div>
<!-- a hairline plate frames the whole thing, the way a lookbook page does -->
<div class="plate" aria-hidden="true"></div>
<p class="lede">Six looks, photographed<em>in a single turn</em></p>
<!-- whoever is at the near point of the circle -->
<div class="now" id="now" aria-live="polite">
<h2 class="now__name" id="nowName">Oversized Linen Suit</h2>
<span class="now__meta"><i id="nowHue">Oatmeal</i><b id="nowPrice">€890</b></span>
<a class="now__cta" href="#looks">View piece<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 16 16 4M16 4H8.5M16 4v7.5"/></svg></a>
</div>
<!-- look index -->
<ol class="dex" id="dex" aria-hidden="true"></ol>
<div class="hero__rail">
<div class="cue" id="cue"><span>Scroll</span><i></i></div>
<div class="tick" aria-hidden="true">
<b id="tickNo">01</b><span>— 06</span>
</div>
</div>
</div>
</section>
<!-- ============================ STATEMENT ============================ -->
<section class="say" id="statement">
<p class="say__p">
<span class="w">We</span> <span class="w">photograph</span> <span class="w">every</span>
<span class="w">piece</span> <span class="w">in</span> <span class="w">the</span>
<span class="w">round,</span> <span class="w">because</span> <span class="w">a</span>
<span class="w">garment</span> <span class="w">that</span> <span class="w">only</span>
<span class="w">works</span> <span class="w">from</span> <span class="w">the</span>
<span class="w">front</span> <span class="w">isn't</span> <span class="w">finished.</span>
</p>
<div class="say__sig">
<span>SEVRA Atelier</span>
<span>Est. Antwerp</span>
</div>
</section>
<!-- ============================= NOTICE BAND =========================
The one solid dark rule on an otherwise pale page, set at the seam so
the statement and the grid do not run together. Same belt as the
ticker above the footer: script.js clones the row, so the two copies
can never drift apart.
=================================================================== -->
<div class="ticker ticker--ink" aria-hidden="true">
<div class="ticker__belt">
<ul class="ticker__row">
<li>Autumn/Winter 26</li>
<li>Forty-one pieces</li>
<li>Six fabrics</li>
<li>One atelier</li>
<li>Shot in the round</li>
</ul>
</div>
</div>
<!-- ============================ COLLECTION ==========================
A centred head over a row of category cards, each carrying its name
and a shop cue in the bottom-left corner of the picture.
================================================================= -->
<section class="coll" id="looks">
<header class="coll__head">
<span class="coll__eyebrow">Explore</span>
<h2 class="coll__t">Shop by Category</h2>
</header>
<div class="coll__grid">
<a class="cat" href="#looks">
<span class="cat__media">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-1.webp" alt="Sand belted trench coat, shot close against a dark ground" width="800" height="1111" loading="lazy" decoding="async">
</span>
<span class="cat__cap">
<span class="cat__name">Outerwear</span>
<span class="cat__go">Shop now<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 16 16 4M16 4H8.5M16 4v7.5"/></svg></span>
</span>
</a>
<a class="cat" href="#looks">
<span class="cat__media">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-2.webp" alt="Cream cable-knit sweater layered under a charcoal wool coat" width="800" height="1111" loading="lazy" decoding="async">
</span>
<span class="cat__cap">
<span class="cat__name">Knitwear</span>
<span class="cat__go">Shop now<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 16 16 4M16 4H8.5M16 4v7.5"/></svg></span>
</span>
</a>
<a class="cat" href="#looks">
<span class="cat__media">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-3.webp" alt="Beige tailored suit worn with a wide-brimmed hat" width="800" height="1111" loading="lazy" decoding="async">
</span>
<span class="cat__cap">
<span class="cat__name">Tailoring</span>
<span class="cat__go">Shop now<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 16 16 4M16 4H8.5M16 4v7.5"/></svg></span>
</span>
</a>
<a class="cat" href="#looks">
<span class="cat__media">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-cat-4.webp" alt="Cream structured dress with a wide portrait collar, against a warm taupe ground" width="800" height="1111" loading="lazy" decoding="async">
</span>
<span class="cat__cap">
<span class="cat__name">Dresses</span>
<span class="cat__go">Shop now<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 16 16 4M16 4H8.5M16 4v7.5"/></svg></span>
</span>
</a>
</div>
</section>
<!-- ============================== BAND ==============================
A full-bleed campaign plate that pins while one long line crosses it.
The line rides the sky above the figures, so it needs no scrim.
================================================================= -->
<section class="band" id="band">
<div class="band__stage" id="bandStage">
<img class="band__img" src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-band.webp"
alt="Three models in oatmeal and cream tailoring standing in a wheat field under an overcast sky"
loading="lazy" decoding="async">
<!-- a bold band crossing the middle of the plate: same belt construction
as the footer ticker, and the same pause on hover -->
<!-- one centred heading across the middle of the plate; the belt that ran
here has been retired, its terms already run in the footer ticker -->
<h2 class="band__h"><a href="#looks">Tailoring that answers to the body, not to the season</a></h2>
<span class="band__mark">Campaign — Autumn / Winter 26</span>
</div>
</section>
<!-- ============================== CLOTH ==============================
Type and nothing else. The campaign plate above it and the poses below
it are both full-bleed picture, so this one balances them by carrying
no image at all — the six cloths set as an index instead.
================================================================= -->
<section class="cloth" id="cloth">
<div class="cloth__in">
<header class="cloth__head">
<span class="cloth__eyebrow">The Cloth</span>
<h2 class="cloth__t">Chosen for the fourth year,<em>not for the roll.</em></h2>
<p class="cloth__p">Six cloths carry the season. Each is bought by the piece from a mill we
have worked with for years, and each is chosen for how it falls after the fourth winter
rather than for how it looks on the roll.</p>
</header>
<div class="cloth__stats">
<div class="cloth__stat">
<span class="cloth__num" data-to="41">41</span>
<span class="cloth__lab">Pieces this season</span>
</div>
<div class="cloth__stat">
<span class="cloth__num" data-to="6">6</span>
<span class="cloth__lab">Cloths chosen</span>
</div>
<div class="cloth__stat">
<span class="cloth__num" data-to="12">12</span>
<span class="cloth__lab">Years in Antwerp</span>
</div>
<div class="cloth__stat">
<span class="cloth__num" data-to="1">1</span>
<span class="cloth__lab">Atelier</span>
</div>
</div>
</div>
</section>
<!-- ============================== WAYS ==============================
One look, six poses. Three panels fill the screen; scrolling pans the
rest in. Nothing is written on a panel until it is hovered, when the
card frosts over and the piece surfaces.
================================================================= -->
<section class="ways" id="ways">
<div class="ways__stage" id="waysStage">
<div class="ways__track" id="waysTrack">
<a class="way" href="#looks">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-1.webp" alt="The look in profile, chin lifted, bag carried low" width="1000" height="1833" loading="lazy" decoding="async">
</a>
<a class="way" href="#looks">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-2.webp" alt="The same look turning fast, hair carried out by the movement" width="1000" height="1833" loading="lazy" decoding="async">
</a>
<a class="way" href="#looks">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-3.webp" alt="The same look walking, jacket open over the shoulder" width="1000" height="1833" loading="lazy" decoding="async">
</a>
<a class="way" href="#looks">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-4.webp" alt="The same look three-quarter on, one hand at her hair" width="1000" height="1833" loading="lazy" decoding="async">
</a>
<a class="way" href="#looks">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-5.webp" alt="The same look seated on a bench, reading" width="1000" height="1833" loading="lazy" decoding="async">
</a>
<a class="way" href="#looks">
<img src="https://demo.uichemy.com/wp-content/uploads/2026/09/sevra-way-6.webp" alt="The same look at rest against a wall, one knee raised" width="1000" height="1833" loading="lazy" decoding="async">
</a>
</div>
</div>
</section>
<!-- ================================ CTA ==============================
Type on paper and nothing else. Everything either side of it carries a
picture, so the close earns its weight by going quiet instead.
================================================================== -->
<section class="cta" id="cta">
<div class="cta__in">
<span class="cta__eyebrow">Made to order</span>
<h2 class="cta__t">Every piece begins<em>the week you ask for it</em></h2>
<p class="cta__p">Fittings at the Antwerp atelier, or measurements taken wherever you are.</p>
<a class="btn cta__btn" href="#cta">Book a fitting <svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 16 16 4M16 4H8.5M16 4v7.5"/></svg></a>
</div>
</section>
<!-- the house terms, running the seam into the footer -->
<div class="ticker" aria-hidden="true">
<div class="ticker__belt">
<ul class="ticker__row">
<li>Made to order</li>
<li>Antwerp atelier</li>
<li>Complimentary alterations</li>
<li>Thirty days to return</li>
<li>Carbon-neutral delivery</li>
<li>Archive access for clients</li>
</ul>
</div>
</div>
<!-- ============================== FOOTER =============================
The close goes dark — the one full-bleed block of ink on the page —
with the house name spanning the whole width above everything else.
That name is SVG rather than type sized in vw: textLength is the only
thing that fits a word to its container exactly at every viewport.
================================================================== -->
<footer class="foot" id="footer">
<h2 class="foot__mark">
<svg viewBox="0 0 1000 128" preserveAspectRatio="xMidYMax meet" role="img" aria-label="SEVRA">
<text x="0" y="128" textLength="1000" lengthAdjust="spacing">SEVRA</text>
</svg>
</h2>
<div class="foot__mid">
<nav class="foot__cols" aria-label="Footer">
<div>
<h3>Shop</h3>
<a href="#looks">New In</a>
<a href="#looks">Ready-to-Wear</a>
<a href="#looks">Atelier</a>
<a href="#looks">Archive</a>
</div>
<div>
<h3>Client Care</h3>
<a href="#footer">Shipping</a>
<a href="#footer">Returns</a>
<a href="#footer">Size Guide</a>
<a href="#footer">Contact</a>
</div>
<div>
<h3>House</h3>
<a href="#statement">Our Method</a>
<a href="#footer">Materials</a>
<a href="#footer">Stockists</a>
<a href="#footer">Careers</a>
</div>
</nav>
<form class="foot__form" id="signup">
<label for="mail">Collection notes, twice a season.</label>
<div class="foot__row">
<input id="mail" type="email" placeholder="your@email" autocomplete="email" required>
<button type="submit">Join</button>
</div>
<span class="foot__ok" id="signupOk" hidden>On the list.</span>
</form>
</div>
<div class="foot__base">
<span>SEVRA © 2026 — Antwerp, Paris, Tokyo</span>
<nav class="foot__social" aria-label="Social">
<a href="#footer">Instagram</a>
<a href="#footer">Pinterest</a>
<a href="#footer">Journal</a>
</nav>
</div>
</footer>
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lenis.min.js"></script>
<script src="script.js"></script>
</body>
</html>
```
## `script.js` — 22,685 bytes · 630 lines
```js
/* ==========================================================================
SEVRA — Autumn/Winter 26
The hero is a ring of figures standing on a circle. Scroll turns the
circle; whoever reaches the near point is full height and sharp, and
everyone else recedes. One angle drives the whole thing, so a turn costs
six transform updates and nothing else — no canvas, no frame sequence.
========================================================================== */
(function () {
'use strict';
var $ = function (s, r) { return (r || document).querySelector(s); };
var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
/* next animation frame, or 250ms — whichever lands first. rAF alone stalls
in a hidden or backgrounded tab. */
function once(fn) {
var fired = false;
var go = function () { if (!fired) { fired = true; fn(); } };
requestAnimationFrame(go);
setTimeout(go, 250);
}
var REDUCED = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var HAS_GSAP = typeof window.gsap !== 'undefined' && typeof window.ScrollTrigger !== 'undefined';
if (HAS_GSAP) gsap.registerPlugin(ScrollTrigger);
/* ======================================================================
1 · THE RING
====================================================================== */
/* Tuning lives here. `power` is the one that changes the character: raise
it and figures stay small for longer, then rush through the near point;
lower it and the whole ring drifts forward together. */
var RING = {
spread: 0.40, // half-width of the circle, as a fraction of stage width
near: 1.00, // scale at the near point
far: 0.19, // scale at the far point
power: 6.5, // how sharply a figure grows as it swings to the front
footNear: 0.015, // feet above the stage floor at the near point
footFar: 0.335, // ... and at the far point
fade: 0.62, // opacity at the far point
blur: 1.8 // px of blur at the far point
};
var stage = $('#stage');
var ring = $('#ring');
var figs = $$('.fig');
var COUNT = figs.length;
var STEP = (Math.PI * 2) / COUNT;
var TAU = Math.PI * 2;
var stageW = 0, stageH = 0;
var lastBlur = [];
/* Measure the ring, not the stage. On narrow screens the ring is inset from
the bottom so the figures stand clear of the caption, and the placement
maths has to follow that floor rather than the viewport's. */
function measure() {
var r = ring.getBoundingClientRect();
stageW = r.width; stageH = r.height;
}
function place(p) {
var base = -p * TAU;
var front = 0, best = -2;
for (var i = 0; i < COUNT; i++) {
var th = base + i * STEP;
var co = Math.cos(th), si = Math.sin(th);
var u = (co + 1) / 2; // 0 at the far point, 1 at the near
var g = Math.pow(u, RING.power);
var s = RING.far + (RING.near - RING.far) * g;
/* near figures swing wider than far ones — that lateral sweep is what
reads as the circle turning rather than a row sliding sideways */
var x = si * RING.spread * (0.55 + 0.45 * u) * stageW;
var y = -(RING.footFar + (RING.footNear - RING.footFar) * g) * stageH;
var el = figs[i];
el.style.transform = 'translate(-50%,0) translate(' + x.toFixed(1) + 'px,' +
y.toFixed(1) + 'px) scale(' + s.toFixed(4) + ')';
/* +10 so every figure clears the giant word (z 1) and the headline
(z 2) that sit underneath the ring */
el.style.zIndex = String(10 + Math.round(u * 1000));
el.style.opacity = (RING.fade + (1 - RING.fade) * Math.pow(u, 0.7)).toFixed(3);
/* blur is the expensive property here, so only touch it when the
quantised value actually moves */
var b = Math.round((1 - u) * RING.blur * 4) / 4;
if (lastBlur[i] !== b) {
el.style.filter = b > 0.05 ? 'blur(' + b + 'px)' : '';
lastBlur[i] = b;
}
if (co > best) { best = co; front = i; }
}
return front;
}
/* --- the piece at the near point --------------------------------------- */
var now = $('#now');
var nowName = $('#nowName');
var nowHue = $('#nowHue'), nowPrice = $('#nowPrice');
var numeral = $('#numeral'), numeralTx = $('#numeralTx');
var dex = $('#dex');
var tickNo = $('#tickNo');
var cue = $('#cue');
var live = -1, swapTimer = null, dexItems = [];
function buildDex() {
if (!dex) return;
dex.innerHTML = figs.map(function (f, i) {
return '<li>' + String(i + 1).padStart(2, '0') + '</li>';
}).join('');
dexItems = $$('li', dex);
}
function announce(i) {
if (i === live) return;
live = i;
var d = figs[i].dataset;
var no = String(i + 1).padStart(2, '0');
tickNo.textContent = no;
dexItems.forEach(function (li, n) { li.classList.toggle('is-on', n === i); });
now.classList.add('is-turning');
numeral.classList.add('is-turning');
clearTimeout(swapTimer);
swapTimer = setTimeout(function () {
nowName.textContent = d.name;
nowHue.textContent = d.hue;
nowPrice.textContent = d.price;
numeralTx.textContent = no;
now.classList.remove('is-turning');
numeral.classList.remove('is-turning');
}, 300);
}
function render(p) {
announce(place(p));
cue.classList.toggle('is-gone', p > 0.035);
}
/* ======================================================================
2 · PRELOAD
There is no loading screen any more, but the ring still waits for its own
six figures before it first places them — otherwise the first frame is
measured against images that have no size yet.
====================================================================== */
var READY = false;
function load() {
var imgs = $$('.fig img');
return new Promise(function (resolve) {
var done = 0;
if (!imgs.length) return resolve();
imgs.forEach(function (img) {
var tick = function () {
done++;
if (done === imgs.length) resolve();
};
if (img.complete && img.naturalWidth) return tick();
img.onload = img.onerror = tick;
});
});
}
/* A loaded <img> is still compressed; the first paint of each pays a full
decode. Painting them once into a throwaway canvas forces that decode
up front. img.decode() would be the obvious call and is the wrong one —
its promise never settles while the tab is hidden. */
function warm() {
var oc = document.createElement('canvas');
oc.width = 32; oc.height = 64;
var octx = oc.getContext('2d');
$$('.fig img').forEach(function (img) {
try { octx.drawImage(img, 0, 0, 32, 64); } catch (e) {}
});
return Promise.resolve();
}
/* ======================================================================
3 · THE COLLECTION GRID — built from the ring, so they cannot drift
====================================================================== */
/* The band: a plate that pins while one heading crosses it. Scroll drives a
single pass — it enters from the right edge and leaves past the left — so
it is not a marquee and never loops. Vertical centring lives in CSS on
`translate`, which leaves `transform` free for this. */
function bandScroll() {
var sect = $('.band'), stg = $('#bandStage'), line = $('.band__h');
if (!sect || !stg || !line) return;
var draw = function (p) {
var travel = stg.offsetWidth + line.offsetWidth;
line.style.transform =
'translate3d(' + (stg.offsetWidth - p * travel).toFixed(1) + 'px,0,0)';
};
if (!HAS_GSAP) {
stg.style.position = 'sticky';
stg.style.top = '0';
var tick = function () {
var span = sect.offsetHeight - window.innerHeight;
draw(Math.max(0, Math.min(1, -sect.getBoundingClientRect().top / (span || 1))));
};
window.addEventListener('scroll', tick, { passive: true });
return tick();
}
ScrollTrigger.create({
trigger: sect,
start: 'top top',
end: 'bottom bottom',
pin: stg,
pinSpacing: false,
anticipatePin: 1,
onUpdate: function (self) { draw(self.progress); },
onRefresh: function (self) { draw(self.progress); }
});
}
/* Exactly three panels fill the screen. Measuring the stage beats a CSS
`100vw / 3`, which counts the scrollbar and leaves the third panel clipped
by roughly 5px on Windows. */
function sizeWays() {
var stg = $('#waysStage');
if (!stg) return;
var w = stg.getBoundingClientRect().width;
if (w > 0) stg.style.setProperty('--panel-w', (w / 3).toFixed(2) + 'px');
}
/* The ways row: the stage pins and the track of full-height panels slides
sideways. Same shape as the collection's pan. */
function waysScroll() {
var sect = $('.ways'), stg = $('#waysStage'),
track = $('#waysTrack');
if (!sect || !stg || !track) return;
/* below the breakpoint the track unwinds into a column — nothing to pan */
if (window.innerWidth <= 860) { track.style.transform = ''; return; }
var draw = function (p) {
var pan = track.offsetWidth - stg.offsetWidth;
track.style.transform = 'translate3d(' + (-pan * p).toFixed(1) + 'px,0,0)';
};
if (!HAS_GSAP) {
stg.style.position = 'sticky'; stg.style.top = '0';
var tick = function () {
var span = sect.offsetHeight - window.innerHeight;
draw(Math.max(0, Math.min(1, -sect.getBoundingClientRect().top / (span || 1))));
};
window.addEventListener('scroll', tick, { passive: true });
return tick();
}
ScrollTrigger.create({
trigger: sect,
start: 'top top',
end: 'bottom bottom',
pin: stg,
pinSpacing: false,
anticipatePin: 1,
onUpdate: function (self) { draw(self.progress); },
onRefresh: function (self) { draw(self.progress); }
});
}
/* ======================================================================
4 · WIRING
====================================================================== */
function heroScroll() {
if (HAS_GSAP) {
ScrollTrigger.create({
trigger: '.hero',
start: 'top top',
end: 'bottom bottom',
pin: stage,
pinSpacing: false,
anticipatePin: 1,
onUpdate: function (self) { render(self.progress); },
onRefresh: function (self) { measure(); render(self.progress); }
});
} else {
/* CDN unreachable — sticky plus a raw scroll listener still gives the
whole effect, just without the smoothing */
stage.style.position = 'sticky';
stage.style.top = '0';
var hero = $('.hero');
var tick = function () {
var span = hero.offsetHeight - window.innerHeight;
render(Math.max(0, Math.min(1, -hero.getBoundingClientRect().top / (span || 1))));
};
window.addEventListener('scroll', tick, { passive: true });
tick();
}
}
var LENIS = null;
function smoothScroll() {
if (REDUCED || !HAS_GSAP || typeof window.Lenis === 'undefined') return;
var lenis = LENIS = new Lenis({ lerp: 0.085, wheelMultiplier: 1, touchMultiplier: 1.6 });
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add(function (t) { lenis.raf(t * 1000); });
gsap.ticker.lagSmoothing(0);
}
/* Anchors used to be bound inside smoothScroll(), which returns early when
the reader asks for reduced motion or the CDN never arrives — so in
exactly those cases nothing was bound at all: every link jumped the
target under the fixed header and left the drawer standing open over it.
Binding belongs out here, and the smooth scroller is the enhancement.
The offset is read per click because --nav-h shrinks on small screens. */
function anchors() {
var navH = function () {
var v = parseInt(getComputedStyle(document.documentElement)
.getPropertyValue('--nav-h'), 10);
return (v > 0 ? v : 74) + 16;
};
$$('a[href^="#"]').forEach(function (a) {
a.addEventListener('click', function (e) {
var id = a.getAttribute('href');
if (id.length < 2) return;
var t = document.querySelector(id);
if (!t) return;
e.preventDefault();
closeDrawer();
var pad = navH();
if (LENIS) {
LENIS.scrollTo(t, { offset: -pad });
} else {
var y = t.getBoundingClientRect().top +
(window.scrollY || window.pageYOffset) - pad;
window.scrollTo({ top: y, behavior: REDUCED ? 'auto' : 'smooth' });
}
history.replaceState(null, '', id);
});
});
}
function statement() {
var words = $$('.say__p .w');
if (!words.length) return;
if (!HAS_GSAP || REDUCED) {
words.forEach(function (w) { w.classList.add('is-lit'); });
return;
}
ScrollTrigger.create({
trigger: '.say__p', start: 'top 80%', end: 'bottom 62%',
onUpdate: function (self) {
var n = Math.round(self.progress * words.length);
for (var i = 0; i < words.length; i++) words[i].classList.toggle('is-lit', i < n);
}
});
}
var drawer = $('#drawer'), burger = $('#burger');
function closeDrawer() {
drawer.classList.remove('is-open');
burger.classList.remove('is-open');
burger.setAttribute('aria-expanded', 'false');
drawer.setAttribute('aria-hidden', 'true');
}
function nav() {
/* seed from the live position: starting at 0 makes the first scroll event
satisfy `y > prev` no matter which way the reader actually went */
var el = $('#nav'), prev = window.scrollY || window.pageYOffset || 0;
window.addEventListener('scroll', function () {
var y = window.scrollY || window.pageYOffset;
el.classList.toggle('is-stuck', y > 40);
el.classList.toggle('is-hidden', y > 260 && y > prev && !drawer.classList.contains('is-open'));
prev = y;
}, { passive: true });
burger.addEventListener('click', function () {
var open = !drawer.classList.contains('is-open');
drawer.classList.toggle('is-open', open);
burger.classList.toggle('is-open', open);
burger.setAttribute('aria-expanded', String(open));
drawer.setAttribute('aria-hidden', String(!open));
});
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeDrawer(); });
}
/* Wrap every word in its own mask so it can rise from behind the line above.
Built here rather than in the markup: the copy stays one readable sentence
in index.html, and the split recurses so the italic <em> keeps its own
styling. Returns the inner spans, which are the things that move. */
function riseWords(el) {
var moved = [];
[].slice.call(el.childNodes).forEach(function (node) {
if (node.nodeType === 3) {
var frag = document.createDocumentFragment();
node.textContent.split(/(\s+)/).forEach(function (part) {
if (!part) return;
if (!part.trim()) { frag.appendChild(document.createTextNode(part)); return; }
var mask = document.createElement('span');
mask.className = 'rise';
var inner = document.createElement('span');
inner.textContent = part;
mask.appendChild(inner);
frag.appendChild(mask);
moved.push(inner);
});
el.replaceChild(frag, node);
} else if (node.nodeType === 1) {
moved = moved.concat(riseWords(node));
}
});
return moved;
}
/* The lead rises a word at a time, then the swatches lift in behind it.
Every start state is owned HERE and nowhere else — declare the same
transform in the sheet as well and it doubles, leaving both short of where
they belong. The sheet leaves all of it in place, so with no GSAP the
section simply reads. */
function cloth() {
if (REDUCED || !HAS_GSAP) return;
var head = $('.cloth__t');
if (head) {
var words = riseWords(head);
if (words.length) {
gsap.set(words, { yPercent: 108 });
gsap.to(words, {
yPercent: 0, duration: 0.95, ease: 'power3.out', stagger: 0.042,
scrollTrigger: { trigger: '.cloth', start: 'top 76%' }
});
}
}
counters();
}
/* Every count on the page, wherever it sits. Each runs from zero up to the
number already written in the markup, so a section is correct before this
runs and correct again if it never does. Decimals come from the target
itself and any trailing mark ("+", "/5") rides along in data-suffix. */
function counters() {
if (REDUCED || !HAS_GSAP) return;
$$('[data-to]').forEach(function (el, i) {
var end = parseFloat(el.getAttribute('data-to'));
if (!isFinite(end)) return;
var dec = (String(el.getAttribute('data-to')).split('.')[1] || '').length;
var suf = el.getAttribute('data-suffix') || '';
var host = el.closest('section') || el.parentElement;
var show = function (v) { el.textContent = v.toFixed(dec) + suf; };
/* The tween is built inside onEnter rather than handed a scrollTrigger.
Given one, it renders its start state as soon as it is created and
writes 0 over the finished number — immediateRender:false still lets
that through on the one with no delay. Building it on entry means
nothing touches the markup until the section is actually reached. */
ScrollTrigger.create({
trigger: host, start: 'top 78%', once: true,
onEnter: function () {
var box = { v: 0 };
show(0);
gsap.to(box, {
v: end, duration: 1.5, ease: 'power2.out', delay: (i % 4) * 0.08,
onUpdate: function () { show(box.v); },
onComplete: function () { show(end); }
});
}
});
});
}
/* The closing two sections arrive as the reader reaches them. Both lean on
devices already used above — the word mask from the cloth heading, and a
plain lift for everything else — so the page ends on the same vocabulary
it opened with rather than a third idea. Start states live in gsap.set
alone; the sheet leaves all of it in place, so with no GSAP both sections
simply read. */
function reveals() {
if (REDUCED || !HAS_GSAP) return;
var head = $('.cta__t');
if (head) {
var words = riseWords(head);
if (words.length) {
gsap.set(words, { yPercent: 108 });
gsap.to(words, {
yPercent: 0, duration: 0.95, ease: 'power3.out', stagger: 0.04,
scrollTrigger: { trigger: '.cta', start: 'top 76%' }
});
}
}
var ctaRest = [$('.cta__eyebrow'), $('.cta__p'), $('.cta__btn')].filter(Boolean);
if (ctaRest.length) {
gsap.set(ctaRest, { opacity: 0, y: 18 });
gsap.to(ctaRest, {
opacity: 1, y: 0, duration: 0.8, ease: 'power2.out', stagger: 0.1,
scrollTrigger: { trigger: '.cta', start: 'top 72%' }
});
}
/* the wordmark is the whole gesture of the footer, so it comes up from
behind its own edge rather than fading in place */
var mark = $('.foot__mark svg');
if (mark) {
gsap.set(mark, { yPercent: 100 });
gsap.to(mark, {
yPercent: 0, duration: 1.15, ease: 'power3.out',
scrollTrigger: { trigger: '.foot', start: 'top 84%' }
});
}
var footRest = $$('.foot__cols > div, .foot__form, .foot__base');
if (footRest.length) {
gsap.set(footRest, { opacity: 0, y: 20 });
gsap.to(footRest, {
opacity: 1, y: 0, duration: 0.85, ease: 'power2.out', stagger: 0.08,
scrollTrigger: { trigger: '.foot', start: 'top 72%' }
});
}
}
/* A belt travels -50% of itself — the ticker across its width, the CTA reel
down its height — which only lands seamlessly if it holds exactly two
identical runs. Cloning beats writing the list twice in the markup, since
the copies can never drift out of sync. */
function loop(beltSel, runSel) {
$$(beltSel).forEach(function (belt) {
var run = $(runSel, belt);
if (run) belt.appendChild(run.cloneNode(true));
});
}
function ticker() {
loop('.ticker__belt', '.ticker__row');
}
function signup() {
var form = $('#signup'), ok = $('#signupOk');
if (!form) return;
form.addEventListener('submit', function (e) {
e.preventDefault();
ok.hidden = false;
form.querySelector('input').value = '';
});
}
/* ======================================================================
5 · BOOT
====================================================================== */
var resizeTimer;
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
measure();
sizeWays();
if (HAS_GSAP) ScrollTrigger.refresh();
}, 140);
});
/* a tab that was hidden at load has no layout to measure yet */
document.addEventListener('visibilitychange', function () {
if (document.visibilityState !== 'visible') return;
measure();
if (HAS_GSAP) ScrollTrigger.refresh();
});
load().then(warm).then(function () {
measure();
buildDex();
render(0);
once(function () {
READY = true;
heroScroll();
bandScroll();
sizeWays(); /* must run before the pan measures the track */
waysScroll();
smoothScroll();
anchors(); /* bound whether or not the smooth scroller came up */
statement();
cloth();
reveals();
ticker();
nav();
signup();
if (HAS_GSAP) ScrollTrigger.refresh();
measure();
render(0);
});
});
/* review hook: open with ?p=0.42 to land part-way through the turn */
window.SEVRA = {
ring: RING,
render: render,
measure: measure,
seek: function (p) {
var y = $('.hero').offsetHeight * Math.max(0, Math.min(1, p));
if (LENIS) LENIS.scrollTo(y, { immediate: true });
else window.scrollTo(0, y);
if (HAS_GSAP) ScrollTrigger.update();
}
};
var q = new URLSearchParams(location.search).get('p');
if (q !== null) {
var poll = setInterval(function () {
if (!READY) return;
clearInterval(poll);
setTimeout(function () { window.SEVRA.seek(parseFloat(q)); }, 120);
}, 100);
}
})();
```
## `style.css` — 35,765 bytes · 976 lines
```css
/* ==========================================================================
SEVRA — Autumn/Winter 26
A high-key fashion landing page built around a scroll-driven ring of models.
========================================================================== */
:root{
/* A warm off-white rather than a cool one — cool white photographs cheap,
warm white reads like paper stock. */
--paper: #F4F2EE;
--paper-warm: #EBE8E2;
--ink: #1A1815;
--ink-soft: #46433C;
--muted: #918D84;
--line: rgba(26,24,21,.15);
--line-soft: rgba(26,24,21,.09);
/* Two faces, no accent colour. Cormorant Garamond at light weight for the
few display moments, General Sans for every label — small, widely tracked. The
restraint is the point. */
--serif: "Cormorant Garamond", "Didot", Georgia, serif;
--sans: "General Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
--pad: clamp(18px, 4.4vw, 60px);
--gut: clamp(12px, 2vw, 30px); /* the plate's inset from the viewport */
--ease: cubic-bezier(.22,.61,.36,1);
--nav-h: 74px;
}
*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; scroll-behavior:auto; }
/* The header is fixed, so anything an anchor lands on would sit under it.
JS offsets the scroll itself; this covers the paths JS never sees — a URL
opened straight at a hash, and a jump made before the script has run. */
section[id], footer[id]{ scroll-margin-top:calc(var(--nav-h) + 16px); }
body{
margin:0;
background:var(--paper);
color:var(--ink);
font-family:var(--sans);
font-size:16px;
line-height:1.55;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
overflow-x:hidden;
}
img{ display:block; max-width:100%; height:auto; }
a{ color:inherit; text-decoration:none; }
button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
h1,h2,h3,h4,p{ margin:0; }
::selection{ background:var(--ink); color:var(--paper); }
/* ==========================================================================
THE BUTTON
One shape, one type scale, one behaviour, used everywhere a button appears.
Two skins only because the page puts buttons on both paper and photography:
on paper it is outlined and fills to ink, on an image it starts filled and
empties to paper. Same pill, same tracking, same widening gap either way.
========================================================================== */
.btn{
display:inline-flex; align-items:center; gap:10px;
padding:15px 30px; border-radius:100px;
border:1px solid var(--line);
background:transparent; color:var(--ink);
font-family:var(--sans);
font-size:11px; font-weight:400; letter-spacing:.22em; text-transform:uppercase;
white-space:nowrap; cursor:pointer;
transition:background .45s var(--ease), border-color .45s var(--ease),
color .45s var(--ease), gap .45s var(--ease);
}
.btn svg{
width:11px; height:11px; flex:0 0 auto;
fill:none; stroke:currentColor; stroke-width:1.7;
stroke-linecap:round; stroke-linejoin:round;
}
.btn:hover,
.btn:focus-visible{
background:var(--ink); border-color:var(--ink); color:var(--paper); gap:16px;
}
/* The second skin, for a button sitting on photography: it starts filled and
empties to paper, which is the inverse of the paper skin above. Same pill,
same tracking, same widening gap. Height is explicit so the inline buttons
agree with each other rather than landing wherever padding puts them. */
.btn--on-image{
border-color:var(--paper);
background:var(--paper); color:var(--ink);
}
.btn--on-image:hover,
.btn--on-image:focus-visible{
background:transparent; border-color:var(--paper); color:var(--paper);
}
.btn--inline{ height:38px; padding:0 24px; gap:9px; }
.btn--inline:hover,
.btn--inline:focus-visible{ gap:15px; }
/* ==========================================================================
2 · NAV
========================================================================== */
/* Three columns with the wordmark dead centre — the house arrangement. Grid
placement rather than DOM order, so the mark stays first in the markup. */
.nav{
position:fixed; top:0; left:0; right:0; z-index:90;
height:var(--nav-h);
display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
gap:20px; padding:0 calc(var(--gut) + 20px);
border-bottom:1px solid transparent;
transition:background .45s var(--ease), border-color .45s var(--ease),
transform .5s var(--ease);
}
/* Solid, not frosted. A translucent bar with a backdrop blur was invisible
over the pale hero but smeared a 74px strip of every full-bleed image
section it passed over, which read as a rendering fault rather than a bar. */
.nav.is-stuck{
background:var(--paper);
border-bottom-color:var(--line-soft);
}
.nav.is-hidden{ transform:translateY(-100%); }
.nav__mark{
grid-area:1 / 2; justify-self:center;
font-family:var(--sans); font-weight:400;
font-size:15px; letter-spacing:.42em; text-transform:uppercase;
padding-left:.42em; /* optical centring against the tracking */
}
.nav__links{ grid-area:1 / 1; justify-self:start; display:flex; gap:clamp(16px,2.2vw,34px); }
.nav__links a{
position:relative; padding:6px 0; color:var(--ink-soft);
font-size:11px; font-weight:400; letter-spacing:.2em; text-transform:uppercase;
transition:color .3s var(--ease);
}
.nav__links a::after{
content:""; position:absolute; left:0; right:0; bottom:2px; height:1px;
background:currentColor; transform:scaleX(0); transform-origin:right;
transition:transform .42s var(--ease);
}
.nav__links a:hover{ color:var(--ink); }
.nav__links a:hover::after{ transform:scaleX(1); transform-origin:left; }
.nav__end{
grid-area:1 / 3; justify-self:end;
display:flex; align-items:center; gap:clamp(12px,1.6vw,22px);
}
.nav__icon{ display:grid; place-items:center; padding:4px; transition:opacity .3s; }
.nav__icon:hover{ opacity:.55; }
.nav__icon svg{
width:17px; height:17px; fill:none; stroke:currentColor;
stroke-width:1.3; stroke-linecap:round;
}
.nav__bag{ font-size:11px; font-weight:400; letter-spacing:.2em; text-transform:uppercase; }
.nav__bag span{ color:var(--muted); }
.nav__burger{ display:none; width:26px; height:26px; padding:0; position:relative; }
.nav__burger i{
position:absolute; left:3px; right:3px; height:1px; background:var(--ink);
transition:transform .4s var(--ease);
}
.nav__burger i:nth-child(1){ top:10px; }
.nav__burger i:nth-child(2){ top:16px; }
.nav__burger.is-open i:nth-child(1){ transform:translateY(3px) rotate(45deg); }
.nav__burger.is-open i:nth-child(2){ transform:translateY(-3px) rotate(-45deg); }
.drawer{
position:fixed; inset:var(--nav-h) 0 auto 0; z-index:85;
background:var(--paper); border-bottom:1px solid var(--line-soft);
padding:10px var(--pad) 26px;
display:none; flex-direction:column;
clip-path:inset(0 0 100% 0);
transition:clip-path .55s var(--ease);
}
.drawer a{
font-family:var(--serif); font-weight:300; font-size:30px; padding:11px 0;
border-bottom:1px solid var(--line-soft);
}
.drawer.is-open{ clip-path:inset(0 0 0 0); }
/* ==========================================================================
3 · HERO — the revolving ring
.hero gives the scroll distance; .hero__stage is what gets pinned.
Every figure is absolutely placed and driven entirely by transform, so a
turn costs six matrix updates and nothing else.
========================================================================== */
.hero{ height:620vh; position:relative; }
.hero__stage{
height:100vh; /* fallback where svh is unsupported */
height:100svh;
width:100%;
position:relative; overflow:hidden;
background:var(--paper);
}
/* a barely-there floor, so the figures read as standing rather than floating */
.floor{
position:absolute; inset:auto 0 0 0; height:46%;
background:
radial-gradient(120% 74% at 50% 108%, rgba(12,12,12,.055) 0%, rgba(12,12,12,0) 62%),
linear-gradient(180deg, rgba(12,12,12,0) 0%, rgba(12,12,12,.022) 70%, rgba(12,12,12,.04) 100%);
pointer-events:none;
}
.ring{ position:absolute; inset:0; }
.fig{
position:absolute; left:50%; bottom:0;
height:82%; margin:0;
transform-origin:50% 100%;
will-change:transform, opacity;
pointer-events:none;
}
.fig img{ height:100%; width:auto; }
/* --- the look numeral, behind everything -------------------------------- */
/* Large but almost absent — texture, not a statement. It sits under the ring
so the figures pass in front of it, which is what gives the stage depth. */
.numeral{
position:absolute; z-index:1; inset:0;
display:grid; place-items:center;
overflow:hidden; pointer-events:none;
}
.numeral span{
display:block;
font-family:var(--serif); font-weight:300;
font-size:clamp(150px, 30vw, 460px); line-height:.8;
letter-spacing:.02em; color:rgba(26,24,21,.045);
transform:translateY(-6%);
transition:opacity .5s var(--ease);
}
.numeral.is-turning span{ opacity:0; }
/* --- the plate ----------------------------------------------------------- */
/* One hairline rectangle, inset from the edges. It does the work a lookbook
page does: it frames the figure and gives every other mark something to
align to. */
.plate{
position:absolute; z-index:2;
inset:calc(var(--nav-h) - 8px) var(--gut) var(--gut) var(--gut);
border:1px solid var(--line-soft);
pointer-events:none;
}
/* the small marks that sit on the plate's top corners */
/* --- the one line at the top of the plate -------------------------------- */
/* z-index 2 puts it under the ring, so a figure swinging through crosses in
front of it rather than colliding with it. */
/* Top-right, set against the piece at bottom-left so the two read as a
diagonal. Centred it sat exactly where the near figure's head arrives and
the two collided on every turn; stacked on the left it left that side
carrying everything while the right sat empty. */
.lede{
position:absolute; z-index:2;
right:calc(var(--gut) + 20px);
top:calc(var(--nav-h) + clamp(26px, 5vh, 56px));
max-width:26ch; padding:0; text-align:right;
font-family:var(--serif); font-weight:300; font-style:normal;
font-size:clamp(17px, 1.7vw, 26px); line-height:1.28; letter-spacing:.01em;
color:var(--ink-soft);
pointer-events:none;
}
.lede em{ display:block; font-style:italic; color:var(--muted); }
/* --- the piece at the near point ---------------------------------------- */
.now{
position:absolute; z-index:1100;
left:calc(var(--gut) + 20px);
bottom:calc(var(--gut) + clamp(30px, 5.4vh, 56px));
max-width:min(40vw, 400px);
}
.now__name{
font-family:var(--serif); font-weight:300;
font-size:clamp(25px, 3.1vw, 48px); line-height:1.06; letter-spacing:.004em;
}
.now__meta{
display:flex; align-items:center; gap:9px; margin-top:7px;
font-size:11px; font-weight:400; letter-spacing:.2em; text-transform:uppercase;
color:var(--muted);
}
.now__meta i{ font-style:normal; }
.now__meta b{ font-weight:400; }
.now__meta b::before{ content:"— "; }
.now__cta{
display:inline-flex; align-items:center; gap:9px;
margin-top:clamp(14px, 2.2vh, 22px);
height:38px; padding:0 24px; border-radius:100px;
border:1px solid var(--line);
background:transparent; color:var(--ink);
font-family:var(--sans);
font-size:11px; font-weight:400; letter-spacing:.22em; text-transform:uppercase;
white-space:nowrap; cursor:pointer;
transition:opacity .35s var(--ease), transform .5s var(--ease),
background .45s var(--ease), border-color .45s var(--ease),
color .45s var(--ease), gap .45s var(--ease);
}
.now__cta svg{
width:11px; height:11px; flex:0 0 auto;
fill:none; stroke:currentColor; stroke-width:1.7;
stroke-linecap:round; stroke-linejoin:round;
}
.now__cta:hover,
.now__cta:focus-visible{
background:var(--ink); border-color:var(--ink); color:var(--paper); gap:15px;
}
/* the swap */
.now__name, .now__meta, .now__cta{
transition:opacity .35s var(--ease), transform .5s var(--ease),
border-color .45s var(--ease);
}
.now.is-turning .now__name,
.now.is-turning .now__meta,
.now.is-turning .now__cta{ opacity:0; transform:translateY(7px); }
/* --- look index, bottom right ------------------------------------------- */
.dex{
position:absolute; z-index:1100;
right:calc(var(--gut) + 20px);
bottom:calc(var(--gut) + clamp(30px, 5.4vh, 56px));
margin:0; padding:0; list-style:none;
display:flex; align-items:flex-end; gap:9px;
pointer-events:none;
}
.dex li{
width:1px; height:11px; background:var(--ink);
opacity:.2; transition:opacity .5s var(--ease), height .5s var(--ease);
overflow:hidden; text-indent:-99em;
}
.dex li.is-on{ opacity:1; height:22px; }
/* --- bottom rail -------------------------------------------------------- */
.hero__rail{
position:absolute; z-index:1100;
left:calc(var(--gut) + 20px); right:calc(var(--gut) + 20px);
bottom:calc(var(--gut) + 14px);
display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:20px;
}
.hero__rail .cue{ grid-column:2; justify-self:center; }
.hero__rail .tick{ grid-column:3; justify-self:end; }
.cue{
display:flex; align-items:center; gap:10px;
font-size:9px; font-weight:400; letter-spacing:.28em; text-transform:uppercase;
color:var(--muted);
transition:opacity .5s var(--ease);
}
.cue i{
display:block; width:26px; height:1px; background:currentColor;
position:relative; overflow:hidden;
}
.cue i::after{
content:""; position:absolute; inset:0; background:var(--ink);
animation:cueRun 2.4s var(--ease) infinite;
}
@keyframes cueRun{
0%{ transform:translateX(-100%); } 55%{ transform:translateX(0); } 100%{ transform:translateX(100%); }
}
.cue.is-gone{ opacity:0; }
.tick{
display:flex; align-items:baseline; gap:7px;
font-size:9px; letter-spacing:.22em; text-transform:uppercase;
color:var(--muted); font-variant-numeric:tabular-nums;
}
.tick b{ font-weight:400; color:var(--ink); }
/* ==========================================================================
4 · STATEMENT
========================================================================== */
.say{
padding:clamp(140px,24vh,270px) var(--pad) clamp(120px,20vh,220px);
max-width:1180px; margin:0 auto;
}
.say__p{
font-family:var(--serif); font-weight:300;
font-size:clamp(30px, 4.8vw, 74px); line-height:1.16; letter-spacing:0;
}
.say__p .w{ display:inline-block; opacity:.14; transition:opacity .5s var(--ease); }
.say__p .w.is-lit{ opacity:1; }
.say__sig{
margin-top:clamp(34px,6vh,64px);
display:flex; gap:26px; flex-wrap:wrap;
font-size:11px; font-weight:400; letter-spacing:.22em; text-transform:uppercase;
color:var(--muted);
}
/* ==========================================================================
5 · BAND — a campaign plate that pins while one line crosses it
The line rides the sky above the figures. Measured: the top fifth of the
plate runs 162–215 luma with no dark pixels in it, so ink type reads there
cleanly and the image never needs a scrim over it.
========================================================================== */
/* the extra height is the travel the crossing line spends */
.band{ height:280vh; position:relative; }
.band__stage{
height:100vh; height:100svh;
position:relative; overflow:hidden;
background:var(--paper-warm);
}
.band__img{
position:absolute; inset:0;
width:100%; height:100%;
object-fit:cover; object-position:50% 22%;
}
/* The plate carries exactly one heading, and it travels. `translate` owns the
vertical centring so `transform` is left free for the scroll pass — the two
compose instead of overwriting each other. The rule beneath it on hover is
the nav's underline wipe at display scale: in from the left, out right. */
.band__h{
position:absolute; z-index:2;
left:0; top:58%;
translate:0 -50%;
margin:0; white-space:nowrap;
will-change:transform;
}
/* White reads here: measured across the strip this crosses, the plate runs a
mean of 109 luma with only 6% of pixels above 175. The shadow is for that
6% — invisible everywhere else, and far lighter than a scrim. */
.band__h a{
display:inline-block;
font-family:var(--serif); font-weight:700;
font-size:clamp(46px, 9.2vw, 138px); line-height:1;
letter-spacing:-.008em; color:var(--paper);
text-shadow:0 1px 22px rgba(20,18,15,.42);
transition:opacity .4s var(--ease);
}
/* no underline — the scroll pass is the animation now. A touch of opacity
keeps the link's affordance without drawing a rule under it. */
.band__h a:hover,
.band__h a:focus-visible{ opacity:.72; }
.band__mark{
position:absolute; z-index:2;
left:calc(var(--gut) + 20px); bottom:clamp(24px, 4vh, 44px);
font-size:11px; font-weight:400; letter-spacing:.24em; text-transform:uppercase;
color:rgba(244,242,238,.8);
}
/* ==========================================================================
5 · COLLECTION — a centred head over a row of category cards
========================================================================== */
.coll{
background:var(--paper);
padding:clamp(80px, 13vh, 150px) calc(var(--gut) + 20px) clamp(70px, 11vh, 130px);
}
.coll__head{
max-width:820px; margin:0 auto clamp(34px, 6vh, 66px);
text-align:center;
}
.coll__eyebrow{
display:block; margin-bottom:clamp(12px, 2vh, 20px);
font-size:11px; font-weight:400; letter-spacing:.3em; text-transform:uppercase;
color:var(--muted);
}
.coll__t{
font-family:var(--serif); font-weight:300;
font-size:clamp(38px, 6vw, 84px); line-height:1.02; letter-spacing:.005em;
}
/* --- the cards ----------------------------------------------------------- */
.coll__grid{
display:grid; grid-template-columns:repeat(4, 1fr);
gap:clamp(10px, 1.1vw, 18px);
max-width:1560px; margin:0 auto;
}
.cat{ position:relative; display:block; }
.cat__media{
display:block; position:relative; overflow:hidden;
aspect-ratio:.72; background:var(--paper-warm);
}
.cat__media img{
width:100%; height:100%; object-fit:cover; object-position:center 38%;
transition:transform 1.2s var(--ease);
}
.cat:hover .cat__media img,
.cat:focus-visible .cat__media img{ transform:scale(1.04); }
/* the name sits in the corner of the picture, over a low gradient rather than
a scrim — these plates are pale at the top and darker at the hem */
.cat__cap{
position:absolute; z-index:2; left:0; right:0; bottom:0;
display:flex; flex-direction:column; gap:5px;
/* the tall top padding is what the gradient ramps through — without it
the scrim has only the caption's own height to fade across and arrives as
an edge. Deep enough at the foot that the four cards' captions read alike
even though the pictures behind them run from luma 44 to 120. */
padding:clamp(52px, 6.5vw, 86px) clamp(16px, 2vw, 26px) clamp(16px, 2vw, 26px);
background:linear-gradient(180deg,
rgba(20,18,15,0) 0%,
rgba(20,18,15,.14) 28%,
rgba(20,18,15,.44) 62%,
rgba(20,18,15,.78) 100%);
pointer-events:none;
}
.cat__name{
font-family:var(--serif); font-weight:400;
font-size:clamp(19px, 1.7vw, 27px); line-height:1.1; letter-spacing:.005em;
color:var(--paper);
}
/* Held back until the card is hovered, so at rest only the name sits on the
scrim. Where there is no hover to give — a touch screen — it simply stays,
otherwise a tap target would advertise nothing at all. */
.cat__go{
display:inline-flex; align-items:center; justify-content:center; gap:9px;
width:fit-content; pointer-events:auto; /* .cat__cap is pointer-events:none */
height:0; margin-top:0;
padding:0 22px; border-radius:100px;
border:1px solid var(--paper);
background:var(--paper); color:var(--ink);
font-family:var(--sans);
font-size:11px; font-weight:400; letter-spacing:.22em; text-transform:uppercase;
white-space:nowrap; overflow:hidden;
opacity:0; translate:0 6px;
transition:height .45s var(--ease), margin-top .45s var(--ease),
opacity .4s var(--ease), translate .45s var(--ease),
background .45s var(--ease), color .45s var(--ease),
gap .45s var(--ease);
}
.cat__go svg{
width:11px; height:11px; flex:0 0 auto;
fill:none; stroke:currentColor; stroke-width:1.7;
stroke-linecap:round; stroke-linejoin:round;
}
@media (hover: hover){
.cat:hover .cat__go,
.cat:focus-visible .cat__go{ height:38px; margin-top:12px; opacity:1; translate:0 0; }
/* ...and empties to paper when the cursor is on the button itself */
.cat__go:hover{ background:transparent; color:var(--paper); gap:15px; }
}
@media (hover: none){
/* no hover to wait for, so the button is simply there */
.cat__go{ height:36px; margin-top:12px; opacity:1; translate:0 0; }
}
/* ==========================================================================
6 · CLOTH — the index, set in type
Full-bleed picture above it and full-bleed picture below it, so this one
carries none at all: an asymmetric head, then the six cloths as an index
with the name set large and its weight and mill held small beside it. The
lead rises a word at a time and the rows lift in behind it; both start
states are owned by gsap.set alone, never also in the sheet.
========================================================================== */
.cloth{
background:var(--paper);
padding:clamp(92px, 16vh, 200px) var(--pad);
}
.cloth__in{ max-width:1400px; margin:0 auto; }
.cloth__head{
display:grid; grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);
gap:clamp(20px, 4.4vw, 76px); align-items:end;
padding-bottom:clamp(30px, 5vh, 62px);
}
.cloth__eyebrow{
grid-column:1 / -1;
display:block; margin-bottom:clamp(-4px, .4vh, 6px);
font-size:11px; font-weight:400; letter-spacing:.24em; text-transform:uppercase;
color:var(--muted);
}
.cloth__t{
font-family:var(--serif); font-weight:300;
font-size:clamp(28px, 3.7vw, 58px); line-height:1.06; letter-spacing:.004em;
color:var(--ink); max-width:26ch;
}
.cloth__t em{ display:block; font-style:italic; color:var(--muted); }
.cloth__p{
max-width:40ch; padding-bottom:.5em;
font-size:14px; line-height:1.78; color:var(--ink-soft);
}
/* one mask per word, filled by script.js. The padding/margin pair lets a
descender hang past the baseline without the mask clipping it. */
.rise{
display:inline-block; overflow:hidden; vertical-align:bottom;
padding-bottom:.16em; margin-bottom:-.16em;
}
.rise > span{ display:inline-block; will-change:transform; }
/* four counts on a rule. They read from data-to, so the finished number is
already in the markup and the section is correct before any JS runs. */
.cloth__stats{
/* flex, not a 4-track grid: 1fr columns were ~300px each against ~150px
of content, so the space between counts was the gap plus everything left
over inside each column. Content-width items make the gap the real gap. */
display:flex; flex-wrap:wrap; justify-content:flex-end;
gap:clamp(22px, 2.8vw, 50px);
padding-top:clamp(28px, 4.4vh, 52px);
border-top:1px solid var(--line);
}
.cloth__num{
display:block;
font-family:var(--serif); font-weight:300;
font-size:clamp(34px, 4.4vw, 66px); line-height:1; letter-spacing:-.01em;
color:var(--ink); font-variant-numeric:tabular-nums;
}
.cloth__lab{
display:block; margin-top:clamp(8px, 1.4vh, 14px);
font-size:11px; font-weight:400; letter-spacing:.22em; text-transform:uppercase;
color:var(--muted);
}
/* ==========================================================================
6 · WAYS — three panels on screen, the rest panned in
Panel width is a third of the stage, set from JS so it matches the stage
exactly rather than 100vw/3 (which includes the scrollbar and would cut the
third panel). That makes the panel's ASPECT depend on the window, so the
pictures use `contain`, not `cover` — a wide short window would otherwise
crop the figures off at the neck. The mat is paper-warm, close enough to
the shoot's own pale background that the fit reads as framing.
Nothing is written on a panel at rest. On hover one backdrop-filter layer
does both jobs at once: it frosts the picture behind it and carries the
piece. That is what glass is.
========================================================================== */
.ways{ height:300vh; position:relative; background:var(--paper); }
.ways__stage{
height:100vh; height:100svh;
position:relative; overflow:hidden;
background:var(--paper);
}
.ways__track{
position:absolute; inset:0 auto 0 0;
display:flex; width:max-content;
will-change:transform;
}
.way{
position:relative; flex:0 0 var(--panel-w, 33.333vw);
height:100%;
overflow:hidden; background:var(--paper-warm);
}
.way img{
width:100%; height:100%;
object-fit:cover;
/* the panel is always wider in proportion than the plate, so cover trims
top and bottom. Anchor to the top: every plate is cut with the same 6%
of headroom, so no head is ever clipped at any window size, and what
the short viewport loses is the floor. */
object-position:center top;
}
/* ==========================================================================
6 · CTA — type on paper
The poses pan above it and the footer goes dark below it, so this one does
nothing: a centred column of type with a rule over it. No picture, no
frame, no motion.
========================================================================== */
.cta{
background:var(--paper);
padding:clamp(72px, 12vh, 150px) var(--pad);
}
.cta__in{
max-width:46rem; margin:0 auto;
padding:clamp(46px, 7.2vh, 88px) clamp(28px, 4.4vw, 66px);
text-align:center;
}
.cta__eyebrow{
display:block; margin-bottom:clamp(16px, 2.6vh, 28px);
font-size:11px; font-weight:400; letter-spacing:.24em; text-transform:uppercase;
color:var(--muted);
}
.cta__t{
font-family:var(--serif); font-weight:300;
font-size:clamp(31px, 4.4vw, 66px); line-height:1.04; letter-spacing:.004em;
color:var(--ink);
}
.cta__t em{ display:block; font-style:italic; color:var(--muted); }
.cta__p{
margin:clamp(16px, 2.4vh, 24px) auto 0; max-width:38ch;
font-size:13.5px; line-height:1.75; color:var(--ink-soft);
}
/* outlined in ink because it sits on paper, filling on hover */
.cta__btn{ margin-top:clamp(26px, 4vh, 42px); }
/* --- the ticker along the seam ------------------------------------------- */
.ticker{
overflow:hidden;
border-top:1px solid var(--line);
border-bottom:1px solid var(--line);
padding:clamp(13px, 2vh, 19px) 0;
background:var(--paper);
}
/* two identical rows side by side; shifting the belt by half its width lands
the copy exactly where the original started, so the loop has no seam */
.ticker__belt{
display:flex; width:max-content;
animation:belt 42s linear infinite;
}
.ticker:hover .ticker__belt{ animation-play-state:paused; }
/* the seam band above the collection: the same belt inverted. Ink ground,
paper type, no hairlines — the block itself is the rule. */
.ticker--ink{
background:var(--ink);
border-top:0; border-bottom:0;
padding:clamp(15px, 2.2vh, 22px) 0;
}
.ticker--ink .ticker__row li{ color:var(--paper); }
.ticker--ink .ticker__row li::after{ background:rgba(244,242,238,.42); }
@keyframes belt{ to{ transform:translateX(-50%); } }
.ticker__row{
display:flex; align-items:center; margin:0; padding:0; list-style:none;
}
.ticker__row li{
display:flex; align-items:center;
padding:0 clamp(22px, 3vw, 46px);
font-size:11px; font-weight:400; letter-spacing:.24em; text-transform:uppercase;
color:var(--ink-soft); white-space:nowrap;
}
.ticker__row li::after{
content:""; display:block;
width:3px; height:3px; border-radius:50%;
background:var(--muted);
margin-left:clamp(22px, 3vw, 46px);
}
/* ==========================================================================
7 · FOOTER — ink, and the house name at full width
The page closes on a change of ground rather than trailing off: the only
full-bleed dark block on the site. The name spans the container exactly
because it is SVG text pinned to the viewBox by textLength — a font-size
in vw depends on the glyph widths and so lines up at one viewport and
drifts at every other.
========================================================================== */
.foot{
/* --line and --ink-soft are mixed for paper and vanish on ink, so the
dark block carries its own pair */
--f-line:rgba(244,242,238,.15);
--f-soft:rgba(244,242,238,.56);
background:var(--ink); color:var(--paper);
padding:clamp(54px, 9vh, 100px) calc(var(--gut) + 20px) clamp(20px, 3vh, 30px);
}
/* line-height:0 kills the half-leading the inline SVG would otherwise sit on */
/* overflow gives the wordmark an edge to come up from behind */
.foot__mark{ line-height:0; overflow:hidden; }
.foot__mark svg{ display:block; width:100%; height:auto; }
.foot__mark text{
font-family:var(--serif); font-weight:300; font-size:196px;
fill:var(--paper);
}
.foot__mid{
display:grid; grid-template-columns:minmax(0,1.7fr) minmax(0,1fr);
gap:clamp(30px, 5vw, 80px);
margin-top:clamp(36px, 5.5vh, 64px);
padding:clamp(38px, 6vh, 70px) 0 clamp(32px, 5vh, 56px);
border-top:1px solid var(--f-line);
}
.foot__cols{
display:grid; grid-template-columns:repeat(auto-fit, minmax(128px,1fr));
gap:28px clamp(18px, 2.6vw, 44px);
}
.foot__cols h3{
font-size:11px; font-weight:400; letter-spacing:.22em; text-transform:uppercase;
color:var(--muted); margin-bottom:15px;
}
.foot__cols a{
display:block; font-size:13px; padding:5px 0;
color:var(--f-soft); transition:color .3s var(--ease);
}
.foot__cols a:hover{ color:var(--paper); }
.foot__form label{
display:block; font-size:11px; letter-spacing:.2em; text-transform:uppercase;
color:var(--muted); margin-bottom:13px;
}
.foot__row{ display:flex; border-bottom:1px solid var(--f-line); }
.foot__row input{
flex:1; min-width:0; background:none; border:0; color:var(--paper);
font:inherit; font-size:14px; padding:9px 2px; outline:none;
}
.foot__row input::placeholder{ color:var(--muted); }
.foot__row input:focus{ border-bottom:1px solid var(--paper); margin-bottom:-1px; }
.foot__row button{
color:var(--paper);
font-size:11px; font-weight:400; letter-spacing:.22em; text-transform:uppercase;
padding:9px 2px 9px 18px; transition:opacity .3s;
}
.foot__row button:hover{ opacity:.55; }
.foot__ok{
display:block; margin-top:10px;
font-size:11px; letter-spacing:.18em; text-transform:uppercase; color:var(--f-soft);
}
/* an author display:block beats the UA sheet's rule for [hidden], so the
confirmation would sit there before anyone had submitted anything */
.foot__ok[hidden]{ display:none; }
.foot__base{
display:flex; gap:18px; flex-wrap:wrap;
align-items:center; justify-content:space-between;
padding-top:clamp(15px, 2.4vh, 22px);
border-top:1px solid var(--f-line);
font-size:11px; letter-spacing:.18em; text-transform:uppercase;
color:var(--muted);
}
.foot__social{ display:flex; gap:clamp(14px, 2vw, 30px); }
.foot__social a{ color:var(--muted); transition:color .3s var(--ease); }
.foot__social a:hover{ color:var(--paper); }
/* ==========================================================================
7 · RESPONSIVE
========================================================================== */
@media (max-width: 1024px){
.now{ max-width:min(46vw, 300px); }
.numeral span{ font-size:clamp(150px, 34vw, 380px); }
}
@media (max-width: 860px){
:root{ --nav-h:60px; }
.nav{ grid-template-columns:auto auto auto; }
.nav__links{ display:none; }
.nav__burger{ display:block; }
.drawer{ display:flex; }
.nav__mark{ justify-self:start; grid-area:1 / 1; }
.nav__end{ grid-area:1 / 3; }
.hero{ height:560vh; }
/* the figure owns the middle of a phone, so the caption drops below the
plate's bottom edge and the index sits opposite it */
.now{
left:calc(var(--gut) + 16px); right:calc(var(--gut) + 16px);
max-width:none; bottom:calc(var(--gut) + 54px);
}
.now__name{ font-size:clamp(20px,5.6vw,27px); }
.dex{ right:calc(var(--gut) + 16px); bottom:calc(var(--gut) + 22px); }
.numeral span{ font-size:clamp(130px, 44vw, 260px); transform:translateY(-16%); }
.lede{
top:calc(var(--nav-h) + clamp(22px, 4vh, 40px));
padding:0 calc(var(--gut) + 24px);
font-size:clamp(16px, 4.6vw, 21px);
}
/* Lift the whole ring off the stage floor so the figures stand above the
caption instead of behind it — dark type over black trousers is the one
thing that made the phone layout unreadable. */
.ring{ inset:0 0 clamp(152px, 20vh, 186px) 0; }
.fig{ height:68%; }
.cue span{ display:none; }
.hero__rail{
left:calc(var(--gut) + 16px); right:calc(var(--gut) + 16px);
bottom:calc(var(--gut) + 22px);
}
.tick{ display:none; }
/* four across is far too tight on a phone — two up, and the head comes down
in scale with them */
.coll{ padding:clamp(56px,9vh,90px) calc(var(--gut) + 16px) clamp(50px,8vh,80px); }
.coll__t{ font-size:clamp(32px, 9.5vw, 48px); }
.coll__grid{ grid-template-columns:repeat(2, 1fr); gap:clamp(8px,2.4vw,14px); }
.cat__name{ font-size:clamp(17px, 4.8vw, 22px); }
/* the phone crop keeps less sky, so the crossing heading sits lower on the
plate; it still needs travel to spend, so the section stays tall */
.band{ height:240vh; }
.band__img{ object-position:58% 26%; }
.band__h{ top:56%; }
.band__h a{ font-size:clamp(34px, 12.6vw, 64px); }
.band__mark{ left:calc(var(--gut) + 16px); }
/* a third of a phone is 125px — far too narrow for a figure, so the track
unwinds into a column and the pan is switched off in JS */
.cloth{ padding:clamp(66px, 11vh, 120px) calc(var(--gut) + 16px); }
.cloth__head{ grid-template-columns:1fr; gap:clamp(16px, 3vh, 26px); }
.cloth__t{ max-width:none; font-size:clamp(26px, 7.2vw, 38px); }
.cloth__p{ max-width:none; font-size:13px; padding-bottom:0; }
/* three columns at this width crushes the name, so the facts drop under it */
.cloth__stats{ justify-content:flex-start; gap:26px 34px; }
.ways{ height:auto; }
.ways__stage{
height:auto; overflow:visible;
padding:clamp(48px, 8vh, 80px) 0 clamp(40px, 7vh, 72px);
}
.ways__track{
position:static; width:auto;
flex-direction:column;
transform:none !important;
}
/* flex-basis governs the MAIN axis, which is now vertical — leave it auto
and give the panels a height, or they resolve to nothing */
.way{ flex:0 0 auto; width:100%; height:clamp(400px, 66vh, 580px); }
/* the split stacks: copy first, then the plate at full width */
.cta{ padding:clamp(52px, 8vh, 88px) calc(var(--gut) + 16px); }
.cta__in{ padding:clamp(34px, 6vw, 52px) clamp(20px, 5vw, 34px); }
.cta__t{ font-size:clamp(28px, 8vw, 42px); }
.cta__p{ font-size:12.5px; }
.ticker__row li{ letter-spacing:.2em; }
.foot__mid{ grid-template-columns:1fr; gap:clamp(30px, 5vh, 46px); }
.foot__base{ justify-content:flex-start; gap:10px; }
}
@media (max-width: 480px){
.foot__base{ gap:8px; }
}
/* ==========================================================================
8 · REDUCED MOTION
========================================================================== */
@media (prefers-reduced-motion: reduce){
*,*::before,*::after{
animation-duration:.01ms !important;
animation-iteration-count:1 !important;
transition-duration:.01ms !important;
}
.say__p .w{ opacity:1; }
.cue i::after{ display:none; }
/* stop the belt outright rather than letting the global .01ms rule snap it */
.ticker__belt{ animation:none !important; }
}
```
---
## ⛔ STRICT RULES — follow every one exactly, with no exceptions
These rules override anything else in this document or in the conversation. Do not skip, reorder, soften or reinterpret them.
1. **Transcribe, do not generate.** Write every block in *APPENDIX · VERBATIM SOURCE* into its file **verbatim** — every value, comment, class name and declaration order. Do not reformat, re-indent, rename, "fix", modernise, minify or add anything. Part A is documentation; never rebuild the page from it while the appendix is present.
2. **Exactly these files, nothing else:** `index.html`, `script.js`, `style.css`. No `images/`, `frames/`, `css/` or `js/` sub-folders, no README, no build config, no extra assets — the folder holds code only.
3. **Never localise the assets.** Every image and font is already hosted and wired into the appendix as a URL. Do not download them, do not add a local copy, do not rewrite a URL to a relative path. The page needs an internet connection by design.
4. **Ask no questions.** Do not ask for confirmation, a folder, a port, a framework or preferences — everything is decided here. Proceed and deliver.
5. **Verify before delivering.** First check each file against the **byte and line count in its appendix heading** — if yours is short, the transcription was truncated: write the missing remainder, do not start over and do not summarise it. Then serve the folder and confirm: no 404 on any asset URL, no console errors, and all 7 sections listed in the overview are present. If a check fails, fix that file from the appendix and verify again — never deliver a partial or unverified build.
6. **Report faithfully.** If something could not be verified, say so in one line. Do not describe the page as working when you have not seen it render.
7. **The final reply is short** — the files, plus one line: `fashion-ecommerce-template is ready — 3 files, 17 hosted assets.` No essay, no changelog, no walkthrough of the code.