A zero-dependency web application for managing tile sample inventory at the NYC Tile Store showroom. Built as a direct replacement for the NYC Showroom.xlsx spreadsheet, with all 2,731 sample records embedded directly in the HTML — no server, no database, no internet connection required.
- Full inventory search — partial match across SKU, collection, description, and bin location simultaneously
- Barcode scanner support — plug in any USB/Bluetooth scanner; items are found by partial SKU and their edit modal opens automatically
- Add & subtract quantity — quick ± buttons on every row, or a full edit modal with large buttons and a direct number input
- Change history log — collapsible audit trail inside the edit modal showing every quantity and field change, when it happened, and how it was made
- Threshold / Alerts Settings — configurable global or per-item reorder thresholds; toast notifications fire when stock hits the limit
- Multi-contact alerts — send reorder emails to multiple recipients and SMS alerts to multiple phone numbers, all managed through the UI with no code editing required
- Reorder panel — consolidated view of all low/out-of-stock items; one-click sends a single email to all configured addresses
- Collection color coding — each collection family is assigned a unique, consistent accent color so staff can visually group tiles at a glance; colors adapt correctly between light and dark mode
- Zebra-striped table — alternating white/gray rows for easy row tracking; out-of-stock rows highlighted in rose red
- Dark / Light mode — toggle in the header, preference persisted across sessions; collection colors update instantly on switch
- Bilingual UI — full English and Spanish translations, switchable at runtime
- LocalStorage persistence — all quantity, contact, and history changes survive page refresh without a backend
- CSV export — downloads the current filtered view
- Mobile responsive — works on phones and tablets; stats collapse to 2-column grid on small screens
- Pagination scroll — changing pages returns to the very top of the page so the stats bar and controls are always visible
| Layer | Technology |
|---|---|
| Markup | HTML5 + inline SVG (logo) |
| Styling | CSS3 (custom properties, CSS Grid, Flexbox, @media queries) |
| Logic | Vanilla JavaScript (ES2020, no frameworks or libraries) |
| Data | JSON embedded in <script type="application/json"> in index.html |
| Persistence | Browser localStorage |
| Email alerts | Native mailto: protocol (multi-recipient) |
| SMS alerts | Native sms: protocol (opens device messaging app) |
| Source data | Microsoft Excel (NYC Showroom.xlsx) extracted via PowerShell + OpenXML |
No npm. No build step. No CDN dependency. Open the file and it works.
Claude test/
├── index.html # HTML shell + all 2,731 inventory records (embedded JSON)
├── styles.css # All CSS — variables, layout, components, dark mode, responsive
├── script.js # All JavaScript — i18n, data, search, render, modals, contacts, history
├── NYC Showroom.xlsx # Original spreadsheet (source of truth for initial data import)
├── inventory.html # Legacy single-file version (kept for reference)
├── Screenshot *.png # UI reference screenshot used during development
└── README.md # This file
All three files must be in the same folder.
index.htmlreferencesstyles.cssandscript.jsas relative paths. Openingindex.htmldirectly from the filesystem (file://) works fine — no local server needed.
No installation required.
- Place
index.html,styles.css, andscript.jsin the same folder - Double-click
index.html— it opens directly in any modern browser - All 2,731 sample records load instantly from the embedded JSON
Browsers supported: Chrome 80+, Firefox 75+, Safari 13+, Edge 80+
Double-click index.html
— or —
Drag index.html into a browser window
Using Python:
cd "path/to/Claude test"
python -m http.server 8080Then open http://<your-computer-ip>:8080/index.html on any device on the same network.
Using Node.js:
npx serve "path/to/Claude test"All user-configurable values are managed through the UI — no code editing required. The defaults are:
| Setting | Default | Where to change |
|---|---|---|
| Alert emails | jay@tilestore.com |
⚙️ Threshold/Alerts Settings → Alert Emails |
| Alert phones | (none) | ⚙️ Threshold/Alerts Settings → Alert Phones |
| Global reorder threshold | 2 |
⚙️ Threshold/Alerts Settings → Global Default Threshold |
| Default language | English | ES / EN toggle in header |
| Items per page | 50 |
Per-page dropdown in table footer |
For developers who need to change compile-time defaults, the relevant variables in script.js are:
| Variable | Purpose |
|---|---|
contacts |
Default emails and phones array |
globalThreshold |
Default reorder quantity threshold |
lang |
Default UI language ('en' or 'es') |
perPage |
Default rows per page |
COLL_HUES |
Array of 20 hues used for collection color assignment |
SK / TK / GK / CK / HK |
localStorage key names |
Type in the header search bar. Search is partial and instant across all fields:
"MKTB5888" → matches SKU SMP-MKTB0000005888SAMPLE
"calacatta" → shows all Calacatta collection samples
"17-10" → shows all samples in bin aisle 17-10
"penny round" → matches by description keyword
"overflow" → matches items flagged on the overflow rack
Quick update (inline): Click − or + on any row. The status badge, stats bar, and reorder panel all update immediately and a change is recorded in the audit log.
Full edit modal:
- Click Edit ✏️ on any row
- Use the large ± buttons, or type a number directly into the manual input field
- Optionally update Notes, Order #, and a per-item Reorder Threshold
- Click Save Changes — only fields that actually changed are written to the history log
Every item carries an audit trail of up to 50 entries (oldest dropped automatically):
- Click Edit ✏️ on any row
- At the bottom of the modal, click 🕐 Change History — the panel expands with a smooth animation
- Each entry shows:
- Date and time of the change
- Source label:
Quick update,Edit modal,Scanner, orItem created - Per-field diff:
old value→ new value in red and green
- Click the toggle again to collapse
Changes are recorded for: quantity, notes, order number, reorder threshold, and item creation.
- Click + Add Item in the toolbar
- Enter a full or partial SKU — partial entries are automatically normalized:
"MKTB0000005999" → SMP-MKTB0000005999SAMPLE - Fill in Description (required), Collection, Bin, and initial Quantity
- Optionally set a custom Reorder Threshold for this item
- Click Add Sample — an "Item created" entry is added to the history log
- Click 📷 in the header — the orange scanner banner appears
- Scan any barcode with a USB or Bluetooth scanner
- The app matches the scan against SKUs using partial matching (strips
SMP-prefix andSAMPLEsuffix automatically) - If a match is found, the edit modal opens for that item; subsequent save is recorded as
Scannerin the history - Press Escape or click Stop to exit scanner mode
Open ⚙️ Threshold/Alerts Settings in the header:
Emails:
- The list starts with
jay@tilestore.comas the default - Click + Add to add another recipient — a blank input appears and is focused automatically
- Edit any address directly in its input field — changes save instantly to
localStorage - All configured addresses are combined into one
mailto:so a single email thread reaches everyone - The remove button (✕) is disabled when only one email remains to prevent accidental lock-out
Phones:
- Click + Add to add a phone number (any format —
+1 (555) 000-0000,5551234567, etc.) - When an alert fires, the app opens one SMS compose window per number, staggered by 600 ms to avoid browser popup blocking
- SMS uses the device's native messaging app via the
sms:protocol — works reliably on iOS and Android; on Windows it opens the Phone Link app if paired
Global threshold (applies to all items without an individual override):
- Click ⚙️ in the header to open Threshold/Alerts Settings
- Enter a number in Global Default Threshold
- Click Apply to All — clears all per-item overrides
Per-item threshold:
- Click Edit ✏️ on any row
- Set a value in the Reorder Threshold field
- Save — this item uses its own threshold and ignores the global value
Single item — in the Reorder panel, click 📧 next to any item. Your email client opens with that item's details pre-filled, addressed to all configured emails.
All low-stock items at once:
- Click 🔔 Reorder in the toolbar to open the Reorder panel, or click 📧 Send All Alerts in ⚙️ settings
- One email opens addressed to all configured email recipients with every flagged item listed
- One SMS compose window opens per configured phone number
Click ⬇️ Export CSV — downloads tilebar-nyc-inventory.csv containing the current filtered view with columns:
SKU, Collection, Description, Bin, Qty, Overflow, OrderNum, Notes, Status, Threshold
Each collection is assigned a unique color derived deterministically from its name using a hash function mapped to a palette of 20 perceptually-spread hues:
"Calacatta" → hash → hue index → HSL color pair (always the same)
"Bond" → hash → different index → different color (always the same)
- Colors are consistent — the same collection always gets the same color across sessions and devices
- Colors adapt to theme — light mode uses soft pastel backgrounds with deep text; dark mode uses muted deep backgrounds with bright text
- Switching between light and dark mode instantly re-renders all collection chips with the correct palette
- Colors appear in the table row chips and in the Collection field inside the edit modal
| Row type | Light mode | Dark mode |
|---|---|---|
| Odd rows (1, 3, 5…) | White #ffffff |
#1c1e26 |
| Even rows (2, 4, 6…) | Light gray #f4f5f7 |
#21232e |
| Low-stock rows | Soft yellow | Muted yellow-brown |
| Out-of-stock rows | Rose red #fde8e6 |
Deep red #3b1212 |
| Hovered row | Blue tint (overrides all above) | Blue tint |
Column headers use font-weight: 700 with full-contrast text for clear readability.
First visit:
index.html (embedded JSON) ──► localStorage
Subsequent visits:
localStorage ──► app state (user edits take priority)
embedded JSON ──► merged in (new SKUs added without overwriting existing)
On every change:
app state ──► localStorage (instant, no round-trip)
Five localStorage keys are used:
| Key | Contains |
|---|---|
tilebar-inv-nyc |
Full inventory array with current quantities |
tilebar-thr-nyc |
Per-item threshold overrides { sku: number } |
tilebar-gthr-nyc |
Global threshold number |
tilebar-contacts-nyc |
{ emails: [...], phones: [...] } |
tilebar-history-nyc |
{ [sku]: [{ts, src, changes}] } — audit log, max 50 entries per item |
If NYC Showroom.xlsx is updated and you need to refresh the embedded records:
- Run the PowerShell extraction scripts (saved in
%TEMP%from the original build):export_v2.ps1— reads the Excel XML and writesinventory_v2.jsoninject_json.ps1— injects the JSON intohtml_template.txtand writesindex.html
- Or paste the updated JSON array directly into the
<script type="application/json" id="inventory-data">block insideindex.html
Existing browser-side quantity changes are preserved after a re-import — the merge logic keeps
localStoragevalues for known SKUs and only adds new ones from the embedded JSON.
Paste in the browser console (DevTools → Console) to wipe all saved changes and reload from the embedded spreadsheet data:
localStorage.removeItem('tilebar-inv-nyc');
localStorage.removeItem('tilebar-thr-nyc');
localStorage.removeItem('tilebar-gthr-nyc');
localStorage.removeItem('tilebar-contacts-nyc');
localStorage.removeItem('tilebar-history-nyc');
location.reload();To reset individual stores without touching others:
// Contacts only
localStorage.removeItem('tilebar-contacts-nyc');
// History/audit log only
localStorage.removeItem('tilebar-history-nyc');
// Inventory quantities only (reverts to embedded spreadsheet data)
localStorage.removeItem('tilebar-inv-nyc');
location.reload();Short answer: No — the current version works fine on any computer, including machines 5–7 years old.
| Concern | Reality |
|---|---|
| Browser compatibility | Chrome 80+ (released 2020) runs on any Windows 7+ machine. A 5–7 year old PC running Windows 10 handles this with ease |
| Performance | 2,731 records in memory is tiny — filtering and sorting happens in microseconds. Even a 2015 Intel Core i3 with 4 GB RAM won't break a sweat |
| No install, no network | Nothing to configure, no IT department needed, works offline — plug in a USB scanner and go |
| Data safety | localStorage survives browser restarts and Windows updates. Only clears if someone explicitly wipes browser data |
| File size | 805 KB HTML + 30 KB JS + 17 KB CSS — loads instantly even on a spinning hard drive |
Consider it only if you hit one of these real problems:
- Multiple people editing at the same time — if two staff members update the same item from different computers, neither sees the other's change. Changes don't sync between machines
- Inventory grows beyond ~10,000 items — embedded JSON starts to feel slow to load at that scale
- You need a central audit trail — currently history is per-browser. If Jay updates from the front desk PC and someone else updates from a tablet, the histories are siloed
- Management wants reports or dashboards — pulling data from a database into charts is straightforward; pulling from scattered
localStorageinstances is not
If the only problem is sync between computers, you can solve it without a database at all:
- Shared network folder — save the exported JSON to a shared drive (or OneDrive / SharePoint) and load it from there; every computer reads the same file
- Google Sheets as a backend — use the free Google Sheets API to keep the spreadsheet workflow your team already knows, with real-time sync added on top
Stay with the current version unless you have two or more people actively editing inventory simultaneously on different machines. For a single showroom where one person at a time manages the sample room, this setup is more reliable than a database — fewer moving parts, nothing that can go down, and it runs comfortably on hardware from 2015.