A single-page marketing site for Corino Studio, a fictional Oslo-based audio-plugin company. It sells two plugins — HALO (saturator) and DRIFT (granular reverb) — plus a bundle, and is wired end-to-end to a real Moonbase tenant for auth, cart, checkout, downloads, and account management.
This repo exists as a reference implementation: the smallest plausible site (one HTML file, no build tooling) that still demonstrates how to embed the Moonbase storefront with realistic intent links, conditional rendering, and live-bound pricing.
- Plain HTML5 + CSS + vanilla JS in a single
index.html - Google Fonts (Space Grotesk, JetBrains Mono, Inter)
@moonbase.sh/storefrontloaded directly fromassets.moonbase.sh- No bundler, no package.json, no framework, no build step
The site needs to be served over HTTP — file:// won't work because the Moonbase module is loaded as an ES module and talks to a real tenant. Any static server will do:
python -m http.server 8000
# or
npx http-server -p 8000Then open http://localhost:8000.
.
├── index.html # the entire site — markup, styles, scripts
├── assets/ # product artwork (SVG + PNG)
└── .context/ # gitignored scratch space for agents
The integration has three moving parts: one initialization call, intent links for user actions, and data-moonbase-* attributes for conditional UI and live data binding. All three live inside index.html.
At the bottom of the page:
<script type="module" src="https://assets.moonbase.sh/storefront/moonbase.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', () => {
Moonbase.setup('https://store.cori.no', {
toolbar: { enabled: false },
theme: {
dark: true,
colors: { primary: '#eca45d' },
corners: 'sharp',
buttons: 'light',
fonts: { heading: 'Montserrat', body: 'Inter' },
},
});
});
</script>- Tenant:
https://store.cori.no— the Corino storefront tenant. - Toolbar: disabled (we drive everything from page UI instead).
- Theme: dark, sharp corners, golden primary
#eca45d, light buttons, custom heading/body fonts. The theme only restyles Moonbase-rendered surfaces (modals, forms, checkout); the page's own CSS is untouched.
Moonbase exposes deep links via the ?mb_intent=… query parameter. Plain <a href> is enough — no JS handlers needed.
| Action | Example |
|---|---|
| Sign in | ?mb_intent=sign_in |
| View account | ?mb_intent=view_account |
| View cart | ?mb_intent=view_cart |
| Add product to cart | ?mb_intent=add_to_cart&mb_product_id=halo |
| Add bundle to cart | ?mb_intent=add_to_cart&mb_bundle_id=duo-bundle |
| View product (trial) | ?mb_intent=view_product&mb_product_id=drift |
| Download owned product | ?mb_intent=download_product&mb_product_id=halo |
The demo tenant uses three IDs: halo, drift, and duo-bundle.
Moonbase observes three attributes on regular HTML elements:
data-moonbase-if="…" — conditional rendering. Examples used in the site:
!user/user— toggle the Log in vs My account links in the nav.cart.has_items— show the cart-count badge only when there's something in it.product.halo.owned/!product.halo.owned— swap the product card CTA between Download and Add to cart.product.halo.has_discount— show the strike-through original price and the discount badge.bundle.duo-bundle.has_discount— show the bundle discount row.
data-moonbase-render="…" — bind text content to live data:
user.name— greet the logged-in user.cart.item_count— populate the cart badge.product.<id>.price,product.<id>.original_price,product.<id>.discount_name— live pricing on each product card.bundle.<id>.price,bundle.<id>.discount_total— bundle pricing.
data-moonbase-form="subscribe": the newsletter form in the footer. Moonbase takes over the submit and posts the email field to the tenant in the background, so the page never reloads. With data-moonbase-feedback="inline" the drawer stays closed and the result lands on the form itself:
data-moonbase-stateon the<form>goessubmitting→subscribed/confirmation_sent(double opt-in) /error. The CSS keys off it to swap the button label to Subscribing… and to hide the input once the visitor is on the list.- The
[data-moonbase-message]paragraph gets the outcome as text (or the server's error), and is made a live region for screen readers.
The pattern is: write static, sensible-looking markup for the empty state, then let Moonbase hydrate the text and visibility once it knows about the user, cart, and catalog.