Desk Tools collection review — Plan a native Shopify collection template with stable product identity, whole-set filter/sort behavior, pagination and an explicit empty…

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Desk Tools collection review</title><style>*{box-sizing:border-box}body{margin:0;font:16px/1.55 system-ui,sans-serif;color:var(--ink);background:var(--bg)}main{max-width:1120px;margin:auto;padding:30px}h1{font-size:clamp(30px,4vw,44px);line-height:1.12;letter-spacing:-.035em;margin:5px 0 12px}h2{font-size:23px;line-height:1.3;margin:0 0 15px}h3{font-size:17px;margin:0 0 8px}p{margin:0 0 12px}.eyebrow{font-size:11px;text-transform:uppercase;letter-spacing:.13em;font-weight:750;color:var(--accent)}.small,footer{font-size:13px;color:var(--muted)}footer{margin:27px 0}.panel{background:white;border:1px solid var(--line);padding:23px;border-radius:9px;margin-top:22px}.two{display:grid;grid-template-columns:1.35fr 1fr;gap:23px}.row{display:flex;gap:9px;flex-wrap:wrap;align-items:center}button{font:700 14px system-ui;min-height:44px;padding:10px 16px;border:1px solid var(--accent);background:var(--accent);color:white;border-radius:5px;cursor:pointer}button.secondary{color:var(--accent);background:white}button[aria-pressed=true]{box-shadow:inset 0 0 0 2px var(--ink)}button:disabled{opacity:.45;cursor:default}:focus-visible{outline:3px solid #d28b25;outline-offset:3px}label{display:block;font-size:13px;font-weight:650}input,textarea,select{display:block;width:100%;padding:11px;font:16px/1.5 system-ui;color:var(--ink);background:white;border:1px solid #9baab6;border-radius:4px;margin:6px 0 15px}textarea{min-height:100px;resize:vertical}.error{color:#a02b20;font-size:14px;min-height:23px}.result{font-size:40px;line-height:1.25;letter-spacing:-.04em;font-weight:750}.notice{padding:15px;background:#fff3db;border-left:4px solid #cf9d3e;font-size:14px;margin:16px 0}details{margin-top:23px;border-top:1px solid var(--line);padding-top:15px}summary{cursor:pointer;min-height:44px;font-weight:700}.time-row{display:grid;grid-template-columns:75px 1fr 70px;gap:14px;padding:15px 0;border-top:1px solid var(--line)}.time-row p{font-size:14px;margin:5px 0}.time-row input{margin:0;text-align:center}.range{font-size:13px;font-weight:700;color:var(--accent)}[hidden]{display:none!important}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:13px/1.6 ui-monospace,Consolas,monospace} @media(max-width:740px){main{padding:23px 16px}.two{grid-template-columns:1fr;gap:0}.panel{padding:20px}}@media(max-width:380px){main{padding:20px 12px}.panel{padding:17px}.time-row{grid-template-columns:1fr 65px}.range{grid-column:1/-1}.row button{flex:1}}:root{--ink:#203c32;--bg:#f7faf6;--accent:#35735a;--muted:#6c8075;--line:#d1ddd3}h1{font-family:Georgia,serif;font-weight:500}.shop-head{display:flex;justify-content:space-between;align-items:end;gap:18px;flex-wrap:wrap;margin-bottom:25px}.toolbar{border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:17px 0;display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:25px;margin:25px 0}.product{background:white;border:1px solid var(--line);border-radius:6px;overflow:hidden}.art{height:215px;background:#e7efe5;display:flex;align-items:center;justify-content:center}.object{width:105px;height:137px;background:#c89669;box-shadow:-8px 4px #3b6f5b;border-radius:4px;transform:rotate(-8deg)}.object.planpad{background:repeating-linear-gradient(#f6eedb 0 15px,#b8cdbf 16px 17px);box-shadow:0 -8px #446552}.object.pen{height:150px;width:16px;border-radius:9px;background:#42638a;box-shadow:3px 0 #adc4dc;transform:rotate(30deg)}.object.case{width:150px;height:70px;background:#c09f7c;border-radius:30px;box-shadow:0 -4px #5a685c;transform:rotate(-8deg)}.object.folder{width:150px;height:100px;background:#c3b87b;border-top:14px solid #aea263;box-shadow:none}.product-info{padding:19px}.product-info h2{font:25px Georgia,serif;margin:5px 0}.product-info p{margin:0}.pages{display:flex;justify-content:center;align-items:center;gap:15px;margin:22px 0}.empty{padding:55px 20px;text-align:center;border:1px dashed var(--line);margin:25px 0}.code{background:#eef3ed;padding:20px;border-radius:5px}.config{display:grid;grid-template-columns:1fr 1fr;gap:0 20px}@media(max-width:500px){.grid{gap:13px}.art{height:155px}.object{width:70px;height:100px}.object.folder{width:100px;height:75px}.object.case{width:100px;height:55px}.product-info{padding:13px}.product-info h2{font-size:21px}.config{grid-template-columns:1fr}.toolbar button{padding:10px 12px}}</style></head><body><main><p class="eyebrow">Original mock collection · browsing review</p><div class="shop-head"><div><h1>Desk Tools</h1><p>Five product identities, two per page. Mock USD prices.</p></div><strong id="range">1–2 of 5</strong></div><div class="toolbar"><div class="row" aria-label="Mock category filters"><button id="filter-all" type="button" aria-pressed="true">All</button><button id="filter-paper" type="button" class="secondary">Paper</button><button id="filter-accessories" type="button" class="secondary">Accessories</button><button id="filter-empty" type="button" class="secondary">No Match fixture</button></div><div class="row"><button id="sort-listed" type="button" aria-pressed="true">Listed order</button><button id="sort-price" type="button" class="secondary">Price: low to high</button></div></div><p id="selection" class="small" style="margin-top:15px">All · Listed order · 5 products</p><section id="products" class="grid"></section><section id="empty" class="empty" hidden><h2>No products match</h2><p>Zero products in the separate No Match fixture.</p><button id="clear-filters" type="button">Clear filters</button></section><div class="pages"><button id="previous" type="button" class="secondary">Previous</button><strong id="page-state">Page 1 of 3</strong><button id="next" type="button" class="secondary">Next</button></div><p class="small">Original CSS product schematics; no stock, product destinations or media URLs are supplied. P01 remains one product across variants.</p><details><summary>Native theme handoff · JSON, section brief and test matrix</summary><p>The grid above is a local fixture companion. Confirm actual template assignment, referenced section, collection filters/sort options, product URLs and media in the approved Shopify theme workflow.</p><div class="config"><label for="section-name">Proposed section type · confirm in theme<input id="section-name" value="desk-tools-collection"></label><label for="theme-notes">Actual configuration / assignment notes<textarea id="theme-notes" placeholder="To confirm"></textarea></label></div><p id="config-error" class="error" role="alert"></p><label for="native-json">Proposed templates/collection.json · manual copy<textarea id="native-json" readonly></textarea></label><button id="select-json" type="button">Select JSON text</button><p id="select-status" class="small" role="status"></p><div class="code"><h3>Referenced Liquid section implementation brief</h3><p id="section-path"></p><p>Render the actual collection object and stable product IDs. HTML/Liquid belongs in the section, not raw JSON. Read available, selected and default native sort choices; use the configured storefront filters. Preserve compatible filter state and reset the page after filter/sort changes. Paginate the resulting collection; verify actual filter/sort URLs, product links and media.</p><p>No section implementation or native runtime is claimed here; confirm the actual theme contracts before writing its production section.</p></div><h3 style="margin-top:22px">Manual native test matrix · planned</h3><ul><li>Five products at size 2: P01/P02, P03/P04, P05; ranges 1–2, 3–4, 5–5 of 5.</li><li>Paper from old page 3 resets to page 1: P01/P02, then P05.</li><li>Ascending Paper: P05/P02, then P01; order the full eligible set first.</li><li>No Match: zero, explicit empty state, no “1 to 0” range.</li><li>Confirm native collection/filter/sort URLs, template assignment, product identity, destinations, media, focus and narrow rendering in the actual theme.</li></ul></details><footer>Self-contained mock browsing and native handoff companion. No Shopify runtime, connected catalog, verified native filtering, navigation, cart, purchase, import, deployment or saved edits. JSON references a proposed section that must exist in the actual theme; it is not a ready-to-deploy theme.</footer></main><script>
'use strict';const $=id=>document.getElementById(id);
const catalog=[{id:'P01',name:'Notebook',category:'Paper',price:8,art:'notebook'},{id:'P02',name:'Planpad',category:'Paper',price:6,art:'planpad'},{id:'P03',name:'Pen',category:'Accessories',price:4,art:'pen'},{id:'P04',name:'Case',category:'Accessories',price:10,art:'case'},{id:'P05',name:'Folder',category:'Paper',price:5,art:'folder'}];let filter='all',sort='listed',page=1;function collection(category,order,wanted){let set=catalog.filter(p=>category==='all'||p.category.toLowerCase()===category);if(category==='empty')set=[];if(order==='price')set.sort((a,b)=>a.price-b.price||a.id.localeCompare(b.id));const count=set.length,pages=Math.ceil(count/2),valid=count?Math.min(Math.max(1,wanted),pages):0;return{count,pages,page:valid,items:valid?set.slice((valid-1)*2,valid*2):[],range:count?((valid-1)*2+1)+'–'+Math.min(valid*2,count)+' of '+count:'0 results'};}function render(){const r=collection(filter,sort,page);page=r.page||1;$('range').textContent=r.range;$('selection').textContent=(filter==='all'?'All':filter==='empty'?'No Match fixture':filter==='paper'?'Paper':'Accessories')+' · '+(sort==='price'?'Price: low to high':'Listed order')+' · '+r.count+' products';$('page-state').textContent=r.count?'Page '+r.page+' of '+r.pages:'No pages';$('previous').disabled=!r.count||r.page<=1;$('next').disabled=!r.count||r.page>=r.pages;$('empty').hidden=r.count!==0;$('products').replaceChildren();for(const p of r.items){const article=document.createElement('article');article.className='product';article.id='product-'+p.id;const art=document.createElement('div');art.className='art';art.setAttribute('aria-hidden','true');const object=document.createElement('div');object.className='object '+p.art;art.appendChild(object);article.appendChild(art);const info=document.createElement('div');info.className='product-info';const id=document.createElement('p');id.className='eyebrow';id.textContent=p.id+' · '+p.category;const name=document.createElement('h2');name.textContent=p.name;const price=document.createElement('p');price.textContent='$'+p.price+' USD · mock';info.appendChild(id);info.appendChild(name);info.appendChild(price);article.appendChild(info);$('products').appendChild(article);}for(const f of['all','paper','accessories','empty'])$('filter-'+f).setAttribute('aria-pressed',String(f===filter));for(const s of['listed','price'])$('sort-'+s).setAttribute('aria-pressed',String(s===sort));}for(const f of['all','paper','accessories','empty'])$('filter-'+f).addEventListener('click',()=>{filter=f;page=1;render();});for(const s of['listed','price'])$('sort-'+s).addEventListener('click',()=>{sort=s;page=1;render();});$('previous').addEventListener('click',()=>{page--;render();});$('next').addEventListener('click',()=>{page++;render();});$('clear-filters').addEventListener('click',()=>{filter='all';page=1;render();});function config(){const s=$('section-name').value.trim(),valid=/^[a-z][a-z0-9-]{0,63}$/.test(s);$('config-error').textContent=valid?'':'Supply a proposed section type using lowercase letters, digits and hyphens, starting with a letter.';$('native-json').value=valid?JSON.stringify({sections:{main:{type:s,settings:{}}},order:['main']},null,2):'';$('section-path').textContent=valid?'Proposed sections/'+s+'.liquid — section must be implemented and confirmed in the actual theme.':'Section path pending.';$('select-json').disabled=!valid;$('select-status').textContent='';}$('section-name').addEventListener('input',config);$('select-json').addEventListener('click',()=>{$('native-json').focus();$('native-json').select();$('select-status').textContent='JSON text selected. Copy manually; confirm the referenced section and template assignment.';});render();config();
</script></body></html>