From 1cb6f8ff844efee9c35ceed9c3abc22a0471bf1c Mon Sep 17 00:00:00 2001
From: David Ashby <delta.mu.alpha@gmail.com>
Date: Sat, 21 Dec 2024 14:06:48 -0500
Subject: [PATCH] properish build system, drop hash-based routing

---
 .gitignore               |   1 +
 Dockerfile               |   5 +-
 index.html               | 968 ---------------------------------------
 index.js                 | 128 +++++-
 index.tmpl.html          | 469 +++++++++++++++++++
 css/site.css => site.css |   2 +-
 stores.json              | 363 ++++++++++-----
 7 files changed, 834 insertions(+), 1102 deletions(-)
 delete mode 100644 index.html
 create mode 100644 index.tmpl.html
 rename css/site.css => site.css (99%)

diff --git a/.gitignore b/.gitignore
index 9daa824..210c9c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 .DS_Store
 node_modules
+build
diff --git a/Dockerfile b/Dockerfile
index 661d2d5..5c79023 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,4 @@ RUN npm install && node ./index.js
 
 FROM caddy:2.8.4
 
-COPY img /usr/share/caddy/img
-COPY css /usr/share/caddy/css
-COPY stores.json robots.txt /usr/share/caddy/
-COPY --from=builder /src/index.html /usr/share/caddy/index.html
+COPY --from=builder /src/build /usr/share/caddy
diff --git a/index.html b/index.html
deleted file mode 100644
index 9759f11..0000000
--- a/index.html
+++ /dev/null
@@ -1,968 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <script
-      defer=""
-      data-domain="nycbookstores.org"
-      src="https://stats.yetaga.in/js/script.hash.outbound-links.js"
-    ></script>
-    <meta charset="utf-8" />
-    <meta http-equiv="x-ua-compatible" content="ie=edge" />
-    <title>
-      Independent Bookstores in New York City - Best Community Bookstores in NYC
-    </title>
-    <meta
-      name="google-site-verification"
-      content="hEfog9h0E3JQW91ZUZM5ayPb6DND0WbUa2_W8yTIuVw"
-    />
-    <link rel="icon" type="image/png" href="/img/favicon.png" />
-    <link rel="apple-touch-icon" href="/img/social.jpg" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <script src="https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.js"></script>
-    <link
-      href="https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.css"
-      rel="stylesheet"
-    />
-    <link
-      href="https://fonts.googleapis.com/css?family=Acme|Lato&amp;display=swap"
-      rel="stylesheet"
-    />
-    <link
-      media="screen"
-      rel="stylesheet"
-      type="text/css"
-      href="/css/site.css?1709780697849"
-    />
-    <meta
-      property="title"
-      name="title"
-      content="Independent Bookstores in New York City - Best Community Bookstores in NYC"
-    />
-    <meta
-      property="description"
-      name="description"
-      content="A guide to and map of every independent bookstore in New York City. We have a complete list of community bookstores in NYC with locations and descriptions."
-    />
-    <meta name="twitter:card" content="summary" />
-    <meta name="twitter:site" content="www.nycbookstores.org" />
-    <meta name="twitter:title" content="NYC Bookstores" />
-    <meta
-      name="twitter:description"
-      content="A Guide To The Many Independent Bookstores Of New York City"
-    />
-    <meta
-      name="twitter:image"
-      content="https://www.nycbookstores.org/img/social.jpg"
-    />
-    <meta property="og:url" content="https://www.nycbookstores.org/" />
-    <meta property="og:type" content="website" />
-    <meta property="og:title" content="NYC Bookstores" />
-    <meta
-      property="og:description"
-      content="A Guide To The Many Independent Bookstores Of New York City"
-    />
-    <meta
-      property="og:image"
-      content="https://www.nycbookstores.org/img/social.jpg"
-    />
-    <link rel="canonical" href="https://www.nycbookstores.org/" />
-  </head>
-  <body>
-    <div id="wrapper">
-      <h1>NYC Bookstores</h1>
-      <div>
-        <ul class="nav">
-          <li>
-            <h2 id="subhed">
-              The Many Independent Bookstores of New York City
-            </h2>
-          </li>
-          <li>
-            <a id="viewInfo" href="#info" onclick="event.preventDefault()"
-              >intro</a
-            >
-          </li>
-          <li>
-            <a
-              href="https://git.yetaga.in/alazyreader/nyc-bookstores/"
-              target="_blank"
-              >source</a
-            >
-          </li>
-          <li>
-            <a href="https://icosahedron.website/@lazyreader" target="_blank"
-              >@lazyreader</a
-            >
-          </li>
-        </ul>
-      </div>
-      <div class="container">
-        <div id="map"></div>
-        <div id="info">
-          <p>
-            New York City loves its independent bookstores. It
-            <a
-              href="https://www.nytimes.com/2006/10/15/nyregion/thecity/15book.html"
-              target="_blank"
-              >eulogizes those that have faded</a
-            >
-            and celebrates when new ventures are launched. And while the
-            historic
-            <a
-              href="https://untappedcities.com/2015/08/26/4th-avenue-the-history-of-nycs-book-row/"
-              target="_blank"
-              >Book Row may have passed away in the 80s</a
-            >, there are still many indie bookstores dotting the map, across all
-            five boroughs. Here, I have attempted to collect all of the
-            currently-open general-interest independent booksellers in NYC. Any
-            store with regular-ish hours (excluding religious booksellers and
-            appointment-only rare book sellers) is included.
-          </p>
-          <p>
-            While Manhattan and Brooklyn still lead the pack, Queens has a
-            respectable number of stores, and all five boroughs are represented,
-            with the Bronx and Staten Island both hosting lone independent
-            stores. Lower Manhattan has the highest density of booksellers.
-          </p>
-          <p>
-            The listings here are kept up-to-date to the best of my ability;
-            however, I make no promises about either the accuracy or reliability
-            of the information. If you spot an error, or I've missed a shop,
-            please let me know by
-            <a href="mailto:delta.mu.alpha@gmail.com" target="_blank">email</a>,
-            <a href="https://icosahedron.website/@lazyreader" target="_blank"
-              >mastodon</a
-            >, or
-            <a href="https://www.twitter.com/alazyreader" target="_blank"
-              >twitter</a
-            >. Originally based on the "<a
-              href="https://github.com/jlord/hack-spots"
-              target="_blank"
-              >Hack Spots</a
-            >" website by
-            <a href="https://www.twitter.com/jllord" target="_blank">@jllord</a>
-            (although I don't believe any of the actual underlying code still
-            survives at this point).
-          </p>
-          <p>
-            There are currently <span id="storeCount">118</span> stores indexed
-            on this page. Last updated
-            <span id="updatedOn">March 6, 2024</span>.
-          </p>
-          <details>
-            <summary>Recent Changes</summary>
-            <ul id="changesList">
-              <li>
-                January 28, 2024 - move Yu and Me back to its original location
-                (congrats!)
-              </li>
-              <li>
-                January 1, 2024 - Add Bibliotheque and The World's Borough
-                Bookshop
-              </li>
-              <li>
-                December 27, 2023 - add La Joie de Vivre, a French/English
-                bookstore in the Flatiron Distirct
-              </li>
-              <li>December 21, 2023 - add canonical link</li>
-            </ul>
-          </details>
-        </div>
-        <div id="selected"></div>
-      </div>
-
-      <div class="clearfix"></div>
-
-      <div class="container">
-        <div id="Stores">
-          <table>
-            <tbody>
-              <tr id="0" class="spotRow">
-                <td class="name">1804 Books</td>
-                <td>320 W 37th St, New York</td>
-              </tr>
-              <tr id="1" class="spotRow">
-                <td class="name">192 Books</td>
-                <td>192 10th Ave, New York</td>
-              </tr>
-              <tr id="2" class="spotRow">
-                <td class="name">Adanne</td>
-                <td>115 Ralph Ave, Brooklyn</td>
-              </tr>
-              <tr id="3" class="spotRow">
-                <td class="name">Aeon Bookstore</td>
-                <td>151 East Broadway, New York</td>
-              </tr>
-              <tr id="4" class="spotRow">
-                <td class="name">Alabaster Bookshop</td>
-                <td>122 4th Ave, New York</td>
-              </tr>
-              <tr id="5" class="spotRow">
-                <td class="name">Albertine Books</td>
-                <td>972 Fifth Ave, New York</td>
-              </tr>
-              <tr id="6" class="spotRow">
-                <td class="name">Archestratus</td>
-                <td>164 Huron St, Brooklyn</td>
-              </tr>
-              <tr id="7" class="spotRow">
-                <td class="name">Argosy Books</td>
-                <td>116 East 59th St, New York</td>
-              </tr>
-              <tr id="8" class="spotRow">
-                <td class="name">Astoria Bookshop</td>
-                <td>36-19 30th St, Astoria</td>
-              </tr>
-              <tr id="9" class="spotRow">
-                <td class="name">Berl's Brooklyn Poetry Shop</td>
-                <td>126A Front St, Brooklyn</td>
-              </tr>
-              <tr id="10" class="spotRow">
-                <td class="name">Better Read Than Dead</td>
-                <td>867 Broadway, Brooklyn</td>
-              </tr>
-              <tr id="11" class="spotRow">
-                <td class="name">Better Read Than Dead &amp; Burly Coffee</td>
-                <td>90 Kosciuszko St, Brooklyn</td>
-              </tr>
-              <tr id="12" class="spotRow">
-                <td class="name">Bibliotheque</td>
-                <td>54 Mercer St, New York</td>
-              </tr>
-              <tr id="13" class="spotRow">
-                <td class="name">Black Spring Books</td>
-                <td>672 Driggs Ave, Brooklyn</td>
-              </tr>
-              <tr id="14" class="spotRow">
-                <td class="name">Bluestockings Cooperative</td>
-                <td>116 Suffolk Street, New York</td>
-              </tr>
-              <tr id="15" class="spotRow">
-                <td class="name">Bonnie Slotnick Cookbooks</td>
-                <td>28 East 2nd St, New York</td>
-              </tr>
-              <tr id="16" class="spotRow">
-                <td class="name">Book Club Bar</td>
-                <td>197 E 3rd St, New York</td>
-              </tr>
-              <tr id="17" class="spotRow">
-                <td class="name">Book Culture</td>
-                <td>536 W 112th St, New York</td>
-              </tr>
-              <tr id="18" class="spotRow">
-                <td class="name">Book Culture LIC</td>
-                <td>26-09 Jackson Ave, Queens</td>
-              </tr>
-              <tr id="19" class="spotRow">
-                <td class="name">Book Culture on Broadway</td>
-                <td>2915 Broadway, New York</td>
-              </tr>
-              <tr id="20" class="spotRow">
-                <td class="name">Book Thug Nation</td>
-                <td>100 North 3rd St, Brooklyn</td>
-              </tr>
-              <tr id="21" class="spotRow">
-                <td class="name">Bookmarc</td>
-                <td>400 Bleecker St, New York</td>
-              </tr>
-              <tr id="22" class="spotRow">
-                <td class="name">BookMark Shoppe</td>
-                <td>8415 3rd Avenue, Brooklyn</td>
-              </tr>
-              <tr id="23" class="spotRow">
-                <td class="name">Bookoff</td>
-                <td>49 W 45nd St, New York</td>
-              </tr>
-              <tr id="24" class="spotRow">
-                <td class="name">Bookoff Brooklyn</td>
-                <td>934 3rd Ave, Brooklyn</td>
-              </tr>
-              <tr id="25" class="spotRow">
-                <td class="name">Books Are Magic (Montague St.)</td>
-                <td>122 Montague St, Brooklyn</td>
-              </tr>
-              <tr id="26" class="spotRow">
-                <td class="name">Books Are Magic (Smith St.)</td>
-                <td>225 Smith St, Brooklyn</td>
-              </tr>
-              <tr id="27" class="spotRow">
-                <td class="name">Books Of Wonder</td>
-                <td>42 West 17th St, New York</td>
-              </tr>
-              <tr id="28" class="spotRow">
-                <td class="name">Bravo's Book Nook</td>
-                <td>115 MacDougal St, New York</td>
-              </tr>
-              <tr id="29" class="spotRow">
-                <td class="name">Burnt Books</td>
-                <td>157 Huron St, Brooklyn</td>
-              </tr>
-              <tr id="30" class="spotRow">
-                <td class="name">Cafe con Libros</td>
-                <td>724 Prospect Place, Brooklyn</td>
-              </tr>
-              <tr id="31" class="spotRow">
-                <td class="name">Catland</td>
-                <td>985 Flushing Ave, Brooklyn</td>
-              </tr>
-              <tr id="32" class="spotRow">
-                <td class="name">Chartwell Booksellers</td>
-                <td>55 E 52nd St, New York</td>
-              </tr>
-              <tr id="33" class="spotRow">
-                <td class="name">Codex Books</td>
-                <td>1 Bleecker St, New York</td>
-              </tr>
-              <tr id="34" class="spotRow">
-                <td class="name">Community Bookstore</td>
-                <td>143 7th Ave, Brooklyn</td>
-              </tr>
-              <tr id="35" class="spotRow">
-                <td class="name">Cups and Books</td>
-                <td>2024 Bedford Ave, Brooklyn</td>
-              </tr>
-              <tr id="36" class="spotRow">
-                <td class="name">Dashwood Books</td>
-                <td>33 Bond St, New York</td>
-              </tr>
-              <tr id="37" class="spotRow">
-                <td class="name">Dear Friend Books</td>
-                <td>343A Tompkins Ave, Brooklyn</td>
-              </tr>
-              <tr id="38" class="spotRow">
-                <td class="name">Desert Island Comics</td>
-                <td>540 Metropolitan Ave, Brooklyn</td>
-              </tr>
-              <tr id="39" class="spotRow">
-                <td class="name">East Village Books</td>
-                <td>99 St. Mark's Place, New York</td>
-              </tr>
-              <tr id="40" class="spotRow">
-                <td class="name">ETG Book Cafe</td>
-                <td>208 Bay St, Staten Island</td>
-              </tr>
-              <tr id="41" class="spotRow">
-                <td class="name">Freebird Books</td>
-                <td>123 Columbia St, Brooklyn</td>
-              </tr>
-              <tr id="42" class="spotRow">
-                <td class="name">Greenlight Bookstore</td>
-                <td>686 Fulton St, Brooklyn</td>
-              </tr>
-              <tr id="43" class="spotRow">
-                <td class="name">Here's A Book Store</td>
-                <td>1964 Coney Island Ave, Brooklyn</td>
-              </tr>
-              <tr id="44" class="spotRow">
-                <td class="name">Hey Kids! Comics</td>
-                <td>157 Huron St, Brooklyn</td>
-              </tr>
-              <tr id="45" class="spotRow">
-                <td class="name">Housing Works Bookstore Café</td>
-                <td>126 Crosby St, New York</td>
-              </tr>
-              <tr id="46" class="spotRow">
-                <td class="name">Human Relations Books</td>
-                <td>1067 Flushing Ave, Brooklyn</td>
-              </tr>
-              <tr id="47" class="spotRow">
-                <td class="name">James Cummins Bookseller</td>
-                <td>699 Madison Ave, 7th Floor, New York</td>
-              </tr>
-              <tr id="48" class="spotRow">
-                <td class="name">Joanne Hendricks Cookbooks</td>
-                <td>488 Greenwich St, New York</td>
-              </tr>
-              <tr id="49" class="spotRow">
-                <td class="name">Karma Bookstore</td>
-                <td>136 East Third St, New York</td>
-              </tr>
-              <tr id="50" class="spotRow">
-                <td class="name">Kew &amp; Willow Books</td>
-                <td>81-63 Lefferts Boulevard, New York</td>
-              </tr>
-              <tr id="51" class="spotRow">
-                <td class="name">Kinokunya</td>
-                <td>1073 Avenue of the Americas, New York</td>
-              </tr>
-              <tr id="52" class="spotRow">
-                <td class="name">Kitchen Arts &amp; Letters</td>
-                <td>1435 Lexington Ave, New York</td>
-              </tr>
-              <tr id="53" class="spotRow">
-                <td class="name">La Joie de Vivre</td>
-                <td>145 W 27th St, New York</td>
-              </tr>
-              <tr id="54" class="spotRow">
-                <td class="name">Leaves Bookstore</td>
-                <td>140 Nassau Ave, Brooklyn</td>
-              </tr>
-              <tr id="55" class="spotRow">
-                <td class="name">Left Bank Books</td>
-                <td>41 Perry St, New York</td>
-              </tr>
-              <tr id="56" class="spotRow">
-                <td class="name">Lofty Pigeon Books</td>
-                <td>743 Church Ave, Brooklyn</td>
-              </tr>
-              <tr id="57" class="spotRow">
-                <td class="name">Logos Bookstore</td>
-                <td>1575 York Ave, New York</td>
-              </tr>
-              <tr id="58" class="spotRow">
-                <td class="name">Mast Books</td>
-                <td>72 Avenue A, New York</td>
-              </tr>
-              <tr id="59" class="spotRow">
-                <td class="name">McNally Jackson Books</td>
-                <td>134 Prince St, New York</td>
-              </tr>
-              <tr id="60" class="spotRow">
-                <td class="name">McNally Jackson Books City Point</td>
-                <td>445 Albee Square W, Unit G112, Brooklyn</td>
-              </tr>
-              <tr id="61" class="spotRow">
-                <td class="name">McNally Jackson Books Seaport</td>
-                <td>4 Fulton St, New York</td>
-              </tr>
-              <tr id="62" class="spotRow">
-                <td class="name">McNally Jackson Books Williamsburg</td>
-                <td>76 North 4th St, Brooklyn</td>
-              </tr>
-              <tr id="63" class="spotRow">
-                <td class="name">McNally Jackson Rockefeller Center</td>
-                <td>1 Rockefeller Plaza, New York</td>
-              </tr>
-              <tr id="64" class="spotRow">
-                <td class="name">Melville House Publishers</td>
-                <td>46 John St, Brooklyn</td>
-              </tr>
-              <tr id="65" class="spotRow">
-                <td class="name">Mercer Street Books &amp; Records</td>
-                <td>206 Mercer St, New York</td>
-              </tr>
-              <tr id="66" class="spotRow">
-                <td class="name">Mil Mundos Books</td>
-                <td>323 Linden St, Brooklyn</td>
-              </tr>
-              <tr id="67" class="spotRow">
-                <td class="name">Molasses Books</td>
-                <td>770 Hart St, Brooklyn</td>
-              </tr>
-              <tr id="68" class="spotRow">
-                <td class="name">Namaste Bookshop</td>
-                <td>2 W 14th St, New York</td>
-              </tr>
-              <tr id="69" class="spotRow">
-                <td class="name">P&amp;T Knitwear</td>
-                <td>180 Orchard St, New York</td>
-              </tr>
-              <tr id="70" class="spotRow">
-                <td class="name">Passageway Books</td>
-                <td>150 9th Ave, New York</td>
-              </tr>
-              <tr id="71" class="spotRow">
-                <td class="name">Pillow-Cat Books</td>
-                <td>328 East 9th St, New York</td>
-              </tr>
-              <tr id="72" class="spotRow">
-                <td class="name">Posman Books Chelsea Market</td>
-                <td>75 9th Avenue, New York</td>
-              </tr>
-              <tr id="73" class="spotRow">
-                <td class="name">POWERHOUSE @ IC</td>
-                <td>220 36th St, Building #2, Brooklyn</td>
-              </tr>
-              <tr id="74" class="spotRow">
-                <td class="name">POWERHOUSE Arena</td>
-                <td>28 Adams St, Brooklyn</td>
-              </tr>
-              <tr id="75" class="spotRow">
-                <td class="name">POWERHOUSE on 8th</td>
-                <td>1111 8th Ave, Brooklyn</td>
-              </tr>
-              <tr id="76" class="spotRow">
-                <td class="name">Printed Matter</td>
-                <td>231 11th Ave, New York</td>
-              </tr>
-              <tr id="77" class="spotRow">
-                <td class="name">Printed Matter St. Marks</td>
-                <td>38 St. Marks Pl, New York</td>
-              </tr>
-              <tr id="78" class="spotRow">
-                <td class="name">Quest Bookshop</td>
-                <td>240 E 53rd St, New York</td>
-              </tr>
-              <tr id="79" class="spotRow">
-                <td class="name">Quimby's Bookstore</td>
-                <td>536 Metropolitan Ave, Brooklyn</td>
-              </tr>
-              <tr id="80" class="spotRow">
-                <td class="name">Recirculation</td>
-                <td>876 Riverside Dr, New York</td>
-              </tr>
-              <tr id="81" class="spotRow">
-                <td class="name">Respect For Life Books-N-Things</td>
-                <td>537 Nostrand Ave, Brooklyn</td>
-              </tr>
-              <tr id="82" class="spotRow">
-                <td class="name">Revolution Books</td>
-                <td>437 Malcolm X Blvd, New York</td>
-              </tr>
-              <tr id="83" class="spotRow">
-                <td class="name">Rizzoli Bookstore</td>
-                <td>1133 Broadway, New York</td>
-              </tr>
-              <tr id="84" class="spotRow">
-                <td class="name">Shakespeare &amp; Company</td>
-                <td>939 Lexington Ave, New York</td>
-              </tr>
-              <tr id="85" class="spotRow">
-                <td class="name">
-                  Shakespeare &amp; Company (Manhattan Valley/Columbia
-                  University)
-                </td>
-                <td>2736 Broadway, New York</td>
-              </tr>
-              <tr id="86" class="spotRow">
-                <td class="name">
-                  Shakespeare &amp; Company (Upper West Side)
-                </td>
-                <td>2020 Broadway, New York</td>
-              </tr>
-              <tr id="87" class="spotRow">
-                <td class="name">Sister's Uptown Bookstore</td>
-                <td>1942 Amsterdam Ave, New York</td>
-              </tr>
-              <tr id="88" class="spotRow">
-                <td class="name">Spoonbill &amp; Sugartown, Booksellers</td>
-                <td>218 Bedford Ave, Brooklyn</td>
-              </tr>
-              <tr id="89" class="spotRow">
-                <td class="name">Standards Manual</td>
-                <td>212 Franklin Street, Brooklyn</td>
-              </tr>
-              <tr id="90" class="spotRow">
-                <td class="name">Strand Bookstore</td>
-                <td>828 Broadway, New York</td>
-              </tr>
-              <tr id="91" class="spotRow">
-                <td class="name">Sweet Pickle Books</td>
-                <td>47 Orchard St, New York</td>
-              </tr>
-              <tr id="92" class="spotRow">
-                <td class="name">Taylor &amp; Co. Books</td>
-                <td>1021 Cortelyou Rd, Brooklyn</td>
-              </tr>
-              <tr id="93" class="spotRow">
-                <td class="name">Terrace Books</td>
-                <td>242 Prospect Park West, Brooklyn</td>
-              </tr>
-              <tr id="94" class="spotRow">
-                <td class="name">The Austin Book Shop</td>
-                <td>104-29 Jamaica Ave, Richmond Hill</td>
-              </tr>
-              <tr id="95" class="spotRow">
-                <td class="name">The Book Cellar</td>
-                <td>1465 York Ave, New York</td>
-              </tr>
-              <tr id="96" class="spotRow">
-                <td class="name">The Center for Fiction</td>
-                <td>15 Lafayette Ave, Brooklyn</td>
-              </tr>
-              <tr id="97" class="spotRow">
-                <td class="name">The Corner Bookstore</td>
-                <td>1313 Madison Ave, New York</td>
-              </tr>
-              <tr id="98" class="spotRow">
-                <td class="name">The Drama Book Shop</td>
-                <td>266 W 39th St, New York</td>
-              </tr>
-              <tr id="99" class="spotRow">
-                <td class="name">The Lit. Bar</td>
-                <td>131 Alexander Ave, Bronx</td>
-              </tr>
-              <tr id="100" class="spotRow">
-                <td class="name">The Mysterious Bookshop</td>
-                <td>58 Warren St, New York</td>
-              </tr>
-              <tr id="101" class="spotRow">
-                <td class="name">The Ripped Bodice</td>
-                <td>218 5th Ave, Brooklyn</td>
-              </tr>
-              <tr id="102" class="spotRow">
-                <td class="name">The Strand At Columbus Ave</td>
-                <td>450 Columbus Ave, New York</td>
-              </tr>
-              <tr id="103" class="spotRow">
-                <td class="name">The Word Is Change</td>
-                <td>368 Tompkins Ave, Brooklyn</td>
-              </tr>
-              <tr id="104" class="spotRow">
-                <td class="name">The World's Borough Bookshop</td>
-                <td>3406 73rd St, Queens</td>
-              </tr>
-              <tr id="105" class="spotRow">
-                <td class="name">Three Lives &amp; Company</td>
-                <td>154 W 10th St, New York</td>
-              </tr>
-              <tr id="106" class="spotRow">
-                <td class="name">Topos Bookstore Cafe</td>
-                <td>788 Woodward Ave, Brooklyn</td>
-              </tr>
-              <tr id="107" class="spotRow">
-                <td class="name">Topos Too</td>
-                <td>59-22 Myrtle Ave, Queens</td>
-              </tr>
-              <tr id="108" class="spotRow">
-                <td class="name">Troubled Sleep Books</td>
-                <td>129 6th Ave, Brooklyn</td>
-              </tr>
-              <tr id="109" class="spotRow">
-                <td class="name">Turn The Page... Again</td>
-                <td>39-15a Bell Blvd, Flushing</td>
-              </tr>
-              <tr id="110" class="spotRow">
-                <td class="name">Unnameable Books</td>
-                <td>600 Vanderbilt Ave, Brooklyn</td>
-              </tr>
-              <tr id="111" class="spotRow">
-                <td class="name">Ursus Books</td>
-                <td>50 East 78th St, Suite 1C, New York</td>
-              </tr>
-              <tr id="112" class="spotRow">
-                <td class="name">Village Works</td>
-                <td>12 St. Marks Pl, New York</td>
-              </tr>
-              <tr id="113" class="spotRow">
-                <td class="name">Westsider Rare &amp; Used Books</td>
-                <td>2246 Broadway, New York</td>
-              </tr>
-              <tr id="114" class="spotRow">
-                <td class="name">Westsider Records</td>
-                <td>233 West 72nd St, New York</td>
-              </tr>
-              <tr id="115" class="spotRow">
-                <td class="name">Word Bookstore</td>
-                <td>126 Franklin St, Brooklyn</td>
-              </tr>
-              <tr id="116" class="spotRow">
-                <td class="name">Word Up Books</td>
-                <td>2113 Amsterdam Ave, New York</td>
-              </tr>
-              <tr id="117" class="spotRow">
-                <td class="name">Yu and Me Books</td>
-                <td>44 Mulberry St, New York</td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div>
-    <!-- end wrapper -->
-
-    <script>
-      mapboxgl.accessToken =
-        "pk.eyJ1IjoiYWxhenlyZWFkZXIiLCJhIjoiY2lucDZhb2JxMHp6MHRxa2pvaTFoOWpuZyJ9.DILGYYxxt7A-A_lHHwp6tQ";
-      var map = new mapboxgl.Map({
-        container: "map",
-        style: "mapbox://styles/mapbox/basic-v9",
-        center: [-73.957292, 40.729071], // arbitrary center point
-        zoom: 9,
-        minZoom: 9,
-        maxZoom: 17,
-        dragRotate: false,
-      });
-
-      var popup = new mapboxgl.Popup({
-        closeOnClick: false,
-        closeButton: false,
-      });
-
-      function TableViewTemplate(rows) {
-        table = "<table>";
-        rows.forEach((row) => {
-          table = table + TableRowTemplate(row);
-        });
-        return table + "</table>";
-      }
-
-      function TableRowTemplate({ rowNumber, name, address, city }) {
-        return `<tr id="${rowNumber}" class="spotRow">
-          <td class="name">${name}</td><td>${address}, ${city}</td>
-        </tr>`;
-      }
-
-      function SelectedStoreTemplate({
-        name,
-        address,
-        city,
-        postcode,
-        website,
-        events,
-        cafe,
-        description,
-      }) {
-        const isAppleIsh = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
-        return `
-          <h2>${name}</h2>
-          <p class="address">${address}</p>
-          <p></p>
-          <p class="address">
-            ${city}, NY ${postcode}
-          </p>
-          <p>
-            View in:
-            <a
-              href="https://maps.google.com/maps?q=${encodeURIComponent(
-                name
-              )}+${address},${city},NY"
-              target="_blank"
-              >Google Maps</a
-            >
-            ${
-              isAppleIsh
-                ? `
-            <a
-              href="http://maps.apple.com/?q=${encodeURIComponent(
-                name
-              )}&address=${address},${city},NY"
-              target="_blank"
-              >Apple Maps</a
-            >`
-                : ""
-            }
-          </p>
-          <ul>
-            ${
-              website
-                ? `<li><a href="${website}" target="_blank">${cleanWebsite(
-                    website
-                  )}</a></li>`
-                : ""
-            }
-            <li class="storeDetails">Events: ${events}</li>
-            <li class="storeDetails">Caf&eacute;: ${cafe}</li>
-          </ul>
-          ${description ? `<p class="description">${description}</p>` : ""}`;
-      }
-
-      function hideElementById(id) {
-        const element = document.getElementById(id);
-        if (element !== undefined) {
-          element.classList.add("hidden");
-        }
-      }
-
-      function showElementById(id) {
-        const element = document.getElementById(id);
-        if (element !== undefined) {
-          element.classList.remove("hidden");
-        }
-      }
-
-      function setContent(id, html) {
-        const element = document.getElementById(id);
-        if (element !== undefined) {
-          element.innerHTML = html;
-        }
-      }
-
-      document.addEventListener("DOMContentLoaded", function () {
-        fetch("./stores.json")
-          .then((resp) => {
-            return resp.json();
-          })
-          .then((data) => {
-            data.sort(function (a, b) {
-              var aname = a.name.toLowerCase();
-              var bname = b.name.toLowerCase();
-              return aname === bname ? 0 : +(aname > bname) || -1;
-            });
-            data.forEach((value, key) => {
-              value.rowNumber = key;
-              value.slug = slugify(value.name);
-            });
-            setContent("storeCount", data.length);
-            window.data = data;
-            loadMap(data);
-          })
-          .catch((err) => {
-            // we'll live with the static cache!
-            console.log(err);
-          });
-      });
-
-      window.addEventListener(
-        "hashchange",
-        function (e) {
-          updateViewBySlug(e.newURL.split("#")[1]);
-        },
-        false
-      );
-
-      function updateHash(slug) {
-        history.pushState(null, null, "#" + slug);
-      }
-
-      function slugify(str) {
-        return str
-          .toLowerCase()
-          .replace(/é/g, "e")
-          .replace(/&/g, " and ")
-          .replace(/ /g, "-")
-          .replace(/[']+/g, "")
-          .replace(/[^\w-]+/g, "-")
-          .replace(/-+/g, "-")
-          .replace(/^-|-$/g, "");
-      }
-
-      function cleanWebsite(str) {
-        return str
-          .toLowerCase()
-          .replace(/^https?:\/\//g, "")
-          .replace(/^www./g, "")
-          .replace(/\/$/g, "");
-      }
-
-      function getStoreBySlug(slug) {
-        var ret = false;
-        window.data.forEach((value, key) => {
-          if (value.slug === slug) {
-            ret = value;
-            return false;
-          }
-        });
-        return ret;
-      }
-
-      function updateViewBySlug(slug) {
-        if (slug === "info" || slug === undefined) {
-          showInfo(false);
-        } else {
-          var store = getStoreBySlug(slug);
-          if (store) {
-            updateSelectedStore(store, false);
-          }
-        }
-      }
-
-      function boundingBox(point) {
-        // add some buffer to a point to give the user some leeway
-        return [
-          [point.x - 5, point.y - 5],
-          [point.x + 5, point.y + 5],
-        ];
-      }
-
-      function updateSelectedStore(store, pushState = false) {
-        map.flyTo({ center: [store.long, store.lat], zoom: 12 });
-
-        popup.setLngLat([store.long, store.lat]).setHTML(store.name).addTo(map);
-
-        hideElementById("info");
-        setContent("selected", SelectedStoreTemplate(store));
-        showElementById("selected");
-        if (pushState) {
-          updateHash(store.slug);
-        }
-      }
-
-      function showInfo(pushState = true) {
-        hideElementById("selected");
-        popup.remove();
-        showElementById("info");
-        if (pushState) {
-          updateHash("info");
-        }
-      }
-
-      function loadMap(data) {
-        var geolocate = new mapboxgl.GeolocateControl();
-
-        var points = [];
-        data.forEach((value, key) => {
-          points.push({
-            type: "Feature",
-            geometry: {
-              type: "Point",
-              coordinates: [value.long, value.lat],
-            },
-            properties: value,
-          });
-        });
-        map.on("load", function () {
-          map.addLayer({
-            id: "stores",
-            type: "circle",
-            source: {
-              type: "geojson",
-              data: {
-                type: "FeatureCollection",
-                features: points,
-              },
-            },
-            paint: {
-              "circle-radius": 5,
-              "circle-color": "#B9FCFC",
-              "circle-stroke-width": 2,
-              "circle-stroke-color": "#000000",
-            },
-          });
-          map.addControl(new mapboxgl.NavigationControl(), "top-left");
-          map.addControl(geolocate, "top-right");
-          updateViewBySlug(window.location.hash.split("#")[1]);
-        });
-
-        map.on("click", function (e) {
-          if (!map.getLayer("stores")) {
-            return;
-          }
-
-          popup.remove();
-          // Use queryRenderedFeatures to get features at a click event's point
-          var features = map.queryRenderedFeatures(boundingBox(e.point), {
-            layers: ["stores"],
-          });
-          // fly to the location of the click event
-          if (features.length) {
-            var store = features[0];
-            // Get coordinates from the symbol and center the map on those coordinates
-            updateSelectedStore(store.properties, true);
-          }
-        });
-
-        // indicate that the symbols are clickable by changing the cursor style to 'pointer'.
-        map.on("mousemove", function (e) {
-          if (!map.getLayer("stores")) {
-            return;
-          }
-          var features = map.queryRenderedFeatures(boundingBox(e.point), {
-            layers: ["stores"],
-          });
-          map.getCanvas().style.cursor = features.length ? "pointer" : "";
-        });
-
-        geolocate.on("geolocate", function (e) {
-          map.setZoom(14);
-          popup
-            .setLngLat([e.coords.longitude, e.coords.latitude])
-            .setHTML("Current Location")
-            .addTo(map);
-        });
-
-        setContent("Stores", TableViewTemplate(data));
-        document.querySelectorAll("#Stores tbody tr").forEach((element) => {
-          element.addEventListener("click", () => {
-            updateSelectedStore(data[element.id], true);
-            document
-              .getElementById("subhed")
-              .scrollIntoView({ behavior: "smooth" });
-          });
-        });
-
-        document.getElementById("viewInfo").addEventListener("click", showInfo);
-      }
-    </script>
-  </body>
-</html>
diff --git a/index.js b/index.js
index d945d19..c1c90bd 100644
--- a/index.js
+++ b/index.js
@@ -1,8 +1,49 @@
 import { load } from "cheerio";
-import { readFile, writeFile } from "fs";
+import { readFile, writeFileSync, mkdirSync, cpSync, rmSync } from "fs";
 import process from "child_process";
 import stores from "./stores.json" with { type: "json" };
 
+function mkDir(path) {
+  try {
+    return mkdirSync(path)
+  } catch (err) {
+    if (err.code !== 'EEXIST') throw err
+  }
+}
+
+function writeFile(path, content) {
+  try {
+    writeFileSync(path, content);
+  } catch (err) {
+    if (err) throw err;
+  }
+   console.log(`${path} updated.`);
+}
+
+function slugify(str) {
+  return str
+    .toLowerCase()
+    .replace(/é/g, "e")
+    .replace(/&/g, " and ")
+    .replace(/ /g, "-")
+    .replace(/[']+/g, "")
+    .replace(/[^\w-]+/g, "-")
+    .replace(/-+/g, "-")
+    .replace(/^-|-$/g, "");
+}
+
+function cleanWebsite(str) {
+  return str
+    .toLowerCase()
+    .replace(/^https?:\/\//g, "")
+    .replace(/^www./g, "")
+    .replace(/\/$/g, "");
+}
+
+function metaDescription({meta, description}) {
+  return meta || description.length > 155 ? description.slice(0, 153) + "..." : description || "A guide to and map of every independent bookstore in New York City. We have a complete list of community bookstores in NYC with locations and descriptions."
+}
+
 function GetRecentChanges() {
   const res = process
     .execSync('git log -15 --pretty=format:"%ct %s"')
@@ -44,15 +85,68 @@ function TableViewTemplate(rows) {
   return table + "</table>";
 }
 
-function TableRowTemplate({ rowNumber, name, address, city }) {
+function TableRowTemplate({ rowNumber, name, slug, address, city }) {
   return `
   <tr id="${rowNumber}" class="spotRow">
-    <td class="name">${name}</td>
-    <td>${address}, ${city}</td>
+    <td class="name"><a href="/${slugify(name)}/">${name}</a></td>
+    <td><a href="/${slugify(name)}/">${address}, ${city}</a></td>
   </tr>`;
 }
 
-readFile("./index.html", function (err, data) {
+function TitleTemplate({ name }) {
+  return `${name} | Independent Bookstores in New York City - Best Community Bookstores in NYC`;
+}
+
+function SelectedStoreTemplate({
+  name,
+  address,
+  city,
+  postcode,
+  website,
+  events,
+  cafe,
+  description,
+}) {
+  return `
+    <h2>${name}</h2>
+    <p class="address">${address}</p>
+    <p></p>
+    <p class="address">
+      ${city}, NY ${postcode}
+    </p>
+    <p>
+      View in:
+      <a
+        href="https://maps.google.com/maps?q=${encodeURIComponent(
+          name
+        )}+${address},${city},NY"
+        target="_blank"
+        >Google Maps</a
+      >
+      <a
+        href="http://maps.apple.com/?q=${encodeURIComponent(
+          name
+        )}&address=${address},${city},NY"
+        target="_blank"
+        >Apple Maps</a
+      >
+    </p>
+    <ul>
+      ${
+        website
+          ? `<li><a href="${website}" target="_blank">${cleanWebsite(
+              website
+            )}</a></li>`
+          : ""
+      }
+      <li class="storeDetails">Events: ${events}</li>
+      <li class="storeDetails">Caf&eacute;: ${cafe}</li>
+    </ul>
+    ${description ? `<p class="description">${description}</p>` : ""}`;
+}
+
+
+readFile("./index.tmpl.html", function (err, data) {
   const changeList = GetRecentChanges();
   if (err) {
     throw err;
@@ -77,8 +171,26 @@ readFile("./index.html", function (err, data) {
   $("#changesList").html(ChangeLog(changeList));
   const cssurl = $("link[type='text/css']").attr("href").split("?")[0];
   $("link[type='text/css']").attr("href", cssurl + "?" + new Date().getTime());
-  writeFile("./index.html", $.html(), (err) => {
-    if (err) throw err;
-    console.log("Default view updated.");
+
+  rmSync("./build", { recursive: true, force: true });
+
+  mkDir("./build")
+  
+  writeFile("./build/index.html", $.html())
+
+  cpSync("./site.css", "./build/site.css");
+  cpSync("./robots.txt", "./build/robots.txt");
+  cpSync("./img", "./build/img", {recursive: true});
+  cpSync("./stores.json", "./build/stores.json");
+
+  stores.forEach((store) => {
+    $("#selected").html(SelectedStoreTemplate(store));
+    $("#info").addClass("hidden");
+    let title = TitleTemplate(store);
+    $("title").html(title);
+    $("meta[name='title']").attr("content", title);
+    $("meta[name='description']").attr("content", metaDescription(store));
+    mkDir(`./build/${slugify(store.name)}`);
+    writeFile(`./build/${slugify(store.name)}/index.html`, $.html());
   });
 });
diff --git a/index.tmpl.html b/index.tmpl.html
new file mode 100644
index 0000000..7e2ff01
--- /dev/null
+++ b/index.tmpl.html
@@ -0,0 +1,469 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <script
+      defer=""
+      data-domain="nycbookstores.org"
+      src="https://stats.yetaga.in/js/script.outbound-links.js"
+    ></script>
+    <meta charset="utf-8" />
+    <meta http-equiv="x-ua-compatible" content="ie=edge" />
+    <title>
+      Independent Bookstores in New York City - Best Community Bookstores in NYC
+    </title>
+    <meta
+      name="google-site-verification"
+      content="hEfog9h0E3JQW91ZUZM5ayPb6DND0WbUa2_W8yTIuVw"
+    />
+    <link rel="icon" type="image/png" href="/img/favicon.png" />
+    <link rel="apple-touch-icon" href="/img/social.jpg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <script src="https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.js"></script>
+    <link
+      href="https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.css"
+      rel="stylesheet"
+    />
+    <link
+      href="https://fonts.googleapis.com/css?family=Acme|Lato&amp;display=swap"
+      rel="stylesheet"
+    />
+    <link
+      media="screen"
+      rel="stylesheet"
+      type="text/css"
+      href="/site.css?1734659401621"
+    />
+    <meta
+      property="title"
+      name="title"
+      content="Independent Bookstores in New York City - Best Community Bookstores in NYC"
+    />
+    <meta
+      property="description"
+      name="description"
+      content="A guide to and map of every independent bookstore in New York City. We have a complete list of community bookstores in NYC with locations and descriptions."
+    />
+    <meta name="twitter:card" content="summary" />
+    <meta name="twitter:site" content="www.nycbookstores.org" />
+    <meta name="twitter:title" content="NYC Bookstores" />
+    <meta
+      name="twitter:description"
+      content="A Guide To The Many Independent Bookstores Of New York City"
+    />
+    <meta
+      name="twitter:image"
+      content="https://www.nycbookstores.org/img/social.jpg"
+    />
+    <meta property="og:url" content="https://www.nycbookstores.org/" />
+    <meta property="og:type" content="website" />
+    <meta property="og:title" content="NYC Bookstores" />
+    <meta
+      property="og:description"
+      content="A Guide To The Many Independent Bookstores Of New York City"
+    />
+    <meta
+      property="og:image"
+      content="https://www.nycbookstores.org/img/social.jpg"
+    />
+    <link rel="canonical" href="https://www.nycbookstores.org/" />
+  </head>
+  <body>
+    <div id="wrapper">
+      <h1>NYC Bookstores</h1>
+      <div>
+        <ul class="nav">
+          <li>
+            <h2 id="subhed">
+              The Many Independent Bookstores of New York City
+            </h2>
+          </li>
+          <li>
+            <a id="viewInfo" href="/">intro</a>
+          </li>
+          <li>
+            <a
+              href="https://git.yetaga.in/alazyreader/nyc-bookstores/"
+              target="_blank"
+              >source</a
+            >
+          </li>
+          <li>
+            <a href="https://icosahedron.website/@lazyreader" target="_blank"
+              >@lazyreader</a
+            >
+          </li>
+        </ul>
+      </div>
+      <div class="container">
+        <div id="map"></div>
+        <div id="info">
+          <p>
+            New York City loves its independent bookstores. It
+            <a
+              href="https://www.nytimes.com/2006/10/15/nyregion/thecity/15book.html"
+              target="_blank"
+              >eulogizes those that have faded</a
+            >
+            and celebrates when new ventures are launched. And while the
+            historic
+            <a
+              href="https://untappedcities.com/2015/08/26/4th-avenue-the-history-of-nycs-book-row/"
+              target="_blank"
+              >Book Row may have passed away in the 80s</a
+            >, there are still many indie bookstores dotting the map, across all
+            five boroughs. Here, I have attempted to collect all of the
+            currently-open general-interest independent booksellers in NYC. Any
+            store with regular-ish hours (excluding religious booksellers and
+            appointment-only rare book sellers) is included.
+          </p>
+          <p>
+            While Manhattan and Brooklyn still lead the pack, Queens has a
+            respectable number of stores, and all five boroughs are represented,
+            with the Bronx and Staten Island both hosting lone independent
+            stores. Lower Manhattan has the highest density of booksellers.
+          </p>
+          <p>
+            The listings here are kept up-to-date to the best of my ability;
+            however, I make no promises about either the accuracy or reliability
+            of the information. If you spot an error, or I've missed a shop,
+            please let me know by
+            <a href="mailto:delta.mu.alpha@gmail.com" target="_blank">email</a>,
+            <a href="https://icosahedron.website/@lazyreader" target="_blank"
+              >mastodon</a
+            >, or
+            <a href="https://www.twitter.com/alazyreader" target="_blank"
+              >twitter</a
+            >. Originally based on the "<a
+              href="https://github.com/jlord/hack-spots"
+              target="_blank"
+              >Hack Spots</a
+            >" website by
+            <a href="https://www.twitter.com/jllord" target="_blank">@jllord</a>
+            (although I don't believe any of the actual underlying code still
+            survives at this point).
+          </p>
+          <p>
+            There are currently <span id="storeCount">121</span> stores indexed
+            on this page. Last updated
+            <span id="updatedOn">December 19, 2024</span>.
+          </p>
+          <details>
+            <summary>Recent Changes</summary>
+            <ul id="changesList"></ul>
+          </details>
+        </div>
+        <div id="selected"></div>
+      </div>
+
+      <div class="clearfix"></div>
+
+      <div class="container">
+        <div id="Stores">
+          <table>
+            <tbody></tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+    <!-- end wrapper -->
+
+    <script>
+      mapboxgl.accessToken =
+        "pk.eyJ1IjoiYWxhenlyZWFkZXIiLCJhIjoiY2lucDZhb2JxMHp6MHRxa2pvaTFoOWpuZyJ9.DILGYYxxt7A-A_lHHwp6tQ";
+      var map = new mapboxgl.Map({
+        container: "map",
+        style: "mapbox://styles/mapbox/basic-v9",
+        center: [-73.957292, 40.729071], // arbitrary center point
+        zoom: 9,
+        minZoom: 9,
+        maxZoom: 17,
+        dragRotate: false,
+      });
+
+      var popup = new mapboxgl.Popup({
+        closeOnClick: false,
+        closeButton: false,
+      });
+
+      function TitleTemplate({ name }) {
+        return `${name} | Independent Bookstores in New York City - Best Community Bookstores in NYC`;
+      }
+
+      function TableViewTemplate(rows) {
+        table = "<table>";
+        rows.forEach((row) => {
+          table = table + TableRowTemplate(row);
+        });
+        return table + "</table>";
+      }
+
+      function TableRowTemplate({ rowNumber, name, address, city }) {
+        return `<tr id="${rowNumber}" class="spotRow">
+          <td class="name">${name}</td><td>${address}, ${city}</td>
+        </tr>`;
+      }
+
+      function SelectedStoreTemplate({
+        name,
+        address,
+        city,
+        postcode,
+        website,
+        events,
+        cafe,
+        description,
+      }) {
+        const isAppleIsh = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
+        return `
+          <h2>${name}</h2>
+          <p class="address">${address}</p>
+          <p></p>
+          <p class="address">
+            ${city}, NY ${postcode}
+          </p>
+          <p>
+            View in:
+            <a
+              href="https://maps.google.com/maps?q=${encodeURIComponent(
+                name
+              )}+${address},${city},NY"
+              target="_blank"
+              >Google Maps</a
+            >
+            ${
+              isAppleIsh
+                ? `
+            <a
+              href="http://maps.apple.com/?q=${encodeURIComponent(
+                name
+              )}&address=${address},${city},NY"
+              target="_blank"
+              >Apple Maps</a
+            >`
+                : ""
+            }
+          </p>
+          <ul>
+            ${
+              website
+                ? `<li><a href="${website}" target="_blank">${cleanWebsite(
+                    website
+                  )}</a></li>`
+                : ""
+            }
+            <li class="storeDetails">Events: ${events}</li>
+            <li class="storeDetails">Caf&eacute;: ${cafe}</li>
+          </ul>
+          ${description ? `<p class="description">${description}</p>` : ""}`;
+      }
+
+      function hideElementById(id) {
+        const element = document.getElementById(id);
+        if (element !== undefined) {
+          element.classList.add("hidden");
+        }
+      }
+
+      function showElementById(id) {
+        const element = document.getElementById(id);
+        if (element !== undefined) {
+          element.classList.remove("hidden");
+        }
+      }
+
+      function setContent(id, html) {
+        const element = document.getElementById(id);
+        if (element !== undefined) {
+          element.innerHTML = html;
+        }
+      }
+
+      function setTitle(string) {
+        const element = document.getElementsByTagName("title");
+        if (element !== undefined && element.length === 1) {
+          element[0].innerText = string;
+        }
+      }
+
+      document.addEventListener("DOMContentLoaded", function () {
+        if (window.location.hash !== "") {
+          updateLocation(window.location.hash.substring(1));
+        }
+
+        fetch("/stores.json")
+          .then((resp) => {
+            return resp.json();
+          })
+          .then((data) => {
+            data.sort(function (a, b) {
+              var aname = a.name.toLowerCase();
+              var bname = b.name.toLowerCase();
+              return aname === bname ? 0 : +(aname > bname) || -1;
+            });
+            data.forEach((value, key) => {
+              value.rowNumber = key;
+              value.slug = slugify(value.name);
+            });
+            setContent("storeCount", data.length);
+            window.data = data;
+            loadMap(data);
+          })
+          .catch((err) => {
+            // we'll live with the static cache!
+            console.log(err);
+          });
+      });
+
+      function updateLocation(slug) {
+        history.pushState(null, null, `/${slug}`);
+      }
+
+      function slugify(str) {
+        return str
+          .toLowerCase()
+          .replace(/é/g, "e")
+          .replace(/&/g, " and ")
+          .replace(/ /g, "-")
+          .replace(/[']+/g, "")
+          .replace(/[^\w-]+/g, "-")
+          .replace(/-+/g, "-")
+          .replace(/^-|-$/g, "");
+      }
+
+      function cleanWebsite(str) {
+        return str
+          .toLowerCase()
+          .replace(/^https?:\/\//g, "")
+          .replace(/^www./g, "")
+          .replace(/\/$/g, "");
+      }
+
+      function getStoreBySlug(slug) {
+        var ret = false;
+        window.data.forEach((value, key) => {
+          if (value.slug === slug) {
+            ret = value;
+            return false;
+          }
+        });
+        return ret;
+      }
+
+      function updateViewBySlug(slug) {
+        if (slug === undefined) {
+          showInfo(false);
+        } else {
+          var store = getStoreBySlug(slug);
+          if (store) {
+            updateSelectedStore(store, false);
+          }
+        }
+      }
+
+      function boundingBox(point) {
+        // add some buffer to a point to give the user some leeway
+        return [
+          [point.x - 5, point.y - 5],
+          [point.x + 5, point.y + 5],
+        ];
+      }
+
+      function updateSelectedStore(store, pushState = false) {
+        map.flyTo({ center: [store.long, store.lat], zoom: 12 });
+
+        popup.setLngLat([store.long, store.lat]).setHTML(store.name).addTo(map);
+
+        hideElementById("info");
+        setContent("selected", SelectedStoreTemplate(store));
+        showElementById("selected");
+        setTitle(TitleTemplate(store));
+        if (pushState) {
+          updateLocation(store.slug);
+        }
+      }
+
+      function showInfo(pushState = true) {
+        hideElementById("selected");
+        popup.remove();
+        showElementById("info");
+        if (pushState) {
+          updateLocation("info");
+        }
+      }
+
+      function loadMap(data) {
+        var geolocate = new mapboxgl.GeolocateControl();
+
+        var points = [];
+        data.forEach((value, key) => {
+          points.push({
+            type: "Feature",
+            geometry: {
+              type: "Point",
+              coordinates: [value.long, value.lat],
+            },
+            properties: value,
+          });
+        });
+        map.on("load", function () {
+          map.addLayer({
+            id: "stores",
+            type: "circle",
+            source: {
+              type: "geojson",
+              data: {
+                type: "FeatureCollection",
+                features: points,
+              },
+            },
+            paint: {
+              "circle-radius": 5,
+              "circle-color": "#B9FCFC",
+              "circle-stroke-width": 2,
+              "circle-stroke-color": "#000000",
+            },
+          });
+          map.addControl(new mapboxgl.NavigationControl(), "top-left");
+          map.addControl(geolocate, "top-right");
+          updateViewBySlug(window.location.pathname.split("/")[1]);
+        });
+
+        map.on("click", function (e) {
+          if (!map.getLayer("stores")) {
+            return;
+          }
+          popup.remove();
+          // Use queryRenderedFeatures to get features at a click event's point
+          var features = map.queryRenderedFeatures(boundingBox(e.point), {
+            layers: ["stores"],
+          });
+          // fly to the location of the click event
+          if (features.length) {
+            var store = features[0];
+            // Get coordinates from the symbol and center the map on those coordinates
+            updateSelectedStore(store.properties, true);
+          }
+        });
+
+        // indicate that the symbols are clickable by changing the cursor style to 'pointer'.
+        map.on("mousemove", function (e) {
+          if (!map.getLayer("stores")) {
+            return;
+          }
+          var features = map.queryRenderedFeatures(boundingBox(e.point), {
+            layers: ["stores"],
+          });
+          map.getCanvas().style.cursor = features.length ? "pointer" : "";
+        });
+
+        geolocate.on("geolocate", function (e) {
+          map.setZoom(14);
+          popup
+            .setLngLat([e.coords.longitude, e.coords.latitude])
+            .setHTML("Current Location")
+            .addTo(map);
+        });
+      }
+    </script>
+  </body>
+</html>
diff --git a/css/site.css b/site.css
similarity index 99%
rename from css/site.css
rename to site.css
index 5721fc9..be3d211 100644
--- a/css/site.css
+++ b/site.css
@@ -180,7 +180,7 @@ ul.nav li a:hover {
   cursor: pointer;
 }
 #Stores tr td {
-  padding: 4px 10px;
+  padding: 4px 4px 12px;
 }
 #Stores tr:not(:last-child) td {
   border-bottom: 1px #ddd solid;
diff --git a/stores.json b/stores.json
index be4f552..28ffc7c 100644
--- a/stores.json
+++ b/stores.json
@@ -11,7 +11,8 @@
     "country": "USA",
     "website": "https://www.housingworks.org/locations/bookstore-cafe",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "McNally Jackson Books",
@@ -25,7 +26,8 @@
     "country": "USA",
     "website": "https://www.mcnallyjackson.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Word Bookstore",
@@ -39,7 +41,8 @@
     "country": "USA",
     "website": "https://www.wordbookstores.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Astoria Bookshop",
@@ -53,7 +56,8 @@
     "country": "USA",
     "website": "https://www.astoriabookshop.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "POWERHOUSE Arena",
@@ -67,7 +71,8 @@
     "country": "USA",
     "website": "https://www.powerhousebookstores.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Mercer Street Books & Records",
@@ -81,7 +86,8 @@
     "country": "USA",
     "website": "http://www.mercerstreetbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Mast Books",
@@ -95,7 +101,8 @@
     "country": "USA",
     "website": "https://www.mastbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The Mysterious Bookshop",
@@ -109,7 +116,8 @@
     "country": "USA",
     "website": "https://www.mysteriousbookshop.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Bookmarc",
@@ -123,7 +131,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/thebookmarc/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Ursus Books",
@@ -137,7 +146,8 @@
     "country": "USA",
     "website": "https://www.ursusbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Alabaster Bookshop",
@@ -151,7 +161,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/alabasterbookshop/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Unnameable Books",
@@ -165,7 +176,8 @@
     "country": "USA",
     "website": "https://unnameablebooks.square.site/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Community Bookstore",
@@ -179,7 +191,8 @@
     "country": "USA",
     "website": "https://www.communitybookstore.net/",
     "events": "",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Greenlight Bookstore",
@@ -193,7 +206,8 @@
     "country": "USA",
     "website": "https://www.greenlightbookstore.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Dashwood Books",
@@ -207,7 +221,8 @@
     "country": "USA",
     "website": "https://www.dashwoodbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Molasses Books",
@@ -221,7 +236,8 @@
     "country": "USA",
     "website": "https://www.molassesbooks.org/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "The Corner Bookstore",
@@ -235,7 +251,8 @@
     "country": "USA",
     "website": "https://cornerbookstorenyc.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "East Village Books",
@@ -249,7 +266,8 @@
     "country": "USA",
     "website": "https://www.newyorkcitybookbuyer.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Strand Bookstore",
@@ -263,7 +281,8 @@
     "country": "USA",
     "website": "https://www.strandbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Bluestockings Cooperative",
@@ -277,7 +296,8 @@
     "country": "USA",
     "website": "https://bluestockings.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Kinokunya",
@@ -291,7 +311,8 @@
     "country": "USA",
     "website": "https://usa.kinokuniya.com/stores-kinokuniya-new-york",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Three Lives & Company",
@@ -305,7 +326,8 @@
     "country": "USA",
     "website": "http://www.threelives.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Shakespeare & Company",
@@ -319,7 +341,8 @@
     "country": "USA",
     "website": "https://shop.shakeandco.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Book Culture",
@@ -333,7 +356,8 @@
     "country": "USA",
     "website": "https://www.bookculture.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Book Culture on Broadway",
@@ -347,7 +371,8 @@
     "country": "USA",
     "website": "https://www.bookculture.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Book Culture LIC",
@@ -361,7 +386,8 @@
     "country": "USA",
     "website": "https://www.bookculture.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Westsider Rare & Used Books",
@@ -375,7 +401,8 @@
     "country": "USA",
     "website": "http://westsiderbooks.com/bookstore.html",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Word Up Books",
@@ -389,7 +416,8 @@
     "country": "USA",
     "website": "https://www.wordupbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Turn The Page... Again",
@@ -403,7 +431,8 @@
     "country": "USA",
     "website": "http://www.turnthepageagain.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Book Thug Nation",
@@ -417,7 +446,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/bookthugnation/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Posman Books Chelsea Market",
@@ -431,7 +461,8 @@
     "country": "USA",
     "website": "https://www.posmanbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Books Of Wonder",
@@ -445,7 +476,8 @@
     "country": "USA",
     "website": "https://booksofwonder.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Bonnie Slotnick Cookbooks",
@@ -459,7 +491,8 @@
     "country": "USA",
     "website": "http://www.bonnieslotnickcookbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Kitchen Arts & Letters",
@@ -473,7 +506,8 @@
     "country": "USA",
     "website": "https://kitchenartsandletters.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Revolution Books",
@@ -487,7 +521,8 @@
     "country": "USA",
     "website": "https://shop.revolutionbooksnyc.org/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Spoonbill & Sugartown, Booksellers",
@@ -501,7 +536,8 @@
     "country": "USA",
     "website": "https://www.spoonbillbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Melville House Publishers",
@@ -515,7 +551,8 @@
     "country": "USA",
     "website": "https://www.mhpbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Printed Matter",
@@ -529,7 +566,8 @@
     "country": "USA",
     "website": "https://www.printedmatter.org/locations",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "192 Books",
@@ -543,7 +581,8 @@
     "country": "USA",
     "website": "http://www.192books.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Freebird Books",
@@ -557,7 +596,8 @@
     "country": "USA",
     "website": "http://www.freebirdbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Berl's Brooklyn Poetry Shop",
@@ -571,7 +611,8 @@
     "country": "USA",
     "website": "http://www.berlspoetry.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Here's A Book Store",
@@ -585,7 +626,8 @@
     "country": "USA",
     "website": "https://www.facebook.com/profile.php?id=100057614522975",
     "events": "?",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Argosy Books",
@@ -599,7 +641,8 @@
     "country": "USA",
     "website": "https://www.argosybooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "POWERHOUSE on 8th",
@@ -613,7 +656,8 @@
     "country": "USA",
     "website": "https://www.powerhousebookstores.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Terrace Books",
@@ -627,7 +671,8 @@
     "country": "USA",
     "website": "https://www.communitybookstore.net/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "BookMark Shoppe",
@@ -641,7 +686,8 @@
     "country": "USA",
     "website": "https://www.bookmarkshoppe.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Better Read Than Dead",
@@ -655,7 +701,8 @@
     "country": "USA",
     "website": "http://betterreadthandeadbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Better Read Than Dead & Burly Coffee",
@@ -669,7 +716,8 @@
     "country": "USA",
     "website": "http://betterreadthandeadbooks.com/",
     "events": "No",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Desert Island Comics",
@@ -683,7 +731,8 @@
     "country": "USA",
     "website": "https://desertislandbrooklyn.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Topos Bookstore Cafe",
@@ -697,7 +746,8 @@
     "country": "USA",
     "website": "https://toposbookstore.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Human Relations Books",
@@ -711,7 +761,8 @@
     "country": "USA",
     "website": "http://www.humanrelationsbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Respect For Life Books-N-Things",
@@ -725,7 +776,8 @@
     "country": "USA",
     "website": "https://respectforlifebookstore.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "ETG Book Cafe",
@@ -739,7 +791,8 @@
     "country": "USA",
     "website": "http://www.etgstores.com/bookcafe/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "The Center for Fiction",
@@ -753,7 +806,8 @@
     "country": "USA",
     "website": "https://centerforfiction.org/the-center/bookstore/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Catland",
@@ -767,7 +821,8 @@
     "country": "USA",
     "website": "https://www.catlandbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Logos Bookstore",
@@ -781,7 +836,8 @@
     "country": "USA",
     "website": "https://www.logosbookstorenyc.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Joanne Hendricks Cookbooks",
@@ -795,7 +851,8 @@
     "country": "USA",
     "website": "http://www.joannehendrickscookbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Sister's Uptown Bookstore",
@@ -809,7 +866,8 @@
     "country": "USA",
     "website": "http://www.sistersuptownbookstore.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Archestratus",
@@ -823,7 +881,8 @@
     "country": "USA",
     "website": "https://www.archestrat.us/",
     "events": "Yes",
-    "cafe": "Sandwiches and Groceries"
+    "cafe": "Sandwiches and Groceries",
+    "meta": ""
   },
   {
     "name": "Quest Bookshop",
@@ -837,7 +896,8 @@
     "country": "USA",
     "website": "https://www.questbookshop.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The Austin Book Shop",
@@ -851,7 +911,8 @@
     "country": "USA",
     "website": "https://www.facebook.com/austinbookshop/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The Book Cellar",
@@ -865,7 +926,8 @@
     "country": "USA",
     "website": "https://www.facebook.com/thebookcellarnyc/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Chartwell Booksellers",
@@ -879,7 +941,8 @@
     "country": "USA",
     "website": "https://www.chartwellbooksellers.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Bookoff",
@@ -893,7 +956,8 @@
     "country": "USA",
     "website": "https://www.bookoffusa.com/bookoff-49-w-45th-ny-store/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "James Cummins Bookseller",
@@ -907,7 +971,8 @@
     "country": "USA",
     "website": "https://www.jamescumminsbookseller.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Books Are Magic (Smith St.)",
@@ -921,7 +986,8 @@
     "country": "USA",
     "website": "https://www.booksaremagic.net/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Books Are Magic (Montague St.)",
@@ -935,7 +1001,8 @@
     "country": "USA",
     "website": "https://www.booksaremagic.net/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Namaste Bookshop",
@@ -949,7 +1016,8 @@
     "country": "USA",
     "website": "https://www.namastebookshop.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Albertine Books",
@@ -963,7 +1031,8 @@
     "country": "USA",
     "website": "https://www.albertine.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Rizzoli Bookstore",
@@ -977,7 +1046,8 @@
     "country": "USA",
     "website": "https://www.rizzolibookstore.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Standards Manual",
@@ -991,7 +1061,8 @@
     "country": "USA",
     "website": "https://standardsmanual.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Westsider Records",
@@ -1005,7 +1076,8 @@
     "country": "USA",
     "website": "http://westsiderbooks.com/recordstore.html",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Kew & Willow Books",
@@ -1019,7 +1091,8 @@
     "country": "USA",
     "website": "https://www.kewandwillow.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Cafe con Libros",
@@ -1033,7 +1106,8 @@
     "country": "USA",
     "website": "https://www.cafeconlibrosbk.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "McNally Jackson Books Williamsburg",
@@ -1047,7 +1121,8 @@
     "country": "USA",
     "website": "https://www.mcnallyjackson.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "McNally Jackson Books Seaport",
@@ -1061,7 +1136,8 @@
     "country": "USA",
     "website": "https://www.mcnallyjackson.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "McNally Jackson Books City Point",
@@ -1075,7 +1151,8 @@
     "country": "USA",
     "website": "https://www.mcnallyjackson.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Codex Books",
@@ -1089,7 +1166,8 @@
     "country": "USA",
     "website": "http://codexbooks.info/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "The Lit. Bar",
@@ -1103,7 +1181,8 @@
     "country": "USA",
     "website": "http://www.thelitbar.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Shakespeare & Company (Upper West Side)",
@@ -1117,7 +1196,8 @@
     "country": "USA",
     "website": "https://shop.shakeandco.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Mil Mundos Books",
@@ -1131,7 +1211,8 @@
     "country": "USA",
     "website": "https://www.milmundosbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Aeon Bookstore",
@@ -1145,7 +1226,8 @@
     "country": "USA",
     "website": "https://www.aeonbookstore.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Bravo's Book Nook",
@@ -1159,7 +1241,8 @@
     "country": "USA",
     "website": "https://www.bravosbooknook.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Karma Bookstore",
@@ -1173,7 +1256,8 @@
     "country": "USA",
     "website": "https://bookstore.karmakarma.org/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Cups and Books",
@@ -1187,7 +1271,8 @@
     "country": "USA",
     "website": "https://www.facebook.com/cupsandbooksllc/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Book Club Bar",
@@ -1201,7 +1286,8 @@
     "country": "USA",
     "website": "https://www.bookclubbar.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Pillow-Cat Books",
@@ -1215,7 +1301,8 @@
     "country": "USA",
     "website": "https://www.pillowcatbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Sweet Pickle Books",
@@ -1229,7 +1316,8 @@
     "country": "USA",
     "website": "https://www.sweetpicklebooks.com/",
     "events": "Yes",
-    "cafe": "No, but they'll give you pickles in exchange for used books"
+    "cafe": "No, but they'll give you pickles in exchange for used books",
+    "meta": ""
   },
   {
     "name": "Yu and Me Books",
@@ -1243,7 +1331,8 @@
     "country": "USA",
     "website": "https://www.yuandmebooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Bookoff Brooklyn",
@@ -1257,7 +1346,8 @@
     "country": "USA",
     "website": "https://www.bookoffusa.com/brooklyn/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Troubled Sleep Books",
@@ -1271,7 +1361,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/troubledsleepbooks/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Leaves Bookstore",
@@ -1285,7 +1376,8 @@
     "country": "USA",
     "website": "https://www.leavesbookstore.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Burnt Books",
@@ -1299,7 +1391,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/burnt_books_/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Hey Kids! Comics",
@@ -1313,7 +1406,8 @@
     "country": "USA",
     "website": "https://www.heykidscomics.net/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The Drama Book Shop",
@@ -1327,7 +1421,8 @@
     "country": "USA",
     "website": "https://www.dramabookshop.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Recirculation",
@@ -1341,7 +1436,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/recirculation.nyc/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "McNally Jackson Rockefeller Center",
@@ -1355,7 +1451,8 @@
     "country": "USA",
     "website": "https://www.mcnallyjackson.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The Strand At Columbus Ave",
@@ -1369,7 +1466,8 @@
     "country": "USA",
     "website": "https://www.strandbooks.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Dear Friend Books",
@@ -1383,7 +1481,8 @@
     "country": "USA",
     "website": "https://www.dearfriendbooks.com/",
     "events": "No",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "P&T Knitwear",
@@ -1397,7 +1496,8 @@
     "country": "USA",
     "website": "https://www.ptknitwear.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Adanne",
@@ -1411,7 +1511,8 @@
     "country": "USA",
     "website": "https://adanne.co/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "POWERHOUSE @ IC",
@@ -1425,7 +1526,8 @@
     "country": "USA",
     "website": "https://www.powerhousebookstores.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Quimby's Bookstore",
@@ -1439,7 +1541,8 @@
     "country": "USA",
     "website": "https://quimbysnyc.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The Word Is Change",
@@ -1453,7 +1556,8 @@
     "country": "USA",
     "website": "https://www.thewordischange.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Left Bank Books",
@@ -1467,7 +1571,8 @@
     "country": "USA",
     "website": "https://www.leftbankbooksny.com/",
     "events": "No",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "1804 Books",
@@ -1481,7 +1586,8 @@
     "country": "USA",
     "website": "https://1804books.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Black Spring Books",
@@ -1495,7 +1601,8 @@
     "country": "USA",
     "website": "https://www.blackspringbookstore.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Passageway Books",
@@ -1509,7 +1616,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/passageway_books/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Taylor & Co. Books",
@@ -1523,7 +1631,8 @@
     "country": "USA",
     "website": "https://www.taylorcobooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The Ripped Bodice",
@@ -1537,7 +1646,8 @@
     "country": "USA",
     "website": "https://www.therippedbodicela.com/ripped-bodice-brooklyn",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Lofty Pigeon Books",
@@ -1551,7 +1661,8 @@
     "country": "USA",
     "website": "https://www.loftypigeonbooks.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Village Works",
@@ -1565,7 +1676,8 @@
     "country": "USA",
     "website": "https://www.villageworksnyc.com/",
     "events": "Yes",
-    "cafe": "Soon?"
+    "cafe": "Soon?",
+    "meta": ""
   },
   {
     "name": "Topos Too",
@@ -1579,7 +1691,8 @@
     "country": "USA",
     "website": "https://www.toposbookstore.com/",
     "events": "?",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "The World's Borough Bookshop",
@@ -1593,7 +1706,8 @@
     "country": "USA",
     "website": "https://www.theworldsboroughbookshop.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Bibliotheque",
@@ -1607,7 +1721,8 @@
     "country": "USA",
     "website": "https://www.bibliothequenyc.com/",
     "events": "No",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Shakespeare & Company (Manhattan Valley/Columbia University)",
@@ -1621,7 +1736,8 @@
     "country": "USA",
     "website": "https://shop.shakeandco.com/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Liz's Book Bar",
@@ -1635,7 +1751,8 @@
     "country": "USA",
     "website": "https://www.lizsbookbar.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Honey Moon Coffee Shop",
@@ -1649,7 +1766,8 @@
     "country": "USA",
     "website": "https://honeymooncoffee.shop/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "Sullaluna Bistrot & Bookshop",
@@ -1663,7 +1781,8 @@
     "country": "USA",
     "website": "https://www.instagram.com/sullaluna.nyc/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   },
   {
     "name": "The Nonbinarian Bookstore",
@@ -1677,7 +1796,8 @@
     "country": "USA",
     "website": "https://thenonbinarian.gay/",
     "events": "Yes",
-    "cafe": "No"
+    "cafe": "No",
+    "meta": ""
   },
   {
     "name": "Hive Mind Books",
@@ -1691,6 +1811,7 @@
     "country": "USA",
     "website": "https://www.hivemindbooks.com/",
     "events": "Yes",
-    "cafe": "Yes"
+    "cafe": "Yes",
+    "meta": ""
   }
 ]