[object Object]

← back to Designtradelive

initial scaffold + GA gtag G-E8R8V9RDXN — designtradelive events directory (25 seed events)

8f611a53730730f3653d9803fb2f81554e28583a · 2026-05-10 03:26:25 -0700 · Steve Abrams

Files touched

Diff

commit 8f611a53730730f3653d9803fb2f81554e28583a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun May 10 03:26:25 2026 -0700

    initial scaffold + GA gtag G-E8R8V9RDXN — designtradelive events directory (25 seed events)
---
 .gitignore                |   9 +
 data/events.json          | 352 +++++++++++++++++
 package-lock.json         | 984 ++++++++++++++++++++++++++++++++++++++++++++++
 package.json              |  19 +
 public/app.js             |  57 +++
 public/robots.txt         |   3 +
 public/styles.css         | 486 +++++++++++++++++++++++
 server.js                 | 106 +++++
 views/404.ejs             |  15 +
 views/event.ejs           |  53 +++
 views/index.ejs           |  85 ++++
 views/partials/footer.ejs |  27 ++
 views/partials/head.ejs   |  25 ++
 views/partials/header.ejs |  17 +
 14 files changed, 2238 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/data/events.json b/data/events.json
new file mode 100644
index 0000000..1a43a55
--- /dev/null
+++ b/data/events.json
@@ -0,0 +1,352 @@
+[
+  {
+    "slug": "icff-nyc-2026",
+    "name": "ICFF — International Contemporary Furniture Fair",
+    "city": "New York, NY",
+    "venue": "Javits Center",
+    "country": "USA",
+    "start_date": "2026-05-17",
+    "end_date": "2026-05-19",
+    "category": "Furniture",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.icff.com",
+    "summary": "North America's premier showcase for contemporary residential and contract design. Co-located with WantedDesign Manhattan during NYCxDESIGN."
+  },
+  {
+    "slug": "high-point-market-spring-2026",
+    "name": "High Point Market (Spring)",
+    "city": "High Point, NC",
+    "venue": "Multiple showrooms across downtown High Point",
+    "country": "USA",
+    "start_date": "2026-04-25",
+    "end_date": "2026-04-29",
+    "category": "Furniture",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.highpointmarket.org",
+    "summary": "The world's largest home furnishings industry trade show, twice yearly. 11.5M sq ft of showrooms, 75,000+ attendees per market."
+  },
+  {
+    "slug": "high-point-market-fall-2026",
+    "name": "High Point Market (Fall)",
+    "city": "High Point, NC",
+    "venue": "Multiple showrooms across downtown High Point",
+    "country": "USA",
+    "start_date": "2026-10-17",
+    "end_date": "2026-10-21",
+    "category": "Furniture",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.highpointmarket.org",
+    "summary": "Fall edition of the global home furnishings market. New product reveals from over 2,000 exhibitors."
+  },
+  {
+    "slug": "neocon-2026",
+    "name": "NeoCon",
+    "city": "Chicago, IL",
+    "venue": "theMART",
+    "country": "USA",
+    "start_date": "2026-06-08",
+    "end_date": "2026-06-10",
+    "category": "Commercial",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.neocon.com",
+    "summary": "The leading trade event for commercial interiors. Workplace, healthcare, education, hospitality & retail design at theMART."
+  },
+  {
+    "slug": "kbis-2026",
+    "name": "KBIS — Kitchen & Bath Industry Show",
+    "city": "Las Vegas, NV",
+    "venue": "Las Vegas Convention Center",
+    "country": "USA",
+    "start_date": "2026-02-17",
+    "end_date": "2026-02-19",
+    "category": "Kitchen & Bath",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.kbis.com",
+    "summary": "North America's largest annual trade show for the kitchen and bath industry, co-located with IBS as Design & Construction Week."
+  },
+  {
+    "slug": "las-vegas-market-winter-2026",
+    "name": "Las Vegas Market (Winter)",
+    "city": "Las Vegas, NV",
+    "venue": "World Market Center Las Vegas",
+    "country": "USA",
+    "start_date": "2026-01-25",
+    "end_date": "2026-01-29",
+    "category": "Furniture",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.lasvegasmarket.com",
+    "summary": "The largest furniture, gift, and home decor market in the western US. Winter and summer editions."
+  },
+  {
+    "slug": "las-vegas-market-summer-2026",
+    "name": "Las Vegas Market (Summer)",
+    "city": "Las Vegas, NV",
+    "venue": "World Market Center Las Vegas",
+    "country": "USA",
+    "start_date": "2026-07-26",
+    "end_date": "2026-07-30",
+    "category": "Furniture",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.lasvegasmarket.com",
+    "summary": "Summer edition. 5,000+ resources spanning furniture, home decor, gift, and lifestyle categories."
+  },
+  {
+    "slug": "bdny-2026",
+    "name": "BDNY — Boutique Design New York",
+    "city": "New York, NY",
+    "venue": "Javits Center",
+    "country": "USA",
+    "start_date": "2026-11-08",
+    "end_date": "2026-11-09",
+    "category": "Hospitality",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://bdny.com",
+    "summary": "Annual hospitality design trade fair for hotel, resort, restaurant, cruise and spa interior design."
+  },
+  {
+    "slug": "salone-del-mobile-2026",
+    "name": "Salone del Mobile.Milano",
+    "city": "Milan",
+    "venue": "Fiera Milano Rho",
+    "country": "Italy",
+    "start_date": "2026-04-21",
+    "end_date": "2026-04-26",
+    "category": "Furniture",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.salonemilano.it",
+    "summary": "The world's most important furniture and design fair. Anchors Milan Design Week with Fuorisalone exhibitions across the city."
+  },
+  {
+    "slug": "maison-objet-january-2026",
+    "name": "Maison&Objet (January)",
+    "city": "Paris",
+    "venue": "Paris Nord Villepinte",
+    "country": "France",
+    "start_date": "2026-01-22",
+    "end_date": "2026-01-26",
+    "category": "Lifestyle",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.maison-objet.com",
+    "summary": "International home decor, design, and lifestyle trade fair, twice a year in Paris."
+  },
+  {
+    "slug": "maison-objet-september-2026",
+    "name": "Maison&Objet (September)",
+    "city": "Paris",
+    "venue": "Paris Nord Villepinte",
+    "country": "France",
+    "start_date": "2026-09-04",
+    "end_date": "2026-09-08",
+    "category": "Lifestyle",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.maison-objet.com",
+    "summary": "Fall edition. Brings together publishers, decorators, designers and architects from around the world."
+  },
+  {
+    "slug": "ad-design-show-2026",
+    "name": "Architectural Digest Design Show",
+    "city": "New York, NY",
+    "venue": "Pier 36",
+    "country": "USA",
+    "start_date": "2026-03-19",
+    "end_date": "2026-03-22",
+    "category": "Residential",
+    "tier": "major",
+    "audience": "trade-and-public",
+    "url": "https://www.addesignshow.com",
+    "summary": "Annual show curated by Architectural Digest featuring luxury furniture, home goods, kitchen, bath, building, and outdoor brands."
+  },
+  {
+    "slug": "americasmart-january-2026",
+    "name": "AmericasMart Atlanta (January)",
+    "city": "Atlanta, GA",
+    "venue": "AmericasMart",
+    "country": "USA",
+    "start_date": "2026-01-13",
+    "end_date": "2026-01-19",
+    "category": "Lifestyle",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.americasmart.com",
+    "summary": "Premier wholesale market for gift, home, and area rug. 7M sq ft, three buildings."
+  },
+  {
+    "slug": "heimtextil-2026",
+    "name": "Heimtextil",
+    "city": "Frankfurt",
+    "venue": "Messe Frankfurt",
+    "country": "Germany",
+    "start_date": "2026-01-13",
+    "end_date": "2026-01-16",
+    "category": "Textiles",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://heimtextil.messefrankfurt.com",
+    "summary": "World's largest trade fair for home and contract textiles. 2,800+ exhibitors from 60+ countries."
+  },
+  {
+    "slug": "decorex-2026",
+    "name": "Decorex International",
+    "city": "London",
+    "venue": "Olympia London",
+    "country": "United Kingdom",
+    "start_date": "2026-10-11",
+    "end_date": "2026-10-14",
+    "category": "Residential",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.decorex.com",
+    "summary": "The premier event for interior design held during London Design Festival. Luxury furniture, fabric, and lighting."
+  },
+  {
+    "slug": "ny-now-winter-2026",
+    "name": "NY NOW (Winter)",
+    "city": "New York, NY",
+    "venue": "Javits Center",
+    "country": "USA",
+    "start_date": "2026-02-08",
+    "end_date": "2026-02-11",
+    "category": "Lifestyle",
+    "tier": "regional",
+    "audience": "trade",
+    "url": "https://nynow.com",
+    "summary": "Wholesale home, lifestyle, gift and handmade marketplace. Winter and summer editions in NYC."
+  },
+  {
+    "slug": "cersaie-2026",
+    "name": "Cersaie",
+    "city": "Bologna",
+    "venue": "BolognaFiere",
+    "country": "Italy",
+    "start_date": "2026-09-21",
+    "end_date": "2026-09-25",
+    "category": "Tile & Stone",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.cersaie.it",
+    "summary": "International exhibition of ceramic tile and bathroom furnishings. Defines tile trends globally."
+  },
+  {
+    "slug": "hd-expo-2026",
+    "name": "HD Expo + Conference",
+    "city": "Las Vegas, NV",
+    "venue": "Mandalay Bay",
+    "country": "USA",
+    "start_date": "2026-05-05",
+    "end_date": "2026-05-07",
+    "category": "Hospitality",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.hdexpo.com",
+    "summary": "Largest hospitality design trade show in North America. Annual event for hotel, restaurant, casino and resort designers."
+  },
+  {
+    "slug": "wanteddesign-manhattan-2026",
+    "name": "WantedDesign Manhattan",
+    "city": "New York, NY",
+    "venue": "Javits Center",
+    "country": "USA",
+    "start_date": "2026-05-17",
+    "end_date": "2026-05-19",
+    "category": "Furniture",
+    "tier": "regional",
+    "audience": "trade",
+    "url": "https://wanteddesignnyc.com",
+    "summary": "Curated platform for international design talent, co-located with ICFF during NYCxDESIGN."
+  },
+  {
+    "slug": "domotex-2026",
+    "name": "Domotex",
+    "city": "Hannover",
+    "venue": "Deutsche Messe",
+    "country": "Germany",
+    "start_date": "2026-01-15",
+    "end_date": "2026-01-17",
+    "category": "Floor Coverings",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.domotex.de",
+    "summary": "World-leading trade fair for the floor-covering industry — carpets, rugs, parquet, laminate, resilient flooring."
+  },
+  {
+    "slug": "coverings-2026",
+    "name": "Coverings",
+    "city": "Orlando, FL",
+    "venue": "Orange County Convention Center",
+    "country": "USA",
+    "start_date": "2026-04-20",
+    "end_date": "2026-04-23",
+    "category": "Tile & Stone",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.coverings.com",
+    "summary": "The largest international tile and stone exhibition in North America. Ceramics, porcelain, natural stone, and installation."
+  },
+  {
+    "slug": "shoppe-object-summer-2026",
+    "name": "Shoppe Object",
+    "city": "New York, NY",
+    "venue": "Pier 36",
+    "country": "USA",
+    "start_date": "2026-08-09",
+    "end_date": "2026-08-11",
+    "category": "Lifestyle",
+    "tier": "regional",
+    "audience": "trade",
+    "url": "https://shoppeobject.com",
+    "summary": "Curated independent home and gift trade show for buyers seeking distinctive, design-forward brands."
+  },
+  {
+    "slug": "casa-decor-2026",
+    "name": "Casa Decor",
+    "city": "Madrid",
+    "venue": "Rotating mansion location",
+    "country": "Spain",
+    "start_date": "2026-03-12",
+    "end_date": "2026-05-10",
+    "category": "Residential",
+    "tier": "regional",
+    "audience": "trade-and-public",
+    "url": "https://casadecor.es",
+    "summary": "Spain's premier interior design and architecture event. Each year a different historic Madrid building is transformed."
+  },
+  {
+    "slug": "design-shanghai-2026",
+    "name": "Design Shanghai",
+    "city": "Shanghai",
+    "venue": "Shanghai World Expo Exhibition Center",
+    "country": "China",
+    "start_date": "2026-06-04",
+    "end_date": "2026-06-07",
+    "category": "Furniture",
+    "tier": "major",
+    "audience": "trade",
+    "url": "https://www.designshanghai.com",
+    "summary": "Asia's leading international design event. Showcases the best contemporary design from around the world."
+  },
+  {
+    "slug": "bdwest-2026",
+    "name": "BDwest — Boutique Design West",
+    "city": "Los Angeles, CA",
+    "venue": "Pasadena Convention Center",
+    "country": "USA",
+    "start_date": "2026-03-24",
+    "end_date": "2026-03-25",
+    "category": "Hospitality",
+    "tier": "regional",
+    "audience": "trade",
+    "url": "https://bdwest.com",
+    "summary": "West Coast hospitality design event for hotel, resort, restaurant and spa designers."
+  }
+]
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..5684316
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,984 @@
+{
+  "name": "designtradelive",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "designtradelive",
+      "version": "0.1.0",
+      "dependencies": {
+        "compression": "^1.7.4",
+        "ejs": "^3.1.10",
+        "express": "^4.21.0"
+      },
+      "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/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/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/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/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/finalhandler": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+      "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "~2.0.2",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.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/http-errors": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "inherits": "~2.0.4",
+        "setprototypeof": "~1.2.0",
+        "statuses": "~2.0.2",
+        "toidentifier": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/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/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/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/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.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/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+      "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.1",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "~2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "~2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.3",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+      "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "~0.19.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.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/statuses": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..4151ef6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+  "name": "designtradelive",
+  "version": "0.1.0",
+  "private": true,
+  "description": "Design industry events + trade shows aggregator (ICFF, NeoCon, KBIS, High Point Market, BDNY, Salone del Mobile, etc.)",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js",
+    "dev": "node server.js"
+  },
+  "dependencies": {
+    "compression": "^1.7.4",
+    "ejs": "^3.1.10",
+    "express": "^4.21.0"
+  },
+  "engines": {
+    "node": ">=20"
+  }
+}
diff --git a/public/app.js b/public/app.js
new file mode 100644
index 0000000..cecc956
--- /dev/null
+++ b/public/app.js
@@ -0,0 +1,57 @@
+(function () {
+  'use strict';
+
+  // Theme toggle
+  const root = document.documentElement;
+  const toggle = document.querySelector('[data-theme-toggle]');
+  if (toggle) {
+    toggle.addEventListener('click', () => {
+      const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
+      root.setAttribute('data-theme', next);
+      try { localStorage.setItem('dtl-theme', next); } catch (e) {}
+    });
+  }
+
+  // Sort: persist + reload with new ?sort=
+  const sortSel = document.querySelector('[data-sort]');
+  if (sortSel) {
+    try {
+      const stored = localStorage.getItem('dtl-sort');
+      if (stored && !new URLSearchParams(location.search).has('sort')) {
+        sortSel.value = stored;
+      }
+    } catch (e) {}
+    sortSel.addEventListener('change', () => {
+      try { localStorage.setItem('dtl-sort', sortSel.value); } catch (e) {}
+      const url = new URL(window.location);
+      url.searchParams.set('sort', sortSel.value);
+      window.location = url.toString();
+    });
+  }
+
+  // Density slider — controls min card width
+  const grid = document.querySelector('[data-grid]');
+  const slider = document.querySelector('[data-density]');
+  if (grid && slider) {
+    function applyDensity(px) {
+      grid.style.setProperty('--col-min', px + 'px');
+      document.documentElement.style.setProperty('--col-min', px + 'px');
+    }
+    try {
+      const stored = parseInt(localStorage.getItem('dtl-density') || '0', 10);
+      if (stored >= 220 && stored <= 460) {
+        slider.value = stored;
+        applyDensity(stored);
+      } else {
+        applyDensity(parseInt(slider.value, 10));
+      }
+    } catch (e) {
+      applyDensity(parseInt(slider.value, 10));
+    }
+    slider.addEventListener('input', () => {
+      const v = parseInt(slider.value, 10);
+      applyDensity(v);
+      try { localStorage.setItem('dtl-density', String(v)); } catch (e) {}
+    });
+  }
+})();
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..c25af44
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+User-agent: *
+Allow: /
+Sitemap: https://designtradelive.com/sitemap.xml
diff --git a/public/styles.css b/public/styles.css
new file mode 100644
index 0000000..7a9d669
--- /dev/null
+++ b/public/styles.css
@@ -0,0 +1,486 @@
+/* DesignTradeLive — single CSS file, dark/light themed */
+
+:root {
+  --col-min: 320px;
+  --max-w: 1280px;
+  --radius: 12px;
+  --radius-lg: 18px;
+  --transition: 0.2s ease;
+  --font-serif: "Cormorant Garamond", "Playfair Display", Georgia, "Times New Roman", serif;
+  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", system-ui, sans-serif;
+}
+
+[data-theme="dark"] {
+  --bg: #0c0c10;
+  --bg-2: #15151c;
+  --bg-elev: #1c1c25;
+  --text: #ececef;
+  --text-dim: #a8a8b3;
+  --line: #28283a;
+  --accent: #c9a14a;
+  --accent-2: #e7c785;
+  --tier-major: #c9a14a;
+  --tier-major-bg: rgba(201, 161, 74, 0.15);
+  --tier-regional: #6c8cd6;
+  --tier-regional-bg: rgba(108, 140, 214, 0.15);
+}
+
+[data-theme="light"] {
+  --bg: #fafaf6;
+  --bg-2: #ffffff;
+  --bg-elev: #ffffff;
+  --text: #111114;
+  --text-dim: #4a4a55;
+  --line: #e7e3d8;
+  --accent: #8a6a1f;
+  --accent-2: #b08a36;
+  --tier-major: #8a6a1f;
+  --tier-major-bg: rgba(138, 106, 31, 0.10);
+  --tier-regional: #345db8;
+  --tier-regional-bg: rgba(52, 93, 184, 0.10);
+}
+
+* { box-sizing: border-box; }
+html, body { margin: 0; padding: 0; }
+body {
+  font-family: var(--font-sans);
+  font-size: 16px;
+  line-height: 1.55;
+  color: var(--text);
+  background: var(--bg);
+  -webkit-font-smoothing: antialiased;
+  transition: background var(--transition), color var(--transition);
+}
+
+a { color: var(--accent); text-decoration: none; }
+a:hover { color: var(--accent-2); }
+
+.visually-hidden {
+  position: absolute !important;
+  height: 1px; width: 1px; overflow: hidden;
+  clip: rect(1px, 1px, 1px, 1px); white-space: nowrap;
+}
+
+/* Header */
+.site-header {
+  display: flex;
+  align-items: center;
+  gap: 1.5rem;
+  padding: 1rem 1.5rem;
+  border-bottom: 1px solid var(--line);
+  background: var(--bg);
+  position: sticky;
+  top: 0;
+  z-index: 50;
+  backdrop-filter: blur(8px);
+}
+.brand {
+  display: inline-flex;
+  align-items: center;
+  gap: 0.6rem;
+  font-family: var(--font-serif);
+  font-size: 1.4rem;
+  font-weight: 600;
+  color: var(--text);
+  letter-spacing: 0.01em;
+}
+.brand-mark {
+  display: inline-grid;
+  place-items: center;
+  width: 36px; height: 36px;
+  border-radius: 8px;
+  background: var(--accent);
+  color: var(--bg);
+  font-weight: 700;
+  font-size: 1.1rem;
+}
+.brand-accent { color: var(--accent); }
+.site-nav {
+  margin-left: auto;
+  display: flex;
+  gap: 1.25rem;
+  font-size: 0.95rem;
+}
+.site-nav a {
+  color: var(--text-dim);
+  border-bottom: 2px solid transparent;
+  padding-bottom: 2px;
+  transition: color var(--transition), border-color var(--transition);
+}
+.site-nav a:hover {
+  color: var(--text);
+  border-color: var(--accent);
+}
+
+.theme-toggle {
+  background: none;
+  border: 1px solid var(--line);
+  border-radius: 999px;
+  width: 38px; height: 38px;
+  cursor: pointer;
+  color: var(--text);
+  font-size: 1.05rem;
+  display: grid;
+  place-items: center;
+  transition: border-color var(--transition);
+}
+.theme-toggle:hover { border-color: var(--accent); }
+[data-theme="dark"] .theme-icon-light,
+[data-theme="light"] .theme-icon-dark { display: none; }
+
+/* Hero */
+.hero {
+  padding: 4rem 1.5rem 2.5rem;
+  border-bottom: 1px solid var(--line);
+  background:
+    radial-gradient(ellipse at top right, rgba(201,161,74,0.08), transparent 60%),
+    radial-gradient(ellipse at bottom left, rgba(108,140,214,0.06), transparent 60%),
+    var(--bg);
+}
+.hero-inner {
+  max-width: var(--max-w);
+  margin: 0 auto;
+}
+.kicker {
+  text-transform: uppercase;
+  letter-spacing: 0.18em;
+  font-size: 0.78rem;
+  color: var(--accent);
+  margin: 0 0 0.75rem;
+}
+.hero h1 {
+  font-family: var(--font-serif);
+  font-weight: 600;
+  font-size: clamp(2rem, 4.5vw, 3.4rem);
+  line-height: 1.1;
+  margin: 0 0 1rem;
+  letter-spacing: -0.01em;
+}
+.hero .lede {
+  font-size: 1.1rem;
+  color: var(--text-dim);
+  max-width: 60ch;
+  margin: 0 0 1.75rem;
+}
+
+.search-bar {
+  display: flex;
+  gap: 0.5rem;
+  max-width: 640px;
+}
+.search-bar input[type="search"] {
+  flex: 1;
+  padding: 0.85rem 1rem;
+  border-radius: var(--radius);
+  border: 1px solid var(--line);
+  background: var(--bg-2);
+  color: var(--text);
+  font: inherit;
+}
+.search-bar input[type="search"]:focus {
+  outline: 2px solid var(--accent);
+  outline-offset: 2px;
+}
+.search-bar button {
+  padding: 0.85rem 1.4rem;
+  border-radius: var(--radius);
+  border: none;
+  background: var(--accent);
+  color: var(--bg);
+  font: inherit;
+  font-weight: 600;
+  cursor: pointer;
+  transition: background var(--transition);
+}
+.search-bar button:hover { background: var(--accent-2); }
+
+/* Container */
+.container {
+  max-width: var(--max-w);
+  margin: 0 auto;
+  padding: 2rem 1.5rem 4rem;
+}
+
+/* Filters */
+.filters {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 1rem 1.5rem;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 1.5rem;
+  padding-bottom: 1rem;
+  border-bottom: 1px solid var(--line);
+}
+.filter-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.4rem;
+  align-items: center;
+}
+.filter-label {
+  font-size: 0.85rem;
+  text-transform: uppercase;
+  letter-spacing: 0.1em;
+  color: var(--text-dim);
+  margin-right: 0.5rem;
+}
+.chip {
+  display: inline-flex;
+  align-items: center;
+  gap: 0.4rem;
+  padding: 0.35rem 0.75rem;
+  border: 1px solid var(--line);
+  border-radius: 999px;
+  font-size: 0.85rem;
+  color: var(--text-dim);
+  background: var(--bg-2);
+  transition: color var(--transition), border-color var(--transition), background var(--transition);
+}
+.chip em {
+  font-style: normal;
+  font-size: 0.72rem;
+  color: var(--text-dim);
+  opacity: 0.7;
+}
+.chip:hover { color: var(--text); border-color: var(--accent); }
+.chip.is-active {
+  color: var(--bg);
+  background: var(--accent);
+  border-color: var(--accent);
+}
+.chip.is-active em { color: var(--bg); opacity: 0.9; }
+
+.grid-controls {
+  display: flex;
+  gap: 1rem;
+  align-items: center;
+}
+.control {
+  display: inline-flex;
+  align-items: center;
+  gap: 0.5rem;
+  font-size: 0.85rem;
+  color: var(--text-dim);
+}
+.control select,
+.control input[type="range"] {
+  font: inherit;
+  padding: 0.35rem 0.6rem;
+  border-radius: 8px;
+  border: 1px solid var(--line);
+  background: var(--bg-2);
+  color: var(--text);
+}
+.control input[type="range"] { padding: 0; width: 140px; }
+
+/* Empty */
+.empty {
+  text-align: center;
+  padding: 3rem 1rem;
+  color: var(--text-dim);
+}
+
+/* Event grid */
+.event-grid {
+  list-style: none;
+  margin: 0; padding: 0;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(var(--col-min), 1fr));
+  gap: 1.25rem;
+}
+.event-card {
+  background: var(--bg-2);
+  border: 1px solid var(--line);
+  border-radius: var(--radius-lg);
+  overflow: hidden;
+  transition: border-color var(--transition), transform var(--transition);
+}
+.event-card:hover {
+  border-color: var(--accent);
+  transform: translateY(-2px);
+}
+.card-link {
+  display: grid;
+  grid-template-columns: 90px 1fr;
+  gap: 1rem;
+  padding: 1.25rem;
+  color: inherit;
+  align-items: start;
+}
+.card-link:hover { color: inherit; }
+.card-date {
+  display: grid;
+  align-content: start;
+  text-align: center;
+  padding: 0.5rem 0.25rem;
+  background: var(--bg-elev);
+  border-radius: var(--radius);
+  border: 1px solid var(--line);
+}
+.card-month {
+  font-size: 0.7rem;
+  letter-spacing: 0.16em;
+  color: var(--accent);
+  font-weight: 600;
+}
+.card-day {
+  font-family: var(--font-serif);
+  font-size: 1.8rem;
+  font-weight: 600;
+  line-height: 1;
+  margin-top: 0.15rem;
+}
+.card-year {
+  font-size: 0.7rem;
+  color: var(--text-dim);
+  margin-top: 0.25rem;
+}
+.card-name {
+  margin: 0 0 0.35rem;
+  font-family: var(--font-serif);
+  font-size: 1.2rem;
+  font-weight: 600;
+  letter-spacing: -0.005em;
+  line-height: 1.25;
+}
+.card-loc {
+  margin: 0 0 0.6rem;
+  font-size: 0.85rem;
+  color: var(--text-dim);
+}
+.card-summary {
+  margin: 0 0 0.85rem;
+  font-size: 0.9rem;
+  color: var(--text-dim);
+  display: -webkit-box;
+  -webkit-line-clamp: 3;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+.card-meta {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.4rem;
+}
+
+.tag {
+  display: inline-block;
+  padding: 0.18rem 0.55rem;
+  border-radius: 999px;
+  font-size: 0.7rem;
+  font-weight: 600;
+  text-transform: uppercase;
+  letter-spacing: 0.08em;
+}
+.tag-major { color: var(--tier-major); background: var(--tier-major-bg); }
+.tag-regional { color: var(--tier-regional); background: var(--tier-regional-bg); }
+.tag-cat {
+  color: var(--text-dim);
+  background: var(--bg-elev);
+  border: 1px solid var(--line);
+}
+
+/* Event detail */
+.event-page { max-width: 880px; }
+.breadcrumb { color: var(--text-dim); margin-bottom: 1rem; }
+.event-header h1 {
+  font-family: var(--font-serif);
+  font-size: clamp(1.8rem, 4vw, 2.8rem);
+  margin: 0.4rem 0 0.6rem;
+  line-height: 1.15;
+}
+.event-when { color: var(--text-dim); font-size: 1.05rem; }
+.event-body .lede {
+  font-size: 1.15rem;
+  margin-top: 1.5rem;
+  margin-bottom: 1.75rem;
+}
+.event-facts {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: 1rem;
+  margin: 0 0 2rem;
+  padding: 1.25rem;
+  background: var(--bg-2);
+  border: 1px solid var(--line);
+  border-radius: var(--radius-lg);
+}
+.event-facts dt {
+  font-size: 0.7rem;
+  text-transform: uppercase;
+  letter-spacing: 0.12em;
+  color: var(--text-dim);
+  margin-bottom: 0.2rem;
+}
+.event-facts dd { margin: 0; }
+
+.btn {
+  display: inline-block;
+  padding: 0.75rem 1.25rem;
+  border-radius: var(--radius);
+  font-weight: 600;
+  border: 1px solid transparent;
+  transition: background var(--transition), border-color var(--transition), color var(--transition);
+}
+.btn-primary { background: var(--accent); color: var(--bg); }
+.btn-primary:hover { background: var(--accent-2); color: var(--bg); }
+.btn-ghost {
+  border-color: var(--line);
+  color: var(--text);
+}
+.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
+.event-cta { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 2rem; }
+
+.disclaimer {
+  font-size: 0.85rem;
+  color: var(--text-dim);
+  padding: 1rem;
+  border-left: 3px solid var(--accent);
+  background: var(--bg-2);
+  border-radius: 4px;
+}
+
+/* Footer */
+.site-footer {
+  border-top: 1px solid var(--line);
+  background: var(--bg-2);
+  padding: 3rem 1.5rem 1.5rem;
+  margin-top: 4rem;
+}
+.footer-grid {
+  max-width: var(--max-w);
+  margin: 0 auto 2rem;
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+  gap: 2rem;
+}
+.footer-grid h3 {
+  font-family: var(--font-serif);
+  font-size: 1.05rem;
+  margin: 0 0 0.6rem;
+  color: var(--accent);
+}
+.footer-grid p, .footer-grid address {
+  font-style: normal;
+  margin: 0;
+  color: var(--text-dim);
+  font-size: 0.93rem;
+  line-height: 1.55;
+}
+.footer-bottom {
+  max-width: var(--max-w);
+  margin: 0 auto;
+  padding-top: 1.25rem;
+  border-top: 1px solid var(--line);
+  color: var(--text-dim);
+  font-size: 0.85rem;
+}
+
+@media (max-width: 720px) {
+  .site-header { gap: 0.75rem; padding: 0.75rem 1rem; }
+  .site-nav { display: none; }
+  .hero { padding: 3rem 1.25rem 2rem; }
+  .filters { gap: 0.75rem; }
+  .grid-controls { width: 100%; }
+}
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..593a29f
--- /dev/null
+++ b/server.js
@@ -0,0 +1,106 @@
+'use strict';
+
+const path = require('path');
+const fs = require('fs');
+const express = require('express');
+const compression = require('compression');
+
+const app = express();
+const PORT = parseInt(process.env.PORT || '9926', 10);
+const DATA_FILE = path.join(__dirname, 'data', 'events.json');
+
+const GA_ID = 'G-E8R8V9RDXN';
+const SITE_NAME = 'DesignTradeLive';
+const DOMAIN = 'designtradelive.com';
+
+let EVENTS = [];
+function loadEvents() {
+  try {
+    EVENTS = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
+  } catch (err) {
+    console.error('[events] failed to load events.json:', err.message);
+    EVENTS = [];
+  }
+}
+loadEvents();
+fs.watchFile(DATA_FILE, { interval: 5000 }, () => loadEvents());
+
+app.set('view engine', 'ejs');
+app.set('views', path.join(__dirname, 'views'));
+app.use(compression());
+app.use(express.static(path.join(__dirname, 'public'), { maxAge: '1h' }));
+
+app.use((req, res, next) => {
+  res.set('Cache-Control', 'no-store, must-revalidate');
+  next();
+});
+
+function categorize(events) {
+  const counts = {};
+  for (const e of events) counts[e.category] = (counts[e.category] || 0) + 1;
+  return Object.entries(counts).sort((a, b) => b[1] - a[1]);
+}
+
+function sortEvents(events, sort) {
+  const arr = events.slice();
+  switch (sort) {
+    case 'name':
+      return arr.sort((a, b) => a.name.localeCompare(b.name));
+    case 'city':
+      return arr.sort((a, b) => a.city.localeCompare(b.city));
+    case 'category':
+      return arr.sort((a, b) => a.category.localeCompare(b.category) || a.start_date.localeCompare(b.start_date));
+    case 'tier':
+      return arr.sort((a, b) => {
+        const order = { major: 0, regional: 1, local: 2 };
+        return (order[a.tier] ?? 9) - (order[b.tier] ?? 9) || a.start_date.localeCompare(b.start_date);
+      });
+    case 'date':
+    default:
+      return arr.sort((a, b) => a.start_date.localeCompare(b.start_date));
+  }
+}
+
+app.get('/', (req, res) => {
+  const q = (req.query.q || '').toLowerCase().trim();
+  const cat = req.query.category || '';
+  const sort = req.query.sort || 'date';
+  let filtered = EVENTS;
+  if (q) {
+    filtered = filtered.filter(e =>
+      e.name.toLowerCase().includes(q) ||
+      e.city.toLowerCase().includes(q) ||
+      e.country.toLowerCase().includes(q) ||
+      e.category.toLowerCase().includes(q) ||
+      (e.summary || '').toLowerCase().includes(q)
+    );
+  }
+  if (cat) filtered = filtered.filter(e => e.category === cat);
+  filtered = sortEvents(filtered, sort);
+  res.render('index', {
+    events: filtered,
+    total: EVENTS.length,
+    categories: categorize(EVENTS),
+    q, cat, sort,
+    GA_ID, SITE_NAME, DOMAIN
+  });
+});
+
+app.get('/events/:slug', (req, res) => {
+  const ev = EVENTS.find(e => e.slug === req.params.slug);
+  if (!ev) return res.status(404).render('404', { GA_ID, SITE_NAME, DOMAIN });
+  res.render('event', { ev, GA_ID, SITE_NAME, DOMAIN });
+});
+
+app.get('/api/events', (req, res) => {
+  res.set('Cache-Control', 'public, max-age=300');
+  res.json({ count: EVENTS.length, events: EVENTS });
+});
+
+app.get('/healthz', (req, res) => res.json({ ok: true, count: EVENTS.length }));
+
+app.use((req, res) => res.status(404).render('404', { GA_ID, SITE_NAME, DOMAIN }));
+
+app.listen(PORT, '127.0.0.1', () => {
+  console.log(`[${SITE_NAME}] listening on http://127.0.0.1:${PORT} — ${EVENTS.length} events loaded`);
+});
diff --git a/views/404.ejs b/views/404.ejs
new file mode 100644
index 0000000..8b9504c
--- /dev/null
+++ b/views/404.ejs
@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <title>Not found — DesignTradeLive</title>
+  <%- include('partials/head', { GA_ID }) %>
+</head>
+<body>
+  <%- include('partials/header') %>
+  <main class="container" style="text-align:center; padding:6rem 1rem;">
+    <h1>That event isn't on our calendar.</h1>
+    <p><a class="btn btn-primary" href="/">Browse all events</a></p>
+  </main>
+  <%- include('partials/footer') %>
+</body>
+</html>
diff --git a/views/event.ejs b/views/event.ejs
new file mode 100644
index 0000000..24f3e06
--- /dev/null
+++ b/views/event.ejs
@@ -0,0 +1,53 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <title><%= ev.name %> — <%= ev.city %> | DesignTradeLive</title>
+  <% const metaDescription = ev.summary + ' ' + ev.start_date + ' through ' + ev.end_date + ' at ' + ev.venue + ', ' + ev.city + '.'; %>
+  <%- include('partials/head', { GA_ID, metaDescription }) %>
+</head>
+<body>
+  <%- include('partials/header') %>
+
+  <main class="container event-page">
+    <p class="breadcrumb"><a href="/">← All events</a></p>
+
+    <header class="event-header">
+      <p class="kicker"><%= ev.category %> · <span class="tag tag-<%= ev.tier %>"><%= ev.tier %></span></p>
+      <h1><%= ev.name %></h1>
+      <p class="event-when">
+        <%
+          const sd = new Date(ev.start_date + 'T00:00:00Z');
+          const ed = new Date(ev.end_date + 'T00:00:00Z');
+          const opts = { month: 'long', day: 'numeric', timeZone: 'UTC' };
+        %>
+        <strong><%= sd.toLocaleDateString('en-US', opts) %> – <%= ed.toLocaleDateString('en-US', { ...opts, year: 'numeric' }) %></strong>
+        · <%= ev.venue %>, <%= ev.city %><% if (ev.country !== 'USA') { %>, <%= ev.country %><% } %>
+      </p>
+    </header>
+
+    <div class="event-body">
+      <p class="lede"><%= ev.summary %></p>
+
+      <dl class="event-facts">
+        <div><dt>Dates</dt><dd><%= ev.start_date %> → <%= ev.end_date %></dd></div>
+        <div><dt>Venue</dt><dd><%= ev.venue %></dd></div>
+        <div><dt>City</dt><dd><%= ev.city %></dd></div>
+        <div><dt>Country</dt><dd><%= ev.country %></dd></div>
+        <div><dt>Category</dt><dd><%= ev.category %></dd></div>
+        <div><dt>Tier</dt><dd><%= ev.tier %></dd></div>
+        <div><dt>Audience</dt><dd><%= ev.audience %></dd></div>
+        <div><dt>Official site</dt><dd><a href="<%= ev.url %>" target="_blank" rel="noopener noreferrer"><%= ev.url.replace(/^https?:\/\//, '') %></a></dd></div>
+      </dl>
+
+      <p class="event-cta">
+        <a class="btn btn-primary" href="<%= ev.url %>" target="_blank" rel="noopener noreferrer">Visit official site →</a>
+        <a class="btn btn-ghost" href="/">Browse all events</a>
+      </p>
+
+      <p class="disclaimer">Dates and venue published from the show organizer's public schedule. Always verify on the official site before booking travel — trade shows occasionally shift dates by a week.</p>
+    </div>
+  </main>
+
+  <%- include('partials/footer') %>
+</body>
+</html>
diff --git a/views/index.ejs b/views/index.ejs
new file mode 100644
index 0000000..5a47d7f
--- /dev/null
+++ b/views/index.ejs
@@ -0,0 +1,85 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <title>DesignTradeLive — design industry trade shows & events calendar</title>
+  <% const metaDescription = 'Live calendar of design industry trade shows: ICFF, NeoCon, KBIS, High Point Market, BDNY, Salone del Mobile, Maison&Objet, Heimtextil, Decorex, Cersaie, and more.'; %>
+  <%- include('partials/head', { GA_ID, metaDescription }) %>
+</head>
+<body>
+  <%- include('partials/header') %>
+
+  <section class="hero">
+    <div class="hero-inner">
+      <p class="kicker">Trade shows · Markets · Design Weeks</p>
+      <h1>The live calendar for the design industry.</h1>
+      <p class="lede">Every major furniture, hospitality, kitchen-and-bath, textile, and tile show — in one place. Filter by city, category, or season. Built for designers, buyers, sales reps, and brand teams who actually go.</p>
+      <form class="search-bar" method="get" action="/">
+        <label for="q" class="visually-hidden">Search events</label>
+        <input id="q" name="q" type="search" placeholder="Search city, show, country, or category" value="<%= q %>">
+        <% if (cat) { %><input type="hidden" name="category" value="<%= cat %>"><% } %>
+        <% if (sort && sort !== 'date') { %><input type="hidden" name="sort" value="<%= sort %>"><% } %>
+        <button type="submit">Search</button>
+      </form>
+    </div>
+  </section>
+
+  <main class="container">
+    <div class="filters">
+      <div class="filter-group">
+        <span class="filter-label">Categories:</span>
+        <a class="chip <%= !cat ? 'is-active' : '' %>" href="/?<%= q ? 'q=' + encodeURIComponent(q) + '&' : '' %>sort=<%= sort %>">All <em><%= total %></em></a>
+        <% categories.forEach(([name, count]) => { %>
+          <a class="chip <%= cat === name ? 'is-active' : '' %>" href="/?<%= q ? 'q=' + encodeURIComponent(q) + '&' : '' %>category=<%= encodeURIComponent(name) %>&sort=<%= sort %>"><%= name %> <em><%= count %></em></a>
+        <% }) %>
+      </div>
+
+      <div class="grid-controls">
+        <label class="control">
+          <span>Sort</span>
+          <select id="sort-select" data-sort>
+            <option value="date" <%= sort === 'date' ? 'selected' : '' %>>Soonest</option>
+            <option value="name" <%= sort === 'name' ? 'selected' : '' %>>Title A→Z</option>
+            <option value="city" <%= sort === 'city' ? 'selected' : '' %>>City A→Z</option>
+            <option value="category" <%= sort === 'category' ? 'selected' : '' %>>Category</option>
+            <option value="tier" <%= sort === 'tier' ? 'selected' : '' %>>Major first</option>
+          </select>
+        </label>
+        <label class="control">
+          <span>Density</span>
+          <input id="density-slider" type="range" min="220" max="460" step="10" value="320" data-density>
+        </label>
+      </div>
+    </div>
+
+    <% if (events.length === 0) { %>
+      <p class="empty">No events match your search. <a href="/">Clear filters</a>.</p>
+    <% } %>
+
+    <ul class="event-grid" data-grid>
+      <% events.forEach(e => { %>
+        <li class="event-card">
+          <a class="card-link" href="/events/<%= e.slug %>">
+            <div class="card-date">
+              <% const sd = new Date(e.start_date + 'T00:00:00Z'); const ed = new Date(e.end_date + 'T00:00:00Z'); %>
+              <span class="card-month"><%= sd.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' }).toUpperCase() %></span>
+              <span class="card-day"><%= sd.getUTCDate() %><% if (ed.getUTCDate() !== sd.getUTCDate() || ed.getUTCMonth() !== sd.getUTCMonth()) { %>–<%= ed.getUTCDate() %><% } %></span>
+              <span class="card-year"><%= sd.getUTCFullYear() %></span>
+            </div>
+            <div class="card-body">
+              <h2 class="card-name"><%= e.name %></h2>
+              <p class="card-loc"><%= e.city %><% if (e.country !== 'USA') { %>, <%= e.country %><% } %></p>
+              <p class="card-summary"><%= e.summary %></p>
+              <div class="card-meta">
+                <span class="tag tag-<%= e.tier %>"><%= e.tier %></span>
+                <span class="tag tag-cat"><%= e.category %></span>
+              </div>
+            </div>
+          </a>
+        </li>
+      <% }) %>
+    </ul>
+  </main>
+
+  <%- include('partials/footer') %>
+</body>
+</html>
diff --git a/views/partials/footer.ejs b/views/partials/footer.ejs
new file mode 100644
index 0000000..cb4bc46
--- /dev/null
+++ b/views/partials/footer.ejs
@@ -0,0 +1,27 @@
+<footer class="site-footer" id="about">
+  <div class="footer-grid">
+    <div>
+      <h3>About DesignTradeLive</h3>
+      <p>The live calendar of design-industry trade shows and markets — from High Point and ICFF to Salone del Mobile and Heimtextil. Curated for designers, buyers, and brand teams who actually attend.</p>
+    </div>
+    <div>
+      <h3>Contact</h3>
+      <p>
+        <a href="mailto:info@designtradelive.com">info@designtradelive.com</a><br>
+        <a href="tel:+18883734564">888-373-4564</a>
+      </p>
+    </div>
+    <div>
+      <h3>Mailing Address</h3>
+      <address>
+        Designer Wallcoverings<br>
+        15442 Ventura Bl #102<br>
+        Sherman Oaks, CA 91403
+      </address>
+    </div>
+  </div>
+  <div class="footer-bottom">
+    <small>© <%= new Date().getFullYear() %> DesignTradeLive. Event dates published from public sources; please verify with the show organizer before booking travel.</small>
+  </div>
+</footer>
+<script src="/app.js" defer></script>
diff --git a/views/partials/head.ejs b/views/partials/head.ejs
new file mode 100644
index 0000000..3f3abc6
--- /dev/null
+++ b/views/partials/head.ejs
@@ -0,0 +1,25 @@
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="description" content="<%= typeof metaDescription !== 'undefined' ? metaDescription : 'DesignTradeLive — the live calendar of design industry trade shows and events: ICFF, NeoCon, KBIS, High Point Market, BDNY, Salone del Mobile, Maison&Objet, Heimtextil, and more.' %>">
+<meta name="theme-color" content="#0c0c10">
+<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><rect width='64' height='64' rx='12' fill='%23c9a14a'/><text x='32' y='42' text-anchor='middle' font-family='Georgia,serif' font-size='34' font-weight='700' fill='%230c0c10'>D</text></svg>">
+<script>
+  // Anti-flash theme init — must run before <body> paints.
+  (function () {
+    try {
+      var t = localStorage.getItem('dtl-theme');
+      if (!t) t = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
+      document.documentElement.setAttribute('data-theme', t);
+    } catch (e) { document.documentElement.setAttribute('data-theme', 'dark'); }
+  })();
+</script>
+<!-- GA4 gtag (auto) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=<%= GA_ID %>"></script>
+<script>
+  window.dataLayer = window.dataLayer || [];
+  function gtag(){dataLayer.push(arguments);}
+  gtag('js', new Date());
+  gtag('config', '<%= GA_ID %>');
+</script>
+<!-- /GA4 gtag -->
+<link rel="stylesheet" href="/styles.css">
diff --git a/views/partials/header.ejs b/views/partials/header.ejs
new file mode 100644
index 0000000..44b4b98
--- /dev/null
+++ b/views/partials/header.ejs
@@ -0,0 +1,17 @@
+<header class="site-header">
+  <a class="brand" href="/" aria-label="DesignTradeLive home">
+    <span class="brand-mark" aria-hidden="true">D</span>
+    <span class="brand-text">DesignTrade<span class="brand-accent">Live</span></span>
+  </a>
+  <nav class="site-nav" aria-label="primary">
+    <a href="/">All Events</a>
+    <a href="/?category=Furniture">Furniture</a>
+    <a href="/?category=Hospitality">Hospitality</a>
+    <a href="/?category=Commercial">Commercial</a>
+    <a href="#about">About</a>
+  </nav>
+  <button class="theme-toggle" type="button" aria-label="Toggle dark/light mode" data-theme-toggle>
+    <span class="theme-icon-dark" aria-hidden="true">☾</span>
+    <span class="theme-icon-light" aria-hidden="true">☀</span>
+  </button>
+</header>

(oldest)  ·  back to Designtradelive  ·  hamburger nav: apply canonical pattern from sister-site flee b1ed8f3 →