← back to Losangelesfabrics Tmp
initial scaffold
8a041b5e76cced19e196da76a790220bf05a3e6a · 2026-05-13 12:13:50 -0700 · Steve Abrams
Files touched
A .gitignoreA index.htmlA script.jsA styles.css
Diff
commit 8a041b5e76cced19e196da76a790220bf05a3e6a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 13 12:13:50 2026 -0700
initial scaffold
---
.gitignore | 9 +
index.html | 3806 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
script.js | 769 ++++++++++++
styles.css | 845 ++++++++++++++
4 files changed, 5429 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/index.html b/index.html
new file mode 100644
index 0000000..16ed9b3
--- /dev/null
+++ b/index.html
@@ -0,0 +1,3806 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<!-- Google Analytics 4 (Los Angeles Fabrics) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-3R2Q55DMKL"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-3R2Q55DMKL');
+</script>
+
+<!-- Anti-flash theme: read saved theme before paint so the page never flickers light → dark -->
+<script>
+ (function() {
+ try {
+ var t = localStorage.getItem('laf_theme');
+ if (!t) t = 'dark';
+ document.documentElement.setAttribute('data-theme', t);
+ } catch (e) {
+ document.documentElement.setAttribute('data-theme', 'dark');
+ }
+ })();
+</script>
+
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Los Angeles Fabrics — Commercial Fabric & Drapery</title>
+ <meta name="description" content="Los Angeles Fabrics — commercial-grade fabrics and drapery for designers, architects, and contract installations. Free memo samples; trade service direct from the mill.">
+ <!-- TODO 2026-05-07: replace favicon with a real LA Fabrics asset; current path is a Novasuede leftover -->
+ <link rel="icon" type="image/jpeg" href="/images/favicon.jpg">
+ <link rel="shortcut icon" type="image/jpeg" href="/images/favicon.jpg">
+
+ <link rel="preconnect" href="https://fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap" rel="stylesheet">
+
+ <style>
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #f5f1e8;
+ background: #0a0a0a;
+ }
+
+ header {
+ background: rgba(10, 10, 10, 0.78);
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+ box-shadow: 0 1px 0 rgba(245, 241, 232, 0.08);
+ position: fixed;
+ width: 100%;
+ top: 0;
+ z-index: 1000;
+ }
+ header .logo h1 { color: #f5f1e8 !important; font-weight: 300; letter-spacing: 0.18em; font-size: 1.05rem; }
+ header nav a, header .nav-container a { color: rgba(245, 241, 232, 0.85) !important; }
+ header nav a:hover, header .nav-container a:hover { color: #fff !important; }
+
+ .nav-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 1rem 2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .logo h1 {
+ color: #DDAB1E;
+ font-size: 1.8rem;
+ }
+
+ .logo h1::before {
+ content: '';
+ display: inline-block;
+ width: 28px;
+ height: 28px;
+ background-image: url('https://losangelesfabrics.com/images/colors/losangelesfabrics-ruby.jpg');
+ background-size: cover;
+ background-position: center;
+ border-radius: 50%;
+ margin-right: 10px;
+ vertical-align: middle;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ }
+
+ .nav-menu {
+ list-style: none;
+ display: flex;
+ gap: 2rem;
+ }
+
+ .nav-menu a {
+ color: #333;
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.3s;
+ }
+
+ .nav-menu a:hover {
+ color: #DDAB1E;
+ }
+
+ .mobile-toggle {
+ display: none;
+ flex-direction: column;
+ background: none;
+ border: none;
+ cursor: pointer;
+ }
+
+ .mobile-toggle span {
+ width: 25px;
+ height: 3px;
+ background: #333;
+ margin: 3px 0;
+ transition: 0.3s;
+ }
+
+ .hero {
+ margin-top: 0;
+ padding: 0;
+ background: #000;
+ text-align: center;
+ position: relative;
+ min-height: 100vh;
+ overflow: hidden;
+ isolation: isolate;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ /* Rotating background layers — full-bleed crossfade through 5 dark/jewel suede tones */
+ .hero-bg {
+ position: absolute;
+ inset: 0;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ opacity: 0;
+ transition: opacity 2200ms cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 0;
+ will-change: opacity;
+ }
+ .hero-bg.on { opacity: 1; }
+ /* Subtle vignette overlay to deepen the edges and hold the wordmark */
+ .hero::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(ellipse at center, rgba(0,0,0,0.0) 35%, rgba(0,0,0,0.55) 100%);
+ z-index: 1;
+ pointer-events: none;
+ }
+ .hero .hero-content {
+ position: relative;
+ z-index: 2;
+ padding: 0 24px;
+ }
+ @media (max-width: 768px) {
+ .hero { min-height: 88vh; }
+ }
+
+ /* No white veil — the suede tones already provide the right backdrop on black */
+ .hero::before { content: none; }
+
+ .hero-content {
+ position: relative;
+ z-index: 2;
+ max-width: 920px;
+ margin: 0 auto;
+ text-align: center;
+ }
+
+ /* Drapery removed — hidden if any leftover markup tries to render it */
+ .curtain { display: none !important; }
+
+ /* Simple Gucci-style wordmark — wide-tracked light caps in white */
+ .ns-wordmark-link {
+ display: inline-block;
+ text-decoration: none;
+ cursor: pointer;
+ }
+ .ns-wordmark {
+ font-family: -apple-system, "Helvetica Neue", "Inter", Arial, sans-serif;
+ font-weight: 200;
+ font-size: clamp(36px, 6.5vw, 96px);
+ letter-spacing: 0.32em;
+ line-height: 1;
+ color: #f5f1e8;
+ margin: 0 auto 2rem;
+ text-shadow: 0 4px 22px rgba(0, 0, 0, 0.55);
+ text-transform: uppercase;
+ transition: color 280ms ease;
+ }
+ .ns-wordmark sup {
+ font-size: 0.32em;
+ font-weight: 300;
+ letter-spacing: 0;
+ position: relative;
+ top: -1.2em;
+ margin-left: 0.08em;
+ }
+ .ns-wordmark-link:hover .ns-wordmark { color: #fff; }
+ @media (max-width: 768px) {
+ .ns-wordmark { letter-spacing: 0.22em; margin-bottom: 1.5rem; }
+ }
+
+ .hero h2 {
+ font-family: 'Cormorant Garamond', 'Didot', 'Bodoni 72', Georgia, serif;
+ font-style: italic;
+ font-weight: 300;
+ font-size: clamp(20px, 2.4vw, 32px);
+ line-height: 1.35;
+ margin: 0 auto 1.6rem;
+ max-width: 640px;
+ color: #f5f1e8;
+ letter-spacing: 0.01em;
+ text-shadow: 0 2px 16px rgba(0, 0, 0, 0.6);
+ }
+
+ .hero p {
+ font-size: 13px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ font-weight: 300;
+ margin: 0 auto 2.25rem;
+ max-width: 580px;
+ color: rgba(245, 241, 232, 0.78);
+ text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
+ }
+ .hero .authorized-dealer { margin-top: 2.5rem; }
+ .hero .authorized-dealer p {
+ font-size: 9.5px;
+ letter-spacing: 0.42em;
+ color: rgba(245, 241, 232, 0.55);
+ margin: 0;
+ }
+
+ /* Gucci-style hairline button — white border on black, no fill */
+ .sample-btn {
+ background: transparent;
+ color: #f5f1e8;
+ border: 1px solid rgba(245, 241, 232, 0.85);
+ padding: 14px 38px;
+ font-family: -apple-system, "SF Pro Text", "Helvetica Neue", Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: 400;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ border-radius: 0;
+ cursor: pointer;
+ transition: background 280ms ease, color 280ms ease, border-color 280ms ease;
+ }
+ .sample-btn:hover {
+ background: #f5f1e8;
+ color: #0a0a0a;
+ border-color: #f5f1e8;
+ }
+
+ .container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+ }
+
+ .colors-section {
+ padding: 4rem 0;
+ background: #0a0a0a;
+ color: #f5f1e8;
+ }
+
+ .colors-section h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ color: #f5f1e8;
+ }
+
+ .section-subtitle {
+ text-align: center;
+ color: rgba(245, 241, 232, 0.6);
+ margin-bottom: 3rem;
+ }
+
+ .colors-grid {
+ display: grid;
+ grid-template-columns: repeat(var(--cols, 6), minmax(0, 1fr));
+ gap: 1.5rem;
+ }
+ @media (max-width: 720px) { .colors-grid { grid-template-columns: repeat(2, 1fr) !important; } }
+ @media (min-width: 721px) and (max-width: 1024px) { .colors-grid { grid-template-columns: repeat(3, 1fr) !important; } }
+ /* Sort + density toolbar (CLAUDE.md "every grid needs sort + density" rule) */
+ .ns-toolbar { display: inline-flex; gap: 18px; align-items: center; margin-left: auto; flex-wrap: wrap; }
+ .ns-ctrl { display: inline-flex; gap: 8px; align-items: center; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #666; }
+ .ns-ctrl select { background: #fff; color: #222; border: 1px solid #ccc; padding: 5px 8px; font: inherit; font-size: 12px; text-transform: none; letter-spacing: 0; border-radius: 3px; cursor: pointer; }
+ .ns-ctrl select:focus { outline: none; border-color: #DDAB1E; }
+ .ns-ctrl input[type="range"] { width: 90px; accent-color: #DDAB1E; cursor: pointer; }
+ .ns-ctrl #nsColsVal { font-family: ui-monospace, "SF Mono", Menlo, monospace; color: #DDAB1E; min-width: 14px; text-align: right; }
+
+ .color-item {
+ cursor: pointer;
+ transition: transform 0.3s;
+ text-align: center;
+ position: relative;
+ background: #0a0a0a;
+ border-radius: 8px;
+ overflow: hidden;
+ }
+
+ .color-item:hover {
+ transform: translateY(-5px);
+ }
+
+ /* Photo-only until hover — chip-actions + name/code overlay reveal on hover */
+ .color-item .color-info {
+ position: absolute;
+ left: 0; right: 0; bottom: 0;
+ margin: 0;
+ padding: 0.85rem 0.85rem 0.7rem;
+ background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 60%);
+ opacity: 0;
+ transform: translateY(8px);
+ transition: opacity 220ms ease, transform 220ms ease;
+ text-align: left;
+ pointer-events: none;
+ color: #fff;
+ }
+ .color-item:hover .color-info,
+ .color-item:focus-within .color-info {
+ opacity: 1;
+ transform: translateY(0);
+ pointer-events: auto;
+ }
+ .color-item .color-name { color: #fff; }
+ .color-item .color-code { color: rgba(255,255,255,0.72); }
+ .color-item .chip-actions { margin-top: 0.55rem; gap: 0.45rem; }
+
+ .color-item .new-badge {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ background: #8b6914;
+ color: #fff;
+ font-size: 0.65rem;
+ font-weight: 700;
+ letter-spacing: 0.05em;
+ padding: 2px 8px;
+ border-radius: 10px;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.2);
+ z-index: 2;
+ }
+
+ .color-swatch {
+ width: 100%;
+ height: 120px;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
+ background: #f0f0f0;
+ }
+
+ .color-swatch img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ .color-info {
+ margin-top: 0.5rem;
+ }
+
+ .color-name {
+ font-weight: 600;
+ color: #333;
+ }
+
+ .color-code {
+ font-size: 0.85rem;
+ color: #999;
+ }
+
+ .chip-actions {
+ display: flex;
+ gap: 6px;
+ margin-top: 6px;
+ justify-content: center;
+ flex-wrap: wrap;
+ }
+ .drapery-btn, .sample-btn-chip {
+ padding: 5px 10px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ color: #fff;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.2s, transform 0.05s;
+ }
+ .drapery-btn { background: #DDAB1E; }
+ .drapery-btn:hover { background: #b3870e; }
+ .sample-btn-chip { background: #5a5a5a; }
+ .sample-btn-chip:hover { background: #2a2a2a; }
+ .drapery-btn:active, .sample-btn-chip:active { transform: scale(0.96); }
+
+ /* When the page is showing a drapery swag, get OUT of the way so the
+ recolored swag shows full strength. No white fade, no blur, no filter.
+ Sections become fully transparent; chips/text get a small dark plate
+ so they remain readable on top of saturated colors. */
+ body.has-drapery .colors-section,
+ body.has-drapery .specifications-section,
+ body.has-drapery .features-section,
+ body.has-drapery .installation-section,
+ body.has-drapery footer {
+ background: transparent !important;
+ }
+ /* Kill the white veil over the hero so the recolored swag reads true. */
+ body.has-drapery .hero::before { background: transparent !important; }
+ /* Header stays opaque-ish so nav remains legible. */
+ body.has-drapery header {
+ background: rgba(20, 20, 20, 0.55) !important;
+ backdrop-filter: blur(6px);
+ -webkit-backdrop-filter: blur(6px);
+ }
+ body.has-drapery header .nav-menu a,
+ body.has-drapery header .logo h1 { color: #fff !important; }
+ /* Chip swatches and section cards keep their own backgrounds, just give
+ text on the swag a soft dark backplate for legibility. */
+ body.has-drapery .colors-section h2,
+ body.has-drapery .colors-section .section-subtitle,
+ body.has-drapery .specifications-section h2,
+ body.has-drapery .features-section h2,
+ body.has-drapery .installation-section h2,
+ body.has-drapery .installation-section .section-subtitle,
+ body.has-drapery .hero h2,
+ body.has-drapery .hero p,
+ body.has-drapery .hero .authorized-dealer p {
+ color: #fff !important;
+ text-shadow: 0 2px 12px rgba(0,0,0,0.85), 0 1px 4px rgba(0,0,0,0.95) !important;
+ }
+ body.has-drapery .authorized-dealer {
+ background: rgba(0,0,0,0.55) !important;
+ border-color: #DDAB1E !important;
+ backdrop-filter: blur(2px);
+ -webkit-backdrop-filter: blur(2px);
+ }
+ body.has-drapery .color-item { background: rgba(255,255,255,0.94); border-radius: 8px; }
+
+ /* Contact info bar — pinned to the top, above the main nav. */
+ .contact-bar {
+ position: fixed;
+ top: 0; left: 0; right: 0;
+ z-index: 200;
+ background: linear-gradient(90deg, #1a1a1a 0%, #2a2a2a 100%);
+ color: #f5e7c0;
+ font-size: 13px;
+ padding: 6px 0;
+ border-bottom: 2px solid #DDAB1E;
+ }
+ .contact-bar-inner {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 0 16px;
+ display: flex;
+ align-items: center;
+ gap: 22px;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+ .cb-link {
+ color: #f5e7c0;
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ transition: color 0.15s;
+ }
+ .cb-link:hover { color: #DDAB1E; }
+ .cb-static { color: #aaa; cursor: default; }
+ .cb-ico { font-size: 14px; }
+ .cb-cta {
+ background: #DDAB1E;
+ color: #1a1a1a;
+ border: none;
+ padding: 5px 14px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: 700;
+ cursor: pointer;
+ margin-left: auto;
+ transition: background 0.15s, transform 0.05s;
+ }
+ .cb-cta:hover { background: #b3870e; color: #fff; }
+ .cb-cta:active { transform: scale(0.97); }
+ /* Push the existing fixed header down so it sits below the contact bar. */
+ header { top: 36px !important; }
+ body { padding-top: 36px; }
+ @media (max-width: 720px) {
+ .contact-bar { font-size: 11px; padding: 5px 0; }
+ .contact-bar-inner { gap: 12px; }
+ .cb-static { display: none; }
+ header { top: 30px !important; }
+ body { padding-top: 30px; }
+ }
+ @media (max-width: 480px) {
+ .cb-text { display: none; } /* icons-only on tiny screens */
+ .cb-cta { padding: 4px 8px; font-size: 11px; }
+ }
+
+ /* Fixed left + right drapery panels — visible at full strength for the
+ entire scroll of the page, not just the hero. Each is the recolored
+ swag image cropped to show only its respective edge. */
+ .has-drapery-side {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ width: 12vw;
+ min-width: 90px;
+ max-width: 220px;
+ z-index: 50;
+ pointer-events: none;
+ background-size: auto 100vh;
+ background-repeat: no-repeat;
+ display: none; /* shown only when body.has-drapery */
+ box-shadow: 0 0 30px rgba(0,0,0,0.25);
+ }
+ .has-drapery-side.left { left: 0; background-position: left center; }
+ .has-drapery-side.right { right: 0; background-position: right center; }
+ body.has-drapery .has-drapery-side { display: block; }
+ @media (max-width: 600px) {
+ body.has-drapery .has-drapery-side { display: none; } /* mobile content too narrow */
+ }
+
+ /* Standalone "New Arrivals 2026" section that sits above the main collection */
+ .new-arrivals-section {
+ padding: 3rem 0 1.5rem;
+ background: #0a0a0a;
+ color: #f5f1e8;
+ }
+ .new-arrivals-header {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 14px;
+ margin-bottom: 6px;
+ flex-wrap: wrap;
+ }
+ .new-arrivals-section h2 {
+ color: #DDAB1E !important;
+ font-size: 2rem;
+ margin: 0;
+ }
+ .new-arrivals-pill {
+ background: #DDAB1E;
+ color: #1a1a1a;
+ font-weight: 700;
+ font-size: 0.85rem;
+ padding: 4px 12px;
+ border-radius: 999px;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+ .new-arrivals-section .section-subtitle {
+ color: #d8c79a;
+ text-align: center;
+ margin-bottom: 1.75rem;
+ }
+ .new-arrivals-section .colors-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 1rem;
+ }
+ .new-arrivals-section .color-item {
+ background: rgba(255, 255, 255, 0.96);
+ color: #1a1a1a;
+ }
+
+ .color-filters {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ margin: 10px 0 18px;
+ flex-wrap: wrap;
+ }
+ .color-filter-btn {
+ padding: 6px 14px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: #555;
+ background: #fff;
+ border: 1px solid #ccc;
+ border-radius: 999px;
+ cursor: pointer;
+ transition: all 0.15s;
+ }
+ .color-filter-btn:hover { border-color: #DDAB1E; color: #1a1a1a; }
+ .color-filter-btn.active { background: #DDAB1E; border-color: #DDAB1E; color: #fff; }
+ .color-count { color: #888; font-size: 0.85rem; margin-left: auto; }
+
+ /* Color-family dot row — circular swatches that filter the grid by hue bucket. */
+ .color-dots {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ margin: 6px 0 14px;
+ flex-wrap: wrap;
+ }
+ .color-dot {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ border: 2px solid rgba(0,0,0,0.12);
+ padding: 0;
+ cursor: pointer;
+ position: relative;
+ transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.15);
+ }
+ .color-dot:hover { transform: scale(1.12); border-color: rgba(0,0,0,0.45); }
+ .color-dot.active {
+ transform: scale(1.18);
+ border-color: #1a1a1a;
+ box-shadow: 0 0 0 2px #fff, 0 0 0 4px #DDAB1E;
+ }
+ .color-dot[data-family="all"] {
+ width: auto; height: 28px; border-radius: 16px; padding: 0 12px;
+ font-size: 0.75rem; font-weight: 600; color: #fff; letter-spacing: 0.04em;
+ }
+ .color-dot[data-family="all"] .dot-label { color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.5); }
+
+ .color-toolbar-row { display: flex; gap: 8px; align-items: center; margin: 6px 0 14px; flex-wrap: wrap; }
+
+ /* Modal Styles */
+ .modal {
+ display: none;
+ position: fixed;
+ z-index: 2000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.5);
+ }
+
+ .modal-content {
+ background: white;
+ margin: 50px auto;
+ padding: 2rem;
+ width: 90%;
+ max-width: 500px;
+ border-radius: 10px;
+ position: relative;
+ max-height: 90vh;
+ overflow-y: auto;
+ }
+
+ .close {
+ position: absolute;
+ right: 1.5rem;
+ top: 1.5rem;
+ font-size: 2rem;
+ cursor: pointer;
+ color: #999;
+ }
+
+ .close:hover {
+ color: #333;
+ }
+
+ .modal h2 {
+ margin-bottom: 1.5rem;
+ color: #333;
+ }
+
+ .color-preview {
+ text-align: center;
+ padding: 0 0 1.25rem;
+ background: transparent;
+ border-radius: 0;
+ margin-bottom: 1.5rem;
+ border-bottom: 1px solid rgba(0,0,0,0.08);
+ }
+
+ .color-preview img {
+ display: block;
+ width: 100%;
+ max-width: 460px;
+ height: 280px;
+ border-radius: 4px;
+ object-fit: cover;
+ margin: 0 auto;
+ box-shadow: 0 8px 24px rgba(0,0,0,0.18);
+ transition: opacity 320ms ease;
+ background: #1a1a1a; /* shows while image loads */
+ }
+ .color-preview img.loading { opacity: 0.35; }
+
+ .color-preview span#colorPreviewName {
+ display: block;
+ margin-top: 0.9rem;
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-size: 11px;
+ font-weight: 400;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: #1a1a1a;
+ }
+ .color-preview .preview-hex {
+ display: block;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ font-size: 11px;
+ color: #888;
+ margin-top: 4px;
+ letter-spacing: 0.04em;
+ text-transform: lowercase;
+ }
+
+ /* Multi-select sample request — pick + Add chips list */
+ .color-pick-row {
+ display: flex;
+ gap: 10px;
+ align-items: stretch;
+ }
+ .color-pick-row select {
+ flex: 1;
+ min-width: 0;
+ }
+ .add-to-request-btn {
+ white-space: nowrap;
+ background: #1a1a1a;
+ color: #f5f1e8;
+ border: 1px solid #1a1a1a;
+ padding: 0 18px;
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 220ms ease, color 220ms ease;
+ }
+ .add-to-request-btn:hover { background: #f5f1e8; color: #1a1a1a; }
+ .add-to-request-btn:disabled {
+ background: #ddd;
+ color: #999;
+ border-color: #ddd;
+ cursor: not-allowed;
+ }
+
+ .request-list {
+ margin-top: 1rem;
+ padding: 14px 16px;
+ background: rgba(184, 153, 104, 0.06);
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ border-radius: 4px;
+ }
+ .request-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ font-size: 9px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: #666;
+ margin-bottom: 10px;
+ font-weight: 500;
+ }
+ .request-list-header .request-count {
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ color: #1a1a1a;
+ font-weight: 600;
+ letter-spacing: 0.05em;
+ }
+ .request-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ }
+ .request-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 10px 6px 8px;
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 12px;
+ color: #1a1a1a;
+ line-height: 1;
+ }
+ .request-chip .chip-swatch {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ border-radius: 2px;
+ border: 1px solid rgba(0,0,0,0.1);
+ flex: none;
+ }
+ .request-chip button {
+ background: transparent;
+ border: none;
+ color: #999;
+ cursor: pointer;
+ font-size: 16px;
+ line-height: 1;
+ padding: 0 0 0 4px;
+ margin: 0;
+ }
+ .request-chip button:hover { color: #1a1a1a; }
+ .request-list.empty { display: none; }
+
+ .form-group {
+ margin-bottom: 1.5rem;
+ }
+
+ .form-group label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: #333;
+ }
+
+ .form-group input,
+ .form-group select,
+ .form-group textarea {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ font-size: 1rem;
+ font-family: inherit;
+ }
+
+ .form-group textarea {
+ min-height: 120px;
+ resize: vertical;
+ }
+
+ .submit-btn {
+ width: 100%;
+ background: #DDAB1E;
+ color: white;
+ border: none;
+ padding: 1rem;
+ font-size: 1.1rem;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: 600;
+ }
+
+ .submit-btn:hover {
+ background: #c4981a;
+ }
+
+ /* Authorized Dealer Badge */
+ .authorized-dealer {
+ margin-top: 2rem;
+ padding: 1rem;
+ background: rgba(0, 0, 0, 0.55);
+ border-radius: 8px;
+ border: 2px solid #DDAB1E;
+ backdrop-filter: blur(2px);
+ -webkit-backdrop-filter: blur(2px);
+ }
+
+ .authorized-dealer p {
+ color: #ffffff;
+ font-weight: 600;
+ margin: 0;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
+ }
+
+ /* Specifications Section */
+ .specifications-section {
+ padding: 4rem 0;
+ background: #0a0a0a;
+ color: #f5f1e8;
+ }
+ .specifications-section h2, .specifications-section .section-subtitle, .specifications-section p, .specifications-section li, .specifications-section td, .specifications-section th { color: #f5f1e8; }
+ .specifications-section .section-subtitle { color: rgba(245, 241, 232, 0.6); }
+
+ .specifications-section h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 3rem;
+ color: #222;
+ }
+
+ .specs-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 1.5rem;
+ max-width: 900px;
+ margin: 0 auto;
+ }
+
+ .spec-item {
+ display: flex;
+ justify-content: space-between;
+ padding: 1rem;
+ background: white;
+ border-radius: 8px;
+ border: 1px solid #e0e0e0;
+ }
+
+ .spec-label {
+ font-weight: 600;
+ color: #666;
+ }
+
+ .spec-value {
+ color: #333;
+ text-align: right;
+ }
+
+ /* Features Section */
+ .features-section {
+ padding: 4rem 0;
+ background: #0a0a0a;
+ color: #f5f1e8;
+ }
+ .features-section h2, .features-section h3, .features-section p, .features-section li, .features-section .section-subtitle { color: #f5f1e8; }
+ .features-section .section-subtitle { color: rgba(245, 241, 232, 0.6); }
+
+ .features-section h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 3rem;
+ color: #222;
+ }
+
+ .features-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 2rem;
+ }
+
+ .feature-card {
+ padding: 2rem;
+ background: #f8f8f8;
+ border-radius: 10px;
+ text-align: center;
+ transition: transform 0.3s;
+ }
+
+ .feature-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 5px 20px rgba(0,0,0,0.1);
+ }
+
+ .feature-card h3 {
+ margin-bottom: 1rem;
+ color: #333;
+ font-size: 1.3rem;
+ }
+
+ .feature-card p {
+ color: #666;
+ line-height: 1.6;
+ }
+
+ /* Installation Section */
+ .installation-section {
+ padding: 4rem 0;
+ background: #0a0a0a;
+ color: #f5f1e8;
+ }
+ .installation-section h2, .installation-section h3, .installation-section p, .installation-section li, .installation-section .section-subtitle { color: #f5f1e8; }
+ .installation-section .section-subtitle { color: rgba(245, 241, 232, 0.6); }
+
+ .installation-section h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ color: #f5f1e8;
+ }
+
+ .info-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 2rem;
+ margin-bottom: 3rem;
+ }
+
+ .info-card-link {
+ text-decoration: none;
+ display: block;
+ transition: transform 0.3s;
+ }
+
+ .info-card-link:hover {
+ transform: translateY(-5px);
+ }
+
+ .info-card {
+ background: white;
+ padding: 2rem;
+ border-radius: 10px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ transition: box-shadow 0.3s, border-color 0.3s;
+ border: 2px solid transparent;
+ height: 100%;
+ cursor: pointer;
+ }
+
+ .info-card:hover {
+ box-shadow: 0 5px 20px rgba(0,0,0,0.15);
+ border-color: #DDAB1E;
+ }
+
+ .info-card h3 {
+ margin-bottom: 1rem;
+ color: #333;
+ font-size: 1.2rem;
+ }
+
+ .info-card p {
+ color: #666;
+ line-height: 1.6;
+ margin-bottom: 1rem;
+ }
+
+ .card-arrow {
+ color: #DDAB1E;
+ font-weight: 600;
+ display: inline-block;
+ transition: margin-left 0.3s;
+ }
+
+ .info-card:hover .card-arrow {
+ margin-left: 5px;
+ }
+
+ .care-highlight {
+ background: white;
+ padding: 2.5rem;
+ border-radius: 10px;
+ max-width: 800px;
+ margin: 0 auto;
+ text-align: center;
+ border: 2px solid #DDAB1E;
+ }
+
+ .care-highlight h3 {
+ color: #333;
+ margin-bottom: 1rem;
+ font-size: 1.5rem;
+ }
+
+ .care-highlight p {
+ color: #666;
+ line-height: 1.8;
+ }
+
+ /* Footer */
+ footer {
+ background: #0a0a0a;
+ color: #f5f1e8;
+ padding: 3rem 0 1rem;
+ border-top: 1px solid rgba(245, 241, 232, 0.08);
+ }
+
+ .footer-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 2rem;
+ margin-bottom: 2rem;
+ }
+
+ .footer-section h3 {
+ margin-bottom: 1rem;
+ color: #DDAB1E;
+ }
+
+ .footer-section p {
+ opacity: 0.9;
+ margin-bottom: 0.5rem;
+ line-height: 1.6;
+ }
+
+ .footer-section ul {
+ list-style: none;
+ }
+
+ .footer-section ul li {
+ margin-bottom: 0.5rem;
+ }
+
+ .footer-section ul li a {
+ color: white;
+ text-decoration: none;
+ opacity: 0.9;
+ transition: opacity 0.3s;
+ }
+
+ .footer-section ul li a:hover {
+ opacity: 1;
+ color: #DDAB1E;
+ }
+
+ .authorized {
+ background: rgba(221, 171, 30, 0.2);
+ padding: 0.5rem;
+ border-radius: 5px;
+ font-size: 0.9rem;
+ }
+
+ .footer-sample-btn {
+ background: #DDAB1E;
+ color: white;
+ border: none;
+ padding: 0.75rem 1.5rem;
+ font-size: 1rem;
+ border-radius: 5px;
+ cursor: pointer;
+ margin-top: 1rem;
+ transition: background 0.3s;
+ }
+
+ .footer-sample-btn:hover {
+ background: #c4981a;
+ }
+
+ .footer-bottom {
+ text-align: center;
+ padding-top: 2rem;
+ border-top: 1px solid rgba(255,255,255,0.1);
+ opacity: 0.7;
+ }
+
+ /* Mobile Styles */
+ @media (max-width: 768px) {
+ .mobile-toggle {
+ display: flex;
+ }
+
+ .nav-menu {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ width: 100%;
+ background: white;
+ flex-direction: column;
+ padding: 1rem;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+ }
+
+ .nav-menu.active {
+ display: flex;
+ }
+
+ .hero h2 {
+ font-size: 2rem;
+ }
+
+ .colors-grid {
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+ gap: 1rem;
+ }
+
+ .color-swatch {
+ height: 80px;
+ }
+
+ .modal-content {
+ margin: 20px;
+ width: calc(100% - 40px);
+ }
+
+ .color-preview img {
+ width: 150px;
+ height: 150px;
+ }
+
+ .specs-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .features-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .info-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .footer-content {
+ grid-template-columns: 1fr;
+ text-align: center;
+ }
+
+ .authorized-dealer {
+ margin-top: 1.5rem;
+ padding: 0.75rem;
+ }
+
+ .care-highlight {
+ padding: 1.5rem;
+ }
+
+ .specifications-section,
+ .features-section,
+ .installation-section {
+ padding: 2rem 0;
+ }
+
+ .specifications-section h2,
+ .features-section h2,
+ .installation-section h2 {
+ font-size: 2rem;
+ }
+ }
+
+ /* ─── Unified dark-theme overrides — keeps every section on #0a0a0a ─── */
+ .color-search input,
+ #colorSearch {
+ background: rgba(245, 241, 232, 0.04) !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ color: #f5f1e8 !important;
+ padding: 12px 16px;
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-size: 13px;
+ letter-spacing: 0.04em;
+ border-radius: 4px;
+ width: 100%;
+ max-width: 460px;
+ }
+ .color-search input::placeholder, #colorSearch::placeholder { color: rgba(245, 241, 232, 0.4); }
+ .color-search input:focus, #colorSearch:focus { outline: none; border-color: rgba(245, 241, 232, 0.45) !important; }
+ .color-search { display: flex; justify-content: center; margin-bottom: 1.4rem; }
+
+ .color-filter-btn {
+ background: transparent;
+ color: rgba(245, 241, 232, 0.75);
+ border: 1px solid rgba(245, 241, 232, 0.22);
+ padding: 9px 18px;
+ font-size: 11px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ border-radius: 4px;
+ cursor: pointer;
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-weight: 400;
+ transition: all 220ms ease;
+ }
+ .color-filter-btn:hover { border-color: rgba(245, 241, 232, 0.7); color: #fff; background: transparent; }
+ .color-filter-btn.active {
+ background: #f5f1e8 !important;
+ border-color: #f5f1e8 !important;
+ color: #1a1a1a !important;
+ }
+ .color-filters {
+ display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
+ justify-content: center;
+ padding: 0 24px;
+ margin-bottom: 1.5rem;
+ }
+ .color-count { color: rgba(245, 241, 232, 0.55); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; margin-left: 8px; }
+
+ .ns-ctrl { color: rgba(245, 241, 232, 0.55); font-size: 10px; letter-spacing: 0.22em; }
+ .ns-ctrl select {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ }
+ .ns-ctrl select:focus { border-color: rgba(245, 241, 232, 0.45) !important; }
+ .ns-ctrl input[type="range"] { accent-color: #f5f1e8; }
+ .ns-ctrl #nsColsVal { color: #f5f1e8 !important; }
+
+ /* Footer dark-theme touch-up */
+ footer .footer-section h3 { color: #f5f1e8 !important; font-weight: 400; letter-spacing: 0.2em; text-transform: uppercase; font-size: 11px; }
+ footer .footer-section p, footer .footer-section a, footer ul a { color: rgba(245, 241, 232, 0.65) !important; text-decoration: none; }
+ footer .footer-section a:hover, footer ul a:hover { color: #fff !important; }
+ footer .footer-bottom { color: rgba(245, 241, 232, 0.4); border-top: 1px solid rgba(245, 241, 232, 0.08); margin-top: 2rem; padding-top: 1.5rem; }
+
+ /* New-arrivals heading on the unified bg — keep gold accent but trim the gradient feel */
+ .new-arrivals-section h2 { color: #f5f1e8 !important; }
+ .new-arrivals-section .new-arrivals-pill { background: #f5f1e8; color: #0a0a0a; }
+ .new-arrivals-section .section-subtitle { color: rgba(245, 241, 232, 0.6); }
+
+ /* Modal still light-on-dark by design — keep its own contrast island */
+ .modal-content { background: #fff; color: #1a1a1a; }
+
+ /* Internal cards in sections — flip from white islands to dark cards on dark bg */
+ .spec-row {
+ background: rgba(245, 241, 232, 0.04) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.10);
+ }
+ .feature-card {
+ background: rgba(245, 241, 232, 0.04) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.10);
+ }
+ .feature-card h3, .feature-card p { color: #f5f1e8 !important; }
+ .info-card {
+ background: rgba(245, 241, 232, 0.04) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.10);
+ }
+ .info-card h3, .info-card p, .info-card li { color: #f5f1e8 !important; }
+
+ /* ──────────────────────────────────────────────────────────────────
+ NOVASUEDE-UX UPGRADE 2026-05-08
+ Right-side hamburger · Dark/Light theme · Live product grid w/ tabs
+ Sort + density slider · Product detail modal · Footer parity
+ ────────────────────────────────────────────────────────────────── */
+
+ /* CSS theme variables — flipped by [data-theme="light"] */
+ :root {
+ --bg: #0a0a0a;
+ --bg-alt: #111;
+ --surface: rgba(245, 241, 232, 0.04);
+ --surface-hover: rgba(245, 241, 232, 0.08);
+ --surface-strong: rgba(245, 241, 232, 0.10);
+ --text: #f5f1e8;
+ --text-mute: rgba(245, 241, 232, 0.65);
+ --text-faint: rgba(245, 241, 232, 0.45);
+ --line: rgba(245, 241, 232, 0.10);
+ --line-strong: rgba(245, 241, 232, 0.22);
+ --accent: #DDAB1E;
+ --accent-soft: #b3870e;
+ --header-bg: rgba(10, 10, 10, 0.78);
+ }
+ html[data-theme="light"] {
+ --bg: #f5f1e8;
+ --bg-alt: #fff;
+ --surface: rgba(26, 26, 26, 0.04);
+ --surface-hover: rgba(26, 26, 26, 0.08);
+ --surface-strong: rgba(26, 26, 26, 0.12);
+ --text: #1a1a1a;
+ --text-mute: rgba(26, 26, 26, 0.70);
+ --text-faint: rgba(26, 26, 26, 0.45);
+ --line: rgba(26, 26, 26, 0.12);
+ --line-strong: rgba(26, 26, 26, 0.28);
+ --header-bg: rgba(245, 241, 232, 0.85);
+ }
+
+ /* Hide the cross-fleet DW nav widget on this page — our LA Fabrics
+ hamburger lives in the same top-right slot and opens an
+ LA-Fabrics-specific side panel. */
+ #dw-fn-trigger, #dw-fn-overlay, #dw-fn-drawer { display: none !important; }
+
+ /* ─── Right-side hamburger button (fixed top-right) ─── */
+ .laf-hamburger {
+ position: fixed;
+ top: 14px;
+ right: 16px;
+ z-index: 1500;
+ width: 46px;
+ height: 46px;
+ background: rgba(10, 10, 10, 0.78);
+ border: 1px solid rgba(245, 241, 232, 0.30);
+ border-radius: 50%;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ transition: background 220ms ease, transform 220ms ease;
+ }
+ .laf-hamburger:hover { background: rgba(221, 171, 30, 0.20); transform: scale(1.05); }
+ .laf-hamburger:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
+ .laf-hamburger span {
+ display: block;
+ width: 20px;
+ height: 2px;
+ background: #f5f1e8;
+ border-radius: 2px;
+ transition: transform 250ms ease, opacity 200ms ease;
+ }
+ .laf-hamburger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
+ .laf-hamburger.open span:nth-child(2) { opacity: 0; }
+ .laf-hamburger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
+
+ /* Dark/light toggle — pinned just below the hamburger */
+ .laf-theme-toggle {
+ position: fixed;
+ top: 68px;
+ right: 16px;
+ z-index: 1500;
+ width: 38px;
+ height: 38px;
+ background: rgba(10, 10, 10, 0.78);
+ border: 1px solid rgba(245, 241, 232, 0.30);
+ border-radius: 50%;
+ cursor: pointer;
+ color: #f5f1e8;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 15px;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ transition: background 220ms ease, transform 220ms ease;
+ }
+ .laf-theme-toggle:hover { background: rgba(221, 171, 30, 0.20); transform: scale(1.05); }
+
+ /* ─── Side-panel (slides in from the right) ─── */
+ .laf-side-panel {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 320px;
+ max-width: 86vw;
+ background: linear-gradient(180deg, #0d0d0d 0%, #1a1a1a 100%);
+ color: #f5f1e8;
+ box-shadow: -10px 0 40px rgba(0, 0, 0, 0.55);
+ transform: translateX(100%);
+ transition: transform 320ms cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 1400;
+ overflow-y: auto;
+ padding: 5rem 1.75rem 2rem;
+ }
+ .laf-side-panel.open { transform: translateX(0); }
+ .laf-side-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.55);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 320ms ease;
+ z-index: 1300;
+ }
+ .laf-side-overlay.open { opacity: 1; pointer-events: auto; }
+ .laf-side-panel h3 {
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-weight: 300;
+ font-size: 11px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: rgba(245, 241, 232, 0.55);
+ margin-bottom: 1rem;
+ padding-bottom: 0.6rem;
+ border-bottom: 1px solid rgba(245, 241, 232, 0.10);
+ }
+ .laf-side-panel ul {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 2rem;
+ }
+ .laf-side-panel ul li { margin-bottom: 0.25rem; }
+ .laf-side-panel ul li a {
+ display: block;
+ padding: 0.85rem 0.5rem;
+ color: #f5f1e8;
+ text-decoration: none;
+ font-size: 15px;
+ font-weight: 300;
+ letter-spacing: 0.04em;
+ border-bottom: 1px solid rgba(245, 241, 232, 0.06);
+ transition: color 200ms ease, background 200ms ease, padding-left 200ms ease;
+ }
+ .laf-side-panel ul li a:hover {
+ color: #DDAB1E;
+ background: rgba(221, 171, 30, 0.06);
+ padding-left: 1rem;
+ }
+ .laf-side-panel .side-mail {
+ display: block;
+ margin-top: 1.25rem;
+ padding: 0.75rem 1rem;
+ background: rgba(221, 171, 30, 0.10);
+ border: 1px solid rgba(221, 171, 30, 0.40);
+ color: #DDAB1E;
+ text-align: center;
+ text-decoration: none;
+ border-radius: 4px;
+ font-size: 13px;
+ letter-spacing: 0.06em;
+ transition: background 200ms ease;
+ }
+ .laf-side-panel .side-mail:hover {
+ background: rgba(221, 171, 30, 0.20);
+ color: #fff;
+ }
+ .laf-side-panel .side-fineprint {
+ margin-top: 1.5rem;
+ font-size: 11px;
+ color: rgba(245, 241, 232, 0.45);
+ line-height: 1.5;
+ }
+
+ @media (max-width: 720px) {
+ .laf-side-panel { width: 88vw; }
+ }
+
+ /* ─── Live product grid section ─── */
+ .laf-products-section {
+ padding: 4rem 0 5rem;
+ background: var(--bg);
+ color: var(--text);
+ }
+ .laf-products-section h2 {
+ text-align: center;
+ font-size: clamp(28px, 4vw, 44px);
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-weight: 200;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--text);
+ margin-bottom: 0.5rem;
+ }
+ .laf-products-section .section-subtitle {
+ text-align: center;
+ color: var(--text-mute);
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-style: italic;
+ font-size: 18px;
+ margin-bottom: 2.5rem;
+ }
+
+ /* Tab strip — horizontal, scrollable on mobile */
+ .laf-tabs {
+ display: flex;
+ gap: 0.4rem;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin-bottom: 1.5rem;
+ padding: 0 0.5rem;
+ }
+ .laf-tab {
+ background: transparent;
+ color: var(--text-mute);
+ border: 1px solid var(--line);
+ padding: 0.55rem 1.15rem;
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-size: 11px;
+ font-weight: 400;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ cursor: pointer;
+ border-radius: 999px;
+ transition: background 200ms ease, color 200ms ease, border-color 200ms ease;
+ white-space: nowrap;
+ }
+ .laf-tab:hover {
+ color: var(--text);
+ border-color: var(--line-strong);
+ }
+ .laf-tab.active {
+ background: var(--accent);
+ color: #1a1a1a;
+ border-color: var(--accent);
+ }
+ .laf-tab .tab-count {
+ margin-left: 0.5rem;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ font-size: 10px;
+ opacity: 0.75;
+ letter-spacing: 0;
+ }
+
+ /* Toolbar: search + sort + density */
+ .laf-toolbar {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin-bottom: 1.75rem;
+ padding: 0 1rem;
+ }
+ .laf-toolbar .toolbar-search {
+ flex: 1 1 280px;
+ max-width: 360px;
+ min-width: 200px;
+ background: var(--surface);
+ color: var(--text);
+ border: 1px solid var(--line);
+ padding: 0.6rem 0.9rem;
+ font-size: 14px;
+ font-family: inherit;
+ border-radius: 999px;
+ transition: border-color 200ms ease, background 200ms ease;
+ }
+ .laf-toolbar .toolbar-search::placeholder { color: var(--text-faint); }
+ .laf-toolbar .toolbar-search:focus {
+ outline: none;
+ border-color: var(--accent);
+ background: var(--surface-hover);
+ }
+ .laf-ctrl {
+ display: inline-flex;
+ gap: 0.5rem;
+ align-items: center;
+ font-size: 10px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--text-faint);
+ font-family: -apple-system, Arial, sans-serif;
+ }
+ .laf-ctrl select {
+ background: var(--surface);
+ color: var(--text);
+ border: 1px solid var(--line);
+ padding: 0.45rem 0.7rem;
+ font-size: 12px;
+ text-transform: none;
+ letter-spacing: 0;
+ border-radius: 4px;
+ cursor: pointer;
+ font-family: inherit;
+ }
+ .laf-ctrl select:focus { outline: none; border-color: var(--accent); }
+ .laf-ctrl input[type="range"] { width: 100px; accent-color: var(--accent); cursor: pointer; }
+ .laf-ctrl .density-val {
+ min-width: 1.5em;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ color: var(--accent);
+ text-align: right;
+ font-size: 12px;
+ letter-spacing: 0;
+ }
+ .laf-result-count {
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ font-size: 11px;
+ color: var(--text-faint);
+ letter-spacing: 0.04em;
+ }
+
+ /* Product grid + cards */
+ .laf-product-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(var(--card-min, 240px), 1fr));
+ gap: 1.25rem;
+ padding: 0 1rem;
+ }
+ @media (max-width: 720px) {
+ .laf-product-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)) !important; gap: 0.75rem; }
+ }
+ .laf-card {
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ overflow: hidden;
+ cursor: pointer;
+ transition: transform 280ms ease, border-color 280ms ease, box-shadow 280ms ease;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ }
+ .laf-card:hover {
+ transform: translateY(-3px);
+ border-color: var(--accent);
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
+ }
+ .laf-card-image {
+ width: 100%;
+ aspect-ratio: 4 / 3;
+ background: #1a1a1a;
+ position: relative;
+ overflow: hidden;
+ }
+ .laf-card-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 320ms ease;
+ opacity: 0;
+ }
+ .laf-card-image img.loaded { opacity: 1; }
+ .laf-card:hover .laf-card-image img { transform: scale(1.04); }
+ .laf-card-body {
+ padding: 0.85rem 0.95rem 0.95rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ flex: 1;
+ }
+ .laf-card-title {
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-size: 13px;
+ font-weight: 400;
+ color: var(--text);
+ letter-spacing: 0.02em;
+ line-height: 1.3;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+ .laf-card-meta {
+ font-size: 10px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--text-faint);
+ margin-top: 0.25rem;
+ }
+ .laf-card-color {
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-style: italic;
+ font-size: 14px;
+ color: var(--accent);
+ }
+ .laf-card-price {
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ font-size: 11px;
+ color: var(--text-mute);
+ margin-top: 0.4rem;
+ letter-spacing: 0.04em;
+ }
+ .laf-card-badge {
+ position: absolute;
+ top: 8px;
+ left: 8px;
+ background: rgba(0, 0, 0, 0.65);
+ color: #f5f1e8;
+ font-size: 9px;
+ font-weight: 500;
+ letter-spacing: 0.20em;
+ padding: 3px 8px;
+ border-radius: 999px;
+ text-transform: uppercase;
+ z-index: 2;
+ }
+ .laf-load-more {
+ display: block;
+ margin: 2rem auto 0;
+ padding: 0.85rem 2.4rem;
+ background: transparent;
+ color: var(--text);
+ border: 1px solid var(--line-strong);
+ font-family: -apple-system, Arial, sans-serif;
+ font-size: 11px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ border-radius: 999px;
+ cursor: pointer;
+ transition: background 220ms ease, color 220ms ease, border-color 220ms ease;
+ }
+ .laf-load-more:hover {
+ background: var(--text);
+ color: var(--bg);
+ border-color: var(--text);
+ }
+ .laf-grid-empty {
+ text-align: center;
+ padding: 3rem 1rem;
+ color: var(--text-faint);
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-style: italic;
+ font-size: 18px;
+ }
+
+ /* Product detail modal — opens on card click */
+ .laf-product-modal {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 2200;
+ background: rgba(0, 0, 0, 0.80);
+ backdrop-filter: blur(6px);
+ -webkit-backdrop-filter: blur(6px);
+ overflow-y: auto;
+ padding: 2rem 1rem;
+ }
+ .laf-product-modal.open { display: block; }
+ .laf-product-modal-inner {
+ background: var(--bg-alt);
+ color: var(--text);
+ max-width: 980px;
+ margin: 0 auto;
+ border-radius: 12px;
+ overflow: hidden;
+ display: grid;
+ grid-template-columns: 1.1fr 1fr;
+ min-height: 480px;
+ border: 1px solid var(--line);
+ }
+ @media (max-width: 720px) {
+ .laf-product-modal-inner { grid-template-columns: 1fr; }
+ }
+ .laf-product-modal-close {
+ position: absolute;
+ top: 1.4rem;
+ right: 1.4rem;
+ width: 36px;
+ height: 36px;
+ background: rgba(0,0,0,0.7);
+ color: #f5f1e8;
+ border: 1px solid rgba(245, 241, 232, 0.30);
+ border-radius: 50%;
+ cursor: pointer;
+ font-size: 18px;
+ line-height: 1;
+ z-index: 2300;
+ }
+ .laf-product-modal-close:hover { background: var(--accent); color: #1a1a1a; }
+ .laf-product-images {
+ background: #0a0a0a;
+ display: flex;
+ flex-direction: column;
+ }
+ .laf-product-hero {
+ width: 100%;
+ aspect-ratio: 1 / 1;
+ background: #1a1a1a;
+ }
+ .laf-product-hero img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ }
+ .laf-product-thumbs {
+ display: flex;
+ gap: 0.4rem;
+ padding: 0.6rem;
+ overflow-x: auto;
+ }
+ .laf-product-thumbs img {
+ width: 64px;
+ height: 64px;
+ object-fit: cover;
+ cursor: pointer;
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ transition: border-color 200ms ease, transform 200ms ease;
+ }
+ .laf-product-thumbs img:hover,
+ .laf-product-thumbs img.active {
+ border-color: var(--accent);
+ transform: scale(1.04);
+ }
+ .laf-product-info {
+ padding: 2rem 1.75rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.85rem;
+ overflow-y: auto;
+ }
+ .laf-product-info .pi-eyebrow {
+ font-family: -apple-system, Arial, sans-serif;
+ font-size: 10px;
+ letter-spacing: 0.32em;
+ text-transform: uppercase;
+ color: var(--text-faint);
+ }
+ .laf-product-info h2 {
+ font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
+ font-weight: 200;
+ font-size: clamp(22px, 2.6vw, 30px);
+ color: var(--text);
+ letter-spacing: 0.04em;
+ margin: 0;
+ line-height: 1.2;
+ }
+ .laf-product-info .pi-color {
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-style: italic;
+ font-size: 22px;
+ color: var(--accent);
+ }
+ .laf-product-info .pi-price {
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
+ font-size: 13px;
+ color: var(--text-mute);
+ letter-spacing: 0.06em;
+ }
+ .laf-product-info .pi-desc {
+ color: var(--text-mute);
+ line-height: 1.7;
+ font-size: 14px;
+ margin-top: 0.5rem;
+ }
+ .laf-product-info .pi-desc p,
+ .laf-product-info .pi-desc div { margin-bottom: 0.6rem; }
+ .laf-product-info .pi-specs {
+ margin-top: 0.75rem;
+ border-top: 1px solid var(--line);
+ padding-top: 0.85rem;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 0.45rem;
+ }
+ .laf-product-info .pi-spec-row {
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ font-size: 12px;
+ padding: 0.4rem 0;
+ border-bottom: 1px solid var(--line);
+ }
+ .laf-product-info .pi-spec-label {
+ font-size: 10px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--text-faint);
+ }
+ .laf-product-info .pi-spec-value {
+ color: var(--text);
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-style: italic;
+ font-size: 14px;
+ text-align: right;
+ }
+ .laf-product-info .pi-cta {
+ margin-top: 1rem;
+ display: flex;
+ gap: 0.6rem;
+ flex-wrap: wrap;
+ }
+ .laf-product-info .pi-cta button {
+ background: var(--accent);
+ color: #1a1a1a;
+ border: 1px solid var(--accent);
+ padding: 0.85rem 1.6rem;
+ font-family: -apple-system, Arial, sans-serif;
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ border-radius: 0;
+ cursor: pointer;
+ transition: background 200ms ease, color 200ms ease;
+ }
+ .laf-product-info .pi-cta button:hover {
+ background: var(--text);
+ color: var(--bg);
+ border-color: var(--text);
+ }
+ .laf-product-info .pi-cta .pi-secondary {
+ background: transparent;
+ color: var(--text);
+ border-color: var(--line-strong);
+ }
+ .laf-product-info .pi-cta .pi-secondary:hover {
+ background: var(--text);
+ color: var(--bg);
+ }
+ .laf-product-info .pi-tags {
+ margin-top: 0.85rem;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ }
+ .laf-product-info .pi-tag {
+ background: var(--surface);
+ border: 1px solid var(--line);
+ padding: 0.25rem 0.7rem;
+ font-size: 10px;
+ letter-spacing: 0.10em;
+ text-transform: uppercase;
+ color: var(--text-mute);
+ border-radius: 999px;
+ }
+
+ /* Footer enhancements (info@ mailto + DW office address + sister-site) */
+ .laf-footer-extra {
+ display: flex;
+ flex-direction: column;
+ gap: 0.45rem;
+ margin-top: 0.9rem;
+ font-size: 12px;
+ color: rgba(245, 241, 232, 0.65);
+ }
+ .laf-footer-extra a { color: rgba(245, 241, 232, 0.85); text-decoration: none; }
+ .laf-footer-extra a:hover { color: #DDAB1E; }
+ .laf-footer-address {
+ font-style: italic;
+ font-family: 'Cormorant Garamond', Georgia, serif;
+ font-size: 14px;
+ color: rgba(245, 241, 232, 0.72);
+ }
+
+ </style>
+
+ <!-- Canonical UX: canonical + social meta + JSON-LD (added 2026-05-08) -->
+ <link rel="canonical" href="https://losangelesfabrics.com/">
+ <meta property="og:type" content="website">
+ <meta property="og:site_name" content="Los Angeles Fabrics">
+ <meta property="og:title" content="Los Angeles Fabrics — Commercial Fabric & Drapery">
+ <meta property="og:description" content="Los Angeles Fabrics — commercial-grade fabrics and drapery for designers, architects, and contract installations. Free memo samples; trade service direct from the mill.">
+ <meta property="og:url" content="https://losangelesfabrics.com/">
+ <meta property="og:image" content="https://losangelesfabrics.com/images/favicon.jpg">
+ <meta name="twitter:card" content="summary_large_image">
+ <meta name="twitter:title" content="Los Angeles Fabrics — Commercial Fabric & Drapery">
+ <meta name="twitter:description" content="Los Angeles Fabrics — commercial-grade fabrics and drapery for designers, architects, and contract installations. Free memo samples; trade service direct from the mill.">
+ <meta name="twitter:image" content="https://losangelesfabrics.com/images/favicon.jpg">
+
+ <script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "Los Angeles Fabrics",
+ "alternateName": "Los Angeles Fabrics™",
+ "url": "https://losangelesfabrics.com/",
+ "logo": "https://losangelesfabrics.com/images/favicon.jpg",
+ "email": "info@losangelesfabrics.com",
+ "parentOrganization": {
+ "@type": "Organization",
+ "name": "Designer Wallcoverings",
+ "url": "https://designerwallcoverings.com/"
+ },
+ "address": {
+ "@type": "PostalAddress",
+ "streetAddress": "15442 Ventura Bl #102",
+ "addressLocality": "Sherman Oaks",
+ "addressRegion": "CA",
+ "postalCode": "91403",
+ "addressCountry": "US"
+ }
+}
+ </script>
+ <script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "WebSite",
+ "name": "Los Angeles Fabrics",
+ "url": "https://losangelesfabrics.com/",
+ "publisher": {
+ "@type": "Organization",
+ "name": "Designer Wallcoverings"
+ },
+ "inLanguage": "en-US"
+}
+ </script>
+ <script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Product",
+ "name": "Los Angeles Fabrics™ Commercial Fabric & Drapery",
+ "description": "Commercial-grade specialty fabrics and draperies for high-traffic offices, hotels, healthcare, and high-end residential. Free memo samples; trade-direct pricing.",
+ "brand": {
+ "@type": "Brand",
+ "name": "Los Angeles Fabrics"
+ },
+ "manufacturer": {
+ "@type": "Organization",
+ "name": "Designer Wallcoverings"
+ },
+ "category": "Commercial Fabric, Drapery",
+ "url": "https://losangelesfabrics.com/",
+ "image": "https://losangelesfabrics.com/images/favicon.jpg"
+}
+ </script>
+</head>
+<body>
+ <!-- Top contact-bar removed 2026-05-07 per Steve -->
+
+ <!-- Right-side hamburger (always visible, fixed top-right) -->
+ <button class="laf-hamburger" id="lafHamburger" aria-label="Open menu" aria-expanded="false" aria-controls="lafSidePanel" onclick="lafToggleSide()">
+ <span></span>
+ <span></span>
+ <span></span>
+ </button>
+ <button class="laf-theme-toggle" id="lafThemeToggle" aria-label="Toggle dark / light theme" onclick="lafToggleTheme()" title="Toggle dark / light">
+ <span id="lafThemeIcon">☾</span>
+ </button>
+
+ <!-- Slide-in side panel (right edge) -->
+ <div class="laf-side-overlay" id="lafSideOverlay" onclick="lafCloseSide()"></div>
+ <aside class="laf-side-panel" id="lafSidePanel" aria-hidden="true">
+ <h3>Navigation</h3>
+ <ul>
+ <li><a href="#home" onclick="lafCloseSide()">Home</a></li>
+ <li><a href="#products" onclick="lafCloseSide()">Live Catalog</a></li>
+ <li><a href="#colors" onclick="lafCloseSide()">Color Collection</a></li>
+ <li><a href="#specifications" onclick="lafCloseSide()">Specifications</a></li>
+ <li><a href="#features" onclick="lafCloseSide()">Features</a></li>
+ <li><a href="#installation" onclick="lafCloseSide()">Installation & Care</a></li>
+ </ul>
+
+ <h3>Resources</h3>
+ <ul>
+ <li><a href="faq.html">FAQ</a></li>
+ <li><a href="fire-ratings.html">Fire Ratings</a></li>
+ <li><a href="installation.html">Installation Guide</a></li>
+ <li><a href="cleaning.html">Cleaning & Care</a></li>
+ <li><a href="cutting.html">Cutting Guidelines</a></li>
+ </ul>
+
+ <h3>Contact</h3>
+ <ul>
+ <li><a href="#" onclick="lafCloseSide();openModal();return false;">Request a Sample</a></li>
+ </ul>
+ <a class="side-mail" href="mailto:info@losangelesfabrics.com">info@losangelesfabrics.com</a>
+
+ <p class="side-fineprint">
+ Los Angeles Fabrics is a Designer Wallcoverings sub-brand.
+ Trade-direct commercial fabric & drapery.<br>
+ Sister site: <a href="https://designerwallcoverings.com" style="color:#DDAB1E">designerwallcoverings.com</a>
+ </p>
+ </aside>
+
+
+ <header>
+ <nav class="navbar">
+ <div class="nav-container">
+ <div class="logo">
+ <h1>Los Angeles Fabrics™</h1>
+ </div>
+ <button class="mobile-toggle" id="mobileToggle" onclick="toggleMobileMenu()">
+ <span></span>
+ <span></span>
+ <span></span>
+ </button>
+ <ul class="nav-menu" id="navMenu">
+ <li><a href="#home">Home</a></li>
+ <li><a href="#colors">Colors</a></li>
+ <li><a href="#specifications">Specifications</a></li>
+ <li><a href="#features">Features</a></li>
+ <li><a href="fire-ratings.html">Fire Ratings</a></li>
+ </ul>
+ </div>
+ </nav>
+ </header>
+
+ <section id="home" class="hero">
+ <!-- Rotating suede — 5 dark/jewel tones, gucci-house aesthetic. -->
+ <div class="hero-bg on" style="background-image:url('images/colors/losangelesfabrics-black.jpg')"></div>
+ <div class="hero-bg" style="background-image:url('images/colors/losangelesfabrics-carbon.jpg')"></div>
+ <div class="hero-bg" style="background-image:url('images/colors/losangelesfabrics-aubergine.jpg')"></div>
+ <div class="hero-bg" style="background-image:url('images/colors/losangelesfabrics-navy.jpg')"></div>
+ <div class="hero-bg" style="background-image:url('images/colors/losangelesfabrics-ruby.jpg')"></div>
+ <div class="hero-content">
+ <a href="#colors" class="ns-wordmark-link" onclick="event.preventDefault();document.getElementById("colors").scrollIntoView({behavior:"smooth"});" aria-label="Enter Los Angeles Fabrics">
+ <h1 class="ns-wordmark">LOSANGELESFABRICS<sup>™</sup></h1>
+ </a>
+ <h2>Commercial Fabric & Drapery, Trade-Direct</h2>
+ <p>Los Angeles Fabrics is the commercial-grade specialty of the Designer Wallcoverings family — fabrics and draperies specified for high-traffic offices, hotels, healthcare, and high-end residential. Free memo samples ship same-day; trade pricing applies to every order.</p>
+ <button class="sample-btn" onclick="openModal()">Request Sample</button>
+ <div class="authorized-dealer">
+ <p>DesignerWallcoverings.com is an authorized dealer of Los Angeles Fabrics™ products</p>
+ </div>
+ </div>
+ </section>
+
+ <section id="new-arrivals" class="new-arrivals-section">
+ <div class="container">
+ <div class="new-arrivals-header">
+ <h2>New Arrivals — 2026</h2>
+ <span class="new-arrivals-pill">36 fresh colors</span>
+ </div>
+ <p class="section-subtitle">Brand-new shades just released. Click <strong>Drapery</strong> to dress the page in any of them, or <strong>Sample</strong> to request a free swatch.</p>
+ <div class="colors-grid" id="newArrivalsGrid"></div>
+ </div>
+ </section>
+
+ <!-- ───────────────────────────────────────────────────────────────
+ Live Catalog: 1,634 DWPR Commercial Fabric SKUs from Shopify.
+ Tabs filter by use (Drapery, Upholstery, Sheer, Outdoor, Fire Rated)
+ and by named Collection (Le Cirque, Monument, Hollywood Babylon, …).
+ Sort + density slider + search above the grid (per CLAUDE.md
+ standing rule). Cards open a Novasuede-style detail modal.
+ ─────────────────────────────────────────────────────────────── -->
+ <section id="products" class="laf-products-section">
+ <div class="container">
+ <h2>Commercial Fabric — Live Catalog</h2>
+ <p class="section-subtitle">Every active Los Angeles Fabrics SKU, mill-direct. Browse, filter, and request memo samples.</p>
+
+ <div class="laf-tabs" id="lafTabs" role="tablist" aria-label="Filter products by category">
+ <!-- tabs injected by JS so counts stay live -->
+ </div>
+
+ <div class="laf-toolbar">
+ <input type="search" id="lafSearch" class="toolbar-search" placeholder="Search by pattern, color, SKU, tag…" aria-label="Search products">
+ <label class="laf-ctrl">Sort
+ <select id="lafSort" aria-label="Sort products">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku-asc">SKU A→Z</option>
+ <option value="title-asc">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ </label>
+ <label class="laf-ctrl">Density
+ <input type="range" id="lafDensity" min="160" max="360" step="20" value="240" aria-label="Card size (density)">
+ <span class="density-val" id="lafDensityVal">240</span>
+ </label>
+ <span class="laf-result-count" id="lafResultCount">loading…</span>
+ </div>
+
+ <div class="laf-product-grid" id="lafProductGrid" aria-live="polite">
+ <!-- cards injected by JS -->
+ </div>
+ <div class="laf-grid-empty" id="lafGridEmpty" style="display:none">
+ No fabrics match your filters. Try clearing the search or switching tabs.
+ </div>
+ <button class="laf-load-more" id="lafLoadMore" type="button" style="display:none">Load more</button>
+ </div>
+ </section>
+
+ <!-- Product detail modal (Novasuede-style). Opened on card click. -->
+ <div class="laf-product-modal" id="lafProductModal" role="dialog" aria-modal="true" aria-labelledby="lafProductTitle" onclick="if(event.target===this)lafCloseProduct()">
+ <button class="laf-product-modal-close" type="button" aria-label="Close" onclick="lafCloseProduct()">×</button>
+ <div class="laf-product-modal-inner">
+ <div class="laf-product-images">
+ <div class="laf-product-hero">
+ <img id="lafProductHeroImg" src="" alt="">
+ </div>
+ <div class="laf-product-thumbs" id="lafProductThumbs"></div>
+ </div>
+ <div class="laf-product-info">
+ <span class="pi-eyebrow" id="lafProductEyebrow">Los Angeles Fabrics</span>
+ <h2 id="lafProductTitle"></h2>
+ <span class="pi-color" id="lafProductColor"></span>
+ <span class="pi-price" id="lafProductPrice"></span>
+ <div class="pi-desc" id="lafProductDesc"></div>
+ <div class="pi-specs" id="lafProductSpecs"></div>
+ <div class="pi-cta">
+ <button type="button" onclick="lafRequestSampleFromProduct()">Request Sample</button>
+ <button type="button" class="pi-secondary" id="lafProductShopifyLink" onclick="lafOpenShopifyLink()">View Full Listing</button>
+ </div>
+ <div class="pi-tags" id="lafProductTags"></div>
+ </div>
+ </div>
+ </div>
+
+ <section id="colors" class="colors-section">
+ <div class="container">
+ <h2>Our Color Collection</h2>
+ <p class="section-subtitle">Choose from our collection of 118 premium colors</p>
+ <div class="color-search">
+ <input type="text" id="colorSearch" placeholder="Search colors by name or code..." onkeyup="filterColors()">
+ </div>
+ <div class="color-filters">
+ <button type="button" class="color-filter-btn active" data-filter="all" onclick="setColorFilter('all')">All</button>
+ <button type="button" class="color-filter-btn" data-filter="new" onclick="setColorFilter('new')">NEW (2026)</button>
+ <button type="button" class="color-filter-btn" data-filter="legacy" onclick="setColorFilter('legacy')">Classic</button>
+ <span id="color-count" class="color-count"></span>
+ </div>
+ <div class="color-dots" role="group" aria-label="Filter by color family">
+ <button type="button" class="color-dot active" data-family="all" style="background:linear-gradient(135deg,#fff 0%,#999 50%,#1a1a1a 100%)" onclick="setColorFamily('all')" title="All colors"><span class="dot-label">All</span></button>
+ <button type="button" class="color-dot" data-family="red" style="background:#c0392b" onclick="setColorFamily('red')" title="Reds"></button>
+ <button type="button" class="color-dot" data-family="orange" style="background:#e67e22" onclick="setColorFamily('orange')" title="Oranges"></button>
+ <button type="button" class="color-dot" data-family="yellow" style="background:#e6c419" onclick="setColorFamily('yellow')" title="Yellows"></button>
+ <button type="button" class="color-dot" data-family="green" style="background:#27ae60" onclick="setColorFamily('green')" title="Greens"></button>
+ <button type="button" class="color-dot" data-family="teal" style="background:#16a3a3" onclick="setColorFamily('teal')" title="Teals"></button>
+ <button type="button" class="color-dot" data-family="blue" style="background:#2c5fa8" onclick="setColorFamily('blue')" title="Blues"></button>
+ <button type="button" class="color-dot" data-family="purple" style="background:#7d3c98" onclick="setColorFamily('purple')" title="Purples"></button>
+ <button type="button" class="color-dot" data-family="pink" style="background:#e8a3b8" onclick="setColorFamily('pink')" title="Pinks"></button>
+ <button type="button" class="color-dot" data-family="brown" style="background:#8b5a2b" onclick="setColorFamily('brown')" title="Browns"></button>
+ <button type="button" class="color-dot" data-family="neutral" style="background:#b8b3a8" onclick="setColorFamily('neutral')" title="Neutrals"></button>
+ <button type="button" class="color-dot" data-family="black" style="background:#1a1a1a" onclick="setColorFamily('black')" title="Blacks"></button>
+ <button type="button" class="color-dot" data-family="white" style="background:#f5f1e8;border-color:#bbb" onclick="setColorFamily('white')" title="Whites & Cream"></button>
+ </div>
+ <div class="color-toolbar-row">
+ <span class="ns-toolbar">
+ <label class="ns-ctrl">Sort
+ <select id="nsSort" onchange="setSort(this.value)">
+ <option value="color-wheel">Color · wheel (hue)</option>
+ <option value="color-light">Color · light → dark</option>
+ <option value="color-dark">Color · dark → light</option>
+ <option value="name-asc">Name A→Z</option>
+ <option value="name-desc">Name Z→A</option>
+ <option value="code-asc">Code A→Z</option>
+ <option value="new-first">NEW first</option>
+ <option value="random">Random</option>
+ </select>
+ </label>
+ <label class="ns-ctrl">Cols
+ <input type="range" id="nsCols" min="4" max="12" step="1" value="6" oninput="setCols(this.value)" />
+ <span id="nsColsVal">6</span>
+ </label>
+ </span>
+ </div>
+ <div class="colors-grid" id="colorsGrid">
+ <!-- Colors will be populated here -->
+ </div>
+ </div>
+ </section>
+
+ <section id="specifications" class="specifications-section">
+ <div class="container">
+ <h2>Product Specifications</h2>
+ <div class="specs-grid">
+ <div class="spec-item">
+ <span class="spec-label">Composition</span>
+ <span class="spec-value">100% Nylon Fiber Matrix</span>
+ </div>
+ <div class="spec-item">
+ <span class="spec-label">Width</span>
+ <span class="spec-value">53-54"</span>
+ </div>
+ <div class="spec-item">
+ <span class="spec-label">Weight</span>
+ <span class="spec-value">8.85 oz/yd</span>
+ </div>
+ <div class="spec-item">
+ <span class="spec-label">Type</span>
+ <span class="spec-value">Commercial Fabric</span>
+ </div>
+ <div class="spec-item">
+ <span class="spec-label">Abrasion</span>
+ <span class="spec-value">100,000 Double Rubs Wyzenbeek</span>
+ </div>
+ <div class="spec-item">
+ <span class="spec-label">Flammability</span>
+ <span class="spec-value">Cal 117, UFAC Class I, NFPA 260</span>
+ </div>
+ <div class="spec-item">
+ <span class="spec-label">Common Uses</span>
+ <span class="spec-value">Upholstery, Wallcovering, Panel</span>
+ </div>
+ <div class="spec-item">
+ <span class="spec-label">Colors Available</span>
+ <span class="spec-value">118 Premium Colors</span>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <section id="features" class="features-section">
+ <div class="container">
+ <h2>Features of Los Angeles Fabrics™</h2>
+ <div class="features-grid">
+ <div class="feature-card">
+ <h3>Mill-Direct</h3>
+ <p>Specified by mills we've worked with directly for over a decade</p>
+ </div>
+ <div class="feature-card">
+ <h3>Contract-Spec</h3>
+ <p>Designed for high-traffic commercial installations</p>
+ </div>
+ <div class="feature-card">
+ <h3>Free Memo Samples</h3>
+ <p>Same-day shipping; CFA available on request for contract projects</p>
+ </div>
+ <div class="feature-card">
+ <h3>Custom Colorways</h3>
+ <p>Custom color options on request; small minimums apply</p>
+ </div>
+ <div class="feature-card">
+ <h3>🔧 Custom Embossing</h3>
+ <p>Available for unique design requirements</p>
+ </div>
+ <div class="feature-card">
+ <h3>🌿 No Plasticizers</h3>
+ <p>No off-gassing for healthier environments</p>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <section id="installation" class="installation-section">
+ <div class="container">
+ <h2>Installation & Care</h2>
+ <p class="section-subtitle">Professional guidance for perfect results</p>
+
+ <div class="info-grid">
+ <a href="installation.html" class="info-card-link">
+ <div class="info-card">
+ <h3>📐 Wallcovering Installation</h3>
+ <p>Professional installation techniques for seamless application of Los Angeles Fabrics™ wallcovering.</p>
+ <span class="card-arrow">View Guide →</span>
+ </div>
+ </a>
+
+ <a href="cutting.html" class="info-card-link">
+ <div class="info-card">
+ <h3>✂️ Cutting Guidelines</h3>
+ <p>Proper cutting techniques to ensure precise measurements and clean edges.</p>
+ <span class="card-arrow">View Guide →</span>
+ </div>
+ </a>
+
+ <a href="cleaning.html" class="info-card-link">
+ <div class="info-card">
+ <h3>🧹 Cleaning & Maintenance</h3>
+ <p>Easy-to-follow cleaning instructions to maintain the beauty of your Los Angeles Fabrics™.</p>
+ <span class="card-arrow">View Chart →</span>
+ </div>
+ </a>
+
+ <a href="faq.html" class="info-card-link">
+ <div class="info-card">
+ <h3>❓ Common Questions</h3>
+ <p>Answers to frequently asked questions about Los Angeles Fabrics™ care and maintenance.</p>
+ <span class="card-arrow">View FAQ →</span>
+ </div>
+ </a>
+
+ <a href="fire-ratings.html" class="info-card-link">
+ <div class="info-card">
+ <h3>🔥 Fire Ratings</h3>
+ <p>Comprehensive fire safety information and flammability test results for Los Angeles Fabrics™.</p>
+ <span class="card-arrow">View Details →</span>
+ </div>
+ </a>
+ </div>
+
+ <div class="care-highlight">
+ <h3>Care Codes Documented Per-Product</h3>
+ <p>Every Los Angeles Fabrics product carries a standard cleaning code on its spec panel — W (water-based), S (solvent), WS (either), or X (vacuum only). Routine maintenance, spot cleaning, and contract-installation protocols are documented on the <a href="/cleaning.html">care & cleaning</a> page.</p>
+ </div>
+ </div>
+ </section>
+
+ <footer>
+ <div class="container">
+ <div class="footer-content">
+ <div class="footer-section">
+ <h3>Los Angeles Fabrics</h3>
+ <p>Commercial fabric and drapery, trade-direct.</p>
+ <div class="laf-footer-extra">
+ <span class="laf-footer-address">Designer Wallcoverings<br>15442 Ventura Bl #102<br>Sherman Oaks, CA 91403</span>
+ </div>
+ </div>
+ <div class="footer-section">
+ <h3>Contact</h3>
+ <p>Email: <a href="mailto:info@losangelesfabrics.com">info@losangelesfabrics.com</a></p>
+ <button class="footer-sample-btn" onclick="openModal()">Request Sample</button>
+ </div>
+ <div class="footer-section">
+ <h3>Quick Links</h3>
+ <ul>
+ <li><a href="#products">Live Catalog</a></li>
+ <li><a href="#colors">Colors</a></li>
+ <li><a href="#specifications">Specifications</a></li>
+ <li><a href="#features">Features</a></li>
+ <li><a href="fire-ratings.html">Fire Ratings</a></li>
+ <li><a href="#installation">Installation</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <p>© 2026 Los Angeles Fabrics.com. All rights reserved. · Sister site of <a href="https://designerwallcoverings.com" style="color:#DDAB1E;text-decoration:none">designerwallcoverings.com</a></p>
+ </div>
+ </div>
+ </footer>
+
+ <!-- Contact Modal -->
+ <div id="contactModal" class="modal">
+ <div class="modal-content">
+ <span class="close" onclick="closeModal()">×</span>
+ <h2>Request a Sample</h2>
+ <div class="color-preview">
+ <img id="colorPreviewImage" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='460' height='280'%3E%3Crect width='460' height='280' fill='%231a1a1a'/%3E%3Ctext x='50%25' y='50%25' fill='%23666' font-family='Helvetica' font-size='12' letter-spacing='0.32em' text-anchor='middle' dy='.3em'%3ESELECT A COLOR BELOW%3C/text%3E%3C/svg%3E" alt="Color Preview">
+ <span id="colorPreviewName">Select a color below</span>
+ <span class="preview-hex" id="colorPreviewHex"></span>
+ <div class="form-group">
+ <label for="selectedColor">Browse colors</label>
+ <div class="color-pick-row">
+ <select id="selectedColor" name="selectedColor" onchange="updatePreview()">
+ <option value="">Choose a color…</option>
+ </select>
+ <button type="button" class="add-to-request-btn" onclick="addToRequest()" title="Add the currently-selected color to your sample request">+ Add to request</button>
+ </div>
+ </div>
+
+ <div class="request-list" id="requestList" style="display:none">
+ <div class="request-list-header">
+ <span>Your sample request</span>
+ <span class="request-count" id="requestCount">0</span>
+ </div>
+ <div class="request-chips" id="requestChips"></div>
+ </div>
+ <!-- hidden — submitted with the form. comma-separated list of all picked SKU codes. -->
+ <input type="hidden" name="requested_colors" id="requestedColorsHidden" value="">
+ </div>
+ <form id="contactForm" onsubmit="handleSubmit(event)" method="POST">
+ <div class="form-group">
+ <label>Full Name*
+ <input type="text" name="name" required>
+ </label>
+ </div>
+ <div class="form-group">
+ <label>Your email*
+ <input type="email" name="email" required>
+ </label>
+ </div>
+ <div class="form-group">
+ <label>Company
+ <input type="text" name="company">
+ </label>
+ </div>
+ <div class="form-group">
+ <label>Shipping Address*
+ <input type="text" name="address" required>
+ </label>
+ </div>
+ <div class="form-group">
+ <label>City*
+ <input type="text" name="city" required>
+ </label>
+ </div>
+ <div class="form-group">
+ <label>State*
+ <input type="text" name="state" required maxlength="2" placeholder="e.g. CA">
+ </label>
+ </div>
+ <div class="form-group">
+ <label>ZIP Code*
+ <input type="text" name="zip" required pattern="[0-9]{5}(-[0-9]{4})?" placeholder="12345">
+ </label>
+ </div>
+ <div class="form-group">
+ <label>Selected Color
+ <input type="text" name="color" id="selectedColorInput" readonly>
+ </label>
+ </div>
+ <div class="form-group">
+ <label>Your message / Project Details*
+ <textarea name="message" required rows="5" style="width: 100%; resize: vertical;" placeholder="Please describe your project needs..."></textarea>
+ </label>
+ </div>
+ <button type="submit" class="submit-btn">Send Sample Request</button>
+ </form>
+ </div>
+ </div>
+
+ <script>
+ // Complete Los Angeles Fabrics Colors Data
+ // NEW (2026-04) colors at top, alphabetically. Legacy list follows below.
+ var colors = [
+ // --- NEW: latest-drop colors, alphabetical ---
+ {name: "Amber", code: "T3Q8W", isNew: true},
+ {name: "Blank Slate", code: "T3L0W", isNew: true},
+ {name: "Bliss", code: "T3Q7W", isNew: true},
+ {name: "Blush Rose", code: "T3RFW", isNew: true},
+ {name: "Cement", code: "T3LAW", isNew: true},
+ {name: "Colonial Blue", code: "T3C9W", isNew: true},
+ {name: "Dusty Ranch", code: "T3FAW", isNew: true},
+ {name: "Dusty Teal", code: "T3NGW", isNew: true},
+ {name: "Fire", code: "T3QAW", isNew: true},
+ {name: "Flagstone", code: "T3LKW", isNew: true},
+ {name: "Henna", code: "T3G3W", isNew: true},
+ {name: "Kiva", code: "T3ECW", isNew: true},
+ {name: "Lipstick", code: "T3M4W", isNew: true},
+ {name: "Noir", code: "T3A2W", isNew: true},
+ {name: "Opal", code: "T3PQW", isNew: true},
+ {name: "Pastel Blue", code: "T3N0W", isNew: true},
+ {name: "Pastel Pink", code: "T3R6W", isNew: true},
+ {name: "Persimmon", code: "T3M8W", isNew: true},
+ {name: "Pink Lace", code: "T3RCW", isNew: true},
+ {name: "Port Blue", code: "T3NJW", isNew: true},
+ {name: "Raisin", code: "T3PRW", isNew: true},
+ {name: "Rosewater", code: "T3RSW", isNew: true},
+ {name: "Royal Blue", code: "T3CCW", isNew: true},
+ {name: "Sedona", code: "T3Q9W", isNew: true},
+ {name: "Shale", code: "T3KDW", isNew: true},
+ {name: "Snow", code: "T3B3W", isNew: true},
+ {name: "Spruce", code: "T3PYW", isNew: true},
+ {name: "Terracotta", code: "T3FCW", isNew: true},
+ {name: "Thistle", code: "T3PMW", isNew: true},
+ {name: "Tusk", code: "T3DJW", isNew: true},
+ {name: "Umber", code: "T3G4W", isNew: true},
+ {name: "Verdigris", code: "T3NHW", isNew: true},
+ {name: "Vermilion", code: "T3MBW", isNew: true},
+ {name: "Wheat", code: "T3FDW", isNew: true},
+ {name: "Winter White", code: "T3B2W", isNew: true},
+ {name: "Woodland", code: "T3H6W", isNew: true},
+ // --- Legacy catalog, alphabetical ---
+ {name: "Acorn", code: "T3H5W"},
+ {name: "Adobe", code: "T3K6W"},
+ {name: "African Violet", code: "T3SAW"},
+ {name: "Apricot", code: "T3Q4W"},
+ {name: "Arrow Wood", code: "T3F8W"},
+ {name: "Aubergine", code: "T3SCW"},
+ {name: "Baltic", code: "T3L6W"},
+ {name: "Beige", code: "T3E8W"},
+ {name: "Biscuit", code: "T3DHW"},
+ {name: "Black", code: "T3BLW"},
+ {name: "Bluestone", code: "T3N7W"},
+ {name: "Blush Pink", code: "T3C7W"},
+ {name: "Bone", code: "T3DSW"},
+ {name: "Brown", code: "T3F6W"},
+ {name: "Buff", code: "T3D1W"},
+ {name: "Buttercup", code: "T3DEW"},
+ {name: "Cane", code: "T3D3W"},
+ {name: "Carbon", code: "T3C6W"},
+ {name: "Cashew", code: "T3D2W"},
+ {name: "Cashmere", code: "T3E6W"},
+ {name: "Celadon", code: "T3P1W"},
+ {name: "Celery", code: "T3PFW"},
+ {name: "Champagne", code: "T3D4W"},
+ {name: "China Blue", code: "T3NBW"},
+ {name: "Chrome Grey", code: "T3PJW"},
+ {name: "Cinder", code: "T3LCW"},
+ {name: "Cloud", code: "T3L4W"},
+ {name: "Cocoa", code: "T315W"},
+ {name: "Coral", code: "T3Q3W"},
+ {name: "Cottage Grey", code: "T3LGW"},
+ {name: "Cream", code: "T3D9W"},
+ {name: "Cypress", code: "T3PHW"},
+ {name: "Dark Sand", code: "T3EAW"},
+ {name: "Dew", code: "T3F3W"},
+ {name: "Dove", code: "T3E2W"},
+ {name: "Dune", code: "T3F4W"},
+ {name: "Dusty Rose", code: "T3R2W"},
+ {name: "Ecru", code: "T3K4W"},
+ {name: "Fawn", code: "T3E4W"},
+ {name: "French Blue", code: "T3NCW"},
+ {name: "Gainsborough", code: "T3LLW"},
+ {name: "Garnet", code: "T3J6W"},
+ {name: "Gelato", code: "T3D7W"},
+ {name: "Gold", code: "T3J9W"},
+ {name: "Goldstone", code: "T3F1W"},
+ {name: "Granite", code: "T3LOW"},
+ {name: "Grape", code: "TES8W"},
+ {name: "Gray", code: "T3L3W"},
+ {name: "Green", code: "T3P5W"},
+ {name: "Green Marble", code: "T3P4W"},
+ {name: "Greystone", code: "T3LNW"},
+ {name: "Harvest", code: "T3E9W"},
+ {name: "Honey", code: "T3DDW"},
+ {name: "Hyacinth", code: "T3S2W"},
+ {name: "Iceberg", code: "T3LHW"},
+ {name: "Indigo", code: "T3C3W"},
+ {name: "Ivory", code: "T3E3W"},
+ {name: "Jade", code: "T3P6W"},
+ {name: "Java", code: "T3H4W"},
+ {name: "Latte", code: "T3E3W"},
+ {name: "Lavender", code: "T3S3W"},
+ {name: "Light Amethyst", code: "T3S6W"},
+ {name: "Light Camel", code: "T3E5W"},
+ {name: "Light Grey", code: "T3L7W"},
+ {name: "Lilac", code: "T3RAW"},
+ {name: "Lime", code: "T3PEW"},
+ {name: "Linen", code: "T3D6W"},
+ {name: "Madison Grey", code: "T3L1W"},
+ {name: "Maize", code: "T3O3W"},
+ {name: "Marine Blue", code: "T3N1W"},
+ {name: "Maroon", code: "T3JAW"},
+ {name: "Mauve", code: "T3S1W"},
+ {name: "Meadow", code: "T3PIW"},
+ {name: "Melon", code: "T3Q6W"},
+ {name: "Milk", code: "T3DQW"},
+ {name: "Mint", code: "T3PAW"},
+ {name: "Mint Julip", code: "T3P5W"},
+ {name: "Mist", code: "T3DDW"},
+ {name: "Misty Dew", code: "T3PLW"},
+ {name: "Mocha", code: "T3H7W"},
+ {name: "Moss", code: "T3L3W"},
+ {name: "Mushroom", code: "T3K1W"},
+ {name: "Navy", code: "T3NAW"},
+ {name: "Newport", code: "T3C2W"},
+ {name: "Nutmeg", code: "T3F9W"},
+ {name: "Ocean", code: "T3POW"},
+ {name: "Olive", code: "T3PEW"},
+ {name: "Orange", code: "T3Q2W"},
+ {name: "Oyster", code: "T3EBW"},
+ {name: "Pale Green", code: "T3P8W"},
+ {name: "Passion Blue", code: "T3NAW"},
+ {name: "Peach", code: "T3Q2W"},
+ {name: "Pebble", code: "T3K2W"},
+ {name: "Peppermint", code: "T3R4W"},
+ {name: "Peridot", code: "T3P7W"},
+ {name: "Periwinkle", code: "T3N6W"},
+ {name: "Pistachio", code: "T3P6W"},
+ {name: "Plum Pleasure", code: "T3S5W"},
+ {name: "Porcelain", code: "T3DAW"},
+ {name: "Powder Grey", code: "T3E5W"},
+ {name: "Prairie", code: "T3KCW"},
+ {name: "Putty", code: "T3K3W"},
+ {name: "Raffia", code: "T3D5W"},
+ {name: "Red", code: "T3R8W"},
+ {name: "Rose", code: "T3R4W"},
+ {name: "Royal Purple", code: "T3S4W"},
+ {name: "Ruby", code: "T3R6W"},
+ {name: "Sable", code: "T3F7W"},
+ {name: "Safari", code: "T3E7W"},
+ {name: "Saffron", code: "T3O2W"},
+ {name: "Sage", code: "T3P3W"},
+ {name: "Salt", code: "T3DBW"},
+ {name: "Sand", code: "T3D8W"},
+ {name: "Sandalwood", code: "T3F5W"},
+ {name: "Scarlet Wine", code: "T3R5W"},
+ {name: "Sea Blue", code: "T3N9W"},
+ {name: "Sea Grass", code: "T3P2W"},
+ {name: "Sea Breeze", code: "T3N8W"},
+ {name: "Seafoam", code: "T3N3W"},
+ {name: "Sienna", code: "T3Q7W"},
+ {name: "Silver", code: "T3LEW"},
+ {name: "Sky Blue", code: "T3N5W"},
+ {name: "Smoke", code: "T3L5W"},
+ {name: "Soft Clay", code: "T3K7W"},
+ {name: "Solar Yellow", code: "T3O1W"},
+ {name: "Spice", code: "T3K1W"},
+ {name: "Steel", code: "T3LBW"},
+ {name: "Stone Grey", code: "T3L2W"},
+ {name: "Stone Taupe", code: "T3K8W"},
+ {name: "Storm", code: "T3LHW"},
+ {name: "Straw", code: "T3DCW"},
+ {name: "Strong White", code: "T3DAW"},
+ {name: "Sugar Plum", code: "T3S7W"},
+ {name: "Sweet Lilac", code: "T3S1W"},
+ {name: "Tan", code: "T3E1W"},
+ {name: "Tarragon", code: "T3PGW"},
+ {name: "Taupe", code: "T3K9W"},
+ {name: "Thyme", code: "T3PDW"},
+ {name: "Trench", code: "T3F2W"},
+ {name: "Vanilla", code: "T3D5W"},
+ {name: "White", code: "T3WHW"},
+ ];
+
+ // Mobile menu toggle
+ function toggleMobileMenu() {
+ var menu = document.getElementById('navMenu');
+ if (menu.className.indexOf('active') === -1) {
+ menu.className = 'nav-menu active';
+ } else {
+ menu.className = 'nav-menu';
+ }
+ }
+
+ // Modal functions
+ function openModal() {
+ document.getElementById('contactModal').style.display = 'block';
+ populateDropdown();
+ }
+
+ function closeModal() {
+ document.getElementById('contactModal').style.display = 'none';
+ }
+
+ // Populate color dropdown
+ function populateDropdown() {
+ var select = document.getElementById('selectedColor');
+ if (select.options.length > 1) return;
+
+ for (var i = 0; i < colors.length; i++) {
+ var option = document.createElement('option');
+ option.value = 'Los Angeles Fabrics-' + colors[i].name;
+ option.text = 'Los Angeles Fabrics-' + colors[i].name + ' - ' + colors[i].code;
+ select.appendChild(option);
+ }
+ }
+
+ // ─── Multi-color sample-request basket ────────────────────────────
+ // Users can pick a color → "+ Add to request" → builds a chip list.
+ // Submits the full list as a comma-separated value in `requested_colors`.
+ var requestedColors = []; // array of "Los Angeles Fabrics-Acorn" strings
+
+ function addToRequest() {
+ var select = document.getElementById('selectedColor');
+ if (!select || !select.value) return;
+ var v = select.value;
+ if (requestedColors.indexOf(v) !== -1) return; // already added
+ requestedColors.push(v);
+ renderRequestList();
+ }
+ function removeFromRequest(v) {
+ requestedColors = requestedColors.filter(function(x){ return x !== v; });
+ renderRequestList();
+ }
+ function renderRequestList() {
+ var list = document.getElementById('requestList');
+ var chips = document.getElementById('requestChips');
+ var count = document.getElementById('requestCount');
+ var hidden = document.getElementById('requestedColorsHidden');
+ if (!list || !chips) return;
+ if (requestedColors.length === 0) {
+ list.style.display = 'none';
+ if (hidden) hidden.value = '';
+ return;
+ }
+ list.style.display = 'block';
+ count.textContent = requestedColors.length + (requestedColors.length === 1 ? ' color' : ' colors');
+ chips.innerHTML = '';
+ requestedColors.forEach(function(v) {
+ var chip = document.createElement('span');
+ chip.className = 'request-chip';
+ var label = v.replace('Los Angeles Fabrics-', '');
+ // Lookup hex for swatch dot
+ var hex = '#cccccc';
+ for (var i = 0; i < colors.length; i++) {
+ if (colors[i].name.toLowerCase() === label.toLowerCase() && colors[i].hex) { hex = colors[i].hex; break; }
+ }
+ chip.innerHTML = '<span class="chip-swatch" style="background:' + hex + '"></span>' + label.replace(/[<>"]/g,'');
+ var btn = document.createElement('button');
+ btn.type = 'button';
+ btn.setAttribute('aria-label', 'Remove ' + label);
+ btn.innerHTML = '×';
+ btn.onclick = function(){ removeFromRequest(v); };
+ chip.appendChild(btn);
+ chips.appendChild(chip);
+ });
+ if (hidden) hidden.value = requestedColors.join(', ');
+ }
+
+ // Update preview when color selected
+ // Loads the FULL product photo for the picked SKU and shows its true
+ // center-pixel hex underneath. Forces the image visible (display:block,
+ // opacity:1) so the photo always appears.
+ function updatePreview() {
+ var select = document.getElementById('selectedColor');
+ var preview = document.getElementById('colorPreviewName');
+ var img = document.getElementById('colorPreviewImage');
+ var hexEl = document.getElementById('colorPreviewHex');
+ var colorInput = document.getElementById('selectedColorInput');
+
+ if (!select || !select.value) return;
+
+ preview.textContent = select.value;
+ if (colorInput) colorInput.value = select.value;
+
+ var colorName = select.value.replace('Los Angeles Fabrics-', '');
+ var fileName = colorName.toLowerCase().replace(/ /g, '-');
+ var src = 'images/colors/losangelesfabrics-' + fileName + '.jpg';
+
+ // Surface the real center-pixel hex from the colors object (hydrated
+ // from /colors-data.json) so the customer sees the canonical color value.
+ var hex = '';
+ for (var i = 0; i < colors.length; i++) {
+ if (colors[i].name.toLowerCase() === colorName.toLowerCase() && colors[i].hex) {
+ hex = colors[i].hex;
+ break;
+ }
+ }
+ if (hexEl) hexEl.textContent = hex || '';
+
+ // Force visible + fade-in via .loading class
+ img.style.display = 'block';
+ img.classList.add('loading');
+ img.onload = function() { img.classList.remove('loading'); };
+ img.onerror = function() {
+ // If the photo 404s, fall back to a flat swatch using the real hex
+ var fallbackHex = (hex || '#cccccc').replace('#', '');
+ img.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="460" height="280"%3E%3Crect width="460" height="280" fill="%23' + fallbackHex + '"/%3E%3C/svg%3E';
+ img.classList.remove('loading');
+ };
+ img.src = src;
+ }
+
+ // Get color hex
+ function getColorHex(name) {
+ var colorMap = {
+ 'Black': '000000', 'White': 'FFFFFF', 'Gray': '808080',
+ 'Red': 'CC0000', 'Blue': '0066CC', 'Green': '008800',
+ 'Brown': '654321', 'Beige': 'F5DEB3', 'Cream': 'FFFDD0',
+ 'Navy': '000080', 'Gold': 'FFD700', 'Silver': 'C0C0C0'
+ };
+ return colorMap[name] || 'CCCCCC';
+ }
+
+
+ // ─── SORT + DENSITY (CLAUDE.md "every grid needs sort + density" rule) ───
+ // Hardcoded best-guess hex per color name. Tunable — fine-tune by sampling actual swatch
+ // images later. Sort-by-luminance uses these for light→dark / dark→light ordering.
+ var NAME_HEX = {
+ "Acorn":"#7e5a3c","Adobe":"#a6694b","African Violet":"#8a4f88","Amber":"#d4a04a","Apricot":"#f4b78a","Arrow Wood":"#7a5a3a","Aubergine":"#523040","Baltic":"#5b6a7a",
+ "Beige":"#e6d8be","Biscuit":"#dec79a","Black":"#1a1a1a","Blank Slate":"#bfb8a8","Bliss":"#f3e3d0","Bluestone":"#5d7686","Blush Pink":"#f1c2c0","Blush Rose":"#e6a9af",
+ "Bone":"#ede4d3","Brown":"#5b3a25","Buff":"#d6b78a","Buttercup":"#f5d97a","Cane":"#c8a172","Carbon":"#3e3e3e","Cashew":"#bfa17a","Cashmere":"#d8c2a3","Celadon":"#a6c4ae",
+ "Celery":"#bdc88a","Cement":"#a39a8b","Champagne":"#ead9b8","China Blue":"#4f6f8c","Chrome Grey":"#9aa1a4","Cinder":"#3a3633","Cloud":"#dce0e2","Cocoa":"#5b3a2c",
+ "Colonial Blue":"#4d6b89","Coral":"#f08070","Cottage Grey":"#a3a39a","Cream":"#f3e8d0","Cypress":"#4f5f48","Dark Sand":"#a89070","Dew":"#cfe0d6","Dove":"#bfb6ad",
+ "Dune":"#bfa37c","Dusty Ranch":"#b08960","Dusty Rose":"#c69594","Dusty Teal":"#5e8a8a","Ecru":"#dccfb6","Fawn":"#a98c6b","Fire":"#a8281c","Flagstone":"#6a6e6e",
+ "French Blue":"#5d7da3","Gainsborough":"#9aa3a5","Garnet":"#7a1a26","Gelato":"#f1d4cf","Gold":"#d4a017","Goldstone":"#a07735","Granite":"#5e5e5e","Grape":"#5a3a6a",
+ "Gray":"#888888","Green":"#3a6e3a","Green Marble":"#5d7d52","Greystone":"#6a6a66","Harvest":"#a07033","Henna":"#8c422a","Honey":"#d09a3a","Hyacinth":"#7a6da8",
+ "Ivory":"#efe6cf","Jade":"#3f8870","Java":"#3e2a1f","Kiva":"#8a5a3e","Latte":"#bfa07a","Lavender":"#aea3cc","Light Amethyst":"#bfa5cf","Light Camel":"#cfae84",
+ "Light Grey":"#cfcfcf","Lilac":"#c5a8d2","Lime":"#bcd34a","Linen":"#e6dac3","Lipstick":"#b22247","Madison Grey":"#7a7a7a","Maize":"#e0c870","Marine Blue":"#26486b",
+ "Maroon":"#5a1f23","Mauve":"#a98090","Meadow":"#789e63","Melon":"#f0a075","Milk":"#f3ece2","Mint":"#a7d6c0","Mint Julip":"#b8d4b3","Mist":"#d6dada","Misty Dew":"#b8c5be",
+ "Mocha":"#6e4830","Moss":"#5b6e3a","Mushroom":"#9c8b73","Navy":"#1f2c54","Newport":"#3a5a78","Noir":"#1a1a1a","Nutmeg":"#7e4a2e","Ocean":"#2a5a7a","Olive":"#5e6b32",
+ "Opal":"#b6c4cc","Orange":"#e07b1a","Oyster":"#bfae90","Pale Green":"#bcd2a3","Passion Blue":"#2a3f6e","Pastel Blue":"#b3cfe5","Pastel Pink":"#f3c8cf","Peach":"#f0b78a",
+ "Pebble":"#9c948a","Peppermint":"#b3d6c4","Peridot":"#9eb050","Periwinkle":"#8090c8","Persimmon":"#c95a2c","Pink Lace":"#f1c5cd","Pistachio":"#b4cc99",
+ "Plum Pleasure":"#5e3a4a","Porcelain":"#f0eadd","Port Blue":"#26405e","Powder Grey":"#c2c4c5","Prairie":"#a07b4f","Putty":"#bca887","Raffia":"#d6c4a3","Raisin":"#3e2a35",
+ "Red":"#a82424","Rose":"#d57a87","Rosewater":"#f0c8c8","Royal Blue":"#1a3a8a","Royal Purple":"#4a2466","Ruby":"#8a1a36","Sable":"#3e2a1f","Safari":"#9a7e52",
+ "Saffron":"#e0a020","Sage":"#9aa888","Salt":"#efe8de","Sand":"#d4c08a","Sandalwood":"#b08a5a","Scarlet Wine":"#6e1a26","Sea Blue":"#3a6a8a","Sea Breeze":"#b3cfd9",
+ "Sea Grass":"#6e9a73","Seafoam":"#a3d3c4","Sedona":"#a05a3a","Shale":"#5a5e5e","Sienna":"#8e4a2a","Silver":"#c0c0c0","Sky Blue":"#9ec3da","Smoke":"#7a7a7a",
+ "Snow":"#f5f5f5","Soft Clay":"#b4937c","Solar Yellow":"#f0c020","Spice":"#9c4a26","Spruce":"#3a5a3a","Steel":"#5e6e7a","Stone Grey":"#8a8a86","Stone Taupe":"#9c8a73",
+ "Storm":"#5a5e62","Straw":"#d4c07a","Strong White":"#efe9da","Sugar Plum":"#4a2a3e","Sweet Lilac":"#bfa3c2","Tan":"#bfa074","Tarragon":"#6e7e3e","Taupe":"#8e7c5e",
+ "Terracotta":"#b35a3a","Thistle":"#a89ab8","Thyme":"#5c6b3e","Trench":"#3e3a30","Tusk":"#dccfb0","Umber":"#5e3a26","Vanilla":"#f0e8d0","Verdigris":"#5a8a7a",
+ "Vermilion":"#d23a1f","Wheat":"#dec38a","White":"#ffffff","Winter White":"#f0e8d0","Woodland":"#3e4a2a"
+ };
+ function nsHex(c) { return c.hex || NAME_HEX[c.name] || '#cccccc'; }
+ function nsLuma(h) {
+ var s = (h || '#cccccc').replace('#',''); if (s.length === 3) s = s.split('').map(function(x){return x+x;}).join('');
+ var r = parseInt(s.slice(0,2),16), g = parseInt(s.slice(2,4),16), b = parseInt(s.slice(4,6),16);
+ return 0.299*r + 0.587*g + 0.114*b;
+ }
+ // Convert hex → HSL for color-wheel (hue) sort. Desaturated colors
+ // (s < 12%) fall to a "neutrals" bucket sorted by lightness so grays
+ // don't shuffle randomly inside the rainbow.
+ function nsHsl(h) {
+ var s = (h || '#cccccc').replace('#',''); if (s.length === 3) s = s.split('').map(function(x){return x+x;}).join('');
+ var r = parseInt(s.slice(0,2),16)/255, g = parseInt(s.slice(2,4),16)/255, b = parseInt(s.slice(4,6),16)/255;
+ var max = Math.max(r,g,b), min = Math.min(r,g,b), L = (max+min)/2;
+ var H = 0, S = 0;
+ if (max !== min) {
+ var d = max - min;
+ S = L > 0.5 ? d / (2 - max - min) : d / (max + min);
+ if (max === r) H = (g - b) / d + (g < b ? 6 : 0);
+ else if (max === g) H = (b - r) / d + 2;
+ else H = (r - g) / d + 4;
+ H *= 60;
+ }
+ return [H, S * 100, L * 100];
+ }
+ function nsWheelKey(c) {
+ var hsl = nsHsl(nsHex(c));
+ // Neutrals (s < 12%) go to the END, sorted dark→light. Saturated
+ // colors come first by hue (red 0° → orange → yellow → green → blue → purple → 360°).
+ if (hsl[1] < 12) return 1000 + hsl[2];
+ return hsl[0]; // 0..360 hue
+ }
+ var NS_SORTERS = {
+ 'color-wheel': function(a,b){ return nsWheelKey(a) - nsWheelKey(b); },
+ 'color-light': function(a,b){ return nsLuma(nsHex(b)) - nsLuma(nsHex(a)); },
+ 'color-dark': function(a,b){ return nsLuma(nsHex(a)) - nsLuma(nsHex(b)); },
+ 'name-asc': function(a,b){ return a.name.localeCompare(b.name); },
+ 'name-desc': function(a,b){ return b.name.localeCompare(a.name); },
+ 'code-asc': function(a,b){ return (a.code||'').localeCompare(b.code||''); },
+ 'new-first': function(a,b){ return ((b.isNew?1:0) - (a.isNew?1:0)) || a.name.localeCompare(b.name); },
+ 'random': function(){ return Math.random() - 0.5; },
+ };
+ var NS_PREFS = (function(){ try { return JSON.parse(localStorage.getItem('ns-prefs')||'{}'); } catch(e){ return {}; } })();
+ function nsSavePrefs(){ try { localStorage.setItem('ns-prefs', JSON.stringify(NS_PREFS)); } catch(e){} }
+ function setSort(mode){ NS_PREFS.sort = mode; nsSavePrefs(); loadColors(); }
+ function setCols(n){
+ NS_PREFS.cols = parseInt(n,10) || 6;
+ nsSavePrefs();
+ var grids = document.querySelectorAll('.colors-grid');
+ for (var i=0;i<grids.length;i++) grids[i].style.setProperty('--cols', String(NS_PREFS.cols));
+ var v = document.getElementById('nsColsVal'); if (v) v.textContent = NS_PREFS.cols;
+ }
+ function nsApplyInitial(){
+ // Default first-visit sort = color-light (per Steve 2026-05-07).
+ var sort = NS_PREFS.sort || 'color-light';
+ var cols = NS_PREFS.cols || 6;
+ var sel = document.getElementById('nsSort'); if (sel) sel.value = sort;
+ var rng = document.getElementById('nsCols'); if (rng) rng.value = cols;
+ var v = document.getElementById('nsColsVal'); if (v) v.textContent = cols;
+ var grids = document.querySelectorAll('.colors-grid');
+ for (var i=0;i<grids.length;i++) grids[i].style.setProperty('--cols', String(cols));
+ }
+
+ // Load colors grid
+ // Render a chip into the given grid element.
+ function renderChip(grid, color) {
+ var item = document.createElement('div');
+ item.className = 'color-item';
+ if (color.hex) item.setAttribute('data-family-hex', String(color.hex).replace('#',''));
+ if (color.isNew) {
+ var badge = document.createElement('div');
+ badge.className = 'new-badge';
+ badge.textContent = 'NEW';
+ item.appendChild(badge);
+ }
+ var swatch = document.createElement('div');
+ swatch.className = 'color-swatch';
+ var img = document.createElement('img');
+ var fileName = color.name.toLowerCase().replace(/ /g, '-');
+ img.alt = color.name;
+ img.onerror = function() {
+ if (this && this.style) this.style.display = 'none';
+ if (this && this.parentElement) this.parentElement.style.background = '#' + getColorHex(this.alt);
+ };
+ swatch.appendChild(img);
+ // src AFTER append — so onerror always finds the parent and the parent's swatch context
+ img.src = '/images/colors/losangelesfabrics-' + fileName + '.jpg';
+ var info = document.createElement('div');
+ info.className = 'color-info';
+ var nameDiv = document.createElement('div');
+ nameDiv.className = 'color-name'; nameDiv.textContent = color.name;
+ var codeDiv = document.createElement('div');
+ codeDiv.className = 'color-code'; codeDiv.textContent = color.code;
+ info.appendChild(nameDiv); info.appendChild(codeDiv);
+ item.appendChild(swatch); item.appendChild(info);
+ var btnRow = document.createElement('div');
+ btnRow.className = 'chip-actions';
+ var drapeBtn = document.createElement('button');
+ drapeBtn.className = 'drapery-btn'; drapeBtn.type = 'button'; drapeBtn.textContent = 'See in Room';
+ (function(n, im) { drapeBtn.onclick = function(e) { e.stopPropagation(); applyDraperyColor(n, im); }; })(color.name, img);
+ btnRow.appendChild(drapeBtn);
+ var sampleBtn = document.createElement('button');
+ sampleBtn.className = 'sample-btn-chip'; sampleBtn.type = 'button'; sampleBtn.textContent = 'Sample';
+ (function(n) { sampleBtn.onclick = function(e) { e.stopPropagation(); openModal(); setTimeout(function() { document.getElementById('selectedColor').value = 'Los Angeles Fabrics-' + n; updatePreview(); addToRequest(); }, 100); }; })(color.name);
+ btnRow.appendChild(sampleBtn);
+ info.appendChild(btnRow);
+ (function(n) { item.onclick = function() { openModal(); setTimeout(function() { document.getElementById('selectedColor').value = 'Los Angeles Fabrics-' + n; updatePreview(); }, 100); }; })(color.name);
+ grid.appendChild(item);
+ }
+
+ function loadColors() {
+ nsApplyInitial();
+ var grid = document.getElementById('colorsGrid');
+ var newGrid = document.getElementById('newArrivalsGrid');
+ // Sorted COPY of colors based on current pref
+ var sortMode = (NS_PREFS && NS_PREFS.sort) || 'color-light';
+ var cmp = NS_SORTERS[sortMode] || NS_SORTERS['color-light'];
+ var sorted = colors.slice().sort(cmp);
+ // New arrivals section — only isNew chips, in current sort order
+ if (newGrid) {
+ newGrid.innerHTML = '';
+ for (var j = 0; j < sorted.length; j++) if (sorted[j].isNew) renderChip(newGrid, sorted[j]);
+ }
+ // Main collection — all colors, sorted
+ if (!grid) return;
+ grid.innerHTML = '';
+ for (var i = 0; i < sorted.length; i++) renderChip(grid, sorted[i]);
+ return;
+ // Below: legacy inline renderer (kept for diff-readability — never executes)
+ for (var i = 0; i < colors.length; i++) {
+ var item = document.createElement('div');
+ item.className = 'color-item';
+
+ if (colors[i].isNew) {
+ var badge = document.createElement('div');
+ badge.className = 'new-badge';
+ badge.textContent = 'NEW';
+ item.appendChild(badge);
+ }
+
+ var swatch = document.createElement('div');
+ swatch.className = 'color-swatch';
+
+ var img = document.createElement('img');
+ var fileName = colors[i].name.toLowerCase().replace(/ /g, '-');
+ img.src = '/images/colors/losangelesfabrics-' + fileName + '.jpg';
+ img.alt = colors[i].name;
+ img.onerror = function(e) {
+ this.style.display = 'none';
+ this.parentElement.style.background = '#' + getColorHex(this.alt);
+ };
+
+ swatch.appendChild(img);
+
+ var info = document.createElement('div');
+ info.className = 'color-info';
+
+ var nameDiv = document.createElement('div');
+ nameDiv.className = 'color-name';
+ nameDiv.textContent = colors[i].name;
+
+ var codeDiv = document.createElement('div');
+ codeDiv.className = 'color-code';
+ codeDiv.textContent = colors[i].code;
+
+ info.appendChild(nameDiv);
+ info.appendChild(codeDiv);
+
+ item.appendChild(swatch);
+ item.appendChild(info);
+
+ // Two action buttons per chip: Drapery (recolor whole page background)
+ // and Sample (open the contact modal pre-filled with this color).
+ var btnRow = document.createElement('div');
+ btnRow.className = 'chip-actions';
+
+ var drapeBtn = document.createElement('button');
+ drapeBtn.className = 'drapery-btn';
+ drapeBtn.type = 'button';
+ drapeBtn.textContent = '🪟 Drapery';
+ (function(colorName, swatchImg) {
+ drapeBtn.onclick = function(e) {
+ e.stopPropagation();
+ applyDraperyColor(colorName, swatchImg);
+ };
+ })(colors[i].name, img);
+ btnRow.appendChild(drapeBtn);
+
+ var sampleBtn = document.createElement('button');
+ sampleBtn.className = 'sample-btn-chip';
+ sampleBtn.type = 'button';
+ sampleBtn.textContent = '📦 Sample';
+ (function(colorName) {
+ sampleBtn.onclick = function(e) {
+ e.stopPropagation();
+ openModal();
+ setTimeout(function() {
+ document.getElementById('selectedColor').value = 'Los Angeles Fabrics-' + colorName;
+ updatePreview();
+ }, 100);
+ };
+ })(colors[i].name);
+ btnRow.appendChild(sampleBtn);
+
+ info.appendChild(btnRow);
+
+ (function(colorName) {
+ item.onclick = function() {
+ openModal();
+ setTimeout(function() {
+ document.getElementById('selectedColor').value = 'Los Angeles Fabrics-' + colorName;
+ updatePreview();
+ }, 100);
+ };
+ })(colors[i].name);
+
+ grid.appendChild(item);
+ }
+ }
+
+ // Sample the average color of the chip's swatch image so we don't need a
+ // hand-maintained hex table for all 118 colors. Falls back to the small
+ // colorMap in getColorHex() if the swatch hasn't loaded.
+ function sampleAverageColor(imgEl, colorName) {
+ try {
+ if (!imgEl || !imgEl.complete || !imgEl.naturalWidth) {
+ return getColorHex(colorName);
+ }
+ var c = document.createElement('canvas');
+ c.width = 16; c.height = 16;
+ var ctx = c.getContext('2d');
+ ctx.drawImage(imgEl, 0, 0, 16, 16);
+ var d = ctx.getImageData(0, 0, 16, 16).data;
+ var r=0,g=0,b=0,n=0;
+ for (var i=0; i<d.length; i+=4) { r+=d[i]; g+=d[i+1]; b+=d[i+2]; n++; }
+ r = Math.round(r/n); g = Math.round(g/n); b = Math.round(b/n);
+ return ((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1).toUpperCase();
+ } catch(e) {
+ return getColorHex(colorName);
+ }
+ }
+
+ // Apply the drapery: every color has a pre-rendered swag at
+ // /images/drapery-swags/<slug>.jpg (Reinhard-style luminance-preserving
+ // recolor of the hero curtain composite — generated by render-drapery-swags.js).
+ // Falls back to live in-browser canvas recolor if the pre-rendered file
+ // 404s (rare — only happens for color names that fail the slug round-trip).
+ function applyDraperyColor(colorName, swatchImg) {
+ var slug = String(colorName).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
+ // v6 — whole frame in one color (sides + center recolored together)
+ var url = '/images/drapery-swags/' + slug + '.jpg?v=6';
+ var hex = sampleAverageColor(swatchImg, colorName);
+ // Probe the pre-rendered file; if it loads, use it directly.
+ var probe = new Image();
+ probe.onload = function() { setBodyDrapery(url, hex); };
+ probe.onerror = function() {
+ console.warn('Pre-rendered drapery missing for', colorName, '→ falling back to live canvas recolor');
+ liveCanvasRecolor(hex);
+ };
+ probe.src = url;
+ }
+
+ // Fallback: if no pre-rendered swag, do the recolor in-browser. Same Reinhard
+ // luminance-preserving algorithm as render-drapery-swags.js.
+ function liveCanvasRecolor(hex) {
+ var leftUrl = '/images/left-curtain-d1.png';
+ var rightUrl = '/images/right-curtain-d1.png';
+ var splashUrl = '/images/bg-site-splash.jpg';
+ loadImg(splashUrl, function(splash) {
+ var W = 1920, H = 1080;
+ if (splash) { W = splash.naturalWidth || W; H = splash.naturalHeight || H; }
+ var canvas = document.createElement('canvas');
+ canvas.width = W; canvas.height = H;
+ var ctx = canvas.getContext('2d');
+ if (splash) ctx.drawImage(splash, 0, 0, W, H);
+ else { ctx.fillStyle = '#1a1a1a'; ctx.fillRect(0,0,W,H); }
+ var pending = 2;
+ function done() { if (--pending === 0) setBodyDrapery(canvas.toDataURL('image/jpeg', 0.85), hex); }
+ loadImg(leftUrl, function(im){ if (im) stampRecolored(ctx, im, hex, 'left', W, H); done(); });
+ loadImg(rightUrl, function(im){ if (im) stampRecolored(ctx, im, hex, 'right', W, H); done(); });
+ });
+ }
+
+ function loadImg(url, cb) {
+ var im = new Image();
+ im.crossOrigin = 'anonymous';
+ im.onload = function() { cb(im); };
+ im.onerror = function() { cb(null); };
+ im.src = url;
+ }
+
+ // HSL conversion helpers — used for pure luminance-preserving recolor
+ // (Reinhard chromaticity transfer). Matches render-drapery-swags.js exactly.
+ function _rgbToHsl(r, g, b) {
+ r/=255; g/=255; b/=255;
+ var mx=Math.max(r,g,b), mn=Math.min(r,g,b);
+ var h, s, l=(mx+mn)/2;
+ if (mx === mn) { h=0; s=0; }
+ else {
+ var d=mx-mn;
+ s = l > 0.5 ? d/(2-mx-mn) : d/(mx+mn);
+ if (mx===r) h=(g-b)/d+(g<b?6:0);
+ else if (mx===g) h=(b-r)/d+2;
+ else h=(r-g)/d+4;
+ h/=6;
+ }
+ return [h,s,l];
+ }
+ function _hslToRgb(h, s, l) {
+ var r,g,b;
+ if (s === 0) { r=g=b=l; }
+ else {
+ var hue2rgb = function(p,q,t) {
+ if (t<0) t+=1; if (t>1) t-=1;
+ if (t<1/6) return p+(q-p)*6*t;
+ if (t<1/2) return q;
+ if (t<2/3) return p+(q-p)*(2/3-t)*6;
+ return p;
+ };
+ var q = l<0.5 ? l*(1+s) : l+s-l*s;
+ var p = 2*l-q;
+ r=hue2rgb(p,q,h+1/3); g=hue2rgb(p,q,h); b=hue2rgb(p,q,h-1/3);
+ }
+ return [Math.round(r*255), Math.round(g*255), Math.round(b*255)];
+ }
+ function stampRecolored(destCtx, img, hex, side, W, H) {
+ var iw = img.naturalWidth, ih = img.naturalHeight;
+ var scale = H / ih;
+ var dw = iw * scale, dh = ih * scale;
+ var dx = side === 'left' ? 0 : (W - dw);
+ var off = document.createElement('canvas');
+ off.width = iw; off.height = ih;
+ var octx = off.getContext('2d');
+ octx.drawImage(img, 0, 0);
+ var id = octx.getImageData(0, 0, iw, ih);
+ var data = id.data;
+ var tr = parseInt(hex.slice(0,2), 16);
+ var tg = parseInt(hex.slice(2,4), 16);
+ var tb = parseInt(hex.slice(4,6), 16);
+ var th = _rgbToHsl(tr, tg, tb);
+ for (var i = 0; i < data.length; i += 4) {
+ var a = data[i+3];
+ if (a === 0) continue;
+ var sl = _rgbToHsl(data[i], data[i+1], data[i+2])[2];
+ var rgb = _hslToRgb(th[0], th[1], sl);
+ data[i] = rgb[0];
+ data[i+1] = rgb[1];
+ data[i+2] = rgb[2];
+ }
+ octx.putImageData(id, 0, 0);
+ destCtx.drawImage(off, dx, 0, dw, dh);
+ }
+
+ function setBodyDrapery(url, hex) {
+ // Body bg (visible below the hero on scroll)
+ document.body.style.backgroundImage = 'url(' + url + ')';
+ document.body.style.backgroundSize = 'cover';
+ document.body.style.backgroundPosition = 'center top';
+ document.body.style.backgroundRepeat = 'no-repeat';
+ document.body.style.backgroundAttachment = 'fixed';
+ // Hero bg — the visible drapery swap. Without this nothing changes
+ // visually because the .hero rule covers the body.
+ var hero = document.querySelector('.hero');
+ if (hero) {
+ hero.style.backgroundImage = 'url(' + url + ')';
+ hero.style.backgroundSize = 'cover';
+ hero.style.backgroundPosition = 'center top';
+ }
+ // Hide the foreground curtain divs — the pre-rendered swag already
+ // contains both curtains recolored, so leaving the originals visible
+ // would double-stack neutral curtains over the recolored ones.
+ document.querySelectorAll('.curtain').forEach(function(c){ c.style.display = 'none'; });
+ // Mark the body so subsequent sections fade their solid white bg and
+ // the swag shows through end-to-end of the page.
+ document.body.classList.add('has-drapery');
+ // Tone down the white veil over the hero so the drapery color reads true
+ var sty = document.getElementById('cncp-drapery-overlay-style');
+ if (!sty) {
+ sty = document.createElement('style');
+ sty.id = 'cncp-drapery-overlay-style';
+ sty.textContent = '.hero::before { background: linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.0) 50%, rgba(0,0,0,0.10) 100%) !important; }';
+ document.head.appendChild(sty);
+ }
+ document.documentElement.style.setProperty('--drapery-color', '#' + hex);
+ }
+
+ // Close modal on outside click
+ window.onclick = function(event) {
+ var modal = document.getElementById('contactModal');
+ if (event.target == modal) {
+ modal.style.display = 'none';
+ }
+ }
+
+ // Filter state — driven by the search box + the All/NEW/Classic chips + color-family dots
+ var _colorFilter = 'all';
+ var _colorFamily = 'all';
+ function setColorFilter(f) {
+ _colorFilter = f;
+ document.querySelectorAll('.color-filter-btn').forEach(function(b){
+ b.classList.toggle('active', b.dataset.filter === f);
+ });
+ filterColors();
+ }
+ function setColorFamily(f) {
+ _colorFamily = f;
+ document.querySelectorAll('.color-dot').forEach(function(b){
+ b.classList.toggle('active', b.dataset.family === f);
+ });
+ filterColors();
+ }
+ // Bucket a hex color into a coarse family label by HSL.
+ function hexToFamily(hex) {
+ if (!hex) return 'neutral';
+ hex = String(hex).replace('#','').trim();
+ if (hex.length === 3) hex = hex.split('').map(function(c){return c+c;}).join('');
+ if (!/^[0-9a-fA-F]{6}$/.test(hex)) return 'neutral';
+ var r = parseInt(hex.substr(0,2),16)/255;
+ var g = parseInt(hex.substr(2,2),16)/255;
+ var b = parseInt(hex.substr(4,2),16)/255;
+ var max = Math.max(r,g,b), min = Math.min(r,g,b);
+ var l = (max+min)/2;
+ var d = max-min;
+ var s = d === 0 ? 0 : d / (1 - Math.abs(2*l-1));
+ var h = 0;
+ if (d !== 0) {
+ if (max === r) h = ((g-b)/d) % 6;
+ else if (max === g) h = (b-r)/d + 2;
+ else h = (r-g)/d + 4;
+ h *= 60; if (h < 0) h += 360;
+ }
+ // Black / white / neutral first
+ if (l < 0.10) return 'black';
+ if (l > 0.90 && s < 0.20) return 'white';
+ if (s < 0.12) return 'neutral';
+ // Brown = warm low-saturation, low-mid lightness
+ if (h < 50 && s < 0.55 && l < 0.55) return 'brown';
+ if (h >= 345 || h < 15) return 'red';
+ if (h < 40) return 'orange';
+ if (h < 70) return 'yellow';
+ if (h < 165) return 'green';
+ if (h < 200) return 'teal';
+ if (h < 255) return 'blue';
+ if (h < 300) return 'purple';
+ return 'pink';
+ }
+ // Resolve the family for a chip element using its hydrated hex (data attr) or the swatch image.
+ function chipFamily(item) {
+ var hex = item.getAttribute('data-family-hex');
+ if (hex) return hexToFamily(hex);
+ var name = (item.querySelector('.color-name')?.textContent || '');
+ var fallback = (typeof getColorHex === 'function') ? getColorHex(name) : '';
+ return hexToFamily(fallback);
+ }
+ function filterColors() {
+ var q = (document.getElementById('colorSearch').value || '').toLowerCase().trim();
+ var items = document.querySelectorAll('#colorsGrid .color-item');
+ var shown = 0;
+ items.forEach(function(item) {
+ var name = (item.querySelector('.color-name')?.textContent || '').toLowerCase();
+ var code = (item.querySelector('.color-code')?.textContent || '').toLowerCase();
+ var isNew = !!item.querySelector('.new-badge');
+ var matchesText = !q || name.includes(q) || code.includes(q);
+ var matchesFilter = _colorFilter === 'all' || (_colorFilter === 'new' && isNew) || (_colorFilter === 'legacy' && !isNew);
+ var matchesFamily = _colorFamily === 'all' || chipFamily(item) === _colorFamily;
+ var visible = matchesText && matchesFilter && matchesFamily;
+ item.style.display = visible ? '' : 'none';
+ if (visible) shown++;
+ });
+ var counter = document.getElementById('color-count');
+ if (counter) counter.textContent = shown + ' of ' + items.length + ' shown';
+ }
+
+ // Hydrate every color in the `colors[]` array with its TRUE
+ // center-pixel hex (extracted by extract-center-hex.js → colors-data.json).
+ // One canonical hex per SKU. Falls back to NAME_HEX lookup if a slug
+ // is missing.
+ function hydrateRealHexes(then) {
+ fetch('/colors-data.json?v=2026-05-07', { cache: 'no-cache' })
+ .then(function(r){ return r.ok ? r.json() : []; })
+ .then(function(rows) {
+ if (!Array.isArray(rows) || !rows.length) { then(); return; }
+ var bySlug = {};
+ for (var i = 0; i < rows.length; i++) bySlug[rows[i].slug] = rows[i].hex;
+ for (var j = 0; j < colors.length; j++) {
+ var slug = (colors[j].name || '').toLowerCase().replace(/ /g, '-');
+ if (bySlug[slug]) colors[j].hex = bySlug[slug];
+ }
+ then();
+ })
+ .catch(function(){ then(); });
+ }
+
+ // Initialize on page load
+ function nsBoot(){ hydrateRealHexes(function(){ loadColors(); filterColors(); }); }
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', nsBoot);
+ } else {
+ nsBoot();
+ }
+ </script>
+
+ <script src="/script.js?v=drapery2"></script>
+
+ <!-- Start of Zendesk Widget script -->
+ <script id="ze-snippet" src="https://static.zdassets.com/ekr/snippet.js?key=4d4f52ea-1e3e-4837-8859-51f9abd5a82b"> </script>
+ <!-- End of Zendesk Widget script -->
+ <script>
+ // Rotating hero — crossfade through 8 suede colors every 6.5s.
+ // Pauses when the tab is hidden so we don't burn cycles on a background tab.
+ (function () {
+ var layers = document.querySelectorAll('.hero .hero-bg');
+ if (layers.length < 2) return;
+ var idx = 0;
+ var ROTATE_MS = 6500;
+ var timer = null;
+ function step() {
+ layers[idx].classList.remove('on');
+ idx = (idx + 1) % layers.length;
+ layers[idx].classList.add('on');
+ }
+ function start() { stop(); timer = setInterval(step, ROTATE_MS); }
+ function stop() { if (timer) { clearInterval(timer); timer = null; } }
+ document.addEventListener('visibilitychange', function () {
+ if (document.hidden) stop(); else start();
+ });
+ start();
+ })();
+ </script>
+
+ <!-- ──────────────────────────────────────────────────────────────────
+ LAF Novasuede-UX module 2026-05-08
+ Right-side hamburger · Theme · Tabs · Sort · Density · Search
+ Live product grid (1.6k SKUs) · Detail modal
+ ────────────────────────────────────────────────────────────────── -->
+ <script>
+ (function() {
+ // ─── Hamburger / side panel ────────────────────────────────────
+ var sidePanel = document.getElementById('lafSidePanel');
+ var sideOverlay = document.getElementById('lafSideOverlay');
+ var hamburger = document.getElementById('lafHamburger');
+ window.lafToggleSide = function() {
+ var open = !sidePanel.classList.contains('open');
+ sidePanel.classList.toggle('open', open);
+ sideOverlay.classList.toggle('open', open);
+ hamburger.classList.toggle('open', open);
+ hamburger.setAttribute('aria-expanded', open ? 'true' : 'false');
+ sidePanel.setAttribute('aria-hidden', open ? 'false' : 'true');
+ document.body.style.overflow = open ? 'hidden' : '';
+ };
+ window.lafCloseSide = function() {
+ sidePanel.classList.remove('open');
+ sideOverlay.classList.remove('open');
+ hamburger.classList.remove('open');
+ hamburger.setAttribute('aria-expanded', 'false');
+ sidePanel.setAttribute('aria-hidden', 'true');
+ document.body.style.overflow = '';
+ };
+ document.addEventListener('keydown', function(e) {
+ if (e.key === 'Escape') {
+ if (sidePanel.classList.contains('open')) lafCloseSide();
+ if (document.getElementById('lafProductModal').classList.contains('open')) lafCloseProduct();
+ }
+ });
+
+ // ─── Theme toggle (dark/light) ─────────────────────────────────
+ var themeIcon = document.getElementById('lafThemeIcon');
+ function applyThemeIcon() {
+ var t = document.documentElement.getAttribute('data-theme') || 'dark';
+ themeIcon.textContent = t === 'light' ? '☀' : '☾';
+ }
+ applyThemeIcon();
+ window.lafToggleTheme = function() {
+ var cur = document.documentElement.getAttribute('data-theme') || 'dark';
+ var next = cur === 'light' ? 'dark' : 'light';
+ document.documentElement.setAttribute('data-theme', next);
+ try { localStorage.setItem('laf_theme', next); } catch (e) {}
+ applyThemeIcon();
+ };
+
+ // ─── Live product catalog ───────────────────────────────────────
+ var STATE = {
+ products: [],
+ tab: 'all',
+ sort: localStorage.getItem('laf_sort') || 'newest',
+ density: parseInt(localStorage.getItem('laf_density') || '240', 10),
+ query: '',
+ visible: 0,
+ pageSize: 96,
+ current: null
+ };
+
+ // Tab definitions — each carries a predicate that runs against a product.
+ var TABS = [
+ { id: 'all', label: 'All', pred: function(p) { return true; } },
+ { id: 'drapery', label: 'Drapery', pred: hasTag('Drapery') },
+ { id: 'upholstery', label: 'Upholstery', pred: hasTag('Upholstery') },
+ { id: 'sheer', label: 'Sheer', pred: hasTag('Sheer') },
+ { id: 'outdoor', label: 'Outdoor', pred: hasTag('Outdoor Upholstery') },
+ { id: 'fire-rated', label: 'Fire Rated', pred: hasTag('Fire Rated') },
+ { id: 'faux-leather',label: 'Faux Leather', pred: hasTag('Faux Leather') },
+ { id: 'textured', label: 'Textured', pred: hasTag('Textured') },
+ { id: 'solid', label: 'Solid', pred: hasTag('Solid') },
+ { id: 'patterned', label: 'Patterned', pred: function(p) {
+ var tags = (p.tags || []).map(function(t){return t.toLowerCase();});
+ return tags.some(function(t) {
+ return ['stripe','geometric','herringbone','abstract','floral','damask'].indexOf(t) !== -1;
+ });
+ }},
+ { id: 'le-cirque', label: 'Le Cirque', pred: collectionIs('Le Cirque') },
+ { id: 'monument', label: 'Monument', pred: collectionIs('Monument') },
+ { id: 'hollywood', label: 'Hollywood', pred: collectionIs('Hollywood Babylon') },
+ { id: 'aesthetics', label: 'Aesthetics', pred: collectionIs('Aesthetics') },
+ { id: 'peak-perf', label: 'Peak Perf.', pred: collectionIs('Peak Performance') }
+ ];
+ function hasTag(tag) {
+ var lower = tag.toLowerCase();
+ return function(p) { return (p.tags || []).some(function(t) { return t.toLowerCase() === lower; }); };
+ }
+ function collectionIs(name) {
+ var lower = name.toLowerCase();
+ return function(p) {
+ var c = (p.specs && p.specs.collection || '').toLowerCase();
+ return c.indexOf(lower) !== -1 || (p.tags || []).some(function(t){return t.toLowerCase().indexOf(lower) !== -1 && t.toLowerCase().indexOf('collection') !== -1;});
+ };
+ }
+
+ // ─── Tab rendering ──────────────────────────────────────────────
+ function renderTabs() {
+ var container = document.getElementById('lafTabs');
+ if (!container) return;
+ container.innerHTML = TABS.map(function(t) {
+ var count = STATE.products.filter(t.pred).length;
+ if (t.id !== 'all' && count === 0) return ''; // hide empty tabs
+ return '<button type="button" class="laf-tab' + (STATE.tab === t.id ? ' active' : '') +
+ '" role="tab" aria-selected="' + (STATE.tab === t.id ? 'true' : 'false') +
+ '" data-tab="' + t.id + '" onclick="lafSetTab(\'' + t.id + '\')">' +
+ t.label + ' <span class="tab-count">' + count + '</span></button>';
+ }).join('');
+ }
+ window.lafSetTab = function(id) {
+ STATE.tab = id;
+ STATE.visible = 0;
+ renderTabs();
+ renderGrid();
+ };
+
+ // ─── Filtering, sorting, rendering grid ─────────────────────────
+ function activeTab() {
+ for (var i = 0; i < TABS.length; i++) if (TABS[i].id === STATE.tab) return TABS[i];
+ return TABS[0];
+ }
+ function filtered() {
+ var pred = activeTab().pred;
+ var q = STATE.query.toLowerCase().trim();
+ var list = STATE.products.filter(pred);
+ if (q) {
+ list = list.filter(function(p) {
+ var hay = (p.title + ' ' + (p.color||'') + ' ' + (p.pattern||'') + ' ' + (p.sku||'') + ' ' + (p.mfrSku||'') + ' ' + (p.tags||[]).join(' ')).toLowerCase();
+ return hay.indexOf(q) !== -1;
+ });
+ }
+ return sortList(list);
+ }
+ function sortList(list) {
+ var arr = list.slice();
+ switch (STATE.sort) {
+ case 'newest':
+ arr.sort(function(a, b) { return (b.createdAt||'').localeCompare(a.createdAt||''); }); break;
+ case 'sku-asc':
+ arr.sort(function(a, b) { return (a.sku||'').localeCompare(b.sku||''); }); break;
+ case 'title-asc':
+ arr.sort(function(a, b) { return (a.title||'').localeCompare(b.title||''); }); break;
+ case 'price-asc':
+ arr.sort(function(a, b) { return parseFloat(a.price||0) - parseFloat(b.price||0); }); break;
+ case 'price-desc':
+ arr.sort(function(a, b) { return parseFloat(b.price||0) - parseFloat(a.price||0); }); break;
+ case 'color':
+ // Group by primary color tag (first color-ish tag)
+ arr.sort(function(a, b) {
+ return colorBucket(a).localeCompare(colorBucket(b)) || (a.title||'').localeCompare(b.title||'');
+ }); break;
+ case 'style':
+ arr.sort(function(a, b) {
+ var sa = (a.specs && a.specs.style) || '';
+ var sb = (b.specs && b.specs.style) || '';
+ return sa.localeCompare(sb) || (a.title||'').localeCompare(b.title||'');
+ }); break;
+ }
+ return arr;
+ }
+ var COLOR_TAGS = ['black','white','off-white','beige','tan','khaki','light beige','light gray','gray','dark gray','charcoal gray','slate gray','silver','steel blue','blue','dark teal','teal','navy','green','olive green','dark olive green','dark green','dark teal','red','burnt sienna','umber','dark brown','brown','light brown','copper','gold','yellow','amber'];
+ function colorBucket(p) {
+ var tags = (p.tags || []).map(function(t){return t.toLowerCase();});
+ for (var i = 0; i < COLOR_TAGS.length; i++) {
+ if (tags.indexOf(COLOR_TAGS[i]) !== -1) return COLOR_TAGS[i];
+ }
+ return 'zzz';
+ }
+
+ function renderGrid() {
+ var grid = document.getElementById('lafProductGrid');
+ var empty = document.getElementById('lafGridEmpty');
+ var more = document.getElementById('lafLoadMore');
+ var count = document.getElementById('lafResultCount');
+ var list = filtered();
+ grid.style.setProperty('--card-min', STATE.density + 'px');
+
+ empty.style.display = list.length === 0 ? '' : 'none';
+ grid.style.display = list.length === 0 ? 'none' : '';
+
+ var slice = list.slice(0, STATE.visible || STATE.pageSize);
+ if (!STATE.visible) STATE.visible = slice.length;
+ grid.innerHTML = slice.map(cardHtml).join('');
+ // Lazy-load image fade-in
+ grid.querySelectorAll('.laf-card-image img').forEach(function(img) {
+ if (img.complete && img.naturalHeight > 0) img.classList.add('loaded');
+ else img.addEventListener('load', function() { img.classList.add('loaded'); }, { once: true });
+ img.addEventListener('error', function() { img.classList.add('loaded'); img.style.opacity = 0.4; }, { once: true });
+ });
+ // Wire click → open product modal
+ grid.querySelectorAll('.laf-card').forEach(function(c) {
+ c.addEventListener('click', function() {
+ var id = c.getAttribute('data-id');
+ var p = STATE.products.find(function(x) { return x.id === id; });
+ if (p) lafOpenProduct(p);
+ });
+ });
+
+ count.textContent = list.length === 1
+ ? '1 fabric'
+ : (Math.min(slice.length, list.length).toLocaleString() + ' of ' + list.length.toLocaleString() + ' fabrics');
+ more.style.display = slice.length < list.length ? '' : 'none';
+ }
+ function escapeHtml(s) {
+ return (s || '').replace(/[&<>"']/g, function(c) {
+ return { '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[c];
+ });
+ }
+ function shortTitle(title) {
+ // Trim trailing " | Phillipe Romano" / " | Los Angeles Fabrics" for the card.
+ return (title || '').replace(/\s*\|\s*(Phillipe Romano|Los Angeles Fabrics|Commercial Fabric).*$/i, '').trim();
+ }
+ function priceLabel(p) {
+ if (!p.price) return '';
+ var pr = parseFloat(p.price);
+ if (pr <= 5) return 'Sample $' + pr.toFixed(2);
+ return '$' + pr.toFixed(2);
+ }
+ function cardHtml(p) {
+ var img = p.image || '';
+ var title = escapeHtml(shortTitle(p.title));
+ var color = escapeHtml(p.color || '');
+ var sku = escapeHtml(p.sku || '');
+ var collectionBadge = (p.specs && p.specs.collection) ? '<span class="laf-card-badge">' + escapeHtml(p.specs.collection) + '</span>' : '';
+ return '<article class="laf-card" data-id="' + escapeHtml(p.id) + '" tabindex="0" role="button" aria-label="' + title + '">' +
+ '<div class="laf-card-image">' + collectionBadge +
+ (img ? '<img loading="lazy" src="' + escapeHtml(img) + '" alt="' + title + '">' : '') +
+ '</div>' +
+ '<div class="laf-card-body">' +
+ '<div class="laf-card-title">' + title + '</div>' +
+ (color ? '<div class="laf-card-color">' + color + '</div>' : '') +
+ '<div class="laf-card-meta">' + sku + '</div>' +
+ '<div class="laf-card-price">' + priceLabel(p) + '</div>' +
+ '</div>' +
+ '</article>';
+ }
+
+ // ─── Toolbar wiring ─────────────────────────────────────────────
+ var search = document.getElementById('lafSearch');
+ var sortSel = document.getElementById('lafSort');
+ var dens = document.getElementById('lafDensity');
+ var densVal = document.getElementById('lafDensityVal');
+ var loadMore = document.getElementById('lafLoadMore');
+
+ sortSel.value = STATE.sort;
+ dens.value = STATE.density;
+ densVal.textContent = STATE.density;
+
+ var searchTimer;
+ search.addEventListener('input', function() {
+ clearTimeout(searchTimer);
+ searchTimer = setTimeout(function() {
+ STATE.query = search.value;
+ STATE.visible = 0;
+ renderGrid();
+ }, 120);
+ });
+ sortSel.addEventListener('change', function() {
+ STATE.sort = sortSel.value;
+ try { localStorage.setItem('laf_sort', STATE.sort); } catch (e) {}
+ STATE.visible = 0;
+ renderGrid();
+ });
+ dens.addEventListener('input', function() {
+ STATE.density = parseInt(dens.value, 10);
+ densVal.textContent = STATE.density;
+ try { localStorage.setItem('laf_density', String(STATE.density)); } catch (e) {}
+ var grid = document.getElementById('lafProductGrid');
+ grid.style.setProperty('--card-min', STATE.density + 'px');
+ });
+ loadMore.addEventListener('click', function() {
+ STATE.visible = (STATE.visible || STATE.pageSize) + STATE.pageSize;
+ renderGrid();
+ // Scroll-restore so the previous bottom card stays in view.
+ });
+
+ // Infinite scroll — load next page when near bottom.
+ window.addEventListener('scroll', function() {
+ if (!STATE.products.length) return;
+ var loadMoreBtn = document.getElementById('lafLoadMore');
+ if (!loadMoreBtn || loadMoreBtn.style.display === 'none') return;
+ var rect = loadMoreBtn.getBoundingClientRect();
+ if (rect.top < window.innerHeight + 800) {
+ STATE.visible = (STATE.visible || STATE.pageSize) + STATE.pageSize;
+ renderGrid();
+ }
+ }, { passive: true });
+
+ // ─── Product detail modal ───────────────────────────────────────
+ function specRow(label, value) {
+ if (!value) return '';
+ return '<div class="pi-spec-row"><span class="pi-spec-label">' + escapeHtml(label) + '</span><span class="pi-spec-value">' + escapeHtml(value) + '</span></div>';
+ }
+ window.lafOpenProduct = function(p) {
+ STATE.current = p;
+ var m = document.getElementById('lafProductModal');
+ document.getElementById('lafProductTitle').textContent = shortTitle(p.title);
+ document.getElementById('lafProductColor').textContent = p.color || '';
+ document.getElementById('lafProductPrice').textContent = priceLabel(p) + (p.sku ? ' · ' + p.sku : '');
+ document.getElementById('lafProductEyebrow').textContent = (p.specs && p.specs.collection) ? p.specs.collection : 'Los Angeles Fabrics';
+ // Description: editorial body only — specs go in the side panel (Steve's "no specs in body" rule).
+ document.getElementById('lafProductDesc').innerHTML = p.descriptionHtml || '<em style="opacity:0.5">No description available.</em>';
+ // Specs panel
+ var sp = p.specs || {};
+ document.getElementById('lafProductSpecs').innerHTML = [
+ specRow('Width', sp.width),
+ specRow('Length', sp.length),
+ specRow('Repeat', sp.repeat),
+ specRow('Content', sp.content),
+ specRow('Finish', sp.finish),
+ specRow('Durability', sp.durability),
+ specRow('Wyzenbeek', sp.wyzenbeek),
+ specRow('Flammability', sp.flammability),
+ specRow('Cleaning', sp.cleaning),
+ specRow('Style', sp.style),
+ specRow('Use', sp.usage),
+ specRow('Collection', sp.collection)
+ ].join('');
+ // Tags: cap at 12 most-relevant
+ var tags = (p.tags || []).filter(function(t) {
+ return ['Architectural','Commercial','Phillipe Romano','Fire Rated'].indexOf(t) === -1;
+ }).slice(0, 12);
+ document.getElementById('lafProductTags').innerHTML = tags.map(function(t) {
+ return '<span class="pi-tag">' + escapeHtml(t) + '</span>';
+ }).join('');
+ // Images (hero + thumbs)
+ var imgs = (p.images && p.images.length) ? p.images : (p.image ? [{ url: p.image, alt: p.title }] : []);
+ var hero = document.getElementById('lafProductHeroImg');
+ hero.src = imgs[0] ? imgs[0].url : '';
+ hero.alt = imgs[0] ? (imgs[0].alt || p.title) : p.title;
+ document.getElementById('lafProductThumbs').innerHTML = imgs.slice(0, 8).map(function(im, i) {
+ return '<img src="' + escapeHtml(im.url) + '" alt="' + escapeHtml(im.alt || '') + '" class="' + (i === 0 ? 'active' : '') + '" data-idx="' + i + '">';
+ }).join('');
+ document.getElementById('lafProductThumbs').querySelectorAll('img').forEach(function(t) {
+ t.addEventListener('click', function() {
+ document.getElementById('lafProductHeroImg').src = t.src;
+ document.getElementById('lafProductThumbs').querySelectorAll('img').forEach(function(o) { o.classList.remove('active'); });
+ t.classList.add('active');
+ });
+ });
+ m.classList.add('open');
+ document.body.style.overflow = 'hidden';
+ };
+ window.lafCloseProduct = function() {
+ document.getElementById('lafProductModal').classList.remove('open');
+ document.body.style.overflow = '';
+ };
+ window.lafOpenShopifyLink = function() {
+ var p = STATE.current;
+ if (!p) return;
+ var url = p.onlineStoreUrl || ('https://designerwallcoverings.com/products/' + p.handle);
+ window.open(url, '_blank', 'noopener');
+ };
+ window.lafRequestSampleFromProduct = function() {
+ var p = STATE.current;
+ lafCloseProduct();
+ // Re-use the existing sample-request modal; pre-fill with this product's color name.
+ try {
+ if (typeof openModal === 'function') {
+ openModal();
+ setTimeout(function() {
+ var picker = document.getElementById('selectedColor');
+ var hidden = document.getElementById('selectedColorInput');
+ if (hidden && p) hidden.value = (p.color || '') + ' (' + (p.sku || '') + ')';
+ if (picker && p) {
+ // Best-effort: try to match by color name
+ var match = Array.from(picker.options).find(function(o) {
+ return p.color && o.text.toLowerCase().indexOf(p.color.toLowerCase()) !== -1;
+ });
+ if (match) { picker.value = match.value; if (typeof updatePreview === 'function') updatePreview(); }
+ }
+ }, 60);
+ }
+ } catch (e) { /* fallback: just open modal */ if (typeof openModal === 'function') openModal(); }
+ };
+
+ // ─── Boot ───────────────────────────────────────────────────────
+ function boot() {
+ fetch('/data/products.json?v=' + Date.now())
+ .then(function(r) {
+ if (!r.ok) throw new Error('products.json fetch ' + r.status);
+ return r.json();
+ })
+ .then(function(d) {
+ STATE.products = (d && Array.isArray(d.products)) ? d.products : [];
+ renderTabs();
+ renderGrid();
+ })
+ .catch(function(err) {
+ console.error('[laf] product load failed:', err);
+ var rc = document.getElementById('lafResultCount');
+ if (rc) rc.textContent = 'product catalog temporarily unavailable';
+ });
+ }
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', boot);
+ } else {
+ boot();
+ }
+ })();
+ </script>
+<script src="/dw-fleet-nav.js" defer></script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..eaade70
--- /dev/null
+++ b/script.js
@@ -0,0 +1,769 @@
+// Safari compatibility polyfill for older versions
+if (!String.prototype.includes) {
+ String.prototype.includes = function(search, start) {
+ 'use strict';
+ if (search instanceof RegExp) {
+ throw TypeError('first argument must not be a RegExp');
+ }
+ if (start === undefined) { start = 0; }
+ return this.indexOf(search, start) !== -1;
+ };
+}
+
+if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function(callback, thisArg) {
+ var T, k;
+ if (this == null) {
+ throw new TypeError('this is null or not defined');
+ }
+ var O = Object(this);
+ var len = O.length >>> 0;
+ if (typeof callback !== 'function') {
+ throw new TypeError(callback + ' is not a function');
+ }
+ if (arguments.length > 1) {
+ T = thisArg;
+ }
+ k = 0;
+ while (k < len) {
+ var kValue;
+ if (k in O) {
+ kValue = O[k];
+ callback.call(T, kValue, k, O);
+ }
+ k++;
+ }
+ };
+}
+
+// Los Angeles Fabrics Colors Data - Complete Collection from Woeller
+// 35 new Los Angeles Fabrics colors launched 2026-04-20 — rendered in NEW ARRIVALS section
+var newLosAngelesFabricsColors = [
+ { name: "Amber", code: "T3Q8W" },
+ { name: "Blank Slate", code: "T3L0W" },
+ { name: "Bliss", code: "T3Q7W" },
+ { name: "Blush Rose", code: "T3RFW" },
+ { name: "Cement", code: "T3LAW" },
+ { name: "Colonial Blue", code: "T3C9W" },
+ { name: "Dusty Ranch", code: "T3FAW" },
+ { name: "Dusty Teal", code: "T3NGW" },
+ { name: "Fire", code: "T3QAW" },
+ { name: "Flagstone", code: "T3LKW" },
+ { name: "Henna", code: "T3G3W" },
+ { name: "Kiva", code: "T3ECW" },
+ { name: "Lipstick", code: "T3M4W" },
+ { name: "Noir", code: "T3A2W" },
+ { name: "Opal", code: "T3PQW" },
+ { name: "Pastel Blue", code: "T3N0W" },
+ { name: "Pastel Pink", code: "T3R6W" },
+ { name: "Persimmon", code: "T3M8W" },
+ { name: "Pink Lace", code: "T3RCW" },
+ { name: "Port Blue", code: "T3NJW" },
+ { name: "Raisin", code: "T3PRW" },
+ { name: "Royal Blue", code: "T3CCW" },
+ { name: "Sedona", code: "T3Q9W" },
+ { name: "Shale", code: "T3KDW" },
+ { name: "Snow", code: "T3B3W" },
+ { name: "Spruce", code: "T3PYW" },
+ { name: "Terracotta", code: "T3FCW" },
+ { name: "Thistle", code: "T3PMW" },
+ { name: "Tusk", code: "T3DJW" },
+ { name: "Umber", code: "T3G4W" },
+ { name: "Verdigris", code: "T3NHW" },
+ { name: "Vermilion", code: "T3MBW" },
+ { name: "Wheat", code: "T3FDW" },
+ { name: "Winter White", code: "T3B2W" },
+ { name: "Woodland", code: "T3H6W" }
+];
+
+var novaSuedeColors = [
+ { name: "Acorn", code: "T3H5W" },
+ { name: "Adobe", code: "T3K6W" },
+ { name: "African Violet", code: "T3SAW" },
+ { name: "Apricot", code: "T3Q4W" },
+ { name: "Arrow Wood", code: "T3F8W" },
+ { name: "Aubergine", code: "T3SCW" },
+ { name: "Baltic", code: "T3L6W" },
+ { name: "Beige", code: "T3E8W" },
+ { name: "Biscuit", code: "T3DHW" },
+ { name: "Bluestone", code: "T3N7W" },
+ { name: "Blush Pink", code: "T3C7W" },
+ { name: "Bone", code: "T3DSW" },
+ { name: "Brown", code: "T3F6W" },
+ { name: "Buff", code: "T3D1W" },
+ { name: "Buttercup", code: "T3DEW" },
+ { name: "Cane", code: "T3D3W" },
+ { name: "Carbon", code: "T3C6W" },
+ { name: "Cashew", code: "T3D2W" },
+ { name: "Cashmere", code: "T3E6W" },
+ { name: "Celadon", code: "T3P1W" },
+ { name: "Celery", code: "T3PFW" },
+ { name: "Champagne", code: "T3D4W" },
+ { name: "China Blue", code: "T3NBW" },
+ { name: "Chrome Grey", code: "T3PJW" },
+ { name: "Cinder", code: "T3LCW" },
+ { name: "Cloud", code: "T3L4W" },
+ { name: "Cocoa", code: "T315W" },
+ { name: "Coral", code: "T3Q3W" },
+ { name: "Cottage Grey", code: "T3LGW" },
+ { name: "Cream", code: "T3D9W" },
+ { name: "Cypress", code: "T3PHW" },
+ { name: "Dark Sand", code: "T3EAW" },
+ { name: "Dew", code: "T3F3W" },
+ { name: "Dove", code: "T3E2W" },
+ { name: "Dune", code: "T3F4W" },
+ { name: "Dusty Rose", code: "T3R2W" },
+ { name: "Ecru", code: "T3K4W" },
+ { name: "Fawn", code: "T3E4W" },
+ { name: "French Blue", code: "T3NCW" },
+ { name: "Gainsborough", code: "T3LLW" },
+ { name: "Garnet", code: "T3J6W" },
+ { name: "Gelato", code: "T3D7W" },
+ { name: "Goldstone", code: "T3F1W" },
+ { name: "Granite", code: "T3LOW" },
+ { name: "Grape", code: "TES8W" },
+ { name: "Green Marble", code: "T3P4W" },
+ { name: "Greystone", code: "T3LNW" },
+ { name: "Harvest", code: "T3E9W" },
+ { name: "Honey", code: "T3DDW" },
+ { name: "Hyacinth", code: "T3S2W" },
+ { name: "Iceberg", code: "T3LHW" },
+ { name: "Indigo", code: "T3C3W" },
+ { name: "Ivory", code: "T3E3W" },
+ { name: "Jade", code: "T3P6W" },
+ { name: "Lavender", code: "T3S3W" },
+ { name: "Light Amethyst", code: "T3S6W" },
+ { name: "Light Grey", code: "T3L7W" },
+ { name: "Linen", code: "T3D6W" },
+ { name: "Madison Grey", code: "T3L1W" },
+ { name: "Maize", code: "T3O3W" },
+ { name: "Maroon", code: "T3JAW" },
+ { name: "Meadow", code: "T3PIW" },
+ { name: "Melon", code: "T3Q6W" },
+ { name: "Milk", code: "T3DQW" },
+ { name: "Mint Julip", code: "T3P5W" },
+ { name: "Misty Dew", code: "T3PLW" },
+ { name: "Moss", code: "T3L3W" },
+ { name: "Mushroom", code: "T3K1W" },
+ { name: "Newport", code: "T3C2W" },
+ { name: "Nutmeg", code: "T3F9W" },
+ { name: "Ocean", code: "T3POW" },
+ { name: "Olive", code: "T3PEW" },
+ { name: "Oyster", code: "T3EBW" },
+ { name: "Pale Green", code: "T3P8W" },
+ { name: "Passion Blue", code: "T3NAW" },
+ { name: "Peach", code: "T3Q2W" },
+ { name: "Pebble", code: "T3K2W" },
+ { name: "Peppermint", code: "T3R4W" },
+ { name: "Peridot", code: "T3P7W" },
+ { name: "Periwinkle", code: "T3N6W" },
+ { name: "Plum Pleasure", code: "T3S5W" },
+ { name: "Porcelain", code: "T3DAW" },
+ { name: "Powder Grey", code: "T3E5W" },
+ { name: "Prairie", code: "T3KCW" },
+ { name: "Putty", code: "T3K3W" },
+ { name: "Raffia", code: "T3D5W" },
+ { name: "Royal Purple", code: "T3S4W" },
+ { name: "Ruby", code: "T3R6W" },
+ { name: "Sable", code: "T3F7W" },
+ { name: "Safari", code: "T3E7W" },
+ { name: "Saffron", code: "T3O2W" },
+ { name: "Sage", code: "T3P3W" },
+ { name: "Salt", code: "T3DBW" },
+ { name: "Sand", code: "T3D8W" },
+ { name: "Sandalwood", code: "T3F5W" },
+ { name: "Scarvar Wine", code: "T3R5W" },
+ { name: "Sea Blue", code: "T3N9W" },
+ { name: "Sea Grass", code: "T3P2W" },
+ { name: "Sea Breeze", code: "T3N8W" },
+ { name: "Seafoam", code: "T3N3W" },
+ { name: "Sienna", code: "T3Q7W" },
+ { name: "Sky Blue", code: "T3N5W" },
+ { name: "Smoke", code: "T3L5W" },
+ { name: "Soft Clay", code: "T3K7W" },
+ { name: "Solar Yellow", code: "T3O1W" },
+ { name: "Steel", code: "T3LBW" },
+ { name: "Stone Grey", code: "T3L2W" },
+ { name: "Stone Taupe", code: "T3K8W" },
+ { name: "Straw", code: "T3DCW" },
+ { name: "Strong White", code: "T3DAW" },
+ { name: "Sugar Plum", code: "T3S7W" },
+ { name: "Sweet Lilac", code: "T3S1W" },
+ { name: "Tan", code: "T3E1W" },
+ { name: "Tarragon", code: "T3PGW" },
+ { name: "Taupe", code: "T3K9W" },
+ { name: "Thyme", code: "T3PDW" },
+ { name: "Trench", code: "T3F2W" },
+ { name: "Vanilla", code: "T3D5W" }
+];
+
+// Populate dropdown as soon as script loads
+setTimeout(function() {
+ populateColorDropdown();
+}, 0);
+
+// Mobile menu toggle
+var mobileToggle = document.querySelector('.mobile-toggle');
+var navMenu = document.querySelector('.nav-menu');
+
+if (mobileToggle) {
+ mobileToggle.addEventListener('click', function() {
+ navMenu.classList.toggle('active');
+ });
+}
+
+// Close mobile menu when clicking a link
+var navLinks = document.querySelectorAll('.nav-menu a');
+for (var i = 0; i < navLinks.length; i++) {
+ navLinks[i].addEventListener('click', function() {
+ navMenu.classList.remove('active');
+ });
+}
+
+// Render helper: populate one grid from a color list (alpha sorted).
+function renderColorGrid(gridId, colorList) {
+ var grid = document.getElementById(gridId);
+ if (!grid) return 0;
+ grid.innerHTML = '';
+ var sorted = colorList.slice().sort(function(a, b) { return a.name.localeCompare(b.name); });
+ var count = 0;
+ for (var j = 0; j < sorted.length; j++) {
+ var color = sorted[j];
+ var fileName = color.name.toLowerCase().replace(/\s+/g, '-');
+ var imageUrl = '/images/colors/losangelesfabrics-' + fileName + '.jpg';
+ var colorItem = document.createElement('div');
+ colorItem.className = 'color-item';
+
+ // NEW badge — applies whenever we render the new-arrivals grid
+ if (gridId === 'newArrivalsGrid') {
+ var badge = document.createElement('div');
+ badge.className = 'new-badge';
+ badge.textContent = 'NEW';
+ colorItem.appendChild(badge);
+ }
+
+ var colorSwatch = document.createElement('div');
+ colorSwatch.className = 'color-swatch';
+ colorSwatch.style.cursor = 'pointer';
+ var imgElement = document.createElement('img');
+ imgElement.src = imageUrl;
+ imgElement.alt = 'Los Angeles Fabrics ' + color.name;
+ imgElement.style.width = '100%';
+ imgElement.style.height = '100%';
+ imgElement.style.objectFit = 'cover';
+ // Click on swatch image → drape the page in this color
+ (function(n, sw) {
+ colorSwatch.onclick = function(e) {
+ e.stopPropagation();
+ if (typeof applyDraperyColor === 'function') applyDraperyColor(n, sw);
+ };
+ })(color.name, imgElement);
+ colorSwatch.appendChild(imgElement);
+
+ var colorInfo = document.createElement('div');
+ colorInfo.className = 'color-info';
+ colorInfo.innerHTML = '<div class="color-name">' + color.name + '</div><div class="color-code">' + color.code + '</div>';
+
+ // Action buttons row: Drapery (recolor page bg) + Sample (open modal)
+ var actions = document.createElement('div');
+ actions.className = 'chip-actions';
+ var drapeBtn = document.createElement('button');
+ drapeBtn.className = 'drapery-btn';
+ drapeBtn.type = 'button';
+ drapeBtn.textContent = '🪟 Drapery';
+ (function(n, sw) {
+ drapeBtn.onclick = function(e) {
+ e.stopPropagation();
+ if (typeof applyDraperyColor === 'function') applyDraperyColor(n, sw);
+ };
+ })(color.name, imgElement);
+ actions.appendChild(drapeBtn);
+ var sampleBtn = document.createElement('button');
+ sampleBtn.className = 'sample-btn-chip';
+ sampleBtn.type = 'button';
+ sampleBtn.textContent = '📦 Sample';
+ (function(n, c, u) {
+ sampleBtn.onclick = function(e) {
+ e.stopPropagation();
+ if (typeof selectColor === 'function') selectColor(n, c, u);
+ else if (typeof openModal === 'function') {
+ openModal();
+ setTimeout(function() {
+ var sel = document.getElementById('selectedColor');
+ if (sel) { sel.value = 'Los Angeles Fabrics-' + n; if (typeof updatePreview === 'function') updatePreview(); }
+ }, 100);
+ }
+ };
+ })(color.name, color.code, imageUrl);
+ actions.appendChild(sampleBtn);
+ colorInfo.appendChild(actions);
+
+ colorItem.appendChild(colorSwatch);
+ colorItem.appendChild(colorInfo);
+ grid.appendChild(colorItem);
+ count++;
+ }
+ return count;
+}
+
+// Populate colors grid - New Arrivals section + Full Color Collection section
+function populateColors() {
+ var newCount = renderColorGrid('newArrivalsGrid', newLosAngelesFabricsColors);
+ var oldCount = renderColorGrid('colorsGrid', novaSuedeColors);
+ console.log('Loaded ' + newCount + ' new arrivals + ' + oldCount + ' older colors');
+ if (typeof updateColorCount === 'function') updateColorCount(newCount + oldCount);
+ return;
+ // --- dead code kept for reference; original single-grid loop follows ---
+ var colorsGrid = document.getElementById('colorsGrid');
+ if (!colorsGrid) {
+ console.error('Colors grid element not found');
+ return;
+ }
+ console.log('Loading all 118 Los Angeles Fabrics colors...');
+ var displayedCount = 0;
+
+ for (var j = 0; j < novaSuedeColors.length; j++) {
+ var color = novaSuedeColors[j];
+ // All images are now local with consistent naming
+ var fileName = color.name.toLowerCase().replace(/\s+/g, '-');
+ var imageUrl = '/images/colors/losangelesfabrics-' + fileName + '.jpg';
+
+ // Create color item directly (no need to test, all images exist)
+ var colorItem = document.createElement('div');
+ colorItem.className = 'color-item';
+ colorItem.onclick = (function(name, code, url) {
+ return function() { selectColor(name, code, url); };
+ })(color.name, color.code, imageUrl);
+
+ var colorSwatch = document.createElement('div');
+ colorSwatch.className = 'color-swatch';
+
+ var imgElement = document.createElement('img');
+ imgElement.src = imageUrl;
+ imgElement.alt = 'Los Angeles Fabrics ' + color.name;
+ imgElement.style.width = '100%';
+ imgElement.style.height = '100%';
+ imgElement.style.objectFit = 'cover';
+ colorSwatch.appendChild(imgElement);
+
+ var colorInfo = document.createElement('div');
+ colorInfo.className = 'color-info';
+ colorInfo.innerHTML = '<div class="color-name">' + color.name + '</div><div class="color-code">' + color.code + '</div>';
+
+ colorItem.appendChild(colorSwatch);
+ colorItem.appendChild(colorInfo);
+ colorsGrid.appendChild(colorItem);
+
+ displayedCount++;
+ }
+
+ // Update final count
+ updateColorCount(displayedCount);
+ console.log('Loaded all ' + displayedCount + ' colors successfully');
+}
+
+// Try loading image from multiple URLs
+function tryLoadImage(urls, index, color, callback) {
+ if (index >= urls.length) {
+ // No more URLs to try
+ callback(null);
+ return;
+ }
+
+ var testImg = new Image();
+ testImg.onload = function() {
+ // Success! Use this URL
+ callback(urls[index]);
+ };
+
+ testImg.onerror = function() {
+ // Try next URL
+ tryLoadImage(urls, index + 1, color, callback);
+ };
+
+ // Remove crossorigin to avoid CORS issues - we just need to test if image loads
+ // External images will load fine without crossorigin attribute for display purposes
+
+ testImg.src = urls[index];
+}
+
+// Update the displayed color count
+function updateColorCount(count) {
+ var subtitle = document.querySelector('.section-subtitle');
+ if (subtitle) {
+ subtitle.textContent = 'Choose from our collection of ' + count + ' premium colors';
+ }
+}
+
+// Get approximate color hex based on color name (fallback)
+function getColorHex(colorName) {
+ var colorMap = {
+ 'Acorn': '87714f',
+ 'Adobe': 'bd6439',
+ 'African Violet': '884f88',
+ 'Apricot': 'ffb87c',
+ 'Arctic': 'e6f3ff',
+ 'Ash': 'b4b4b4',
+ 'Atlantic': '006994',
+ 'Basalt': '5c5c5c',
+ 'Beach Glass': '96ded1',
+ 'Black': '000000',
+ 'Black Pearl': '1e1e1e',
+ 'Blush': 'ffb3ba',
+ 'Bone': 'f0e6d2',
+ 'Brandy': '87413f',
+ 'Bronze': 'cd7f32',
+ 'Burgundy': '800020',
+ 'Burnt Orange': 'cc5500',
+ 'Buttercup': 'f3e37c',
+ 'Butter Rum': 'c48c47',
+ 'Caramel': 'af6f09',
+ 'Carbon': '333333',
+ 'Cardinal': 'c41e3a',
+ 'Celery': 'b5e3b5',
+ 'Cement': '999999',
+ 'Chai': 'd2b48c',
+ 'Chalk': 'ffffff',
+ 'Champagne': 'f7e7ce',
+ 'Charcoal': '36454f',
+ 'Chestnut': '954535',
+ 'Chocolate': '7b3f00',
+ 'Clay': 'cc8855',
+ 'Cognac': '9a463d',
+ 'Concord': '6f4e7c',
+ 'Copper': 'b87333',
+ 'Coral': 'ff7f50',
+ 'Cordovan': '814141',
+ 'Cornflower': '6495ed',
+ 'Cranberry': '950714',
+ 'Cream': 'fffdd0',
+ 'Denim': '1560bd',
+ 'Desert': 'c19a6b',
+ 'Dove': 'd0d0d0',
+ 'Ebony': '3c3c3c',
+ 'Ecru': 'c2b280',
+ 'Eggplant': '614051',
+ 'Espresso': '4e312d',
+ 'Fawn': 'e5aa70',
+ 'Forest': '228b22',
+ 'Garnet': '733635',
+ 'Gold': 'ffd700',
+ 'Graphite': '404040',
+ 'Harvest': 'da8a67',
+ 'Hemp': '907874',
+ 'Honey': 'ec9706',
+ 'Hunter': '355e3b',
+ 'Ice': 'f0f8ff',
+ 'Indigo': '4b0082',
+ 'Ivory': 'fffff0',
+ 'Jade': '00a86b',
+ 'Jute': 'c8b88b',
+ 'Kelly': '4cbb17',
+ 'Latte': 'c8a882',
+ 'Madison Grey': '808080',
+ 'Mahogany': 'c04000',
+ 'Marine': '00477d',
+ 'Mauve': 'e0b0ff',
+ 'Mocha': '967969',
+ 'Mushroom': 'beaa9e',
+ 'Natural': 'f2e8d7',
+ 'Navy': '000080',
+ 'Nutmeg': '8b6f47',
+ 'Oatmeal': 'd8cfc4',
+ 'Ochre': 'cc7722',
+ 'Olive': '808000',
+ 'Oyster': 'e8ddc7',
+ 'Paprika': 'b22222',
+ 'Pecan': '9a6e2e',
+ 'Pewter': '8a8a8a',
+ 'Pistachio': '93c572',
+ 'Port': '3b0910',
+ 'Putty': 'c8c2ae',
+ 'Royal': '4169e1',
+ 'Sable': '75533d',
+ 'Saddle': '8b4513',
+ 'Sage': '87a96b',
+ 'Salmon': 'fa8072',
+ 'Sand': 'c2b280',
+ 'Sandstone': '786d5f',
+ 'Scarlet': 'ff2400',
+ 'Sea Foam': '93e0d1',
+ 'Sienna': '882d17',
+ 'Silver': 'c0c0c0',
+ 'Slate': '708090',
+ 'Smoke': 'bebebe',
+ 'Snow': 'fffafa',
+ 'Spice': '8b2500',
+ 'Sterling': 'd4d4d4',
+ 'Stone': '878e83',
+ 'Storm': '4f666a',
+ 'Straw': 'e4d96f',
+ 'Suede': 'a5815c',
+ 'Sunset': 'ffc87c',
+ 'Tan': 'd2b48c',
+ 'Taupe': '483c32',
+ 'Teal': '008080',
+ 'Terracotta': 'cc4125',
+ 'Thyme': '636f48',
+ 'Trench': '544333',
+ 'Vanilla': 'f3e5ab'
+ };
+
+ // Check if we have a direct match
+ if (colorMap[colorName]) {
+ return colorMap[colorName];
+ }
+
+ // Check partial matches
+ for (var key in colorMap) {
+ if (colorName.toLowerCase().indexOf(key.toLowerCase()) !== -1) {
+ return colorMap[key];
+ }
+ }
+
+ // Default neutral color
+ return '8b7355';
+}
+
+// Filter colors
+function filterColors() {
+ var searchValue = document.getElementById('colorSearch').value.toLowerCase();
+ var colorItems = document.querySelectorAll('.color-item');
+
+ for (var i = 0; i < colorItems.length; i++) {
+ var item = colorItems[i];
+ var colorName = item.querySelector('.color-name').textContent.toLowerCase();
+ var colorCode = item.querySelector('.color-code').textContent.toLowerCase();
+
+ if (colorName.indexOf(searchValue) !== -1 || colorCode.indexOf(searchValue) !== -1) {
+ item.style.display = 'block';
+ } else {
+ item.style.display = 'none';
+ }
+ }
+}
+
+// Select color for sample request
+function selectColor(name, code, imageUrl) {
+ var selectedColorDropdown = document.getElementById('selectedColor');
+ var colorPreview = document.getElementById('colorPreview');
+ var colorPreviewImage = document.getElementById('colorPreviewImage');
+ var colorPreviewName = document.getElementById('colorPreviewName');
+
+ // Update dropdown selection
+ selectedColorDropdown.value = name + ' (' + code + ')';
+
+ // Always show color preview with image
+ colorPreviewImage.src = imageUrl;
+ colorPreviewImage.alt = 'Los Angeles Fabrics ' + name;
+ colorPreviewName.textContent = name;
+ colorPreview.style.display = 'flex';
+
+ openContactModal();
+}
+
+// Modal functions
+function openContactModal() {
+ // Ensure dropdown is populated
+ var selectedColorDropdown = document.getElementById('selectedColor');
+ if (selectedColorDropdown && selectedColorDropdown.options.length <= 1) {
+ populateColorDropdown();
+ }
+
+ // If no color pre-selected, show a default image
+ if (!selectedColorDropdown.value) {
+ var colorPreviewImage = document.getElementById('colorPreviewImage');
+ var colorPreviewName = document.getElementById('colorPreviewName');
+
+ // Show first color as default preview
+ var firstColor = novaSuedeColors[0];
+ var fileName = firstColor.name.toLowerCase().replace(/\s+/g, '-');
+ colorPreviewImage.src = '/images/colors/losangelesfabrics-' + fileName + '.jpg';
+ colorPreviewImage.alt = 'Los Angeles Fabrics color selection';
+ colorPreviewName.textContent = 'Select a color from the dropdown';
+ }
+
+ document.getElementById('contactModal').style.display = 'block';
+}
+
+function closeContactModal() {
+ document.getElementById('contactModal').style.display = 'none';
+}
+
+// Close modal when clicking outside
+window.onclick = function(event) {
+ var modal = document.getElementById('contactModal');
+ if (event.target === modal) {
+ modal.style.display = 'none';
+ }
+}
+
+// Handle form submission — sends to our server which forwards via George Gmail agent
+function handleSubmit(event) {
+ event.preventDefault();
+
+ var form = event.target;
+ var submitBtn = form.querySelector('.submit-btn');
+ var originalText = submitBtn.textContent;
+
+ // Get form values using name attributes
+ var formData = {
+ name: form.elements['name'] ? form.elements['name'].value : '',
+ email: form.elements['email'] ? form.elements['email'].value : '',
+ company: form.elements['company'] ? form.elements['company'].value : '',
+ address: form.elements['address'] ? form.elements['address'].value : '',
+ city: form.elements['city'] ? form.elements['city'].value : '',
+ state: form.elements['state'] ? form.elements['state'].value : '',
+ zip: form.elements['zip'] ? form.elements['zip'].value : '',
+ color: document.getElementById('selectedColor').value || '',
+ message: form.elements['message'] ? form.elements['message'].value : ''
+ };
+
+ // Show loading state
+ submitBtn.textContent = 'Sending...';
+ submitBtn.disabled = true;
+
+ fetch('/api/send-sample-request', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(formData)
+ })
+ .then(function(response) { return response.json(); })
+ .then(function(result) {
+ if (result.success) {
+ submitBtn.textContent = 'Sent!';
+ setTimeout(function() {
+ form.reset();
+ closeContactModal();
+ submitBtn.textContent = originalText;
+ submitBtn.disabled = false;
+ }, 1500);
+ } else {
+ submitBtn.textContent = originalText;
+ submitBtn.disabled = false;
+ alert('There was an error: ' + (result.error || 'Please try again.'));
+ }
+ })
+ .catch(function(error) {
+ console.error('Error submitting form:', error);
+ submitBtn.textContent = originalText;
+ submitBtn.disabled = false;
+ alert('There was an error submitting your request. Please try again or contact us directly at info@DesignerWallcoverings.com');
+ });
+}
+
+// Smooth scrolling for navigation links
+var anchors = document.querySelectorAll('a[href^="#"]');
+for (var i = 0; i < anchors.length; i++) {
+ anchors[i].addEventListener('click', function (e) {
+ e.preventDefault();
+ var target = document.querySelector(this.getAttribute('href'));
+ if (target) {
+ // Fallback for older browsers
+ if ('scrollBehavior' in document.documentElement.style) {
+ target.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+ } else {
+ target.scrollIntoView();
+ }
+ }
+ });
+}
+
+// Initialize on page load
+document.addEventListener('DOMContentLoaded', function() {
+ populateColors();
+ populateColorDropdown();
+});
+
+// Also populate when window fully loads (backup)
+window.addEventListener('load', function() {
+ var selectElement = document.getElementById('selectedColor');
+ if (selectElement && selectElement.options.length <= 1) {
+ populateColorDropdown();
+ }
+});
+
+// Populate the color dropdown in the contact form
+function populateColorDropdown() {
+ var selectElement = document.getElementById('selectedColor');
+ if (!selectElement) {
+ console.log('Dropdown not found, will retry later');
+ // Try again in 100ms
+ setTimeout(populateColorDropdown, 100);
+ return;
+ }
+
+ // Check if already populated
+ if (selectElement.options.length > 100) {
+ console.log('Dropdown already populated');
+ return;
+ }
+
+ // Clear any existing options except the first
+ while (selectElement.options.length > 1) {
+ selectElement.remove(1);
+ }
+
+ // Add all colors as options
+ for (var k = 0; k < novaSuedeColors.length; k++) {
+ var color = novaSuedeColors[k];
+ var option = document.createElement('option');
+ option.value = color.name + ' (' + color.code + ')';
+ option.textContent = color.name + ' - ' + color.code;
+ selectElement.appendChild(option);
+ }
+
+ console.log('Successfully populated dropdown with ' + novaSuedeColors.length + ' colors');
+
+ // Add event listener to update preview when selection changes (only once)
+ if (!selectElement.hasAttribute('data-listener-added')) {
+ selectElement.setAttribute('data-listener-added', 'true');
+ selectElement.addEventListener('change', function() {
+ var selectedValue = this.value;
+ var colorPreviewImage = document.getElementById('colorPreviewImage');
+ var colorPreviewName = document.getElementById('colorPreviewName');
+
+ if (selectedValue) {
+ // Extract color name from the value
+ var colorName = selectedValue.split(' (')[0];
+ var color = null;
+ for (var m = 0; m < novaSuedeColors.length; m++) {
+ if (novaSuedeColors[m].name === colorName) {
+ color = novaSuedeColors[m];
+ break;
+ }
+ }
+
+ if (color) {
+ var fileName = color.name.toLowerCase().replace(/\s+/g, '-');
+ var imageUrl = '/images/colors/losangelesfabrics-' + fileName + '.jpg';
+
+ // Update preview
+ colorPreviewImage.src = imageUrl;
+ colorPreviewImage.alt = 'Los Angeles Fabrics ' + color.name;
+ colorPreviewName.textContent = color.name;
+ }
+ } else {
+ // Show prompt to select a color
+ var firstColor = novaSuedeColors[0];
+ var fileName = firstColor.name.toLowerCase().replace(/\s+/g, '-');
+ colorPreviewImage.src = '/images/colors/losangelesfabrics-' + fileName + '.jpg';
+ colorPreviewImage.alt = 'Los Angeles Fabrics color selection';
+ colorPreviewName.textContent = 'Select a color from the dropdown';
+ }
+ });
+ }
+}
+
+// Add scroll effect to navigation
+window.addEventListener('scroll', function() {
+ var navbar = document.querySelector('.navbar');
+ if (window.scrollY > 50) {
+ navbar.style.boxShadow = '0 4px 20px rgba(0,0,0,0.1)';
+ } else {
+ navbar.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
+ }
+});
\ No newline at end of file
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..1c684cd
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,845 @@
+:root {
+ --primary-color: #2c3e50;
+ --secondary-color: #8b7355;
+ --accent-color: #d4a574;
+ --text-dark: #333333;
+ --text-light: #666666;
+ --bg-light: #f8f7f5;
+ --bg-white: #ffffff;
+ --border-color: #e0e0e0;
+ --shadow: 0 2px 10px rgba(0,0,0,0.1);
+ --shadow-hover: 0 5px 20px rgba(0,0,0,0.15);
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Montserrat', sans-serif;
+ line-height: 1.6;
+ color: var(--text-dark);
+ background-color: var(--bg-white);
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 20px;
+}
+
+/* Navigation */
+.navbar {
+ background: var(--bg-white);
+ box-shadow: var(--shadow);
+ position: fixed;
+ width: 100%;
+ top: 0;
+ z-index: 1000;
+}
+
+.nav-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 1rem 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.logo h1 {
+ color: var(--primary-color);
+ font-size: 1.8rem;
+ font-weight: 700;
+}
+
+.logo span {
+ color: var(--accent-color);
+ font-size: 0.8rem;
+ vertical-align: super;
+}
+
+.mobile-toggle {
+ display: none;
+ flex-direction: column;
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.mobile-toggle span {
+ width: 25px;
+ height: 3px;
+ background: var(--primary-color);
+ margin: 3px 0;
+ transition: 0.3s;
+}
+
+.nav-menu {
+ display: flex;
+ list-style: none;
+ gap: 2rem;
+}
+
+.nav-menu a {
+ color: var(--text-dark);
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.3s;
+}
+
+.nav-menu a:hover {
+ color: var(--accent-color);
+}
+
+.cta-button {
+ background: var(--accent-color);
+ color: white !important;
+ padding: 0.5rem 1.5rem;
+ border-radius: 25px;
+ transition: background 0.3s;
+}
+
+.cta-button:hover {
+ background: var(--secondary-color);
+}
+
+/* Hero Section */
+.hero {
+ margin-top: 80px;
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
+ color: white;
+ padding: 100px 20px;
+ text-align: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.hero::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="%23ffffff" fill-opacity="0.1" d="M0,96L48,112C96,128,192,160,288,160C384,160,480,128,576,133.3C672,139,768,181,864,181.3C960,181,1056,139,1152,117.3C1248,96,1344,96,1392,96L1440,96L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path></svg>') no-repeat bottom;
+ background-size: cover;
+ opacity: 0.3;
+}
+
+.hero-content {
+ position: relative;
+ z-index: 1;
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.hero h2 {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+ font-weight: 700;
+}
+
+.hero p {
+ font-size: 1.2rem;
+ margin-bottom: 2rem;
+ opacity: 0.95;
+}
+
+.sample-btn {
+ background: var(--accent-color);
+ color: white;
+ border: none;
+ padding: 1rem 2.5rem;
+ font-size: 1.1rem;
+ border-radius: 30px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: all 0.3s;
+ box-shadow: 0 4px 15px rgba(0,0,0,0.2);
+}
+
+.sample-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(0,0,0,0.3);
+}
+
+.authorized-dealer {
+ margin-top: 3rem;
+ padding: 1rem;
+ background: rgba(255,255,255,0.1);
+ border-radius: 10px;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+}
+
+.authorized-dealer p {
+ font-size: 0.95rem;
+ margin: 0;
+}
+
+/* Features Section */
+.features-section {
+ padding: 80px 20px;
+ background: var(--bg-light);
+}
+
+.features-section h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 3rem;
+ color: var(--primary-color);
+}
+
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 2rem;
+}
+
+.feature-card {
+ background: white;
+ padding: 2rem;
+ border-radius: 10px;
+ box-shadow: var(--shadow);
+ transition: transform 0.3s, box-shadow 0.3s;
+ text-align: center;
+}
+
+.feature-card:hover {
+ transform: translateY(-5px);
+ box-shadow: var(--shadow-hover);
+}
+
+.feature-icon {
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+}
+
+.feature-card h3 {
+ color: var(--primary-color);
+ margin-bottom: 1rem;
+}
+
+.feature-card p {
+ color: var(--text-light);
+}
+
+/* Installation & Care Section */
+.installation-section {
+ padding: 80px 20px;
+ background: white;
+}
+
+.installation-section h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ color: var(--primary-color);
+}
+
+.info-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 2rem;
+ margin-top: 3rem;
+}
+
+.info-card {
+ background: var(--bg-light);
+ padding: 2rem;
+ border-radius: 10px;
+ text-align: center;
+ transition: transform 0.3s, box-shadow 0.3s;
+}
+
+.info-card:hover {
+ transform: translateY(-5px);
+ box-shadow: var(--shadow-hover);
+}
+
+.info-icon {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+}
+
+.info-card h3 {
+ color: var(--primary-color);
+ margin-bottom: 1rem;
+ font-size: 1.3rem;
+}
+
+.info-card p {
+ color: var(--text-light);
+ margin-bottom: 1.5rem;
+ line-height: 1.6;
+}
+
+.info-link {
+ color: var(--accent-color);
+ text-decoration: none;
+ font-weight: 600;
+ transition: color 0.3s;
+}
+
+.info-link:hover {
+ color: var(--secondary-color);
+ text-decoration: underline;
+}
+
+.care-highlight {
+ background: var(--bg-light);
+ padding: 2.5rem;
+ border-radius: 10px;
+ margin-top: 3rem;
+ text-align: center;
+ border-left: 4px solid var(--accent-color);
+}
+
+.care-highlight h3 {
+ color: var(--primary-color);
+ margin-bottom: 1rem;
+}
+
+.care-highlight p {
+ color: var(--text-light);
+ max-width: 800px;
+ margin: 0 auto;
+ line-height: 1.8;
+}
+
+/* Specifications Section */
+.specifications {
+ padding: 80px 20px;
+ background: var(--bg-light);
+}
+
+.specifications h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 3rem;
+ color: var(--primary-color);
+}
+
+.specs-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 2rem;
+ max-width: 900px;
+ margin: 0 auto;
+}
+
+.spec-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem;
+ border-bottom: 1px solid var(--border-color);
+ gap: 2rem;
+}
+
+.spec-label {
+ font-weight: 600;
+ color: var(--text-dark);
+ flex-shrink: 0;
+}
+
+.spec-value {
+ color: var(--text-light);
+ text-align: right;
+ flex-grow: 1;
+}
+
+/* Colors Section */
+.colors-section {
+ padding: 80px 20px;
+ background: var(--bg-light);
+}
+
+.colors-section h2 {
+ text-align: center;
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ color: var(--primary-color);
+}
+
+.section-subtitle {
+ text-align: center;
+ color: var(--text-light);
+ margin-bottom: 2rem;
+}
+
+.color-filter {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+#colorSearch {
+ padding: 0.75rem 1.5rem;
+ border: 1px solid var(--border-color);
+ border-radius: 25px;
+ width: 100%;
+ max-width: 400px;
+ font-size: 1rem;
+}
+
+.colors-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: 1.5rem;
+}
+
+.color-item {
+ background: white;
+ border-radius: 10px;
+ overflow: hidden;
+ box-shadow: var(--shadow);
+ transition: transform 0.3s, box-shadow 0.3s;
+ cursor: pointer;
+}
+
+.color-item:hover {
+ transform: translateY(-5px);
+ box-shadow: var(--shadow-hover);
+}
+
+.color-item:active {
+ transform: translateY(-3px);
+}
+
+.color-swatch {
+ height: 120px;
+ position: relative;
+ overflow: hidden;
+ background: #f5f5f5;
+}
+
+.color-swatch img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.3s;
+}
+
+.color-item:hover .color-swatch img {
+ transform: scale(1.1);
+}
+
+.color-info {
+ padding: 1rem;
+ text-align: center;
+}
+
+.color-name {
+ font-weight: 600;
+ color: var(--text-dark);
+ margin-bottom: 0.25rem;
+}
+
+.color-code {
+ font-size: 0.85rem;
+ color: var(--text-light);
+}
+
+/* Modal */
+.modal {
+ display: none;
+ position: fixed;
+ z-index: 2000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.5);
+ -webkit-backdrop-filter: blur(5px);
+ backdrop-filter: blur(5px);
+}
+
+.modal-content {
+ background-color: white;
+ margin: 30px auto;
+ padding: 2rem;
+ border-radius: 15px;
+ width: 90%;
+ max-width: 1200px;
+ max-height: 90vh;
+ overflow-y: auto;
+ position: relative;
+ animation: slideIn 0.3s ease;
+}
+
+@keyframes slideIn {
+ from {
+ transform: translateY(-50px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+.close {
+ color: var(--text-light);
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: color 0.3s;
+}
+
+.close:hover {
+ color: var(--primary-color);
+}
+
+.modal h2 {
+ color: var(--primary-color);
+ margin-bottom: 1.5rem;
+}
+
+.form-group {
+ margin-bottom: 1.5rem;
+}
+
+/* Two-column layout for form fields */
+@media (min-width: 600px) {
+ #contactForm {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+ }
+
+ #contactForm .form-group:nth-child(3), /* Company */
+ #contactForm .form-group:nth-child(4), /* Address */
+ #contactForm .form-group:nth-child(8), /* Scope */
+ #contactForm .form-group:nth-child(9), /* Color */
+ #contactForm button {
+ grid-column: span 2;
+ }
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ color: var(--text-dark);
+}
+
+.form-group input,
+.form-group select,
+.form-group textarea {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid var(--border-color);
+ border-radius: 5px;
+ font-size: 1rem;
+ transition: border-color 0.3s;
+ background: white;
+ font-family: inherit;
+}
+
+.form-group input:focus,
+.form-group select:focus,
+.form-group textarea:focus {
+ outline: none;
+ border-color: var(--accent-color);
+}
+
+.form-group select {
+ cursor: pointer;
+}
+
+.form-group textarea {
+ resize: vertical;
+ min-height: 80px;
+}
+
+.color-preview {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+ padding: 1.5rem;
+ background: var(--bg-light);
+ border-radius: 8px;
+ margin-bottom: 1.5rem;
+ border: 1px solid var(--border-color);
+}
+
+.color-preview img {
+ width: 200px;
+ height: 200px;
+ border-radius: 8px;
+ object-fit: cover;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+}
+
+.color-preview span {
+ font-weight: 600;
+ color: var(--text-dark);
+ font-size: 1.2rem;
+ text-align: center;
+}
+
+.color-dropdown-group {
+ width: 100%;
+ max-width: 300px;
+ margin-top: 1rem;
+}
+
+.color-dropdown-group label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: var(--text-dark);
+ font-size: 0.95rem;
+}
+
+.color-dropdown-group select {
+ width: 100%;
+ padding: 0.75rem;
+ border: 2px solid var(--border-color);
+ border-radius: 5px;
+ font-size: 1rem;
+ background: white;
+ cursor: pointer;
+ transition: border-color 0.3s;
+ -webkit-appearance: none;
+ appearance: none;
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-position: right 0.75rem center;
+ background-size: 1.2em;
+ padding-right: 2.5rem;
+}
+
+.color-dropdown-group select:hover {
+ border-color: var(--accent-color);
+}
+
+.color-dropdown-group select:focus {
+ outline: none;
+ border-color: var(--accent-color);
+ box-shadow: 0 0 0 3px rgba(221, 171, 30, 0.1);
+}
+
+.submit-btn {
+ width: 100%;
+ background: var(--accent-color);
+ color: white;
+ border: none;
+ padding: 1rem;
+ font-size: 1.1rem;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: background 0.3s;
+}
+
+.submit-btn:hover {
+ background: var(--secondary-color);
+}
+
+/* Footer */
+footer {
+ background: var(--primary-color);
+ color: white;
+ padding: 3rem 20px 1rem;
+}
+
+.footer-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 2rem;
+ margin-bottom: 2rem;
+}
+
+.footer-section h3 {
+ margin-bottom: 1rem;
+}
+
+.footer-section p {
+ opacity: 0.9;
+ margin-bottom: 0.5rem;
+}
+
+.footer-section ul {
+ list-style: none;
+}
+
+.footer-section ul li {
+ margin-bottom: 0.5rem;
+}
+
+.footer-section a {
+ color: white;
+ text-decoration: none;
+ opacity: 0.9;
+ transition: opacity 0.3s;
+}
+
+.footer-section a:hover {
+ opacity: 1;
+}
+
+.footer-sample-btn {
+ background: var(--accent-color);
+ color: white;
+ border: none;
+ padding: 0.75rem 1.5rem;
+ border-radius: 25px;
+ cursor: pointer;
+ margin-top: 1rem;
+ font-weight: 600;
+ transition: background 0.3s;
+}
+
+.footer-sample-btn:hover {
+ background: var(--secondary-color);
+}
+
+.footer-bottom {
+ text-align: center;
+ padding-top: 2rem;
+ border-top: 1px solid rgba(255,255,255,0.2);
+ opacity: 0.8;
+}
+
+.authorized {
+ font-style: italic;
+ opacity: 0.95;
+ margin-top: 1rem;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .mobile-toggle {
+ display: flex;
+ }
+
+ .nav-menu {
+ position: fixed;
+ left: -100%;
+ top: 70px;
+ flex-direction: column;
+ background-color: white;
+ width: 100%;
+ text-align: center;
+ transition: 0.3s;
+ box-shadow: 0 10px 27px rgba(0,0,0,0.05);
+ padding: 2rem 0;
+ }
+
+ .nav-menu.active {
+ left: 0;
+ }
+
+ .hero h2 {
+ font-size: 2rem;
+ }
+
+ .hero p {
+ font-size: 1rem;
+ }
+
+ .features-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .specs-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .colors-grid {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ }
+
+ .footer-content {
+ grid-template-columns: 1fr;
+ text-align: center;
+ }
+
+ .color-preview {
+ padding: 1.25rem;
+ }
+
+ .color-preview img {
+ width: 180px;
+ height: 180px;
+ }
+
+ .color-dropdown-group {
+ max-width: 280px;
+ }
+}
+
+@media (max-width: 480px) {
+ .hero h2 {
+ font-size: 1.5rem;
+ }
+
+ .colors-grid {
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+ gap: 1rem;
+ }
+
+ .color-swatch {
+ height: 80px;
+ }
+
+ .modal-content {
+ margin: 20px;
+ width: calc(100% - 40px);
+ }
+
+ .color-preview {
+ padding: 1rem;
+ margin-bottom: 1rem;
+ }
+
+ .color-preview img {
+ width: 150px;
+ height: 150px;
+ }
+
+ .color-preview span {
+ font-size: 1rem;
+ }
+
+ .color-dropdown-group {
+ max-width: 100%;
+ margin-top: 0.75rem;
+ }
+
+ .color-dropdown-group label {
+ font-size: 0.9rem;
+ }
+
+ .color-dropdown-group select {
+ padding: 0.65rem;
+ font-size: 16px; /* Prevents zoom on iOS */
+ min-height: 44px; /* Touch-friendly minimum height */
+ background-position: right 0.65rem center;
+ }
+
+ /* Ensure form inputs are also touch-friendly on mobile */
+ .form-group input,
+ .form-group select,
+ .form-group textarea {
+ font-size: 16px; /* Prevents zoom on iOS */
+ min-height: 44px; /* Touch-friendly minimum height */
+ }
+
+ /* Make close button easier to tap on mobile */
+ .modal .close {
+ width: 44px;
+ height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2rem;
+ }
+}
\ No newline at end of file
(oldest)
·
back to Losangelesfabrics Tmp
·
Remove New Arrivals — 2026 section per Steve 3c8baa1 →