← back to Thedesignerlibrary
public/index.html
257 lines
<!doctype html>
<html lang="en">
<head>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5278231299883833" crossorigin="anonymous"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Designer Library — Wallcovering Collections, Brands, Styles & Hues</title>
<meta name="description" content="A 3D library of designer wallcoverings — browse 84,000+ designs as books on shelves, organized by hue, brand, collection and style. Pull a book, flip through, click into the store.">
<link rel="canonical" href="https://thedesignerlibrary.com/">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<meta property="og:type" content="website">
<meta property="og:url" content="https://thedesignerlibrary.com/">
<meta property="og:title" content="The Designer Library — Wallcovering Collections, Brands, Styles & Hues">
<meta property="og:description" content="A 3D library of designer wallcoverings — browse 84,000+ designs as books on shelves, organized by hue, brand, collection and style.">
<meta property="og:image" content="https://thedesignerlibrary.com/og-image.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:site_name" content="The Designer Library">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="The Designer Library — Wallcovering Collections, Brands, Styles & Hues">
<meta name="twitter:description" content="A 3D library of designer wallcoverings — 84,000+ designs organized by hue, brand, collection and style.">
<meta name="twitter:image" content="https://thedesignerlibrary.com/og-image.jpg">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": "https://thedesignerlibrary.com/#website",
"url": "https://thedesignerlibrary.com/",
"name": "The Designer Library",
"description": "A 3D library of designer wallcoverings — 84,000+ designs organized by hue, brand, collection and style.",
"potentialAction": {
"@type": "SearchAction",
"target": { "@type": "EntryPoint", "urlTemplate": "https://thedesignerlibrary.com/browse?q={search_term_string}" },
"query-input": "required name=search_term_string"
}
},
{
"@type": "Organization",
"@id": "https://thedesignerlibrary.com/#organization",
"name": "The Designer Library",
"url": "https://thedesignerlibrary.com/",
"sameAs": ["https://www.designerwallcoverings.com"]
}
]
}
</script>
<script type="importmap">{ "imports": { "three": "/vendor/three-0.160.0.module.js" } }</script>
<style>
:root { --bg:#171310; --ink:#efe8db; --dim:#a99e8d; --brass:#c9a227; --walnut:#3a2e24; }
* { box-sizing:border-box; }
html,body { margin:0; height:100%; background:var(--bg); color:var(--ink);
font:15px/1.5 Georgia, 'Times New Roman', serif; overflow:hidden; }
#shelf { position:fixed; inset:0; }
header { position:fixed; top:0; left:0; right:0; z-index:10; display:flex; align-items:center;
justify-content:space-between; padding:16px 22px; pointer-events:none; }
.wordmark { pointer-events:auto; text-decoration:none; color:var(--ink); letter-spacing:.14em;
font-size:clamp(30px, 3.2vw, 48px); font-weight:600; text-transform:uppercase; white-space:nowrap; }
.wordmark em { color:var(--brass); font-style:normal; }
.tagline { font-style:italic; color:var(--dim); font-size:13px; margin-left:14px; letter-spacing:.04em; }
#burger { pointer-events:auto; background:none; border:1px solid rgba(201,162,39,.35); border-radius:50%;
width:42px; height:42px; display:flex; align-items:center; justify-content:center;
color:var(--ink); font-size:18px; cursor:pointer; }
#burger:hover { border-color:var(--brass); color:var(--brass); }
nav#menu { position:fixed; top:0; right:0; bottom:0; width:270px; background:#211b16; z-index:30;
transform:translateX(100%); transition:transform .25s ease; padding:70px 26px; }
nav#menu.open { transform:none; }
nav#menu a { display:block; color:var(--ink); text-decoration:none; padding:11px 0; font-size:16px;
border-bottom:1px solid rgba(255,255,255,.07); }
nav#menu a:hover { color:var(--brass); }
#menuClose { position:absolute; top:16px; right:18px; background:none; border:0; color:var(--dim); font-size:22px; cursor:pointer; }
.bar { position:fixed; top:96px; left:0; right:0; z-index:9; display:flex; gap:8px; justify-content:center;
align-items:center; flex-wrap:wrap; padding:0 16px; }
.tab { pointer-events:auto; background:rgba(33,27,22,.85); color:var(--dim); border:1px solid rgba(201,162,39,.25);
border-radius:999px; padding:7px 18px; font:600 12px Georgia, serif; letter-spacing:.14em; cursor:pointer; }
.tab.on { color:#171310; background:var(--brass); border-color:var(--brass); }
.tab:hover:not(.on) { color:var(--ink); }
select#brandPick { display:none; background:rgba(33,27,22,.9); color:var(--ink); border:1px solid rgba(201,162,39,.35);
border-radius:999px; padding:7px 12px; font:600 12px Georgia, serif; max-width:220px; }
select#brandPick.show { display:inline-block; }
input#search { background:rgba(33,27,22,.85); color:var(--ink); border:1px solid rgba(255,255,255,.14);
border-radius:999px; padding:7px 14px; font:13px Georgia, serif; width:180px; }
input#search::placeholder { color:var(--dim); }
/* footer content lives LEFT so the DW-network ad card (bottom-right) never
covers the count or the interaction hints */
footer { position:fixed; bottom:0; left:0; right:340px; z-index:8; display:flex; justify-content:flex-start;
gap:18px; align-items:center; padding:10px 22px; color:var(--dim); font-size:11px; font-style:italic;
letter-spacing:.06em; opacity:.8; pointer-events:none; }
footer a { pointer-events:auto; color:var(--dim); }
#count { pointer-events:none; }
#reader { position:fixed; bottom:44px; left:0; right:0; z-index:8; text-align:center; color:var(--dim);
font-size:13px; display:none; }
#loading { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; z-index:5;
color:var(--dim); font-style:italic; letter-spacing:.08em; }
@media (max-width:640px){
.tagline{display:none} input#search{width:120px}
.wordmark{font-size:17px; letter-spacing:.14em;}
#burger{width:36px; height:36px; font-size:15px;}
.bar{top:58px;}
footer{bottom:96px; right:0; flex-wrap:wrap; gap:6px;}
#reader{bottom:140px;}
}
</style>
</head>
<body>
<header>
<div><a class="wordmark" href="/">The Designer <em>Library</em></a><span class="tagline">Every collection, shelved.</span></div>
<button id="burger" aria-label="Menu">☰</button>
</header>
<nav id="menu">
<button id="menuClose" aria-label="Close">✕</button>
<a href="/" data-shelf="hues">Shelf · Hues</a>
<a href="/" data-shelf="brands">Shelf · Brands</a>
<a href="/" data-shelf="collections">Shelf · Collections</a>
<a href="/" data-shelf="styles">Shelf · Styles</a>
<a href="/browse">Browse the full catalog</a>
<a href="https://www.designerwallcoverings.com" rel="nofollow sponsored">Designer Wallcoverings ↗</a>
</nav>
<div class="bar">
<button class="tab" data-shelf="hues">HUES</button>
<button class="tab" data-shelf="brands">BRANDS</button>
<button class="tab" data-shelf="collections">COLLECTIONS</button>
<button class="tab" data-shelf="styles">STYLES</button>
<select id="brandPick" aria-label="Pick a brand"></select>
<input id="search" list="bookNames" placeholder="find a book…" autocomplete="off">
<datalist id="bookNames"></datalist>
</div>
<div id="shelf"></div>
<div id="loading">fetching the library…</div>
<div id="reader"></div>
<!-- IG-EMBED:START (TK-11322) -->
<section class="ig-embed" aria-label="Follow us on Instagram" style="max-width:1120px;margin:44px auto;padding:0 20px">
<h2 style="font-family:Georgia,'Times New Roman',serif;font-weight:500;letter-spacing:.02em;text-align:center;margin:0 0 20px">Follow <a href="https://www.instagram.com/thedesignerlibrary/" target="_blank" rel="noopener noreferrer">@thedesignerlibrary</a> on Instagram</h2>
<div class="ig-embed__row" style="display:flex;gap:16px;flex-wrap:wrap;justify-content:center;align-items:flex-start">
<blockquote class="instagram-media" data-instgrm-permalink="https://www.instagram.com/p/DdFzJREGxuh/" data-instgrm-version="14" style="margin:0;min-width:250px;max-width:328px;width:100%"></blockquote>
<blockquote class="instagram-media" data-instgrm-permalink="https://www.instagram.com/p/DdDPrYAm28t/" data-instgrm-version="14" style="margin:0;min-width:250px;max-width:328px;width:100%"></blockquote>
<blockquote class="instagram-media" data-instgrm-permalink="https://www.instagram.com/p/DdCqcKLmeBK/" data-instgrm-version="14" style="margin:0;min-width:250px;max-width:328px;width:100%"></blockquote>
</div>
</section>
<script async src="//www.instagram.com/embed.js"></script>
<!-- IG-EMBED:END -->
<footer>
<span id="hints">scroll / drag the shelf · click a spine to open · ←/→ flip · Esc reshelves</span>
<span id="count"></span>
<div style="text-align:center;padding:8px 0;font-size:11px"><a href="/privacy.html" style="color:inherit;opacity:.7;text-decoration:none">Privacy Policy</a></div>
</footer>
<script type="module">
import { BookshelfCatalog } from '/bookshelf.js';
const $ = (s) => document.querySelector(s);
const shelfEl = $('#shelf'), loading = $('#loading'), reader = $('#reader'), countEl = $('#count');
let shelf = null, currentBooks = [], currentShelf = null;
const TOUCH = matchMedia('(pointer: coarse)').matches;
const THEME = {
background: '#171310', shelfColor: '#3a2e24', accent: '#c9a227',
onOpen(book) { reader.style.display = 'block'; reader.textContent = `${book.title} — ${book.subtitle || ''}`; },
onClose() { reader.style.display = 'none'; },
onPageChange(book, i) {
const p = book.pages[i];
reader.textContent = p ? `${book.title} · ${p.title}` : book.title;
if (window.gtag) gtag('event', 'page_flip', { book: book.title, page: i });
},
onLinkClick(page, book) { if (window.gtag) gtag('event', 'store_click', { book: book.title, item: page.title }); return true; },
};
async function loadShelf(name, brand) {
loading.style.display = 'flex'; loading.textContent = 'fetching the library…';
const url = name === 'collections' && brand
? `/api/shelf/collections?brand=${encodeURIComponent(brand)}`
: `/api/shelf/${name}`;
const data = await fetch(url).then(r => r.json()).catch(() => ({ books: [] }));
if (name === 'collections' && !brand) {
// first entry into collections: populate the brand picker, show the first brand
const pick = $('#brandPick');
pick.replaceChildren(...data.brands.map(b => new Option(b, b)));
pick.classList.add('show');
return loadShelf('collections', data.brands[0]);
}
if (name !== 'collections') $('#brandPick').classList.remove('show');
currentBooks = data.books || [];
currentShelf = name;
if (shelf) { shelf.destroy(); shelf = null; }
reader.style.display = 'none';
if (currentBooks.length) shelf = new BookshelfCatalog(shelfEl, currentBooks, THEME);
loading.style.display = 'none';
// design count = the LAST number in the subtitle — collections subtitles are
// "Brand · 12", hues/brands are "23,799 designs"; a leading parseInt gets NaN
const total = currentBooks.reduce((n, b) => {
const nums = (b.subtitle || '').match(/[\d,]+/g);
return n + (nums ? parseInt(nums[nums.length - 1].replace(/,/g, ''), 10) || 0 : 0);
}, 0);
countEl.textContent = `${currentBooks.length} books` + (total ? ` · ${total.toLocaleString()} designs` : '');
$('#bookNames').replaceChildren(...currentBooks.map(b => {
const o = document.createElement('option'); o.value = b.title; return o;
}));
document.querySelectorAll('.tab').forEach(t => t.classList.toggle('on', t.dataset.shelf === name));
if (window.gtag) gtag('event', 'shelf_view', { shelf: name, brand: brand || '' });
history.replaceState(null, '', name === 'hues' ? '/' : `/#${name}${brand ? ':' + encodeURIComponent(brand) : ''}`);
}
document.querySelectorAll('.tab').forEach(t => t.onclick = () => loadShelf(t.dataset.shelf));
$('#brandPick').onchange = (e) => loadShelf('collections', e.target.value);
$('#search').addEventListener('change', (e) => {
const term = e.target.value.trim().toLowerCase();
if (!term) return;
// exact, then prefix, then substring against the current shelf's books;
// no book match → fall through to a full catalog search on /browse
let i = currentBooks.findIndex(b => b.title.toLowerCase() === term);
if (i < 0) i = currentBooks.findIndex(b => b.title.toLowerCase().startsWith(term));
if (i < 0) i = currentBooks.findIndex(b => b.title.toLowerCase().includes(term));
if (i >= 0 && shelf) { shelf.goToBook(i); setTimeout(() => shelf.openBook(i), 600); }
else location.href = '/browse?q=' + encodeURIComponent(e.target.value.trim());
});
if (TOUCH) $('#hints').textContent = 'drag the shelf · tap a spine to open · tap the page to flip · tap outside the book to reshelve';
$('#burger').onclick = () => $('#menu').classList.add('open');
$('#menuClose').onclick = () => $('#menu').classList.remove('open');
document.querySelectorAll('#menu a[data-shelf]').forEach(a => a.onclick = (e) => {
e.preventDefault(); $('#menu').classList.remove('open'); loadShelf(a.dataset.shelf);
});
// no WebGL → browse grid is the site
function webglOK() {
try { const c = document.createElement('canvas'); return !!(c.getContext('webgl2') || c.getContext('webgl')); }
catch { return false; }
}
if (!webglOK()) { location.replace('/browse'); }
else {
const h = location.hash.replace('#', '');
const [name, brand] = h.split(':');
loadShelf(['hues', 'brands', 'collections', 'styles'].includes(name) ? name : 'hues',
brand ? decodeURIComponent(brand) : undefined);
}
</script>
<!-- GA4: The Designer Library property (549043503) under the DesignerWallcoverings.com account -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LPY7H5RZ20"></script>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}
gtag('js', new Date()); gtag('config', 'G-LPY7H5RZ20');</script>
<!-- Meta Pixel: Designer Wallcoverings (1431180262113856) -->
<script>!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init','1431180262113856');fbq('track','PageView');</script>
<noscript><img height="1" width="1" style="display:none" alt=""
src="https://www.facebook.com/tr?id=1431180262113856&ev=PageView&noscript=1"></noscript>
<!-- DW-network ad slot (ads.agentabrams.com; links are rel="nofollow sponsored") -->
<script src="https://ads.agentabrams.com/embed.js" data-slot="sidebar-rail"
data-site="thedesignerlibrary.com" data-context="wallcovering,wallpaper,designer,library"></script>
</body>
</html>