# Page themes Public shows use the default React SPA feed unless you pick a **page theme** under **Page Customizations**. Packaged themes are Liquid templates with CSS, optional images/fonts, and HarborFM mount points for interactive blocks (episodes, player, cast, reviews, and so on). For show-level toggles (accent, which blocks appear), see [Podcasts: Page Customizations](Podcasts.md#page-customizations). For listener-facing feed behavior, see [Public feeds and embed](Public-feeds-and-embed.md). ## Choosing a theme 1. Open the show and click **Page Customizations**. 2. Pick a **Page Theme**: - **Default HarborFM** - built-in React feed layout. - **Server-wide themes** - Fluid, Folio, and any theme an admin has promoted. - **Imported themes** - packages you imported under **Themes** (on podcasts you own). 3. Optionally set **Primary Color** and visibility toggles (author, descriptions, funding, podroll, cast, reviews, and related blocks). 4. Optionally set a **Meta Pixel ID**. Public podcast and episode pages load the pixel; when the site consent banner is enabled, it loads only after analytics consent. The picker shows package preview images when the theme provides one. When a theme sets `homepage` in `theme.json`, **Preview** opens that live URL. Visibility toggles and accent color apply to both Default and packaged themes. Packaged themes can read accent colors and `show.*` flags in template context. ## Themes studio Users with **Can Import Theme** can open **Themes** to: - Browse **Server Themes** (preview thumbnails, description, live Preview when `homepage` is set) - **Download** a server theme as a zip - **Import** a theme zip (max 10 MB; rate limited to 2 per minute per user) - Edit an imported copy in the near-fullscreen theme editor (name, version, home template, page routes, Liquid/CSS/images/fonts) Re-importing the same `theme.json` `id` updates that theme in place (including personal copies of server-wide themes). Theme files count toward the account storage limit. Assigning a **server-wide** theme id uses the data-backed server package. To use your fork, pick your imported copy’s row in Page Customizations. ### Admins Admins can **promote** a personal theme to a server-wide theme (available to every show), **demote** it back, delete server themes, and edit server theme files. Admin edits and promote set `allowOverride: false` so customized server themes are not overwritten on upgrade. ## Built-ins and storage **Fluid** and **Folio** ship with HarborFM and are seeded into `{DATA_DIR}/themes/server` on first boot. Folio is multi-page (home plus about, crew, support, connect, episodes). Both include preview images, homepage demo links, and optional `not_found` templates for unknown theme `.html` routes. On upgrade, HarborFM replaces a data copy only when its `theme.json` still allows override (default) and the shipped `version` differs. Packages under `{DATA_DIR}/themes/server` persist across Docker image updates. ## Gallery and authoring docs Community/gallery themes (with previews) are published from [`harborfm-themes`](https://github.com/LoganRickert/harborfm-themes) and listed on the docs [Themes](https://harborfm.com/themes/) page. Authoring guides: - Docs: [Theme Authoring Guide](https://harborfm.com/theme-guide/) - Repo / AI skill: [theme-SKILL.md](../blob/main/theme-SKILL.md) (also downloadable as **SKILL.md** from the Themes page) Local developers can clone `harborfm-themes/` next to the HarborFM tree and run `pnpm themes:sync` to copy gallery packages into `{DATA_DIR}/themes/server`. See the main [README](../blob/main/README.md#page-themes). ## Package layout ``` theme.json required (id, name, version; optional fields below) templates/podcast.liquid required templates/episode.liquid required templates/*.liquid optional extra pages and _partials css/ optional (*.css; all auto-linked) images/ optional (png, jpg, jpeg, gif, webp) fonts/ optional (.woff2, .ttf) ``` Optional `theme.json` fields: | Field | Purpose | |-------|---------| | `description` | Short blurb for Themes cards and the gallery | | `preview` | Path under `images/` for picker / gallery cards | | `homepage` | https URL for live Preview | | `index` | Home template basename (default `podcast`) | | `pages` | Map template basename → public `.html` filename | | `not_found` | Template for unknown theme pages (HTTP 404; not a public page) | | `allowOverride` | When `false`, shipped upgrades do not overwrite this server theme | Extra templates become `/feed/{slug}/{page}.html` (or `/{page}.html` on a custom domain). Theme pages are included in the podcast sitemap. ### Mounts and actions Interactive HarborFM blocks use Liquid mounts: ```liquid {% render 'harborfm/episodes' %} {% render 'harborfm/player' %} {% render 'harborfm/search' %} {% render 'harborfm/reviews' %} {% render 'harborfm/cast' %} {% render 'harborfm/funding' %} {% render 'harborfm/links' %} {% render 'harborfm/podroll' %} {% render 'harborfm/site_header' %} {% render 'harborfm/show_header' %} {% render 'harborfm/breadcrumbs' %} ``` Put `harborfm/episodes` on exactly one page. Themes can also wire native controls: ```html RSS ``` Unavailable actions are hidden automatically. Script tags, event handlers, `javascript:` URLs, and the Liquid `| raw` filter are rejected on import. ### Liquid context and styling Useful context includes `podcast`, `episode` / `episodes`, `cast`, `funding_links`, `links`, `podroll`, `reviews`, `theme` (id/name/version/homepage), `accent` (including `accent.fg` for text on accent), `show` (visibility booleans), `urls` (`home`, `pages`, `theme_asset_base`), `site`, and `page`. Accent CSS variables include `--accent`, `--accent-dim`, `--accent-glow`, and `--accent-fg`. Dialogs (Message, Get Alerts, Share, Subscribe, Write Review) render under themed chrome so theme CSS can style them, for example: ```css [data-harborfm-dialog="feedback"] { /* Message */ } [data-harborfm-dialog="alerts"] { /* Get Alerts */ } [data-harborfm-dialog="share"] { /* Share */ } [data-harborfm-dialog-overlay="feedback"], [data-harborfm-dialog-overlay="alerts"] { /* backdrop */ } ``` Theme partials (`templates/_nav.liquid`, etc.) use `{% render %}` with an isolated scope. Pass context explicitly: ```liquid {% render '_nav', urls: urls, podcast: podcast, site: site, page: page %} ``` ## Who can import themes - **`defaultCanImportTheme`** (Settings > Default Limits) - whether newly registered users can import and edit themes. Default is on. - **`canImportTheme`** (Users admin) - per-user flag. When off, that user cannot import, download, or edit theme files (403). Selecting a built-in or server theme on a show they manage still works when the theme is available. See [Admin and settings](Admin-and-settings.md). ## See also - [Podcasts: Page Customizations](Podcasts.md#page-customizations) - [Public feeds and embed](Public-feeds-and-embed.md) - [Admin and settings](Admin-and-settings.md) - Main [README: Page themes](../blob/main/README.md#page-themes) - [Theme Authoring Guide](https://harborfm.com/theme-guide/) - [Themes gallery](https://harborfm.com/themes/)