[object Object]

← back to Dw Yolo Loop

Artmura microsite: editorial Italian storefront (Express) for the 161 staged products

6bb84eea9776fc6b9e534942f92d2a9f232d07bd · 2026-06-12 09:04:12 -0700 · Steve Abrams

- Hero rotation (luxury room shots) + serif wordmark; corner-nav UR, logo UL
- Product grid with sort (Newest/Color/Collection/SKU/Title/Price) + density slider, both localStorage-persisted (standing rule)
- Collection-book chip filter (Opera / La Scala Milano)
- PDP = Figma V1 Editorial Magazine: hero gallery left, spec rail right, full spec table, story/specs/care
- Serves swatch+room images from staged .newwall-ref; PREVIEW banner: pricing reference-only, not deployed
- Server on :9920 (auto-bumps to free port)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 6bb84eea9776fc6b9e534942f92d2a9f232d07bd
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 12 09:04:12 2026 -0700

    Artmura microsite: editorial Italian storefront (Express) for the 161 staged products
    
    - Hero rotation (luxury room shots) + serif wordmark; corner-nav UR, logo UL
    - Product grid with sort (Newest/Color/Collection/SKU/Title/Price) + density slider, both localStorage-persisted (standing rule)
    - Collection-book chip filter (Opera / La Scala Milano)
    - PDP = Figma V1 Editorial Magazine: hero gallery left, spec rail right, full spec table, story/specs/care
    - Serves swatch+room images from staged .newwall-ref; PREVIEW banner: pricing reference-only, not deployed
    - Server on :9920 (auto-bumps to free port)
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 artmura-site/package-lock.json   | 827 +++++++++++++++++++++++++++++++++++++++
 artmura-site/package.json        |   9 +
 artmura-site/public/index.html   | 162 ++++++++
 artmura-site/public/product.html |  79 ++++
 artmura-site/public/styles.css   | 144 +++++++
 artmura-site/server.js           |  85 ++++
 6 files changed, 1306 insertions(+)

diff --git a/artmura-site/package-lock.json b/artmura-site/package-lock.json
new file mode 100644
index 0000000..0e660e2
--- /dev/null
+++ b/artmura-site/package-lock.json
@@ -0,0 +1,827 @@
+{
+  "name": "artmura-site",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "artmura-site",
+      "version": "0.1.0",
+      "dependencies": {
+        "express": "^4.19.2"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "license": "MIT"
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.5",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+      "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "~1.2.0",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "on-finished": "~2.4.1",
+        "qs": "~6.15.1",
+        "raw-body": "~2.5.3",
+        "type-is": "~1.6.18",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+      "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+      "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.22.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+      "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "~1.20.5",
+        "content-disposition": "~0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "~0.7.1",
+        "cookie-signature": "~1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.3.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.0",
+        "merge-descriptors": "1.0.3",
+        "methods": "~1.1.2",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "~0.1.12",
+        "proxy-addr": "~2.0.7",
+        "qs": "~6.15.1",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "~0.19.0",
+        "serve-static": "~1.16.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "~2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+      "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "~2.0.2",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+      "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "inherits": "~2.0.4",
+        "setprototypeof": "~1.2.0",
+        "statuses": "~2.0.2",
+        "toidentifier": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+      "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+      "license": "MIT"
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.15.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+      "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+      "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+      "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.1",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "~2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "~2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.3",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+      "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "~0.19.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+      "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4",
+        "side-channel-list": "^1.0.1",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+      "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    }
+  }
+}
diff --git a/artmura-site/package.json b/artmura-site/package.json
new file mode 100644
index 0000000..c5eb02f
--- /dev/null
+++ b/artmura-site/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "artmura-site",
+  "version": "0.1.0",
+  "private": true,
+  "description": "Artmura wallcoverings microsite (DW vendor onboard, stage-only preview)",
+  "main": "server.js",
+  "scripts": { "start": "node server.js" },
+  "dependencies": { "express": "^4.19.2" }
+}
diff --git a/artmura-site/public/index.html b/artmura-site/public/index.html
new file mode 100644
index 0000000..19700ab
--- /dev/null
+++ b/artmura-site/public/index.html
@@ -0,0 +1,162 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Artmura — Italian Wallcoverings</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/styles.css">
+</head>
+<body>
+
+<div class="corner ul on-hero" id="cUL"><span class="wordmark">Artmura<span class="dot">.</span></span></div>
+<nav class="corner ur on-hero" id="cUR">
+  <a href="#collections">Collections</a>
+  <a href="#catalog">Catalog</a>
+  <a href="#" id="navTrade">Trade</a>
+</nav>
+
+<!-- HERO -->
+<header class="hero" id="hero">
+  <div class="hero-inner">
+    <div class="hero-kicker">Made in Italy · Non-Woven Wallcoverings</div>
+    <h1>Artmura</h1>
+    <div class="sub">Architectural surfaces, hand-finished.</div>
+  </div>
+  <a class="enter" href="#catalog">Explore the Collection</a>
+</header>
+
+<!-- COLLECTIONS -->
+<section class="books" id="collections">
+  <div class="eyebrow">The Books</div>
+  <h2>Opera &amp; La Scala Milano</h2>
+  <div class="chips" id="chips"></div>
+</section>
+
+<!-- CONTROLS -->
+<div class="controls" id="catalog">
+  <div class="controls-in">
+    <a href="#" class="bar-wm">Artmura<span class="dot">.</span></a>
+    <div class="count"><b id="cnt">—</b> wallcoverings</div>
+    <div class="ctl">
+      <label for="sort">Sort</label>
+      <select id="sort">
+        <option value="newest">Newest</option>
+        <option value="color">Color</option>
+        <option value="series">Collection</option>
+        <option value="sku">SKU A→Z</option>
+        <option value="title">Title A→Z</option>
+        <option value="price_asc">Price ↑</option>
+        <option value="price_desc">Price ↓</option>
+      </select>
+    </div>
+    <div class="ctl">
+      <label for="density">Density</label>
+      <input type="range" id="density" min="2" max="6" step="1" value="4">
+    </div>
+  </div>
+</div>
+
+<!-- GRID -->
+<main class="grid-wrap">
+  <div class="grid" id="grid"></div>
+</main>
+
+<footer>
+  <div class="foot-in">
+    <div>
+      <div class="wm">Artmura<span style="color:var(--gold)">.</span></div>
+      <div style="margin-top:8px">Italian non-woven wallcoverings · Residential grade</div>
+    </div>
+    <div class="gate">Preview catalog for Designer Wallcoverings onboarding. Pricing shown is a market
+      reference only and is not a published trade price.</div>
+  </div>
+</footer>
+
+<div class="preview-flag">PREVIEW · staged onboarding build · pricing is <b>reference only</b>, not customer-facing until trade pricing is confirmed</div>
+
+<script>
+const $=s=>document.querySelector(s), grid=$('#grid');
+let ALL=[], view=[], activeBook=null;
+const LS={sort:'artmura.sort', dens:'artmura.density'};
+
+const heroBg = url => `linear-gradient(0deg,rgba(0,0,0,0),rgba(0,0,0,0)),url("${url}")`;
+
+// hero rotation from room shots
+function startHero(items){
+  const pool=items.filter(p=>p.room).sort(()=>0).slice(0,7); // first 7 with room shots
+  const h=$('#hero');
+  pool.forEach((p,i)=>{
+    const d=document.createElement('div');
+    d.className='slide'+(i===0?' on':'');
+    d.style.backgroundImage=`url("${p.room}")`;
+    h.insertBefore(d,h.firstChild);
+  });
+  const slides=[...document.querySelectorAll('.hero .slide')];
+  let i=0; if(slides.length>1) setInterval(()=>{slides[i].classList.remove('on');i=(i+1)%slides.length;slides[i].classList.add('on');},5200);
+}
+
+const COLOR_ORDER=['white','off-white','cream','beige','sand','silver','grey','gray','taupe','gold','golden','green','blue','navy','red','brown','charcoal','black'];
+function colorRank(c){const k=(c||'').toLowerCase();const i=COLOR_ORDER.findIndex(x=>k.includes(x));return i<0?99:i;}
+
+function sortItems(items,mode){
+  const a=[...items];
+  const num=p=>p.price||0;
+  switch(mode){
+    case 'color': a.sort((x,y)=>colorRank(x.color)-colorRank(y.color)||x.series.localeCompare(y.series));break;
+    case 'series': a.sort((x,y)=>(x.series||'').localeCompare(y.series)|| (x.color||'').localeCompare(y.color));break;
+    case 'sku': a.sort((x,y)=>(x.sku||'').localeCompare(y.sku));break;
+    case 'title': a.sort((x,y)=>(x.title||'').localeCompare(y.title));break;
+    case 'price_asc': a.sort((x,y)=>num(x)-num(y));break;
+    case 'price_desc': a.sort((x,y)=>num(y)-num(x));break;
+    default: a.sort((x,y)=> new Date(y.published_at||0)-new Date(x.published_at||0));
+  }
+  return a;
+}
+
+function render(){
+  let items = activeBook ? ALL.filter(p=>p.book===activeBook) : ALL;
+  items = sortItems(items, $('#sort').value);
+  view=items;
+  $('#cnt').textContent=items.length;
+  grid.innerHTML = items.map(p=>`
+    <div class="card" data-h="${p.handle}">
+      <div class="ph" style="background-image:url('${p.swatch||p.room||''}')"></div>
+      <div class="body">
+        <div class="ser">${p.series||''}</div>
+        <div class="nm">${p.color||p.title}</div>
+        <div class="meta">
+          <span class="col">${p.book||'Artmura'}</span>
+          <span class="pr">$${(p.price||0).toFixed(0)} <small>/yd</small></span>
+        </div>
+      </div>
+    </div>`).join('');
+}
+grid.addEventListener('click',e=>{const c=e.target.closest('.card');if(c)location.href='/product/'+c.dataset.h;});
+
+function chips(facets){
+  const wrap=$('#chips');
+  const all=`<div class="chip ${!activeBook?'active':''}" data-b="">All<span class="n">${facets.total}</span></div>`;
+  wrap.innerHTML=all+facets.books.map(([b,n])=>`<div class="chip ${activeBook===b?'active':''}" data-b="${b}">${b}<span class="n">${n}</span></div>`).join('');
+  wrap.querySelectorAll('.chip').forEach(ch=>ch.onclick=()=>{activeBook=ch.dataset.b||null;chips(facets);render();$('#catalog').scrollIntoView({behavior:'smooth'});});
+}
+
+// density + sort persistence
+function applyDensity(v){document.documentElement.style.setProperty('--cols',v);}
+$('#density').addEventListener('input',e=>{applyDensity(e.target.value);localStorage.setItem(LS.dens,e.target.value);});
+$('#sort').addEventListener('change',()=>{localStorage.setItem(LS.sort,$('#sort').value);render();});
+
+// hide hero corners + show bar wordmark once scrolled into catalog
+addEventListener('scroll',()=>{document.body.classList.toggle('scrolled',scrollY>innerHeight*0.82);});
+
+(async()=>{
+  const savedSort=localStorage.getItem(LS.sort); if(savedSort)$('#sort').value=savedSort;
+  const savedDens=localStorage.getItem(LS.dens)||'4'; $('#density').value=savedDens; applyDensity(savedDens);
+  const [pd,fc]=await Promise.all([fetch('/api/products').then(r=>r.json()),fetch('/api/facets').then(r=>r.json())]);
+  ALL=pd.products; startHero(ALL); chips(fc); render();
+})();
+</script>
+</body>
+</html>
diff --git a/artmura-site/public/product.html b/artmura-site/public/product.html
new file mode 100644
index 0000000..860cffe
--- /dev/null
+++ b/artmura-site/public/product.html
@@ -0,0 +1,79 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Artmura — Wallcovering</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/styles.css">
+</head>
+<body>
+
+<div class="corner ul"><a href="/" class="wordmark">Artmura<span class="dot">.</span></a></div>
+<nav class="corner ur">
+  <a href="/#collections">Collections</a>
+  <a href="/#catalog">Catalog</a>
+</nav>
+
+<main class="pdp" id="pdp"><div class="crumbs">Loading…</div></main>
+
+<div class="preview-flag">PREVIEW · staged onboarding build · pricing is <b>reference only</b>, not customer-facing until trade pricing is confirmed</div>
+
+<script>
+const handle=location.pathname.split('/').pop();
+const esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
+
+function specRow(k,v){return v?`<div class="row"><span class="k">${k}</span><span class="v">${esc(v)}</span></div>`:'';}
+
+(async()=>{
+  const r=await fetch('/api/product/'+handle);
+  if(!r.ok){document.getElementById('pdp').innerHTML='<div class="crumbs"><a href="/">← Back</a></div><h1 style="font-family:var(--serif)">Not found</h1>';return;}
+  const p=await r.json();
+  document.title=`${p.title} — Artmura`;
+  const imgs=p.images.length?p.images:[p.swatch].filter(Boolean);
+  const main=imgs[0];
+
+  document.getElementById('pdp').innerHTML=`
+    <div class="crumbs"><a href="/">Artmura</a> &nbsp;/&nbsp; <a href="/#catalog">${esc(p.series)}</a> &nbsp;/&nbsp; ${esc(p.color||p.title)}</div>
+    <div class="pdp-top">
+      <div class="gallery">
+        <div class="main" id="main" style="background-image:url('${main}')"></div>
+        ${imgs.length>1?`<div class="thumbs">${imgs.map((s,i)=>`<div class="t ${i===0?'on':''}" data-s="${s}" style="background-image:url('${s}')"></div>`).join('')}</div>`:''}
+      </div>
+      <div class="rail">
+        <div class="ser">${esc(p.series)} · ${esc(p.book||'Artmura')}</div>
+        <h1>${esc(p.color||p.title)}</h1>
+        <div class="colr">${esc(p.sku)} · ${esc(p.substrate||'Non-Woven')}</div>
+        <div class="price">$${(p.price||0).toFixed(0)} <small>per yard · reference</small></div>
+        <button class="cta" id="sample">Order Sample · $${(p.sample_price||5).toFixed(0)}</button>
+        <a class="cta ghost" href="${esc(p.product_url)}" target="_blank" rel="noopener noreferrer">View source reference ↗</a>
+        <div class="spec">
+          ${specRow('Item code',p.sku)}
+          ${specRow('Grade',p.grade)}
+          ${specRow('Format',p.sold_by)}
+          ${specRow('Dimensions',p.width)}
+          ${specRow('Substrate',p.substrate)}
+          ${specRow('Wall coverage',p.coverage)}
+          ${specRow('Lead-time',p.lead_time)}
+          ${specRow('Origin',p.origin)}
+        </div>
+      </div>
+    </div>
+    <div class="story">
+      <div><h3>The story</h3><div>${p.body_html||'<p>'+esc(p.title)+' by Artmura.</p>'}</div></div>
+      <div><h3>Specifications</h3><p>${esc(p.width||'')} · ${esc(p.substrate||'Non-Woven')} · ${esc(p.grade||'Residential')} grade · ${esc(p.coverage||'')} · ${esc(p.origin||'Made in Italy')}.</p></div>
+      <div><h3>Care &amp; install</h3><p>Paste-the-wall non-woven · spongeable · lead-time ${esc(p.lead_time||'3–4 weeks')}. Order a sample before specifying.</p></div>
+    </div>`;
+
+  // gallery swap
+  document.querySelectorAll('.thumbs .t').forEach(t=>t.onclick=()=>{
+    document.getElementById('main').style.backgroundImage=`url('${t.dataset.s}')`;
+    document.querySelectorAll('.thumbs .t').forEach(x=>x.classList.remove('on'));t.classList.add('on');
+  });
+  document.getElementById('sample').onclick=()=>alert('Sample ordering is disabled in this preview build.');
+})();
+</script>
+</body>
+</html>
diff --git a/artmura-site/public/styles.css b/artmura-site/public/styles.css
new file mode 100644
index 0000000..87c06af
--- /dev/null
+++ b/artmura-site/public/styles.css
@@ -0,0 +1,144 @@
+/* Artmura — editorial Italian luxury. Warm neutrals, serif display, clean specs. */
+:root{
+  --bg:#f6f2ec;            /* warm cream */
+  --paper:#fffdf9;
+  --ink:#211d18;          /* near-black warm */
+  --ink-soft:#6b6258;
+  --line:#e3dccf;
+  --taupe:#b7a890;
+  --accent:#8c7a5f;       /* champagne-taupe */
+  --gold:#a98c54;
+  --cols:4;
+  --maxw:1320px;
+  --serif:"Cormorant Garamond",Georgia,serif;
+  --sans:"Inter","Helvetica Neue",Arial,sans-serif;
+}
+*{box-sizing:border-box}
+html{scroll-behavior:smooth}
+body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);
+  -webkit-font-smoothing:antialiased;font-size:15px;line-height:1.55}
+a{color:inherit;text-decoration:none}
+img{display:block;max-width:100%}
+
+/* ---------- top corners (standing rules: logo UL, nav UR) ---------- */
+.corner{position:fixed;top:0;z-index:40;padding:22px 26px;display:flex;align-items:center;gap:18px}
+.corner.ul{left:0}
+.corner.ur{right:0;gap:22px}
+.wordmark{font-family:var(--serif);font-weight:600;letter-spacing:.34em;font-size:20px;
+  text-transform:uppercase;color:var(--ink)}
+.wordmark .dot{color:var(--gold)}
+.corner.ur a{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-soft);
+  transition:color .2s}
+.corner.ur a:hover{color:var(--ink)}
+.on-hero .wordmark,.on-hero.ur a{color:#fff}
+.on-hero.ur a{color:rgba(255,255,255,.85)}
+
+/* ---------- hero ---------- */
+.hero{position:relative;height:100vh;min-height:620px;overflow:hidden;background:#1a1612}
+.hero .slide{position:absolute;inset:0;background-size:cover;background-position:center;
+  opacity:0;transition:opacity 1.4s ease;transform:scale(1.04)}
+.hero .slide.on{opacity:1}
+.hero::after{content:"";position:absolute;inset:0;
+  background:linear-gradient(180deg,rgba(20,16,12,.42) 0%,rgba(20,16,12,.12) 38%,rgba(20,16,12,.62) 100%)}
+.hero-inner{position:absolute;inset:0;z-index:5;display:flex;flex-direction:column;
+  align-items:center;justify-content:center;text-align:center;color:#fff;padding:0 24px}
+.hero-kicker{font-size:11px;letter-spacing:.42em;text-transform:uppercase;opacity:.86;margin-bottom:18px}
+.hero h1{font-family:var(--serif);font-weight:500;font-size:clamp(52px,9vw,118px);line-height:.96;
+  margin:0;letter-spacing:.02em;text-shadow:0 2px 30px rgba(0,0,0,.45)}
+.hero .sub{font-family:var(--serif);font-style:italic;font-size:clamp(18px,2.4vw,27px);
+  margin-top:14px;opacity:.94;text-shadow:0 1px 16px rgba(0,0,0,.5)}
+.enter{position:absolute;bottom:42px;left:50%;transform:translateX(-50%);z-index:6;
+  border:1px solid rgba(255,255,255,.55);color:#fff;border-radius:40px;
+  padding:13px 34px;font-size:11px;letter-spacing:.26em;text-transform:uppercase;
+  background:rgba(20,16,12,.18);backdrop-filter:blur(3px);transition:.25s}
+.enter:hover{background:#fff;color:var(--ink);border-color:#fff}
+
+/* ---------- collections strip ---------- */
+.books{max-width:var(--maxw);margin:0 auto;padding:64px 26px 8px;text-align:center}
+.books .eyebrow{font-size:11px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-soft)}
+.books h2{font-family:var(--serif);font-weight:500;font-size:38px;margin:8px 0 26px}
+.chips{display:flex;flex-wrap:wrap;gap:10px;justify-content:center}
+.chip{border:1px solid var(--line);background:var(--paper);border-radius:30px;
+  padding:9px 18px;font-size:12px;letter-spacing:.04em;cursor:pointer;transition:.2s;color:var(--ink-soft)}
+.chip:hover{border-color:var(--taupe);color:var(--ink)}
+.chip.active{background:var(--ink);color:#fff;border-color:var(--ink)}
+.chip .n{opacity:.55;margin-left:6px;font-variant-numeric:tabular-nums}
+
+/* ---------- controls (sort + density — standing rule) ---------- */
+.controls{position:sticky;top:0;z-index:45;background:rgba(246,242,236,.94);
+  backdrop-filter:blur(8px);border-bottom:1px solid var(--line)}
+.controls-in{max-width:var(--maxw);margin:0 auto;padding:14px 26px;display:flex;
+  align-items:center;gap:20px;flex-wrap:wrap}
+.bar-wm{display:none;font-family:var(--serif);font-weight:600;letter-spacing:.28em;
+  text-transform:uppercase;font-size:16px;color:var(--ink)}
+.bar-wm .dot{color:var(--gold)}
+body.scrolled .bar-wm{display:block}
+body.scrolled .corner{opacity:0;pointer-events:none}
+.corner{transition:opacity .3s}
+.count{font-size:12px;letter-spacing:.04em;color:var(--ink-soft);margin-right:auto}
+.count b{color:var(--ink);font-weight:600}
+.ctl{display:flex;align-items:center;gap:9px}
+.ctl label{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft)}
+.ctl select{font-family:var(--sans);font-size:13px;border:1px solid var(--line);background:var(--paper);
+  color:var(--ink);border-radius:8px;padding:7px 30px 7px 11px;cursor:pointer;
+  appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%238c7a5f'/%3E%3C/svg%3E");
+  background-repeat:no-repeat;background-position:right 11px center}
+.ctl input[type=range]{width:120px;accent-color:var(--accent)}
+
+/* ---------- grid ---------- */
+.grid-wrap{max-width:var(--maxw);margin:0 auto;padding:26px 26px 80px}
+.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:22px}
+.card{background:var(--paper);border:1px solid var(--line);border-radius:4px;overflow:hidden;
+  cursor:pointer;transition:transform .25s ease,box-shadow .25s ease;display:flex;flex-direction:column}
+.card:hover{transform:translateY(-3px);box-shadow:0 16px 40px -22px rgba(33,29,24,.45)}
+.card .ph{aspect-ratio:1/1;background:#efe9df center/cover}
+.card .body{padding:13px 14px 15px}
+.card .ser{font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold)}
+.card .nm{font-family:var(--serif);font-size:20px;line-height:1.1;margin:3px 0 2px}
+.card .meta{display:flex;justify-content:space-between;align-items:baseline;margin-top:7px}
+.card .col{font-size:12px;color:var(--ink-soft)}
+.card .pr{font-size:13px;font-variant-numeric:tabular-nums;color:var(--ink)}
+.card .pr small{color:var(--ink-soft);font-size:10px;letter-spacing:.06em}
+
+/* ---------- footer ---------- */
+footer{border-top:1px solid var(--line);background:var(--paper)}
+.foot-in{max-width:var(--maxw);margin:0 auto;padding:46px 26px;display:flex;
+  justify-content:space-between;gap:24px;flex-wrap:wrap;font-size:12px;color:var(--ink-soft)}
+.foot-in .wm{font-family:var(--serif);letter-spacing:.3em;text-transform:uppercase;color:var(--ink);font-size:16px}
+.gate{font-size:11px;color:var(--ink-soft);max-width:420px;line-height:1.5}
+
+/* ---------- PDP (Editorial Magazine / Figma V1) ---------- */
+.pdp{max-width:1280px;margin:0 auto;padding:104px 26px 90px}
+.crumbs{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:22px}
+.crumbs a:hover{color:var(--ink)}
+.pdp-top{display:grid;grid-template-columns:1.55fr 1fr;gap:48px;align-items:start}
+.gallery .main{aspect-ratio:4/3;background:#efe9df center/cover;border:1px solid var(--line);border-radius:4px}
+.thumbs{display:flex;gap:10px;margin-top:12px}
+.thumbs .t{width:76px;height:76px;background:#efe9df center/cover;border:1px solid var(--line);
+  border-radius:3px;cursor:pointer;opacity:.7;transition:.2s}
+.thumbs .t.on,.thumbs .t:hover{opacity:1;border-color:var(--taupe)}
+.rail .ser{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--gold)}
+.rail h1{font-family:var(--serif);font-weight:500;font-size:46px;line-height:1.02;margin:8px 0 4px}
+.rail .colr{font-size:14px;color:var(--ink-soft);margin-bottom:22px}
+.price{font-family:var(--serif);font-size:34px}
+.price small{font-family:var(--sans);font-size:12px;letter-spacing:.06em;color:var(--ink-soft)}
+.cta{display:block;width:100%;text-align:center;margin-top:20px;padding:15px;border-radius:40px;
+  background:var(--ink);color:#fff;font-size:11px;letter-spacing:.22em;text-transform:uppercase;
+  cursor:pointer;border:0;transition:.2s}
+.cta:hover{background:var(--accent)}
+.cta.ghost{background:transparent;color:var(--ink);border:1px solid var(--line);margin-top:10px}
+.cta.ghost:hover{border-color:var(--ink)}
+.spec{margin-top:30px;border-top:1px solid var(--line)}
+.spec .row{display:flex;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--line);font-size:13px}
+.spec .row .k{color:var(--ink-soft);letter-spacing:.04em}
+.spec .row .v{color:var(--ink);text-align:right}
+.story{display:grid;grid-template-columns:repeat(3,1fr);gap:36px;margin-top:64px;
+  padding-top:40px;border-top:1px solid var(--line)}
+.story h3{font-family:var(--serif);font-weight:500;font-size:22px;margin:0 0 12px}
+.story p{font-size:13.5px;color:var(--ink-soft);margin:0}
+.preview-flag{position:fixed;bottom:0;left:0;right:0;z-index:50;background:#211d18;color:#f6f2ec;
+  font-size:11px;letter-spacing:.05em;text-align:center;padding:7px 14px}
+.preview-flag b{color:#caa45f}
+
+@media(max-width:1080px){:root{--cols:3}.pdp-top{grid-template-columns:1fr;gap:30px}.story{grid-template-columns:1fr;gap:26px}}
+@media(max-width:720px){:root{--cols:2}.hero h1{font-size:64px}.grid{gap:14px}}
diff --git a/artmura-site/server.js b/artmura-site/server.js
new file mode 100644
index 0000000..1320d82
--- /dev/null
+++ b/artmura-site/server.js
@@ -0,0 +1,85 @@
+/* Artmura microsite — serves the staged catalog as an editorial storefront.
+   PREVIEW ONLY: pricing is newwall retail reference; not deployed/customer-facing
+   until Artmura wholesale cost + trade discount land (see artmura-onboard-gated memory). */
+const express = require('express');
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.resolve(__dirname, '..');
+const PKG = path.join(ROOT, 'scripts/artmura-onboard/data/artmura.json');
+const IMG_DIR = path.join(ROOT, '.newwall-ref/artmura/images');
+const PORT = process.env.PORT || 9921;
+
+const app = express();
+
+// --- load + normalize catalog ---
+function localImages(r) {
+  return (r.images || []).map((src, i) => {
+    const base = src.split('/').pop().split('?')[0];
+    const ext = path.extname(base) || '.jpg';
+    return `/images/${r.mfr_sku}-${i + 1}${ext}`;
+  });
+}
+let CATALOG = [];
+function load() {
+  const d = JSON.parse(fs.readFileSync(PKG, 'utf8'));
+  CATALOG = d.products.map(r => {
+    const imgs = localImages(r);
+    return {
+      handle: r.handle,
+      sku: r.mfr_sku,
+      series: r.pattern_series,
+      color: r.color,
+      title: r.title,
+      book: r.collection_book,
+      tags: r.tags,
+      price: r.price_newwall_retail,        // newwall retail reference
+      sample_price: r.sample_price,
+      width: r.dimensions,
+      substrate: r.substrate,
+      grade: r.grade,
+      coverage: r.wall_coverage,
+      lead_time: r.lead_time,
+      origin: r.origin,
+      sold_by: r.sold_by,
+      body_html: r.body_html,
+      product_url: r.product_url,
+      swatch: imgs[0] || null,             // flat pattern swatch
+      room: imgs[1] || imgs[0] || null,    // styled room shot when present
+      images: imgs,
+      published_at: r.published_at,
+    };
+  });
+  console.log(`loaded ${CATALOG.length} Artmura products`);
+}
+load();
+
+// --- API ---
+app.get('/api/products', (_req, res) => res.json({ count: CATALOG.length, products: CATALOG }));
+app.get('/api/product/:handle', (req, res) => {
+  const p = CATALOG.find(x => x.handle === req.params.handle);
+  if (!p) return res.status(404).json({ error: 'not found' });
+  res.json(p);
+});
+app.get('/api/facets', (_req, res) => {
+  const books = {}, series = {};
+  for (const p of CATALOG) {
+    if (p.book) books[p.book] = (books[p.book] || 0) + 1;
+    if (p.series) series[p.series] = (series[p.series] || 0) + 1;
+  }
+  res.json({
+    total: CATALOG.length,
+    books: Object.entries(books).sort((a, b) => b[1] - a[1]),
+    series: Object.entries(series).sort((a, b) => b[1] - a[1]),
+  });
+});
+
+// --- images (served from the gitignored ref dir) ---
+app.use('/images', express.static(IMG_DIR, { maxAge: '7d' }));
+app.use(express.static(path.join(__dirname, 'public')));
+
+// clean PDP route
+app.get('/product/:handle', (_req, res) =>
+  res.sendFile(path.join(__dirname, 'public', 'product.html')));
+
+app.listen(PORT, () => console.log(`Artmura site → http://127.0.0.1:${PORT}`));

← f4eba66 Artmura onboard: full-monte feed-first capture of 161 newwal  ·  back to Dw Yolo Loop  ·  Artmura site: pricing finalized at website price, no trade d dfb4bac →