# Theme development

A theme is the look of a BaxPay storefront: colours, fonts, homepage sections and optional custom
CSS. Build one in Theme studio, share it as a file, or sell it on the BaxPay theme store.

## Who can make themes

Any BaxPay business, on any plan, without needing an online shop of its own. Join the theme
developer program in the Business dashboard: **Theme studio** (sidebar → Workspace), with a public
developer profile (name, support email, optional website and description) and the developer terms.
Payouts go to the business's BaxPay account.

| Action | Owner / Admin | Manager | Viewer / Attendant |
|--------|:---:|:---:|:---:|
| See themes and sales | ✓ | ✓ | ✓ |
| Create and design themes, edit the listing, delete | ✓ | ✓ | — |
| Join the program, set a price, submit, withdraw | ✓ | — | — |

## What a theme contains

- `Tokens`: Colours, heading and body fonts, corner radius, light or dark mode.
- `Sections`: Which homepage blocks appear, in what order, shown or hidden, and their layout options.
- `Custom CSS` (optional): Up to 20,000 characters, applied on every storefront page.

A theme never carries content. When it is saved to the store or exported, your texts, photos,
links and chosen products are removed. When a merchant applies it, their own texts, photos and
products are laid into your layout; sections your theme leaves out are kept but hidden, so nothing
they wrote is lost. **Design for content you have never seen**: long and short names, with and
without photos, few or many products, services only.

## Quick start

Theme studio → Join: public name, support email, terms.
Pick a name, a category and a starting point: a BaxPay theme, your shop's design, a theme file, or a blank page.
The same builder as Online shop → Design, filled with sample products and texts that are never saved. Colours, fonts, sections, options and custom CSS.
A one or two-sentence description, a preview image (shown at 4:3: use a real screenshot of a shop using the theme), an optional demo link and a price.
The theme goes **In review**. BaxPay publishes it or sends it back with feedback; you get a notification either way.

## The theme file

Themes travel as a `baxpay-theme` JSON file (version 1). Theme studio can start from one, and
Online shop → Design → Themes → **Export my theme** downloads a shop's design as one (content
already removed). The importer also accepts a bare `{ "tokens": …, "sections": … }` object.

```json
{
  "format": "baxpay-theme",
  "version": 1,
  "name": "Atelier Nord",
  "category": "crafts",
  "description": "Warm, editorial look for makers and small workshops.",
  "author": "Studio Nord",
  "theme": {
    "tokens": {
      "primaryColor": "#2B2420",
      "accentColor": "#A4552E",
      "backgroundColor": "#F7F3EE",
      "surfaceColor": "#FFFDF9",
      "textColor": "#2B2420",
      "mutedColor": "#7A6E66",
      "fontHeading": "Fraunces",
      "fontBody": "Lora",
      "borderRadius": "0.25rem",
      "colorMode": "light"
    },
    "sections": [
      { "id": "announcement", "type": "announcement", "visible": true, "props": {} },
      { "id": "hero", "type": "hero", "visible": true, "props": { "layout": "split", "align": "left", "height": "lg", "overlayOpacity": 30 } },
      { "id": "categories", "type": "categories", "visible": true, "props": { "style": "tiles" } },
      { "id": "featuredProducts", "type": "featuredProducts", "visible": true, "props": { "limit": 8 } },
      { "id": "imageText", "type": "imageText", "visible": true, "props": { "imageSide": "left" } },
      { "id": "contact", "type": "contact", "visible": true, "props": { "showHours": true, "showMap": false } },
      { "id": "footer", "type": "footer", "visible": true, "props": { "showPoweredBy": true } }
    ],
    "customCss": ".shop-root h1,.shop-root h2{letter-spacing:-.02em}"
  }
}
```

- `format` (string, required): `"baxpay-theme"`.
- `version` (number, required): `1`. A higher number is refused as made for a newer version of BaxPay.
- `name` (string): Up to 60 characters.
- `category` (string): `general`, `fashion`, `home`, `food`, `beauty`, `tech`, `crafts`, `services`, `kids`, `sports`, `luxury`, `florist` or `books`; anything else becomes `general`.
- `description` (string): Up to 400 characters.
- `author` (string): Informational. On import the author is your theme developer profile, not this field.
- `theme` (object, required): `tokens` and `sections`, plus optional `customCss`.

Everything is cleaned on import: a value outside the rules falls back to the default instead of
failing the import. Check the result in the builder.

### Tokens

| Token | Allowed values |
|-------|----------------|
| `primaryColor`, `accentColor`, `backgroundColor`, `surfaceColor`, `textColor`, `mutedColor` | Hex colour, `#RGB` or `#RRGGBB` (`#RGB` is stored as `#RRGGBB`) |
| `fontHeading`, `fontBody` | Inter, DM Sans, Space Grotesk, Outfit, Manrope, Sora, Playfair Display, Fraunces, Libre Baskerville, Cormorant Garamond, Poppins, Montserrat, Lora, Nunito, Quicksand, Syne, Archivo, Josefin Sans, EB Garamond, Work Sans, Rubik, or `system-ui` (Google Fonts, weights 400–700) |
| `borderRadius` | `0` to `99` (up to three decimals) in `rem` or `px`, e.g. `0.75rem`, `12px` |
| `colorMode` | `light` or `dark` |
| `containerWidth` | `narrow`, `normal`, `wide`, `full` |
| `sectionSpacing` | `compact`, `normal`, `spacious` |
| `headingWeight` | `400`, `500`, `600`, `700`, `800` |
| `headingCase` | `normal`, `uppercase` |
| `headingTracking` | `tight`, `normal`, `wide` |
| `baseFontSize` | `sm`, `md`, `lg` |
| `buttonStyle` | `pill`, `rounded`, `square` |
| `buttonSize` | `sm`, `md`, `lg` |
| `cardStyle` | `flat`, `bordered`, `shadow`, `filled` |
| `imageRatio` | `square`, `portrait`, `landscape`, `tall` |
| `imageFit` | `cover`, `contain` |
| `hoverEffect` | `zoom`, `lift`, `fade`, `none` |
| `priceStyle` | `default`, `accent`, `muted` |
| `catalogColumns` | `2` to `5` |
| `headerLayout` | `left`, `center`, `split` |
| `headerStyle` | `solid`, `blur`, `transparent` |
| `headerSticky`, `showSearch`, `showAccount` | `true` or `false` |

The text colour on primary and accent buttons is picked automatically (dark or white) for contrast.

### Sections

Up to **40** sections, drawn in order. `id` is free text (up to 64 characters); `visible: false` keeps
a section but hides it. Only **style** props are part of a theme; **content** props (titles, text,
links, images, items) belong to the merchant and are dropped from saved and exported themes.

Every section also takes a `design` object: `paddingY` (`default`, `none`, `sm`, `md`, `lg`, `xl`),
`background` (`default`, `surface`, `tint`, `primary`, `accent`, `custom` with `backgroundColor`),
`width` (`default`, `narrow`, `wide`, `full`), `titleAlign` (`left`, `center`) and `headingSize`
(`sm`, `md`, `lg`).

| Type | Main style props |
|------|------|
| `announcement` | `variant`: `bar` \| `marquee`; `dismissible` |
| `hero` | `layout`: `overlay` \| `split` \| `stacked`; `align`: `left` \| `center` \| `right`; `height`: `sm` \| `md` \| `lg` \| `full`; `overlayOpacity`: 0–80 |
| `featuredProducts`, `catalogGrid` | `limit`: 1–48; `layout`: `grid` \| `carousel`; `columns`: 2–6; `columnsMobile`: 1–2; `showPrice`, `showCategory`, `showQuickAdd`, `showViewAll` |
| `categories` | `style`: `tiles` \| `chips` \| `circles` \| `list` \| `cards`; `columns`: 2–6; `limit`: 1–24; `showCount` |
| `about` | `align`: `left` \| `center`; `columns`: 1–2 |
| `imageText` | `imageSide`: `left` \| `right` |
| `richText` | `align`: `left` \| `center`; `size`: `sm` \| `md` \| `lg` |
| `video` | `autoplay` |
| `countdown` | `style`: `banner` \| `card` |
| `features` | `layout`: `grid` \| `list` \| `centered`; `columns`: 2–4 |
| `steps` | `layout`: `row` \| `column` \| `cards` |
| `testimonials` | `layout`: `grid` \| `carousel` \| `single`; `columns`: 1–4; `showRating` |
| `faq` | `layout`: `accordion` \| `twoColumn` \| `cards`; `openFirst` |
| `ctaBanner` | `align`: `left` \| `center`; `size`: `sm` \| `md` \| `lg` |
| `trustBadges` | `layout`: `inline` \| `cards` \| `centered`; `columns`: 2–6 |
| `logoBar` | `size`: `sm` \| `md` \| `lg` |
| `gallery` | `layout`: `grid` \| `masonry` \| `featured` \| `carousel`; `columns`: 2–6 |
| `productSpotlight` | `imageSide`: `left` \| `right`; `showPrice` |
| `promoTiles` | Tile layout |
| `team` | `columns`: 2–5 |
| `services` | `limit`: 1–24; `layout`: `cards` \| `list` \| `grid`; `columns`: 1–4; `showDescription`, `showDuration`, `showPrice`, `showViewAll` |
| `contact` | `layout`: `card` \| `split` \| `centered`; `showEmail`, `showPhone`, `showAddress`, `showSocials`, `showHours`, `showMap` |
| `divider` | `height`: `sm` \| `md` \| `lg` \| `xl` |
| `footer` | `layout`: `columns` \| `simple` \| `centered`; `showPoweredBy`, `showSocials`, `showPolicies`, `showContact`, `showPayments` |

A value outside these rules falls back to the section's default.

Unknown section types are dropped. Product, catalog, cart, checkout and order pages are not
sections: they follow your tokens and custom CSS.

## Custom CSS

Your CSS is added after the storefront's base styles on every page, so it can override them.
Style with the variables below so the merchant's colour changes keep working.

- `--shop-primary, --shop-on-primary`: Primary colour and readable text on it.
- `--shop-accent, --shop-on-accent`: Accent colour (primary buttons, links, highlights) and text on it.
- `--shop-bg, --shop-surface`: Page background; cards and inputs.
- `--shop-text, --shop-muted`: Body text; secondary text.
- `--shop-border`: The muted colour at 20 % opacity.
- `--shop-radius`: The `borderRadius` token.
- `--shop-font-heading`: Heading font stack (the body font is set on `.shop-root`).

**Stable hooks**: `.shop-root` (the storefront wrapper), `[data-section="hero"]` and the like
(every section of a type), and the base classes `.shop-btn`,
`.shop-btn-primary` (accent), `.shop-btn-dark` (primary), `.shop-btn-ghost` (outlined),
`.shop-card`, `.shop-input`, `.shop-muted`, `.shop-heading`. Utility class names in the
storefront's markup can change without notice; these hooks stay. Don't style `#section-<id>` in a
theme: when a merchant applies it, section ids are renamed to their own.

```css
/* Square, outlined buttons everywhere */
.shop-btn { border-radius: 0; text-transform: uppercase; letter-spacing: .08em; }
.shop-btn-primary { background: transparent; color: var(--shop-accent); border: 2px solid var(--shop-accent); }

/* A tinted band behind the FAQ */
[data-section="faq"] { background: color-mix(in srgb, var(--shop-accent) 6%, var(--shop-bg)); }
```

> **What custom CSS cannot do**
> `<`, `@import`, `@charset`, `@namespace`, `expression(`, `javascript:`, `behavior` and
> `-moz-binding` are removed, and `url(…)` must be `https://` (anything else becomes `none`). The
> storefront only loads **images** from BaxPay's media hosts (upload them as shop media) and
> **fonts** from the list above. Themes cannot add sections, scripts or page templates.

Test light and dark mode, a phone-width screen, a long shop name, a hero without a photo and a shop
with only services. Never hide or disguise prices, taxes, delivery costs, the cart, the checkout,
legal or consent links, or the merchant's "Powered by" choice.

## Review

Every submission is reviewed by BaxPay before it appears on the store. Reviewers check that:

- **It works**: readable and usable on phone and desktop, with real-world content.
- **It is honest**: name, description, category and preview image show what the buyer gets.
- **It keeps the shop safe and legal**: nothing hidden or disguised (see above), no imitation of
  BaxPay's payment page or another brand.
- **It is original**: no copy of another theme or company's look, no third-party assets you may
  not sell.
- **The price is fair** compared with the free BaxPay themes.

Only your business sees it.
Waiting for BaxPay; not on the store.
Published for every BaxPay shop, or sent back with feedback to fix and submit again.
Off the store; shops that already use it keep it.

Changing a published theme's look or price sends it back to review (it is off the store until
approved again); name, category, description, demo link and preview changes do not. Shops are not
updated automatically: a theme is copied into a shop when applied, so buyers apply it again to
get a new version. A theme can only be deleted while no shop holds a licence for it.

## Selling and getting paid

- A paid theme is sold through **your own BaxPay payment link**: you are the seller and the money
  reaches your BaxPay account and payouts like any other sale.
- BaxPay takes a **commission** on each paid sale (15 % by default), fixed when the sale starts and
  taken with the usual payment fee. You keep the rest. Prices are in euros, within the range BaxPay
  sets for the marketplace.
- A paid price needs card payments set up on your business (Settings → Payments).
- The buyer's licence turns active as soon as the link is paid. Free themes are available at once
  and carry no commission.
- A full refund or a lost dispute withdraws the buyer's licence automatically; the design already
  saved in their shop stays.
- Theme studio → **Sales** lists every sale with the price, the commission and what you keep.

## Theme studio API

The dashboard uses these endpoints with a signed-in dashboard session (not an API key). Paths are
under `https://api.baxpay.com/v1`.

- `GET /business/theme-studio` — program terms, my profile, totals
- `POST /business/theme-studio/join` — { displayName, supportEmail, websiteUrl?, bio?, acceptTerms: true }
- `PATCH /business/theme-studio/profile`
- `GET /business/theme-studio/themes`
- `POST /business/theme-studio/themes` — { name, category, start: preset | shop | file | blank, presetId?, package? }
- `GET /business/theme-studio/themes/{id}`
- `PATCH /business/theme-studio/themes/{id}` — name, category, description, demoUrl, previewImageKey, priceMinor, theme
- `POST /business/theme-studio/themes/{id}/submit`
- `POST /business/theme-studio/themes/{id}/unpublish`
- `DELETE /business/theme-studio/themes/{id}` — only without licences
- `GET /business/theme-studio/sales` — ?page=

`priceMinor` is in cents: `0`, or inside the marketplace range. `previewImageKey` is the key of an
image uploaded as shop media.

## Related

- [Online shop](https://developers.baxpay.com/online-shop) — how storefronts work
- [Storefront API](https://developers.baxpay.com/online-shop/storefront-api) — headless front ends
- [Payment links](https://developers.baxpay.com/payment-links) — how paid themes are sold
