← 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>