← back to Losangelesfabrics Tmp
index.html
3797 lines
<!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>
<!-- New Arrivals section removed 2026-05-18 per Steve -->
<!-- ───────────────────────────────────────────────────────────────
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>