# Runway shop-the-look section

> A fashion store section where full-length looks line up, step forward and open with numbered pins on every piece you can add to the bag.

- Category: E-commerce
- Version: 1.0
- Source: https://ownlyplans.com/bhaskar/runway-shop-the-look-section/

## Overview

A single-file, no-library shop-the-look section for a fashion store. Six full-length looks stand on a soft stage with a mirror floor. The front look steps forward every few seconds; tap it and numbered pins pop onto each piece. The side panel lists every piece with sizes (sold-out sizes crossed out), and shoppers can add one item or the whole outfit with a bundle discount. Four layouts (Line-up, Runway queue, Turning ring, Gallery), three colour schemes, a bag drawer that remembers its contents, swipe on phones and a sticky add-to-bag bar. The demo store is Loom & Lane with example products and INR prices; swap in your own looks, products and photos in CONFIG and LOOKS.

## Tech stack

- CSS
- HTML
- JavaScript

## Build steps

### 1. Build the stage and the line-up

Create one HTML file with a store header, the section title and a soft stage where six full-length looks stand in a row. The front look is sharp; the rest step back, shrink and blur.

Prompt:

```text
Create a single-file HTML shop-the-look section for a fashion store called "Loom & Lane". No libraries, no outside requests. Layout: a sticky store header, then a two-column section (stage on the left, a 380px product panel on the right). The stage is a rounded box, height clamp(420px, 58vh, 600px), with a white-to-#f4f4f2 gradient and a faint floor line at 83%. Six looks stand on the floor as full-length transparent cut-out photos with a soft mirror reflection under each. Arrange them as a line-up: the active look is centred at full size, neighbours sit 0.8 model-widths apart, 5% smaller per step, blurred 1.6px per step and hidden beyond two steps. Animate every change with transform, filter and opacity over 900ms, cubic-bezier(.22,.8,.22,1). Under the stage add previous / next buttons, a pause button and a row of tabs whose progress bar fills over 6 seconds and then moves to the next look.
```

**Done when:** Six looks stand on the stage and step forward one by one every 6 seconds.

### 2. Open a look with numbered pins

Tapping the front look opens it: the others slide away and numbered pins pop onto each piece. The right-hand panel lists the pieces with size buttons and an Add to bag button.

Prompt:

```text
Add an open state to the runway. Tapping the front look sets the stage to open: other looks slide off to the sides and fade, a "All looks" back button appears top left, and numbered round pins (26px, white, monospace number) pop onto each piece with a staggered delay and a soft ping ring. Hovering a pin shows its product name. The right-hand panel shows "In this look", the look number and name, and one row per piece: a cropped thumbnail of that piece with its pin number, brand, product name, price (sale price in red with the old price struck through) and a + button that expands size buttons. Sold-out sizes are crossed out and disabled. Clicking a pin opens the matching row, highlights it and makes that pin dark. Never pick a size for the shopper.
```

**Done when:** Tapping the front look shows pins; tapping a pin opens that piece's sizes in the panel.

### 3. Add the bag and the full-look discount

Shoppers can add one piece or the whole outfit. The bag opens as a side drawer with quantities, and a full-look bundle takes a percentage off.

Prompt:

```text
Add a working bag. Each piece has "Add to bag" (asks for a size first). The panel footer shows "Complete the look" with the bundle price (e.g. 10% off, code LOOK10) and an "Add full look to bag" button; if any size is missing, open the look, expand the first missing piece and say which sizes are needed. The bag opens as a right-hand drawer with a scrim: lines with thumbnail, name, size, a quantity stepper, remove, subtotal, the bundle discount line when every piece of a look is in the bag, total and a Checkout button. Show a toast after each add and bump the bag count in the header. Save the bag in localStorage and close the drawer with Escape. Prices in INR with Intl.NumberFormat en-IN.
```

**Done when:** Adding the full look opens the bag with the discount applied, and the bag survives a reload.

### 4. Layouts, schemes and the phone version

Add the extra layouts and colour schemes, then make it work well on phones with swipe and a sticky add-to-bag bar.

Prompt:

```text
Add three more layouts with a segmented switch: Runway queue (looks queue into the distance and walk out one at a time), Turning ring (looks turn on a ring with the front one in focus) and Gallery (all six side by side). Each layout has its own eyebrow, title and subtitle. Add Studio, Paper (warm cream) and Night colour schemes with CSS variables, and a Mirror floor toggle. Under 860px wide: stack the panel under the stage, make the stage edge-to-edge, hide tab names, show a sticky bottom bar with the look name, bundle price and "Add full look", and support left/right swipe on the stage. Support arrow keys, pause autoplay while the panel is hovered, the bag is open or the tab is hidden, and respect prefers-reduced-motion.
```

**Done when:** All four layouts and three schemes switch cleanly, and on a phone you can swipe looks and add one from the sticky bar.

## Assets

### runway-shop-the-look.html — The finished file. Add six transparent model cut-outs in a photos/ folder (feet on the bottom edge), or edit PHOTOS.

```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Runway · Shop the look — Loom &amp; Lane</title>
<meta name="description" content="A shop-the-look runway: full-length looks line up, step forward, and open with numbered pins on every piece.">
<style>
/* =========================================================
   Runway — shop-the-look section (single file, no outside requests)
   Edit CONFIG and LOOKS in the <script> at the bottom.
   ========================================================= */
:root{
  --bg:#ffffff; --ink:#111111; --muted:#6b6b6b; --line:#e7e7e4;
  --stage:#f4f4f2; --stage2:#ffffff; --chip:#ffffff; --accent:#111111; --accent-ink:#ffffff;
  --sale:#b42318; --pin:#ffffff; --pin-ink:#111111;
  --radius:14px;
  --font: "Inter","Helvetica Neue",Helvetica,Arial,system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono: ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --ease: cubic-bezier(.22,.8,.22,1);
  --dur: 900ms;
}
[data-scheme="paper"]{
  --bg:#f7f2ea; --ink:#1d1a16; --muted:#7a7166; --line:#e3dacb;
  --stage:#efe7da; --stage2:#f7f2ea; --chip:#fbf8f3; --accent:#1d1a16; --accent-ink:#f7f2ea;
}
[data-scheme="night"]{
  --bg:#0e0e0f; --ink:#f3f3f1; --muted:#9a9a97; --line:#262628;
  --stage:#17171a; --stage2:#0e0e0f; --chip:#1b1b1e; --accent:#f3f3f1; --accent-ink:#0e0e0f;
  --sale:#ff8a7a; --pin:#f3f3f1; --pin-ink:#0e0e0f;
}
*{box-sizing:border-box}
html,body{margin:0}
html,body{overflow-x:clip}
body{font-family:var(--font);background:var(--bg);color:var(--ink);-webkit-font-smoothing:antialiased;transition:background .4s,color .4s}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:0}
a{color:inherit}
.sr{position:absolute!important;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
:focus-visible{outline:2px solid var(--ink);outline-offset:3px;border-radius:6px}

/* ---------- Store header ---------- */
.hdr{position:sticky;top:0;z-index:40;background:color-mix(in srgb,var(--bg) 92%,transparent);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.hdr-in{max-width:1320px;margin:0 auto;display:flex;align-items:center;gap:36px;height:68px;padding:0 32px}
.logo{display:flex;align-items:center;gap:10px;text-decoration:none;font-weight:700;letter-spacing:-.02em;font-size:19px}
.logo-mark{width:30px;height:30px;border-radius:8px;background:var(--ink);color:var(--bg);display:grid;place-items:center;font-size:13px;font-weight:800;letter-spacing:-.04em}
.nav{display:flex;gap:26px}
.nav a{text-decoration:none;font-weight:500;font-size:15px}
.nav a:hover{text-decoration:underline;text-underline-offset:5px}
.hdr-right{margin-left:auto;display:flex;align-items:center;gap:14px}
.bag-btn{display:flex;align-items:center;gap:10px;border:1px solid var(--line);border-radius:999px;padding:7px 8px 7px 16px;font-size:15px;transition:transform .2s}
.bag-btn .count{min-width:22px;height:22px;border-radius:999px;background:var(--ink);color:var(--bg);font-family:var(--mono);font-size:12px;display:grid;place-items:center;padding:0 6px}
.bag-btn.bump{animation:bump .5s var(--ease)}
@keyframes bump{30%{transform:scale(1.12)}}

/* ---------- Demo customiser bar ---------- */
.custom{max-width:1320px;margin:22px auto 0;padding:0 32px;display:flex;flex-wrap:wrap;gap:10px 22px;align-items:center;font-size:13px}
.custom .lbl{font-family:var(--mono);text-transform:uppercase;letter-spacing:.12em;font-size:11px;color:var(--muted);margin-right:8px}
.seg{display:inline-flex;border:1px solid var(--line);border-radius:999px;padding:3px;background:var(--chip)}
.seg button{padding:6px 13px;border-radius:999px;font-size:13px;color:var(--muted);transition:background .25s,color .25s}
.seg button[aria-pressed="true"]{background:var(--ink);color:var(--bg)}
.seg .sh{display:none}
.toggle{display:inline-flex;align-items:center;gap:8px;cursor:pointer;color:var(--muted)}
.toggle input{accent-color:var(--ink);width:16px;height:16px}

/* ---------- Section ---------- */
.cw{max-width:1320px;margin:0 auto;padding:34px 32px 80px}
.eyebrow{font-family:var(--mono);text-transform:uppercase;letter-spacing:.16em;font-size:11px;color:var(--muted);margin:0 0 14px}
.cw-grid{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:0;align-items:stretch}
.cw-main{min-width:0;padding-right:32px}
.cw-title{font-size:clamp(30px,3.6vw,46px);line-height:1.04;letter-spacing:-.035em;font-weight:750;margin:0 0 12px;max-width:16ch}
.cw-sub{color:var(--muted);margin:0 0 8px;font-size:15px}

/* stage */
.cw-stage{position:relative;height:clamp(420px,58vh,600px);overflow:hidden;border-radius:var(--radius);background:linear-gradient(180deg,var(--stage2) 0%,var(--stage) 78%,var(--stage) 100%);touch-action:pan-y;user-select:none;-webkit-user-select:none;outline:none}
.cw-stage:focus-visible{box-shadow:0 0 0 2px var(--ink) inset}
.cw-stage::after{content:"";position:absolute;left:0;right:0;top:83.3%;height:1px;background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--ink) 10%,transparent),transparent);pointer-events:none}
.cw-models{list-style:none;margin:0;padding:0;position:absolute;inset:0}
.model{position:absolute;left:50%;bottom:calc(var(--floor-gap,0px));height:var(--mh,90%);aspect-ratio:200/640;margin-left:calc(var(--mw,180px) / -2);
  transform-origin:50% 81.4%;will-change:transform,filter,opacity;
  transition:transform var(--dur) var(--ease),filter var(--dur) var(--ease),opacity var(--dur) var(--ease)}
.model.no-anim{transition:none!important}
.model svg{display:block;width:100%;height:100%;overflow:visible}
.model-hit{position:absolute;left:18%;right:18%;top:2%;bottom:18%;border-radius:40px;z-index:1}
.model:not(.is-active) .model-hit{cursor:pointer}
.model.is-active .model-hit{cursor:zoom-in}
.cw-stage.is-open .model.is-active .model-hit{cursor:zoom-out}
.no-mirror .refl{display:none}

/* pins */
.pins{position:absolute;inset:0;pointer-events:none;z-index:2}
.pin{position:absolute;width:26px;height:26px;margin:-13px 0 0 -13px;border-radius:50%;background:var(--pin);color:var(--pin-ink);font:600 12px/26px var(--mono);text-align:center;box-shadow:0 2px 10px rgba(0,0,0,.18),0 0 0 1px rgba(0,0,0,.06);
  opacity:0;transform:scale(.4);transition:opacity .4s var(--ease),transform .4s var(--ease);pointer-events:none}
.pin::before{content:"";position:absolute;inset:-6px;border-radius:50%;border:1.5px solid var(--pin);opacity:0}
.model.is-active .pin{opacity:1;transform:scale(.86);pointer-events:auto;transition-delay:calc(var(--i) * 70ms + 300ms)}
.cw-stage.is-open .model.is-active .pin{transform:scale(1)}
.cw-stage.is-open .model.is-active .pin::before{animation:ping 2.2s var(--ease) infinite;animation-delay:calc(var(--i) * 250ms)}
.pin.is-hot{transform:scale(1.18)!important;background:var(--ink);color:var(--bg)}
.pin .tip{position:absolute;left:34px;top:50%;transform:translateY(-50%);white-space:nowrap;background:var(--ink);color:var(--bg);font:500 12px/1 var(--font);padding:7px 10px;border-radius:8px;opacity:0;pointer-events:none;transition:opacity .2s}
.pin:hover .tip,.pin:focus-visible .tip{opacity:1}
.pin.tip-left .tip{left:auto;right:34px}
@keyframes ping{0%{opacity:.9;transform:scale(.8)}80%,100%{opacity:0;transform:scale(1.9)}}

.cw-back{position:absolute;left:16px;top:16px;z-index:30;display:flex;align-items:center;gap:8px;background:var(--chip);border:1px solid var(--line);border-radius:999px;padding:8px 14px;font-size:13px;opacity:0;transform:translateY(-8px);pointer-events:none;transition:.35s var(--ease)}
.cw-stage.is-open .cw-back{opacity:1;transform:none;pointer-events:auto}
.cw-hint{position:absolute;left:0;right:0;bottom:16px;text-align:center;margin:0;font-size:12px;color:var(--muted);z-index:25;pointer-events:none;transition:opacity .3s}
.cw-view{position:absolute;right:16px;top:16px;z-index:25;font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);background:var(--chip);border:1px solid var(--line);border-radius:999px;padding:7px 12px;pointer-events:none;transition:opacity .3s}
.cw-stage.is-open .cw-view{opacity:0}

/* controls */
.cw-controls{display:flex;align-items:center;gap:10px;margin-top:16px}
.icon-btn{width:42px;height:42px;flex:0 0 42px;border-radius:50%;border:1px solid var(--line);display:grid;place-items:center;background:var(--chip);transition:background .2s,border-color .2s}
.icon-btn:hover{border-color:var(--ink)}
.icon-btn svg{width:16px;height:16px}
.tabs{flex:1;display:flex;gap:6px;min-width:0}
.tab{flex:1 1 0;min-width:0;text-align:left;padding:8px 2px 4px;transition:flex-grow .5s var(--ease)}
.tab.is-active{flex-grow:2.4}
.tab .bar{display:block;height:2px;background:var(--line);border-radius:2px;overflow:hidden;margin-bottom:8px}
.tab .fill{display:block;height:100%;width:0;background:var(--ink)}
.tab.is-done .fill{width:100%}
.tab.is-active .fill{animation:fill var(--auto,6000ms) linear forwards}
.cw-controls.is-resting .tab.is-active .fill{animation-play-state:paused}
.cw-controls.is-static .tab.is-active .fill{animation:none;width:100%}
@keyframes fill{from{width:0}to{width:100%}}
.tab .num{font-family:var(--mono);font-size:11px;color:var(--muted);letter-spacing:.06em}
.tab .nm{font-size:13px;margin-left:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;transition:opacity .3s}
.tab.is-active .num{color:var(--ink)}
.tab.is-active .nm{opacity:1}
.tab-lbl{display:flex;align-items:baseline;overflow:hidden}

/* panel */
.cw-panel{border-left:1px solid var(--line);padding-left:28px;display:flex;flex-direction:column;min-height:0;max-height:calc(clamp(420px,58vh,600px) + 200px)}
.p-head{display:flex;justify-content:space-between;align-items:baseline;margin-top:4px}
.p-head h3{margin:0;font-size:21px;letter-spacing:-.02em;font-weight:650}
.p-count{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
.p-sub{margin:6px 0 0;font-size:13px;color:var(--muted)}
.p-list{list-style:none;margin:16px 0 0;padding:0;border-top:1px solid var(--line);overflow:auto;flex:1;min-height:0;scrollbar-width:thin}
.item{border-bottom:1px solid var(--line);padding:16px 0;transition:background .4s}
.item.flash{animation:flash 1.1s var(--ease)}
@keyframes flash{0%,40%{background:color-mix(in srgb,var(--ink) 7%,transparent)}100%{background:transparent}}
.item-row{display:grid;grid-template-columns:88px 1fr 36px;gap:16px;align-items:center}
.thumb{position:relative;width:88px;height:110px;border-radius:6px;background:var(--stage);overflow:hidden}
.thumb svg{width:100%;height:100%;display:block}
.thumb .badge{position:absolute;right:6px;top:6px;width:20px;height:20px;border-radius:50%;background:var(--pin);color:var(--pin-ink);font:600 10px/20px var(--mono);text-align:center;box-shadow:0 1px 4px rgba(0,0,0,.15)}
.brand{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin:0 0 6px}
.pname{margin:0 0 6px;font-size:16px;font-weight:500;letter-spacing:-.01em}
.pname a{text-decoration:none}
.pname a:hover{text-decoration:underline;text-underline-offset:3px}
.price{margin:0;font-size:14px}
.price s{color:var(--muted);margin-left:6px;font-size:13px}
.price .now.on-sale{color:var(--sale)}
.picked{font-size:12px;color:var(--muted);margin-top:4px}
.plus{width:36px;height:36px;border-radius:50%;display:grid;place-items:center;transition:transform .35s var(--ease),background .2s}
.plus:hover{background:color-mix(in srgb,var(--ink) 7%,transparent)}
.plus svg{width:16px;height:16px}
.item.is-expanded .plus{transform:rotate(45deg)}
.opts{display:grid;grid-template-rows:0fr;transition:grid-template-rows .45s var(--ease)}
.item.is-expanded .opts{grid-template-rows:1fr}
.opts-in{overflow:hidden}
.opts-pad{padding:14px 0 2px 104px}
.opt-lbl{font-size:12px;color:var(--muted);margin:0 0 8px;display:flex;justify-content:space-between}
.sizes{display:flex;flex-wrap:wrap;gap:6px}
.size{min-width:44px;height:36px;padding:0 10px;border:1px solid var(--line);border-radius:8px;font-size:13px;background:var(--chip);transition:border-color .2s,background .2s,color .2s}
.size:hover:not(:disabled){border-color:var(--ink)}
.size[aria-pressed="true"]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.size:disabled{color:var(--muted);text-decoration:line-through;cursor:not-allowed;opacity:.6;background:transparent}
.add-one{margin-top:12px;width:100%;height:40px;border-radius:999px;border:1px solid var(--ink);font-size:14px;font-weight:500;transition:background .2s,color .2s}
.add-one:hover{background:var(--ink);color:var(--bg)}
.opt-msg{min-height:16px;font-size:12px;color:var(--sale);margin:8px 0 0}

.p-foot{border-top:1px solid var(--line);padding-top:16px;margin-top:auto;background:var(--bg)}
.ctl-row{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:14px}
.ctl-row .t{margin:0;font-size:15px;font-weight:500}
.ctl-row .n{margin:3px 0 0;font-size:12px;color:var(--muted)}
.total{text-align:right}
.total .big{font-size:21px;font-weight:650;letter-spacing:-.02em;margin:0}
.total s{color:var(--muted);font-size:13px;display:block}
.save{font-size:12px;color:var(--muted);margin:0 0 12px}
.save b{font-family:var(--mono);font-weight:600;color:var(--ink);letter-spacing:.06em}
.add-look{width:100%;height:52px;border-radius:999px;background:var(--accent);color:var(--accent-ink);display:flex;align-items:center;justify-content:center;gap:10px;font-size:15px;font-weight:550;transition:transform .2s,opacity .2s}
.add-look:hover{transform:translateY(-1px)}
.add-look:active{transform:none}
.add-look svg{width:16px;height:16px;transition:transform .25s}
.add-look:hover svg{transform:translateX(3px)}
.status{min-height:18px;font-size:12px;margin:10px 0 0;color:var(--muted)}
.status.err{color:var(--sale)}

/* sticky bar (phones) */
.cw-sticky{position:fixed;left:0;right:0;bottom:0;z-index:45;display:none;align-items:center;gap:12px;padding:12px 16px calc(12px + env(safe-area-inset-bottom));background:var(--bg);border-top:1px solid var(--line);transform:translateY(110%);transition:transform .4s var(--ease)}
.cw-sticky.show{transform:none}
.cw-sticky .s-info{flex:1;min-width:0}
.cw-sticky .s-look{font-size:12px;color:var(--muted);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cw-sticky .s-price{font-size:17px;font-weight:650;margin:2px 0 0}
.cw-sticky .add-look{width:auto;padding:0 20px;height:46px;font-size:14px}

/* ---------- Bag drawer ---------- */
.scrim{position:fixed;inset:0;background:rgba(0,0,0,.36);z-index:60;opacity:0;pointer-events:none;transition:opacity .35s}
.scrim.show{opacity:1;pointer-events:auto}
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(440px,100%);z-index:61;background:var(--bg);color:var(--ink);display:flex;flex-direction:column;transform:translateX(102%);visibility:hidden;transition:transform .45s var(--ease),visibility 0s .45s}
.drawer.show{transform:none;visibility:visible;transition:transform .45s var(--ease);box-shadow:-20px 0 60px rgba(0,0,0,.15)}
.d-head{display:flex;justify-content:space-between;align-items:center;padding:22px 24px;border-bottom:1px solid var(--line)}
.d-head h2{margin:0;font-size:20px;letter-spacing:-.02em}
.d-close{font-size:14px;display:flex;align-items:center;gap:6px;color:var(--muted)}
.d-body{flex:1;overflow:auto;padding:4px 24px}
.d-empty{color:var(--muted);text-align:center;padding:60px 0;font-size:15px}
.line{display:grid;grid-template-columns:64px 1fr auto;gap:14px;padding:16px 0;border-bottom:1px solid var(--line);align-items:start}
.line .thumb{width:64px;height:80px}
.line .ln{margin:0;font-size:14px;font-weight:500}
.line .lm{margin:4px 0 0;font-size:12px;color:var(--muted)}
.line .lp{margin:0;font-size:14px;text-align:right}
.qty{display:inline-flex;align-items:center;border:1px solid var(--line);border-radius:999px;margin-top:10px}
.qty button{width:30px;height:28px;font-size:15px}
.qty span{min-width:22px;text-align:center;font-size:13px;font-family:var(--mono)}
.rm{font-size:12px;color:var(--muted);text-decoration:underline;margin-top:10px;display:block;margin-left:auto}
.bundle-tag{display:inline-block;font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;border:1px solid var(--line);border-radius:999px;padding:2px 7px;margin-top:6px;color:var(--muted)}
.d-foot{padding:18px 24px calc(18px + env(safe-area-inset-bottom));border-top:1px solid var(--line)}
.sum{display:flex;justify-content:space-between;font-size:14px;margin:0 0 8px}
.sum.disc{color:var(--sale)}
.sum.tot{font-size:18px;font-weight:650;margin:12px 0 16px}
.checkout{width:100%;height:52px;border-radius:999px;background:var(--accent);color:var(--accent-ink);font-size:15px;font-weight:550}
.d-note{font-size:12px;color:var(--muted);text-align:center;margin:10px 0 0}

/* toast */
.toast{position:fixed;left:50%;bottom:28px;z-index:70;transform:translate(-50%,20px);background:var(--ink);color:var(--bg);padding:12px 18px;border-radius:999px;font-size:14px;opacity:0;pointer-events:none;transition:.35s var(--ease);max-width:calc(100% - 32px);text-align:center}
.toast.show{opacity:1;transform:translate(-50%,0)}

/* footer */
.ftr{border-top:1px solid var(--line);margin-top:20px}
.ftr-in{max-width:1320px;margin:0 auto;padding:28px 32px;display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;font-size:13px;color:var(--muted)}


/* photo looks */
.pbox{position:absolute;left:50%;bottom:18.6%;height:81.4%;transform:translateX(-50%);pointer-events:none}
.pbox img{display:block;height:100%;width:100%;object-fit:contain;-webkit-user-drag:none}
.pbox img.refl{position:absolute;left:0;top:100%;transform:scaleY(-1);opacity:.28;-webkit-mask-image:linear-gradient(to top,#000 0%,transparent 22%);mask-image:linear-gradient(to top,#000 0%,transparent 22%)}
.pbox .pins{pointer-events:none}
.ph-thumb{display:block;width:100%;height:100%;background-repeat:no-repeat}
.credits{font-size:12px;color:var(--muted)}
.credits a{color:inherit}
/* ---------- Responsive ---------- */
@media (max-width:1080px){
  .cw-grid{grid-template-columns:minmax(0,1fr) 330px}
  .nav{display:none}
}
@media (max-width:860px){
  .hdr-in,.custom,.cw{padding-left:18px;padding-right:18px}
  .cw{padding-bottom:110px}
  .cw-grid{grid-template-columns:1fr}
  .cw-main{padding-right:0}
  .cw-stage{height:min(118vw,560px);margin:0 -18px;border-radius:0}
  .cw-panel{border-left:0;padding-left:0;margin-top:26px;max-height:none}
  .p-list{overflow:visible}
  .p-foot{display:none}
  .cw-sticky{display:flex}
  .tab .nm{display:none}
  .tab.is-active{flex-grow:1.4}
  .opts-pad{padding-left:0}
  .cw-hint{bottom:10px}
  .hdr-in{height:60px;gap:16px}
  .logo{font-size:17px}
  .custom{flex-direction:column;align-items:stretch;gap:10px;margin-top:16px}
  .custom>span{display:block;min-width:0}
  .custom .lbl{display:none}
  .seg .lg{display:none}
  .seg .sh{display:inline}
  .ftr-in{padding-bottom:110px}
  .custom .seg{display:flex;width:100%;max-width:520px}
  .custom .seg button{flex:1 1 0;min-width:0;white-space:nowrap;padding:7px 4px;font-size:12.5px;overflow:hidden;text-overflow:ellipsis}
  .cw{padding-top:22px}
  .cw-controls{gap:8px}
  .icon-btn{width:40px;height:40px;flex-basis:40px}
  .cw-view{top:12px;right:12px}
  .cw-back{top:12px;left:12px}
  .toast{bottom:calc(92px + env(safe-area-inset-bottom))}
  .item-row{grid-template-columns:76px 1fr 36px;gap:14px}
  .thumb{width:76px;height:95px}
  .pin .tip{display:none}
}
@media (max-width:380px){
  .tabs{gap:4px}
  .cw-sticky .add-look{padding:0 16px}
  .item-row{grid-template-columns:68px 1fr 32px;gap:12px}
  .thumb{width:68px;height:85px}
}
@media (prefers-reduced-motion:reduce){
  :root{--dur:1ms}
  .pin::before{animation:none!important}
}
</style>
</head>
<body data-scheme="studio">

<header class="hdr">
  <div class="hdr-in">
    <a class="logo" href="#" aria-label="Loom & Lane home"><span class="logo-mark">L&amp;L</span>Loom &amp; Lane</a>
    <nav class="nav" aria-label="Store">
      <a href="#">New in</a><a href="#">Women</a><a href="#">Ethnic</a><a href="#">Western</a><a href="#">Sale</a>
    </nav>
    <div class="hdr-right">
      <button class="bag-btn" id="bagBtn" aria-haspopup="dialog">Bag <span class="count" id="bagCount">0</span></button>
    </div>
  </div>
</header>

<!-- Demo customiser: remove this block in production and set CONFIG instead -->
<div class="custom" aria-label="Section settings">
  <span><span class="lbl">Layout</span>
    <span class="seg" id="layoutSeg">
      <button data-v="lineup">Line-up</button><button data-v="queue"><span class="lg">Runway queue</span><span class="sh">Runway</span></button><button data-v="ring"><span class="lg">Turning ring</span><span class="sh">Ring</span></button><button data-v="gallery">Gallery</button>
    </span></span>
  <span><span class="lbl">Scheme</span>
    <span class="seg" id="schemeSeg">
      <button data-v="studio">Studio</button><button data-v="paper">Paper</button><button data-v="night">Night</button>
    </span></span>
  <label class="toggle"><input type="checkbox" id="mirrorToggle" checked> Mirror floor</label>
</div>

<main>
<section class="cw" id="runway" aria-labelledby="cwTitle">
  <div class="cw-grid">
    <div class="cw-main">
      <p class="eyebrow" id="cwEyebrow">New season · Line-up</p>
      <h2 class="cw-title" id="cwTitle">This season, in one line-up.</h2>
      <p class="cw-sub" id="cwSub">Every look steps forward in turn. Tap the one in front to shop it.</p>

      <div class="cw-stage" id="stage" tabindex="0" role="region" aria-roledescription="carousel" aria-label="Looks">
        <button class="cw-back" id="backBtn"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M10 3 5 8l5 5"/></svg>All looks</button>
        <span class="cw-view" id="viewTag">View</span>
        <ul class="cw-models" id="models"></ul>
        <p class="cw-hint" id="hint">Tap the front look to open it</p>
      </div>

      <div class="cw-controls" id="controls">
        <button class="icon-btn" id="prevBtn" aria-label="Previous look"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M10 3 5 8l5 5"/></svg></button>
        <div class="tabs" id="tabs" role="tablist" aria-label="Looks"></div>
        <button class="icon-btn" id="pauseBtn" aria-label="Pause the looks"></button>
        <button class="icon-btn" id="nextBtn" aria-label="Next look"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="m6 3 5 5-5 5"/></svg></button>
      </div>
    </div>

    <aside class="cw-panel" id="panel" aria-label="In this look"></aside>
  </div>
  <p class="sr" aria-live="polite" id="live"></p>
</section>
</main>

<div class="cw-sticky" id="sticky">
  <div class="s-info"><p class="s-look" id="sLook"></p><p class="s-price" id="sPrice"></p></div>
  <button class="add-look" id="sAdd">Add full look<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
</div>

<div class="scrim" id="scrim"></div>
<div class="drawer" id="drawer" role="dialog" aria-modal="true" aria-labelledby="dTitle" aria-hidden="true">
  <div class="d-head"><h2 id="dTitle">Your bag</h2><button class="d-close" id="dClose">Close <span aria-hidden="true">×</span></button></div>
  <div class="d-body" id="dBody"></div>
  <div class="d-foot" id="dFoot"></div>
</div>
<div class="toast" id="toast" role="status"></div>

<footer class="ftr"><div class="ftr-in"><span>© Loom &amp; Lane — demo store.</span><span class="credits">Reference showcase, not for commercial use. Photos from <a href="https://unsplash.com/license" target="_blank" rel="noopener">Unsplash</a> by Anil Sharma (<a href="https://unsplash.com/photos/2RMTiOaRM4Y" target="_blank" rel="noopener">1</a>, <a href="https://unsplash.com/photos/1ML4upZvkeg" target="_blank" rel="noopener">2</a>) and others (<a href="https://unsplash.com/photos/1qsb5rGOcC4" target="_blank" rel="noopener">3</a>, <a href="https://unsplash.com/photos/GsBSWTCP9UM" target="_blank" rel="noopener">4</a>, <a href="https://unsplash.com/photos/7ewzWNm0rZQ" target="_blank" rel="noopener">5</a>, <a href="https://unsplash.com/photos/na3CUPiuIA8" target="_blank" rel="noopener">6</a>). Product names and prices are examples.</span></div></footer>

<script>
/* Model photos: transparent cut-outs (PNG or WebP), feet on the bottom edge.
   Put the files next to this HTML, or change the paths to your own image URLs. */
const PHOTOS = {"k1":"photos/look-1.webp","k4":"photos/look-2.webp","k5":"photos/look-3.webp","w1":"photos/look-4.webp","w3":"photos/look-5.webp","w4":"photos/look-6.webp"};
</script>
<script>
/* =========================================================
   CONFIG — change these
   ========================================================= */
const CONFIG = {
  layout: "lineup",          // lineup | queue | ring | gallery
  scheme: "studio",          // studio | paper | night
  mirror: true,              // soft reflection under each model
  autoplayMs: 6000,          // time per look; 0 = no autoplay
  currency: "INR", locale: "en-IN",
  brand: "Loom & Lane",
  copy: {
    lineup:  {eyebrow:"New season · Line-up",   title:"This season, in one line-up.",  sub:"Every look steps forward in turn. Tap the one in front to shop it."},
    queue:   {eyebrow:"New season · Runway",    title:"Next on the runway.",           sub:"The looks queue into the distance and walk out one by one."},
    ring:    {eyebrow:"New season · Turntable", title:"Every look, all the way round.", sub:"The looks turn on a ring, the front one in focus."},
    gallery: {eyebrow:"New season · Gallery",   title:"Six looks, one floor.",     sub:"Every look stands side by side. Tap one to shop it."}
  }
};

/* =========================================================
   LOOKS — each look = a model + up to 4 pieces.
   Piece types draw the illustrated stand-in:
   tops: tee | jacket | kurta | dress   bottoms: trousers | wide | skirt
   shoes: sneakers | flats | boots      extra: bag
   To use real photos instead, set look.photo = "model.png" (transparent PNG,
   feet on the bottom edge) and give each piece pin:[x%, y%] and img:"thumb.jpg".
   ========================================================= */
const LOOKS = [
  { id:"indigo-bloom", name:"Indigo Bloom", photo:PHOTOS.k1, ratio:0.3111, bundle:10, code:"LOOK10",
    pieces:[
      {id:"a1", slot:"top",    name:"Floral Print Straight Kurta", price:1899, compare:2499, sizes:["XS","S","M","L","XL"], soldOut:["XL"], pin:[50,40], thumb:[50,36,1.5]},
      {id:"a2", slot:"bottom", name:"Light Wash Slim Jeans",       price:1699, sizes:["26","28","30","32","34"], pin:[45,82], thumb:[45,84,2.2]},
      {id:"a3", slot:"shoes",  name:"Tan Strappy Flats",           price:999,  sizes:["UK 3","UK 4","UK 5","UK 6","UK 7"], soldOut:["UK 3"], pin:[45,97], thumb:[45,95,2.4]}
    ]},
  { id:"maroon-yoke", name:"Maroon Yoke", photo:PHOTOS.k4, ratio:0.3156, bundle:10, code:"LOOK10",
    pieces:[
      {id:"b1", slot:"top",    name:"Embroidered Yoke Anarkali Kurta", price:2499, sizes:["XS","S","M","L","XL"], pin:[52,30], thumb:[50,30,1.5]},
      {id:"b2", slot:"bottom", name:"Ankle Length Denim",          price:1599, sizes:["26","28","30","32"], soldOut:["26"], pin:[40,86], thumb:[42,86,2.4]},
      {id:"b3", slot:"shoes",  name:"Everyday Flip Sandal",        price:799,  sizes:["UK 4","UK 5","UK 6","UK 7"], pin:[45,97], thumb:[45,95,2.4]}
    ]},
  { id:"paisley-coord", name:"Paisley Co-ord", photo:PHOTOS.k5, ratio:0.3811, bundle:15, code:"LOOK15",
    pieces:[
      {id:"c1", slot:"top",    name:"Paisley Print Tunic Shirt",   price:1799, sizes:["XS","S","M","L"], pin:[55,33], thumb:[55,32,1.4]},
      {id:"c2", slot:"bottom", name:"Paisley Wide-leg Pants",      price:1599, compare:1999, sizes:["XS","S","M","L"], pin:[50,72], thumb:[50,72,1.4]},
      {id:"c3", slot:"shoes",  name:"Block Heel Mules",            price:1499, sizes:["UK 3","UK 4","UK 5","UK 6"], soldOut:["UK 6"], pin:[38,96], thumb:[45,95,2.0]}
    ]},
  { id:"blush", name:"Blush", photo:PHOTOS.w1, ratio:0.2922, bundle:10, code:"LOOK10",
    pieces:[
      {id:"d1", slot:"top",    name:"Blush Bustier Top",           price:1299, sizes:["XS","S","M","L"], soldOut:["S"], pin:[52,30], thumb:[52,28,1.7]},
      {id:"d2", slot:"bottom", name:"Pleated Wide Trousers",       price:1999, sizes:["26","28","30","32"], pin:[50,62], thumb:[50,64,1.5]},
      {id:"d3", slot:"shoes",  name:"White Court Sneakers",        price:2299, sizes:["UK 3","UK 4","UK 5","UK 6"], pin:[55,95], thumb:[52,94,2.4]}
    ]},
  { id:"onyx-denim", name:"Onyx & Denim", photo:PHOTOS.w3, ratio:0.3211, bundle:10, code:"LOOK10",
    pieces:[
      {id:"e1", slot:"top",    name:"Black Corset Top",            price:1499, sizes:["XS","S","M","L","XL"], pin:[52,25], thumb:[52,25,1.6]},
      {id:"e2", slot:"bottom", name:"High-rise Straight Jeans",    price:2199, compare:2699, sizes:["26","28","30","32","34"], soldOut:["34"], pin:[50,62], thumb:[50,62,1.4]}
    ]},
  { id:"monochrome", name:"Monochrome", photo:PHOTOS.w4, ratio:0.3333, bundle:10, code:"LOOK10",
    pieces:[
      {id:"f1", slot:"top",    name:"Oversized Poplin Shirt",      price:1699, sizes:["XS","S","M","L"], pin:[45,28], thumb:[48,28,1.4]},
      {id:"f2", slot:"bottom", name:"Slim Ankle Trousers",         price:1899, sizes:["26","28","30","32"], pin:[35,62], thumb:[45,64,1.3]},
      {id:"f3", slot:"shoes",  name:"Pointed Stiletto Pumps",      price:2499, sizes:["UK 3","UK 4","UK 5","UK 6"], soldOut:["UK 4"], pin:[60,97], thumb:[75,86,1.6]}
    ]}
];

/* =========================================================
   Illustrated stand-in models (SVG, viewBox 0 0 200 640, floor at y=521)
   ========================================================= */
let UID = 0;
const shade = (hex, amt) => { // darken (amt<0) / lighten (amt>0)
  const n = parseInt(hex.slice(1),16); let r=n>>16,g=n>>8&255,b=n&255;
  const f = v => Math.max(0,Math.min(255,Math.round(amt<0? v*(1+amt) : v+(255-v)*amt)));
  return "#"+[f(r),f(g),f(b)].map(v=>v.toString(16).padStart(2,"0")).join("");
};
const TYPE_INFO = {
  tee:{slot:"top", pin:[100,168], box:[40,88,120,190]},
  jacket:{slot:"top", pin:[100,176], box:[36,88,128,200]},
  kurta:{slot:"top", pin:[100,212], box:[36,88,128,330]},
  dress:{slot:"top", pin:[100,250], box:[34,88,132,350]},
  trousers:{slot:"bottom", pin:[87,392], box:[50,236,100,280]},
  wide:{slot:"bottom", pin:[84,404], box:[44,236,112,286]},
  skirt:{slot:"bottom", pin:[100,326], box:[44,236,112,170]},
  sneakers:{slot:"shoes", pin:[84,509], box:[56,440,88,96]},
  flats:{slot:"shoes", pin:[84,512], box:[56,446,88,90]},
  boots:{slot:"shoes", pin:[86,480], box:[56,430,88,106]},
  bag:{slot:"extra", pin:[162,292], box:[126,226,72,104]}
};
function pinFor(look, p){
  if (p.pin) return p.pin;
  const base = TYPE_INFO[p.type].pin.slice();
  const top = look.pieces.find(q=>TYPE_INFO[q.type].slot==="top");
  if (TYPE_INFO[p.type].slot==="bottom" && top && (top.type==="kurta"||top.type==="dress")) base[1]=455;
  return [base[0]/2, base[1]/640*100]; // to percent of box
}
function drawPiece(p, u){
  const c=p.color, d=shade(c,-.28), sh=`url(#sh${u})`, s=`stroke="${d}"`;
  const fillShade = path => `<path d="${path}" fill="${c}"/><path d="${path}" fill="${sh}"/>`;
  switch(p.type){
    case "tee": {
      const b="M66,100 Q100,90 134,100 L131,264 L69,264 Z";
      return `<path d="M66,100 L48,150 L63,157 L73,120 Z" fill="${c}"/><path d="M134,100 L152,150 L137,157 L127,120 Z" fill="${shade(c,-.08)}"/>${fillShade(b)}<path d="M88,96 Q100,111 112,96" fill="none" ${s} stroke-width="2"/>`;
    }
    case "jacket": {
      const b="M63,100 Q100,89 137,100 L135,276 L65,276 Z";
      return `<path d="M68,108 L54,262" stroke="${c}" stroke-width="21" stroke-linecap="round"/><path d="M132,108 L146,262" stroke="${shade(c,-.1)}" stroke-width="21" stroke-linecap="round"/>${fillShade(b)}
      <path d="M100,104 L100,276" ${s} stroke-width="1.6"/><path d="M82,95 L100,124 L118,95" fill="none" ${s} stroke-width="2.2"/>
      <path d="M72,150 h16 M112,150 h16" ${s} stroke-width="1.6"/><path d="M65,262 h70" stroke="${shade(c,-.18)}" stroke-width="7"/>`;
    }
    case "kurta": {
      const b="M64,100 Q100,89 136,100 L133,206 L153,404 L47,404 L67,206 Z";
      return `<path d="M68,108 L56,238" stroke="${c}" stroke-width="20" stroke-linecap="round"/><path d="M132,108 L144,238" stroke="${shade(c,-.1)}" stroke-width="20" stroke-linecap="round"/>${fillShade(b)}
      <path d="M86,96 Q100,112 114,96" fill="none" stroke="${shade(c,.35)}" stroke-width="3"/><path d="M100,104 L100,150" stroke="${shade(c,.35)}" stroke-width="2"/>
      <path d="M49,388 L151,388 L153,404 L47,404 Z" fill="${shade(c,.35)}"/><path d="M50,394 h100" stroke="${d}" stroke-width="1" stroke-dasharray="3 3"/>`;
    }
    case "dress": {
      const b="M74,102 Q100,95 126,102 L124,196 L159,428 L41,428 L76,196 Z";
      return `${fillShade(b)}<path d="M76,196 L124,196" ${s} stroke-width="3"/><path d="M60,322 Q100,334 140,322" fill="none" ${s} stroke-width="1.4" opacity=".6"/><path d="M50,382 Q100,396 150,382" fill="none" ${s} stroke-width="1.4" opacity=".6"/>`;
    }
    case "trousers": {
      const b="M70,248 L130,248 L127,503 L104,503 L100,302 L96,503 L73,503 Z";
      return `${fillShade(b)}<path d="M85,262 L84,500 M115,262 L116,500" ${s} stroke-width="1" opacity=".5"/>`;
    }
    case "wide": {
      const b="M69,248 L131,248 L145,509 L103,509 L100,322 L97,509 L55,509 Z";
      return `${fillShade(b)}<path d="M84,266 L76,506 M116,266 L124,506" ${s} stroke-width="1" opacity=".45"/>`;
    }
    case "skirt": {
      const b="M71,246 L129,246 L149,394 L51,394 Z";
      return `${fillShade(b)}<path d="M71,252 h58" ${s} stroke-width="5"/><path d="M100,256 L100,394" ${s} stroke-width="1.2" opacity=".5"/>`;
    }
    case "sneakers": return `<rect x="66" y="497" width="32" height="23" rx="9" fill="${c}"/><rect x="102" y="497" width="32" height="23" rx="9" fill="${shade(c,-.05)}"/><path d="M66,515 h32 M102,515 h32" stroke="${shade(c,-.25)}" stroke-width="3"/>`;
    case "flats": return `<path d="M68,508 h30 v8 q0,4 -4,4 h-32 q0,-8 6,-12z" fill="${c}"/><path d="M102,508 h30 q6,4 6,12 h-32 q-4,0 -4,-4z" fill="${shade(c,-.08)}"/>`;
    case "boots": return `<rect x="70" y="450" width="28" height="70" rx="6" fill="${c}"/><rect x="102" y="450" width="28" height="70" rx="6" fill="${shade(c,-.1)}"/><path d="M70,514 h28 M102,514 h28" stroke="${shade(c,-.35)}" stroke-width="4"/>`;
    case "bag": return `<path d="M150,262 Q162,230 174,262" fill="none" stroke="${d}" stroke-width="3.5"/><rect x="141" y="258" width="42" height="60" rx="5" fill="${c}"/><rect x="141" y="258" width="42" height="60" rx="5" fill="${sh}"/><path d="M141,270 h42" stroke="${d}" stroke-width="1.2" opacity=".6"/>`;
  }
  return "";
}
function svgModel(look, {crop=null, mirror=true}={}){
  const u = ++UID, skin = look.skin, hair = look.hairColor;
  const by = slot => look.pieces.filter(p=>TYPE_INFO[p.type]?.slot===slot);
  const top = by("top")[0], bottoms = by("bottom"), shoes = by("shoes"), extras = by("extra");
  const showLegs = !bottoms.length || bottoms.some(b=>b.type==="skirt");
  let g = "";
  if (!crop) g += `<ellipse cx="100" cy="521" rx="58" ry="7" fill="url(#gs${u})"/>`;
  if (showLegs) g += `<path d="M87,250 L87,506 M113,250 L113,506" stroke="${skin}" stroke-width="21" stroke-linecap="round"/>`;
  else g += `<path d="M87,480 L87,506 M113,480 L113,506" stroke="${skin}" stroke-width="18" stroke-linecap="round"/>`;
  bottoms.forEach(p=> g += drawPiece(p,u));
  shoes.forEach(p=> g += drawPiece(p,u));
  if (!shoes.length) g += `<rect x="68" y="502" width="30" height="18" rx="8" fill="#222"/><rect x="102" y="502" width="30" height="18" rx="8" fill="#222"/>`;
  g += `<path d="M67,106 L54,262 M133,106 L146,262" stroke="${skin}" stroke-width="16" stroke-linecap="round"/>`;
  if (look.hair==="long") g += `<path d="M74,56 Q74,30 100,30 Q126,30 126,56 L130,136 Q100,146 70,136 Z" fill="${hair}"/>`;
  g += `<rect x="92" y="78" width="16" height="24" rx="6" fill="${shade(skin,-.08)}"/>`;
  if (top) g += drawPiece(top,u); else g += `<path d="M64,100 Q100,90 136,100 L131,262 L69,262 Z" fill="#ddd"/>`;
  g += `<circle cx="54" cy="270" r="8.5" fill="${skin}"/><circle cx="146" cy="270" r="8.5" fill="${skin}"/>`;
  g += `<ellipse cx="100" cy="60" rx="22" ry="25" fill="${skin}"/><ellipse cx="100" cy="60" rx="22" ry="25" fill="url(#sh${u})" opacity=".6"/>`;
  if (look.hair==="short") g += `<path d="M77,58 Q76,32 100,32 Q125,32 123,58 Q118,44 100,44 Q84,44 77,58Z" fill="${hair}"/>`;
  if (look.hair==="long") g += `<path d="M78,56 Q80,34 100,34 Q122,34 123,58 Q110,40 92,46 Q84,50 78,56Z" fill="${hair}"/>`;
  if (look.hair==="bun") g += `<circle cx="100" cy="28" r="12" fill="${hair}"/><path d="M78,58 Q77,34 100,34 Q123,34 122,58 Q114,42 100,42 Q86,42 78,58Z" fill="${hair}"/>`;
  extras.forEach(p=> g += drawPiece(p,u));

  const defs = `<defs>
    <linearGradient id="sh${u}" x1="0" x2="1"><stop offset=".45" stop-color="#000" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".16"/></linearGradient>
    <radialGradient id="gs${u}"><stop offset="0" stop-color="#000" stop-opacity=".22"/><stop offset="1" stop-color="#000" stop-opacity="0"/></radialGradient>
    <linearGradient id="rg${u}" gradientUnits="userSpaceOnUse" x1="0" y1="521" x2="0" y2="610"><stop offset="0" stop-color="#fff" stop-opacity=".55"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>
    <mask id="rm${u}" maskUnits="userSpaceOnUse" x="-50" y="521" width="300" height="140"><rect x="-50" y="521" width="300" height="140" fill="url(#rg${u})"/></mask>
  </defs>`;
  const vb = crop ? crop.join(" ") : "0 0 200 640";
  const refl = (!crop && mirror) ? `<g class="refl" mask="url(#rm${u})"><use href="#m${u}" transform="matrix(1 0 0 -1 0 1042)"/></g>` : "";
  return `<svg viewBox="${vb}" preserveAspectRatio="xMidYMax meet" aria-hidden="true" focusable="false">${defs}<g id="m${u}">${g}</g>${refl}</svg>`;
}
function thumbSvg(look, p){
  if (look.photo && p.thumb){
    const [cx,cy,sc]=p.thumb, hn=sc*0.8/look.ratio;
    const px=((0.5-cx/100*sc)/(1-sc))*100, py=((0.5-cy/100*hn)/(1-hn))*100;
    return `<span class="ph-thumb" style="background-image:url(${look.photo});background-size:${sc*100}% auto;background-position:${px.toFixed(2)}% ${py.toFixed(2)}%"></span>`;
  }
  if (p.img) return `<img src="${p.img}" alt="" style="width:100%;height:100%;object-fit:cover">`;
  const [x,y,w,h] = TYPE_INFO[p.type].box;
  return svgModel(look,{crop:[x,y,w,h]}).replace('preserveAspectRatio="xMidYMax meet"','preserveAspectRatio="xMidYMid meet"');
}

/* =========================================================
   State & helpers
   ========================================================= */
const $ = s => document.querySelector(s);
const fmt = v => new Intl.NumberFormat(CONFIG.locale,{style:"currency",currency:CONFIG.currency,maximumFractionDigits:0}).format(v);
const store = { get(k,d){try{return JSON.parse(localStorage.getItem(k))??d}catch(e){return d}}, set(k,v){try{localStorage.setItem(k,JSON.stringify(v))}catch(e){}} };
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
const S = {
  i:0, open:false, layout:CONFIG.layout, paused: reduce || !CONFIG.autoplayMs,
  hoverPanel:false, hidden:false, drawer:false, expanded:null,
  picks:{}, // lookId -> {pieceId:size}
  bag: store.get("ll_bag_photos_v1", [])
};
const N = LOOKS.length;
const lookTotal = L => L.pieces.reduce((a,p)=>a+p.price,0);
const pieceById = id => { for (const L of LOOKS){ const p=L.pieces.find(q=>q.id===id); if(p) return [L,p]; } return [null,null]; };
// drop any saved bag lines whose product no longer exists (e.g. after editing LOOKS)
S.bag = Array.isArray(S.bag) ? S.bag.filter(l=>l && pieceById(l.pieceId)[0] && l.qty>0) : [];
const picks = L => (S.picks[L.id] ||= {});
for (const L of LOOKS) for (const p of L.pieces) if (p.sizes.length===1) picks(L)[p.id]=p.sizes[0]; // one option = nothing to guess

/* =========================================================
   Build stage
   ========================================================= */
const stage=$("#stage"), modelsEl=$("#models"), tabsEl=$("#tabs"), panel=$("#panel"), controls=$("#controls");
modelsEl.innerHTML = LOOKS.map((L,i)=>`
  <li class="model" data-i="${i}" style="--i:${i}">
    <button class="model-hit" aria-label="Show look ${i+1}: ${L.name}"></button>
    ${L.photo ? `<div class="pbox" style="aspect-ratio:${L.ratio}"><img src="${L.photo}" alt="${L.name} look" draggable="false"><img class="refl" src="${L.photo}" alt="" aria-hidden="true" draggable="false">` : svgModel(L,{mirror:true})}
    <div class="pins">${L.pieces.map((p,k)=>{const [x,y]=pinFor(L,p);return `<button class="pin${x>60?' tip-left':''}" style="left:${x}%;top:${y}%;--i:${k}" data-look="${i}" data-piece="${p.id}" aria-label="${k+1}: ${p.name}">${k+1}<span class="tip">${p.name}</span></button>`}).join("")}</div>${L.photo?'</div>':''}
  </li>`).join("");
const modelEls=[...modelsEl.children];
tabsEl.innerHTML = LOOKS.map((L,i)=>`<button class="tab" role="tab" data-i="${i}" aria-label="${String(i+1).padStart(2,"0")} ${L.name}, look ${i+1}"><span class="bar"><span class="fill"></span></span><span class="tab-lbl"><span class="num">${String(i+1).padStart(2,"0")}</span><span class="nm">${L.name}</span></span></button>`).join("");
const tabEls=[...tabsEl.children];

const prevD = new Array(N).fill(null);
function wrapD(i){ let d=(i-S.i)%N; if(d>N/2) d-=N; if(d<=-N/2) d+=N; return d; }

function place(){
  const W=stage.clientWidth, H=stage.clientHeight;
  const mh = H*0.9, mw = mh*200/640;
  stage.style.setProperty("--mw", mw+"px");
  modelEls.forEach((el,i)=>{
    let d=wrapD(i), x=0,y=0,s=1,b=0,o=1,z=10;
    const ad=Math.abs(d);
    switch(S.layout){
      case "lineup":
        x=d*mw*0.8; s=1-Math.min(ad,3)*0.05; b=ad?Math.min(1.6+ad*1.6,6):0; o=ad>2?0:(ad===2?.75:1); z=10-ad; break;
      case "queue": {
        let q=d; if(q<-1) q+=N;
        if(q===-1){ s=1.32; y=H*0.1; o=0; z=30; x=mw*0.1; b=2; }
        else { s=Math.max(1-q*0.17,.3); x=q*mw*0.16*(q%2?1:-1)+(q?mw*0.05:0); y=-q*H*0.045; b=q?Math.min(1.5+q*1.4,6):0; o=q>3?0:1; z=20-q; }
        break; }
      case "ring": {
        const a=d*2*Math.PI/N, depth=Math.cos(a);
        x=Math.sin(a)*W*0.34; s=.6+.4*(depth+1)/2; y=-(1-depth)*H*0.045; b=(1-depth)*2.6; o=.3+.7*(depth+1)/2; z=Math.round(depth*10)+10; break; }
      case "gallery": {
        const sp=Math.min(mw*0.86,(W-24)/N), vis=mw*0.9, base=Math.min(1,sp/vis);
        x=(i-(N-1)/2)*sp; s=base*(d===0&&base>=1?1.04:1); b=d===0?0:1.6; o=1; z=d===0?20:10; break; }
    }
    if (S.open){ if(d===0){ x=S.layout==="gallery"?0:x; s=1; b=0; z=40; y=0; } else { x+=(d>0?1:-1)*W*0.75; o=0; } }
    // no-anim on wrap-around jumps
    if (prevD[i]!==null && Math.abs(d-prevD[i])>N/2 && S.layout!=="gallery"){ el.classList.add("no-anim"); el.offsetWidth; }
    prevD[i]=d;
    el.style.transform=`translate(${x}px,${y}px) scale(${s})`;
    el.style.filter=b>0.05?`blur(${b.toFixed(2)}px)`:"none";
    el.style.opacity=o; el.style.zIndex=z;
    el.classList.toggle("is-active", d===0);
    el.setAttribute("aria-hidden", o===0?"true":"false");
    el.querySelector(".model-hit").setAttribute("aria-label", d===0 ? (S.open?`Close the look: ${LOOKS[i].name}`:`Open the look: ${LOOKS[i].name}`) : `Show look ${i+1}: ${LOOKS[i].name}`);
    el.querySelector(".model-hit").tabIndex = o===0?-1:0;
    if (el.classList.contains("no-anim")) requestAnimationFrame(()=>requestAnimationFrame(()=>el.classList.remove("no-anim")));
  });
  stage.classList.toggle("is-open", S.open);
  $("#hint").textContent = S.open ? "Tap a dot to shop the piece" : (S.layout==="gallery" ? "Tap a look to bring it forward" : "Tap the front look to open it");
  $("#viewTag").textContent = `Look ${String(S.i+1).padStart(2,"0")} / ${String(N).padStart(2,"0")}`;
}

function setTabs(){
  tabEls.forEach((t,i)=>{
    t.classList.toggle("is-active", i===S.i);
    t.classList.toggle("is-done", i<S.i);
    t.setAttribute("aria-selected", i===S.i);
    const f=t.querySelector(".fill"); const nf=f.cloneNode(); f.replaceWith(nf); // restart timer
    nf.addEventListener("animationend",()=>{ if(i===S.i) go(S.i+1,true); });
  });
  rest();
}
function rest(){
  const resting = S.paused || S.open || S.hoverPanel || S.hidden || S.drawer;
  controls.classList.toggle("is-resting", resting);
  controls.classList.toggle("is-static", !CONFIG.autoplayMs);
  stage.style.setProperty("--auto", CONFIG.autoplayMs+"ms"); controls.style.setProperty("--auto", CONFIG.autoplayMs+"ms");
  $("#pauseBtn").innerHTML = S.paused
    ? `<svg viewBox="0 0 16 16" fill="currentColor"><path d="M4 2.5v11l9-5.5z"/></svg>`
    : `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="3.5" y="2.5" width="3" height="11" rx="1"/><rect x="9.5" y="2.5" width="3" height="11" rx="1"/></svg>`;
  $("#pauseBtn").setAttribute("aria-label", S.paused?"Play the looks":"Pause the looks");
}
function go(n, auto=false){
  const ni=((n%N)+N)%N;
  if (ni===S.i && !auto) return;
  S.i=ni; S.expanded=null;
  if (!auto) S.open = S.open && false;
  place(); setTabs(); renderPanel();
  $("#live").textContent=`Look ${S.i+1} of ${N}: ${LOOKS[S.i].name}`;
}
function setOpen(v){ S.open=v; place(); rest(); }

/* =========================================================
   Panel
   ========================================================= */
function priceHTML(p){
  return `<span class="now${p.compare?' on-sale':''}">${fmt(p.price)}</span>${p.compare?`<s>${fmt(p.compare)}</s>`:""}`;
}
function renderPanel(keepScroll){
  const L=LOOKS[S.i], pk=picks(L), tot=lookTotal(L), bundled=Math.round(tot*(1-(L.bundle||0)/100));
  const st = panel.querySelector(".p-list")?.scrollTop||0;
  panel.innerHTML = `
    <div class="p-head"><h3>In this look</h3><span class="p-count">${L.pieces.length} pieces</span></div>
    <p class="p-sub">Look ${String(S.i+1).padStart(2,"0")} · ${L.name}</p>
    <ul class="p-list">${L.pieces.map((p,k)=>`
      <li class="item${S.expanded===p.id?' is-expanded':''}" data-piece="${p.id}">
        <div class="item-row">
          <div class="thumb">${thumbSvg(L,p)}<span class="badge">${k+1}</span></div>
          <div>
            <p class="brand">${CONFIG.brand}</p>
            <p class="pname"><a href="#" data-open="${p.id}">${p.name}</a></p>
            <p class="price">${priceHTML(p)}</p>
            ${pk[p.id]&&p.sizes.length>1?`<p class="picked">Size ${pk[p.id]}</p>`:""}
          </div>
          <button class="plus" data-toggle="${p.id}" aria-expanded="${S.expanded===p.id}" aria-label="${p.name}: options"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v12M2 8h12"/></svg></button>
        </div>
        <div class="opts"><div class="opts-in"><div class="opts-pad">
          <p class="opt-lbl"><span>Size</span><span>${p.soldOut?.length?"Crossed out = sold out":""}</span></p>
          <div class="sizes" role="group" aria-label="${p.name} sizes">${p.sizes.map(sz=>{const so=(p.soldOut||[]).includes(sz);return `<button class="size" data-size="${sz}" data-p="${p.id}" aria-pressed="${pk[p.id]===sz}" ${so?'disabled aria-label="'+sz+', sold out"':''}>${sz}</button>`}).join("")}</div>
          <button class="add-one" data-add="${p.id}">Add to bag · ${fmt(p.price)}</button>
          <p class="opt-msg" id="msg-${p.id}"></p>
        </div></div></div>
      </li>`).join("")}
    </ul>
    <div class="p-foot">
      <div class="ctl-row"><div><p class="t">Complete the look</p><p class="n">${L.pieces.length} pieces</p></div>
        <div class="total">${L.bundle?`<s>${fmt(tot)}</s>`:""}<p class="big">${fmt(bundled)}</p></div></div>
      ${L.bundle?`<p class="save">Save ${L.bundle}% on the full look · code <b>${L.code}</b> applied in the bag</p>`:""}
      <button class="add-look" data-addlook>Add full look to bag<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
      <p class="status" id="lookStatus" role="status"></p>
    </div>`;
  if (keepScroll) panel.querySelector(".p-list").scrollTop=st;
  $("#sLook").textContent=`Look ${String(S.i+1).padStart(2,"0")} · ${L.name} · ${L.pieces.length} pieces`;
  $("#sPrice").innerHTML=fmt(bundled)+(L.bundle?` <s style="font-size:12px;font-weight:400;color:var(--muted)">${fmt(tot)}</s>`:"");
}
function expand(pid, flash){
  S.expanded = (S.expanded===pid && !flash) ? null : pid;
  panel.querySelectorAll(".item").forEach(li=>{
    const on=li.dataset.piece===S.expanded; li.classList.toggle("is-expanded",on);
    li.querySelector(".plus").setAttribute("aria-expanded",on);
    if (flash && li.dataset.piece===pid){ li.classList.remove("flash"); li.offsetWidth; li.classList.add("flash"); li.scrollIntoView({block:"nearest",behavior:reduce?"auto":"smooth"}); }
  });
  hotPin(S.expanded);
}
function hotPin(pid){ document.querySelectorAll(".pin").forEach(p=>p.classList.toggle("is-hot", p.dataset.piece===pid && +p.dataset.look===S.i)); }

panel.addEventListener("click",e=>{
  const t=e.target.closest("button,a"); if(!t) return;
  const L=LOOKS[S.i];
  if (t.dataset.toggle){ expand(t.dataset.toggle); }
  else if (t.dataset.open){ e.preventDefault(); expand(t.dataset.open,true); }
  else if (t.dataset.size){ picks(L)[t.dataset.p]=t.dataset.size; S.expanded=t.dataset.p; renderPanel(true); hotPin(S.expanded); $("#lookStatus").textContent=""; }
  else if (t.dataset.add){
    const p=L.pieces.find(q=>q.id===t.dataset.add), sz=picks(L)[p.id];
    if(!sz){ $("#msg-"+p.id).textContent="Choose a size first."; return; }
    addToBag([{pieceId:p.id,size:sz}],null); toast(`${p.name} (${sz}) added to your bag`);
  }
  else if (t.hasAttribute("data-addlook")) addLook();
});
panel.addEventListener("pointerenter",()=>{S.hoverPanel=true;rest();});
panel.addEventListener("pointerleave",()=>{S.hoverPanel=false;rest();});
panel.addEventListener("focusin",()=>{S.hoverPanel=true;rest();});
panel.addEventListener("focusout",e=>{ if(!panel.contains(e.relatedTarget)){S.hoverPanel=false;rest();} });

function addLook(){
  const L=LOOKS[S.i], pk=picks(L);
  const missing=L.pieces.filter(p=>!pk[p.id]);
  const status=$("#lookStatus");
  if (missing.length){
    const m=missing[0];
    if (!S.open) setOpen(true);
    expand(m.id,true);
    const msg=`Choose a size for ${missing.map(p=>p.name).join(", ")}.`;
    if (status){ status.textContent=msg; status.className="status err"; }
    const om=$("#msg-"+m.id); if(om) om.textContent="Choose a size — we never guess one for you.";
    if (matchMedia("(max-width:860px)").matches){ toast(msg); }
    return;
  }
  addToBag(L.pieces.map(p=>({pieceId:p.id,size:pk[p.id]})), L.id);
  if (status){ status.textContent=`The full look is in your bag${L.bundle?` with ${L.bundle}% off`:""}.`; status.className="status"; }
  toast(`Look ${String(S.i+1).padStart(2,"0")} · ${L.name} added — ${L.pieces.length} pieces`);
  openDrawer();
}
$("#sAdd").addEventListener("click", addLook);

/* =========================================================
   Bag
   ========================================================= */
function addToBag(items, bundleId){
  items.forEach(({pieceId,size})=>{
    const key=`${pieceId}|${size}|${bundleId||""}`;
    const ex=S.bag.find(l=>l.key===key);
    if (ex) ex.qty++; else S.bag.push({key,pieceId,size,qty:1,bundle:bundleId});
  });
  saveBag(); const b=$("#bagBtn"); b.classList.remove("bump"); b.offsetWidth; b.classList.add("bump");
}
function saveBag(){ store.set("ll_bag_photos_v1",S.bag); renderBag(); }
function bundleDiscounts(){
  let total=0; const notes=[];
  LOOKS.forEach(L=>{
    if(!L.bundle) return;
    const lines=S.bag.filter(l=>l.bundle===L.id);
    if (L.pieces.every(p=>lines.some(l=>l.pieceId===p.id))){
      const sets=Math.min(...L.pieces.map(p=>lines.filter(l=>l.pieceId===p.id).reduce((a,l)=>a+l.qty,0)));
      const amt=Math.round(lookTotal(L)*sets*L.bundle/100); total+=amt; notes.push({L,amt});
    }
  });
  return {total,notes};
}
function renderBag(){
  const count=S.bag.reduce((a,l)=>a+l.qty,0);
  $("#bagCount").textContent=count;
  const body=$("#dBody"), foot=$("#dFoot");
  if(!S.bag.length){ body.innerHTML=`<p class="d-empty">Your bag is empty.<br>Open a look and add a piece, or the whole outfit.</p>`; foot.innerHTML=""; return; }
  body.innerHTML=S.bag.map((l,idx)=>{ const [L,p]=pieceById(l.pieceId); if(!L) return ""; return `
    <div class="line"><div class="thumb">${thumbSvg(L,p)}</div>
      <div><p class="ln">${p.name}</p><p class="lm">${p.sizes.length>1?"Size "+l.size+" · ":""}${CONFIG.brand}</p>
        ${l.bundle?`<span class="bundle-tag">Look · ${LOOKS.find(x=>x.id===l.bundle).name}</span>`:""}
        <div class="qty"><button data-q="${idx}" data-d="-1" aria-label="Fewer">−</button><span>${l.qty}</span><button data-q="${idx}" data-d="1" aria-label="More">+</button></div></div>
      <div><p class="lp">${fmt(p.price*l.qty)}</p><button class="rm" data-rm="${idx}">Remove</button></div></div>`}).join("");
  const sub=S.bag.reduce((a,l)=>a+(pieceById(l.pieceId)[1]?.price||0)*l.qty,0), disc=bundleDiscounts();
  foot.innerHTML=`<p class="sum"><span>Subtotal</span><span>${fmt(sub)}</span></p>
    ${disc.notes.map(n=>`<p class="sum disc"><span>${n.L.code} · full look ${n.L.name}</span><span>−${fmt(n.amt)}</span></p>`).join("")}
    <p class="sum tot"><span>Total</span><span>${fmt(sub-disc.total)}</span></p>
    <button class="checkout" id="checkout">Checkout</button><p class="d-note">Taxes and shipping calculated at checkout.</p>`;
  $("#checkout").onclick=()=>toast("Demo store — connect your checkout here.");
}
$("#dBody").addEventListener("click",e=>{
  const t=e.target.closest("button"); if(!t) return;
  if (t.dataset.q!==undefined){ const l=S.bag[+t.dataset.q]; l.qty+=+t.dataset.d; if(l.qty<1) S.bag.splice(+t.dataset.q,1); saveBag(); }
  if (t.dataset.rm!==undefined){ S.bag.splice(+t.dataset.rm,1); saveBag(); }
});
let lastFocus=null;
function openDrawer(){ lastFocus=document.activeElement; S.drawer=true; rest(); $("#drawer").classList.add("show"); $("#scrim").classList.add("show"); $("#drawer").setAttribute("aria-hidden","false"); setTimeout(()=>$("#dClose").focus(),50); }
function closeDrawer(){ S.drawer=false; rest(); $("#drawer").classList.remove("show"); $("#scrim").classList.remove("show"); $("#drawer").setAttribute("aria-hidden","true"); lastFocus?.focus?.(); }
$("#bagBtn").onclick=openDrawer; $("#dClose").onclick=closeDrawer; $("#scrim").onclick=closeDrawer;

let tt; function toast(m){ const t=$("#toast"); t.textContent=m; t.classList.add("show"); clearTimeout(tt); tt=setTimeout(()=>t.classList.remove("show"),2600); }

/* =========================================================
   Stage interactions: click, pins, swipe, keys
   ========================================================= */
let swiped=false;
modelsEl.addEventListener("click",e=>{
  if (swiped){ swiped=false; return; }
  const pin=e.target.closest(".pin");
  if (pin){ if(!S.open) setOpen(true); expand(pin.dataset.piece,true); if(matchMedia("(max-width:860px)").matches) panel.scrollIntoView({behavior:reduce?"auto":"smooth",block:"start"}); return; }
  const hit=e.target.closest(".model-hit"); if(!hit) return;
  const i=+hit.closest(".model").dataset.i;
  if (i!==S.i){ S.open=false; go(i); }
  else setOpen(!S.open);
});
$("#backBtn").onclick=()=>setOpen(false);
$("#prevBtn").onclick=()=>{S.open=false;go(S.i-1);};
$("#nextBtn").onclick=()=>{S.open=false;go(S.i+1);};
$("#pauseBtn").onclick=()=>{S.paused=!S.paused;rest();};
tabsEl.addEventListener("click",e=>{const t=e.target.closest(".tab"); if(t){S.open=false;go(+t.dataset.i);}});

let sx=null, sy=null;
stage.addEventListener("pointerdown",e=>{sx=e.clientX;sy=e.clientY;});
stage.addEventListener("pointerup",e=>{
  if(sx===null) return; const dx=e.clientX-sx, dy=e.clientY-sy; sx=null;
  if(Math.abs(dx)>45 && Math.abs(dx)>Math.abs(dy)){ swiped=true; setTimeout(()=>swiped=false,50); S.open=false; go(S.i+(dx<0?1:-1)); }
});
stage.addEventListener("keydown",e=>{
  if(e.key==="ArrowRight"){e.preventDefault();S.open=false;go(S.i+1);}
  if(e.key==="ArrowLeft"){e.preventDefault();S.open=false;go(S.i-1);}
  if((e.key==="Enter"||e.key===" ") && e.target===stage){e.preventDefault();setOpen(!S.open);}
});
document.addEventListener("keydown",e=>{ if(e.key==="Escape"){ if(S.drawer) closeDrawer(); else if(S.open) setOpen(false);} });
document.addEventListener("visibilitychange",()=>{S.hidden=document.hidden;rest();});

/* =========================================================
   Customiser, sticky bar, resize
   ========================================================= */
function applyCopy(){ const c=CONFIG.copy[S.layout]; $("#cwEyebrow").textContent=c.eyebrow; $("#cwTitle").textContent=c.title; $("#cwSub").textContent=c.sub; }
function segInit(id, cur, cb){ const seg=$(id); const mark=v=>seg.querySelectorAll("button").forEach(b=>b.setAttribute("aria-pressed",b.dataset.v===v)); mark(cur); seg.onclick=e=>{const b=e.target.closest("button"); if(!b) return; mark(b.dataset.v); cb(b.dataset.v);}; }
segInit("#layoutSeg", S.layout, v=>{ S.layout=v; S.open=false; prevD.fill(null); applyCopy(); place(); rest(); });
segInit("#schemeSeg", CONFIG.scheme, v=>{ document.body.dataset.scheme=v; });
document.body.dataset.scheme=CONFIG.scheme;
const mt=$("#mirrorToggle"); mt.checked=CONFIG.mirror; stage.classList.toggle("no-mirror",!CONFIG.mirror);
mt.onchange=()=>stage.classList.toggle("no-mirror",!mt.checked);

const io=new IntersectionObserver(([en])=>$("#sticky").classList.toggle("show",en.isIntersecting),{threshold:.08});
io.observe($("#runway"));
new ResizeObserver(()=>{ modelEls.forEach(el=>el.classList.add("no-anim")); place(); requestAnimationFrame(()=>modelEls.forEach(el=>el.classList.remove("no-anim"))); }).observe(stage);

applyCopy(); place(); setTabs(); renderPanel(); renderBag();
</script>
</body>
</html>
```


## Build instructions by tool

### Claude Code

Open a terminal in an empty folder, run `claude`, then paste the prompt.

```text
Build the "Runway shop-the-look section" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/runway-shop-the-look-section/.
Stack: CSS, HTML, JavaScript.
Follow the plan's steps in order: Build the stage and the line-up → Open a look with numbered pins → Add the bag and the full-look discount → Layouts, schemes and the phone version.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Claude Code.)
```

### Cursor

Open an empty folder in Cursor, start an Agent chat, then paste the prompt.

```text
Build the "Runway shop-the-look section" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/runway-shop-the-look-section/.
Stack: CSS, HTML, JavaScript.
Follow the plan's steps in order: Build the stage and the line-up → Open a look with numbered pins → Add the bag and the full-look discount → Layouts, schemes and the phone version.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Cursor.)
```

### Lovable

Start a new Lovable project and paste the prompt as your first message.

```text
Build the "Runway shop-the-look section" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/runway-shop-the-look-section/.
Stack: CSS, HTML, JavaScript.
Follow the plan's steps in order: Build the stage and the line-up → Open a look with numbered pins → Add the bag and the full-look discount → Layouts, schemes and the phone version.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Lovable.)
```

## Definition of done

- Every build step is complete and the app runs locally.
- The main user flow works end to end.
- Share your build on OwnlyPlans with a demo link, build time and tool.
