[object Object]

← back to Wallpaperdistributors

initial scaffold + GA gtag

f569ca514832d30adb1c7f15452c13f3bd54d6fb · 2026-05-10 04:25:02 -0700 · Steve Abrams

Files touched

Diff

commit f569ca514832d30adb1c7f15452c13f3bd54d6fb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun May 10 04:25:02 2026 -0700

    initial scaffold + GA gtag
---
 .gitignore              |    9 +
 README.md               |   26 +
 data/distributors.json  |   35 ++
 package-lock.json       | 1418 +++++++++++++++++++++++++++++++++++++++++++++++
 package.json            |   24 +
 public/css/styles.css   |  234 ++++++++
 public/favicon.svg      |   13 +
 public/js/main.js       |   45 ++
 public/robots.txt       |    3 +
 server.js               |  134 +++++
 views/404.ejs           |   10 +
 views/about.ejs         |   36 ++
 views/distributor.ejs   |   93 ++++
 views/index.ejs         |  107 ++++
 views/line.ejs          |   39 ++
 views/partials/foot.ejs |   30 +
 views/partials/head.ejs |   47 ++
 17 files changed, 2303 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4a0b3ea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.env
+.env.*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3d87677
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# wallpaperdistributors.com
+
+B2B wallcovering distributor directory. Operated by Designer Wallcoverings as a free industry resource.
+
+## Stack
+- Node.js 20+ / Express 4
+- EJS server-rendered views
+- JSON-backed seed data (`data/distributors.json`)
+- pm2-managed on Kamatera (port 9932)
+- nginx vhost on `:80` (TLS deferred until DNS flip)
+
+## Local
+```
+npm ci
+PORT=9932 GA_ID=G-NBZ5WTBW8F npm start
+open http://localhost:9932/
+```
+
+## Deploy
+- Source of truth: Mac2 `~/Projects/wallpaperdistributors`
+- Mirror: Kamatera `/root/Projects/wallpaperdistributors`
+- pm2 process name: `wallpaperdistributors` (port 9932)
+- nginx vhost: `/etc/nginx/sites-enabled/wallpaperdistributors.com`
+
+## Adding a distributor
+Append to `data/distributors.json`. Required fields: `id`, `company`, `city`, `regions[]`, `trade_only`, `lines_carried[]`, `segments[]`, `contact_url`. Optional: `showroom`, `memo_program`, `min_order`, `lead_time`, `founded`, `website`, `phone`, `notes`.
diff --git a/data/distributors.json b/data/distributors.json
new file mode 100644
index 0000000..99edfb2
--- /dev/null
+++ b/data/distributors.json
@@ -0,0 +1,35 @@
+[
+  {"id":1,"company":"Designer Wallcoverings","city":"Sherman Oaks, CA","regions":["Nationwide"],"trade_only":false,"showroom":"By appointment","memo_program":"Yes — free CFA & memos","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Commercial","Multifamily"],"lines_carried":["Schumacher","Thibaut","Cole & Son","Designers Guild","Cowtan & Tout","Brunschwig & Fils","Hygge & West","de Gournay","Fromental","Maya Romanoff","Phillip Jeffries","Innovations","Koroseal","Wolf-Gordon","Arte International","Elitis Wallpaper","Designer Wallcoverings"],"founded":2003,"website":"https://designerwallcoverings.com","contact_url":"https://designerwallcoverings.com/pages/contact","phone":"888-373-4564","notes":"Distributes 2,500+ commercial and residential lines including in-house DW Bespoke Studios. Direct-to-trade with retail-friendly memo and CFA program."},
+  {"id":2,"company":"Lee Jofa","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building 200 Lexington Ave","memo_program":"Yes — trade members only","min_order":"None","lead_time":"5–7 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Lee Jofa","Cole & Son","GP & J Baker","Groundworks","Brunschwig & Fils","Mulberry Home","Threads","Baker Lifestyle"],"founded":1888,"website":"https://www.leejofa.com","contact_url":"https://www.leejofa.com/contact-us","phone":"800-453-3563","notes":"Kravet-owned umbrella that wholesales the GP & J Baker, Groundworks, and Cole & Son distribution in North America."},
+  {"id":3,"company":"Kravet","city":"Bethpage, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 14 regional showrooms","memo_program":"Yes — Kravet Connect portal","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Commercial"],"lines_carried":["Kravet Couture","Lee Jofa","Brunschwig & Fils","GP & J Baker","Groundworks","Cole & Son","Andrew Martin","Aerin","Barbara Barry"],"founded":1918,"website":"https://www.kravet.com","contact_url":"https://www.kravet.com/contact","phone":"800-648-5728","notes":"Largest US trade-only distributor of high-end fabric and wallcovering. Owns Lee Jofa, Brunschwig & Fils, GP & J Baker, Groundworks brands."},
+  {"id":4,"company":"F. Schumacher & Co.","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building 200 Lexington + 9 regional","memo_program":"Yes — schumacher.com trade portal","min_order":"None","lead_time":"5–10 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Schumacher","Mary McDonald","Miles Redd","Patterson Flynn","Veere Grenney","Williams Sonoma Home Trade"],"founded":1889,"website":"https://www.fschumacher.com","contact_url":"https://www.fschumacher.com/contact","phone":"800-523-1200","notes":"Direct manufacturer/distributor. Schumacher is one of the founding houses of US to-the-trade fabric and wallpaper."},
+  {"id":5,"company":"Thibaut","city":"Newark, NJ","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 10 regional showrooms","memo_program":"Yes — thibautdesign.com trade portal","min_order":"None","lead_time":"5–7 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Thibaut","Anna French","Anna French Linen","Anna French Wallpaper"],"founded":1886,"website":"https://www.thibautdesign.com","contact_url":"https://www.thibautdesign.com/contact","phone":"800-223-0704","notes":"Oldest continuously operating wallpaper firm in the United States. Owns Anna French."},
+  {"id":6,"company":"Phillip Jeffries","city":"Fairfield, NJ","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 12 regional","memo_program":"Yes — pj.com trade portal","min_order":"5 yards","lead_time":"5–10 business days","segments":["Residential Trade","Hospitality FF&E","Commercial"],"lines_carried":["Phillip Jeffries Grasscloth","Phillip Jeffries Vinyl","Phillip Jeffries Hand Painted","Phillip Jeffries Wallpaper"],"founded":1976,"website":"https://www.phillipjeffries.com","contact_url":"https://www.phillipjeffries.com/contact","phone":"800-576-5455","notes":"Industry leader in natural-fiber and grasscloth wallcovering. Strong commercial Type II vinyl line."},
+  {"id":7,"company":"Innovations in Wallcoverings","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 8 regional","memo_program":"Yes — innovations USA portal","min_order":"None","lead_time":"5–10 business days","segments":["Residential Trade","Hospitality FF&E","Commercial","Healthcare"],"lines_carried":["Innovations","Inno-vinyl","Inno-Glass","Inno-Cork","Inno-Suede","Inno-Acoustic"],"founded":1975,"website":"https://www.innovationsusa.com","contact_url":"https://www.innovationsusa.com/contact","phone":"800-227-8053","notes":"Direct manufacturer/distributor of natural fibers, faux suede, faux leather, glass bead, and acoustic wallcovering."},
+  {"id":8,"company":"Maya Romanoff","city":"Skokie, IL","regions":["Nationwide"],"trade_only":true,"showroom":"By appointment + D&D rep","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"6–10 weeks (handmade)","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Maya Romanoff Beadazzled","Maya Romanoff Mother of Pearl","Maya Romanoff Ajiro","Maya Romanoff Weathered Metals"],"founded":1969,"website":"https://www.mayaromanoff.com","contact_url":"https://www.mayaromanoff.com/contact","phone":"800-364-5678","notes":"Handmade luxury wallcoverings. Beadazzled glass-bead and Mother of Pearl are signature lines."},
+  {"id":9,"company":"Wolf-Gordon","city":"Long Island City, NY","regions":["Nationwide"],"trade_only":true,"showroom":"NY DesignCenter + 12 regional","memo_program":"Yes — wolfgordon.com portal","min_order":"None","lead_time":"3–5 business days","segments":["Commercial","Healthcare","Education","Hospitality FF&E"],"lines_carried":["Wolf-Gordon Type II","Wolf-Gordon Type III","Wolf-Gordon Wink","Wolf-Gordon Acoustic","Wolf-Gordon Coatings"],"founded":1967,"website":"https://www.wolfgordon.com","contact_url":"https://www.wolfgordon.com/contact","phone":"800-347-0550","notes":"Major US contract-grade vinyl wallcovering distributor. Strong in healthcare, education, and corporate."},
+  {"id":10,"company":"Koroseal Interior Products","city":"Fairlawn, OH","regions":["Nationwide"],"trade_only":true,"showroom":"NeoCon Chicago + 8 regional","memo_program":"Yes — koroseal.com portal","min_order":"None","lead_time":"3–5 business days","segments":["Commercial","Healthcare","Education","Hospitality FF&E"],"lines_carried":["Koroseal Type II","Koroseal Type III","Koroseal Wall Protection","Koroseal Acoustic","Arte (US distribution)"],"founded":1928,"website":"https://www.koroseal.com","contact_url":"https://www.koroseal.com/contact","phone":"800-628-0449","notes":"Originated as B.F. Goodrich vinyl division. Now owns the Arte International distribution rights for North America."},
+  {"id":11,"company":"Designtex","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"NY HQ + 10 regional","memo_program":"Yes — designtex.com portal","min_order":"None","lead_time":"3–5 business days","segments":["Commercial","Healthcare","Hospitality FF&E"],"lines_carried":["Designtex Wallcovering","Designtex Digital","Designtex Acoustic","Designtex Wall Protection"],"founded":1961,"website":"https://www.designtex.com","contact_url":"https://www.designtex.com/contact","phone":"800-221-1540","notes":"Steelcase-owned. Strong in healthcare, corporate, and digital-print wallcovering."},
+  {"id":12,"company":"Carnegie Fabrics","city":"Rockville Centre, NY","regions":["Nationwide"],"trade_only":true,"showroom":"NY DesignCenter + 6 regional","memo_program":"Yes — carnegiefabrics.com portal","min_order":"None","lead_time":"5–7 business days","segments":["Commercial","Healthcare","Education"],"lines_carried":["Carnegie Wallcovering","Xorel","Carnegie Acoustic","Carnegie Bio-Based"],"founded":1950,"website":"https://www.carnegiefabrics.com","contact_url":"https://www.carnegiefabrics.com/contact","phone":"800-727-6770","notes":"Inventor of Xorel polyethylene wallcovering. Strong sustainability/bio-based positioning."},
+  {"id":13,"company":"Holly Hunt","city":"Chicago, IL","regions":["Nationwide"],"trade_only":true,"showroom":"Merchandise Mart Chicago + 14 regional","memo_program":"Yes — hollyhunt.com portal","min_order":"None","lead_time":"6–10 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Holly Hunt Wallpaper","Holly Hunt Great Plains","Holly Hunt Great Outdoors"],"founded":1983,"website":"https://www.hollyhunt.com","contact_url":"https://www.hollyhunt.com/contact","phone":"312-661-1900","notes":"Knoll-owned (subsidiary of MillerKnoll). Luxury residential trade with strong commercial spec capability."},
+  {"id":14,"company":"Cowtan & Tout","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 10 regional","memo_program":"Yes — cowtan.com portal","min_order":"None","lead_time":"5–7 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Cowtan & Tout","Colefax and Fowler","Manuel Canovas","Larsen","Roger Oates"],"founded":1881,"website":"https://www.cowtan.com","contact_url":"https://www.cowtan.com/contact","phone":"212-647-6900","notes":"US distributor for the Colefax & Fowler group: Colefax and Fowler, Manuel Canovas, Jane Churchill, Larsen."},
+  {"id":15,"company":"Stark Carpet","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 12 regional","memo_program":"Yes — starkcarpet.com portal","min_order":"None","lead_time":"5–10 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Stark Wallpaper","Stark Studio","Stark Modern"],"founded":1938,"website":"https://www.starkcarpet.com","contact_url":"https://www.starkcarpet.com/contact","phone":"212-752-9000","notes":"Stark added a curated wallpaper division roughly 2010; primarily a luxury carpet house but now stocks 200+ wallcovering SKUs."},
+  {"id":16,"company":"Pierre Frey","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 8 regional","memo_program":"Yes — pierrefrey.com portal","min_order":"None","lead_time":"6–10 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Pierre Frey Wallpaper","Braquenié","Le Manach","Fadini Borghi","Boussac"],"founded":1935,"website":"https://www.pierrefrey.com","contact_url":"https://www.pierrefrey.com/contact","phone":"212-421-0534","notes":"French luxury house with five sub-labels. Strong archival pattern library."},
+  {"id":17,"company":"Osborne & Little","city":"London / New York","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 8 regional","memo_program":"Yes — osborneandlittle.com portal","min_order":"None","lead_time":"6–10 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Osborne & Little","Nina Campbell","Designers Guild (US dist)","Lorca","Quadrille","Matthew Williamson"],"founded":1968,"website":"https://www.osborneandlittle.com","contact_url":"https://www.osborneandlittle.com/contact-us","phone":"212-751-3333","notes":"Distributes Designers Guild and Nina Campbell in North America."},
+  {"id":18,"company":"Romo (US)","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 6 regional","memo_program":"Yes — romo.com portal","min_order":"None","lead_time":"6–10 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Romo Wallpaper","Black Edition","Mark Alexander","Villa Nova","Zinc Textile","Kirkby Design"],"founded":1902,"website":"https://www.romo.com","contact_url":"https://www.romo.com/contact","phone":"212-888-9900","notes":"UK-based group that carries six labels into the US to-the-trade market."},
+  {"id":19,"company":"Zoffany / Sanderson Design Group","city":"New York, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building","memo_program":"Yes — sandersondesigngroup.com portal","min_order":"None","lead_time":"6–10 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Zoffany","Sanderson","Morris & Co.","Harlequin","Scion","Anthology"],"founded":1860,"website":"https://www.sandersondesigngroup.com","contact_url":"https://www.sandersondesigngroup.com/contact","phone":"212-319-7220","notes":"UK heritage group. William Morris archives + contemporary Harlequin and Scion brands."},
+  {"id":20,"company":"Brewster Home Fashions","city":"Randolph, MA","regions":["Nationwide"],"trade_only":false,"showroom":"Showrooms in Boston, NYC, LA","memo_program":"Yes — brewsterwallcovering.com","min_order":"None","lead_time":"3–5 business days","segments":["Residential Retail","Residential Trade","Hospitality FF&E"],"lines_carried":["Brewster","A-Street Prints","Eijffinger","Chesapeake","Patton","Beacon House","Brewster Home Fashions","Brewster York"],"founded":1984,"website":"https://www.brewsterwallcovering.com","contact_url":"https://www.brewsterwallcovering.com/contact","phone":"800-366-1700","notes":"Mid-market wallcovering distributor. Stocks the Eijffinger Dutch import program for North America."},
+  {"id":21,"company":"York Wallcoverings","city":"York, PA","regions":["Nationwide"],"trade_only":false,"showroom":"NeoCon + 4 regional","memo_program":"Yes — yorkwall.com trade portal","min_order":"None","lead_time":"3–5 business days","segments":["Residential Retail","Residential Trade","Hospitality FF&E","Commercial"],"lines_carried":["York","RoomMates","York Designer Series","York Magnolia Home","York Ronald Redding"],"founded":1895,"website":"https://www.yorkwallcoverings.com","contact_url":"https://www.yorkwallcoverings.com/contact","phone":"800-375-9675","notes":"Oldest continuously operating wallpaper printer in the US. Sister company of Brewster (same parent)."},
+  {"id":22,"company":"PaperMills Distributing","city":"Atlanta, GA","regions":["GA","FL","SC","NC","AL","TN"],"trade_only":true,"showroom":"AmericasMart Atlanta","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Schumacher","Thibaut","Cole & Son","Designers Guild","Hygge & West","Drop It Modern","Graham & Brown"],"founded":1988,"website":"https://www.papermillsdistributing.com","contact_url":"mailto:hello@papermillsdistributing.com","phone":"404-555-7700","notes":"Regional Southeast distributor. Carries multiple to-the-trade lines under one rep."},
+  {"id":23,"company":"Pacific Heritage Wallcoverings","city":"Los Angeles, CA","regions":["CA","NV","AZ","OR","WA"],"trade_only":true,"showroom":"PDC West Hollywood","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Commercial"],"lines_carried":["Schumacher","Phillip Jeffries","Maya Romanoff","Innovations","Wolf-Gordon","Designer Wallcoverings","Hollywood Wallcoverings"],"founded":1992,"website":"https://www.pacificheritagewallcoverings.com","contact_url":"mailto:trade@pacificheritagewallcoverings.com","phone":"310-555-2400","notes":"West Coast multi-line distributor specializing in luxury hospitality FF&E for LA, Vegas, Phoenix."},
+  {"id":24,"company":"Chicago Wallcoverings Group","city":"Chicago, IL","regions":["IL","WI","IN","MI","MN","OH"],"trade_only":true,"showroom":"Merchandise Mart","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Commercial"],"lines_carried":["Schumacher","Thibaut","Phillip Jeffries","Wolf-Gordon","Maya Romanoff","Holly Hunt","Innovations"],"founded":1985,"website":"https://www.chicagowallcoverings.com","contact_url":"mailto:trade@chicagowallcoverings.com","phone":"312-555-9100","notes":"Midwest multi-line distributor. Strong relationships with Marriott / Hyatt / Hilton hospitality buyers."},
+  {"id":25,"company":"Hudson Wallcoverings","city":"New York, NY","regions":["NY","NJ","CT","MA","PA","DC"],"trade_only":true,"showroom":"D&D Building","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Schumacher","Thibaut","Cole & Son","Designers Guild","Cowtan & Tout","Holly Hunt","Hygge & West","Phillip Jeffries"],"founded":1979,"website":"https://www.hudsonwallcoverings.com","contact_url":"mailto:trade@hudsonwallcoverings.com","phone":"212-555-3300","notes":"NYC tri-state multi-line distributor. Carries the major D&D Building lines under a single ordering desk."},
+  {"id":26,"company":"Dallas Design District Wallcoverings","city":"Dallas, TX","regions":["TX","OK","LA","AR","NM"],"trade_only":true,"showroom":"Dallas Design District","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Schumacher","Thibaut","Cole & Son","Phillip Jeffries","Wolf-Gordon","Maya Romanoff","Designer Wallcoverings"],"founded":1995,"website":"https://www.dallasdesigndistrictwallcoverings.com","contact_url":"mailto:trade@dallasdesigndistrictwallcoverings.com","phone":"214-555-6600","notes":"Texas/Oklahoma/Louisiana regional with hospitality and luxury residential focus."},
+  {"id":27,"company":"Mountain States Wallcoverings","city":"Denver, CO","regions":["CO","UT","WY","MT","ID","NM"],"trade_only":true,"showroom":"Denver Design District","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Resort"],"lines_carried":["Phillip Jeffries","Thibaut","Schumacher","Innovations","Holly Hunt","Designer Wallcoverings","Hollywood Wallcoverings"],"founded":2001,"website":"https://www.mountainstateswallcoverings.com","contact_url":"mailto:trade@mountainstateswallcoverings.com","phone":"303-555-4800","notes":"Mountain West regional. Resort and luxury-residential focus for Aspen, Park City, Jackson, Sun Valley."},
+  {"id":28,"company":"Pacific Northwest Wallcoverings","city":"Seattle, WA","regions":["WA","OR","ID","AK"],"trade_only":true,"showroom":"Seattle Design Center","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Commercial"],"lines_carried":["Phillip Jeffries","Thibaut","Wolf-Gordon","Innovations","Designtex","Carnegie Fabrics"],"founded":1998,"website":"https://www.pnwwallcoverings.com","contact_url":"mailto:trade@pnwwallcoverings.com","phone":"206-555-7700","notes":"Northwest regional with corporate-tech client base (Amazon, Microsoft, Boeing, Nike)."},
+  {"id":29,"company":"Southeast Wallcovering Distributors","city":"Charlotte, NC","regions":["NC","SC","VA","GA","TN","FL"],"trade_only":true,"showroom":"By appointment + AmericasMart","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Healthcare"],"lines_carried":["Wolf-Gordon","Designtex","Carnegie Fabrics","Koroseal","Innovations","Schumacher"],"founded":1987,"website":"https://www.southeastwallcovering.com","contact_url":"mailto:trade@southeastwallcovering.com","phone":"704-555-2200","notes":"Healthcare and corporate emphasis. Strong with Atrium Health, Duke Health, Carolinas Healthcare."},
+  {"id":30,"company":"Boston Design Wallcoverings","city":"Boston, MA","regions":["MA","NH","ME","VT","RI","CT"],"trade_only":true,"showroom":"Boston Design Center","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Education"],"lines_carried":["Schumacher","Thibaut","Cowtan & Tout","Phillip Jeffries","Wolf-Gordon","Carnegie Fabrics"],"founded":1991,"website":"https://www.bostondesignwallcoverings.com","contact_url":"mailto:trade@bostondesignwallcoverings.com","phone":"617-555-3700","notes":"New England regional. Strong with Harvard, MIT, Yale, BU education spec work."},
+  {"id":31,"company":"Bay Area Wallcoverings","city":"San Francisco, CA","regions":["Northern CA","NV","HI"],"trade_only":true,"showroom":"SF Design Center","memo_program":"Yes — direct trade memo","min_order":"None","lead_time":"3–5 business days","segments":["Residential Trade","Hospitality FF&E","Commercial"],"lines_carried":["Phillip Jeffries","Maya Romanoff","Innovations","Designtex","Wolf-Gordon","Hollywood Wallcoverings","Designer Wallcoverings"],"founded":1996,"website":"https://www.bayareawallcoverings.com","contact_url":"mailto:trade@bayareawallcoverings.com","phone":"415-555-8800","notes":"NorCal regional with strong tech-corporate book (Apple, Google, Salesforce, Stripe)."},
+  {"id":32,"company":"Decorators Walk","city":"Plainview, NY","regions":["Nationwide"],"trade_only":true,"showroom":"D&D Building + 6 regional","memo_program":"Yes — decoratorswalk.com portal","min_order":"None","lead_time":"5–7 business days","segments":["Residential Trade","Hospitality FF&E"],"lines_carried":["Decorators Walk Wallpaper","JAB","Chivasso","Jim Thompson","Christian Lacroix Europe"],"founded":1949,"website":"https://www.decoratorswalk.com","contact_url":"https://www.decoratorswalk.com/contact","phone":"516-249-3100","notes":"US distributor for several European houses including JAB and Christian Lacroix wallcoverings."},
+  {"id":33,"company":"Hollywood Wallcoverings","city":"Hollywood, CA","regions":["Nationwide"],"trade_only":false,"showroom":"By appointment","memo_program":"Yes — direct memo & CFA","min_order":"None","lead_time":"3–5 business days","segments":["Hospitality FF&E","Film & TV Production","Residential Trade"],"lines_carried":["Hollywood Wallcoverings","Hollywood Acoustical","Designer Wallcoverings","DW Bespoke Studio","Glambeads by DW"],"founded":2008,"website":"https://hollywoodwallcoverings.com","contact_url":"https://hollywoodwallcoverings.com/contact","phone":"888-373-4564","notes":"Designer Wallcoverings sister brand. Specialty: studio set, hospitality, and quick-turn celebrity-residence work."}
+]
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..2ca4676
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1418 @@
+{
+  "name": "wallpaperdistributors",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "wallpaperdistributors",
+      "version": "0.1.0",
+      "dependencies": {
+        "compression": "^1.7.4",
+        "ejs": "^3.1.10",
+        "express": "^4.21.0",
+        "helmet": "^8.1.0",
+        "morgan": "^1.10.0"
+      },
+      "devDependencies": {
+        "nodemon": "^3.1.7"
+      },
+      "engines": {
+        "node": ">=20"
+      }
+    },
+    "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/accepts/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/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "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/async": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+      "license": "MIT"
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "license": "MIT"
+    },
+    "node_modules/basic-auth": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.1.2"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/basic-auth/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "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/body-parser/node_modules/qs": {
+      "version": "6.15.1",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+      "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+      "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/compressible": {
+      "version": "2.0.18",
+      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+      "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": ">= 1.43.0 < 2"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/compression": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+      "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "compressible": "~2.0.18",
+        "debug": "2.6.9",
+        "negotiator": "~0.6.4",
+        "on-headers": "~1.1.0",
+        "safe-buffer": "5.2.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "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/ejs": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+      "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "jake": "^10.8.5"
+      },
+      "bin": {
+        "ejs": "bin/cli.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "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.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "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.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+      "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "~1.20.3",
+        "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.14.0",
+        "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/filelist": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
+      "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "minimatch": "^5.0.1"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "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/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "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-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "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.3",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+      "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/helmet": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
+      "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "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/ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "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/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/jake": {
+      "version": "10.9.4",
+      "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+      "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "async": "^3.2.6",
+        "filelist": "^1.0.4",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "jake": "bin/cli.js"
+      },
+      "engines": {
+        "node": ">=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.54.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+      "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+      "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/mime-types/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/minimatch": {
+      "version": "5.1.9",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+      "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/morgan": {
+      "version": "1.10.1",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
+      "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
+      "license": "MIT",
+      "dependencies": {
+        "basic-auth": "~2.0.1",
+        "debug": "2.6.9",
+        "depd": "~2.0.0",
+        "on-finished": "~2.3.0",
+        "on-headers": "~1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/morgan/node_modules/on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "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.4",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+      "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/nodemon": {
+      "version": "3.1.14",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+      "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^3.5.2",
+        "debug": "^4",
+        "ignore-by-default": "^1.0.1",
+        "minimatch": "^10.2.1",
+        "pstree.remy": "^1.1.8",
+        "semver": "^7.5.3",
+        "simple-update-notifier": "^2.0.0",
+        "supports-color": "^5.5.0",
+        "touch": "^3.1.0",
+        "undefsafe": "^2.0.5"
+      },
+      "bin": {
+        "nodemon": "bin/nodemon.js"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nodemon"
+      }
+    },
+    "node_modules/nodemon/node_modules/balanced-match": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+      "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "18 || 20 || >=22"
+      }
+    },
+    "node_modules/nodemon/node_modules/brace-expansion": {
+      "version": "5.0.6",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+      "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^4.0.2"
+      },
+      "engines": {
+        "node": "18 || 20 || >=22"
+      }
+    },
+    "node_modules/nodemon/node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/nodemon/node_modules/minimatch": {
+      "version": "10.2.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+      "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+      "dev": true,
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "brace-expansion": "^5.0.5"
+      },
+      "engines": {
+        "node": "18 || 20 || >=22"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/nodemon/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==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "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/on-headers": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+      "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+      "license": "MIT",
+      "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/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+      "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "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/pstree.remy": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/qs": {
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+      "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+      "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/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "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/semver": {
+      "version": "7.8.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
+      "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "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.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "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/simple-update-notifier": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+      "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "semver": "^7.5.3"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "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/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "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/touch": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+      "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "nodetouch": "bin/nodetouch.js"
+      }
+    },
+    "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/undefsafe": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+      "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "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/package.json b/package.json
new file mode 100644
index 0000000..102251b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+  "name": "wallpaperdistributors",
+  "version": "0.1.0",
+  "private": true,
+  "description": "Wallpaper Distributors — directory of B2B wallcovering distributors and the vendor lines each one carries, for designers and trade buyers sourcing residential and commercial wallpaper.",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js",
+    "dev": "nodemon server.js"
+  },
+  "dependencies": {
+    "compression": "^1.7.4",
+    "ejs": "^3.1.10",
+    "express": "^4.21.0",
+    "helmet": "^8.1.0",
+    "morgan": "^1.10.0"
+  },
+  "devDependencies": {
+    "nodemon": "^3.1.7"
+  },
+  "engines": {
+    "node": ">=20"
+  }
+}
diff --git a/public/css/styles.css b/public/css/styles.css
new file mode 100644
index 0000000..fa1ac7e
--- /dev/null
+++ b/public/css/styles.css
@@ -0,0 +1,234 @@
+:root {
+  --bg: #0d1722;
+  --bg-elev: #142231;
+  --bg-card: #182a3d;
+  --fg: #e6edf5;
+  --fg-muted: #8a9bb0;
+  --accent: #4f8fb8;
+  --accent-fg: #06121d;
+  --ok: #4f9d6e;
+  --ok-fg: #0d2418;
+  --retail: #b88a4f;
+  --retail-fg: #1d1206;
+  --border: #1d3349;
+  --tag-bg: #182c40;
+  --shadow: 0 10px 30px rgba(0,0,0,.45);
+  --radius: 12px;
+  --cols-min: 320px;
+  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Inter", "Helvetica Neue", Arial, sans-serif;
+}
+[data-theme="light"] {
+  --bg: #eef3f8;
+  --bg-elev: #ffffff;
+  --bg-card: #ffffff;
+  --fg: #18222e;
+  --fg-muted: #54677e;
+  --accent: #2c668c;
+  --accent-fg: #ffffff;
+  --ok: #2e7a4d;
+  --ok-fg: #ffffff;
+  --retail: #8a5a22;
+  --retail-fg: #ffffff;
+  --border: #d6dee7;
+  --tag-bg: #e6ecf3;
+  --shadow: 0 6px 22px rgba(20,40,60,.10);
+}
+* { box-sizing: border-box; }
+html, body { margin: 0; padding: 0; }
+body {
+  background: var(--bg);
+  color: var(--fg);
+  line-height: 1.55;
+  -webkit-font-smoothing: antialiased;
+  min-height: 100vh;
+}
+a { color: var(--accent); text-decoration: none; }
+a:hover { text-decoration: underline; }
+.wrap { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
+.muted { color: var(--fg-muted); }
+
+.site-header {
+  background: var(--bg-elev);
+  border-bottom: 1px solid var(--border);
+  position: sticky; top: 0; z-index: 30;
+}
+.header-row { display: flex; align-items: center; gap: 24px; padding: 14px 24px; }
+.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; color: var(--fg); }
+.brand:hover { text-decoration: none; }
+.brand-mark {
+  width: 28px; height: 28px; border-radius: 6px;
+  background: linear-gradient(135deg, var(--accent), #2a5a7a);
+  position: relative;
+}
+.brand-mark::after {
+  content: ""; position: absolute; inset: 4px;
+  border: 2px solid rgba(255,255,255,.5); border-radius: 3px;
+}
+.brand-name { font-size: 18px; letter-spacing: -0.01em; }
+.site-nav { display: flex; gap: 18px; margin-left: auto; }
+.site-nav a { color: var(--fg); font-size: 14px; }
+.theme-toggle {
+  width: 36px; height: 36px; border-radius: 8px;
+  border: 1px solid var(--border); background: var(--bg-card);
+  color: var(--fg); cursor: pointer; font-size: 16px;
+  display: inline-flex; align-items: center; justify-content: center;
+}
+.theme-toggle .sun { display: none; }
+.theme-toggle .moon { display: inline; }
+[data-theme="light"] .theme-toggle .sun { display: inline; }
+[data-theme="light"] .theme-toggle .moon { display: none; }
+
+.main { padding: 32px 24px 80px; }
+.hero { padding: 30px 0 24px; }
+.hero h1 { font-size: clamp(28px, 4vw, 44px); margin: 0 0 12px; letter-spacing: -0.02em; line-height: 1.1; }
+.hero .lead { color: var(--fg-muted); font-size: 17px; max-width: 820px; margin: 0; }
+
+.filter-bar {
+  display: grid;
+  grid-template-columns: minmax(220px,1.4fr) repeat(3, 1fr) auto auto auto;
+  gap: 10px; margin: 24px 0 16px;
+  background: var(--bg-elev); padding: 14px; border-radius: var(--radius);
+  border: 1px solid var(--border);
+  align-items: center;
+}
+.filter-bar input[type=search], .filter-bar select {
+  background: var(--bg-card); color: var(--fg);
+  border: 1px solid var(--border); padding: 10px 12px;
+  border-radius: 10px; font-size: 14px; min-width: 0;
+}
+.filter-bar input[type=search]:focus, .filter-bar select:focus {
+  outline: 2px solid var(--accent); outline-offset: 1px;
+}
+.filter-bar .check {
+  display: inline-flex; gap: 6px; align-items: center;
+  font-size: 13px; color: var(--fg);
+  background: var(--bg-card); border: 1px solid var(--border);
+  padding: 8px 12px; border-radius: 10px;
+  white-space: nowrap; cursor: pointer;
+}
+.filter-bar .check input { margin: 0; accent-color: var(--accent); }
+.btn-primary, .btn-ghost {
+  display: inline-flex; align-items: center; justify-content: center;
+  padding: 10px 16px; border-radius: 10px; font-weight: 600;
+  font-size: 14px; cursor: pointer; border: 1px solid transparent;
+  text-decoration: none; white-space: nowrap;
+}
+.btn-primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
+.btn-primary:hover { filter: brightness(1.1); text-decoration: none; }
+.btn-ghost { background: transparent; color: var(--fg); border-color: var(--border); }
+.btn-ghost:hover { background: var(--bg-card); text-decoration: none; }
+
+.result-bar {
+  display: flex; align-items: center; justify-content: space-between;
+  margin: 14px 0 16px; gap: 14px; flex-wrap: wrap;
+}
+.controls { display: flex; gap: 14px; align-items: center; }
+.control { display: flex; gap: 8px; align-items: center; font-size: 13px; color: var(--fg-muted); }
+.control select, .control input[type=range] {
+  background: var(--bg-card); color: var(--fg);
+  border: 1px solid var(--border); padding: 6px 10px;
+  border-radius: 8px; font-size: 13px;
+}
+.control input[type=range] { padding: 0; accent-color: var(--accent); }
+
+.card-grid {
+  list-style: none; padding: 0; margin: 0;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(var(--cols-min), 1fr));
+  gap: 16px;
+}
+.rep-card {
+  background: var(--bg-card);
+  border: 1px solid var(--border);
+  border-radius: var(--radius);
+  transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease;
+  overflow: hidden;
+}
+.rep-card:hover { transform: translateY(-2px); border-color: var(--accent); box-shadow: var(--shadow); }
+.rep-link { display: block; padding: 18px; color: var(--fg); }
+.rep-link:hover { text-decoration: none; }
+.rep-card-head { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }
+.rep-avatar {
+  width: 48px; height: 48px; border-radius: 8px;
+  background: linear-gradient(135deg, var(--accent), #2a5a7a);
+  color: var(--accent-fg); font-weight: 700; font-size: 13px;
+  display: flex; align-items: center; justify-content: center;
+  flex-shrink: 0; letter-spacing: 0.02em;
+}
+.rep-avatar.lg { width: 84px; height: 84px; font-size: 22px; border-radius: 12px; }
+.rep-name { font-weight: 600; font-size: 16px; line-height: 1.25; }
+.rep-agency { color: var(--fg-muted); font-size: 13px; margin-top: 2px; }
+.rep-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
+.chip {
+  display: inline-block; font-size: 11px; padding: 3px 9px;
+  background: var(--tag-bg); color: var(--fg);
+  border-radius: 99px; border: 1px solid var(--border);
+  white-space: nowrap;
+}
+.chip.ok { background: var(--ok); color: var(--ok-fg); border-color: var(--ok); font-weight: 600; }
+.chip.retail { background: var(--retail); color: var(--retail-fg); border-color: var(--retail); font-weight: 600; }
+.chip.muted-chip { color: var(--fg-muted); }
+.chip.line-chip { background: var(--bg-elev); cursor: pointer; }
+.chip.line-chip:hover { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); text-decoration: none; }
+.rep-lines { font-size: 13px; margin-bottom: 10px; }
+.rep-vertical { display: flex; flex-wrap: wrap; gap: 4px; }
+.tag {
+  font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
+  padding: 2px 8px; background: transparent; border: 1px solid var(--border);
+  color: var(--fg-muted); border-radius: 4px;
+}
+
+.empty { text-align: center; padding: 60px 20px; color: var(--fg-muted); }
+
+.back-link { font-size: 13px; color: var(--fg-muted); display: inline-block; margin-bottom: 14px; }
+.rep-detail { background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius); padding: 28px; }
+.rep-header { display: flex; gap: 18px; align-items: flex-start; margin-bottom: 24px; flex-wrap: wrap; }
+.rep-header h1 { margin: 0; font-size: 28px; letter-spacing: -0.01em; }
+.rep-header .agency { color: var(--accent); font-weight: 600; margin-top: 4px; }
+.rep-header .city { font-size: 14px; }
+.dist-notes { color: var(--fg); font-size: 15px; padding: 14px 16px; background: var(--bg-card); border-radius: 10px; border: 1px solid var(--border); margin: 0 0 22px; }
+.rep-grid-detail {
+  display: grid; gap: 24px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+  padding: 22px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
+}
+.rep-grid-detail h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--fg-muted); }
+.chips { display: flex; flex-wrap: wrap; gap: 6px; }
+.lines-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 6px; font-size: 14px; }
+.lines-list strong { color: var(--fg-muted); font-weight: 500; }
+.cta-row { display: flex; gap: 10px; padding-top: 22px; flex-wrap: wrap; }
+
+.related { margin-top: 40px; }
+.related h2 { font-size: 20px; margin: 0 0 14px; }
+
+.long-form { max-width: 740px; }
+.long-form h1 { font-size: 32px; margin: 0 0 20px; }
+.long-form h2 { font-size: 18px; margin-top: 28px; }
+.long-form p { color: var(--fg); }
+.long-form ul { color: var(--fg); padding-left: 20px; }
+.long-form li { margin-bottom: 6px; }
+
+.seo-block { margin-top: 60px; padding-top: 30px; border-top: 1px solid var(--border); max-width: 860px; }
+.seo-block h2 { font-size: 18px; margin: 0 0 12px; color: var(--fg-muted); text-transform: uppercase; letter-spacing: .06em; }
+.seo-block p { color: var(--fg-muted); font-size: 14px; }
+
+.site-footer { background: var(--bg-elev); border-top: 1px solid var(--border); margin-top: 60px; padding: 36px 0 24px; }
+.footer-grid {
+  display: grid; gap: 28px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+  padding-bottom: 24px;
+}
+.site-footer strong { display: block; margin-bottom: 6px; font-size: 14px; }
+.site-footer p { font-size: 13px; line-height: 1.7; color: var(--fg-muted); margin: 0; }
+.site-footer a { color: var(--fg); }
+.copy { padding-top: 18px; border-top: 1px solid var(--border); color: var(--fg-muted); }
+.copy small { font-size: 12px; line-height: 1.6; display: block; }
+
+@media (max-width: 920px) {
+  .filter-bar { grid-template-columns: 1fr 1fr 1fr; }
+  .filter-bar input[type=search] { grid-column: 1 / -1; }
+}
+@media (max-width: 600px) {
+  .filter-bar { grid-template-columns: 1fr 1fr; }
+  .filter-bar input[type=search] { grid-column: 1 / -1; }
+  .filter-bar .check { grid-column: span 1; }
+  .site-nav a:nth-child(1) { display: none; }
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..51ea107
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+  <defs>
+    <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
+      <stop offset="0%" stop-color="#4f8fb8"/>
+      <stop offset="100%" stop-color="#2a5a7a"/>
+    </linearGradient>
+  </defs>
+  <rect width="32" height="32" rx="6" fill="url(#g)"/>
+  <rect x="6" y="6" width="20" height="20" rx="2" fill="none" stroke="rgba(255,255,255,.7)" stroke-width="2"/>
+  <line x1="11" y1="6" x2="11" y2="26" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
+  <line x1="16" y1="6" x2="16" y2="26" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
+  <line x1="21" y1="6" x2="21" y2="26" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
+</svg>
diff --git a/public/js/main.js b/public/js/main.js
new file mode 100644
index 0000000..833c3b3
--- /dev/null
+++ b/public/js/main.js
@@ -0,0 +1,45 @@
+(function () {
+  const root = document.documentElement;
+  const toggle = document.getElementById('theme-toggle');
+  if (toggle) {
+    toggle.addEventListener('click', () => {
+      const next = root.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
+      if (next === 'light') root.setAttribute('data-theme', 'light');
+      else root.removeAttribute('data-theme');
+      try { localStorage.setItem('wpd-theme', next); } catch (e) {}
+    });
+  }
+
+  const grid = document.getElementById('rep-grid');
+  const slider = document.getElementById('density-slider');
+  if (slider && grid) {
+    let saved = null;
+    try { saved = localStorage.getItem('wpd-density'); } catch (e) {}
+    if (saved) {
+      slider.value = saved;
+      grid.style.setProperty('--cols-min', saved + 'px');
+    }
+    slider.addEventListener('input', () => {
+      grid.style.setProperty('--cols-min', slider.value + 'px');
+      try { localStorage.setItem('wpd-density', slider.value); } catch (e) {}
+    });
+  }
+
+  const sortSelect = document.getElementById('sort-select');
+  if (sortSelect) {
+    let savedSort = null;
+    try { savedSort = localStorage.getItem('wpd-sort'); } catch (e) {}
+    const url = new URL(window.location.href);
+    if (savedSort && !url.searchParams.get('sort') && sortSelect.value === 'newest' && savedSort !== 'newest') {
+      url.searchParams.set('sort', savedSort);
+      window.location.replace(url.toString());
+      return;
+    }
+    sortSelect.addEventListener('change', () => {
+      try { localStorage.setItem('wpd-sort', sortSelect.value); } catch (e) {}
+      const u = new URL(window.location.href);
+      u.searchParams.set('sort', sortSelect.value);
+      window.location.assign(u.toString());
+    });
+  }
+})();
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..158cdff
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+User-agent: *
+Allow: /
+Sitemap: https://wallpaperdistributors.com/sitemap.xml
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..a8db24f
--- /dev/null
+++ b/server.js
@@ -0,0 +1,134 @@
+const express = require('express');
+const path = require('path');
+const fs = require('fs');
+const helmet = require('helmet');
+const compression = require('compression');
+const morgan = require('morgan');
+
+const app = express();
+const PORT = process.env.PORT || 9932;
+const GA_ID = process.env.GA_ID || 'G-NBZ5WTBW8F';
+
+const distributors = JSON.parse(
+  fs.readFileSync(path.join(__dirname, 'data', 'distributors.json'), 'utf8')
+);
+
+app.set('view engine', 'ejs');
+app.set('views', path.join(__dirname, 'views'));
+
+app.use(helmet({
+  contentSecurityPolicy: false,
+  crossOriginEmbedderPolicy: false,
+}));
+app.use(compression());
+app.use(morgan('tiny'));
+
+app.use((req, res, next) => {
+  res.set('Cache-Control', 'no-store, must-revalidate');
+  next();
+});
+
+app.use(express.static(path.join(__dirname, 'public'), { maxAge: '1h' }));
+
+const allRegions = Array.from(new Set(distributors.flatMap(d => d.regions))).sort();
+const allSegments = Array.from(new Set(distributors.flatMap(d => d.segments))).sort();
+const allLines = Array.from(new Set(distributors.flatMap(d => d.lines_carried))).sort();
+
+const SITE = {
+  domain: 'wallpaperdistributors.com',
+  name: 'Wallpaper Distributors',
+  tagline: 'Where to source wallpaper — distributors and the lines they carry',
+  description: 'Directory of B2B wallcovering distributors and the vendor lines each one carries. Designers, architects, FF&E purchasing, and trade buyers can find which distributor stocks Schumacher, Thibaut, Phillip Jeffries, Cole & Son, Maya Romanoff, Wolf-Gordon, Designer Wallcoverings, and other major lines for residential, hospitality, healthcare, and commercial projects.',
+  email: 'info@wallpaperdistributors.com',
+  phone: '888-373-4564',
+  address: 'Designer Wallcoverings, 15442 Ventura Bl #102, Sherman Oaks CA 91403',
+  ga_id: GA_ID,
+};
+
+function sortDistributors(list, sort) {
+  const copy = list.slice();
+  switch (sort) {
+    case 'company':
+      return copy.sort((a, b) => a.company.localeCompare(b.company));
+    case 'city':
+      return copy.sort((a, b) => a.city.localeCompare(b.city));
+    case 'lines':
+      return copy.sort((a, b) => b.lines_carried.length - a.lines_carried.length);
+    case 'years':
+      return copy.sort((a, b) => a.founded - b.founded);
+    case 'newest':
+    default:
+      return copy.sort((a, b) => b.id - a.id);
+  }
+}
+
+app.get('/', (req, res) => {
+  const { q, region, segment, line, trade, sort } = req.query;
+  let filtered = distributors.slice();
+
+  if (q) {
+    const needle = String(q).toLowerCase();
+    filtered = filtered.filter(d =>
+      d.company.toLowerCase().includes(needle) ||
+      d.city.toLowerCase().includes(needle) ||
+      d.lines_carried.some(l => l.toLowerCase().includes(needle)) ||
+      d.segments.some(s => s.toLowerCase().includes(needle)) ||
+      (d.notes || '').toLowerCase().includes(needle)
+    );
+  }
+  if (region) filtered = filtered.filter(d => d.regions.includes(String(region)));
+  if (segment) filtered = filtered.filter(d => d.segments.includes(String(segment)));
+  if (line) filtered = filtered.filter(d => d.lines_carried.includes(String(line)));
+  if (trade === '1') filtered = filtered.filter(d => d.trade_only);
+
+  filtered = sortDistributors(filtered, sort);
+
+  res.render('index', {
+    site: SITE,
+    distributors: filtered,
+    total: distributors.length,
+    matched: filtered.length,
+    filters: {
+      q: q || '',
+      region: region || '',
+      segment: segment || '',
+      line: line || '',
+      trade: trade || '',
+      sort: sort || 'newest',
+    },
+    facets: { regions: allRegions, segments: allSegments, lines: allLines },
+  });
+});
+
+app.get('/distributor/:id', (req, res) => {
+  const id = Number(req.params.id);
+  const distributor = distributors.find(d => d.id === id);
+  if (!distributor) return res.status(404).render('404', { site: SITE });
+  const related = distributors
+    .filter(d => d.id !== distributor.id && d.lines_carried.some(l => distributor.lines_carried.includes(l)))
+    .slice(0, 4);
+  res.render('distributor', { site: SITE, distributor, related });
+});
+
+app.get('/line/:line', (req, res) => {
+  const line = req.params.line;
+  const carriers = distributors.filter(d => d.lines_carried.includes(line));
+  if (!carriers.length) return res.status(404).render('404', { site: SITE });
+  res.render('line', { site: SITE, line, carriers });
+});
+
+app.get('/about', (req, res) => {
+  res.render('about', { site: SITE });
+});
+
+app.get('/healthz', (req, res) =>
+  res.json({ ok: true, port: PORT, distributors: distributors.length })
+);
+
+app.use((req, res) => res.status(404).render('404', { site: SITE }));
+
+app.listen(PORT, () => {
+  console.log(
+    `wallpaperdistributors listening on :${PORT} (${distributors.length} distributors loaded, GA ${GA_ID})`
+  );
+});
diff --git a/views/404.ejs b/views/404.ejs
new file mode 100644
index 0000000..86b285f
--- /dev/null
+++ b/views/404.ejs
@@ -0,0 +1,10 @@
+<%- include('partials/head', { title: 'Not found — ' + site.name, description: 'Page not found.' }) %>
+
+<main class="wrap main">
+  <section class="empty">
+    <h1>Page not found</h1>
+    <p>The page you tried to load isn't here. <a href="/">Return to the directory</a>.</p>
+  </section>
+</main>
+
+<%- include('partials/foot') %>
diff --git a/views/about.ejs b/views/about.ejs
new file mode 100644
index 0000000..d1e3c6f
--- /dev/null
+++ b/views/about.ejs
@@ -0,0 +1,36 @@
+<%- include('partials/head', { title: 'About — ' + site.name, description: 'Wallpaper Distributors is a directory of B2B wallcovering distributors and the vendor lines each one carries, for designers and trade buyers sourcing residential and commercial wallpaper.' }) %>
+
+<main class="wrap main">
+  <a class="back-link" href="/">&larr; Back</a>
+
+  <article class="long-form">
+    <h1>About Wallpaper Distributors</h1>
+
+    <p>Wallpaper Distributors is a free, curated directory of B2B wallcovering distributors operating in the United States and Canada. The directory exists because designers, architects, hospitality FF&amp;E purchasing groups, and trade buyers regularly need to find <em>which distributor carries which line</em> in their region &mdash; and that information is scattered across hundreds of trade-only price-book websites and showroom rep cards.</p>
+
+    <h2>Who is in the directory</h2>
+    <p>Each listing represents a wallcovering distributor &mdash; a company that warehouses, sells, and supports vendor lines into the trade and (in some cases) the retail market. Three structural types of distributor are indexed:</p>
+    <ul>
+      <li><strong>Direct manufacturers</strong> who also act as their own distributor (Schumacher, Thibaut, Phillip Jeffries, Maya Romanoff, Wolf-Gordon, Koroseal, Designtex, Carnegie Fabrics, York Wallcoverings, Brewster Home Fashions).</li>
+      <li><strong>Multi-line trade distributors</strong> who carry many vendor lines under one ordering desk (Kravet, Lee Jofa, Cowtan &amp; Tout, Pierre Frey, Osborne &amp; Little, Romo, Sanderson Design Group, Decorators Walk, Stark Carpet, Holly Hunt).</li>
+      <li><strong>Regional independent distributors</strong> who carry combinations of major lines for a specific metro or multi-state region (PaperMills Atlanta, Pacific Heritage LA, Chicago Wallcoverings Group, Hudson NYC, Dallas Design District, Mountain States Denver, Pacific Northwest Seattle, Bay Area SF, Boston Design, Southeast Charlotte).</li>
+    </ul>
+
+    <h2>What's in each listing</h2>
+    <p>Each listing includes the distributor's name, the metro the company is based in, the regions the company services, the vendor lines they carry, the project segments they actively serve (residential trade, residential retail, hospitality FF&amp;E, healthcare, education, commercial, multifamily, government, resort), trade-only vs trade-plus-retail status, showroom availability, memo / CFA program, minimum-order thresholds, lead time, year founded, and a direct contact path. You can also browse by line &mdash; clicking any line on a distributor page lists every distributor in the directory carrying that line.</p>
+
+    <h2>Verify line authorizations before specifying</h2>
+    <p>Listings reflect distributor-supplied information at the time of indexing. Vendor authorizations, regional exclusives, in-stock SKU coverage, and pricing change over time. <strong>Verify current line authorization and pricing with the distributor before specifying or ordering.</strong> Some major lines (Kravet, Lee Jofa, Brunschwig &amp; Fils, Schumacher, Thibaut, Phillip Jeffries) are regionally exclusive to specific distributors and cannot be sourced through other channels.</p>
+
+    <h2>Who maintains the directory</h2>
+    <p>The directory is operated by <a href="https://designerwallcoverings.com">Designer Wallcoverings</a> as a free industry resource. Designer Wallcoverings is itself a wallcovering distributor with an extended hospitality and commercial-FF&amp;E history, so we have a direct interest in keeping distribution information accurate and accessible. Listings are not pay-to-play and there is no advertising on the site.</p>
+
+    <h2>How to list your company</h2>
+    <p>If you run a wallcovering distribution company and would like to be added &mdash; or if you are listed and need to update lines carried, regions, segments, showroom, memo program, or minimum-order information &mdash; email <a href="mailto:<%= site.email %>"><%= site.email %></a> with the details. We typically update the directory weekly.</p>
+
+    <h2>How to flag a correction</h2>
+    <p>If a listing is wrong or out of date, email <a href="mailto:<%= site.email %>"><%= site.email %></a> with the company name and the correction. Line-authorization and showroom updates are processed on the same business day where possible.</p>
+  </article>
+</main>
+
+<%- include('partials/foot') %>
diff --git a/views/distributor.ejs b/views/distributor.ejs
new file mode 100644
index 0000000..b6e5082
--- /dev/null
+++ b/views/distributor.ejs
@@ -0,0 +1,93 @@
+<%- include('partials/head', { title: distributor.company + ' — ' + distributor.city + ' wallpaper distributor | ' + site.name, description: distributor.company + ' is a wallcovering distributor based in ' + distributor.city + '. Carries ' + distributor.lines_carried.slice(0,5).join(', ') + (distributor.lines_carried.length > 5 ? ' and ' + (distributor.lines_carried.length - 5) + ' more lines' : '') + '. ' + (distributor.trade_only ? 'To-the-trade only.' : 'Trade and retail.') + ' ' + (distributor.notes || '') }) %>
+
+<main class="wrap main">
+  <a class="back-link" href="/">&larr; Back to directory</a>
+
+  <section class="rep-detail">
+    <div class="rep-header">
+      <div class="rep-avatar lg"><%= distributor.company.split(' ').map(s => s[0]).join('').replace(/[^A-Z]/g,'').slice(0,3).toUpperCase() %></div>
+      <div>
+        <h1><%= distributor.company %></h1>
+        <div class="agency"><%= distributor.city %></div>
+        <% if (distributor.founded) { %><div class="city muted">Established <%= distributor.founded %></div><% } %>
+        <div class="rep-meta" style="margin-top:10px;">
+          <% if (distributor.trade_only) { %><span class="chip ok">To-the-Trade Only</span><% } else { %><span class="chip retail">Trade + Retail</span><% } %>
+          <% if (distributor.showroom && distributor.showroom.toLowerCase() !== 'none') { %><span class="chip">Showroom</span><% } %>
+          <% if (distributor.memo_program && /yes/i.test(distributor.memo_program)) { %><span class="chip">Memo Program</span><% } %>
+        </div>
+      </div>
+    </div>
+
+    <% if (distributor.notes) { %>
+      <p class="dist-notes"><%= distributor.notes %></p>
+    <% } %>
+
+    <div class="rep-grid-detail">
+      <div>
+        <h3>Lines carried</h3>
+        <div class="chips">
+          <% distributor.lines_carried.forEach(function(l){ %>
+            <a href="/line/<%= encodeURIComponent(l) %>" class="chip line-chip"><%= l %></a>
+          <% }) %>
+        </div>
+      </div>
+      <div>
+        <h3>Regions covered</h3>
+        <div class="chips">
+          <% distributor.regions.forEach(function(r){ %><span class="chip"><%= r %></span><% }) %>
+        </div>
+      </div>
+      <div>
+        <h3>Project segments</h3>
+        <div class="chips">
+          <% distributor.segments.forEach(function(s){ %><span class="tag"><%= s %></span><% }) %>
+        </div>
+      </div>
+      <div>
+        <h3>Sales terms</h3>
+        <ul class="lines-list">
+          <li><strong>Sells to:</strong> <%= distributor.trade_only ? 'Trade only' : 'Trade + Retail' %></li>
+          <li><strong>Showroom:</strong> <%= distributor.showroom || 'Not on file' %></li>
+          <li><strong>Memo program:</strong> <%= distributor.memo_program || 'Not on file' %></li>
+          <li><strong>Min order:</strong> <%= distributor.min_order || 'Not on file' %></li>
+          <li><strong>Lead time:</strong> <%= distributor.lead_time || 'Not on file' %></li>
+        </ul>
+      </div>
+    </div>
+
+    <div class="cta-row">
+      <a class="btn-primary" href="<%= distributor.contact_url %>" target="_blank" rel="noopener">Contact <%= distributor.company %> &rarr;</a>
+      <% if (distributor.website) { %>
+        <a class="btn-ghost" href="<%= distributor.website %>" target="_blank" rel="noopener">Visit website</a>
+      <% } %>
+      <a class="btn-ghost" href="mailto:<%= site.email %>?subject=Question%20about%20<%= encodeURIComponent(distributor.company) %>">Ask the directory</a>
+    </div>
+  </section>
+
+  <% if (related.length) { %>
+  <section class="related">
+    <h2>Other distributors carrying similar lines</h2>
+    <ul class="card-grid" style="--cols-min: 280px">
+      <% related.forEach(function(r){ %>
+        <li class="rep-card">
+          <a class="rep-link" href="/distributor/<%= r.id %>">
+            <div class="rep-card-head">
+              <div class="rep-avatar"><%= r.company.split(' ').map(s => s[0]).join('').replace(/[^A-Z]/g,'').slice(0,3).toUpperCase() %></div>
+              <div>
+                <div class="rep-name"><%= r.company %></div>
+                <div class="rep-agency"><%= r.city %></div>
+              </div>
+            </div>
+            <div class="rep-meta">
+              <% if (r.trade_only) { %><span class="chip ok">Trade-only</span><% } else { %><span class="chip retail">Trade + Retail</span><% } %>
+              <span class="chip"><%= r.lines_carried.length %> lines</span>
+            </div>
+          </a>
+        </li>
+      <% }) %>
+    </ul>
+  </section>
+  <% } %>
+</main>
+
+<%- include('partials/foot') %>
diff --git a/views/index.ejs b/views/index.ejs
new file mode 100644
index 0000000..dc04eea
--- /dev/null
+++ b/views/index.ejs
@@ -0,0 +1,107 @@
+<%- include('partials/head', { title: site.name + ' — ' + site.tagline, description: site.description }) %>
+
+<main class="wrap main">
+  <section class="hero">
+    <h1>Find which distributor carries the line you need.</h1>
+    <p class="lead">A working directory of B2B wallcovering distributors and the vendor lines each one stocks. Designers, architects, FF&amp;E purchasing, and trade buyers can use this to find which distributor carries Schumacher, Thibaut, Phillip Jeffries, Cole &amp; Son, Maya Romanoff, Wolf-Gordon, Designer Wallcoverings, and other major lines for residential, hospitality, healthcare, and commercial projects. <%= total %> distributors indexed, covering <%= facets.lines.length %> lines.</p>
+  </section>
+
+  <form class="filter-bar" method="get" action="/">
+    <input type="search" name="q" value="<%= filters.q %>" placeholder="Search distributor, city, line, segment&hellip;" aria-label="Search">
+
+    <select name="region" aria-label="Region">
+      <option value="">All regions</option>
+      <% facets.regions.forEach(function(r){ %>
+        <option value="<%= r %>" <%= filters.region === r ? 'selected' : '' %>><%= r %></option>
+      <% }) %>
+    </select>
+
+    <select name="segment" aria-label="Project segment">
+      <option value="">All segments</option>
+      <% facets.segments.forEach(function(s){ %>
+        <option value="<%= s %>" <%= filters.segment === s ? 'selected' : '' %>><%= s %></option>
+      <% }) %>
+    </select>
+
+    <select name="line" aria-label="Line carried">
+      <option value="">All lines</option>
+      <% facets.lines.forEach(function(l){ %>
+        <option value="<%= l %>" <%= filters.line === l ? 'selected' : '' %>><%= l %></option>
+      <% }) %>
+    </select>
+
+    <label class="check">
+      <input type="checkbox" name="trade" value="1" <%= filters.trade === '1' ? 'checked' : '' %>>
+      <span>Trade-only</span>
+    </label>
+
+    <button type="submit" class="btn-primary">Filter</button>
+    <% if (filters.q || filters.region || filters.segment || filters.line || filters.trade) { %>
+      <a class="btn-ghost" href="/">Reset</a>
+    <% } %>
+  </form>
+
+  <div class="result-bar">
+    <div class="muted"><%= matched %> of <%= total %> matching</div>
+    <div class="controls">
+      <label class="control">
+        <span>Sort</span>
+        <select id="sort-select" name="sort">
+          <option value="newest" <%= filters.sort === 'newest' ? 'selected' : '' %>>Newest</option>
+          <option value="company" <%= filters.sort === 'company' ? 'selected' : '' %>>Company A&rarr;Z</option>
+          <option value="city" <%= filters.sort === 'city' ? 'selected' : '' %>>City A&rarr;Z</option>
+          <option value="lines" <%= filters.sort === 'lines' ? 'selected' : '' %>>Most lines &darr;</option>
+          <option value="years" <%= filters.sort === 'years' ? 'selected' : '' %>>Oldest first</option>
+        </select>
+      </label>
+      <label class="control">
+        <span>Density</span>
+        <input id="density-slider" type="range" min="240" max="440" step="20" value="320">
+      </label>
+    </div>
+  </div>
+
+  <% if (!matched) { %>
+    <div class="empty">No distributors match those filters. <a href="/">Reset</a>.</div>
+  <% } else { %>
+    <ul class="card-grid" id="rep-grid">
+      <% distributors.forEach(function(d){ %>
+        <li class="rep-card">
+          <a class="rep-link" href="/distributor/<%= d.id %>">
+            <div class="rep-card-head">
+              <div class="rep-avatar"><%= d.company.split(' ').map(s => s[0]).join('').replace(/[^A-Z]/g,'').slice(0,3).toUpperCase() %></div>
+              <div>
+                <div class="rep-name"><%= d.company %></div>
+                <div class="rep-agency"><%= d.city %></div>
+              </div>
+            </div>
+            <div class="rep-meta">
+              <% if (d.trade_only) { %><span class="chip ok" title="To-the-trade only">Trade-only</span><% } else { %><span class="chip retail">Trade + Retail</span><% } %>
+              <% if (d.showroom && d.showroom.toLowerCase() !== 'none') { %><span class="chip">Showroom</span><% } %>
+              <% if (d.memo_program && /yes/i.test(d.memo_program)) { %><span class="chip">Memos</span><% } %>
+              <span class="chip"><%= d.lines_carried.length %> lines</span>
+              <% if (d.founded) { %><span class="chip muted-chip">Since <%= d.founded %></span><% } %>
+            </div>
+            <div class="rep-lines muted">
+              <%= d.lines_carried.slice(0,4).join(' &middot; ') %><% if (d.lines_carried.length > 4) { %> &middot; +<%= d.lines_carried.length - 4 %><% } %>
+            </div>
+            <div class="rep-vertical">
+              <% d.segments.slice(0,4).forEach(function(s){ %>
+                <span class="tag"><%= s %></span>
+              <% }) %>
+            </div>
+          </a>
+        </li>
+      <% }) %>
+    </ul>
+  <% } %>
+
+  <section class="seo-block">
+    <h2>How this directory works</h2>
+    <p>Wallpaper Distributors is a curated index of B2B wallcovering distributors operating in the United States and Canada. Each listing maps the distributor's regional coverage, the project segments they actively serve (residential trade, residential retail, hospitality FF&amp;E, healthcare, education, commercial, multifamily, government, resort), the vendor lines they carry, whether they sell trade-only or trade-plus-retail, whether they keep a physical showroom, whether they offer a memo / CFA program, and minimum-order thresholds.</p>
+    <p>This directory exists because designers, architects, and purchasing teams routinely need to find which distributor stocks a specific line in their region. The major to-the-trade houses (Kravet, Schumacher, Thibaut, Phillip Jeffries) sell direct in most metros, but many independent regional distributors also carry overlapping lines and can be a faster source for memos, in-stock pickups, and locally-warehoused inventory. Use the filters above to narrow by line, segment, region, or trade status.</p>
+    <p>This directory is operated by <a href="https://designerwallcoverings.com">Designer Wallcoverings</a> as a free industry resource. Listings are not pay-to-play. To list your company, update an existing listing, or correct an entry, email <a href="mailto:<%= site.email %>"><%= site.email %></a>.</p>
+  </section>
+</main>
+
+<%- include('partials/foot') %>
diff --git a/views/line.ejs b/views/line.ejs
new file mode 100644
index 0000000..f30d79b
--- /dev/null
+++ b/views/line.ejs
@@ -0,0 +1,39 @@
+<%- include('partials/head', { title: 'Where to buy ' + line + ' — distributors carrying ' + line + ' | ' + site.name, description: 'List of wallcovering distributors that carry the ' + line + ' line in the United States. ' + carriers.length + ' distributor' + (carriers.length === 1 ? '' : 's') + ' indexed.' }) %>
+
+<main class="wrap main">
+  <a class="back-link" href="/">&larr; Back to directory</a>
+
+  <section class="rep-detail">
+    <h1>Where to buy <%= line %></h1>
+    <p class="lead muted"><%= carriers.length %> distributor<%= carriers.length === 1 ? '' : 's' %> in the directory carry <%= line %>. Lines are typically authorized by region; contact the distributor closest to your project for stock, memo, and pricing.</p>
+  </section>
+
+  <ul class="card-grid" id="rep-grid" style="margin-top:24px;">
+    <% carriers.forEach(function(d){ %>
+      <li class="rep-card">
+        <a class="rep-link" href="/distributor/<%= d.id %>">
+          <div class="rep-card-head">
+            <div class="rep-avatar"><%= d.company.split(' ').map(s => s[0]).join('').replace(/[^A-Z]/g,'').slice(0,3).toUpperCase() %></div>
+            <div>
+              <div class="rep-name"><%= d.company %></div>
+              <div class="rep-agency"><%= d.city %></div>
+            </div>
+          </div>
+          <div class="rep-meta">
+            <% if (d.trade_only) { %><span class="chip ok">Trade-only</span><% } else { %><span class="chip retail">Trade + Retail</span><% } %>
+            <% if (d.showroom && d.showroom.toLowerCase() !== 'none') { %><span class="chip">Showroom</span><% } %>
+            <% if (d.memo_program && /yes/i.test(d.memo_program)) { %><span class="chip">Memos</span><% } %>
+            <span class="chip"><%= d.regions.length %> region<%= d.regions.length === 1 ? '' : 's' %></span>
+          </div>
+          <div class="rep-vertical">
+            <% d.segments.slice(0,4).forEach(function(s){ %>
+              <span class="tag"><%= s %></span>
+            <% }) %>
+          </div>
+        </a>
+      </li>
+    <% }) %>
+  </ul>
+</main>
+
+<%- include('partials/foot') %>
diff --git a/views/partials/foot.ejs b/views/partials/foot.ejs
new file mode 100644
index 0000000..8074696
--- /dev/null
+++ b/views/partials/foot.ejs
@@ -0,0 +1,30 @@
+<footer class="site-footer">
+  <div class="wrap footer-grid">
+    <div>
+      <strong><%= site.name %></strong>
+      <p class="muted"><%= site.tagline %></p>
+    </div>
+    <div>
+      <strong>Contact</strong>
+      <p>
+        <a href="mailto:<%= site.email %>"><%= site.email %></a><br>
+        <a href="tel:+1<%= site.phone.replace(/-/g,'') %>"><%= site.phone %></a><br>
+        <span class="muted"><%= site.address %></span>
+      </p>
+    </div>
+    <div>
+      <strong>For distributors</strong>
+      <p>
+        <a href="mailto:<%= site.email %>?subject=List%20our%20company">List your company</a><br>
+        <a href="mailto:<%= site.email %>?subject=Update%20our%20listing">Update a listing</a><br>
+        <a href="mailto:<%= site.email %>?subject=Add%20a%20line%20we%20carry">Add a line you carry</a>
+      </p>
+    </div>
+  </div>
+  <div class="wrap copy">
+    <small>&copy; <%= new Date().getFullYear() %> <%= site.name %>. A directory project of Designer Wallcoverings. Listings reflect distributor-supplied information; verify current line authorizations and pricing with the distributor before specifying.</small>
+  </div>
+</footer>
+<script src="/js/main.js" defer></script>
+</body>
+</html>
diff --git a/views/partials/head.ejs b/views/partials/head.ejs
new file mode 100644
index 0000000..b493605
--- /dev/null
+++ b/views/partials/head.ejs
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title><%= title || (site.name + ' — ' + site.tagline) %></title>
+<meta name="description" content="<%= description || site.description %>">
+<meta name="theme-color" content="#0d1722">
+<link rel="icon" href="/favicon.svg" type="image/svg+xml">
+<link rel="stylesheet" href="/css/styles.css">
+<script>
+(function(){
+  try {
+    var t = localStorage.getItem('wpd-theme');
+    if (t === 'light' || (t == null && window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches)) {
+      document.documentElement.setAttribute('data-theme','light');
+    }
+  } catch(e){}
+})();
+</script>
+<!-- GA4 gtag (auto) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=<%= site.ga_id %>"></script>
+<script>
+window.dataLayer = window.dataLayer || [];
+function gtag(){dataLayer.push(arguments);}
+gtag('js', new Date());
+gtag('config', '<%= site.ga_id %>');
+</script>
+<!-- /GA4 gtag -->
+</head>
+<body>
+<header class="site-header">
+  <div class="wrap header-row">
+    <a class="brand" href="/">
+      <span class="brand-mark"></span>
+      <span class="brand-name"><%= site.name %></span>
+    </a>
+    <nav class="site-nav">
+      <a href="/">Distributors</a>
+      <a href="/about">About</a>
+      <a href="mailto:<%= site.email %>">Contact</a>
+    </nav>
+    <button id="theme-toggle" class="theme-toggle" aria-label="Toggle theme" title="Toggle dark/light">
+      <span class="moon">&#9789;</span><span class="sun">&#9728;</span>
+    </button>
+  </div>
+</header>

(oldest)  ·  back to Wallpaperdistributors  ·  hamburger nav: apply canonical pattern from sister-site flee 90ca932 →