Shops templates

Full design · Shops

Online store

A complete storefront and back office for a small independent Australian maker, Currawong, selling technical rain shells, packs and merino trail wear.

Live preview. Click around, it works.

About this template

A complete storefront and back office for a small independent Australian maker, Currawong, selling technical rain shells, packs and merino trail wear. Shoppers browse an editorial home page and a featured collection, filter a catalogue by category, price, colour, size and in stock, sort results, and quick add from the grid. Each product has a gallery with zoom and keyboard arrows, real stock per colour and size, a size and fit guide, a simulated delivery estimate by postcode, reviews marked Sample and related products. A cart drawer tracks quantity with undo on remove, a free shipping progress bar and discount codes with real rules: percentage or fixed, a minimum spend, one code per order and an expiry date. Checkout collects an address and a shipping method priced from the owner's own shipping zones, ends in a clearly labelled demo payment, and every order gets a confirmation and a status tracking page. The owner side has an order pipeline with fulfil and a demo refund, a products and inventory editor with per variant stock and low stock alerts, a discount code builder the storefront actually honours, a customers list and settings for shipping zones and rates that really change what checkout charges.

What the agents are asked

The first message sent when you use this template. You can edit the plan before anything is built.

Adapt this online store to my business. Keep the shopper side (Home with the featured collection and brand story, Collection with filters, sorting and quick add, Product with the gallery, variants, size guide, delivery estimate and reviews, the cart drawer with the free shipping bar and discount codes, Checkout, and the order confirmation and tracking page) and the owner side (Orders with the status pipeline, fulfil and refund, Products and inventory with variants, stock and low stock alerts, Discounts, Customers, and Settings with shipping zones and rates). Change the brand, the products, variants, prices, photography, shipping zones and discount codes to match my business, and keep stock, discounts and shipping calculated for real rather than shown as fixed numbers. Where I want real payment, wire in Stripe from project settings as the checkout message says, never putting a secret key in the browser.

A storefront and back office for a small independent Australian maker of rain shells, packs and merino trail wear. Built to docs/guardrails/TEMPLATES.md: real stock per variant, discount codes with real rules, shipping that is actually priced from the owner's own settings, and an owner side that runs the shop rather than just describing it.

Files

FilePurpose
template.jsonslug, name, category, description, prompt, tags, identity
media.jsonthe nine photographs and one video generated for this template
index.htmlthe entry point, one <script type="module" src="./script.js">
styles.csstokens first, then base, layout, components, screens, motion
script.jsone ES module: router, store (data layer), every screen, no dependencies
schema.sqlthe Postgres tables, row level security and seed rows the real store needs

Roles and the Viewing as switch

The header carries a "Viewing as" control in demo mode: Shopper or Owner. A real build replaces this with authentication, store_staff deciding who sees the owner side (schema.sql). Everything a shopper does (cart, addresses, orders they place) is kept in localStorage; the owner side reads and writes the same store object, so flipping the switch is instant and the two sides always agree on stock and orders.

Going live: how the first owner gets in

This template ships as a demo (localStorage only); wiring script.js to Supabase is the one time swap described under Data model below. Once that is done, nobody starts as owner automatically: apply schema.sql to a Supabase project (with Confirm email turned on under Authentication settings, so a person cannot claim an invite by simply typing someone else's address), sign in as the person who should run the store, and call start_as_owner('Their Name'). It only ever succeeds once, for whoever gets there first, and is refused once a real owner exists. To add a second person later, insert a store_staff row with just name, email and role ('owner' or 'staff', user_id left null); once that person signs in with the same, confirmed email, claim_invite() links their account automatically. Neither function takes a client-supplied id: each acts only on the caller's own, confirmed email.

Screens

Shopper

  • Home #/: an editorial hero (photograph and a five second loop of the trail it was shot on), the Wet Weather Edit featured collection, and the brand story with the workshop photograph.
  • Collection #/shop: every product, filtered by category, price band, colour, size and in stock, sorted by featured, price or newest, with quick add from the grid.
  • Product #/product/:slug: a gallery (the photograph plus a swatch per colour, zoomable, arrow key and swipe navigation), variant pickers with real stock per combination, a size and fit guide, a simulated delivery estimate by postcode, reviews marked Sample, and related products from the same category.
  • Cart drawer: quantity steppers, remove with an undo toast, a free shipping progress bar against the store's threshold, and a discount code field with real rules (below).
  • Checkout #/checkout: address, a shipping method priced from the destination's zone, and a payment step that says "Demo checkout. Connect Stripe in project settings." The order is created for real (stock is decremented, a discount is redeemed) with no money moving.
  • Order #/order/:id: confirmation right after checkout, and a status timeline (Paid, Fulfilled, Shipped, Delivered) any time after, reachable again from Track an order #/track with the order number and the email it was placed under (there are no shopper accounts in this demo).

Owner (#/owner/...)

  • Dashboard #/owner: today's orders and revenue, a low stock list, recent orders.
  • Orders #/owner/orders: the pipeline (Pending, Paid, Fulfilled, Shipped, Delivered, Refunded), filterable, with Fulfil, Ship (writes a tracking number) and a demo Refund that restocks every line.
  • Products #/owner/products: edit price, description and variants; every stock change is a delta with a reason, written to stock_movements, not an overwrite; a low stock badge below the product's threshold; add a new product (it gets a typeset tile, like Scree Gaiters and the Two Bottle Sling here).
  • Discounts #/owner/discounts: create a code (percentage or fixed, a minimum spend, an expiry) and the storefront honours it immediately; only one code applies per order, enforced by the data model (an order has one discount_id, not a list).
  • Customers #/owner/customers: every customer created by a placed order, with their order count and lifetime spend.
  • Settings #/owner/settings: the store name and tagline, the free shipping threshold, and the shipping zones and rates. Changing a rate here changes what Checkout charges on the next order, because Checkout reads the same store.shippingZones.

Data model

The store object in script.js mirrors schema.sql one to one: products, variants (flattened per product with productId), collections, reviews, customers, shippingZones, discounts, orders (each carrying its own items), and stockMovements. Swapping localStorage for Supabase means:

  1. Replace the read/write pair in the "Persistence" section of script.js with calls to src/api/.
  2. Replace the client side discount check, checkout total and stock decrement with calls to the matching Postgres functions: check_discount, place_order, fulfil_order, ship_order, deliver_order, refund_order, adjust_stock, track_order / track_order_items. Every one of them prices and validates on the server, exactly like the client side logic they replace, so nothing else in the screens needs to change.
  3. Wire the "Demo checkout" button to a real Stripe payment element; place_order already exists to run from the webhook once payment is confirmed, and store_staff already exists to grant the owner side.

Money is stored in cents everywhere, formatted only at render time. Stock is never overwritten: every change is a signed delta into stock_movements, so a variant's stock column is always the sum of its own ledger and the number on screen always has an explanation.

Discount codes: the real rules

A code is checked against the live cart subtotal, in this order, and fails with a specific reason if any step does not pass:

  1. Exists and is active. The owner can switch a code off without deleting it (STAFF25 in the seed data is off).
  2. Not expired. Checked against expires_at (WETWEATHER15 in the seed data has already expired, to exercise this).
  3. Minimum spend met. FREIGHT20 needs a $180 subtotal; add fewer items and it explains why it will not apply rather than silently doing nothing.
  4. One per order. Applying a second code replaces the first; an order can only ever reference one discount_id, so there is no "stack two codes" path to test for.

WELCOME10 is 10% off with no minimum. FREIGHT20 is a flat $20 off once the cart clears $180, which together with the free shipping threshold often means a shopper is at $0 shipping either way, and the cart explains that rather than double counting it.

Shipping and the delivery estimate

Three zones (Metro, Regional, Remote) each carry a standard and an express rate and an estimated range. The postcode a shopper types on the Product page or at Checkout is matched against a small illustrative table of Australian postcode prefixes (clearly labelled simulated, not real Australia Post data) to pick a zone, and the same zone and the owner's current rates price the order. Standard shipping is waived once the subtotal after any discount clears the store's free shipping threshold; express is never free, because it is a paid service, not a shipping tier.

Honesty

Currawong, its products, its people and its reviews are fictional, invented for this template. Reviews are marked "Sample" in the UI and in is_sample in the data, and are never presented as verified purchases. Checkout is a demo: no card is charged, and the page says so before the shopper reaches it. Delivery estimates are simulated from postcode prefixes, not a live carrier lookup, and the Product page says so.

Art direction

One shoot, art directed as "the hour after rain" on Australian high country moorland: flat, soft, overcast silver grey light with one low warm break of sun on the horizon, 35 to 90mm primes, fine film grain, muted desaturated colour with a single accent hue per shot tied to that product's own colourway (moss, brass, rust, sand). The two brand images (the moorland trail, and hands finishing a seam at the workshop bench) set the palette that every product shot then sits inside: wet stone, weathered timber, eucalypt scrub soft behind. No two photographs share a subject, and each photographed product appears once, as its own record.

What could not be verified here

Video playback, document.startViewTransition and animation-timeline: view() were written to the spec in TEMPLATES.md and degrade behind feature checks and prefers-reduced-motion, but the automation window this was built in reports itself hidden to Chrome, which silently skips both. A reviewer's own visible tab is the real test for the hero loop, the sticky gallery and the view transition between screens.