{"site":"https://superfast.shop","stores":[{"id":"photo-store","name":"Photo Store","url":"https://photo-store-vector.superfast.shop","llms":"https://photo-store-vector.superfast.shop/llms.txt","industry":"Camera retail · B2C and B2B","thesis":"Every shopper sees their own store, ranked from their order history with every position explained — and the same typed catalogue (focal length, aperture, megapixels as numbers) is what the in-store assistant and the MCP server for Claude shop with.","color":"#4f46e5","onColor":"#ffffff","stats":[{"value":"350","label":"products"},{"value":"3,300","label":"typed specs"},{"value":"21","label":"campaigns"},{"value":"15","label":"MCP tools"}],"features":["Vector ranking everywhere","Explainable scores","Typed specs as real filters","AI assistant and MCP for Claude"],"audience":["b2c","b2b"],"sector":"electronics","capabilities":["personalisation","explainable-ranking","spec-filters","ai-agent","merchandising","forgiving-search","campaigns","multi-market","multi-currency","contract-pricing"],"payments":[],"currencies":["GBP","USD"],"languages":["EN"],"topic":"Camera Retail Ecommerce with AI Product Ranking and an MCP Shopping Agent","description":"Camera and photo equipment ecommerce demo: 350 products from 42 brands with typed specifications, ranked per shopper by vector personalisation, an in-store AI assistant and an MCP server for Claude, with B2B and B2C pricing in two markets.","page":"https://superfast.shop/demos/photo-store","screenshot":"https://superfast.shop/shots/photo-store.jpg","preview":"https://superfast.shop/previews/photo-store.mp4","icon":null,"promptUrl":"https://superfast.shop/demos/photo-store/prompt.md","prompt":"# Build my own camera retail store on Crystallize, like Photo Store\n\nI want a storefront for my business that works like **Photo Store** (https://photo-store-vector.superfast.shop), a demo store built on Crystallize: Every shopper sees their own store, ranked from their order history with every position explained — and the same typed catalogue (focal length, aperture, megapixels as numbers) is what the in-store assistant and the MCP server for Claude shop with.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: vector personalisation, explainable ranking, typed specs and filters, ai concierge, merchandising rules, typo-tolerant search, campaigns, multiple markets, multi-currency, customer price lists. It is B2C and B2B, in English; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which brands do I carry? Which of them own a system (a lens mount, a battery or accessory platform) and which make compatible products?\n- What compatibility matters in my range (camera mounts, platforms, ecosystems), and which products fit everything?\n- My product types, and which ones replace each other (two cameras) and which go together (a camera and a memory card)?\n- The use cases or customer interests I merchandise by (wildlife, portrait, travel, video…), and levels (beginner to pro).\n- My markets, currencies and tax rates; do prices in my data include tax?\n- Can I export margin, units sold in the last 30 days, ratings and stock per SKU? The ranking uses them.\n- Do I have order history per customer to import (through the Shop API's `/order` `create`, never Core)? It builds each shopper's taste; without it the store starts on house ranking.\n- Which campaigns am I running: name, dates, priority and which products.\n- My product types, and the specifications that matter for each (focal length and aperture for lenses; sensor, megapixels and video for cameras; flight time for drones; guide number for flashes; capacity and speed for cards). Do I have manufacturer spec sheets, or should they be researched per product?\n- Should an AI assistant shop the store — inside the site, and/or as an MCP server so Claude can? Which AI provider key?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\n- **seo** piece: title, description, image.\n- **category** (folder): intro, description, hero, seo.\n- **brand** (document): description, logo, website, seo.\n- **campaign** (document): headline, subheading, badge, body, `image` (a banner with no text burned in), `cta` chunk (label, url), `layout` selection (hero, tall or square; required, so set it at creation), `priority`, `valid-from`, `valid-to`, `products` relation (max 8), seo.\n- **product** (product):\n  - `description`, `brand` relation (exactly one, set at creation), `usp` (repeatable chunk: text), `related` relation → product (max 8), seo.\n  - `specs`: a component **choice** of exactly one typed piece per product type — `camera-specs`, `lens-specs`, `drone-specs`, `flash-specs`, `light-specs`, `support-specs`, `gimbal-specs`, `storage-specs`, `battery-specs`, `binoculars-specs`, `microphone-specs`, `monitor-specs`, `bag-specs` — plus a shared `physical` piece (weight g, width / height / depth mm). Every field is a numeric with a unit (`focalLengthMin` mm, `maxAperture` f/, `megapixels` MP, `maxFlightTime` min …), a boolean (`stabilised`, `weatherSealed`, `ibis`) or a selection (`coverage`, `sensorFormat`, `maxVideoResolution`). No label/value strings, no properties tables: every spec is a Discovery filter. Keep the model in one shared file that the setup script, the storefront and the agent all read (field, label, unit, options).\n  - Numerics without a `units` list (a list makes a unit mandatory on every item, and products that lack a spec then fail to publish); write the unit with each value instead. Selection keys must be identifiers (`full_frame`, not `full-frame`): Discovery turns them into field names.\n  - Discoverable merchandising numerics: `margin` (0–1), `sold-30d`, `rating`, `campaign-boost`.\n  - One variant per product with images and stock on it. SKU as `externalReference`.\n- Topic maps, exactly two levels, where the root is a taste dimension and the leaf its value: `brand`, `system` (mounts or platforms, plus `system-agnostic`), `category` (product types), `usecase`, `level`, `price-tier`. Topic order on a product matters: own brand before compatible brand, strongest use case first.\n- Vector vocabularies, with weights by topic position:\n  - `brand`: brand [1.0, 0.5, 0.25], system [1.0, 0.5].\n  - `usecase`: usecase [1.0, 0.7, 0.45, 0.3].\n  - `gear`: category [1.0, 0.5], level [1.0], price tier [0.8].\n  - Leave out keys that mean \"no preference\" (`system:system-agnostic`, `brand:other`).\n- Catalogue: two levels of category folders (e.g. `/cameras/mirrorless-cameras`, `/lenses/sony-lenses`, `/accessories/tripods`), plus `/brands` and `/campaigns`.\n- Pricing: net prices (four decimals), one price variant per currency. Tax is applied per market in the cart.\n- Customer groups per market and segment (B2C, B2B): B2B sees prices excluding tax by default, with a VAT toggle.\n- Stock: one default location.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo holds 350 products from about 43 brands in 28 folders, with 21 campaigns, and ~3,300 typed specification values researched per product from manufacturer spec sheets, each entry with a source URL. Its merchandising signals, customers and orders are fictional.\n- Build order: tenant (VAT, price variants, currency) → model (piece, shapes, topic maps, vocabularies) → images → catalogue → customers and order history → vectors (last).\n- From me: products with brand, category, compatible system, use cases, level, price tier, net prices per currency, stock and images; margin, sales and rating (or proxies); campaigns; order history per customer.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, plain `fetch` GraphQL clients; Crystallize's WebP renditions go straight into `srcset`. One catch-all route resolves category, product, campaign and brand by path.\n- **Discovery API** (public) for all browsing: front page, campaigns, category listings with topic facets, product pages, brand pages, search, autocomplete and recommendations.\n- **Ranking per surface:** `search(context: { userTaste }, rankBy: { terms, tieBreaker })`, with a badge that says why a product is where it is, built from the shopper's taste and the product's topics. Request `explain: true` only behind a merchandiser flag (`rankExplain` is null on taste-only and `nearestTo`-only queries).\n  - Field names are per-tenant enums: introspect `TenantRankByField` and `TenantRankByTieBreaker` (e.g. `fieldBoost` on `sold_30d_number`, `inStockBoost` on `stock_default`, `recency` on `publishedAt`). A numeric you facet on can't be boosted.\n  - House terms: boosts on sales (0.55), margin (0.35), rating (0.3), campaign boost (0.25), in stock (0.25), and recency (0.2, 90-day half-life).\n  - Front page: `tasteCosine` brand 1.7, use case 1.4, gear 0.8, plus house. Category pages: 1.4 / 1.2 / 1.0 plus house. Campaigns: taste plus campaign priority. Search: relevance 2.0 plus lighter taste.\n  - Upsell (\"others also buy\"): the basket's taste blended over the shopper's, excluding what's in the basket and products that replace it.\n  - Drop the taste terms when there is no shopper taste; never send `context: null`.\n- **Similar products:** `nearestTo: { vocabulary: gear, like: { sku }, k }` (`k` replaces the page size). An anchor with no `gear` taste returns nothing, so check before rendering; to blend in stock or margin, add a `tasteCosine` term `from: nearestTo` to a `rankBy`.\n- **Search:** `term` with `options: { fuzzy: { fuzziness: SINGLE, prefixLength: 1 } }`. **Autocomplete** is a prefix match per word (it doesn't fix typos): use `filters: { name: { autocomplete: { term } } }` with the same `term` and `sorting: { score: desc }`, and drop hits with score 0. If it finds nothing, fall back to single-edit fuzzy search; use double only as a last resort.\n- **Shopper taste:** read the customer's orders from the Shop API (`orders(customerIdentifier)`, on the server), get each SKU's leaf topics from Discovery, and sum them, weighted by quantity (capped at 3), share of spend and recency (180-day half-life). Send one `userTaste` entry per vocabulary as `{ vocabulary, weights: { \"dim:value\": w }, magnitude }`, where magnitude is the square root of the sum of squared weights (a wrong magnitude raises no error, so unit-test it). Read orders with `limit: 100` and the identifier from the session, deduplicated by `coreId`.\n- **Typed specs on the page:** the product page shows the product's spec group and then weight and size, labelled and unit-formatted from the model; cards carry two or three key specs (\"70–200 mm · f/2.8 · Full frame\"); JSON-LD `additionalProperty` and the agents' product details come from the same rows. Discovery serves the choice as `specs { lens { focalLengthMin focalLengthMinUnit … } camera { … } }` with the other branches null; filters are `specs_<group>_<field>_number` (range), `specs_<group>_<field>` (boolean) and one `specs_<group>_<field>_<optionKey>` per selection option.\n- **AI assistant and MCP server:** a streamed chat on the site with tools over Discovery and the Shop API (search with the same ranking as the site, product details, similar products, basket); the same tools as an MCP server at `/api/mcp` (Streamable HTTP) with OAuth 2.1 where the store is its own authorization server for the demo sign-in, an MCP Apps UI resource that renders results as cards with Add-to-basket, and `llms.txt` plus a Markdown version of every page. The search tool understands lens optics as numbers (\"35mm f/1.4 prime\", \"70-200 f/2.8\", \"lyssterk\" = f/2 or brighter) and takes a structured `specs` argument (`camera.megapixels ≥ 40`, `camera.sensorFormat = full-frame`, `physical.weight ≤ 500`): numbers become index range filters, booleans and options are checked on the hits, and the hits are ordered by closeness to the asked optics. Vague questions return \"Did you mean\" suggestions with the exact arguments to run. The model never sees a token and only answers from tool results.\n- **Shop API:** a JWT from `/auth/token` (scopes `cart`, `order`); `hydrate` with taxes not included, the market's tax rate, `decimals: 4` and the market's price variant; then `place`, and create the order with `/order` `createFromCart` once per cart (after a verified payment webhook when paying by card).\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Vector ranking must be enabled for the tenant, then indexed with `igniteDiscoApi(stacks: opensearch)`: poll the task until complete and allow a few minutes. Detect the capability by checking that `__type(name: \"RankByInput\")` is non-null, not by error text; `ExperimentalFeaturesNotAvailableError` means ranking isn't enabled.\n- Vectors go last, in this order: vocabulary (`upsertVocabulary` replaces it whole) → `setItemTaste` (it writes the draft and empties variant components) → rewrite variant components → `publishItem` per item and language → `igniteDiscoApi(stacks: opensearch)`. After the first index, pass the vocabulary as an unquoted enum.\n- Topic maps must be exactly two levels for the path-to-taste-key rule (or store the taste key in each topic's meta); share the taxonomy code between the setup scripts and the storefront.\n- Pass required components (the brand relation, the layout selection) when creating items, not afterwards.\n- Create shapes empty first, then add their components, so relations and pieces resolve. Content chunks can't nest inside a choice; pieces can. A component whose type changes (a chunk that becomes a choice) has to be dropped from the shape first and re-added.\n- `publishItems` is asynchronous and fails silently on validation: after a model change, check `item(id, versionLabel: published)` for the new component types before indexing. New fields reach the Discovery schema only after `igniteDiscoApi`, sometimes a second one, and then roll out node by node for a few minutes.\n- Changing a price later needs `modifyProductVariantPrice` and a republish.\n- Keep four-decimal net prices and `decimals: 4` in the Shop API, or basket totals are off by pennies.\n- Under `rankBy`, `term` still decides which items match; only a `relevance` term makes the text match affect the order. Don't pass `sorting` with ranking; page ranked lists with `skip` and `limit` inside the rerank window (500 by default). Double fuzziness on full search matches nearly everything.\n- Discovery rejects explicit `null` variables: leave them out.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"food-universe","name":"Food Universe","url":"https://food-universe.superfast.shop","llms":"https://food-universe.superfast.shop/llms.txt","industry":"Food wholesale · B2B","thesis":"A food wholesaler built live in an hour: case prices, GS1 data and allergens, recipes that sell their ingredients, and trade customers ordering on invoice within their credit limit.","color":"#1f3d2b","onColor":"#ffffff","stats":[{"value":"435","label":"products"},{"value":"63","label":"brands"},{"value":"12","label":"recipes"},{"value":"3","label":"languages"}],"features":["GS1 and allergens","Recipes to basket","Invoice and credit limits","Built in 60 minutes"],"audience":["b2b"],"sector":"food-beverage","capabilities":["invoice-credit","shoppable-content","gs1","volume-pricing"],"payments":["invoice"],"currencies":["EUR"],"languages":["EN","NL","NO"],"topic":"B2B Food Wholesale & Foodservice Ordering","description":"Foodservice wholesale demo: 435 GS1 products, case prices ex VAT, allergen filters, invoice with credit limits, contract price lists and recipes that order ingredients.","page":"https://superfast.shop/demos/food-universe","screenshot":"https://superfast.shop/shots/food-universe.jpg","preview":"https://superfast.shop/previews/food-universe.mp4","icon":"https://superfast.shop/icons/food-universe.png","promptUrl":"https://superfast.shop/demos/food-universe/prompt.md","prompt":"# Build my own food wholesale store on Crystallize, like Food Universe\n\nI want a storefront for my business that works like **Food Universe** (https://food-universe.superfast.shop), a demo store built on Crystallize: A food wholesaler built live in an hour: case prices, GS1 data and allergens, recipes that sell their ingredients, and trade customers ordering on invoice within their credit limit.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: invoice and credit limits, shoppable content, gs1 and allergens, volume tiers. It is B2B, in English, Dutch, Norwegian; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which product feed or export do I have: GTIN, pack sizes, ingredients, allergens, nutrition, storage temperatures? Do I have packshots? (GS1 fields are ideal; Open Food Facts is a free fallback for branded retail products.)\n- What do I sell by: the case or the unit? Which currencies and VAT rates (food is often a reduced rate)? Do my prices include VAT?\n- Do I sell B2B, B2C or both? Do my trade customers have credit limits, payment terms and their own contract prices?\n- My category tree, and what buyers filter on: brand, cuisine, diet, allergens, storage.\n- Do I have recipes or other content that should sell the products in them?\n- Do I need stock or delivery slots (chilled and frozen logistics)?\n- How do customers pay: invoice only, or card too (and through which provider)?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nEvery component is discoverable. Item relations are not translated.\n\n- Pieces:\n  - `seo`: title, description, image.\n  - `nutrition` per 100 g, all numeric: energy (kJ and kcal), fat, saturated fat, carbohydrates, sugars, protein, salt.\n- **category** (folder): description, hero, seo. **folder** (folder): description, seo.\n- **brand** (document): logo, tagline, description, website, featured products (max 8), seo.\n- **allergen** (document): GS1 allergen code, icon, description, `eu-regulated` boolean. The EU 14, split into their sub-types, as items.\n- **product** (product):\n  - `brand` relation (exactly one), tagline, description, ingredients (rich text).\n  - `segments` selection (foodservice, retail), `diet-types` selection (vegan, vegetarian, gluten-free, lactose-free, organic).\n  - `identifiers` chunk: GTIN (13 digits, validated by pattern), supplier number.\n  - `allergens`: repeatable chunk of an allergen relation and a level (contains or may contain).\n  - `nutrition` piece; `storage` chunk (ambient, chilled or frozen; min and max °C).\n  - `commercial` chunk for ranking: margin %, sales velocity (cases a week), return rate %.\n  - `recipes` relation → recipe (back-links, max 12), `related-products` (max 8), seo.\n  - One variant per product, the case, with flat variant components (content chunks on variants may not publish): `units-per-case` and `unit-net-content` (numeric) and `pack-label` (single line, e.g. \"6 × 450 g\").\n- **recipe** (document): image, intro, servings, prep and cook time, difficulty, diet types, `ingredients` (repeatable chunk, translated: quantity, unit, name, and a relation to the product that sells it), steps (paragraphs), tips, related recipes, seo.\n- Topic maps: `cuisine` (two levels, e.g. asian → thai), `occasion`, `product-type` (group → type, e.g. sauces → soy sauce), `main-ingredient`, and `asset-type` for tagging images (packshot, recipe photo, brand logo).\n- Catalogue: root folders `/products` (flat category folders underneath, products directly in them), `/brands`, `/recipes`, `/allergens`. Paths are translated from the item names.\n- Pricing: one price variant, excluding VAT, per case. VAT applied in the cart.\n- Contract pricing: price lists targeted at one company each (`targetAudience: { type: SOME, customerIdentifiers: [company] }`; its contacts inherit it), on all SKUs or some. Prefer percentage modifiers: they keep volume tiers, while an absolute list sets a flat price.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo's 435 products come from an anonymised supplier export with GS1-style fields and packshots. Prices, GTINs and ranking signals were generated. Recipe photos were made with an image model from one shared style prompt.\n- The AI translated and classified the products (topics, diets, segments) in batches, then validated them.\n- Build order: model → topics (with translations) → images (tagged by asset type) → folders, brands, allergens → products → recipes and back-links → publish per item and language → customers → order history (Shop API `/order` `create`, never Core `registerOrder`) → taste, publish, re-index.\n- From me: a product feed, packshots and logos, prices, customers and their terms, and recipes. Missing recipes can be written and illustrated with me, clearly marked.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, plain `fetch` GraphQL clients, one catch-all route that resolves the item at each path. URLs are `/<lang>/<path>`.\n- **Discovery API** for every catalogue read:\n  - Category pages: browse products under the category path, with disjunctive facets (one query per facet, leaving that facet's own filter out) over brand, topics and diet types. Without a taste: sort by sales velocity, then item id. With a taste: `rankBy` only (sales velocity as a `fieldBoost`), no `sorting`, paged with `skip` and `limit`.\n  - Search: `search(term)` ranks by relevance. Suggestions: `filters: { name: { autocomplete: { term } } }` plus the same `term` and `sorting: { score: desc }`, dropping hits with score 0.\n  - Path resolution: `search(path: \"<path>\", pathResolutionMethod: canonical)` returns the item and its shape at a path.\n  - Drafts for preview: token headers plus `publicationState: draft`.\n- **Shop API:** the server exchanges the token for a JWT (`/auth/token`, scopes `cart`, `cart:admin`, `order`) and caches it. `hydrate` with the full item list every time and `decimals: 4`; the cart id lives in an httpOnly cookie. Order history from `/order` `orders(customerIdentifier, limit: 100)`, the identifier from the session, deduplicated by `coreId`.\n- **Checkout on invoice:**\n  1. Check credit: the limit minus the gross total of unpaid orders. Load the company's credit limit and terms once at sign-in on the server and keep them in the session, never per page view.\n  2. `hydrate` with the company as customer, and meta for who ordered and the PO reference.\n  3. `place`, then `/order` `createFromCart` with `paymentStatus: unpaid` and `payments: [{ provider: \"invoice\", method: \"invoice\", amount }]`.\n- **Trade customers (created in setup through the Core API):** each company is an organization customer with credit limit, payment terms and segment in its meta; contacts are individuals with the company as parent.\n- **Contract prices:** the cart applies the price list when hydrated with the company. Discovery prices don't, so product cards and pages read the company's price list on the server from the Catalogue API: `priceList(identifier) { productVariants(language, first) { edges { node { sku priceVariant(identifier) { priceList(identifier) { modifier modifierType } } } } } }`, paged until an empty page (`hasNextPage` stays true). Apply by `modifierType`, or use `priceFor(count, customerIdentifiers: [company])`, which applies volume tiers too. Round for display, and cache per company.\n- **Recipes that sell their ingredients:** \"Order all ingredients\" adds one case of each linked product; portions scale the quantities. Product pages list the recipes they're used in.\n- **Reorder** puts a past order back in the cart. **Recommendations:** the company's most-ordered products, plus the other ingredients of recipes using what's in the cart.\n- **Taste ranking:** a `taste` vocabulary over cuisine, product type, main ingredient and occasion. `setItemTaste` for every product (it empties the variant components, so rewrite the pack fields), `publishItem` per item and language, then `igniteDiscoApi(stacks: opensearch)` and poll until complete. Send `context.userTaste: [{ vocabulary: taste, weights, magnitude }]`. A company's taste mixes chosen preferences (0.6) with the topics of its order history (0.4), top 12 keys. Rank with `tasteCosine` plus a 0.25 boost on sales velocity; similar products with `nearestTo`.\n- **Preview:** a draft route registered as a custom preview view in the Crystallize App.\n- **SEO and agents:** hreflang, Product and Recipe JSON-LD, sitemaps per language, `llms.txt`, and every page as Markdown.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, `NEXT_PUBLIC_SITE_URL`.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- In mass operations, get item ids with `fetchItemByResourceIdentifier … 'itemId'`; `valueOf … 'id'` returns the document id.\n- An upsert replaces all components. A numeric with 0 decimal places is rejected. The Core API pages at 100.\n- A mass-operation topic subtree is capped at 30 topics; create large maps through the Core API.\n- Chunks with translated text must be multilingual, or one language overwrites the others.\n- `publishItems` is async and can hide failures: publish per item and language. An empty numeric piece blocks publishing unless you pass `disableComponentValidation: true`.\n- Discovery: never pass explicit `null`; filter on topics or parent paths, not on name patterns; name-autocomplete filters return score-0 hits in no useful order, so pair them with `term` and sort by score; relations inside chunks come back in the default language.\n- Shop API: the JWT expires; `hydrate` replaces the cart; `taxRate` is a fraction; use `decimals: 4` and round half up for display; `place` locks the cart.\n- Price lists: `build-mass-operation` drops unknown fields silently, so check the real shape with the MCP.\n- Arguments to cached calls must be stable (sort arrays). Retry Discovery on 429 and 5xx.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"tools-universe","name":"Tools Universe","url":"https://tools-universe.superfast.shop","llms":"https://tools-universe.superfast.shop/llms.txt","industry":"Power tools · B2B and B2C","thesis":"Six manufacturers on typed, ETIM-aligned specs, with the battery platform as the strongest signal and heavy equipment to rent by the day — and the same typed catalogue is what the on-site assistant and the MCP server for Claude shop with.","color":"#d7f23a","onColor":"#0f1113","stats":[{"value":"416","label":"products"},{"value":"2,800","label":"typed specs"},{"value":"24","label":"rental items"},{"value":"18","label":"MCP tools"}],"features":["Equipment rental","ETIM 10.0 typed specs","Battery platforms","AI assistant and MCP for Claude"],"audience":["b2b","b2c"],"sector":"industrial","capabilities":["rental","booking","quotes","etim","spec-filters","ai-agent","personalisation","contract-pricing","stock-per-location","multi-market","multi-currency"],"payments":[],"currencies":["EUR","NOK","SEK"],"languages":["EN","NO","SV","NL"],"topic":"Power Tools B2B Ecommerce & Equipment Rental with an MCP Shopping Agent","description":"Power tools and construction supply demo: typed ETIM specs, shop by battery platform, trade price lists, quotes, equipment rental booked by the day from five depots, an on-site AI assistant and an MCP server for Claude.","page":"https://superfast.shop/demos/tools-universe","screenshot":"https://superfast.shop/shots/tools-universe.jpg","preview":"https://superfast.shop/previews/tools-universe.mp4","icon":"https://superfast.shop/icons/tools-universe.png","promptUrl":"https://superfast.shop/demos/tools-universe/prompt.md","prompt":"# Build my own power tools store on Crystallize, like Tools Universe\n\nI want a storefront for my business that works like **Tools Universe** (https://tools-universe.superfast.shop), a demo store built on Crystallize: Six manufacturers on typed, ETIM-aligned specs, with the battery platform as the strongest signal and heavy equipment to rent by the day — and the same typed catalogue is what the on-site assistant and the MCP server for Claude shop with.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: rental, native booking, quotes, etim, typed specs and filters, ai concierge, vector personalisation, customer price lists, stock per location, multiple markets, multi-currency. It is B2B and B2C, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which brands and battery platforms do I carry? The battery a trade already owns is the strongest signal in this store.\n- Do I get ETIM data (or a BMEcat feed) from my suppliers, and which ETIM release? It gives typed specs and filters for free.\n- Local item numbers: GTIN, and in the Nordics NOBB or NRF? (Never invent these.)\n- Do I rent out equipment? Which depots, which units, rental periods and booking rules, deposits, damage waiver and site delivery?\n- Trade pricing: a flat trade discount, contracts per company, volume tiers? Invoice with credit limits for trade accounts? Quote-only products?\n- Which trades and materials define my customers (electrician, carpenter; concrete, wood)?\n- Which margin, sales and rating data can drive ranking?\n- Can I use my manufacturers' images, or do I have my own?\n- Should an AI assistant shop the store — inside the site, and/or as an MCP server so Claude can? Which AI provider key?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nEvery text, rich text, selection and relation the storefront shows must be discoverable; translate text if I sell in more than one language.\n\n- Pieces:\n  - `seo` (title, description, image).\n  - `spec-power`: power source, voltage, batteries required, motor, rated input, output power, connectivity, cable length.\n  - `spec-physical`: weight (with and without battery), dimensions, vibration, sound pressure and power, IP rating, LED light, belt hook.\n  - One spec piece per product family (drilling, concrete, sawing, abrasive, woodworking, fastening, installation, measuring, extraction, battery and charging, site, storage, outdoor, consumables). Typed components only (numeric with units, boolean, selection); no properties tables. The ETIM feature code goes in each component's description. Batteries and chargers relate to the platforms they fit; consumables to the tools they fit.\n  - Page-builder blocks: hero, product grid (curated, topic or folder; ranked by house, personal or newest), product spotlight, category tiles, brand strip, platform picker, rental teaser, campaign banner, story, USP bar, trade call to action, video.\n- **tool** (product):\n  - tagline, summary, story (paragraphs), highlights (repeatable chunk), videos.\n  - `brand` (exactly one), `platform` (max one), `technologies` relations.\n  - `power` and `physical` pieces; `specs` as a component choice of one family piece.\n  - `in-the-box` (repeatable chunk: variant SKU, quantity, item, note) on the product, keyed by variant SKU.\n  - `warranty` chunk (years, years if registered, registration required).\n  - `launch-date`; ranking numerics `margin`, `sold-30d`, `rating`, `review-count`, `campaign-boost`.\n  - `pairs-with` (max 8), `fits` (tools it fits, max 75), `rental` relation → the rental version; seo.\n  - Variant components: GTIN and local item numbers.\n- **rental** (product): the equipment it is, tagline, summary, what's included, requirements, operator certificate needed, delivery (pickup or site delivery), deposit class, depots, damage-waiver relation. Variants are rental periods with a `duration-hours` component: day (24 h), weekend (68 h), week (168 h), four weeks (672 h).\n- **service** (product): damage waiver, site delivery.\n- Documents: **brand**, **platform** (brand, voltage, compatible platforms, its batteries), **technology**, **depot** (market, address, location, opening hours, contact), **landing-page** (audience, markets, validity, a `blocks` component multiple choice), **campaign** (audience, markets, priority, validity, products), **guide**.\n- **category** (folder). Catalogue: one root folder per category group, plus rental, brands, platforms, technology, depots, pages, campaigns, guides and services.\n- Topic maps: brand, platform (plus corded and no-battery), category (group → type), trade, material, level, price tier, feature, offer (buy, rent, quote), image type, and `etim` (group → class, with the ETIM class code in the topic's meta).\n- Vector vocabularies: `ecosystem` (brand [1, 0.5], platform [1, 0.6]), `work` (trade [1, 0.7, 0.45], material [1, 0.6, 0.35]), `gear` (category [1, 0.5], level [1], price tier [0.7]). Leave corded and no-battery out.\n- Pricing: net prices, one price variant per currency, set so the price including VAT lands on a price point. VAT types per country.\n- Customer groups per market and segment. A market `trade` (all trade companies) with a percentage price list (−12 % in the demo). Contract prices: one percentage price list per company (−18 % in the demo), `targetAudience: { type: SOME, customerIdentifiers: [company] }`; its people inherit it.\n- Stock: `default` (central warehouse) plus one stock location per depot.\n- Order pipelines: Orders, Rentals, Quotes (create pipelines and depot stock locations in the PIM API; the Shop API returns stage ids only, so keep an id-to-name map from setup).\n- Give the rental folder `externalReference: folder:/rental`, so code recognises it without matching paths in four languages.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo holds 416 products (611 versions) with ~2,800 typed spec values; 106 spec components carry their ETIM 10.0 feature code. The range was researched from manufacturer sites only, with every image URL checked. ETIM 10.0 classes and features come from the public ETIM viewer (viewer.etim-international.com) in four languages.\n- Images were copied from the manufacturers' CDNs, then given alt text and topics per language. Letterboxed photos were cropped.\n- Build order: tenant → model → taxonomy → images → catalogue → rental → translations → pages → vectors → customers.\n- From me: my range with specs, prices and images, platforms, item numbers, depots and rental fleet, trade discounts, and ETIM classes (from an ETIM export or my suppliers' BMEcat).\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, plain `fetch` GraphQL clients. The storefront reads Discovery, the Catalogue API (contract prices, on the server) and the Shop API; the Core API is for setup and back office.\n- **Discovery API:** browse with filters, topic facets, range facets (with `boundaries`) on spec numerics, and pagination; search and autocomplete. Trade prices through `<variant>PriceFor(marketIdentifiers: [\"trade\"])`. Contract prices are confidential, and Discovery is public: read them on the server from the Catalogue API (`priceFor(count, customerIdentifiers: [company])`), cached per company. Hydrating the cart with the company applies the same list.\n- **Shop by battery platform:** a platform picker and platform pages list everything on that platform; batteries and chargers relate to the platforms they fit.\n- **Ranking per shopper:** taste rebuilt per request from the shopper's order history (line meta carries the product's topic paths; 120-day half-life; orders count 1, rentals 0.7, quotes 0.6). Rank with `tasteCosine` per vocabulary plus house boosts (sales, margin, rating, campaign boost, in stock). Alternatives with `nearestTo: { vocabulary: gear }`. Cart add-ons from the basket's own taste.\n- **Rental (booked natively in Crystallize):** in setup, booking policies (durations in seconds) and one `setBookable` pool per rental product with named units whose meta holds the depot; `publishItem` after every `setBookable` or `reapplyBookablePolicy`. In the Shop API: `availability` (Discovery never serves it), then `bookSkuItem` one at a time per cart, quantity 1 per unit, checking `__typename`. Remove a rental from the basket by re-hydrating without it; `cancelReservation` and `rebookReservation` only work outside the cancellation window. Damage waiver and site delivery are ordinary lines added through `hydrate`, `type: service`, in the booking line's `group`.\n- **Checkout:** the customer goes on the cart before any booking (`hydrate` with the customer, the customer's markets and meta such as the PO number) → `place` → optional `confirmCartBooking` → payment (card: create the order from the provider's verified webhook; invoice: a credit check) → `/order` `createFromCart` (standard or quote, in the Orders, Rentals or Quotes pipeline) → wait for the cart's order id → `confirmCartBooking` again. If payment fails after the first confirm, cancel the reservations through `/booking/admin` (a server token with `booking` and `booking:admin`). Load a company's credit limit and terms once at sign-in on the server, never per page view.\n- **Quotes:** accepting a quote creates a new order that points to it, and moves the quote to accepted.\n- **On-site assistant:** a streamed agent loop on the server (`/api/assistant`, Claude Haiku) in a header drawer, with tools over Discovery and the Shop API: search, search by taste, complete my kit (batteries and chargers on the platforms the shopper owns, blades and bits for the machines they bought, the next machine on their platform), similar, product details with the typed specs, add to basket, rentals, rent or buy (purchase price against the cheapest way to rent for the job's days, with the break-even) and battery-platform compatibility. It reads the shopper's taste and order history from the session, never sees a token, answers only from tool results, and renders results as the store's own cards. \"Ask the assistant about this\" on the product page opens it with the question typed in.\n- **MCP server:** the same catalogue at `/api/mcp` (Streamable HTTP, stateless), 18 tools: search, product, similar, show products, battery platforms, categories, brands, basket, add-ons, checkout, rental search, availability and booking, and the signed-in `whoami`, `my_orders` and `recommendations`. Numbers with units, option names and platforms in the question become hard Discovery filters in English, Norwegian, Swedish or Dutch (\"M18 impact driver over 200 Nm\", \"støvsuger M-klasse\"), parsed from a spec model generated from the seed model, not a word list; vague questions get \"Did you mean\" suggestions with the exact arguments to run. Demo OAuth 2.1 (DCR, client ID metadata documents, PKCE) where signing in picks a demo customer, so rankings use their taste vector and their company's price list. An MCP Apps resource draws results as cards with Add to basket, and `/mcp/{view,buy,cart}` hand a basket over to the store.\n- **Semantic product data:** JSON-LD with the typed specs as `PropertyValue` with UN/CEFACT `unitCode` and min/max bounds, the ETIM class as a `CategoryCode` and ETIM value codes on options, the battery platform as a `DefinedTerm` shared with its own page, accessory and consumable relations (`isAccessoryOrSparePartFor`, `isConsumableFor`), `ProductGroup` for versions, and rentals as leased offers with depots. The same facts in a Markdown version of every page and in `llms.txt` / `llms-full.txt`.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, `NEXT_PUBLIC_SITE_URL`; for the assistant `ANTHROPIC_API_KEY`; for the MCP sign-in `MCP_AUTH_SECRET`.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Vector ranking must be enabled for the tenant. Bookings need no flag, only the `bookingPolicies` permission on the token's role (`FORBIDDEN` otherwise). Discovery answers \"no ignited Tenant\" until the first `igniteDiscoApi(stacks: opensearch)`.\n- A component that isn't discoverable is missing from Discovery, and rich text isn't discoverable by default. After changing that, republish; new fields can need a second ignite.\n- `setItemTaste` empties variant components on the draft: taste → rewrite variant components → publish → ignite.\n- Content chunks on variants don't publish: keep `in-the-box` on the product, keyed by variant SKU.\n- Updating with `components` replaces all of them: use `updateComponent` for translations. `updateProductVariant` without components empties them.\n- Always send `pathIdentifier` when updating topics; translate parents first.\n- Item relations cap at 75. Volume tiers start at threshold 1. Chunks with translated text must be multilingual. Create shapes and pieces empty, then fill them.\n- Price lists targeted at customer groups fail: target markets or customers. Prefer percentage lists; an absolute list replaces the price and loses the volume tiers.\n- Booking policy durations are in seconds; set the cart's customer before `bookSkuItem`.\n- Orders must be created and moved through the Shop API. It can't delete orders.\n- Typed specs make the agents precise: give the search tool a structured `specs` argument and list every field, unit and option in its description (or as a resource). Generate that list from the model, so a new spec is understood without code.\n- Discovery: `language` is an enum literal; relations come back in the default language; autocomplete is a filter, so pair it with `term` and sort by score.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"car-parts-universe","name":"Car Parts Universe","url":"https://car-parts-universe.superfast.shop","llms":"https://car-parts-universe.superfast.shop/llms.txt","industry":"Car parts · B2B for workshops","thesis":"Start from the car on the lift: a registration number narrows the whole store to the parts that fit, with OE numbers, stock at the workshop’s branch and delivery in the next round.","color":"#ff6b1a","onColor":"#0e1114","stats":[{"value":"429","label":"parts"},{"value":"12","label":"brands"},{"value":"30","label":"vehicles"},{"value":"6","label":"branches"}],"features":["Plate lookup","Vehicle fitment","OE number search","Stock per branch"],"audience":["b2b"],"sector":"automotive","capabilities":["fitment","forgiving-search","stock-per-location","invoice-credit","quotes","personalisation","contract-pricing"],"payments":["invoice"],"currencies":["NOK"],"languages":["EN","NO","SV","NL"],"topic":"Auto Parts Ecommerce with Vehicle Fitment","description":"Auto parts B2B demo for workshops: registration-plate lookup, vehicle fitment, OE numbers and cross-references, stock per branch, trade prices, quotes and invoice.","page":"https://superfast.shop/demos/car-parts-universe","screenshot":"https://superfast.shop/shots/car-parts-universe.jpg","preview":"https://superfast.shop/previews/car-parts-universe.mp4","icon":"https://superfast.shop/icons/car-parts-universe.png","promptUrl":"https://superfast.shop/demos/car-parts-universe/prompt.md","prompt":"# Build my own car parts store on Crystallize, like Car Parts Universe\n\nI want a storefront for my business that works like **Car Parts Universe** (https://car-parts-universe.superfast.shop), a demo store built on Crystallize: Start from the car on the lift: a registration number narrows the whole store to the parts that fit, with OE numbers, stock at the workshop’s branch and delivery in the next round.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: vehicle fitment, typo-tolerant search, stock per location, invoice and credit limits, quotes, vector personalisation, customer price lists. It is B2B, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which countries do I sell in, and is there a registration-plate lookup for each? (Norway: Statens vegvesen's free Enkeltoppslag API; Netherlands: RDW open data; UK: DVLA Vehicle Enquiry Service. Sweden has no free equivalent. Where there is none, a make → model → engine picker does the job.)\n- Where does my fitment data come from: a TecDoc/TecAlliance licence (the industry standard, with KTypes), my brands' own catalogues, or my ERP? At what level: vehicle type, engine code?\n- Which part categories and brands do I carry, and roughly how many SKUs? Which vehicles matter most to my customers?\n- Do I sell to workshops only (prices excluding VAT), or to consumers too?\n- How are trade prices set: a percentage per customer group, contract prices for chains, volume breaks on cartons?\n- My branches and stock locations, and my delivery promises (order cut-offs and delivery rounds per branch).\n- Checkout: invoice with credit limits, quotes, returns, deposits (batteries, cores)?\n- My product feed: does it have EAN, OE numbers, cross-references to other brands, and typed specs?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nEverything the storefront shows must be marked discoverable. Translate the text components if I sell in more than one language.\n\n- **part** (product), one variant per pack size:\n  - `tagline` single line, `summary` rich text, `story` paragraphs, `highlights` (repeatable chunk: title, text, image).\n  - `brand` item relation → brand (exactly one), `article-number` single line.\n  - `fitment` (repeatable chunk: `vehicle` = vehicle id, `position` selection such as front/rear/left/right, `note`).\n  - `oe-numbers` (repeatable chunk: maker, number), `cross-references` (repeatable chunk: brand, number).\n  - `specs`: a component choice holding exactly one spec piece per product family (filter, brake disc, brake pads, wiper, battery, bulb, lubricant…). Every spec is typed: numeric with a unit, boolean or selection. No properties tables, so every spec can be a filter.\n  - `physical` piece (weight, package dimensions, origin), `safety` piece (GHS hazard data) for chemicals.\n  - `kit-contents` (repeatable chunk: quantity, name, article number, relation), `documents` (repeatable chunk: type SDS/TDS/datasheet/fitting instructions, language, title, url).\n  - `deposit` relation → service, `pairs-with` relation → part (max 8), `replaces` relation → part.\n  - Ranking signals as numerics: `margin`, `sold-30d`, `campaign-boost`. Plus an `seo` piece.\n  - Variant components: `ean`, `volume`, `net-weight`, `pieces`. SKU: brand prefix + normalised article number.\n- **service** (product): battery deposits and similar charges.\n- **vehicle** (document): `make`, `model`, `generation`, `engine`, `engine-codes` (comma list, used by plate matching), `fuel` selection, `power` (kW), `displacement` (ccm), `year-from`, `year-to`, `body`, `topic-path`, `service-data` chunk (oil capacity, oil approvals, viscosity, service and timing intervals), `image`, `notes`.\n- **brand** and **technology** (documents); **branch** (document: market, stock location identifier, address, opening hours, `delivery-rounds` repeatable chunk of cut-off and delivery time, contact).\n- **category** (folder); **landing-page** (document with a `blocks` component multiple choice: hero with a plate-search switch, product grid, vehicle picker…); **campaign** and **guide** documents.\n- Topic maps:\n  - `/vehicle/<make>/<model>/<generation>/<vehicle-id>`: the leaf is one vehicle, with engine code, kW, ccm, fuel and years in its topic meta.\n  - `/category/<group>/<type>`, `/brand/<brand>`, `/axle/front|rear`, `/price-tier/value|mid-range|premium`.\n- Catalogue: one folder per category group (filters, brakes, ignition, electrics, suspension, oils and fluids…), plus services, brands, vehicles, branches, pages, campaigns and guides.\n- Pricing: net prices, one price variant per currency, with volume tiers on carton items. VAT types per country.\n- Customers: companies are customers, and their people have the company as parent. A `trade` market (all workshops) carries a percentage price list, if trade prices aren't confidential. Contract terms are percentage price lists aimed at the chain's organization (`targetAudience: { type: SOME, customerIdentifiers }`); the people under it inherit them. The most specific target wins: customer, then group, then market.\n- Stock: a central `default` location plus one per branch.\n- Order pipelines: Orders, Quotes, Returns.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo has about 430 parts from 12 brands, researched from manufacturer sites only, and 30 vehicles checked against the brands' fitment data.\n- Build order: tenant settings → shapes and pieces → topics → images → catalogue → translations → pages → Discovery index → customers, price lists and sample orders.\n- From me: a product feed (typed specs, EAN, OE numbers, cross-references), a vehicle list (engine codes, kW, ccm, years), part-to-vehicle fitment, images, net prices per currency, stock per location, and customers with their discount levels.\n- Fitment is the hard part. Without a TecDoc licence, start with my top vehicles and the brands' own application lists.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, with plain `fetch` GraphQL clients (no SDK needed).\n- **Discovery API** for every public read: browse parts with filters, facets, sorting and search; list and trade prices; stock per location; topic facets. Discovery is public, so anyone can read any price it exposes: per-company contract prices come from the **Catalogue API** on the server (`priceVariant(identifier) { priceFor(count, customerIdentifiers: [...]) }`), cached per organization.\n- Rank listings with Discovery `rankBy` (a `fieldBoost` on `sold_30d` and `campaign_boost`, an `inStockBoost` on the workshop's branch stock, and the required `tieBreaker`); introspect `TenantRankByField` first. A discoverable `margin` is publicly readable: secure the Discovery endpoint, or leave margin out of Discovery.\n- **Shop API** for carts and orders:\n  - Get a JWT from `/auth/token` with the access token (scopes `cart`, `cart:admin`, `order`), on the server only.\n  - `hydrate` the cart with the price context (currency, price variant, taxes not included, tax rate, the customer's markets) and the company as customer, so price lists apply.\n  - Checkout: `hydrate` (customer, markets, meta) → `place` → `/order` `createFromCart(id, input: { type: standard or quote, paymentStatus, payments, pipelines: [{ identifier, stage }] })`. The cart becomes `ordered` and the order id is the cart id; don't call `fulfill`. Order history from `/order` `orders(customerIdentifier)`, with the identifier taken from the session, never from the browser.\n- **Core API** only in the setup scripts (shapes, topics, items, customers, price lists).\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, the plate API key, `NEXT_PUBLIC_SITE_URL`.\n- **Fitment:** each part carries the vehicle topics it fits. Parts for a car are a Discovery filter on that vehicle's topic. The chosen vehicle lives in a cookie and narrows every listing; the `fitment` chunk shows position and notes.\n- **Plate lookup:** a server action calls the national registry, maps make, model, engine code, power, fuel, displacement and first registration, then matches against the vehicle documents. Make and fuel must agree. Score the rest: engine code 4, model 3, kW (±4) 2, year (±1) 1, ccm (±30) 1. Accept a match on engine code plus one more signal, or on model + kW + year. With no match, fall back to the vehicle picker.\n- Workshops pick their branch; listings show stock there and the next delivery round.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- A numeric component with a unit list is required in practice: items without a value fail validation. For optional values, drop the unit list and put the unit in the component name.\n- Discovery uses the shape identifier as the hit type (`part`), but its inputs are `PartFilter`, `PartFacet` and `PartSort`. Components that aren't discoverable are missing from Discovery.\n- Chunks holding translated text must be multilingual themselves.\n- Updating an item with `components` replaces the whole set; use `updateComponent` for partial or translated writes.\n- Pass `pathIdentifier` on every topic update, or the path is regenerated from the translated name. Translate parents before children.\n- A pipeline with `placeNewOrders: true` pulls in every new order.\n- Create and change orders through the Shop API (`createFromCart`, `setMeta`, `addToStage`), never `updateOrder` in Core, which adds a duplicate to the Shop list. Orders reach Core about ten seconds later; when reading a list, group by `coreId` and keep the newest.\n- Re-hydrate the cart when the customer, language or price lists change.\n- After changing the model, re-index Discovery and wait before querying.\n- Wait for every `copyRemoteAsset` task, then check that each image actually resolves; upload the failures yourself (`generatePresignedUploadRequest`, then `registerImage`).\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"fashion-universe","name":"Fashion Universe","url":"https://fashion-universe.superfast.shop","llms":"https://fashion-universe.superfast.shop/llms.txt","industry":"Fashion retail · B2C","thesis":"Ten brands, each a style tribe: sign in as a shopper and the store re-ranks to their taste — with shoppable looks, colourways and sizes, and the supply chain behind every garment.","color":"#141413","onColor":"#f6f4ef","stats":[{"value":"496","label":"products"},{"value":"10","label":"brands"},{"value":"64","label":"looks"},{"value":"3","label":"markets"}],"features":["Personalised by style","Shoppable looks","Colourways and sizes","Supply chain per garment"],"audience":["b2c"],"sector":"fashion","capabilities":["personalisation","shoppable-content","campaigns","video","variants","sustainability","multi-market","multi-currency","sale-pricing"],"payments":[],"currencies":["EUR","NOK","SEK"],"languages":["EN","NO","SV","NL"],"topic":"Fashion Ecommerce with Shoppable Lookbooks","description":"Fashion ecommerce demo: 10 brands, colour and size variants, shoppable looks with hotspots, click and collect, sale pricing and supply-chain data per garment.","page":"https://superfast.shop/demos/fashion-universe","screenshot":"https://superfast.shop/shots/fashion-universe.jpg","preview":"https://superfast.shop/previews/fashion-universe.mp4","icon":"https://superfast.shop/icons/fashion-universe.svg","promptUrl":"https://superfast.shop/demos/fashion-universe/prompt.md","prompt":"# Build my own fashion retail store on Crystallize, like Fashion Universe\n\nI want a storefront for my business that works like **Fashion Universe** (https://fashion-universe.superfast.shop), a demo store built on Crystallize: Ten brands, each a style tribe: sign in as a shopper and the store re-ranks to their taste — with shoppable looks, colourways and sizes, and the supply chain behind every garment.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: vector personalisation, shoppable content, campaigns, video, large variant sets, supply chain and co₂e, multiple markets, multi-currency, sale price lists. It is B2C, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- One brand or several? If several, what style segments (tribes) do my shoppers belong to?\n- My variant axes (colour × size, wash, length) and size systems, and whether I have size guides.\n- My product families and the specs shoppers filter on (denim fit and rise, outerwear membrane and fill power, heel height…).\n- How I do markdowns: a was/now price per variant, or separate price lists?\n- Do I have lookbook or campaign photography? Who places the hotspots: me, or you from my notes on what's in each photo?\n- My warehouses and physical stores, and whether I offer click & collect.\n- What I publish about materials and supply chain: composition, certifications, factories, CO₂e.\n- My payment provider, and my order and return flow.\n- Do I want the store to re-rank itself per shopper? Can I share margin, sales, ratings and return rates to rank on?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nEverything the storefront shows must be discoverable; translate the text components if I sell in more than one language.\n\n- **product** (product): one style, with variants colour × size.\n  - `tagline`, `summary` (rich text), `story` (paragraphs), `highlights` (repeatable chunk: title, text, image).\n  - `brand` relation → brand (exactly one), `line` relation → product line, `style-number`, `season` selection, `launch-date`.\n  - Pieces: `material`, `size-fit`, `seo`.\n  - `specs`: a component choice of one typed spec piece per family: denim, apparel, outerwear, activewear, swim, footwear, accessory.\n  - `videos`, `complete-the-look` relation → product (max 8), `services` relation → service (max 4).\n  - Ranking signals as numerics: `margin`, `sold-30d`, `rating`, `review-count`, `return-rate`, `campaign-boost`.\n  - Variant attributes `colour` and `size`. Variant components are plain fields only: `gtin`, `colour-family` selection, `colour-code`, `wash` selection, `size-order` (numeric sort key).\n  - The first size of each colour carries all of that colourway's images and its video; the other sizes carry one image.\n- **material** piece: composition (repeatable chunk: part, fibre, percent, certification), fabric, fabric weight, technologies, care (selection), certifications, recycled and organic share, origin, supply chain (repeatable chunk: role, relation → factory), CO₂e, water, traceability URL, sustainability text.\n- **size-fit** piece: size system, fit advice, model height and size, notes, relation → size guide.\n- **look** (document): headline, text, `images` (at least one, with hotspots), video, `products` relation → product (1 to 8), campaign, credits.\n- **campaign** (document): brand, season, start and end, headline, hero, gallery, products, looks, call to action.\n- **brand**, **product-line**, **technology**, **size-guide**, **factory** (country, city, certifications, workers) documents.\n- **service** (product): hemming, repair, gift wrap.\n- **landing-page** (document): a `blocks` component multiple choice of page-builder pieces: hero, product grid (curated, category, topic, personal or new), campaign banner, look grid, brand strip, category tiles, tribe picker, editorial split, USP bar, video.\n- Folders: `category` and `section`. Catalogue roots: shop, brands, lines, campaigns, looks, factories, technology, pages, services. `/shop/<group>/<type>` mirrors the category topic map and is gender-neutral; women and men are storefront routes filtered by the gender topic (women = women OR unisex).\n- Topic maps: brand, tribe, category (group → type), gender, colour, material, fit, occasion, weather, style, price tier, sustainability, season, offer (`/offer/sale`), image type.\n- Vector vocabularies for taste ranking: `style` (tribe, style, brand), `wardrobe` (category, occasion, weather, gender), `palette` (colour, material, fit), `value` (price tier, sustainability).\n- Pricing (the pricing skill's convention): prices excluding VAT; per currency a `retail-<cur>` price variant, always set, and a `sales-<cur>` price variant set only on marked-down variants. Show a strikethrough when sales is lower than retail. Sale items also get the `/offer/sale` topic (a filter on the sales price existing works too).\n- Stock locations: `default` (web) plus one per store for click & collect.\n- Order pipelines: Orders (new, picking, shipped, delivered, cancelled), Returns, and one click & collect board per store.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo's data comes from the brands' own sites, rewritten in its own words. Stock and ranking numbers are generated.\n- Build order: tenant → model → topics → media → catalogue → marketing (campaigns, looks) → translations → pages → vectors → customers.\n- From me: products with colourways, sizes, SKUs and GTINs, and prices per currency; packshots and on-model images per colourway, and product videos; look and campaign photos with which product and colourway is where; size guides, composition, care, origin and factories; brand logos and hero media; and, for ranking, margin, sales, ratings and return rates.\n- Videos: `generatePresignedUploadRequest(type: MEDIA)` → upload → write `{ key, title }` on the videos component (`copyRemoteAsset` is images only). Crystallize transcodes them to HLS and DASH in about a minute.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, with plain `fetch` GraphQL clients. Routes are prefixed by language; unknown paths resolve the translated Crystallize path through Discovery.\n- **Discovery API** for every read: browse with topic facets, `rankBy`, and `context: { userTaste }`; prices per price variant; stock per location.\n- **Shop API** for bag and checkout:\n  - The server exchanges the tokens for a JWT at `/auth/token`; the browser only holds the cart id in an httpOnly cookie.\n  - `hydrate` all lines with `context.price { currency, selectedVariantIdentifier: sales-<cur>, fallbackVariantIdentifiers: [retail-<cur>], compareAtVariantIdentifier: retail-<cur>, pricesHaveTaxesIncludedInCrystallize: false, taxRate, decimals: 4 }`.\n  - Line meta carries the product's topic paths, brand, size and colour. Hemming is a service line; the inseam goes in the garment's meta.\n  - Checkout meta (market, language, delivery, store, gift wrap) goes on the cart with `hydrate(input: { meta })` and the customer with `setCustomer`; then `place` → `/order` `createFromCart(id, input: { pipelines: [{ identifier, stage }], stockLocationIdentifier })`, into Orders or the store's click & collect board. Later changes use `/order` `setMeta(id, meta, merge: true)`.\n- **Shoppable looks with native hotspots:**\n  - Write the hotspots in setup with Core `updateImage(key, language, input: { showcase: [...] })` (repeat per language if needed): `showcase: [{ hotspot: { x, y }, itemIds: [productId], skus: [first size of the colourway], meta: [{ key: \"colourway\", value }] }]`. x and y are fractions (0–1) of the image, at the centre of the garment.\n  - Read them back from Discovery as `images { showcases { hotspot meta items { … } variants { sku attributes price images } } }`.\n  - Draw dots at those positions (adjusted for cropping); each opens a card with price, was-price and a link to the product in that colourway.\n- **Taste per shopper:** on the server, read the shopper's orders from the Shop API (`orders(customerIdentifier)`, identifier from the session) and build a taste from the topics on their order lines (older orders count less; returns count less). Pass it as `context.userTaste` (weights keyed `dimension:value`, with `magnitude` = the square root of the sum of squared weights) and rank with one `tasteCosine` term per vocabulary (`from: userTaste`) plus `fieldBoost` on sales, margin, rating and campaign boost, and a negative-weight `fieldBoost` on return rate. `tieBreaker` is required; don't pass `sorting` with ranking; page with `skip` and `limit` inside the rerank window (500 by default); leave `context` out entirely when there is no taste yet. Probe `__type(name: \"RankByInput\")` first: ranking is enabled per tenant.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, `NEXT_PUBLIC_SITE_URL`.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Discovery field names are generated (`{componentId}_{subfield}_{type}`, e.g. `sold_30d_number`), and hit fields can be cased differently (`campaignBoost`). Introspect `TenantRankByField`, the filter inputs and the hit type instead of guessing.\n- Setting item taste empties variant components on the draft: `setItemTaste` → rewrite the variants (one update replaces them all) → `publishItem` per item and language → `igniteDiscoApi(stacks: opensearch)`, poll the task until complete, then wait. Without `stacks: opensearch` there are no vectors, and no error either.\n- Updating with `components` replaces all components in that language; translate with `updateComponent`, one at a time.\n- `copyRemoteAsset` handles images only, and can return a key even when the copy failed. Wait for every copy task, then check the keys resolve. Don't call `registerImage` after a copy.\n- Image renditions are generated in a queue: fall back to the original URL, and republish once they exist. Ask for specific renditions (webp, a width range) so a page of cards stays light.\n- Number facets need `boundaries`. Discovery rejects `context: null`.\n- Content chunks on variants aren't published; keep variant components plain.\n- Create and change orders through the Shop API; editing an order in Core adds a duplicate to the Shop list.\n- Don't use a per-SKU price list for the sale: pricing every product for everyone that way is too slow. Use the `sales-<cur>` price variants and the sale topic.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"boutique-universe","name":"Boutique Universe","url":"https://boutique-universe.superfast.shop","industry":"Travel and stays · B2C","thesis":"Small hotels, villas and unusual places to stay, booked natively in Crystallize: rooms and cabins from a pool, experiences in time slots, and a concierge that searches, checks availability and holds the dates.","color":"#2f5d6b","onColor":"#ffffff","stats":[{"value":"40","label":"places"},{"value":"82","label":"stays"},{"value":"123","label":"experiences"},{"value":"14","label":"destinations"}],"features":["Native booking","Experiences in time slots","Taste ranking with reasons","AI concierge"],"audience":["b2c"],"sector":"hospitality","capabilities":["booking","time-slots","personalisation","explainable-ranking","member-pricing","multi-market","ai-agent"],"payments":[],"currencies":[],"languages":["EN","NO","SV","NL"],"topic":"Hotel Booking & Experience Commerce","description":"Hotel and boutique stay booking demo: rooms, rate plans and experiences booked in time slots, member pricing tiers and stays ranked to each guest’s taste.","page":"https://superfast.shop/demos/boutique-universe","screenshot":"https://superfast.shop/shots/boutique-universe.jpg","preview":"https://superfast.shop/previews/boutique-universe.mp4","icon":"https://superfast.shop/icons/boutique-universe.png","llms":null,"promptUrl":"https://superfast.shop/demos/boutique-universe/prompt.md","prompt":"# Build my own travel and stays store on Crystallize, like Boutique Universe\n\nI want a storefront for my business that works like **Boutique Universe** (https://boutique-universe.superfast.shop), a demo store built on Crystallize: Small hotels, villas and unusual places to stay, booked natively in Crystallize: rooms and cabins from a pool, experiences in time slots, and a concierge that searches, checks availability and holds the dates.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: native booking, time-slot bookings, vector personalisation, explainable ranking, member pricing, multiple markets, ai concierge. It is B2C, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which places do I rent out, and is each booked by room or as a whole place? Are rooms named units, or identical rooms sold from a capacity?\n- My rate plans per stay (flexible, non-refundable, half-board, long-stay, members) and their prices per night, in which currencies, including or excluding VAT.\n- My booking rules: cancellation windows, how far ahead guests can book, minimum nights, fixed arrival days, check-in and check-out times.\n- Experiences I sell: times, duration, seats per slot, per person or private group, season, minimum age, who hosts them.\n- My markets, languages and VAT rates (accommodation is often taxed differently from services).\n- Loyalty tiers and their discounts, and anything members-only.\n- The themes or moods my places group into, and my regions. My own photography, or stock photos with credits?\n- An AI concierge on the site: yes or no? If yes, which AI provider and key. And my payment provider.\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nText components are translated; everything is discoverable. Every shape except host gets an `seo` piece (title, description, image).\n\n- **destination** (folder): tagline, intro, hero images, location, map zoom, best season, getting there, worth knowing, stories.\n- **property** (folder): tagline, intro, `cover` (one image) and `gallery` (up to 24), story (paragraphs), location, place, `booking-model` (rooms or whole), `service-level`, `price-from`, max guests, bedrooms, check-in and check-out times, worth knowing, house rules, getting there, `host` relation, `nearby` relation → property (max 4), rating, review count, `booked-30d`, `members-only` boolean.\n- **stay** (product, the bookable resource): tagline, description, images, max guests, bedrooms, beds, size (m²), view, min nights, check-in days (empty = any day), features, rating, review count, `booked-30d`.\n  - Variants are rate plans, attribute `plan` (flexible, non-refundable, half-board, long-stay, members). Variant components: includes, cancellation, min nights, members-only.\n- **experience** (product, sold in time slots): tagline, description, images, duration (minutes), capacity (seats per slot), meeting point, season, slot times, weather-dependent, minimum age, host. One variant, attribute `unit` = person or group.\n- **host** (document): portrait, role, bio, quote, languages, response time, three picks.\n- **universe** (document, a curated world): tagline, manifesto, hero, accent colour, properties (up to 24), stories. **story** (document) for the journal.\n- Catalogue: `/stays/<destination>/<property>/<stay or experience>`, plus `/hosts`, `/universes`, `/journal`.\n- Topic maps, where each root is a taste dimension: universe, destination, stay type, setting, pace (quiet/social), finish (rustic/refined), reach (remote/connected), climate (cold/warm), travel party, amenity, price band. Properties carry all of them; stays their own price band; experiences destination, universe and setting.\n- Pricing: prices excluding VAT, per night per unit, one price variant per currency. VAT types for accommodation and for experiences.\n- Member tiers as markets (e.g. explorer, insider, inner circle) with percentage price lists (−5, −10, −15 %). Members-only rate plans and places are hidden from lower tiers.\n- No stock locations: availability comes from booking pools.\n- Order pipelines: Stays (confirmed, pre-arrival, in house, completed, cancelled) and Experiences (booked, confirmed with host, done, cancelled).\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo's places are fictional, one JSON file per property plus a translation per language, with Unsplash photos credited in the caption.\n- From me: my places, their rooms or units, rate plans and prices, experiences and slots, hosts, photos, regions, booking policies, VAT and loyalty tiers.\n- Build order: tenant → model → topics → catalogue → booking setup → images → translations → vectors (last) → customers.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, a map (MapLibre), plain `fetch` GraphQL clients.\n- **Discovery API** for every read. **Shop API** for holds, carts and orders: the server exchanges the tokens for a JWT at `/auth/token` (scopes `cart`, `cart:admin`, `order`, plus a separate server-only token with `booking` and `booking:admin` for the booking admin); the browser only holds the cart id in an httpOnly cookie.\n- **Booking setup (Core API):**\n  - Booking policies with a booking horizon, cancellation window and hold times. The demo uses: hotel rooms (450 days ahead, cancel 48 h), whole places (cancel 14 days), experiences (cancel 24 h), pending holds 20 minutes, placed holds 24 hours. Every duration is in seconds (450 days = 38880000, 48 h = 172800, 20 min = 1200): read `humanized` back after `createBookingPolicy`. After editing a policy, run `reapplyBookablePolicy` per product, then publish.\n  - `setBookable` on each stay product: named rooms → a unit pool, identical rooms → a capacity pool, a whole place → a pool of one; an experience → a capacity of seats. All rate plans share the product's pool. Publish afterwards.\n- **Availability:** Shop `availability(productId, sku, range, granularitySec: 86400, language)` gives `free`, `freeUnitIds` and `reason` per night (`language` is required; the wrong one reads as not bookable). `nearestAvailability(productId, sku, around, durationSec, n, language)` suggests other dates, and `checkBooking` does a dry run. The storefront enforces minimum nights and arrival days.\n- **Hold:** hydrate an empty cart with the context (language, currency, price variant, markets, customer), then `bookSkuItem` with quantity (rooms or guests), tax rate and the booking window (in the property's time zone). The input is `{ sku, quantity, booking: { start, end, unitId? }, meta }`. Check `__typename`: conflicts come back as results (`ReservationConflict`, `NotBookable`, `InvalidRange`, `InvalidUnitId`), not errors; on a conflict, try the next free unit. Write the unit id into the line meta yourself. The Shop API prices a booked line as one night: add the other nights as an external companion line pointing at it.\n- **Checkout:** check the holds are still live → `setCustomer`, with checkout meta on the cart through `hydrate(input: { meta })` → `place` → `confirmCartBooking` → `/order` `createFromCart` into the right pipeline → wait for the cart's order id → `confirmCartBooking` again to link the reservations to the order. Don't call `fulfill`. A confirmed reservation never expires: if payment fails after the first `confirmCartBooking`, cancel the reservations on `/booking/admin` (`cancel` or `bulkCancel`).\n- **Taste ranking:**\n  - Vocabularies: `world` (universe, setting, stay type), `mood` (pace, finish, reach, climate), `party` (travel party, price band).\n  - Set every property's, stay's and experience's taste from its topics: `setItemTaste` → rewrite the variant components → `publishItem` per item and language → `igniteDiscoApi(stacks: opensearch)`, and poll until it's complete.\n  - Build the guest's taste per request from their persona, a mood dial and past stays, pass it as `context.userTaste`, and rank with `tasteCosine` per vocabulary plus boosts on rating and bookings. Build the \"because…\" lines from the guest's own taste weights and the item's topics; use `explain: true` only behind a debug flag, since it's a tuning tool. With no taste yet, rank on the house terms only.\n- **Concierge (optional):** a streamed chat endpoint with tools that search stays, show a property, check availability, hold a stay or experience and show the trip, all over Discovery and the Shop API. It only holds after the guest says yes.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, `NEXT_PUBLIC_SITE_URL`, and the AI provider's key if there is a concierge.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Set item tastes last: `setItemTaste` writes drafts and empties variant components. Rewrite the rate-plan components, publish every language and re-index; redo it after any catalogue change.\n- Updating with `components` replaces the whole set; use `updateComponent`. `updateProduct(variants)` replaces all variants.\n- Send numeric components with units when creating a product, even optional ones.\n- `bookSkuItem` quantity is pool units, not nights. Repricing a booked line makes `place` fail, so use companion lines. Serialise booking writes per cart (a concurrent `bookSkuItem` silently loses one) and read the cart back. Every `hydrate` must resend each booked line with its `booking { start, end, unitId }`, line id and meta, or that booking is cancelled. Remove a booking by re-hydrating without it. After `place` the cart can't be hydrated.\n- Set the customer on the cart before booking: a reservation records its holder only when it's made. An orphaned hold lets go by itself after the pending hold time. Give rate-plan variants images (the admin calendar shows them).\n- In Discovery, pass `context` as a variable (its weight keys contain colons; `rankBy` too, ideally); `tieBreaker` is required; never send `context: null`; vocabulary enum values only exist after an index run; new components need a re-index and a few minutes.\n- Resend `pathIdentifier` on every topic translation, parents first.\n- A percentage price list adds its modifier: a discount is negative.\n- Orders must be created through the Shop API.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"screen-universe","name":"Screen Universe","url":"https://screen-universe.superfast.shop","industry":"Streaming · B2C subscriptions","thesis":"A streaming service for film history on Crystallize subscriptions: plans with a free trial, pause, upgrade and metered downloads, films to rent or buy one-off, and a home that reorders for each profile in the household.","color":"#ffc23d","onColor":"#15120f","stats":[{"value":"169","label":"films"},{"value":"3","label":"plans"},{"value":"15","label":"collections"},{"value":"4","label":"households"}],"features":["Subscriptions with a trial","Metered downloads","Rent or buy","Taste per profile"],"audience":["b2c"],"sector":"media","capabilities":["subscriptions","trials","metered","digital-goods","personalisation","explainable-ranking","video","multi-market","multi-currency"],"payments":[],"currencies":["EUR","NOK","SEK"],"languages":["EN","NO","SV","NL"],"topic":"Video Streaming Subscriptions & Pay-per-View","description":"Video streaming commerce demo: subscription plans with a free trial, pause and upgrade, metered downloads, rent or buy per title, and a home ranked per profile.","page":"https://superfast.shop/demos/screen-universe","screenshot":"https://superfast.shop/shots/screen-universe.jpg","preview":"https://superfast.shop/previews/screen-universe.mp4","icon":"https://superfast.shop/icons/screen-universe.png","llms":null,"promptUrl":"https://superfast.shop/demos/screen-universe/prompt.md","prompt":"# Build my own streaming store on Crystallize, like Screen Universe\n\nI want a storefront for my business that works like **Screen Universe** (https://screen-universe.superfast.shop), a demo store built on Crystallize: A streaming service for film history on Crystallize subscriptions: plans with a free trial, pause, upgrade and metered downloads, films to rent or buy one-off, and a home that reorders for each profile in the household.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: subscriptions, free trials, metered usage, digital goods, vector personalisation, explainable ranking, video, multiple markets, multi-currency. It is B2C, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- What do I stream or sell: films, courses, music, software? Which titles are included in a membership, and which are rent or buy only?\n- My tiers: names, what each includes (streams, quality, downloads), monthly and yearly prices per market, and the length of the free trial.\n- Any metered usage (downloads, seats, credits): how much is included, and the price above that?\n- Add-ons sold as separate subscriptions (channels, packs)?\n- My markets, currencies, VAT rate and languages.\n- Do I hold the rights and the files for every video? Full titles or clips? Do I need DRM? Crystallize's playlists are public URLs, so paid full titles need a DRM-protected video host.\n- Which dimensions describe taste in my catalogue (genre, mood, era…)? Per-profile or per-household recommendations?\n- My payment provider for recurring billing: Crystallize keeps the contracts and the usage, but charges nothing itself.\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nText components are translated and discoverable unless noted.\n\n- Pieces: `seo` (title, description, image); `licence` (basis such as public domain or CC BY, reason, source, source URL, credit).\n- **folder** (folder): tagline, intro, seo.\n- **title** (product, one film or item):\n  - original title, `logline` (up to 110 characters), synopsis, why watch, trivia.\n  - `poster`, `key-art`, `stills` (up to 12) images; `clip` videos (a 60–90 s excerpt); `trailer` (a YouTube id, new releases only).\n  - `year`, `runtime` (minutes), countries, languages, silent, colour, aspect ratio.\n  - `directors`, `writers`, `cast` relations → person; the licence piece; `watched-30d` (popularity) and `rating`; seo.\n  - Variants by attribute `offer`: `rent`, `buy`, `stream`. Stream (price 0) for titles included in every plan; rent and buy for the rest; new releases rent or buy only. Variant component `access-hours` (48 for a rental, 0 otherwise). Every variant carries the key art as its image.\n- **person** (document): portrait, born, died, roles, bio, seo.\n- **collection** (document): tagline, intro, hero images, accent colour, titles (up to 40), seo.\n- **plan** (product, \"Membership\"): tagline, intro. One variant per tier (basic, standard, premium) with plain variant components: tagline, includes, streams, quality, downloads included, rentals discount. Each variant carries the subscription plan `membership`.\n- **channel** (product, an add-on): tagline, intro, hero, collections. One variant carrying the plan `channel`.\n- Catalogue: `/watch/<title>`, `/people/<person>`, `/collections/<collection>`, `/plans`.\n- Topic maps: genre, mood, era, kind (feature, short, serial, documentary, animation).\n- Vector vocabularies: `story` (genre [1, 0.7, 0.5], kind [0.4]), `feel` (mood [1, 0.7, 0.5]), `period` (era [1]).\n- Pricing: excluding VAT, one price variant per currency, set so prices including VAT land on round price points; one VAT type for digital services. No stock.\n- Subscription plan `membership`: Monthly and Yearly periods, each with an initial phase (a 14-day free trial at 0) and a recurring phase; a metered variable `downloads` with graduated tiers (the first 10 free, then a price per download) on the top tier only. Plan `channel`: monthly.\n- Order pipelines: Subscriptions (trial, active, paused, past due, cancelled) and Rentals and purchases (paid, delivered, refunded).\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo's 169 titles come from Wikidata (CC0), Wikimedia Commons and the Internet Archive (public domain), Blender Studio's open movies (CC BY) and NASA; every title keeps its licence and shows a credit.\n- Clips were cut with ffmpeg (60–90 s, stereo AAC) and uploaded with `generatePresignedUploadRequest(type: MEDIA)` within the `maxSize` it returns, then written as `{ key, title }` into the `clip` component (no register step); Crystallize transcodes them to HLS and DASH up to 1080p.\n- From me: titles and their metadata, the rights, video files (MP4 or MOV), posters and key art, tier definitions and prices, and my taste dimensions.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, hls.js for playback. Every action (rent, buy, subscribe, pause) is a server action that also works without JavaScript.\n- **Discovery API** for all catalogue reads, facets, plan cards (variants with their subscription plans, periods, prices and metered tiers) and video playlists.\n- **Per-profile ranking:** check that ranking is enabled (`__type(name: \"RankByInput\")`), then rank the whole catalogue once per profile with `rankBy` (one `tasteCosine` term per vocabulary, `from: userTaste`, and a `tieBreaker` from the introspected `TenantRankByTieBreaker` enum) and `context.userTaste`. Build \"Because you like horror and eerie\" from the profile's top keys matched against the hit's `topicPaths`; keep `explain: true` for a debug flag. Reuse that one ranking for the hero, rows and \"For you\". Boost recent years with a `fieldBoost` on the year only if it's in `TenantRankByField`. Kids' profiles exclude topics.\n- **More like this:** `nearestTo: { vocabulary, like: { itemId }, k }` with a `rankBy` of `tasteCosine from: nearestTo` (0.8) and `from: userTaste` (0.2). An anchor with no vector returns nothing, so check first.\n- A profile's taste is its stated keys plus the topics of what it watched, weighted by progress. A household is one Core customer.\n- **Item taste (setup):** `upsertVocabulary` (a full replace: send every dimension), then `setItemTaste` per item, vocabulary and language, then rewrite the variant components (taste empties them), then `publishItem` per item and language, then one `igniteDiscoApi(stacks: opensearch)`, polled until complete.\n- **Rent and buy (Shop API):** a JWT from `/auth/token` on the server (scopes `cart`, `cart:admin`, `order`); `hydrate` with the household as customer → `place` → `/order` `createFromCart` (the order id is the cart id). Entitlement from Shop `orders(customerIdentifier)`, read on the server with the identifier from the session.\n- **Playback gate:** the watch page checks a session claim (tier, state, active until) written at sign-in, after every contract action and by the renewal job. It never reads Core.\n- **Subscriptions: the one place this store uses the Core API**, because metered usage can only be tracked there and contracts must live in one store. On the server only, from account pages behind sign-in and from scheduled jobs, never per page view:\n  - Create the household's customer first, then `createSubscriptionContract` (`subscriptionPlan { identifier, periodId, periodName }`, renew at = active until = the trial's end, meta for tier, next tier, period, market and period start). Pause and resume (on resume, push the dates forward by the time paused). A tier change is `updateSubscriptionContract { item, recurring }` from the next renewal; monthly ↔ yearly is cancel plus a new contract. `cancelSubscriptionContract(deactivate: false)` ends at the paid period; undo a cancel with `updateSubscriptionContract { status }`, not renew.\n  - Metered usage with `trackSubscriptionContractUsage` by the metered variable's `identifier`, with an idempotency key such as `<contract>:<title>:<profile>:<day>`; read `usage(meta.periodStart, now)`, and set `periodStart` at sign-up and at every renewal.\n  - Renewals and receipts as orders through the Shop API `/order` `create` (type recurring; a subscription line with the contract id; overage as its own line) in the Subscriptions pipeline, so account pages list them from the Shop API. Make renewals idempotent with meta `renewalOf = <contract>:<renewAt>`; renew adds one period to the previous renew-at date. Add-on channels are separate contracts pointing at the membership.\n- Subscription plans, VAT types, order pipelines and markets are created in the PIM API (`pim.crystallize.com/graphql`, which takes the tenant **id**, not the identifier). Price variants are created in the Core API (`createPriceVariant`). Record every generated id (plan period ids, metered-variable ids, pipeline and stage ids) in the setup config.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, `NEXT_PUBLIC_SITE_URL`.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Subscription plans live in the PIM API, and their period and metered-variable ids are generated: send them back on every update, or prices and contracts break.\n- In Discovery, a plan's initial phase repeats the recurring one: take the trial length from your own config.\n- Manage contracts in one place: the Shop API's contracts and Core's barely sync. Public pages (plans, prices) read from Discovery; only the signed-in account touches the contracts.\n- Nothing happens by itself at `renewAt`: run renewals yourself in a scheduled job, never on a page view (read usage → price the tiers → skip if an order with `renewalOf` exists → Shop `/order` `create` → `renewSubscriptionContract` → update `periodStart`). Send `renewAt` and `activeUntil` together, never with `activateAt`. Pausing freezes no dates. Updating `meta` replaces all of it.\n- A metered variable is `id` on variant pricing and order lines, `identifier` on contracts and usage.\n- `setItemTaste` empties variant components on the draft: write them back before publishing. Serialise taste writes per item. `userTaste` has at most one entry per vocabulary, each with `weights` and `magnitude` (the square root of the sum of squared weights; a wrong one fails silently), sent as a variable; `rankBy` needs a `tieBreaker`.\n- Publish a title only after its video has finished transcoding, or Discovery keeps an empty playlist. Publish collections after the titles they point to are indexed.\n- Send numerics with their unit when creating a product. Publishing a language without content creates items with a missing name.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"lab-universe","name":"Lab Universe","url":"https://lab-universe.superfast.shop","industry":"Lab supplies · B2B procurement","thesis":"A laboratory supplier built around the buyer’s procurement: roles, approval per department, budgets per cost centre, contract prices per institution, standing orders and regulated chemicals with GHS data.","color":"#1f5e9c","onColor":"#ffffff","stats":[{"value":"377","label":"products"},{"value":"7","label":"manufacturers"},{"value":"2,211","label":"SKUs"},{"value":"3","label":"institutions"}],"features":["Approval per department","Budgets per cost centre","Contract prices","Standing orders"],"audience":["b2b"],"sector":"life-sciences","capabilities":["approvals","budgets","roles","standing-orders","subscriptions","contract-pricing","volume-pricing","ghs","variants","spec-filters","multi-market","multi-currency"],"payments":[],"currencies":["NOK","SEK","EUR"],"languages":["EN","NO","SV","NL"],"topic":"Lab Supplies B2B eProcurement","description":"Laboratory supplies B2B demo: 2,211 SKUs with typed specs, contract prices per institution, approval workflows, budgets per cost centre, standing orders and GHS data.","page":"https://superfast.shop/demos/lab-universe","screenshot":"https://superfast.shop/shots/lab-universe.jpg","preview":"https://superfast.shop/previews/lab-universe.mp4","icon":"https://superfast.shop/icons/lab-universe.svg","llms":null,"promptUrl":"https://superfast.shop/demos/lab-universe/prompt.md","prompt":"# Build my own lab supplies store on Crystallize, like Lab Universe\n\nI want a storefront for my business that works like **Lab Universe** (https://lab-universe.superfast.shop), a demo store built on Crystallize: A laboratory supplier built around the buyer’s procurement: roles, approval per department, budgets per cost centre, contract prices per institution, standing orders and regulated chemicals with GHS data.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: approval flows, budgets and cost centres, buyer roles, standing orders, subscriptions, customer price lists, volume tiers, ghs hazard data, large variant sets, typed specs and filters, multiple markets, multi-currency. It is B2B, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which product families do I sell, and which typed specs matter for each (volume range and accuracy for pipettes, capacity and readability for balances…)?\n- Is there compatibility between products (a tip fits a pipette, a rotor fits a centrifuge)? Where does that data come from?\n- How my customers are organised: institutions, departments or cost centres, people. Which roles order, approve and purchase?\n- Approval rules: the amount limit per department, which categories or hazards need approval, who approves.\n- Contract terms: a percentage per brand or SKU, fixed prices, the contract period, a restricted range per customer?\n- List prices, volume tiers on consumables, currencies, VAT per market, languages.\n- Standing orders for consumables, and at which intervals?\n- Regulated data (GHS hazard labelling, safety data sheets, certifications), and who may buy hazardous items.\n- Where orders go after approval (an ERP, EDI)?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nOne product shape; a single component choice `specs` picks one typed family per product. Typed values only (numeric with units, boolean, selection), no properties tables. Relations for things, topics for properties. No content chunks on variants. Text is translated.\n\n- **lab-product** (product):\n  - tagline, summary, story (paragraphs), highlights.\n  - `manufacturer` chunk: `brand` relation (required), product line.\n  - `specs`: a component choice of one spec piece per family: pipette (channels, volume min and max in µL, adjustment, operation, increment, accuracy %, precision CV %), tip (max volume, filter, low retention, graduated, format, length, material), serological pipette, balance (capacity, readability, repeatability, calibration, pan, draft shield, legal for trade), centrifuge (max speed and RCF, places, tube formats, refrigerated), thermomixer, tube, cell culture, glove (material, thickness, length, powder-free, standards, AQL), glassware, chemical (CAS, formula, molar mass, grade, purity, form, density), accessory. Name the standards in component descriptions (ISO 8655, EN ISO 374, CLP/GHS, UNSPSC).\n  - `compatibility` chunk: `tip-system` relation (0–1) and `fits` relation (a tip → its pipettes, a rotor → its centrifuges).\n  - Pieces: `handling`, `safety`, `procurement`, `seo`.\n  - `documents` (repeatable chunk: title, type such as SDS, manual or certificate, and a URL to the manufacturer's copy, never re-hosted).\n  - `merchandising` chunk: sold in 30 days, margin, rating, campaign boost, pairs with (max 8).\n  - Images live on the variants. Variant components: GTIN, catalogue number; SKU = the manufacturer's catalogue number. Variant attributes by type: a pipette's volume range, a tip's volume and format, a glove's size, a tube's volume and pack, a chemical's pack size.\n- **handling** piece: storage (room temperature, 2–8 °C, −20, −80, dry and dark), shelf life (months), sterility, certified free of (DNase, RNase, human DNA, pyrogens, PCR inhibitors), autoclavable, single use.\n- **safety** piece: GHS pictograms (GHS01–09), signal word, hazard and precautionary statements (key = code, value = official text), UN number, and `verified-customers-only`, which drives who may see it.\n- **procurement** piece: UNSPSC, consumable (may go on a standing order), unit, pack contents, minimum order.\n- **brand** (document), **tip-system** (document: brand, locked, its pipettes and tips), **category** (folder).\n- Catalogue: `/<category>/<subcategory>/<product>` (pipetting, weighing, centrifugation, cell culture, tubes and containers, gloves and protection, laboratory glass, chemicals), plus brands and tip systems.\n- Topic maps: category, application (PCR, cell culture, protein work, microbiology…), tip system, volume class, hazard; image topics for image type and manufacturer.\n- Pricing: list prices excluding VAT, one price variant per currency, with volume tiers on consumables (the first tier at threshold 1 is the base price). VAT types per country.\n- **Contract prices:** one price list per institution, targeted at that customer, with a percentage modifier per SKU, valid over the contract period. It applies on top of the list tiers, and departments and people inherit it. An institution's list can leave out a range (e.g. chemicals).\n- Standing orders: a subscription plan `standing-order` with monthly and quarterly periods, on consumables' variants.\n- Order pipelines: Requisitions (awaiting approval, approved, rejected, sent to supplier) and Standing orders (active, paused).\n- Meta presets for customers, orders and subscription contracts, so the Crystallize App shows the meta as fields.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo's facts (catalogue numbers, specs, compatibility, GHS) come from the manufacturers' own sites and PDFs and from PubChem; the descriptions were written for it. 377 products, 2,211 variants and 1,571 images copied from the manufacturers' CDNs, with alt text in four languages.\n- From me: product master data with typed specs per family, compatibility data, links to safety data sheets, images, list prices and tiers, my customers' organisation tree, contract terms, approval rules and budgets.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, plain `fetch` GraphQL clients. The storefront never calls the Core API (it's rate limited): only Discovery, Catalogue and Shop API. Core and PIM are for setup and tools.\n- **Discovery API:** listings, facets, search and typeahead, product pages, list prices and tiers; sort by sales. \"Tips that fit\" filters on tips related to the pipette through `fits`, or through its tip system. Hide `verified-customers-only` products from unverified customers and guests, and enforce it on the server too (Discovery is public): refuse restricted SKUs before `hydrate` and `place`.\n- **Catalogue API** (server side): the institution's contract terms, `priceList(identifier) { productVariants(language, first) { edges { node { sku priceVariant(identifier) { priceList(identifier) { modifier modifierType } } } } } }`, one cached read per institution, paged until an empty page (`hasNextPage` stays true past the last SKU). The storefront applies the percentage to the list price and tier. Per SKU, `productVariants(skus) { priceVariant(identifier) { priceFor(count, customerIdentifiers: [...]) } }` does the same (at most 150 SKUs).\n- **Shop API** (JWT on the server):\n  - One cart per person: `hydrate` the whole basket with the person as customer, so the price list applies.\n  - Checkout: set the customer and put meta on the cart with `hydrate(input: { meta })`, `place`, then `/order` `createFromCart` into Requisitions: \"awaiting approval\" if it's over the department's limit or holds a hazardous item, otherwise \"sent to supplier\".\n  - Approvals move the order with `addToStage` and merge meta (approver, decision, reason, time). A department's requisitions are its people's orders (`orders(customerIdentifier, limit: 100)`, identifiers from the session) filtered on the department meta, grouped by `coreId` keeping the newest. Budget used = approved + sent this year, per cost centre.\n- **Standing orders** through the Shop API's subscription contracts (experimental; the JWT needs `subscription-contract` and `subscription-contract:admin`): one contract per standing order, the department as customer, all lines in `recurring.productVariants`; update the phase, pause, resume, skip by changing dates. Nothing renews by itself: a scheduled server job writes each period's order with Shop `/order` `create` (type `recurring`, subscription lines), then calls `renew`. Pause doesn't stop the dates: on resume, push `renewAt` and `activeUntil` forward. A list read right after `create` can be empty.\n- **Customers (created in setup through the Core API):** an institution is an organization; a department is an organization under it (meta: cost centre, budget, approval limit, approver, equipment); a person is an individual under both (meta: role orders, approves or purchasing). The demo signs in with a persona switcher; real customers need real authentication.\n- Order meta: your own order number, department, cost centre, orderer, approver, approval, reason, hazardous, decided at, comments.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Price variants are price types, not customer prices: contracts are percentage price lists (absolute lists lose the tiers). Target customers directly, not customer groups; children inherit.\n- Discovery resolves price lists only per market: get customer terms from the Catalogue API and cache them (`productVariants(skus)` takes at most 150).\n- Subscription contracts made in the Core API can't be used from the Shop API: create the ones the storefront edits through the Shop API. Updating the recurring phase replaces it whole.\n- Every Core `updateOrder` adds another copy to the Shop API's order list: group by the Core id and keep the latest. Shop-to-Core order sync takes about 10–20 seconds, and `coreId` can stay empty after a Shop `create`, so don't use it as a sync flag. An order's reference can't be set: keep your number in meta.\n- Meta preset choices are stored as JSON arrays (`[\"approved\"]`); filter on that string. Write order meta with Shop `/order` `setMeta(id, meta, merge: true)`; without `merge` it replaces all meta.\n- Discovery: a selection is one field per option key (`<field>_<key>: { exists: true }`); number facets need at least two boundaries; no explicit nulls; `term` matches whole words; answers are cached per query body.\n- Subscription plans are created and edited only in the PIM API (send the existing period ids back, or they are re-minted); variant plan prices are written through Core; the storefront reads plans from Discovery.\n- `copyRemoteAsset` can register a key whose file never arrives (bot-protected CDNs): wait for the tasks, check each key resolves, and upload the failures yourself. Respect the source site's rules.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"furniture-universe","name":"Furniture Universe","url":"https://furniture-universe.superfast.shop","llms":"https://furniture-universe.superfast.shop/llms.txt","industry":"Furniture retail · B2C","thesis":"A high-end furniture store where the configurable pieces are the product: a modular sofa and a wall system built in 3D and priced part by part, with shoppable inspiration and a store that re-sorts itself to each shopper’s taste.","color":"#2d5a57","onColor":"#f5f2ec","stats":[{"value":"145","label":"products"},{"value":"3","label":"brands"},{"value":"2","label":"3D configurators"},{"value":"8","label":"shoppable stories"}],"features":["Sofa and wall in 3D","Priced part by part","Shoppable inspiration","Taste per shopper"],"audience":["b2c"],"sector":"home","capabilities":["configurator","shoppable-content","personalisation","member-pricing","multi-market"],"payments":[],"currencies":[],"languages":["EN","NO","SV","NL"],"topic":"3D Furniture Configurator & Modular Sofa Store","description":"Furniture ecommerce demo with a 3D product configurator: a modular sofa and a wall system priced part by part, AR preview, shoppable room inspiration and quotes.","page":"https://superfast.shop/demos/furniture-universe","screenshot":"https://superfast.shop/shots/furniture-universe.jpg","preview":"https://superfast.shop/previews/furniture-universe.mp4","icon":"https://superfast.shop/icons/furniture-universe.png","promptUrl":"https://superfast.shop/demos/furniture-universe/prompt.md","prompt":"# Build my own furniture retail store on Crystallize, like Furniture Universe\n\nI want a storefront for my business that works like **Furniture Universe** (https://furniture-universe.superfast.shop), a demo store built on Crystallize: A high-end furniture store where the configurable pieces are the product: a modular sofa and a wall system built in 3D and priced part by part, with shoppable inspiration and a store that re-sorts itself to each shopper’s taste.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: product configurator, shoppable content, vector personalisation, member pricing, multiple markets. It is B2C, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which of my products are configurable, and how: modular pieces joined in a line or around corners (like a sofa), parts hung on a grid (like a wall system), or just picking options on one product?\n- The rules: which parts connect to which, left and right versions, corner angles, which ends must be closed, what requires or excludes what, and the limits (size, load).\n- How the price is built: per part, per fabric or finish group, surcharges, and a discount on ready-made setups?\n- **Do I have 3D models?** In which format, units and origin, and how big are the files? If I don't, go through the options below with me and help me pick one:\n  - The manufacturer: many supply CAD or configurator exports (STEP, FBX, OBJ). They need converting to GLB in Blender, reducing in detail, and re-scaling and re-origining to the conventions below.\n  - A 3D artist, commissioned with the conventions below as the brief. The most reliable route for a range that must snap together.\n  - Built from dimensions in code or Blender, as the demo did: simple, parametric shapes, good for modular upholstery and panels.\n  - Photogrammetry for a hero piece: realistic, but needs heavy clean-up and doesn't suit modular parts.\n  - Marketplaces (Sketchfab, CGTrader, TurboSquid) for props and accessories only: check that the licence allows commercial use in a configurator, then re-scale and re-origin.\n- My materials and finishes: how many, and do I have texture scans or swatches with their real-world size? If not, CC0 texture libraries (Poly Haven, ambientCG) or my suppliers' swatch scans work.\n- My markets, currencies, VAT and languages; delivery or click & collect; made-to-order lead times.\n- Member or trade prices, campaigns, and quotes through a sales desk?\n- Do I want AR (seeing the piece in the room on a phone)?\n- Ready-made setups to sell as named products with their own photos?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nA new configurable product is a new `specs` choice on these shapes, never a new shape.\n\n- Pieces:\n  - `model-3d`: a `glb` files component, `mirrored` boolean, `thumbnail` image, notes.\n  - `compatibility`: requires and excludes relations, `accepts-arm`.\n  - `dimensions`: width, depth, height (cm).\n  - `merchandising`: launch date, sold in 30 days, margin, rating, review count, campaign boost, staff pick, hide from listings.\n- **module** (product, one buyable part): description, `options` relation, compatibility, seo, brand, designers, gallery, merchandising, and `specs` as a component choice:\n  - `sofa-module`: module type (seat, corner, end, chaise, pouf, arm), run role (seat, corner, terminator, standalone), turn angle, pocket-spring surcharge, seat height.\n  - `wall-part`: role (wall panel, floor panel, shelf, cabinet, desk, rail), the system it belongs to, hook offsets, collision height, max load, surface height, depth class.\n  - Variant components: `dimensions`, `model-3d`, `colour`.\n- **setup** (product, a ready-made configuration): tagline, summary, description, option slots, series, brand, designers, materials and care, product facts, pairs with, gallery, and `specs` (seating, or a wall setup with mount, bays and a `wall-layout` chunk of which module sits in which bay at which height). Variant: the fabric, dimensions, model and colour. The `recipe` (repeatable chunk: setup SKU, position, module, quantity) lives on the product, one row per variant, because variant chunks may not publish.\n- **series** (folder, a product family): description, hero, brand, designers, and `specs`: for a sofa, its fabrics and option slots (type, min, max, allowed, default); for a wall system, its grid, panel thickness, bay widths, depth classes, max bays, first and last slots, clearance, load per section, and finishes (code, name, swatch colour, materials).\n- **fabric** (product, a colourway family): price group, fabric type (bouclé, velvet, woven, leather), material relation, a spec chunk (Martindale, pilling, colour fastness), care. Variants are colourways: colour code, hex colour, and a `material-3d` piece (tint, colour map, normal map, roughness map, weave scale in mm).\n- **material** (document, a PBR texture set): material model (standard, sheen, leather), albedo, normal, ARM (occlusion, roughness, metalness) and roughness maps, tile size in mm, roughness and normal scale, sheen, source and licence.\n- **option** (product): comfort, armrest or leg details, description, model, compatibility.\n- Retail: **furniture** (product with a `specs` choice for seating, tables, storage, lighting, textiles, accessories; variants with colour, physical size and model; GTINs in a product-level identifiers chunk, one row per SKU), plus **brand**, **designer**, **store** (address, location, hours, services, market), **collection**, **article**, **landing-page** (blocks and targeting), **category** (folder), **care-instruction**.\n- Catalogue: `/sofas/<series>/{modules,setups,options}` and the same for wall systems, plus fabrics, materials, care, brands, designers, stores, inspiration, pages, and retail categories (seating, tables, storage, lighting, textiles, accessories).\n- Topic maps in three taste groups: style (brand, collection, designer, style), home (room, material, colour), product (product type, price tier, feature). Images are topic-tagged too, with `updateImage(key, language, { topicIds })` once per language.\n- Pricing: net prices, one price variant per currency. Fabric price groups are absolute price lists (`group-a`, `group-b`…; `SOME_SKUS` with a modifier per SKU). Campaigns are dated percentage lists aimed at a market or everyone. The member discount is a percentage list with `targetAudience: { type: SOME, customerIdentifiers: [...] }`: customer groups can't be targeted. `updatePriceList` replaces the whole SKU selection.\n- Stock: an online warehouse plus one location per store for click & collect; made-to-order pieces are delivery only.\n- Order pipelines: Quotes (new, sent, in dialogue, accepted, lost) and Orders (new, in production, shipped, delivered).\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo's models are all generated in code (Python with trimesh): 26 sofa GLBs of 41–184 KB each, and 22 wall GLBs, 730 KB together. Tests fail if a model's bounding box doesn't match the catalogue's dimensions; do the same.\n- **The conventions my models must follow:**\n  - GLB (binary glTF 2.0), in metres, Y up.\n  - The back of the piece at z = 0, the body toward +z.\n  - Sofa modules: origin at back-left-bottom. Wall parts: a panel's origin at its left face and bottom edge; a hung item's at the inner face of the left panel, at its hang slot.\n  - Model the right-hand version; the left is a mirror (or its own file).\n  - One GLB per geometry. Finishes and fabrics are named materials swapped at runtime (e.g. `Upholstery`; `Panel`, `Board`, `Front`, `Hardware`).\n  - Closed meshes, outward normals, and one UV scale in real-world units (the demo uses 15 cm per UV unit) so fabric textures show at their true size.\n  - Budget: roughly 50–200 KB per module, 2048 px textures (albedo, normal, packed ARM).\n- Fabric and wood textures in the demo come from Poly Haven (CC0), with tile size and author recorded.\n- From me: my configurable range with its rules and prices, 3D models (or a plan to get them), materials and finishes with swatches, stores, and photos.\n\n## Step 5: the storefront\n\n- A Next.js storefront (App Router, Vercel), with the configurator as its own Vite + React + three.js app, served on the same domain through rewrites (`/<lang>/configure`). AR through `@google/model-viewer`.\n- The configurator's small server is the only holder of the token; the browser never calls Crystallize. It serves one catalogue snapshot (modules, setups, fabrics, options, price lists, wall systems), cached for 5 minutes: from the Discovery API (products with their variants, dimensions and models) and the Catalogue API (the price lists).\n- One shared engine package (plain JS: layout, rules, pricing, markets) runs the same in the browser, on the server and in tests.\n- **Sofa snapping:** each module has an entry and an exit connector (a point and a heading). A sofa is a run, an ordered chain of SKUs where each exit meets the next entry; the turn angle (0, 45, 90, −90) turns the heading. Terminators (arm, open end, chaise) close the ends; poufs stand alone. `canAttach()` gives a reason when it refuses. Handedness comes from a `Side` attribute; an arm is modelled for the right end and mirrored at the start.\n- **Wall snapping:** a 4 cm grid; sections of one to six bays (60 or 90 cm) between shared blades; an item hangs in a bay at a slot whose hook offsets exist on both blades; its depth class must match; no overlaps; desk surface 72–76 cm; warnings for tight clearance and heavy wall loads.\n- **Price, part by part:** each module's price comes from the chosen fabric's price-group list in the market's currency, plus surcharges. A run that matches a ready-made setup is priced as that setup. Then member and campaign discounts apply. Walls are the sum of their parts. The server re-prices every cart line and sets it with `changeCartItemPricing`.\n- **Shop API** for carts, checkout and orders. A shared configuration is a cart of type `wishlist` with the configuration in its meta. Quotes are Shop `/order` orders of type quote, created with `pipelines: [{ identifier: \"quotes\", stage: \"new\" }]` and moved with `addToStage` (never Core `updateOrder`); accepting one creates a normal order.\n- **Discovery API** for the storefront's catalogue reads. Per-SKU price lists (fabric groups, member and campaign lists) come from the Catalogue API on the server: a whole list via `priceList(identifier) { productVariants(language, first) { … priceVariant(identifier) { priceList(identifier) { modifier modifierType } } } }`, stopping on an empty page, or a member's price via `priceVariant { priceFor(count, customerIdentifiers: [...]) }` (at most 150 SKUs a call). Discovery resolves market-targeted lists only.\n- **AR:** rebuild the configuration off-screen, export a GLB in the browser (textures capped at 1024 px), and open it in model-viewer (WebXR and Quick Look, fixed scale, on the floor). Desktop shows a QR code.\n- Uploads: GLB and PDF files through a presigned STATIC upload, video as MEDIA, then attach the key to the component.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, a shopper session secret, and the configurator's origin and API URL.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- The Catalogue API can come back with no variants for these products: read variants from Discovery.\n- The mass-operation runner can say \"completed\" when operations failed: read every operation's log. Files can't go through mass operations: upload them, then attach the key.\n- `updateComponent` replaces a whole piece: read, merge, write (updating a thumbnail once wiped the GLB).\n- Handedness is the most common mistake: define right and left seen from the front, in one table, and test the geometry.\n- A product can't change shape in place, and SKUs are unique across the tenant.\n- Image renditions for swatches are small; use them.\n- The Shop API applies price variants and the lists aimed at the cart's customer or markets, but can't know the fabric group or surcharges: set each configured line with `changeCartItemPricing` (the line becomes unmanaged).\n- Discovery ranking needs ranking enabled on the tenant (probe `__type(name: \"RankByInput\")`) and `igniteDiscoApi(stacks: opensearch)`, polled until complete. Boost only on fields in the introspected `TenantRankByField`; numbers you facet on are excluded.\n- The 3D scene, AR export and thumbnails stall in background tabs.\n- A material's roughness multiplies the ARM map's green channel; the GLB's UV scale and the scene's must match.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"spirits-universe","name":"Spirits Universe","url":"https://spirits-universe.superfast.shop","industry":"Beverage supplier · B2B","thesis":"A drinks supplier to restaurants, bars, shops and importers, where the range follows the buyer’s licence and country: deposits and kegs out, export documents, cost per serving and margin at the venue’s menu price — and its customer, Wine and Dine Universe, is another Crystallize store.","color":"#ffc400","onColor":"#000000","stats":[{"value":"397","label":"drinks"},{"value":"193","label":"brands"},{"value":"984","label":"packs"},{"value":"12","label":"campaigns"}],"features":["Range by licence and country","Deposits and kegs out","Margin at the menu price","Supplier to Wine and Dine"],"audience":["b2b"],"sector":"food-beverage","capabilities":["subscriptions","booking","time-slots","invoice-credit","roles","approvals","standing-orders","multi-market","multi-currency","contract-pricing","personalisation","explainable-ranking","spec-filters","shoppable-content","campaigns"],"payments":["invoice"],"currencies":["SEK","DKK","NOK","EUR"],"languages":["EN","SV","NO","NL"],"topic":"B2B Beverage Wholesale & Drinks Distribution","description":"Beverage wholesale demo for bars and restaurants: range by licence and country, keg and bottle deposits, contract prices, standing orders and margin per serving.","page":"https://superfast.shop/demos/spirits-universe","screenshot":"https://superfast.shop/shots/spirits-universe.jpg","preview":"https://superfast.shop/previews/spirits-universe.mp4","icon":"https://superfast.shop/icons/spirits-universe.svg","llms":null,"promptUrl":"https://superfast.shop/demos/spirits-universe/prompt.md","prompt":"# Build my own beverage supplier store on Crystallize, like Spirits Universe\n\nI want a storefront for my business that works like **Spirits Universe** (https://spirits-universe.superfast.shop), a demo store built on Crystallize: A drinks supplier to restaurants, bars, shops and importers, where the range follows the buyer’s licence and country: deposits and kegs out, export documents, cost per serving and margin at the venue’s menu price — and its customer, Wine and Dine Universe, is another Crystallize store.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: subscriptions, native booking, time-slot bookings, invoice and credit limits, buyer roles, approval flows, standing orders, multiple markets, multi-currency, customer price lists, vector personalisation, explainable ranking, typed specs and filters, shoppable content, campaigns. It is B2B, in English, Swedish, Norwegian, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which countries and currencies do I sell in? Do my list prices include alcohol duty, and do I export duty-suspended?\n- Which licence rules decide what a customer may see and buy (alcohol-free for guests, a low-strength threshold for grocery, full range for licensed venues and importers)? The thresholds per country.\n- Which deposit and return schemes apply per pack and country, and the amounts? Do I take back empties and kegs?\n- My pack formats (kegs, crates, cans, returnable and one-way glass, bag-in-box) and case and pallet sizes.\n- Contract pricing: a percentage per chain, tiers, dates it's valid between?\n- Services I sell (tap installation, line cleaning, sommelier, tastings): hosted by people, or sold from a capacity?\n- Subscriptions: campaign content packages, regular line cleaning, standing orders, and at which intervals?\n- Where my product facts and taste data come from: my own PIM, a spreadsheet, a public registry such as Systembolaget's assortment?\n- Do my customers publish their menu prices (for margin per serving), or will they enter them? Do any of them run Crystallize too? (Then the two stores can sync, as Spirits Universe does with its restaurant customer.)\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nNumerics carry the unit in their identifier (no unit lists); everything shown is discoverable; chunks with translated text are multilingual.\n\n- Pieces:\n  - `seo`; `alcohol`: ABV, licence class (free, low-strength, licensed, derived from ABV), licence required, alcohol-free twin (relation).\n  - `taste`: body, bitterness, sweetness, acidity, roast (1–12, the taste-clock scale), aroma, where the taste data came from.\n  - `serving`: min and max temperature, glass, pour (cl), tip. `ingredients`: ingredients, allergens (EU 14), vegan, organic. `sustainability`: CO₂e per litre, recyclable packaging.\n  - One spec piece per family: beer (IBU, EBC, fermentation, hops, malts), cider, wine (colour, sparkling, grapes, vintage, sweetness), spirit (type, cask, age), soft drink, water.\n  - `kit` for campaign content: headline, intro, social posts (repeatable: text, image, suggested date, channel), menu card lines, pairings, files and images, usage notes.\n- **drink** (product):\n  - tagline, summary, story, highlights; `origin` chunk (brand, producer, country, region); `specs` component choice of the family pieces; the alcohol, taste, serving, ingredients and sustainability pieces; `pairing` (serve with recipes); `merchandising` chunk (sold in 30 days, margin, rating, campaign boost, pairs with); seo.\n  - One variant per pack, SKU = article number. Variant components: pack type, volume (cl), units per case, `deposit` relation (max 2), deposit scheme, `markets` (multi-selection of the countries it's sold in), servings, pallet. GTINs (unit and case) go in a product-level chunk, one row per SKU.\n- **deposit** (product): container (kegs of 20, 30 and 50 litres, bottle, crate, can…), scheme, markets, returnable. Priced per scheme.\n- **service** (product, bookable): type, duration, seats, price per, hosts, featured drink.\n- **campaign-plan** (product): what's included, print, events per campaign. Sold as a subscription.\n- Documents: **brand**, **producer**, **person** (hosts and contacts), **recipe** (cocktail, mocktail or dish; ingredients that relate to drinks), **article**, **campaign** (dates, featured drinks, a bundle with a discount, the kit piece). Folder: **category**.\n- Catalogue: `/<category>/<subcategory>/<drink>` (beer, cider, alcohol-free, wine, spirits, soft drinks, water, mixers), plus services, campaign content, campaigns, people, inspiration and insights.\n- Topic maps: category, style, occasion, cuisine, dish, origin, seasonal, image type.\n- Vector vocabularies: `taste` (the five taste dimensions as `high` or `low` keys, centred on the catalogue's average), `menu` (cuisine, dish, occasion), `range` (category, style, origin).\n- Pricing: excluding VAT; a home-market list price including duty, and export price variants (duty-suspended) per currency, with volume tiers per full pallet. VAT types per country.\n- \"Market\" lives in the customer's meta and on each variant's `markets` selection, not as Crystallize markets.\n- Contract prices: dated percentage price lists per SKU, targeted at the group customer; its venues inherit them.\n- Customers: organizations with venues as child customers and people as individuals under them. Meta: business type, country, market, licence type, number and expiry; a person's role.\n- Subscription plans: campaign content (a season or a year), monthly line cleaning, standing orders (weekly, every two weeks, monthly).\n- Order pipelines: Orders (received, picked, out for delivery, delivered, empties returned), Export, Services.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo holds about 400 drinks in 984 packs, 193 brands, 152 producers and 12 deposits. Facts come from the Swedish Systembolaget assortment and the brand owners' sites, collected as research files and built into catalogue JSON. Customers, orders and campaigns are fictional.\n- Pack shots were copied into Crystallize; campaign photos come from Unsplash with credit.\n- From me: products with ABV, packs, volumes and case sizes, GTINs, prices per currency, deposit schemes and amounts, my market's licence rules, customers and contract discounts, services with hosts or capacity, and images I have the rights to.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, plain `fetch` GraphQL clients. Sign-in in the demo is a persona picker; real customers need real authentication.\n- **Discovery API:** listings, facets, search, products, campaigns, services (with their booking pool), plans, and taste ranking (`rankBy` with one `tasteCosine` term per vocabulary and an introspected `tieBreaker`, plus `context.userTaste` with a `magnitude` per vocabulary; `explain: true` only behind a debug flag).\n- **Range by licence and country:** Discovery filters on ABV and licence class, plus the variant's market. The cart action refuses what the customer may not buy too. Read the customer's licence type, number, expiry and market once at sign-in and keep them in the signed session: listings and the cart read the session, never Core. Guests see alcohol-free only, without prices on alcohol.\n- **Contract prices** from the Catalogue API on the server (`priceFor(count, customerIdentifiers: [org])`, at most 150 SKUs a call, or the whole `priceList(identifier)`), cached per organization; Discovery doesn't resolve per-customer lists. The cart resolves the same list from its customer.\n- **Drinks cart (Shop API):** written whole with `hydrate`: drink lines, one deposit line per unit, and the bundle discount as an external line. Cart meta: venue, licence number, delivery day. Returned empties are a negative external line on the next order. `place`, then create the order.\n- **Services cart (separate, since `hydrate` replaces a cart):** `availability` (with the item's `language`), then `bookSkuItem` with the customer already on the cart: a hosted service books quantity 1 plus the host's unit (also written into the line meta), a capacity service books quantity = seats with no unit. Check `__typename`. Then `place` → optional `confirmCartBooking` → payment → `/order` `createFromCart` → poll the cart's order id → `confirmCartBooking` again. If payment fails after the first confirm, cancel on `/booking/admin`. Extra seats as an external line.\n- **Subscriptions through the Shop API** `/subscription-contract` (experimental; the JWT needs `subscription-contract` and `subscription-contract:admin`), on the server for the signed-in customer: create, pause, resume and cancel. A different plan or period is cancel plus a new contract; a different basket is an update to the recurring phase from the next renewal. A standing order is one contract whose `recurring.productVariants` lists every line. Nothing renews by itself: a scheduled job writes each delivery or invoice with Shop `/order` `create` (type recurring, the contract id on the line), then renews. Pausing freezes no dates: on resume, push them forward. Never create contracts for these customers in Core (the two barely sync).\n- **Margin at the menu price:** servings = volume × units ÷ pour; cost per serving = contract unit price ÷ servings; margin = (menu price excluding VAT − cost) ÷ menu price excluding VAT.\n- **Ranking per venue:** the venue's taste, its cuisines, dishes and occasions, plus a range vector from its order history.\n- **Sync to a customer's store (optional):** webhooks on item publish and order create, with a shared secret header, post to the customer store's sync endpoint. The supplier never writes into the other tenant.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, and the customer tenant's identifier if they sync.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Selections filter by `exists` on each option's field, not by label. Filter, facet and sort fields follow `{componentId}_{subfield}_{type}`, and a hyphen before a digit becomes `_` (`sugar-g-per-100ml` → `sugarGPer_100ml`): introspect the generated names.\n- Rich text isn't discoverable by default; numerics with a unit list become required.\n- No content chunks on variants. `updateProductVariant` without components empties them; updating with `components` replaces all.\n- Relations resolve once the target is indexed: publish targets first, then republish the items that point to them, per language.\n- Re-index Discovery after shape changes. Discovery caches per query body, errors included.\n- Taste cosine needs centred keys (`<dim>:high|low`), or everything scores about 1.\n- Price lists target customers, not customer groups.\n- Bookings: customer on the cart before the hold; one booking write at a time per cart (two concurrent `bookSkuItem` calls lose one). Drop a booked line by re-hydrating without it (not `cancelReservation` or `removeCartItem`). Cancel a reservation on a placed or ordered cart on the server through Shop `/booking/admin` (`cancel(id, reason)`, scopes `booking` and `booking:admin`).\n- Negative external lines need `variant.product`.\n- Subscription contracts: send `renewAt` and `activeUntil` together (with only `renewAt` a contract is born cancelled); dates can't be in the past, so back-date `signedAt` and the orders instead; updating `meta` replaces all of it.\n- Webhook concerns are plural (`items`/`publish`); a failing webhook query means no call; `updateWebhook` resets the query target if you leave it out; `createWebhook` accepts any concern or event string without complaint, so confirm each webhook arrives.\n- Multi-word search can hit OpenSearch's clause limit: query per word.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"wine-and-dine-universe","name":"Wine and Dine Universe","url":"https://wine-and-dine-universe.superfast.shop","industry":"Restaurant group · B2C bookings","thesis":"One site for a group of five restaurants that sells nothing online: guests book a table on the floor plan or a table at an event, and every dish suggests a drink from a list synced from its supplier, Spirits Universe.","color":"#a3243b","onColor":"#ffffff","stats":[{"value":"5","label":"restaurants"},{"value":"14","label":"tables on the plan"},{"value":"1,066","label":"past bookings"},{"value":"4","label":"languages"}],"features":["Tables on a floor plan","Events booked per table","Drinks synced from Spirits","Estimated spend per booking"],"audience":["b2c"],"sector":"hospitality","capabilities":["booking","time-slots","campaigns"],"payments":[],"currencies":["SEK"],"languages":["EN","SV","NO","NL"],"topic":"Restaurant Table Booking & Event Reservations","description":"Restaurant group booking demo: book a table on the floor plan or a seat at an event, menus with allergens, and a drinks list synced from the supplier’s store.","page":"https://superfast.shop/demos/wine-and-dine-universe","screenshot":"https://superfast.shop/shots/wine-and-dine-universe.jpg","preview":"https://superfast.shop/previews/wine-and-dine-universe.mp4","icon":"https://superfast.shop/icons/wine-and-dine-universe.svg","llms":null,"promptUrl":"https://superfast.shop/demos/wine-and-dine-universe/prompt.md","prompt":"# Build my own restaurant group store on Crystallize, like Wine and Dine Universe\n\nI want a storefront for my business that works like **Wine and Dine Universe** (https://wine-and-dine-universe.superfast.shop), a demo store built on Crystallize: One site for a group of five restaurants that sells nothing online: guests book a table on the floor plan or a table at an event, and every dish suggests a drink from a list synced from its supplier, Spirits Universe.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: native booking, time-slot bookings, campaigns. It is B2C, in English, Swedish, Norwegian, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- How many restaurants, in which cities, with which cuisines? Which are open now, and which should show as opening soon?\n- Per restaurant: the floor plan (a drawing or photo I can share is enough to trace), the tables with seats, minimum party and zone, seating times, how long a seating lasts, the turnaround between guests, and closed days.\n- Booking rules: cancellation window, how long a table is held while booking, the largest party bookable online, and whether a booking takes a deposit.\n- Menus: dishes with allergens, diets, courses and prices, and the dates each menu is valid.\n- Drinks: my own list, or synced from my supplier? If my supplier runs Crystallize, which tenant, and can I get a read-only token? Who sets the menu prices?\n- Events: tastings, dinners, masterclasses; the price per guest, which tables or rooms, and the hosts.\n- The look of each restaurant (colours, fonts, logo) and of the group.\n- My photos, or should they be sourced or generated (no people, text or logos)?\n- VAT for food and for drinks in my country, and my currency.\n- Would my managers like estimated spend per booking, for statistics?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\n- Pieces:\n  - `seo`; `taste`: body, bitterness, sweetness, acidity, roast (1–12, the same scale as the supplier), note.\n  - `source` (owned by the supplier sync): supplier item id, SKU, order id, path, synced at.\n  - `kit` for campaigns: headline, intro, social posts, menu card lines, pairings, files and images, usage notes.\n- **group** (document, one item): hero.\n- **restaurant** (folder): tagline, summary, story, hero, gallery, `status` (open, opening soon, closed), opens on, `look` chunk (background, ink, accent, accent ink, heading and body fonts, logo), `address` chunk (street, postcode, city, country, location, directions), `opening-hours` (repeatable chunk: days, opens, closes, note), `contact` chunk, seats, price level (1–4), taste piece, the restaurant's customer identifier at the supplier, seo.\n- **menu** (document): kind (à la carte, lunch, set menu, drinks, campaign), intro, valid from and to, `sections` (repeatable chunk, translated: title, note, dishes, drinks).\n- **dish** (product): menu line, summary, image, allergens (EU 14), seasonal, taste piece, `pairs-with` → menu drink (max 4), pairing note. Its variant holds the SKU, menu price and VAT.\n- **menu-drink** (product): source piece, tagline, summary, image, kind, brand, producer, origin, ABV, taste piece, `new-for` → restaurant, and `served` (repeatable chunk: restaurant, supplier SKU, serving size, price, by the glass). The sync writes everything but `served`, which the restaurant owns.\n- **table-booking** (product, one per restaurant, price 0): summary, `seating` chunk (seating length, times, min and max party, days ahead), `floor-plan` chunk (background image, width, height), policy text. Bookable, with each table as a unit.\n- **event** (product): kind, starts, ends, seats, hosts, featured drinks, dishes, campaign. Bookable, with the restaurant's tables as its units.\n- **campaign** (document): status (offered, taken, live, ended, declined), dates, kit, dishes, events.\n- Catalogue: one folder per restaurant holding its menus, dishes, table booking, events and campaigns, with a short alias path (e.g. `/brasseri`); `/drinks` shared by all restaurants.\n- Topic maps: cuisine, dish, occasion, style, course, diet, city, restaurant, image type. Keep the supplier's keys where they are shared.\n- Vector vocabularies: `taste` (the supplier's centred high/low keys) and `guest` (cuisine, dish, occasion, restaurant).\n- Pricing: one price variant holding the menu price including VAT; VAT types for food and tables, and for drinks and events. No markets, price lists, stock or deposits.\n- Order pipeline: Bookings (requested, confirmed, seated, cancelled, no-show), created in the PIM API; map pipeline and stage ids to names at setup.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo's restaurants, dishes, menus and guests are fictional; its drinks, campaign kits and events come from the supplier through the sync. It has 1,066 back-dated visits for statistics.\n- The floor plan's table positions were traced from a design SVG; the room outline is drawn in the storefront.\n- Photos of rooms and dishes come from Unsplash with credit.\n- From me: restaurants with addresses and hours, floor plans and tables, seating rules, menus with dishes, allergens and prices, drinks and their menu prices, events, and photos.\n\n## Step 5: the storefront\n\n- Next.js (App Router) on Vercel, plain `fetch` GraphQL clients. No sign-in: each browser gets a guest customer identifier in a signed, httpOnly cookie, and \"My bookings\" reads that customer's orders on the server from Shop `/order`.\n- **Discovery API:** restaurants (filter by city, cuisine and occasion; nearest by location), menus, dishes, drinks, events, campaigns, and each bookable's pool units with their meta.\n- **Booking policies (Core API, setup only; all five durations required, in seconds):** tables `advanceWindow: 950400` (11 days), `bufferBefore: 0`, `bufferAfter: 900`, `cancellationWindow: 7200`, `pendingHoldDuration: 600`; events `advanceWindow: 10368000` (120 days), `cancellationWindow: 86400`. Read `humanized` back to check the units. The storefront enforces opening hours and seating times; the policy doesn't.\n- **Tables:** `setBookable` with one unit per table. Unit meta (strings): seats, min seats, zone, label, number, x, y, w, h, shape (rectangle, round, stool), chairs. A party fits a table when min seats ≤ guests ≤ seats. `setBookable` writes the draft: `publishItem` after it, or the Shop API answers not bookable. After a policy edit, run `reapplyBookablePolicy` on each product and publish.\n- **The floor plan:** draw the room as SVG and place the tables from their unit meta; free tables are clickable.\n- **Booking (Shop API):**\n  1. `hydrate` with the guest customer first.\n  2. `availability(…, granularitySec: 1800, language)`, in a language the item exists in: a table is free only if every half hour of the seating is.\n  3. `bookSkuItem` with quantity 1 and the table's unit, also written into the line meta. Check `__typename`; on a conflict, try the next free table. One booking write at a time per cart.\n  4. Cart meta: party size, name, phone, restaurant.\n  5. `place` → `confirmCartBooking` → `/order` `createFromCart` with `pipelines: [{ identifier: \"bookings\", stage: \"requested\" }]` → poll the cart until it has its order id → `confirmCartBooking` again. Move stages with Shop `addToStage`, never Core.\n- **Events** use their own pool of copies of the restaurant's table units, so check for overlaps with table bookings to avoid double booking. Extra guests are an external line.\n- **Estimated spend:** each table booking gets an external line of guests × an estimated spend per guest, so the orders in Crystallize give managers sales statistics.\n- **Pairings:** each dish suggests drinks; the reason is shared taste (both high on a dimension, or both light).\n- **Sync from a supplier on Crystallize (optional):** an endpoint takes the supplier's webhooks (item published, order created), checks a shared secret, and upserts menu drinks (reading the supplier's Discovery in every language and copying images with `copyRemoteAsset`, waiting for each task and checking the keys), marks drinks new for a restaurant, and creates offered campaigns and events. It writes only the fields it owns, only when they changed, with `updateComponent`. A nightly job reconciles everything.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`; with a supplier sync also the supplier's tenant identifier and read-only token, a webhook secret and a cron secret.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- An item's path follows its name, and no API sets paths: use aliases and external references.\n- Discovery serves a selection as a key-to-label map; filter with one field per option (`status_openingSoon`).\n- Price variants have no \"includes VAT\" flag: tell the cart with `context.price.pricesHaveTaxesIncludedInCrystallize: true` on `hydrate`.\n- Unit meta values are strings. Capacity → units is refused while a capacity pool is published: `clearBookable`, publish, cancel or wait out open reservations, then `setBookable` with units and publish.\n- Put a customer on the cart before `bookSkuItem`. A reservation on a placed cart can't be cancelled through the cart, but it can on the server through Shop `/booking/admin` (`cancel(id, reason)`, scopes `booking` and `booking:admin`; it ignores the cancellation window, so enforce the two-hour rule yourself). A guest's cancellation cancels the reservation there (or the table stays blocked), then moves the order to Cancelled with `addToStage` and records a note with `setMeta`.\n- Read order items as `name quantity meta`, not `variant { sku }`.\n- Relations resolve only to items published in that language: publish restaurants, then dishes, then menus, per language.\n- A sync must own only its fields: never update with a whole `components` set.\n- Webhook payloads are thin: query what you need; a failing webhook query means no call; publishing in four languages is four calls; `createWebhook` accepts any concern or event string, so confirm each webhook arrives.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"kitchen-universe","name":"Kitchen Universe","url":"https://kitchen-universe.superfast.shop","industry":"Kitchen retail · B2C","thesis":"A kitchen store where the configurator is the shop: start from one of six ready-made kitchens, rearrange it in 3D or let an AI concierge build it from a chat, and watch it priced from its parts in each market — with the yearly catalogue as a PDF made from the live data.","color":"#2d5a46","onColor":"#f7f5f0","stats":[{"value":"6","label":"kitchens to start from"},{"value":"29","label":"products"},{"value":"13","label":"concierge tools"},{"value":"4","label":"languages"}],"features":["Kitchens built in 3D","An AI concierge that builds the kitchen","PDF catalogue from live prices","Priced from parts per market"],"audience":["b2c"],"sector":"home","capabilities":["configurator","ai-agent","pdf-catalogue","agent-checkout","multi-market","multi-currency"],"payments":[],"currencies":["EUR","NOK","SEK"],"languages":["EN","NO","SV","NL"],"topic":"3D Kitchen Planner, AI Kitchen Designer & PDF Catalogue","description":"Kitchen ecommerce demo: a 3D kitchen planner, an AI concierge that builds kitchens from a chat, and a PDF catalogue made from live prices in EUR, NOK and SEK.","page":"https://superfast.shop/demos/kitchen-universe","screenshot":"https://superfast.shop/shots/kitchen-universe.jpg","preview":"https://superfast.shop/previews/kitchen-universe.mp4","icon":"https://superfast.shop/icons/kitchen-universe.svg","llms":null,"promptUrl":"https://superfast.shop/demos/kitchen-universe/prompt.md","prompt":"# Build my own kitchen retail store on Crystallize, like Kitchen Universe\n\nI want a storefront for my business that works like **Kitchen Universe** (https://kitchen-universe.superfast.shop), a demo store built on Crystallize: A kitchen store where the configurator is the shop: start from one of six ready-made kitchens, rearrange it in 3D or let an AI concierge build it from a chat, and watch it priced from its parts in each market — with the yearly catalogue as a PDF made from the live data.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: product configurator, ai concierge, pdf catalogues, agent checkout, multiple markets, multi-currency. It is B2C, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- What do I sell: whole kitchens from one maker's range, parts to complete a kitchen, or both? Which ready-made kitchens (layouts like a straight run, an L, an island, a galley, a tall wall) should shoppers start from?\n- My range as parts: cabinet types and widths (base, drawer, sink base, corner, wall, tall, oven housing), front ranges and their finishes, worktops, handles, sinks and taps, appliances.\n- The rules: what requires what (a sink needs a sink base, an oven needs an oven housing), what excludes what (a handleless front and a knob), and the minimum and maximum of each category in a kitchen.\n- How the price is built: per cabinet, fronts and handles counted per cabinet, worktops per centimetre of run, appliances per piece? Are prices stored with or without VAT, and do I want rounded shelf prices per currency?\n- **Do I have 3D models?** In which format, units and origin? If I don't, go through the options below with me and help me pick one:\n  - Built from dimensions in code, as the demo did: parametric cabinets, fronts and worktops in Python, good for a modular range where every part is a box with doors, drawers and handles.\n  - The manufacturer's CAD or planner exports (STEP, FBX, OBJ), converted to GLB in Blender, reduced in detail and re-origined to the conventions below.\n  - A 3D artist, commissioned with the conventions below as the brief.\n  - Marketplaces (Sketchfab, CGTrader) for props and appliances only: check the licence allows commercial use in a configurator.\n- My finishes: the colours with their hex values, and texture scans for wood and stone (or CC0 textures from Poly Haven or ambientCG).\n- My markets, currencies, VAT and languages; delivery lead time and installation.\n- Do I want a printable quote, a shareable link to a kitchen, and an in-house tool for staff to photograph products and compose the ready-made kitchens?\n- Do I make a printed or PDF catalogue today (a yearly catalogue, price lists for dealers)? Which pages, in which languages and currencies?\n- Do I want an AI concierge that builds the kitchen from a chat, and which model provider do I have a key for? Should it be open to everyone or limited per visitor?\n- Should AI agents (ChatGPT, Gemini and the like) be able to find my kitchens and start a checkout for a shopper?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\n- Pieces:\n  - `kitchen-component`: `material` relation (up to 10 finishes), `requires` and `excludes` relations to the nine part shapes, a specifications table.\n  - `variant-3d`: a `model` files component accepting `model/gltf-binary`.\n  - `dimensions`: width, height, depth, length (cm).\n  - `assembly-delivery`: assembly text, lead time in days, installation available, warranty in years, downloads.\n  - Appliance specs, one piece each: `appliance-oven` (capacity, cavity width, energy label, pyrolytic, steam), `appliance-hob` (type, zones, phases, bridge zone), `appliance-hood` (mounting, extraction, noise, recirculation, duct), `appliance-cooling`, `appliance-dishwasher`.\n  - `seo`, and landing blocks: `lp-banner`, `lp-gallery`, `lp-video`, `lp-product-slider`, `lp-story-slider`.\n- Nine part shapes (products), all with description, short description, `kitchen-component`, a `brand` relation and `assembly-delivery`, and the variant components `variant-3d` and `dimensions`: **base-cabinet** (unit type: standard, drawer, sink, corner), **wall-cabinet**, **tall-cabinet**, **front** (style: flat, shaker, glass; variants are the finishes), **countertop**, **handle** (bar, knob, recessed), **appliance** (type, available brands, and an `appliance-spec` component choice of the five spec pieces), **sink**, **faucet**.\n- **setup** (product, a ready-made kitchen): description, USPs, eight repeatable category chunks (base, wall and tall cabinets, fronts, countertops, handles, appliances, sinks and faucets), each with `allowed` and `default` relations and min and max; a `starting-point` chunk, one row per part in run order with the variant named on the relation; a `room` chunk (layout straight or L left or right, floor, wall, environment, depth, island and its length and walkway, backsplash); `daylight` (sun azimuth, elevation, intensity, sky, colour temperature); `view` (render mode and camera); repeatable `openings` (window or door, wall, offset, size, sill, hinge) and `lights` (ceiling or pendant, position, intensity, colour temperature); seo. Its own variant is priced 0.\n- **material** (document): description, a hex `color`, finish type (matte, gloss, wood, stone, metal, ceramic), images (the swatch).\n- **brand** (document), **story** (document: intro, hero, paragraphs, featured products and setups, seo), **landing-page** (document with a multiple-choice of the landing blocks), **category** (folder).\n- Catalogue: `/setups`, `/products/{base-cabinets,wall-cabinets,tall-cabinets,fronts,worktops,handles,appliances,sinks-and-taps}`, `/materials`, `/brands`, `/stories`, and `/home` for the landing page.\n- Topic maps: colour (white, cream, beige, grey, green, blue, red, brown, black, wood), style, layout (straight, L-shape, island), collection (the front ranges), media type and source (for images).\n- Pricing: gross shelf prices, one price variant per currency: `default` (EUR), `nok`, `sek`, written from one EUR list price with shelf rounding. Worktops are priced per centimetre.\n- Markets: international and the Netherlands on EUR at 21 % VAT, Norway on NOK and Sweden on SEK at 25 %. One language per market: en, nl, no, sv.\n- Stock: one online warehouse. Order pipelines: Orders (new, processing, shipped, delivered) and Quotes (sent, accepted, lost).\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo is one invented maker, Tessel, and one store: 29 products with 53 variants (cabinets, three front ranges in their finishes, four worktops, two handles, five appliances, two sinks and a tap), 28 materials and six setups, in English, Norwegian, Swedish and Dutch. All of it lives as data in the repo and is the single source for the tenant.\n- **Seed pipeline:** a tenant script sets languages, price variants, VAT types, the stock location, pipelines and markets (no mass-operation intent covers them). Everything else is mass operations: pure generators turn the data into numbered operation files (model, taxonomy, catalogue folders, materials, products and relations, setups, translations per language, swatches, stories, landing page), which are committed and replayed in order, stopping at the first failed operation. A check proves the committed files equal the generators' output; a dry run validates without touching the tenant. Translations are updates of the same items by id in each language, with the complete component lists and only the text swapped.\n- **3D models:** 41 GLBs (1.1 MB together) generated in Python with trimesh by `scripts/generate_glbs.py`, uploaded once with their keys cached, then attached to the variants by the operations.\n- **The conventions my models must follow:**\n  - GLB (binary glTF 2.0), in metres, Y up; the wall at z = 0 and the cabinet toward +z.\n  - The origin at the back-left-bottom corner.\n  - Named materials swapped at runtime: `Front` and `Handle` take the chosen finish, `Carcass` stays neutral.\n  - Named meshes for what moves: doors (with their stack and index, which decides the hinge side), drawers and handles, so doors open in 3D.\n  - The fixed heights in one table shared by the model generator and the layout (base 87 cm, worktop 4 cm, wall cabinets from 145 cm, tall 215 cm).\n- **Swatches:** one deterministic 512 px image per material from its hex (wood and stone tinted from CC0 Poly Haven textures), uploaded to the material and also used as the front's texture in 3D.\n- **Imagery:** 15 marketing and story images made with Magnific (Seedream), each from a studio render of a setup passed as the reference, reviewed by hand for text, logos and faces, then resized and uploaded once by hash. Every variant and setup also has a studio render, made by the studio tool in a batch.\n- HDRIs, floor and wall textures and props are CC0 from Poly Haven.\n- From me: my range as parts with their rules and prices, my ready-made kitchens, 3D models (or a plan to get them), finishes with hex values and texture scans, and photos or renders.\n\n## Step 5: the storefront\n\n- Next.js (App Router) with react-three-fiber, three.js and zustand. Every page sits under a language prefix, and the language picks the market, the price variant, the currency and the VAT rate; middleware sends a bare URL to the language from a cookie, then `Accept-Language`, then English.\n- **Discovery API** for the catalogue: one `browse` over the nine part shapes and `setup` builds the configurator's snapshot (products, variants, dimensions, GLB keys, rules, setups), cached per language. The **Catalogue API** reads stories and the landing page by path, and the draft tree for the App's preview.\n- **The configurator:** one store holds the kitchen as an ordered list of cabinets plus one pick per singleton category (front, worktop, handle, sink, tap, appliances). A pure layout function turns that into placed boxes in metres; the 3D scene and an SVG plan both draw from the same boxes, so they never disagree. The plan is also the editor for cabinet order, windows, doors and lights. Requires and excludes come from the catalogue relations, so the rules are data, not code.\n- **Setups priced from their parts:** a setup's own variant is priced 0; its \"From\" price is the sum of its starting point's parts in the market's price variant. The configurator recomputes the price on every change (fronts and handles per cabinet, worktop per centimetre of run), and the cart is built from the same bill of materials, with a test keeping the two equal.\n- **Shop API** for everything the customer does: carts are hydrated per market (`selectedVariantIdentifier`, prices with tax included, the market's `taxRate`) and carry the market in their meta, so the order keeps its currency; checkout sets the customer, places the cart and creates the order. A shared kitchen is a cart of type `wishlist` with the configuration in its meta, which the Shop API keeps without a database of your own. A quote is a printable page of the same bill of materials.\n- **Studio and setup editor** (`/studio`, `/editor`): staff tools opened as custom views in the Crystallize App. The studio photographs a variant or a whole setup on a neutral stage (or through an AI pass) and uploads the image onto the variant; the editor composes a setup in the configurator and saves it to the item's draft, then publishes. Only these use the **Core API**, on the server, behind a `STUDIO_KEY` that the App passes once and the site turns into a cookie; without the key they are closed in production. They are framed only by `app.crystallize.com`.\n- **PDFs:** the ready-made kitchens as one catalogue (`/<lang>/setups.pdf`: cover, contents and a page per kitchen) and each kitchen alone (`/<lang>/configure/<kitchen>.pdf`, with what can be swapped), rendered on the server with `@react-pdf/renderer` from the same Discovery snapshot and the same bill of materials as the configurator, in the market's currency and the brand's fonts (TTF files bundled with the route). PDFs can't embed WebP, so photos are taken in their original format from the image's own variant ladder. Cached for an hour at the edge.\n- **The concierge:** a chat docked in the configurator, on the Vercel AI SDK with Claude. The server route gives the model this kitchen's catalogue (SKUs, prices, sizes, rules) and the kitchen as it stands, sent by the browser with every message; the tools (add, move, swap or remove a cabinet, choose a finish, set the layout, room and island position, add windows and doors, open another kitchen) have no server side: they run in the browser on the configurator's own store, so every change obeys the same rules, shows in 3D at once and can be undone, and each tool answers with the kitchen's new state. Rate-limited per visitor; an optional free-prompt cap is counted in a signed cookie. Env: `ANTHROPIC_API_KEY`.\n- **For search engines and AI agents:** every page has a Markdown twin (`.md`, or `Accept: text/markdown`), `llms.txt`, JSON-LD (Product, ProductGroup, Offer, BreadcrumbList), product feeds per market, and agent checkout over the Agentic Commerce Protocol and the Universal Commerce Protocol: an agent's checkout session is a Shop API cart, and the shopper finishes on the store's own checkout page.\n- Seed scripts use the Core API too: tenant settings (languages, price variants, VAT types, stock location, pipelines, markets) and file uploads.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_TENANT_ID`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, `STUDIO_KEY`, and optionally an image model key for the studio's AI pass.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Discovery serves published items only, and a setup's allowed and default relations index against what was published at that moment: publish the parts before the setups.\n- Mass operations store every showcase hotspot at 0,0: leave hotspots out of gallery blocks sent that way.\n- The Catalogue API doesn't resolve path aliases, and a translated item's path follows its translated name: read structure and paths from English and lay the translated text over it by item id.\n- A Shop API hydrate merges identical SKUs into one line, so the run order of the cabinets can't be rebuilt from cart lines: keep the configuration itself in the meta.\n- Prices are gross in Crystallize: tell the hydrate so and pass the market's tax rate, or a NOK cart splits its VAT at the wrong rate.\n- The model's mesh and material names are a silent contract with the scene: rename one and doors stop opening or fronts stop taking their finish, with no error. Test them.\n- The studio waits on animation frames, which a background tab doesn't draw: run its batches in a visible window.\n- A replay of the setups overwrites what the editor saved, and an image uploaded after the last cache refresh is overwritten by a replay: refresh the caches from the tenant first.\n- Files can't go through mass operations: upload them, then attach the key.\n- A PDF renderer needs real TrueType fonts (not WOFF), and a serverless bundle may leave the renderer's own font files behind: require them explicitly and bundle the TTFs with the route.\n- The concierge's tools must run on the same store as the panels, never a copy: then it cannot build a kitchen the rules don't allow, and the camera should reframe when a turn has reshaped the room.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"troll-universe","name":"Troll Universe","url":"https://troll.superfast.shop","industry":"Browser game · B2C digital goods","thesis":"A 3D adventure game in the browser where the shop is part of the world: access to the game, cosmetics, gear and bundles are Crystallize products, sold by merchants in five worlds of Norwegian nature and folklore and bought through real carts and orders.","color":"#6effc8","onColor":"#03070a","stats":[{"value":"5","label":"worlds"},{"value":"43","label":"items for sale"},{"value":"4","label":"merchants"},{"value":"3","label":"languages"}],"features":["A web shop inside the game","Deals and bundles as price variants","Try on before you buy","Gifts to other players"],"audience":["b2c"],"sector":"media","capabilities":["digital-goods","sale-pricing"],"payments":[],"currencies":["USD"],"languages":["EN","NO","SV"],"topic":"In-Game Store & Digital Goods for a 3D Browser Game","description":"Game commerce demo: a 3D browser game where access, cosmetics, gear and bundles are Crystallize products, with weekly deals, try-on, gifts and real orders.","page":"https://superfast.shop/demos/troll-universe","screenshot":"https://superfast.shop/shots/troll-universe.jpg","preview":"https://superfast.shop/previews/troll-universe.webp","icon":"https://superfast.shop/icons/troll-universe.svg","llms":null,"promptUrl":"https://superfast.shop/demos/troll-universe/prompt.md","prompt":"# Build my own browser game store on Crystallize, like Troll Universe\n\nI want a storefront for my business that works like **Troll Universe** (https://troll.superfast.shop), a demo store built on Crystallize: A 3D adventure game in the browser where the shop is part of the world: access to the game, cosmetics, gear and bundles are Crystallize products, sold by merchants in five worlds of Norwegian nature and folklore and bought through real carts and orders.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: digital goods, sale price lists. It is B2C, in English, Norwegian, Swedish; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- What kind of game is it, and what do I sell: access to the game as a one-time purchase, optional items inside it (cosmetics, gear, skills that change the gameplay, emotes), bundles, or all of these?\n- My rules for selling inside a game. The demo's are: a fixed price on everything, no in-game currency, never random packs, and the game is complete without the optional items. Do I keep them?\n- Which items come in variants (a beanie or a sweater in several colours, each with its own price and image)?\n- How do I mark down: weekly deals on single items, bundles priced below the sum of their parts, or both?\n- Who sells in the game: one shop behind a key, or merchants standing in the world who each carry part of the range?\n- May players buy for each other (a gift to a player nearby)?\n- My currency or currencies, VAT for digital goods per market, and languages.\n- Is there an age limit (the demo is 18+ and asks for confirmation at sign-up)?\n- Where do accounts, saves and multiplayer live? Crystallize keeps the customers, carts and orders; it is not the game's account system. The demo uses Supabase for sign-in by magic link, saves and realtime.\n- My payment provider. The demo's payment is a mock: the carts and orders are real, but no money is charged.\n- Where do the product images come from? The demo renders them from the game's own models and effects, so what you buy is what you get.\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\n- **folder** (folder): `description` (rich text, translated). The tree is the game → Access, and the shop → one folder per category (lanterns, companions, outfits, trails, campfire, fishing, boat, headwear, gear, camp, emotes, bundles). Each folder has an external reference `<game>/<key>`, and each product its SKU, so the setup can be run again without making duplicates.\n- **digital-item** (product), one shape for everything sold:\n  - `summary` (single line, translated, up to 200 characters).\n  - `category` (selection, exactly one): access, lantern, companion, outfit, trail, campfire, fishing, boat, hat, gear, camp, emote, bundle.\n  - `image` (one image, with alt text per language).\n  - `game-effect` (single line): JSON the game reads to know what the item does, such as `{\"type\":\"lantern\",\"color\":\"#4dffc8\",\"glow\":\"#58d8ff\"}`. An item with variants adds `variants: { <sku>: {…} }` with what differs per colour.\n  - `merchants` (selection, multiple): which merchants in the world carry the item.\n  - `includes` (item relations to `digital-item` variants, up to 12 SKUs): what a bundle contains.\n- Variants: one per colour on the items that have colours, each with its own SKU, price, image and a `color` attribute. Everything else has one variant.\n- Pricing: gross prices, two price variants in one currency: `usd` (the regular price, always set) and `usd-sale` (set only on weekly deals and on bundles). No stock: the goods are digital and owned once.\n- Languages: English (default), Norwegian and Swedish; names, summaries and alt texts in all three.\n- Customers: the e-mail address is the identifier, with meta for the game account's id and the age confirmation.\n- Orders: type standard, one line per item (type `digital`, with image and discount), and cart meta for where it was bought (the landing page, the shop, a named merchant), the language, and the recipient when it is a gift.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo is one game, Troll Universe: five worlds of Norwegian nature and folklore, access as a one-time purchase, and a shop of cosmetics (lanterns, companions, outfits, headwear, trails), gear, a camp, emotes, three skills that change the gameplay (a campfire, a fishing rod, a rowboat) and two bundles. The product list lives as one file in the repo and seeds the tenant.\n- Product images are 1024 × 1024 WebP, one per variant, rendered on a small page of their own with the game's real models and effects, uploaded once and remembered by key.\n- The game's own models are built by scripts in Blender and compressed with gltf-transform; textures are CC0 from Poly Haven and ambientCG; all sound is generated in the browser.\n- From me: the game, the list of what I sell with prices and what each item does in the game, which merchant carries what, the bundles, and an image per item and colour.\n\n## Step 5: the storefront\n\n- A static front end (the demo is Three.js and Vite; any game engine that runs in the browser works) with server functions beside it. Every Crystallize call is made on the server: the browser never holds a Crystallize token, and price, total, discount and ownership are never decided in the browser.\n- **The catalogue (Catalogue API or Discovery):** one public server route returns the whole shop in the player's language, flattened to what the game needs per item: SKU, name, price, compare-at price, currency, category, summary, effect, image with its ready-made sizes, merchants, bundle contents and variants. The price shown is the sale price when it is set and lower than the regular one; the regular price is then the compare-at. Cache it for a minute in memory and at the CDN. The landing page lists the same items from the same route.\n- **The shop in the game:** categories, search, filters (owned, not owned, on sale), sorting, product pages with the colour choice, a basket of up to 12 items, \"This week's offers\", \"Others also bought\" and \"My purchases\". A merchant in the world opens the same shop filtered to the items whose `merchants` selection names them.\n- **Try on:** the product page puts the item on the player's own character from its `game-effect` before anything is bought, and takes it off again.\n- **Checkout (Shop API):** the server checks the player's session, that every SKU exists in the catalogue, that it isn't already owned and that a bundle's contents aren't partly owned; upserts the customer; then `hydrate` with lines `{ sku, quantity: 1, type: 'digital' }` and `context.price { currency, decimals: 2, pricesHaveTaxesIncludedInCrystallize: true, selectedVariantIdentifier: 'usd-sale', fallbackVariantIdentifiers: ['usd'], compareAtVariantIdentifier: 'usd' }`, so Crystallize computes the discount per line and on the total. Check that every line made it in and the total is above zero, then `place`. The payment window shows exactly what came back.\n- **Order (Shop API):** a JWT from `/auth/token` on the server (scopes `cart`, `order`), then `/order` `createFromCart` with the payment. The order id is the cart id: read the order first, create it, and on failure read again, so one cart never becomes two orders. Before that, check that the cart's customer and its meta both belong to the signed-in player and that the cart is placed.\n- **From order to inventory:** the SKUs on the order, plus the contents of any bundle, are added to the player's entitlements in the game's account store, where only the server can write. The game equips an owned item per slot and interprets its `game-effect`; a skill that changes gameplay is checked on the server where it matters (a catch is only accepted from a player who owns the fishing rod).\n- **Gifts:** the buyer picks a player nearby; the recipient is named by an encrypted code, never a raw account id. The cart carries the recipient in its meta, the order belongs to the buyer, and the entitlement goes to the recipient.\n- **My purchases:** the player's orders from the Shop API by customer identifier, read on the server with the identifier from the session: date, lines with image, price, discount and whether it was a gift.\n- **Others also bought:** counted from recent orders (which SKUs the same customers bought) in a cached server job on the Core API, never per page view.\n- **Setup:** one script that can be run again creates the languages, price variants, shapes, folders, products and variants on the Core API, uploads the images, sets them on both the component and the variant (so cart and order lines carry an image), and publishes every item in every language. After setup, Crystallize is the source of truth for names, prices and content.\n- With a real payment provider: create the payment session from the placed cart, and let the provider's verified webhook create the order and grant the entitlements. The browser must never be able to trigger an entitlement.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_TENANT_ID`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, on the server only, plus those of the account system and the e-mail sender.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- Keep ownership out of the browser and out of the save file: a purchase is an order in Crystallize and an entitlement only the server can write.\n- A bundle grants its contents, not itself: resolve `includes` when the order is created, and refuse a bundle when the player already owns part of it.\n- Make every step safe to repeat: the same cart paid twice must give one order and no new entitlements.\n- Weekly deals are a sale price variant, not a promotion: the product page can show was and now, and the cart computes the same discount.\n- Never leave the regular price empty: it is the fallback and the compare-at price.\n- Set the image on the variant as well as on the component, or cart and order lines come back without one.\n- Paths differ per language, so find folders by shape or external reference rather than by path.\n- Publish every item in every language, then the tree; a language published without content gives items with no name.\n- A purchase announced to other players is only a message. What they see on a character must come from the server-confirmed profile.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"block-builder-universe","name":"Block Builder Universe","url":"https://block-builder.superfast.shop","industry":"Building blocks · B2C","thesis":"A 3D block builder where the build is the basket: every block placed is a real variant in Crystallize, so a model made by hand, from a ready-made set or by an AI from a sentence is priced as you build and ordered as exactly the blocks it is made of.","color":"#c4281c","onColor":"#ffffff","stats":[{"value":"1,056","label":"blocks to build with"},{"value":"62","label":"shapes"},{"value":"20","label":"colours"},{"value":"8","label":"ready-made sets"}],"features":["The build is the basket","An AI that builds from a sentence","Building instructions as a PDF","A gallery of shared builds"],"audience":["b2c"],"sector":"toys","capabilities":["configurator","ai-agent","variants"],"payments":[],"currencies":["EUR"],"languages":["EN"],"topic":"3D Brick Builder with AI Model Generator","description":"Building blocks ecommerce demo: a 3D brick builder where every block is a product variant, an AI that builds a model from a sentence, and instructions as a PDF.","page":"https://superfast.shop/demos/block-builder-universe","screenshot":"https://superfast.shop/shots/block-builder-universe.jpg","preview":"https://superfast.shop/previews/block-builder-universe.mp4","icon":"https://superfast.shop/icons/block-builder-universe.svg","llms":null,"promptUrl":"https://superfast.shop/demos/block-builder-universe/prompt.md","prompt":"# Build my own building blocks store on Crystallize, like Block Builder Universe\n\nI want a storefront for my business that works like **Block Builder Universe** (https://block-builder.superfast.shop), a demo store built on Crystallize: A 3D block builder where the build is the basket: every block placed is a real variant in Crystallize, so a model made by hand, from a ready-made set or by an AI from a sentence is priced as you build and ordered as exactly the blocks it is made of.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: product configurator, ai concierge, large variant sets. It is B2C, in English; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- What do I sell piece by piece, and what makes two pieces different products: shape, size, colour, finish? Do all colours of a shape cost the same?\n- How do my pieces join? The demo's are blocks on a grid of studs; anything that snaps together on a grid (tiles, beads, modular parts) works the same way. What may float, and what must rest on something?\n- Which ground does a build stand on: one plate in a few sizes, or several laid out as a town, some with roads?\n- Do I want ready-made sets to start from, and a public gallery of builds people have shared?\n- Do I want an AI that builds a model from a sentence or a picture? Which provider do I have a key for, and should it be open to everyone or limited per visitor?\n- What should a buyer get besides the pieces: building instructions and a pick list as PDFs, a share link, a picture of the build?\n- My currency, VAT and where I ship. The demo is one market in EUR, and its payment is a mock.\n- May I use the shapes I have in mind? Check that a piece's design isn't protected before it goes in the catalogue, and keep other makers' names and brands out of the store.\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\n- **block** (product): one product per shape (a 2 × 4 brick, a 1 × 2 slope), in a **category** folder, with a `block-spec` piece: its `shape` (box, tile, slope, curve, arch, round, window, door and so on), width, length and height on the grid, and weight. One variant per colour, with a `colors` component (`label`, `hex`), a `Finish` attribute for see-through colours, its own SKU, image and price.\n- **baseplate** (product): the ground, with its size and layout (plain, or a road running through it).\n- **set** (product): a ready-made build, with a description and its whole design packed into one `design` rich text. Priced from its blocks, never on its own.\n- Topics for the gallery: a build theme (animals, buildings, vehicles and so on) and a size, set on the build's picture.\n- Pricing: one price variant per currency. An Orders pipeline whose first stage takes new orders.\n- A design is small and plain: `{ plate, blocks: [{ sku, x, y, z, r }] }`, with positions on the grid and `r` in quarter turns. It is the build, the basket and the bill of materials at once.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo is 62 shapes in 20 colours (1,056 variants), eleven baseplates (seven plain, four with roads) and eight sets, from a cottage of 294 blocks to a town on nine plates. All of it is generated from one file in the repo into mass operations: model, catalogue, publish, sets, topics.\n- The sets are written as rules in code (walls, roofs, trees) rather than block by block, and the seed packs each result into the set's `design`.\n- Product images are rendered by the app itself from the same geometry the builder draws, one per variant, then uploaded and attached by key.\n- From me: my pieces with sizes, colours and prices, the sets I want to start with, and the rules for how pieces join.\n\n## Step 5: the storefront\n\n- A single-page app (the demo is Vite, React and three.js) with a small server beside it. The browser only talks to that server; every Crystallize token stays there.\n- **One engine, three places:** the grid, collisions, what grips what, the bill of materials and the price live in one plain JavaScript file with no imports, run by the browser, the server and the tests. Geometry is generated from a shape's dimensions, so there are no model files.\n- **The catalogue (Discovery API):** one query builds a snapshot of every shape, colour, baseplate and set, cached on the server. The palette, the prices and the sets all come from it.\n- **The build is a cart (Shop API):** a moment after each change the server hydrates the same cart with one line per SKU. An order re-validates the design, re-prices it with the engine and refuses if the Shop API total differs; then it completes that cart.\n- **Sharing:** a shared build is a Shop API cart of type `wishlist` with the design in its meta, opened at `/b/<id>`. A gallery build is the same with a name and a theme, and its picture in the media library, tagged with the theme topic and carrying the build's id and numbers in its meta. The gallery lists those pictures; it never reads the wishlists.\n- **PDFs:** building instructions layer by layer and a pick list, drawn on the server from the design as flat isometric shapes, with a small PDF writer of its own.\n- **The AI builder:** the model never names a SKU or places a block. It writes a plan of coarse parts (a box, a roof, a cylinder, an outline pushed through a thickness, each with a colour and a size), and a compiler fills the parts with real blocks, staggers the joints and drops what would float. The model is told once what did not hold and writes parts to carry it. A slower mode writes three drafts, picks the best from pictures of them, then looks at the build twice and corrects it. Structured output, streamed, so blocks appear as the plan arrives. Env: `ANTHROPIC_API_KEY`, server only.\n- **Usage and admin (optional):** one record per AI build, share and order, kept in a small Redis store, with a password-protected page that lists them.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, on the server only.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- The Catalogue API can come back with no variants for these products: read the catalogue from Discovery, and ignite Discovery again after the content model changes.\n- A cart merges identical SKUs into one line, so the build can't be rebuilt from cart lines: keep the design itself in the meta.\n- Never trust the browser's price or design: validate and price on the server with the same engine, and compare with the Shop API total.\n- Don't let the model place blocks one by one: it loses count and builds things that float. Let it describe parts and let code do the bricks.\n- A long AI build needs a function that may run for minutes and a stream that reports progress; set the limit and show what is happening.\n- Listing a gallery from Core on every visit runs into rate limits: read it on the server every few minutes and cache it.\n- Rendering product pictures and the 3D scene stops in a background tab: run the batch in a visible window.\n- Files can't go through mass operations: upload them, then attach the key.\n- Records of who built what hold IP addresses and prompts: tell visitors, and decide how long to keep them.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"},{"id":"pos-universe","name":"POS Universe","url":"https://pos-universe.superfast.shop","industry":"Point of sale · physical retail","thesis":"A complete till in the browser on the store’s own Crystallize catalogue: choose a grocery, fashion or tools store, sign in with a fingertip, scan with the camera, and the customer display sells the item just scanned — offline, on hardware the store already owns.","color":"#5b2bb5","onColor":"#ffffff","stats":[{"value":"3","label":"stores, one till"},{"value":"1,373","label":"products to sell"},{"value":"4","label":"languages"},{"value":"0","label":"drivers to install"}],"features":["Sign in with Touch ID","The camera is the scanner","A customer display that sells","Keeps selling offline"],"audience":["b2c"],"sector":"retail","capabilities":["point-of-sale","click-and-collect","stock-per-location","campaigns","ai-agent"],"payments":[],"currencies":["EUR"],"languages":["EN","NO","SV","NL"],"isNew":true,"topic":"Point of Sale (POS) Demo on a Headless Commerce Catalogue","description":"Browser POS demo on Crystallize: grocery, fashion and tools stores, Touch ID staff login, camera scanning, a customer display that sells, offline sales.","page":"https://superfast.shop/demos/pos-universe","screenshot":"https://superfast.shop/shots/pos-universe.jpg","preview":"https://superfast.shop/previews/pos-universe.mp4","icon":"https://superfast.shop/icons/pos-universe.png","llms":null,"promptUrl":"https://superfast.shop/demos/pos-universe/prompt.md","prompt":"# Build my own point of sale store on Crystallize, like POS Universe\n\nI want a storefront for my business that works like **POS Universe** (https://pos-universe.superfast.shop), a demo store built on Crystallize: A complete till in the browser on the store’s own Crystallize catalogue: choose a grocery, fashion or tools store, sign in with a fingertip, scan with the camera, and the customer display sells the item just scanned — offline, on hardware the store already owns.\n\nThe content model and the wiring below are proven in that store: keep them for the features I want, and rename things to fit my trade. If a feature doesn't fit my business, leave its parts out rather than forcing it. Everything I see is mine to decide: brand, colours, typography, layout, copy and pages. Don't copy the demo's look; build my brand.\n\nWhat the demo does: point of sale, click and collect, stock per location, campaigns, ai concierge. It is B2C, in English, Norwegian, Swedish, Dutch; my markets and languages may differ.\n\n## How to work with me\n\n- You need to run commands, edit files and use MCP tools. If you can't (for example in a plain chat), tell me, and suggest I paste this prompt into Claude Code, Cursor or another coding agent instead.\n- Ask your questions a few at a time (three to five), in the order of the steps below, and suggest a sensible default for each so I can just say yes. Ask each thing once, even where two lists overlap, and ask the ones that change the build first. Don't start a step before the one before it is done.\n- The technical detail below is for you. Talk to me in plain words, and explain a choice only when I need to make it.\n- Keep the folder empty until step 5, apart from `.env.local`.\n- Never ask me to paste an access token secret into the chat, and tell me not to paste a filled-in command or its output either. Tell me which command to run or which file to put it in, and I'll do it.\n- Before you create or change anything in Crystallize, show me the plan (shapes, pieces, topics, price variants, markets) and wait for my yes. Before an import, show me a sample of five items.\n- Use the Crystallize skills (the `skills` MCP tool, or the use-crystallize plugin) for how-to: `content-model`, `information-architecture`, `taxonomy`, `data-creation`, `mass-operations`, `pricing`, `query`, `mutation`, `js-api-client`, `responsive-images`, `payments`, and when this store needs them `subscriptions`, `bookable-resources`, `vector-ranking` and `permissions`. Where this prompt and a skill disagree, the skill wins. Check the live schemas with the MCP instead of guessing field names.\n- When something I asked for isn't possible in Crystallize the way I pictured it, say so and offer the closest thing that is.\n- **The storefront never reads from the Core API on customer-facing pages.** Products, public prices, stock and content come from the Discovery API and the Catalogue API; confidential prices (negotiated B2B price lists) from the Catalogue API on the server.\n- **Use the Shop API for everything about customers:** carts, checkout, customers, orders (including orders created directly, such as renewals), order meta, pipeline stages, payments and subscription contracts. Take the customer identifier from the server session, never from the browser. The Core API is for setup scripts, back-office tools, scheduled jobs and what the Shop API can't do, always on the server and never once per page view.\n\n## Step 1: Crystallize tenant, tokens and MCP\n\nAsk me:\n1. Do I have a Crystallize account and a tenant for this store? If not, I sign up for free at https://app.crystallize.com/signup and create an empty tenant. Ask for its **tenant identifier**.\n2. Do I have access tokens? If not, tell me: in the Crystallize App, go to **Settings → Access Tokens → Generate a new token**, name it, and keep the ID and the secret (the secret is shown once). A token acts with its user's role, so while we build, that user needs to be Tenant Admin (or have a role that can write shapes, pieces, topics, items and prices and run mass operations). Once the store is built, switch the MCP to a token from a read-only user.\n3. Is the Crystallize MCP connected, with write access? Check that you have its write tools (`run-mass-operation`, `mutate-core`), not only the read tools (`tenant-overview`, `fetch-content-model`): with only the read tools, it was added without `?exposeWrite=true`. If it's missing, have me run this in my terminal with my own token, then restart you:\n   ```bash\n   npx add-mcp \"https://mcp.crystallize.com/mcp?exposeWrite=true\" \\\n     --header \"X-Crystallize-Access-Token-Id: <token id>\" \\\n     --header \"X-Crystallize-Access-Token-Secret: <token secret>\"\n   ```\n   If the MCP can't be connected in my tool, say so and fall back to scripts that call the Crystallize APIs with the tokens from `.env.local`.\n   In Claude Code, also suggest the Crystallize skills: `/plugin marketplace add crystallizeapi/ai`, then `/plugin install use-crystallize@crystallize-ai`. The plugin brings its own read-only Crystallize MCP entry with placeholder credentials: keep using the one added above (and add `&exposeSkills=false` to its URL once the plugin's skills are installed).\n4. Confirm the connection with `tenant-overview`, and tell me what's already in the tenant. If it isn't empty, ask whether to build next to what's there or in a fresh tenant.\n\nThe storefront needs the tenant identifier and its own credentials: an access token pair for the Shop API (server only), and a static auth token if Discovery and the Catalogue API are secured. Don't reuse the admin build token in production. Put them in `.env.local` (have me fill in the secrets) and make sure `.env*` is in `.gitignore`.\n\n## Step 2: my business and my brand\n\nAsk me:\n- What I sell, who buys it, and what makes us different. My brand name and a one-line pitch.\n- My markets, currencies and languages, and my VAT/tax setup.\n- Where my product data lives today: a spreadsheet or CSV, an export from Shopify, WooCommerce or another platform, a PIM or ERP, my current website, or nothing yet. Where my product images are.\n- My brand: logo (SVG if I have it), colours, typefaces, tone of voice, and two or three sites whose look I like. If I have no brand guide, propose a direction from my answers and show it before building pages.\n- Where it should run: my own domain, and hosting (suggest Vercel for Next.js).\n\nThen ask what is particular to this kind of store:\n\n- Which stores and tills: how many stores, registers per store, and which devices (iPad, Android tablet, phone, PC)? Is there a second screen facing the customer?\n- What do I sell over the counter: weighed fresh produce (PLU codes, tare), deposit on drinks, age-limited items (beer hours), sizes and colours, items sold by the unit?\n- Barcodes: do my products carry GTIN/EAN in Crystallize, and which in-store price or weight barcodes (prefixes 20–25) do my scales print?\n- Payment: cash, a standalone card terminal, Tap to Pay on the phone (which provider: Stripe Terminal or Adyen, and is the agreement in place), mobile payment, gift cards?\n- Receipts: printed (which printer: Epson or Star, USB, Bluetooth or network), digital by QR, or both? Which country's cash register rules apply (in Norway, kassasystemforskriften: electronic journal, X/Z reports, SAF-T Cash Register)?\n- Staff: roles (cashier, manager, admin), which actions need a manager (discounts over a limit, returns, cash drops), and do staff share devices?\n- Stock: one stock location per store, a central warehouse, transfers between stores? Click and collect and ship-from-store?\n- Promotions: multi-buy, percentage off, member prices, coupon codes; are they already set up for the web shop?\n- Does the till have to keep selling when the internet drops (it should)?\n\n## Step 3: the content model\n\nCreate this in my tenant with the MCP, renamed to fit my trade, after I have approved the plan. Keep the structure: it is what the storefront's features depend on.\n\nBuild in dependency order, since mass operations run in sequence with no rollback: pieces and shapes (empty first, then their components), topic maps (at most 30 topics per `topic/create`), price variants, markets and VAT types, the folder tree (two to four levels, five to twelve per level), and only then items. Use lowercase-hyphen identifiers. Decide now which fields are translated and which are discoverable. Restrict every item relation with `acceptedShapeIdentifiers`. Never add price or stock components to a shape: variants have them built in.\n\nThe till sells from the same catalogue as the web shop; nothing is copied into a second product database.\n\n- Products and variants as the web shop has them. The till needs, per variant: SKU, price per price variant (one per currency; say whether prices include VAT), stock per stock location, images, and barcodes (a GTIN/EAN component or an identifiers chunk on the variant, discoverable).\n- Grocery facts where they apply: sold by weight with price per kg and tare, deposit per unit, age limit, organic flag, PLU code, storage (ambient, chilled, frozen), allergens. Keep them as typed components so the till can read them.\n- **POS screens as Crystallize grids:** one grid per till screen, laid out by the merchandiser with column and row spans; product cells become keys, folder cells drill in. Without grids, screens are generated from the catalogue folders under a root path, with subfolders as filter chips.\n- Related products (an item relation) and topics, used for \"goes well with\" on the customer display.\n- One stock location per store (and the central warehouse); order pipelines for click and collect (new, picking, ready, collected) and for ship-to-customer.\n- Customers with customer groups (for member prices) and consent fields.\n- Promotions in Crystallize (multi-buy, percentage, member, coupon codes), the same the web shop uses.\n\n## Step 4: my data\n\nImport my products and content with mass operations, mapping my data onto the model: validate each file with `build-mass-operation`, run it with `run-mass-operation` and follow it with `get-mass-operation-status`. Read the operation logs, not just the task status: a task can finish as complete with failed operations, and there is no rollback. Give every upsert a `resourceIdentifier` so a re-run updates instead of duplicating, and chunk large imports. Start with a small batch, check it in the Crystallize App with me, then run the rest.\n\nPublish every imported item in each language: the storefront only sees published versions (in a mass operation, `item/publish` needs real `itemId`s). Import images from URLs with `copyRemoteAsset`, or upload them (presigned upload, then `registerImage`). Renditions are generated in a queue: after a large import, republish once they exist, and have the storefront fall back to the original image URL while `variants` is empty. Before building pages, query Discovery once; if the tenant isn't ignited yet, run `igniteDiscoApi` (with `stacks: opensearch` if we use vector ranking), wait for the task to complete, and allow a few minutes. If I have no data yet, create a small, clearly labelled placeholder set (ten or so products) so we can build the pages, and remind me to replace it.\n\n- The demo sells from three existing Crystallize tenants, food-universe (grocery, with three POS grids), fashion-universe and tools-universe (screens from folders), in EUR. No product data was made for the till.\n- From me: my catalogue in Crystallize with prices, stock per store and barcodes; the stores, registers and staff; my payment provider and printer; my promotions.\n\n## Step 5: the storefront\n\n- Next.js (App Router), a PWA with a service worker; every register setting is per device (tenant, catalogue language, price variant, VAT mode, stock location, root path, grid ids, store, register id).\n- **Catalogue API** for grids and folder screens, and **Discovery** for search and barcode lookup (`sku` or GTIN `equals`). The till keeps the whole catalogue offline: a server route builds one bundle (products, screens, folders) with a content hash; the browser stores it in IndexedDB, re-checks the hash every 15 minutes, and caches the images in Cache Storage. Every scan and search hits the local copy first.\n- **Orders through the Shop API:** a server route gets a Shop API token with the `order` scopes from an access token pair, then creates the order with its payments, the store's stock location, and meta (sale id, register, receipt number, cashier). Sales are written to the device's journal first and synced when online; retries look up the sale id first so a lost response never creates two orders. Returns are credit notes related to the original order.\n- **Journal on the device:** an append-only IndexedDB journal with a hash chain, X and Z reports and SAF-T Cash Register export. Never let the server be the only copy of a sale.\n- **Staff:** PINs hashed per device; passkeys (WebAuthn with the platform authenticator, user verification required) verified on the device with WebCrypto: challenge, origin, rpId hash, the UP and UV flags, the signature and the sign counter. Manager override offers only the passkeys of staff allowed to approve.\n- **Hardware from the browser:** camera scanning with `BarcodeDetector`, and a WASM decoder (zxing) where it is missing; receipt printers and the cash drawer over WebUSB, Web Bluetooth or Web Serial with a small ESC/POS encoder.\n- **Customer display:** a second window fed over a `BroadcastChannel` (or a local relay for a separate tablet): the lines, the item just scanned with its image and allergens, related products, and a QR for the digital receipt (a signed, short-lived token that carries the receipt itself, so no database is needed).\n- **Tap to Pay:** Stripe Terminal (server-driven) or Adyen's cloud Terminal API behind server routes, with the sale reference as the idempotency key; a phone as the reader needs a native shell (Capacitor) and the Apple Tap to Pay entitlement.\n- **Assistant (optional):** an LLM with tools over the catalogue search, product details and stock; every product it shows is validated against the tool results and filled from the catalogue. Without a key, a local keyword, price and allergen search.\n- Env: `CRYSTALLIZE_TENANT_IDENTIFIER`, `CRYSTALLIZE_ACCESS_TOKEN_ID`, `CRYSTALLIZE_ACCESS_TOKEN_SECRET`, `NEXT_PUBLIC_POS_GRID_IDS`, plus `POS_RECEIPT_SECRET` for digital receipts and the payment provider's keys.\n\nBuild my design, not a demo's: my typefaces, colours and tone, with pages that suit my products. Show me the home page, a category and a product page early, and adjust the design with me before building the rest.\n\nEvery page is built for search engines, answer engines and AI agents from the start, not added at the end:\n- **SEO:** server-rendered HTML, one `<h1>`, a unique title and description, canonical URLs, `hreflang` per language, a sitemap and `robots.txt`, and clean, translated paths.\n- **Structured data:** JSON-LD on every page that has a subject: `Organization` and `WebSite` (with a `SearchAction`) on the home page, `Product` with `Offer` (price, currency, availability) on product pages, `BreadcrumbList` on categories and products, `Article` or `Recipe` on content. Generate it from the same data as the page.\n- **GEO, for answer engines and agents:** `/llms.txt` (an H1, a summary, links to every section), `/llms-full.txt`, and a Markdown twin of every page (its URL plus `.md`, or `Accept: text/markdown`), with prices, stock and facts written out in plain sentences.\n- **Agents can act, not only read:** register the store's actions (search, add to cart, check availability) with WebMCP (`navigator.modelContext.registerTool`, or annotated forms), and keep the accessibility tree clean: semantic HTML, labelled controls, real buttons and links.\n- **Fast and stable:** responsive images from Crystallize, no layout shift (reserve space for images and late content), little client JavaScript. Aim for 100 in every Lighthouse category (Performance, Accessibility, Best Practices, SEO; heavy 3D or video pages may score lower on performance) and pass every Agentic Browsing check.\n\n## Step 6: run it, then ship it\n\nRun it locally and walk me through each feature above with my own data. Fix what I find. Before you call it done, run Lighthouse on the home page, a category and a product page (`npx lighthouse <url> --only-categories=performance,accessibility,best-practices,seo,agentic-browsing`), validate the JSON-LD (https://validator.schema.org), open `/llms.txt` and a page's `.md` twin, and fix what falls short. Deploy only when I ask: set the same environment variables on the host, point my domain, and give me the live URL.\n\n## Watch out for\n\n- The till's API routes create orders with the server's credentials: put authentication or a private network in front of them before real keys are used, and lock the credentials to the tenant.\n- Prices: say whether a price variant includes VAT, and round cash to the currency's smallest coin where the law asks for it (Norway: whole kroner).\n- The Catalogue API has no \"list grids\" query: configure the grid ids.\n- Some tenants return an empty `variants` list with a `defaultVariant`: fall back to it.\n- Discovery's schema is generated per tenant (GTIN fields only exist where the tenant has them): fall back to the offline index when a query fails validation.\n- Passkeys need HTTPS and a stable origin; they are per device unless public keys are shared between registers.\n- Cash register law: the journal, receipts and reports are regulated; a product declaration may be needed before real use.\n- Don't invent facts about my business (prices, stock, certifications, delivery promises). Ask, or leave a clearly marked placeholder.\n\nAnd for every Crystallize store, from the skills:\n- **Orders:** create and change them through the Shop API `/order` (`createFromCart`, `create`, `setMeta` with `merge: true`, `addToStage`, `setPayments`). Editing an order in Core adds a duplicate to the Shop API's list. Read a customer's orders with the identifier from the session, `limit: 100`, grouped by `coreId` (keep the newest). Seed order history through `/order` `create`, not Core `registerOrder`. The order id is the cart id; don't call `fulfill` after `createFromCart`.\n- **Payments:** create the order from the payment provider's verified webhook, never from the browser, once per cart.\n- **Cart meta** goes in `hydrate(input: { meta })`; `hydrate` is the whole cart, so send every line each time.\n- **Images:** serve them straight from Crystallize (not through an image optimizer such as `next/image` in its default setup): a WebP `srcset` from the returned `variants` with an accurate `sizes`, falling back to the original URL while renditions are missing. Replace an image by uploading a new one, not with `registerImageRevision`.\n- **Ranking (if the store ranks per shopper):** check that `__type(name: \"RankByInput\")` exists; pass `context` as a variable, with `userTaste` entries of `{ vocabulary, weights, magnitude }` (magnitude = the square root of the sum of squared weights); `tieBreaker` is required; don't combine `sorting` with ranking, and page with `skip` and `limit` inside the rerank window; introspect `TenantRankByField` for field names; leave `context` out when there's no taste; use `explain: true` only for tuning.\n- **Bookings (if the store books):** durations are in seconds; the role needs the `bookingPolicies` permission; put the customer on the cart before `bookSkuItem`; every `hydrate` must resend each booked line with its window and unit, or the booking is cancelled; remove a booking by re-hydrating without it.\n\n## Done when\n\n- My tenant has the content model above with my products, prices and images in it.\n- The storefront runs on my domain in my brand, and every feature I chose works with my data: we have clicked through each one together.\n- Lighthouse scores close to 100 in every category and every Agentic Browsing check passes on the home, category and product pages.\n- I know how to add a product, change a price and publish, in the Crystallize App.\n"}]}