# Audoryx theme packages

Audoryx themes are versioned, declarative ZIP packages. Administrators install them from **Admin → Themes**, then activate a package for the creator dashboard, public frontend, or both. The permanent configured default is always listed first as **Theme 1 · Default** and cannot be replaced, updated, or uninstalled. No secondary bundled/demo themes are shown: every other card represents a package installed by an administrator from ZIP.

## Security model

A theme changes presentation and declared content; it is not an application extension. Theme ZIPs therefore cannot contain PHP, JavaScript, TypeScript, HTML, Blade templates, shell files, or SVG. Remote CSS imports, executable CSS expressions, external/data URLs, unsafe archive paths, duplicate case-conflicting paths, oversized entries, and dangerous compression ratios are rejected.

This separation is intentional: accepting executable code from a browser upload would give that package the permissions of the application and would turn theme installation into remote code execution. Product logic, authorization, routes, secrets, and server policies always remain in core Audoryx code.

## Package structure

`theme.json` may be at the ZIP root or inside one top-level directory.

```text
stellar-theme.zip
├── theme.json
└── assets/
    ├── dashboard.css
    ├── frontend.css
    ├── preview.webp
    └── fonts/stellar.woff2
```

The installer accepts up to 300 files, 20 MB compressed by default, 50 MB expanded, and 10 MB per file. The compressed upload limit can be adjusted with `AUDORYX_THEME_MAX_UPLOAD_KB`.

## Manifest v3

```json
{
  "schema_version": 3,
  "key": "vendor.stellar",
  "name": "Stellar",
  "version": "1.0.0",
  "description": "A focused dashboard treatment.",
  "author": "Vendor name",
  "preview": "assets/preview.webp",
  "entrypoints": {
    "dashboard": "assets/dashboard.css"
  },
  "tokens": {
    "background": "#070a13",
    "surface": "#111827",
    "accent": "#68a7ff",
    "secondary": "#ec72c6",
    "highlight": "#9d7cff"
  },
  "pages": {
    "Studio/Home": {
      "template": "spotlight",
      "class_name": "stellar-home",
      "slots": {
        "hero.title": "Build the next sound",
        "prompt.placeholder": "Describe an idea, mood, or complete song",
        "feature.voice.title": "Begin with your voice",
        "tracks.recent_title": "Your latest sessions"
      },
      "components": {
        "studio.home.create": {
          "label": "Compose",
          "variant": "glow",
          "class_name": "stellar-create"
        }
      },
      "sections": {
        "collections": { "hidden": true }
      }
    }
  },
  "supports_frontend": false,
  "supports_dashboard": true
}
```

Keys use lowercase letters, numbers, dots, underscores, and hyphens. Versions use semantic versioning. Each supported surface must have a packaged CSS entrypoint.

## Runtime and fallback

Audoryx publishes verified assets under `/themes/{key}/{version}` and loads only the active surface stylesheet. Every surface root exposes these stable attributes:

```html
<div
  data-theme-surface="dashboard"
  data-audoryx-theme="vendor.stellar"
  data-audoryx-theme-version="1.0.0"
  data-audoryx-view="Studio/Home"
>
```

Scope selectors to the package key and, when appropriate, the view:

```css
[data-audoryx-theme="vendor.stellar"][data-audoryx-view="Studio/Home"] .studio-home__hero {
  max-width: 920px;
}
```

The runtime merges package tokens with any administrator token overrides and exposes them as `--theme-*` custom properties. Core CSS maps those properties to the stable `--ui-*` contract. A `pages` declaration affects only the named page. Pages, sections, slots, and components not declared by the package use their original core implementation automatically. Unknown declarative adapters are ignored safely, so a package cannot break unrelated pages.

The following manifest token names form the supported visual contract. Underscores and hyphens are normalized to CSS hyphens at runtime.

| Group | Tokens |
| --- | --- |
| Canvas and surfaces | `background`, `background-raised`, `surface`, `surface-raised`, `surface-soft`, `overlay` |
| Typography | `text`, `muted`, `muted-bright`, `font-sans`, `font-display` |
| Lines and depth | `border`, `border-strong`, `shadow-color` |
| Brand and status | `accent`, `secondary`, `highlight`, `info`, `success`, `warning`, `danger` |
| Light mode | Prefix any colour token with `light-`, for example `light-background`, `light-surface`, `light-text`, or `light-border` |

Core components consume semantic `--ui-*` values rather than package-specific colours. A package can still apply tightly scoped CSS for layout or component variations:

```css
[data-audoryx-theme="vendor.stellar"] {
  --theme-font-display: "Stellar Sans", sans-serif;
}

[data-audoryx-theme="vendor.stellar"][data-theme-surface="frontend"] .contact-card {
  border-radius: 1.25rem;
}
```

The two canonical surfaces are `frontend` and `dashboard`. `frontend` covers landing, contact, legal, authentication, and account pages. `dashboard` covers the creator studio and admin console. Schema v1/v2 packages using `marketing` or `supports_marketing` remain installable and are normalized to `frontend`.

The current `Studio/Home` adapter supports:

- Text slots: `search.placeholder`, `hero.title`, `prompt.placeholder`, `prompt.create_label`, `feature.voice.*`, `feature.model.*`, `tracks.recent_title`, `tracks.voice_title`, `tracks.other_title`, and `collections.title`.
- Component: `studio.home.create` with `label`, `variant`, and `class_name`.
- Sections: `featured_tools`, `recent_tracks`, and `collections`, each supporting `{ "hidden": true }`.
- Page presentation: `template` and `class_name`, exposed as CSS hooks.

Other pages can always be restyled through the stable root view attribute. Content or structural replacement requires a core declarative adapter, preserving validation, accessibility, and application behavior.

## Per-theme landing content

Landing-page content is stored separately for every installed frontend theme. In **Admin → Brand & content → Landing hero / Landing sections**, select a theme before editing. The selected theme owns its headlines, supporting copy, button labels, prompt and player labels, section visibility, repeatable lists, trusted logos, and light/dark artwork. Activating another frontend theme automatically resolves that theme's saved landing content without copying or overwriting the previous theme.

The theme-scoped contract also covers the public navigation and header actions, composer mode and validation messages, trending playback/empty-state/accessibility labels, the optional voice-to-song experience, pricing interval and credit labels, and every footer navigation/right-notice label. These strings are data rather than component constants, so a frontend theme can carry a complete vocabulary alongside its visual identity. Section toggles—including the voice experience—remain independent per theme.

Resolution is deterministic: core defaults for the selected theme are loaded first, intentional legacy `landing.*` site settings are applied as a compatibility fallback, and the selected theme's saved values win last. Uploaded media is isolated under a theme-specific `site-content/themes/{theme-key}` directory, so replacing an image in one theme cannot delete or change another theme's asset. A theme package update preserves this administrator-managed content; uninstalling an inactive uploaded theme removes its private landing uploads.

The theme package remains responsible for presentation. Core landing adapters remain responsible for validated content, accessible markup, section behavior, and safe media handling. This keeps browser-uploaded themes declarative while still allowing administrators to customize the complete public story independently per theme.

## Installation lifecycle

Installation is staged and published atomically. The database record is created only after the source and public assets are complete. Activation changes only supported surfaces inside a database transaction. An uploaded package cannot be uninstalled while active; activate Theme 1 or another package first.

Use **Update** on an uploaded theme card to provide a complete replacement ZIP. The package key must stay unchanged and its semantic version must be newer than the installed version. Audoryx publishes the new version first, switches the database manifest without changing activation state, and then retires the old version. If validation or publication fails, the previous source, assets, manifest, and active state remain intact.

See `documentation/theme-package-example/` for a valid source package and `documentation/theme.schema.json` for editor validation.
