Theme Marketplace
40 premium branding packages, 7 categories, 5 pricing tiers, per-page overrides, copy-on-apply snapshot semantics, and a full clean-architecture data layer — all seeded and ready to browse.
The Theme Marketplace is SCRIPE's curated catalog of 40 production-ready branding packages. Each theme is a comprehensive visual identity — not just a color swap — containing 50+ design tokens spanning colors, typography, spacing, overlay, dark mode, branding panel, and per-page overrides for Login, Forgot Password, and Reset Password pages. Themes are stored as structured JSON in the LoginTheme entity, browsable via a full-page gallery with rich filtering, and applied to tenant settings with copy-on-apply snapshot semantics that permanently isolate applied configurations from future marketplace updates.
Marketplace Architecture
The Theme Marketplace follows a pipeline architecture: backend seeder populates the LoginTheme table with 40 records → API exposes paginated list, detail, and apply endpoints → frontend gallery renders themes with advanced filtering → apply action snapshots the ThemeDataJson into tenant's DraftBrandingJson → publish propagates to LiveBrandingJson. Each layer is fully decoupled.
Data Flow Pipeline
1. LoginThemeSeeder.cs seeds 40 themes at application startup (upsert-safe). 2. ThemesController exposes GET /themes (list), GET /themes/{id} (detail), POST /themes/{id}/apply (apply). 3. Frontend ThemeMarketplaceService calls the API via IApiService. 4. ThemeMarketplaceMapper converts DTOs to domain entities. 5. ThemeMarketplaceRepository orchestrates services + mappers. 6. useThemeMarketplace hook provides ViewModel state. 7. ThemeGalleryView renders the marketplace UI.
Clean Architecture Layers
The marketplace follows SCRIPE's standard 3-layer module structure: Domain layer (ThemeDetail entity, IThemeMarketplaceRepository, IThemeMarketplaceService interfaces), Data layer (ThemeMarketplaceService, ThemeMarketplaceRepository, ThemeMarketplaceMapper, ThemeMarketplaceTypes models), and Presentation layer (ThemeGalleryView, ThemeManagementView, ThemeDetailModal, ThemeCard, useThemeMarketplace hook).
src/modules/system/customization/
├── src/
│ ├── domain/
│ │ ├── entities/ThemeDetail.ts # Rich domain entity
│ │ └── interfaces/
│ │ ├── IThemeMarketplaceService.ts # HTTP service contract
│ │ └── IThemeMarketplaceRepository.ts
│ ├── data/
│ │ ├── models/ThemeMarketplaceTypes.ts # Raw DTOs
│ │ ├── services/ThemeMarketplaceService.ts
│ │ ├── repositories/ThemeMarketplaceRepository.ts
│ │ └── mappers/ThemeMarketplaceMapper.ts
│ └── presentation/
│ ├── views/
│ │ ├── ThemeGalleryView.tsx # 26KB — Full-page marketplace
│ │ └── ThemeManagementView.tsx # 12KB — Admin CRUD
│ ├── components/
│ │ ├── ThemeDetailModal.tsx # 28KB — Detail + preview
│ │ └── ThemeCard.tsx # Gallery card
│ └── hooks/
│ └── useThemeMarketplace.ts # ViewModel hookLoginTheme Entity
Each marketplace theme is stored as a LoginTheme entity in the Identity module's database. The entity extends AuditableEntity, providing soft-delete, audit trail, and optimistic concurrency. The core data is stored in ThemeDataJson — a JSON column containing the full design specification.
Entity Fields
| Field | Type | Description |
|---|---|---|
| Name | string (unique) | Human-readable theme name (e.g., 'Midnight Aurora') |
| Category | enum | corporate, creative, dark, elegant, luxury, minimal, nature |
| Description | string | Marketing-quality description of the visual identity |
| ThumbnailUrl | string? | Preview image URL for gallery cards |
| PreviewUrl | string? | Full-size preview for the detail modal |
| ThemeDataJson | JSON | Complete design specification (50+ tokens) |
| Tier | enum | Free, Starter, Professional, Enterprise, StandaloneAddon |
| IsSystemTheme | bool | System themes are seeded and cannot be deleted |
| IsActive | bool | Inactive themes hidden from gallery |
| Tags | string? | Comma-separated search tags |
| Version | string | Semantic version (e.g., '1.0.0') |
| Author | string | Creator identifier |
| LikesCount | int | Engagement: how many tenants favorited |
| AppliedCount | int | Usage: how many tenants applied |
ThemeDataJson Schema (50+ Design Tokens)
The ThemeDataJson column stores a comprehensive JSON object containing every visual parameter needed to fully render a branded login page. The schema is versioned and contains 7 major sections: layout, colors, dark mode colors, typography, spacing, overlay, and branding panel. Each token maps directly to a CSS custom property via the useLoginBrandingTokens pipeline.
Layout Configuration
Controls the page structure: selectedLayout (one of 22 layout identifiers), loginPosition (left/center/right), formWidth, formMaxWidth, containerPadding, and formAlignment. Layout selection determines which rendering strategy the LoginPage component uses.
Color System (16 Tokens)
The colors section defines the complete light-mode palette: primaryColor (brand accent), secondaryColor (complementary), backgroundColor (page background), formBackground (form card), textColor (primary text), secondaryTextColor (muted text), inputBackground (form input fields), inputBorderColor, inputTextColor, buttonColor (primary CTA), buttonTextColor, buttonHoverColor, linkColor, linkHoverColor, borderColor (general borders), and accentColor (highlights/badges).
Dark Mode Color System (10 Tokens)
Independent dark-mode palette: darkEnabled (boolean toggle), darkFormBackground, darkTextColor, darkInputBackground, darkInputBorderColor, darkInputTextColor, darkButtonColor, darkButtonTextColor, darkSecondaryTextColor, and darkBorderColor. These tokens are emitted as --login-dark-* CSS variables and activated via the [data-theme='dark'] selector.
Typography Configuration (8 Tokens)
Controls all text rendering: fontFamily (Google Fonts name, e.g., 'Playfair Display'), headingFontFamily (optional separate heading font), fontSize (base size in px), headingSize, labelSize, inputFontSize, fontWeight (normal/medium/semibold/bold), and letterSpacing. Fonts are loaded dynamically via the Google Fonts CDN.
Spacing & Dimensions (6 Tokens)
Controls layout geometry: borderRadius (global border-radius in px), inputBorderRadius, buttonBorderRadius, inputHeight (in px), buttonHeight, and gap (spacing between form elements). These values are emitted as CSS custom properties and applied uniformly across all 22 layouts.
Overlay & Effects (8 Tokens)
Controls visual effects layered on backgrounds: overlayColor (RGBA), overlayOpacity (0–100%), overlayBlur (0–20px in Gaussian blur), backgroundType ('color', 'gradient', 'image'), backgroundValue (CSS gradient string or image URL), backgroundSize, backgroundPosition, and backgroundRepeat. Overlay settings can be scoped to the form section or branding panel independently.
Branding Panel Configuration (12 Tokens)
Controls the branding side of split layouts: panelLogo (URL), panelHeadline (heading text), panelSubtitle (subheading text), panelHeadlineColor, panelSubtitleColor, panelBackgroundType, panelBackgroundValue, panelOverlayColor, panelOverlayOpacity, panelOverlayBlur, panelLogoSize (small/medium/large), and panelAlignment (left/center/right). These tokens are only rendered in T1 Split layouts.
| Section | Token Count | CSS Prefix | Controlled By |
|---|---|---|---|
| Layout | 6 | --login-layout-* | Layout selector tab |
| Colors (Light) | 16 | --login-* | Color picker tab |
| Colors (Dark) | 10 | --login-dark-* | Dark mode panel |
| Typography | 8 | --login-font-* | Typography tab |
| Spacing | 6 | --login-spacing-* | Spacing controls |
| Overlay | 8 | --login-overlay-* | Overlay panel |
| Branding Panel | 12 | --login-panel-* | Branding settings |
Per-Page Branding Architecture
Each theme can define independent visual overrides for three authentication pages: Login, Forgot Password, and Reset Password. The 'pages' block in ThemeDataJson contains page-specific layout, headline, subtitle, overlay, and background settings that are merged on top of the global design when that page is active. This enables a single theme to present different messaging and visual treatments for different auth flows.
Pages Block Structure
The 'pages' object in ThemeDataJson contains three optional keys: 'login', 'forgotPassword', and 'resetPassword'. Each key maps to a page override object with fields: selectedLayout, panelHeadline, panelSubtitle, overlayColor, overlayOpacity, backgroundType, backgroundValue, and any other token that should differ from the global configuration.
{
"version": "2.0",
"selectedLayout": "split-right",
"primaryColor": "#1e40af",
"fontFamily": "Inter",
// ... 50+ global tokens ...
"pages": {
"login": {
"panelHeadline": "Welcome Back",
"panelSubtitle": "Sign in to continue"
},
"forgotPassword": {
"selectedLayout": "centered",
"panelHeadline": "Password Recovery",
"panelSubtitle": "We'll help you get back in",
"overlayColor": "rgba(30, 64, 175, 0.3)"
},
"resetPassword": {
"panelHeadline": "Create New Password",
"panelSubtitle": "Choose a strong password"
}
}
}Merge Strategy
When a tenant previews a theme's Forgot Password page, the frontend merges the global design tokens with the forgotPassword override using spread semantics: { ...globalTokens, ...pages.forgotPassword }. This means any token not specified in the page override inherits from the global design — only the explicitly overridden values change. The merge happens in the previewTheme() function in useStudioViewModel.ts.
40-Theme Catalog Overview
SCRIPE ships with 40 meticulously designed branding packages. Each theme is a unique visual identity crafted for a specific market segment or brand aesthetic. Themes span 7 categories, use 30+ different Google Fonts, cover all 22 layouts, and include per-page branding overrides for Login, Forgot Password, and Reset Password.
Design Diversity Matrix
The 40-theme catalog achieves maximum diversity across multiple axes: each theme uses a unique Google Font pairing, no two themes share the same color palette, all 7 categories are represented, and the layout distribution covers T1 Split (16), T2 Full-Page (12), T3 Centered (6), and T4 Special (6). This ensures every tenant can find a theme that matches their brand identity.
| Layout Family | Theme Count | Percentage |
|---|---|---|
| T1 Split (split-right, split-left, magazine, etc.) | 16 | 40% |
| T2 Full-Page (branded-full, overlay, immersive) | 12 | 30% |
| T3 Centered (centered, floating-card) | 6 | 15% |
| T4 Special (glass-morphism, corner-card) | 6 | 15% |
5-Tier Pricing Model
Themes are organized into 5 pricing tiers that align with SCRIPE's edition system. Each tier provides increasing design sophistication and customization depth. Tier enforcement is handled by the theme marketplace frontend — themes from higher tiers display an 'Upgrade Required' badge and disable the Apply button for tenants on lower editions.
| Tier | Themes | Font Complexity | Design Level |
|---|---|---|---|
| Free | 8 | Standard (Inter, Poppins) | Clean, professional |
| Starter | 8 | Premium (Outfit, Nunito Sans) | Rich palettes, gradients |
| Professional | 10 | Editorial (DM Serif, Sora) | Glass-morphism, multi-tone |
| Enterprise | 8 | Luxury (Cormorant, Italiana) | Cinematic, exclusive dark |
| Standalone Add-on | 6 | Display (Cinzel Decorative) | Botanical, Art Deco, Brutalist |
7 Theme Categories
Every theme belongs to exactly one category. Categories enable intuitive gallery browsing and filtering. The distribution ensures broad coverage: Corporate (8), Creative (6), Dark (6), Minimal (5), Elegant (5), Luxury (5), Nature (5).
| Category | Count | Target Market | Design DNA |
|---|---|---|---|
| Corporate | 8 | Finance, consulting, law | Serif/sans-serif pairs, navy/gray, subtle gradients |
| Creative | 6 | Agencies, startups, tech | Vibrant colors, Poppins/Quicksand, animated gradients |
| Dark | 6 | Dev tools, media, gaming | Deep backgrounds, cyan/amber accents, glass effects |
| Minimal | 5 | Productivity, SaaS | Monochromatic, whitespace, thin borders |
| Elegant | 5 | Beauty, fashion, hospitality | Rose gold, Playfair Display, delicate overlays |
| Luxury | 5 | High-end, private banking | Black/gold, Italiana, full-bleed imagery |
| Nature | 5 | Sustainability, wellness | Forest greens, terracotta, rounded shapes |
Frontend Component Inventory
The Theme Marketplace frontend consists of 8 purpose-built components spanning 3 pages and 1 modal. Each component follows SCRIPE's presentation-layer patterns using domain entities (never DTOs) and consuming data exclusively through the DI container.
| Component | Size | Purpose |
|---|---|---|
| ThemeGalleryView | 26KB | Full-page marketplace — hero, filters, grid/list, pagination |
| ThemeManagementView | 12KB | Admin CRUD — DataTable with bulk operations |
| ThemeDetailModal | 28KB | Detail preview — feature matrix, apply, like/favorite |
| ThemeCard | ~5KB | Gallery card — palette strip, font preview, engagement |
| ThemeMarketplacePanel | ~4KB | Inline studio panel with quick-apply |
Theme Application Flow
Applying a marketplace theme follows a 5-step pipeline: 1. User clicks Apply on a theme. 2. Frontend reads the theme's ThemeDataJson. 3. previewTheme() in useStudioViewModel merges the design tokens (including per-page overrides) into the current StudioDraft. 4. The merged draft is saved to the tenant's DraftBrandingJson via PUT /tenants/{id}/settings. 5. Admin publishes the draft to make it live.
Copy-on-Apply Snapshot Semantics
When a theme is applied, the ThemeDataJson is COPIED into the tenant's DraftBrandingJson — not linked. This means the tenant's branding is permanently isolated from future marketplace updates. If the theme is updated in v2.0, existing tenants who applied v1.0 retain their v1.0 snapshot. This prevents unexpected visual changes to production login pages.
Preview Before Apply
The previewTheme() function in useStudioViewModel.ts performs a non-destructive preview by temporarily injecting theme tokens into the draft state. The preview is displayed in the sandboxed iframe via postMessage CSS variable injection. The draft is NOT saved until the user explicitly confirms the application. Canceling the preview restores the previous draft state.
Backend Seeding Architecture
All 40 themes are seeded at application startup by LoginThemeSeeder.cs. The seeder uses an upsert-safe strategy: it checks for existing themes by Name and only inserts new ones — existing themes are never overwritten. This ensures idempotent deployment across environments.
Build() Helper Architecture
The seeder uses a fluent Build() helper with ThemeMeta and ThemeDesign records for clean theme definition. ThemeMeta contains name, category, tier, description, author, version, and tags. ThemeDesign contains all 50+ design tokens plus per-page overrides (PageOverrideDesign records for ForgotPassword and ResetPassword). The BuildFullThemeJson() method serializes the ThemeDesign record into the JSON format expected by the frontend.
// ThemeMeta: name, category, tier, description, author, version, tags
// ThemeDesign: 50+ tokens + PageOverrideDesign records
private static LoginTheme Build(ThemeMeta meta, ThemeDesign design)
{
return new LoginTheme
{
Name = meta.Name,
Category = meta.Category,
Tier = meta.Tier,
Description = meta.Description,
Author = meta.Author,
Version = meta.Version,
Tags = meta.Tags,
IsSystemTheme = true,
IsActive = true,
ThemeDataJson = BuildFullThemeJson(design),
};
}Upsert-Safe Strategy
The seeder queries all existing theme names before processing. For each of the 40 themes, it checks the existing set — if the name exists, the theme is skipped. New themes are added to the DbContext in a single batch and saved with one SaveChangesAsync call. This makes the seeder safe to run repeatedly without duplicating themes or losing manual edits.
Marketplace Governance
Theme access is controlled by a combination of edition-based tier enforcement and permission-based administrative access. The marketplace respects SCRIPE's multi-tenancy model — themes are globally visible but apply operations are scoped to the current tenant.
| Action | Required Permission | Enforcement |
|---|---|---|
| Browse marketplace | branding.view | Frontend visibility |
| Apply theme | branding.manage | Backend verification |
| Manage themes (CRUD) | themes.manage | System Admin only |
| Like/favorite | branding.view | Per-admin tracking |
Developer Profiles & Author Metadata
Themes can include creator attribution, documentation links, and verification status for community and partner theme submissions.
Tier & Edition Purchase Verification
Commercial themes require tenant edition verification before application, validating against the active tenant subscription tier.
Theme Download & Asset Package Verification
Theme asset bundles (fonts, high-resolution backgrounds) undergo SHA-256 integrity verification upon extraction.
Theme API Endpoints
The theme marketplace exposes endpoints through the existing TenantSettings and Themes controllers. Theme data is served as part of the branding configuration pipeline.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/themes | Paginated list with category, tier, search filters |
| GET | /api/v1/themes/{id} | Full detail including ThemeDataJson + counters |
| POST | /api/v1/themes/{id}/apply | Apply to current tenant's draft settings |
| POST | /api/v1/themes/{id}/like | Toggle like/favorite for current admin |
| POST | /api/v1/themes | Create new theme (System Admin) |
| PUT | /api/v1/themes/{id} | Update theme (System Admin) |
| DELETE | /api/v1/themes/{id} | Soft-delete theme (System Admin) |
Source File Reference
Backend: LoginThemeSeeder.cs (seeder), LoginTheme.cs (entity), ThemeConfiguration.cs (EF config)
Frontend: ThemeGalleryView.tsx, ThemeManagementView.tsx, ThemeDetailModal.tsx, ThemeCard.tsx
Data Layer: ThemeMarketplaceService.ts, ThemeMarketplaceRepository.ts, ThemeMarketplaceMapper.ts, ThemeMarketplaceTypes.ts
Domain Layer: ThemeDetail.ts (entity), IThemeMarketplaceService.ts, IThemeMarketplaceRepository.ts
ViewModel: useThemeMarketplace.ts (gallery state), useStudioViewModel.ts (preview/apply integration)