diff --git a/templates/croct/starter/ecommerce-nanostores/README.md b/templates/croct/starter/ecommerce-nanostores/README.md
new file mode 100644
index 00000000..7f3d5a21
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/README.md
@@ -0,0 +1,33 @@
+# Introduction
+
+This template sets up an ecommerce site using [React](https://react.dev/?utm_source=croct),
+[Vite](https://vite.dev/?utm_source=croct), and [Croct Nanostores](https://croct-nano.fryuni.dev).
+
+It demonstrates how to use the [croct-nanostores](https://croct-nano.fryuni.dev) library to add
+personalized banners, product recommendations, and cart tracking to a storefront using
+framework-agnostic [Nanostores](https://github.com/nanostores/nanostores) atoms.
+
+## What's included
+
+- **Announcement bar** — A personalized top-of-page banner managed via a Croct slot.
+- **Hero banner** — A full-width hero section with title, subtitle, and call-to-action.
+- **Product recommendations** — A personalized product grid powered by Croct content rules.
+- **Cart tracking** — Cart state tracked via `trackCart`, triggering automatic content refresh.
+- **Auto-refresh** — Content automatically updates when user behavior changes (e.g., cart modifications).
+
+## Usage
+
+To create a new project using this template, run:
+
+```croct-cmd
+croct use croct://starter/ecommerce-nanostores
+```
+
+## Options
+
+The following options are available for this template:
+
+| Option | Description | Required | Default |
+|-------------------|--------------------------------------------------|----------|-----------------|
+| `name` | The name of the project. | No | `my-croct-shop` |
+| `disableLauncher` | Whether to disable the project launcher. | No | `false` |
diff --git a/templates/croct/starter/ecommerce-nanostores/code/App.tsx b/templates/croct/starter/ecommerce-nanostores/code/App.tsx
new file mode 100644
index 00000000..ec431331
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/App.tsx
@@ -0,0 +1,45 @@
+import { AnnouncementBar } from './components/AnnouncementBar';
+import { HeroBanner } from './components/HeroBanner';
+import { ProductGrid } from './components/ProductGrid';
+import { Cart } from './components/Cart';
+
+export function App() {
+ return (
+
+ );
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/code/components/AnnouncementBar.tsx b/templates/croct/starter/ecommerce-nanostores/code/components/AnnouncementBar.tsx
new file mode 100644
index 00000000..a41dce43
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/components/AnnouncementBar.tsx
@@ -0,0 +1,28 @@
+import { useStore } from '@nanostores/react';
+import { announcementBar } from '../stores/banner';
+
+export function AnnouncementBar() {
+ const { content } = useStore(announcementBar);
+
+ const bar = (
+
+ {content.text}
+
+ );
+
+ if (content.link) {
+ return (
+
+ {bar}
+
+ );
+ }
+
+ return bar;
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/code/components/Cart.tsx b/templates/croct/starter/ecommerce-nanostores/code/components/Cart.tsx
new file mode 100644
index 00000000..8086ec74
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/components/Cart.tsx
@@ -0,0 +1,78 @@
+import { useState } from 'react';
+import { useStore } from '@nanostores/react';
+import { $cartItems, $cartTotal, $cartCount, removeFromCart, updateQuantity } from '../stores/cart';
+
+export function Cart() {
+ const [isOpen, setIsOpen] = useState(false);
+ const items = useStore($cartItems);
+ const total = useStore($cartTotal);
+ const count = useStore($cartCount);
+
+ return (
+ <>
+ setIsOpen(!isOpen)}>
+ Cart ({count})
+
+ {isOpen && (
+
+
+
Shopping Cart
+ setIsOpen(false)}>
+ ×
+
+
+ {items.length === 0 ? (
+
Your cart is empty.
+ ) : (
+ <>
+
+ {items.map(item => (
+
+
+ {item.name}
+
+ ${(item.unitPrice * item.quantity).toFixed(2)}
+
+
+
+
+ updateQuantity(
+ item.productId,
+ item.quantity - 1,
+ )
+ }
+ >
+ −
+
+ {item.quantity}
+
+ updateQuantity(
+ item.productId,
+ item.quantity + 1,
+ )
+ }
+ >
+ +
+
+ removeFromCart(item.productId)}
+ >
+ Remove
+
+
+
+ ))}
+
+
+ Total: ${total.toFixed(2)}
+
+ >
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/code/components/HeroBanner.tsx b/templates/croct/starter/ecommerce-nanostores/code/components/HeroBanner.tsx
new file mode 100644
index 00000000..ab535015
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/components/HeroBanner.tsx
@@ -0,0 +1,18 @@
+import { useStore } from '@nanostores/react';
+import { heroBanner } from '../stores/banner';
+
+export function HeroBanner() {
+ const { content } = useStore(heroBanner);
+
+ return (
+
+ );
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/code/components/ProductGrid.tsx b/templates/croct/starter/ecommerce-nanostores/code/components/ProductGrid.tsx
new file mode 100644
index 00000000..b6ed8682
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/components/ProductGrid.tsx
@@ -0,0 +1,44 @@
+import { useStore } from '@nanostores/react';
+import { recommendedProducts } from '../stores/products';
+import { addToCart } from '../stores/cart';
+
+export function ProductGrid() {
+ const { content } = useStore(recommendedProducts);
+
+ return (
+
+ {content.title && {content.title} }
+ {content.description && {content.description}
}
+
+ {content.cards.map((card, index) => (
+
+ {card.image && (
+
+ )}
+
+
{card.name}
+ {card.description && (
+
{card.description}
+ )}
+
${card.price.toFixed(2)}
+ {card.cta && (
+
+ addToCart({
+ productId: card.name.toLowerCase().replace(/\s+/g, '-'),
+ name: card.name,
+ unitPrice: card.price,
+ })
+ }
+ >
+ {card.cta.label}
+
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/code/index.css b/templates/croct/starter/ecommerce-nanostores/code/index.css
new file mode 100644
index 00000000..4e42907f
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/index.css
@@ -0,0 +1,347 @@
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family:
+ system-ui,
+ -apple-system,
+ BlinkMacSystemFont,
+ 'Segoe UI',
+ Roboto,
+ sans-serif;
+ color: #1a1a2e;
+ background: #fafafa;
+ line-height: 1.6;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+/* Announcement Bar */
+.announcement-bar {
+ text-align: center;
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+}
+
+.announcement-bar-link {
+ display: block;
+}
+
+.announcement-bar-link:hover .announcement-bar {
+ opacity: 0.9;
+}
+
+/* Header */
+.header {
+ border-bottom: 1px solid #e5e7eb;
+ background: #fff;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+.header-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 1rem 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 2rem;
+}
+
+.logo {
+ font-size: 1.5rem;
+ font-weight: 700;
+ letter-spacing: -0.025em;
+}
+
+.nav {
+ display: flex;
+ gap: 1.5rem;
+}
+
+.nav a:hover {
+ color: #6366f1;
+}
+
+/* Hero Banner */
+.hero-banner {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: #fff;
+ padding: 5rem 2rem;
+ text-align: center;
+}
+
+.hero-content {
+ max-width: 640px;
+ margin: 0 auto;
+}
+
+.hero-content h1 {
+ font-size: 3rem;
+ font-weight: 800;
+ line-height: 1.1;
+ margin-bottom: 1rem;
+}
+
+.hero-subtitle {
+ font-size: 1.25rem;
+ opacity: 0.9;
+ margin-bottom: 2rem;
+}
+
+.hero-cta {
+ display: inline-block;
+ background: #fff;
+ color: #667eea;
+ padding: 0.75rem 2rem;
+ border-radius: 0.5rem;
+ font-weight: 600;
+ font-size: 1rem;
+ transition: transform 0.15s, box-shadow 0.15s;
+}
+
+.hero-cta:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+/* Product Section */
+.product-section {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 4rem 2rem;
+}
+
+.section-title {
+ font-size: 2rem;
+ font-weight: 700;
+ text-align: center;
+ margin-bottom: 0.5rem;
+}
+
+.section-description {
+ text-align: center;
+ color: #6b7280;
+ margin-bottom: 2.5rem;
+}
+
+.product-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1.5rem;
+}
+
+.product-card {
+ background: #fff;
+ border-radius: 0.75rem;
+ overflow: hidden;
+ border: 1px solid #e5e7eb;
+ transition: box-shadow 0.2s;
+}
+
+.product-card:hover {
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+}
+
+.product-image {
+ width: 100%;
+ height: 240px;
+ object-fit: cover;
+ background: #f3f4f6;
+}
+
+.product-info {
+ padding: 1.25rem;
+}
+
+.product-name {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin-bottom: 0.25rem;
+}
+
+.product-description {
+ color: #6b7280;
+ font-size: 0.875rem;
+ margin-bottom: 0.75rem;
+}
+
+.product-price {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: #111827;
+ margin-bottom: 1rem;
+}
+
+.product-cta {
+ width: 100%;
+ padding: 0.625rem;
+ background: #111827;
+ color: #fff;
+ border: none;
+ border-radius: 0.5rem;
+ font-size: 0.875rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.15s;
+}
+
+.product-cta:hover {
+ background: #374151;
+}
+
+/* Cart */
+.cart-toggle {
+ background: none;
+ border: 1px solid #e5e7eb;
+ padding: 0.5rem 1rem;
+ border-radius: 0.5rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.15s;
+}
+
+.cart-toggle:hover {
+ background: #f3f4f6;
+}
+
+.cart-panel {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 380px;
+ max-width: 100vw;
+ height: 100vh;
+ background: #fff;
+ border-left: 1px solid #e5e7eb;
+ box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
+ z-index: 100;
+ display: flex;
+ flex-direction: column;
+ padding: 1.5rem;
+}
+
+.cart-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+}
+
+.cart-header h2 {
+ font-size: 1.25rem;
+ font-weight: 700;
+}
+
+.cart-close {
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: 0.25rem;
+ color: #6b7280;
+}
+
+.cart-empty {
+ color: #6b7280;
+ text-align: center;
+ padding: 2rem 0;
+}
+
+.cart-items {
+ list-style: none;
+ flex: 1;
+ overflow-y: auto;
+}
+
+.cart-item {
+ padding: 1rem 0;
+ border-bottom: 1px solid #f3f4f6;
+}
+
+.cart-item-info {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 0.5rem;
+}
+
+.cart-item-name {
+ font-weight: 500;
+}
+
+.cart-item-price {
+ font-weight: 600;
+}
+
+.cart-item-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.cart-item-actions button {
+ background: #f3f4f6;
+ border: none;
+ width: 28px;
+ height: 28px;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ font-size: 0.875rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.cart-item-actions button:hover {
+ background: #e5e7eb;
+}
+
+.cart-item-remove {
+ width: auto !important;
+ padding: 0 0.5rem !important;
+ margin-left: auto;
+ color: #ef4444;
+ background: transparent !important;
+ font-size: 0.75rem !important;
+}
+
+.cart-item-remove:hover {
+ background: #fef2f2 !important;
+}
+
+.cart-total {
+ padding-top: 1rem;
+ border-top: 2px solid #111827;
+ text-align: right;
+ font-size: 1.125rem;
+}
+
+/* Footer */
+.footer {
+ text-align: center;
+ padding: 2rem;
+ border-top: 1px solid #e5e7eb;
+ color: #6b7280;
+ font-size: 0.875rem;
+}
+
+.footer a {
+ color: #6366f1;
+ font-weight: 500;
+}
+
+.footer a:hover {
+ text-decoration: underline;
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/code/main.tsx b/templates/croct/starter/ecommerce-nanostores/code/main.tsx
new file mode 100644
index 00000000..9b39cc33
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { App } from './App';
+import './index.css';
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/templates/croct/starter/ecommerce-nanostores/code/stores/banner.ts b/templates/croct/starter/ecommerce-nanostores/code/stores/banner.ts
new file mode 100644
index 00000000..43ca2715
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/stores/banner.ts
@@ -0,0 +1,18 @@
+import { croctContent } from 'croct-nanostores';
+import './croct';
+
+export const announcementBar = croctContent('%announcementBarSlotId%@%announcementBarSlotVersion%', {
+ text: 'Free shipping on orders over $50!',
+ link: 'https://example.com/products',
+ style: {
+ backgroundColor: '#000000',
+ textColor: '#ffffff',
+ },
+});
+
+export const heroBanner = croctContent('%heroBannerSlotId%@%heroBannerSlotVersion%', {
+ title: 'Welcome to our store',
+ subtitle: 'Discover our curated collection of premium products.',
+ ctaLabel: 'Shop now',
+ ctaLink: 'https://example.com/products',
+});
diff --git a/templates/croct/starter/ecommerce-nanostores/code/stores/cart.ts b/templates/croct/starter/ecommerce-nanostores/code/stores/cart.ts
new file mode 100644
index 00000000..979bd163
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/stores/cart.ts
@@ -0,0 +1,82 @@
+import { atom, computed } from 'nanostores';
+import { trackCart } from 'croct-nanostores';
+import './croct';
+
+type CartItem = {
+ productId: string;
+ name: string;
+ unitPrice: number;
+ quantity: number;
+};
+
+type CroctCart = {
+ items: Array<{
+ productId: string;
+ name: string;
+ unitPrice: number;
+ quantity: number;
+ }>;
+};
+
+export const $cartItems = atom([]);
+
+export const $cartTotal = computed($cartItems, items =>
+ items.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0),
+);
+
+export const $cartCount = computed($cartItems, items =>
+ items.reduce((sum, item) => sum + item.quantity, 0),
+);
+
+const $croctCart = atom(null);
+
+trackCart($croctCart);
+
+function syncCroctCart(): void {
+ const items = $cartItems.get();
+
+ $croctCart.set(
+ items.length > 0
+ ? {
+ items: items.map(item => ({
+ productId: item.productId,
+ name: item.name,
+ unitPrice: item.unitPrice,
+ quantity: item.quantity,
+ })),
+ }
+ : null,
+ );
+}
+
+export function addToCart(item: Omit): void {
+ const items = $cartItems.get();
+ const existing = items.find(i => i.productId === item.productId);
+
+ if (existing) {
+ $cartItems.set(
+ items.map(i =>
+ i.productId === item.productId ? { ...i, quantity: i.quantity + 1 } : i,
+ ),
+ );
+ } else {
+ $cartItems.set([...items, { ...item, quantity: 1 }]);
+ }
+
+ syncCroctCart();
+}
+
+export function removeFromCart(productId: string): void {
+ $cartItems.set($cartItems.get().filter(i => i.productId !== productId));
+ syncCroctCart();
+}
+
+export function updateQuantity(productId: string, quantity: number): void {
+ if (quantity <= 0) {
+ removeFromCart(productId);
+ return;
+ }
+
+ $cartItems.set($cartItems.get().map(i => (i.productId === productId ? { ...i, quantity } : i)));
+ syncCroctCart();
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/code/stores/croct.ts b/templates/croct/starter/ecommerce-nanostores/code/stores/croct.ts
new file mode 100644
index 00000000..1b7f91e0
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/stores/croct.ts
@@ -0,0 +1,6 @@
+import { croct } from 'croct-nanostores';
+
+croct.plug({
+ appId: '%appId%',
+ plugins: ['auto-refresh-atom'],
+});
diff --git a/templates/croct/starter/ecommerce-nanostores/code/stores/products.ts b/templates/croct/starter/ecommerce-nanostores/code/stores/products.ts
new file mode 100644
index 00000000..a7c8a767
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/code/stores/products.ts
@@ -0,0 +1,36 @@
+import { croctContent } from 'croct-nanostores';
+import './croct';
+
+export const recommendedProducts = croctContent('%productsSlotId%@%productsSlotVersion%', {
+ title: 'Recommended for you',
+ description: 'Handpicked products based on your preferences.',
+ cards: [
+ {
+ name: 'Classic T-Shirt',
+ description: 'Premium cotton comfort for everyday wear.',
+ price: 29.99,
+ cta: {
+ label: 'Add to cart',
+ link: 'https://example.com/products/classic-t-shirt',
+ },
+ },
+ {
+ name: 'Running Shoes',
+ description: 'Lightweight performance for every run.',
+ price: 89.99,
+ cta: {
+ label: 'Add to cart',
+ link: 'https://example.com/products/running-shoes',
+ },
+ },
+ {
+ name: 'Leather Backpack',
+ description: 'Durable everyday carry with timeless style.',
+ price: 129.99,
+ cta: {
+ label: 'Add to cart',
+ link: 'https://example.com/products/leather-backpack',
+ },
+ },
+ ],
+});
diff --git a/templates/croct/starter/ecommerce-nanostores/configuration/announcement-bar-content.en.json b/templates/croct/starter/ecommerce-nanostores/configuration/announcement-bar-content.en.json
new file mode 100644
index 00000000..562651fe
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/configuration/announcement-bar-content.en.json
@@ -0,0 +1,39 @@
+{
+ "$schema": "https://schema.croct.com/json/v1/template-content.json",
+ "type": "structure",
+ "attributes": {
+ "text": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Free shipping on orders over $50!"
+ }
+ },
+ "link": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://example.com/products"
+ }
+ },
+ "style": {
+ "type": "structure",
+ "attributes": {
+ "backgroundColor": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "#000000"
+ }
+ },
+ "textColor": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "#ffffff"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/configuration/announcement-bar-schema.json b/templates/croct/starter/ecommerce-nanostores/configuration/announcement-bar-schema.json
new file mode 100644
index 00000000..b9ece105
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/configuration/announcement-bar-schema.json
@@ -0,0 +1,60 @@
+{
+ "$schema": "https://schema.croct.com/json/v1/template-content-schema.json",
+ "type": "structure",
+ "attributes": {
+ "text": {
+ "type": {
+ "type": "text",
+ "minimumLength": 1
+ },
+ "label": "Message",
+ "private": false,
+ "optional": false,
+ "position": 0,
+ "description": "The message to display in the announcement bar."
+ },
+ "link": {
+ "type": {
+ "type": "text",
+ "format": "url"
+ },
+ "label": "Link",
+ "private": false,
+ "optional": true,
+ "position": 1,
+ "description": "The link to navigate when clicking the announcement bar."
+ },
+ "style": {
+ "type": {
+ "type": "structure",
+ "attributes": {
+ "backgroundColor": {
+ "type": {
+ "type": "text",
+ "format": "color"
+ },
+ "label": "Background Color",
+ "private": false,
+ "optional": false,
+ "position": 0,
+ "description": "The background color of the announcement bar."
+ },
+ "textColor": {
+ "type": {
+ "type": "text",
+ "format": "color"
+ },
+ "label": "Text Color",
+ "private": false,
+ "optional": false,
+ "position": 1,
+ "description": "The text color of the announcement bar."
+ }
+ }
+ },
+ "label": "Style",
+ "position": 2,
+ "description": "The style of the announcement bar."
+ }
+ }
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/configuration/hero-banner-content.en.json b/templates/croct/starter/ecommerce-nanostores/configuration/hero-banner-content.en.json
new file mode 100644
index 00000000..5ca34ed7
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/configuration/hero-banner-content.en.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://schema.croct.com/json/v1/template-content.json",
+ "type": "structure",
+ "attributes": {
+ "title": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Welcome to our store"
+ }
+ },
+ "subtitle": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Discover our curated collection of premium products."
+ }
+ },
+ "ctaLabel": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Shop now"
+ }
+ },
+ "ctaLink": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://example.com/products"
+ }
+ }
+ }
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/configuration/hero-banner-schema.json b/templates/croct/starter/ecommerce-nanostores/configuration/hero-banner-schema.json
new file mode 100644
index 00000000..98a17065
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/configuration/hero-banner-schema.json
@@ -0,0 +1,64 @@
+{
+ "$schema": "https://schema.croct.com/json/v1/template-content-schema.json",
+ "type": "structure",
+ "attributes": {
+ "title": {
+ "type": {
+ "type": "text",
+ "minimumLength": 1
+ },
+ "label": "Title",
+ "private": false,
+ "optional": false,
+ "position": 0,
+ "description": "The main heading of the hero banner."
+ },
+ "subtitle": {
+ "type": {
+ "type": "text",
+ "minimumLength": 1
+ },
+ "label": "Subtitle",
+ "private": false,
+ "optional": true,
+ "position": 1,
+ "description": "The subtitle displayed below the title."
+ },
+ "ctaLabel": {
+ "type": {
+ "type": "text",
+ "minimumLength": 1
+ },
+ "label": "CTA Label",
+ "private": false,
+ "optional": false,
+ "position": 2,
+ "description": "The label of the call-to-action button."
+ },
+ "ctaLink": {
+ "type": {
+ "type": "text",
+ "format": "url"
+ },
+ "label": "CTA Link",
+ "private": false,
+ "optional": false,
+ "position": 3,
+ "description": "The target URL of the call-to-action button."
+ },
+ "image": {
+ "type": {
+ "id": "@croct/file",
+ "type": "reference",
+ "properties": {
+ "maximumSize": 307200
+ }
+ },
+ "label": "Background Image",
+ "private": false,
+ "optional": true,
+ "position": 4,
+ "description": "An optional background image for the hero banner."
+ }
+ }
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/configuration/product-cards-content.en.json b/templates/croct/starter/ecommerce-nanostores/configuration/product-cards-content.en.json
new file mode 100644
index 00000000..34c44ada
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/configuration/product-cards-content.en.json
@@ -0,0 +1,181 @@
+{
+ "$schema": "https://schema.croct.com/json/v1/template-content.json",
+ "type": "structure",
+ "attributes": {
+ "title": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Recommended for you"
+ }
+ },
+ "description": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Handpicked products based on your preferences."
+ }
+ },
+ "cards": {
+ "type": "list",
+ "items": [
+ {
+ "type": "structure",
+ "attributes": {
+ "name": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Classic T-Shirt"
+ }
+ },
+ "description": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Premium cotton comfort for everyday wear."
+ }
+ },
+ "image": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://cdn.croct.io/workspace/customer-assets/bac910ff-a18e-4ca5-bdd0-155139967ea4/4ed0d2a8-07e4-4808-a5f5-a163aa114e48"
+ }
+ },
+ "price": {
+ "type": "number",
+ "value": {
+ "type": "static",
+ "value": 29.99
+ }
+ },
+ "cta": {
+ "type": "structure",
+ "attributes": {
+ "label": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Add to cart"
+ }
+ },
+ "link": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://example.com/products/classic-t-shirt"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "structure",
+ "attributes": {
+ "name": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Running Shoes"
+ }
+ },
+ "description": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Lightweight performance for every run."
+ }
+ },
+ "image": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://cdn.croct.io/workspace/customer-assets/bac910ff-a18e-4ca5-bdd0-155139967ea4/4ed0d2a8-07e4-4808-a5f5-a163aa114e48"
+ }
+ },
+ "price": {
+ "type": "number",
+ "value": {
+ "type": "static",
+ "value": 89.99
+ }
+ },
+ "cta": {
+ "type": "structure",
+ "attributes": {
+ "label": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Add to cart"
+ }
+ },
+ "link": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://example.com/products/running-shoes"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "type": "structure",
+ "attributes": {
+ "name": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Leather Backpack"
+ }
+ },
+ "description": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Durable everyday carry with timeless style."
+ }
+ },
+ "image": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://cdn.croct.io/workspace/customer-assets/bac910ff-a18e-4ca5-bdd0-155139967ea4/4ed0d2a8-07e4-4808-a5f5-a163aa114e48"
+ }
+ },
+ "price": {
+ "type": "number",
+ "value": {
+ "type": "static",
+ "value": 129.99
+ }
+ },
+ "cta": {
+ "type": "structure",
+ "attributes": {
+ "label": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "Add to cart"
+ }
+ },
+ "link": {
+ "type": "text",
+ "value": {
+ "type": "static",
+ "value": "https://example.com/products/leather-backpack"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/configuration/product-cards-schema.json b/templates/croct/starter/ecommerce-nanostores/configuration/product-cards-schema.json
new file mode 100644
index 00000000..41459bff
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/configuration/product-cards-schema.json
@@ -0,0 +1,121 @@
+{
+ "$schema": "https://schema.croct.com/json/v1/template-content-schema.json",
+ "type": "structure",
+ "attributes": {
+ "title": {
+ "type": {
+ "type": "text"
+ },
+ "label": "Title",
+ "private": false,
+ "optional": true,
+ "position": 0,
+ "description": "The section heading displayed above the product grid."
+ },
+ "description": {
+ "type": {
+ "type": "text"
+ },
+ "label": "Description",
+ "private": false,
+ "optional": true,
+ "position": 1,
+ "description": "A short description displayed below the section title."
+ },
+ "cards": {
+ "type": {
+ "type": "list",
+ "items": {
+ "type": "structure",
+ "attributes": {
+ "name": {
+ "type": {
+ "type": "text",
+ "minimumLength": 1
+ },
+ "label": "Name",
+ "private": false,
+ "optional": false,
+ "position": 0,
+ "description": "The name of the product."
+ },
+ "description": {
+ "type": {
+ "type": "text",
+ "minimumLength": 1
+ },
+ "label": "Description",
+ "private": false,
+ "optional": true,
+ "position": 1,
+ "description": "A short description of the product."
+ },
+ "image": {
+ "type": {
+ "id": "@croct/file",
+ "type": "reference",
+ "properties": {}
+ },
+ "label": "Image",
+ "private": false,
+ "optional": true,
+ "position": 2,
+ "description": "The product image."
+ },
+ "price": {
+ "type": {
+ "type": "number",
+ "minimum": 0
+ },
+ "label": "Price",
+ "private": false,
+ "optional": false,
+ "position": 3,
+ "description": "The display price of the product."
+ },
+ "cta": {
+ "type": {
+ "type": "structure",
+ "attributes": {
+ "label": {
+ "type": {
+ "type": "text",
+ "minimumLength": 1
+ },
+ "label": "Label",
+ "private": false,
+ "optional": false,
+ "position": 0,
+ "description": "The label of the button."
+ },
+ "link": {
+ "type": {
+ "type": "text",
+ "format": "url"
+ },
+ "label": "Link",
+ "private": false,
+ "optional": false,
+ "position": 1,
+ "description": "The target URL for the button."
+ }
+ }
+ },
+ "label": "Call to action",
+ "private": false,
+ "optional": true,
+ "position": 4,
+ "description": "The call-to-action button for the product."
+ }
+ }
+ },
+ "itemLabel": "Product card"
+ },
+ "label": "Cards",
+ "private": false,
+ "optional": false,
+ "position": 2,
+ "description": "The list of product cards to display."
+ }
+ }
+}
diff --git a/templates/croct/starter/ecommerce-nanostores/cover.png b/templates/croct/starter/ecommerce-nanostores/cover.png
new file mode 100644
index 00000000..7b64fd2c
Binary files /dev/null and b/templates/croct/starter/ecommerce-nanostores/cover.png differ
diff --git a/templates/croct/starter/ecommerce-nanostores/template.json5 b/templates/croct/starter/ecommerce-nanostores/template.json5
new file mode 100644
index 00000000..317db4b9
--- /dev/null
+++ b/templates/croct/starter/ecommerce-nanostores/template.json5
@@ -0,0 +1,316 @@
+{
+ "$schema": "https://schema.croct.com/json/v1/catalog-template.json",
+ "title": "Ecommerce + Nanostores starter",
+ "description": "An ecommerce site with personalized banners and product recommendations using Croct Nanostores.",
+ "metadata": {
+ "id": "boilerplate/starter/ecommerce-nanostores",
+ "ecosystem": {
+ "name": "Croct",
+ "avatarUrl": "https://github.com/croct-tech.png",
+ "websiteUrl": "https://croct.com"
+ },
+ "documentationUrl": "https://github.com/croct-tech/templates/blob/master/templates/croct/starter/ecommerce-nanostores/README.md",
+ "sourceUrl": "https://github.com/croct-tech/templates/blob/master/templates/croct/starter/ecommerce-nanostores/template.json5",
+ "coverImageUrl": "https://github.com/croct-tech/templates/blob/master/templates/croct/starter/ecommerce-nanostores/cover.png",
+ "installationUrl": "croct://starter/ecommerce-nanostores",
+ "categories": [
+ "boilerplate/starter",
+ "framework/react",
+ "language/typescript",
+ "library/nanostores"
+ ],
+ "relatedTemplates": [
+ "boilerplate/starter/nextjs",
+ "boilerplate/starter/shadcn-ui"
+ ]
+ },
+ "options": {
+ "name": {
+ "type": "string",
+ "description": "The name of the project.",
+ "default": "my-croct-shop"
+ },
+ "disableLauncher": {
+ "type": "boolean",
+ "description": "Whether to disable opening the project in the browser.",
+ "default": false
+ }
+ },
+ "actions": [
+ {
+ "name": "test",
+ "condition": "${project.platform === 'unknown'}",
+ "then": [
+ {
+ "name": "print",
+ "semantics": "info",
+ "message": "No project found in the current directory"
+ },
+ {
+ "name": "prompt",
+ "type": "confirmation",
+ "message": "Start a new project?",
+ "default": true,
+ "result": "createProject"
+ },
+ {
+ "name": "test",
+ "condition": "${this.createProject}",
+ "then": [
+ {
+ "name": "define",
+ "variables": {
+ "projectName": "${options.name}"
+ }
+ },
+ {
+ "name": "import",
+ "template": "croct://utils/filename-generator",
+ "options": {
+ "reference": "projectName"
+ }
+ },
+ {
+ "name": "execute-package",
+ "runner": "npm",
+ "command": "create-vite@latest",
+ "arguments": "${[this.projectName, '--template', 'react-ts']}",
+ "interactions": [
+ {
+ "when": "Ok to proceed",
+ "then": [
+ "y",
+ "[enter]"
+ ]
+ },
+ {
+ "when": "Scaffolding project",
+ "final": true
+ },
+ {
+ "when": "Done.",
+ "final": true
+ }
+ ]
+ },
+ {
+ "name": "change-directory",
+ "path": "${this.projectName}"
+ }
+ ],
+ "else": {
+ "name": "fail",
+ "title": "Project not found",
+ "message": "Unable to locate a project in the current directory.",
+ "suggestions": [
+ "Navigate to the project directory and try again."
+ ]
+ }
+ }
+ ],
+ "else": [
+ {
+ "name": "integrate-croct"
+ }
+ ]
+ },
+ {
+ "name": "add-dependency",
+ "dependencies": [
+ "croct-nanostores",
+ "nanostores",
+ "@nanostores/react"
+ ]
+ },
+ {
+ "name": "create-resource",
+ "resources": {
+ "components": {
+ "announcement-bar": {
+ "name": "Announcement bar",
+ "description": "A small text bar featured at the top of a page.",
+ "schema": "${import('./configuration/announcement-bar-schema.json')}"
+ },
+ "hero-banner": {
+ "name": "Hero banner",
+ "description": "A full-width banner with a title, subtitle, and call-to-action.",
+ "schema": "${import('./configuration/hero-banner-schema.json')}"
+ },
+ "product-cards": {
+ "name": "Product cards",
+ "description": "A section featuring personalized product recommendations.",
+ "schema": "${import('./configuration/product-cards-schema.json')}"
+ }
+ },
+ "slots": {
+ "announcement-bar": {
+ "name": "Announcement bar",
+ "component": "announcement-bar",
+ "content": {
+ "en": "${import('./configuration/announcement-bar-content.en.json')}"
+ }
+ },
+ "home-hero-banner": {
+ "name": "Home hero banner",
+ "component": "hero-banner",
+ "content": {
+ "en": "${import('./configuration/hero-banner-content.en.json')}"
+ }
+ },
+ "recommended-products": {
+ "name": "Recommended products",
+ "component": "product-cards",
+ "content": {
+ "en": "${import('./configuration/product-cards-content.en.json')}"
+ }
+ }
+ }
+ },
+ "result": {
+ "slots": {
+ "announcement-bar": {
+ "id": "announcementBarSlotId",
+ "version": "announcementBarSlotVersion"
+ },
+ "home-hero-banner": {
+ "id": "heroBannerSlotId",
+ "version": "heroBannerSlotVersion"
+ },
+ "recommended-products": {
+ "id": "productsSlotId",
+ "version": "productsSlotVersion"
+ }
+ }
+ }
+ },
+ {
+ "name": "add-slot",
+ "slots": [
+ "${this.announcementBarSlotId}@${this.announcementBarSlotVersion}",
+ "${this.heroBannerSlotId}@${this.heroBannerSlotVersion}",
+ "${this.productsSlotId}@${this.productsSlotVersion}"
+ ]
+ },
+ {
+ "name": "download",
+ "source": "code/stores/croct.ts",
+ "destination": "${project.path.source}/stores"
+ },
+ {
+ "name": "download",
+ "source": "code/stores/banner.ts",
+ "destination": "${project.path.source}/stores"
+ },
+ {
+ "name": "download",
+ "source": "code/stores/products.ts",
+ "destination": "${project.path.source}/stores"
+ },
+ {
+ "name": "download",
+ "source": "code/stores/cart.ts",
+ "destination": "${project.path.source}/stores"
+ },
+ {
+ "name": "download",
+ "source": "code/components/AnnouncementBar.tsx",
+ "destination": "${project.path.source}/components"
+ },
+ {
+ "name": "download",
+ "source": "code/components/HeroBanner.tsx",
+ "destination": "${project.path.source}/components"
+ },
+ {
+ "name": "download",
+ "source": "code/components/ProductGrid.tsx",
+ "destination": "${project.path.source}/components"
+ },
+ {
+ "name": "download",
+ "source": "code/components/Cart.tsx",
+ "destination": "${project.path.source}/components"
+ },
+ {
+ "name": "download",
+ "source": "code/App.tsx",
+ "destination": "${project.path.source}",
+ "overwrite": true
+ },
+ {
+ "name": "download",
+ "source": "code/main.tsx",
+ "destination": "${project.path.source}",
+ "overwrite": true
+ },
+ {
+ "name": "download",
+ "source": "code/index.css",
+ "destination": "${project.path.source}",
+ "overwrite": true
+ },
+ {
+ "name": "replace-file-content",
+ "files": [
+ {
+ "path": "${project.path.source}/stores/croct.ts",
+ "replacements": [
+ {
+ "pattern": "%appId%",
+ "value": "${project.workspace.applicationId}"
+ }
+ ]
+ },
+ {
+ "path": "${project.path.source}/stores/banner.ts",
+ "replacements": [
+ {
+ "pattern": "%announcementBarSlotId%",
+ "value": "${this.announcementBarSlotId}"
+ },
+ {
+ "pattern": "%announcementBarSlotVersion%",
+ "value": "${this.announcementBarSlotVersion}"
+ },
+ {
+ "pattern": "%heroBannerSlotId%",
+ "value": "${this.heroBannerSlotId}"
+ },
+ {
+ "pattern": "%heroBannerSlotVersion%",
+ "value": "${this.heroBannerSlotVersion}"
+ }
+ ]
+ },
+ {
+ "path": "${project.path.source}/stores/products.ts",
+ "replacements": [
+ {
+ "pattern": "%productsSlotId%",
+ "value": "${this.productsSlotId}"
+ },
+ {
+ "pattern": "%productsSlotVersion%",
+ "value": "${this.productsSlotVersion}"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "print",
+ "semantics": "success",
+ "message": "Ecommerce project with Nanostores successfully set up."
+ },
+ {
+ "name": "test",
+ "condition": "${!options.disableLauncher}",
+ "then": [
+ {
+ "name": "import",
+ "template": "croct://utils/example-launcher"
+ }
+ ]
+ }
+ ]
+}