diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md new file mode 100644 index 0000000..1822abe --- /dev/null +++ b/RECOMMENDED_FONTS.md @@ -0,0 +1,39 @@ +# LCARS — recommended fonts + +This theme **bundles** two libre families (OFL-1.1) so it renders on-brand with +no admin setup: + +| Slot | Bundled family | Role | +|------|----------------|------| +| Heading | **Antonio** | Tall condensed grotesque for LCARS labels, headings, buttons, and nav pills. | +| Body | **Antonio** | Same family for panel copy; condensed and legible at reading sizes. | +| Mono | **Share Tech Mono** | Numeric readouts, stardate stamps, statistics, prices, metadata. Tabular by design. | + +Bundled files live in `assets/fonts/` (latin subsets); licenses in +`assets/fonts/LICENSES.md`. The theme CSS resolves every family through +`var(--font-heading | --font-body | --font-mono, )`, and the fallback +stacks name the bundled families, so the identity holds even before an admin +touches the typography panel. + +## Optional upgrade — Roboto Condensed for body (Google Fonts tab) + +The locked direction pairs Antonio labels with **Roboto Condensed** (Apache-2.0) +for longer readout copy. Roboto Condensed is not bundled here (no libre woff2 was +available in the workspace at build time), so the body fallback stack degrades to +Antonio then a system condensed sans (`"Arial Narrow", system-ui`). To match the +locked direction exactly, assign Roboto Condensed to the **Body** slot from the +Google Fonts tab: + +| Slot | Source | Family | Why | +|------|--------|--------|-----| +| Body | `google:Roboto Condensed` | Roboto Condensed | The locked body face for readout copy. Wider aperture than Antonio at small sizes. | +| Mono | `google:Share Tech Mono` | Share Tech Mono | Already bundled; also available in the picker if you prefer the Google source. | + +## How to apply an override + +1. Open **Admin → Theme → Typography**. +2. Switch to the **Google Fonts** tab. +3. Search and pick the family. +4. Assign it to the matching slot (Heading / Body / Mono). +5. Save. The pick is stored as `google:` and binds the `--font-*` + variable, overriding the bundled fallback. diff --git a/RECOMMENDED_ICONS.md b/RECOMMENDED_ICONS.md new file mode 100644 index 0000000..746d1a9 --- /dev/null +++ b/RECOMMENDED_ICONS.md @@ -0,0 +1,15 @@ +# LCARS — required icon packs + +The theme renders icons via the sprite system only +(`` and the `::pack:name::` +shorthand). It declares its packs in `manifest.yaml` `required_icon_packs`. +Until the loader auto-installs declared packs, install them by hand before +activation. + +| Pack | Used for | Install | +|------|----------|---------| +| **lucide** | All UI accents: arrows, chevrons, checks, contact channels, nav toggle, play controls, mail/phone marks. Bundled with the CMS. | Ships with the CMS. Verify it is enabled at `/admin/icons`. | + +lucide ships with BlockNinja, so no npm download is needed. If a block picks an +icon from a different pack, install that pack at `/admin/icons` first (`lucide` +is the safe default fallback for any icon-picker field). diff --git a/assets/fonts/LICENSES.md b/assets/fonts/LICENSES.md new file mode 100644 index 0000000..80a649b --- /dev/null +++ b/assets/fonts/LICENSES.md @@ -0,0 +1,21 @@ +# Bundled font licenses + +Both bundled families are libre and redistributable. Latin subsets only. + +## Antonio + +- Files: `antonio-latin.woff2`, `antonio-latin-ext.woff2` (variable weight axis; theme uses 300/400/600/700) +- Family: Antonio (Vernon Adams / Marcelo Magalhaes) +- License: SIL Open Font License 1.1 (OFL-1.1) +- Source: https://fonts.google.com/specimen/Antonio — latin and latin-ext subsets as distributed by google-webfonts-helper. +- Role: LCARS labels, headings, buttons, nav pills. A tall condensed grotesque that reads as library-computer type. + +## Share Tech Mono + +- File: `share-tech-mono-latin.woff2` (weight 400) +- Family: Share Tech Mono (Carrois Apostrophe) +- License: SIL Open Font License 1.1 (OFL-1.1) +- Source: https://fonts.google.com/specimen/Share+Tech+Mono — latin subset as distributed by google-webfonts-helper. +- Role: numeric readouts, stardate stamps, metadata, statistics, prices. Tabular by design. + +The full OFL-1.1 text applies to both. See https://openfontlicense.org. diff --git a/assets/fonts/share-tech-mono-latin.woff2 b/assets/fonts/share-tech-mono-latin.woff2 new file mode 100644 index 0000000..f48075f Binary files /dev/null and b/assets/fonts/share-tech-mono-latin.woff2 differ diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml index 8ed7faf..8274bf9 100644 --- a/blocks/blocks.yaml +++ b/blocks/blocks.yaml @@ -1,28 +1,36 @@ -# LCARS theme block definitions (codeless .bnp, WO-WZ-021). +# LCARS theme block definitions (codeless .bnp, WO-TF-020). # Keys are fully qualified (lcars:) because the codeless loader registers # definitions verbatim — there is no PluginBlockRegistry prefixing in the -# codeless path (unlike the retired wasm/pongo-guest path). The qualified keys -# match the master_pages.json block references. +# codeless path. The qualified keys match the data-block attributes and the +# master_pages.json / seed references. # -# header and sidebar previously used MustBlockTemplateWithDefaults; the codeless -# path has no defaults-merge, so their defaults are baked into the templates via -# |default filters (title/subtitle) and the {% empty %} branch (sidebar items). +# Only true persona furniture stays a theme block. Everything that duplicated a +# builtin (the old lcars_header navbar, lcars_panel card, lcars_sidebar rail) is +# now a template override of the builtin key or page-template chrome instead. +# All four blocks below are content-map-only (no data providers), so they render +# fully under the frozen codeless loader. blocks: - - key: lcars:lcars_header - title: LCARS Header - description: Top bar with elbow bracket, status indicators, and stardate - category: navigation - schema: lcars_header.schema.json - template: lcars_header.ninjatpl - - key: lcars:lcars_sidebar - title: LCARS Sidebar - description: Left panel with rounded-rectangle navigation buttons - category: navigation - schema: lcars_sidebar.schema.json - template: lcars_sidebar.ninjatpl - - key: lcars:lcars_panel - title: LCARS Panel - description: Framed content area with LCARS border treatment - category: layout - schema: lcars_panel.schema.json - template: lcars_panel.ninjatpl + - key: lcars:episode_console + title: Episode Console + description: Featured podcast episode as an LCARS playback console with duration readout, stardate, and platform channels. + category: content + schema: episode_console.schema.json + template: episode_console.ninjatpl + - key: lcars:crew_roster + title: Crew Roster + description: Roster of hosts and crew as LCARS ID panels with role and callsign. + category: content + schema: crew_roster.schema.json + template: crew_roster.ninjatpl + - key: lcars:stardate_log + title: Stardate Log + description: Dated log entries on a numeric stardate rail — a captain's-log timeline. + category: content + schema: stardate_log.schema.json + template: stardate_log.ninjatpl + - key: lcars:transmission_status + title: Transmission Status + description: Status board of channels and systems as LCARS readouts with online, standby, or alert states. + category: content + schema: transmission_status.schema.json + template: transmission_status.ninjatpl diff --git a/blocks/crew_roster.ninjatpl b/blocks/crew_roster.ninjatpl new file mode 100644 index 0000000..821dce6 --- /dev/null +++ b/blocks/crew_roster.ninjatpl @@ -0,0 +1,11 @@ +
+{% if eyebrow %}

{{ eyebrow }}

{% endif %} +{% if heading %}

{{ heading }}

{% endif %} +{% if members %}
{% for m in members %} +
+
{% if m.avatar %}{% img m.avatar alt=m.name|default:"Crew" %}{% else %}{% endif %}
{% if m.name %}
{{ m.name }}
{% endif %}{% if m.role %}
{{ m.role }}
{% endif %}
+{% if m.callsign %}
Callsign {{ m.callsign }}
{% endif %} +{% if m.bio %}

{{ m.bio }}

{% endif %} +
+{% endfor %}
{% endif %} +
diff --git a/blocks/crew_roster.schema.json b/blocks/crew_roster.schema.json new file mode 100644 index 0000000..577cbdf --- /dev/null +++ b/blocks/crew_roster.schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Crew Roster", + "description": "Hosts and crew as LCARS ID panels.", + "type": "object", + "properties": { + "eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" }, + "heading": { "type": "string", "title": "Heading", "x-editor": "text" }, + "members": { + "type": "array", + "title": "Crew", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "name": { "type": "string", "title": "Name", "x-editor": "text" }, + "role": { "type": "string", "title": "Role", "x-editor": "text" }, + "callsign": { "type": "string", "title": "Callsign", "x-editor": "text" }, + "bio": { "type": "string", "title": "Bio", "x-editor": "textarea" }, + "avatar": { "type": "string", "title": "Portrait", "x-editor": "media" } + } + } + } + } +} diff --git a/blocks/episode_console.ninjatpl b/blocks/episode_console.ninjatpl new file mode 100644 index 0000000..ee45df2 --- /dev/null +++ b/blocks/episode_console.ninjatpl @@ -0,0 +1,14 @@ +
+
+
+
{% if cover %}{% img cover alt=title|default:"Episode cover" %}{% else %}
{% endif %}
+
+
{% if eyebrow %}{{ eyebrow }}{% else %}Now Broadcasting{% endif %}
+
{% if episodeNumber %}EP {{ episodeNumber }}{% endif %}{% if stardate %}Stardate {{ stardate }}{% endif %}{% if duration %}{{ duration }}{% endif %}
+{% if title %}

{{ title }}

{% endif %} +{% if description %}

{{ description }}

{% endif %} +
{% if listenUrl %}::lucide:play:sm::{{ listenLabel|default:"Listen now" }}{% endif %}{% for p in platforms %}{{ p.label }}{% endfor %}
+
+
+
+
diff --git a/blocks/episode_console.schema.json b/blocks/episode_console.schema.json new file mode 100644 index 0000000..a16dc3a --- /dev/null +++ b/blocks/episode_console.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Episode Console", + "description": "Featured episode as an LCARS playback console.", + "type": "object", + "properties": { + "eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" }, + "title": { "type": "string", "title": "Episode title", "x-editor": "text" }, + "episodeNumber": { "type": "string", "title": "Episode number", "description": "e.g. \"047\"", "x-editor": "text" }, + "duration": { "type": "string", "title": "Duration readout", "description": "e.g. \"58:12\"", "x-editor": "text" }, + "stardate": { "type": "string", "title": "Stardate", "description": "e.g. \"78412.3\"", "x-editor": "text" }, + "description": { "type": "string", "title": "Description", "x-editor": "textarea" }, + "cover": { "type": "string", "title": "Cover art", "x-editor": "media" }, + "listenUrl": { "type": "string", "title": "Listen link", "x-editor": "text" }, + "listenLabel": { "type": "string", "title": "Listen label", "x-editor": "text" }, + "platforms": { + "type": "array", + "title": "Platform channels", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "label": { "type": "string", "title": "Label", "x-editor": "text" }, + "url": { "type": "string", "title": "URL", "x-editor": "text" } + } + } + } + } +} diff --git a/blocks/lcars_header.ninjatpl b/blocks/lcars_header.ninjatpl deleted file mode 100644 index ff66914..0000000 --- a/blocks/lcars_header.ninjatpl +++ /dev/null @@ -1,23 +0,0 @@ -
-
-
-
-

{{ title|default:"LCARS" }}

- {% if subtitle|default:"Library Computer Access/Retrieval System" %} - {{ subtitle|default:"Library Computer Access/Retrieval System" }} - {% endif %} -
-
- {% if stardate %} - SD {{ stardate }} - {% endif %} - - {{ status|default:"online"|upper }} - -
-
-
- {% if ctx.isEditor %} -
EDIT MODE
- {% endif %} -
diff --git a/blocks/lcars_header.schema.json b/blocks/lcars_header.schema.json deleted file mode 100644 index 70a5cf5..0000000 --- a/blocks/lcars_header.schema.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "LCARS Header", - "description": "Top bar with elbow bracket, status indicators, and stardate", - "type": "object", - "properties": { - "title": { - "type": "string", - "title": "Title", - "description": "Main display title", - "x-editor": "text" - }, - "subtitle": { - "type": "string", - "title": "Subtitle", - "description": "Secondary text below the title", - "x-editor": "text" - }, - "stardate": { - "type": "string", - "title": "Stardate", - "description": "Stardate display (leave empty for auto)", - "x-editor": "text" - }, - "status": { - "type": "string", - "title": "Status", - "description": "System status indicator", - "enum": ["online", "standby", "alert", "offline"], - "default": "online", - "x-editor": "select" - }, - "menu_name": { - "type": "string", - "title": "Menu Name", - "description": "Navigation menu to display", - "x-editor": "text" - } - }, - "required": ["title"] -} diff --git a/blocks/lcars_panel.ninjatpl b/blocks/lcars_panel.ninjatpl deleted file mode 100644 index 9d2e658..0000000 --- a/blocks/lcars_panel.ninjatpl +++ /dev/null @@ -1,14 +0,0 @@ -
- {% if title %} -
-
- {{ title }} -
-
- {% endif %} -
- {% if content %} - {{ content|safe }} - {% endif %} -
-
diff --git a/blocks/lcars_panel.schema.json b/blocks/lcars_panel.schema.json deleted file mode 100644 index a97fe4e..0000000 --- a/blocks/lcars_panel.schema.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "LCARS Panel", - "description": "Framed content area with LCARS border treatment", - "type": "object", - "properties": { - "title": { - "type": "string", - "title": "Panel Title", - "description": "Title shown in the panel header bar", - "x-editor": "text" - }, - "content": { - "type": "string", - "title": "Content", - "description": "Panel body content (HTML)", - "x-editor": "richtext" - }, - "border_color": { - "type": "string", - "title": "Border Color", - "description": "Which theme color for the panel border", - "enum": ["primary", "secondary", "accent"], - "default": "primary", - "x-editor": "select" - } - }, - "required": ["title"] -} diff --git a/blocks/lcars_sidebar.ninjatpl b/blocks/lcars_sidebar.ninjatpl deleted file mode 100644 index 8634fe4..0000000 --- a/blocks/lcars_sidebar.ninjatpl +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/blocks/lcars_sidebar.schema.json b/blocks/lcars_sidebar.schema.json deleted file mode 100644 index 42425e5..0000000 --- a/blocks/lcars_sidebar.schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "LCARS Sidebar", - "description": "Left panel with rounded-rectangle navigation buttons", - "type": "object", - "properties": { - "items": { - "type": "array", - "title": "Navigation Items", - "description": "List of sidebar navigation buttons", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string", - "title": "Label" - }, - "url": { - "type": "string", - "title": "URL" - }, - "color": { - "type": "string", - "title": "Color", - "enum": ["primary", "secondary", "accent"], - "default": "primary" - } - }, - "required": ["label", "url"] - } - } - } -} diff --git a/blocks/stardate_log.ninjatpl b/blocks/stardate_log.ninjatpl new file mode 100644 index 0000000..e2d0a1c --- /dev/null +++ b/blocks/stardate_log.ninjatpl @@ -0,0 +1,10 @@ +
+{% if eyebrow %}

{{ eyebrow }}

{% endif %} +{% if heading %}

{{ heading }}

{% endif %} +{% if entries %}
{% for e in entries %} +
+
{{ e.stardate|default:"—" }}{% if not forloop.Last %}{% endif %}
+
{% if e.title %}
{{ e.title }}
{% endif %}{% if e.text %}

{{ e.text }}

{% endif %}
+
+{% endfor %}
{% endif %} +
diff --git a/blocks/stardate_log.schema.json b/blocks/stardate_log.schema.json new file mode 100644 index 0000000..bb327ff --- /dev/null +++ b/blocks/stardate_log.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Stardate Log", + "description": "Dated log entries on a numeric stardate rail.", + "type": "object", + "properties": { + "eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" }, + "heading": { "type": "string", "title": "Heading", "x-editor": "text" }, + "entries": { + "type": "array", + "title": "Log entries", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "stardate": { "type": "string", "title": "Stardate", "x-editor": "text" }, + "title": { "type": "string", "title": "Entry title", "x-editor": "text" }, + "text": { "type": "string", "title": "Entry", "x-editor": "textarea" } + } + } + } + } +} diff --git a/blocks/transmission_status.ninjatpl b/blocks/transmission_status.ninjatpl new file mode 100644 index 0000000..94b1f3f --- /dev/null +++ b/blocks/transmission_status.ninjatpl @@ -0,0 +1,10 @@ +
+{% if eyebrow %}

{{ eyebrow }}

{% endif %} +{% if heading %}

{{ heading }}

{% endif %} +{% if systems %}
{% for s in systems %} +
+
{% if s.label %}
{{ s.label }}
{% endif %}{% if s.detail %}
{{ s.detail }}
{% endif %}
+{% if s.status == "alert" %}Alert{% elif s.status == "standby" %}Standby{% elif s.status == "offline" %}Offline{% else %}Online{% endif %} +
+{% endfor %}
{% endif %} +
diff --git a/blocks/transmission_status.schema.json b/blocks/transmission_status.schema.json new file mode 100644 index 0000000..f3fdb32 --- /dev/null +++ b/blocks/transmission_status.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Transmission Status", + "description": "Status board of channels and systems as LCARS readouts.", + "type": "object", + "properties": { + "eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" }, + "heading": { "type": "string", "title": "Heading", "x-editor": "text" }, + "systems": { + "type": "array", + "title": "Systems", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "label": { "type": "string", "title": "Label", "x-editor": "text" }, + "status": { + "type": "string", + "title": "Status", + "x-editor": "select", + "enum": ["online", "standby", "alert", "offline"], + "default": "online" + }, + "detail": { "type": "string", "title": "Detail readout", "x-editor": "text" } + } + } + } + } +} diff --git a/fonts.json b/fonts.json index 1806db6..2001a71 100644 --- a/fonts.json +++ b/fonts.json @@ -3,26 +3,17 @@ "name": "Antonio", "family": "Antonio", "variants": [ - { - "weight": "300", - "style": "normal", - "file": "fonts/antonio-latin.woff2" - }, - { - "weight": "400", - "style": "normal", - "file": "fonts/antonio-latin.woff2" - }, - { - "weight": "600", - "style": "normal", - "file": "fonts/antonio-latin.woff2" - }, - { - "weight": "700", - "style": "normal", - "file": "fonts/antonio-latin.woff2" - } + { "weight": "300", "style": "normal", "file": "fonts/antonio-latin.woff2" }, + { "weight": "400", "style": "normal", "file": "fonts/antonio-latin.woff2" }, + { "weight": "600", "style": "normal", "file": "fonts/antonio-latin.woff2" }, + { "weight": "700", "style": "normal", "file": "fonts/antonio-latin.woff2" } + ] + }, + { + "name": "Share Tech Mono", + "family": "Share Tech Mono", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/share-tech-mono-latin.woff2" } ] } ] diff --git a/manifest.yaml b/manifest.yaml index ed6414a..3b1c0fb 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,32 +1,128 @@ -# LCARS theme — codeless .bnp manifest (WO-WZ-021 Phase B1). +# LCARS theme — codeless .bnp manifest (WO-TF-020, Wave A). # Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm). -# This theme was previously a pongo2 wasm guest (it shipped the pongo engine -# inside the guest); the codeless path renders the SAME .ninjatpl sources -# host-side via cms/backend/plugin/pongoengine, so no engine ships here. +# +# Identity: the full library-computer interface of a sci-fi podcast / media +# outfit. Pill rails, elbow brackets, live numeric readouts, stardate stamps +# and episode-console furniture frame every surface — the chrome IS the +# interface. Dark = canonical LCARS black with orange / amber / lilac panels; +# light = daylight-ops cream with the colour bars kept vivid. Motion tier +# GO BIG: one dependency-free canvas sweep on the hero / landing only. +# +# This theme was previously a pongo2 wasm guest; the codeless path renders the +# SAME .ninjatpl sources host-side via cms/backend/plugin/pongoengine, so no +# engine ships here. Every override maps its classes 1:1 onto the lcars-* +# vocabulary defined below; semantic colour tokens only (hsl(var(--token))). theme_presets: presets.json bundled_fonts: fonts.json master_pages: master_pages.json +required_icon_packs: + - lucide + system_templates: - key: lcars title: LCARS - description: "Star Trek LCARS computer interface theme" + description: "Library-computer interface theme for sci-fi podcast and media sites: elbow-rail chrome, numeric readouts, stardate stamps, dual daylight-ops cream and canonical-black modes." page_templates: - system: lcars key: default title: Standard Display - description: "Classic LCARS layout with sidebar and header frame" + description: "Elbow header, left command rail, main panel, and footer bar." slots: [header, sidebar, main, footer] - system: lcars - key: full-display + key: full-width title: Full Display - description: "Full-width LCARS display without sidebar" + description: "Edge-to-edge LCARS frame with no command rail." slots: [header, main, footer] + - system: lcars + key: full-display + title: Full Display (legacy) + description: "Pre-existing full-width key, retained for sites already using it." + slots: [header, main, footer] + - system: lcars + key: landing + title: Landing + description: "Hero readout band, main panel stack, closing CTA, and footer." + slots: [hero, main, cta, footer] + - system: lcars + key: article + title: Article + description: "Long-form transcript column with a data aside rail." + slots: [header, main, aside, footer] + - system: lcars + key: blog-index + title: Episode Index + description: "Title readout, featured lead, and an episode-panel grid." + slots: [header, featured, main, footer] + - system: lcars + key: contact + title: Hail Channel + description: "Contact console: form on the main panel, channel details in the aside rail." + slots: [header, main, aside, footer] + - system: lcars + key: auth + title: Access Terminal + description: "Centered access panel on a canonical-black terminal ground." + slots: [main, footer] template_overrides: + # Chrome (4) + - { template: lcars, block: navbar } + - { template: lcars, block: footer } + - { template: lcars, block: utility-bar } + - { template: lcars, block: announcement-bar } + # Marketing (12) + - { template: lcars, block: hero } + - { template: lcars, block: feature-grid } + - { template: lcars, block: rich-section } + - { template: lcars, block: stats } + - { template: lcars, block: testimonial } + - { template: lcars, block: logo-strip } + - { template: lcars, block: pricing } + - { template: lcars, block: team } + - { template: lcars, block: faq } + - { template: lcars, block: timeline } + - { template: lcars, block: cta } + - { template: lcars, block: countdown } + # Primitives (11) - { template: lcars, block: heading } - { template: lcars, block: text } + - { template: lcars, block: rich-text } + - { template: lcars, block: image } + - { template: lcars, block: quote } + - { template: lcars, block: divider } + - { template: lcars, block: button } + - { template: lcars, block: card } + - { template: lcars, block: gallery } + - { template: lcars, block: video-embed } + - { template: lcars, block: audio-embed } + # Commerce / local (6) + - { template: lcars, block: menu-list } + - { template: lcars, block: event-list } + - { template: lcars, block: contact-form } + - { template: lcars, block: contact-card } + - { template: lcars, block: hours-location } + - { template: lcars, block: social-links } + # Blog family (12) + - { template: lcars, block: blog-hero } + - { template: lcars, block: featured-posts } + - { template: lcars, block: related-posts } + - { template: lcars, block: popular-posts } + - { template: lcars, block: category-list } + - { template: lcars, block: post-hero } + - { template: lcars, block: post-metadata } + - { template: lcars, block: author-bio } + - { template: lcars, block: author-bio-hero } + - { template: lcars, block: newsletter-signup } + - { template: lcars, block: breadcrumbs } + - { template: lcars, block: sidebar-nav } + # Auth surface (3) + - { template: lcars, block: auth-form } + - { template: lcars, block: password-reset-form } + - { template: lcars, block: auth-status } + # System (1) + - { template: lcars, block: page-suggestions } email_wrappers: - lcars @@ -34,425 +130,327 @@ email_wrappers: css: input_css_append: | /* ============================================================ - LCARS Theme — Star Trek Computer Interface + LCARS — appended component CSS (WO-TF-020). + Library-computer interface: pill rails, elbow brackets, + numeric readouts, stardate stamps. Semantic tokens ONLY — + hsl(var(--token)); never literal colours. Class names map + 1:1 onto the override vocabulary. ============================================================ */ - + + /* --- Base / layout primitives --- */ + .lcars-page { font-family: var(--font-body, "Antonio", "Roboto Condensed", "Arial Narrow", system-ui, sans-serif); letter-spacing: 0.02em; } + .lcars-body { font-family: var(--font-body, "Antonio", "Roboto Condensed", "Arial Narrow", system-ui, sans-serif); } + .lcars-mono { font-family: var(--font-mono, "Share Tech Mono", ui-monospace, "SFMono-Regular", monospace); font-variant-numeric: tabular-nums; letter-spacing: 0.05em; } + .numeric-tabular { font-variant-numeric: tabular-nums; } + .lcars-well { width: 100%; max-width: 1280px; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; box-sizing: border-box; } + .lcars-measure { max-width: 72ch; } + .lcars-section { padding-top: 3rem; padding-bottom: 3rem; } + .lcars-section-tight { padding-top: 1.75rem; padding-bottom: 1.75rem; } + .lcars-stack > * + * { margin-top: 1.5rem; } + .lcars-stack-sm > * + * { margin-top: 0.625rem; } + /* --- Typography --- */ - - .lcars-page { - font-family: 'Antonio', sans-serif; - letter-spacing: 0.05em; - } - - /* --- Color Utilities --- */ - - .lcars-bg-primary { background-color: hsl(var(--primary)); } - .lcars-bg-secondary { background-color: hsl(var(--secondary)); } - .lcars-bg-accent { background-color: hsl(var(--accent)); } - .lcars-bg-muted { background-color: hsl(var(--muted)); } - .lcars-text-primary { color: hsl(var(--primary)); } - .lcars-text-secondary { color: hsl(var(--secondary)); } - .lcars-text-accent { color: hsl(var(--accent)); } - - /* --- Frame Layout --- */ - - .lcars-frame { - display: flex; - flex-direction: column; - min-height: 100vh; - padding: 0.5rem; - gap: 0.25rem; - } - - .lcars-body { - display: grid; - grid-template-columns: 1fr; - grid-template-rows: auto 1fr auto; - flex: 1; - gap: 0.25rem; - } - - .lcars-content-area { - display: grid; - grid-template-columns: 12rem 0.25rem 1fr; - gap: 0; - flex: 1; - } - - .lcars-frame-full .lcars-body-full { - display: flex; - flex-direction: column; - flex: 1; - gap: 0.25rem; - } - - /* --- Elbow Brackets --- */ - - .lcars-elbow-top, - .lcars-elbow-bottom { - display: flex; - align-items: stretch; - height: 2rem; - } - - .lcars-elbow-corner { - width: 12rem; - min-width: 12rem; - background-color: hsl(var(--primary)); - } - - .lcars-elbow-tl { - border-bottom-left-radius: 0; - border-bottom-right-radius: 2rem; - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - .lcars-elbow-bl { - border-top-left-radius: 0; - border-top-right-radius: 2rem; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - - .lcars-bar { - flex: 1; - margin-left: 0.25rem; - } - - .lcars-bar-top { - background-color: hsl(var(--secondary)); - border-radius: 0 0 1rem 0; - } - - .lcars-bar-bottom { - background-color: hsl(var(--secondary)); - border-radius: 0 1rem 0 0; - } - - .lcars-bar-accent { - height: 0.5rem; - background: linear-gradient( - to right, - hsl(var(--primary)) 0%, - hsl(var(--primary)) 30%, - hsl(var(--secondary)) 30%, - hsl(var(--secondary)) 60%, - hsl(var(--accent)) 60%, - hsl(var(--accent)) 100% - ); - border-radius: 0.25rem; - } - - /* --- Divider (between sidebar and main) --- */ - - .lcars-divider { - background-color: hsl(var(--primary)); - width: 0.25rem; - } - - /* --- Sidebar --- */ - - .lcars-sidebar-area { - display: flex; - flex-direction: column; - overflow: hidden; - } - - .lcars-sidebar { - display: flex; - flex-direction: column; - gap: 0.25rem; - padding: 0.25rem 0; - } - - .lcars-sidebar-btn { - display: flex; - align-items: center; - justify-content: flex-end; - padding: 0.5rem 1rem; - border-radius: 0 1.5rem 1.5rem 0; - text-decoration: none; - color: hsl(var(--background)); - font-weight: 700; - font-size: 0.875rem; - text-transform: uppercase; - letter-spacing: 0.1em; - transition: filter 0.15s ease; - min-height: 2.5rem; - } - - .lcars-sidebar-btn:hover { - filter: brightness(1.2); - } - - .lcars-sidebar-btn-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - /* --- Main Content --- */ - - .lcars-main-area { - padding: 1.5rem 2rem; - } - - .lcars-main-full { - flex: 1; - padding: 2rem; - } - - .lcars-empty-state { - display: flex; - align-items: center; - justify-content: center; - min-height: 20rem; - } - - /* --- Header Block --- */ - - .lcars-header-bar { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 0; - } - - .lcars-header-pill { - width: 6rem; - height: 2.5rem; - border-radius: 1.25rem; - flex-shrink: 0; - } - - .lcars-header-title-area { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - } - - .lcars-title { - font-size: 2rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.15em; - color: hsl(var(--primary)); - line-height: 1; - margin: 0; - } - - .lcars-subtitle { - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.2em; - color: hsl(var(--secondary)); - } - - .lcars-header-indicators { - display: flex; - align-items: center; - gap: 1rem; - flex-shrink: 0; - } - - .lcars-stardate { - font-size: 0.875rem; - font-weight: 600; - color: hsl(var(--accent)); - text-transform: uppercase; - letter-spacing: 0.1em; - } - - .lcars-status { - font-size: 0.75rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.15em; - padding: 0.25rem 0.75rem; - border-radius: 0.75rem; - } - - .lcars-status-online { - background-color: hsl(var(--primary)); - color: hsl(var(--background)); - animation: lcars-pulse 3s ease-in-out infinite; - } - - .lcars-status-standby { - background-color: hsl(var(--secondary)); - color: hsl(var(--background)); - } - - .lcars-status-alert { - background-color: hsl(var(--destructive)); - color: hsl(var(--destructive-foreground)); - animation: lcars-blink 1s step-end infinite; - } - - .lcars-status-offline { - background-color: hsl(var(--muted)); - color: hsl(var(--muted-foreground)); - } - - .lcars-editor-badge { - position: absolute; - top: 0.25rem; - right: 0.5rem; - font-size: 0.625rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.2em; - color: hsl(var(--accent)); - opacity: 0.7; - } - - /* --- Panel Block --- */ - - .lcars-panel { - border-left: 0.25rem solid hsl(var(--primary)); - margin: 1rem 0; - } - - .lcars-panel-secondary { border-left-color: hsl(var(--secondary)); } - .lcars-panel-accent { border-left-color: hsl(var(--accent)); } - - .lcars-panel-header { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.5rem; - } - - .lcars-panel-header-pill { - width: 3rem; - height: 1.25rem; - background-color: inherit; - border-radius: 0.625rem; - background-color: hsl(var(--primary)); - } - - .lcars-panel-secondary .lcars-panel-header-pill { background-color: hsl(var(--secondary)); } - .lcars-panel-accent .lcars-panel-header-pill { background-color: hsl(var(--accent)); } - - .lcars-panel-title { - font-size: 0.875rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.15em; - color: hsl(var(--foreground)); - white-space: nowrap; - } - - .lcars-panel-header-bar { - flex: 1; - height: 0.125rem; - background-color: hsl(var(--border)); - } - - .lcars-panel-body { - padding: 0.75rem 1rem 0.75rem 1.25rem; - } - - /* --- Heading Override --- */ - - .lcars-heading { - font-family: 'Antonio', sans-serif; - text-transform: uppercase; - letter-spacing: 0.12em; - color: hsl(var(--primary)); - border-bottom: 0.125rem solid hsl(var(--primary)); - padding-bottom: 0.25rem; - margin-bottom: 1rem; - } - - .lcars-heading-1 { font-size: 2.25rem; } - .lcars-heading-2 { font-size: 1.75rem; } + .lcars-display, .lcars-heading { font-family: var(--font-heading, "Antonio", "Roboto Condensed", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: hsl(var(--primary)); line-height: 1.05; margin: 0; } + .lcars-heading-h2-accent { border-bottom: 0.1875rem solid hsl(var(--primary)); padding-bottom: 0.25rem; } + .lcars-heading-1 { font-size: clamp(2rem, 5vw, 2.75rem); } + .lcars-heading-2 { font-size: clamp(1.625rem, 3.5vw, 2rem); } .lcars-heading-3 { font-size: 1.375rem; } .lcars-heading-4 { font-size: 1.125rem; } - - /* --- Text Override --- */ - - .lcars-prose { - color: hsl(var(--foreground)); - line-height: 1.7; - font-size: 1rem; + .lcars-eyebrow { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.8125rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.22em; color: hsl(var(--secondary)); margin: 0 0 0.75rem 0; } + .lcars-kicker { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 0.75rem; letter-spacing: 0.18em; text-transform: uppercase; color: hsl(var(--accent)); } + .lcars-text { font-family: var(--font-body, "Antonio", "Roboto Condensed", system-ui, sans-serif); font-size: 1.0625rem; line-height: 1.7; color: hsl(var(--foreground)); } + .lcars-text a { color: hsl(var(--secondary)); text-decoration: underline; text-underline-offset: 0.2em; } + .lcars-text a:hover { color: hsl(var(--primary)); } + .lcars-text strong { color: hsl(var(--primary)); font-weight: 700; } + .lcars-text code, .lcars-prose code { font-family: var(--font-mono, "Share Tech Mono", monospace); background-color: hsl(var(--muted)); padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875em; color: hsl(var(--accent)); } + .lcars-prose { font-family: var(--font-body, "Antonio", "Roboto Condensed", system-ui, sans-serif); color: hsl(var(--foreground)); line-height: 1.7; font-size: 1.0625rem; } + .lcars-prose a { color: hsl(var(--secondary)); text-decoration: underline; text-underline-offset: 0.2em; } + .lcars-prose a:hover { color: hsl(var(--primary)); } + .lcars-prose strong { color: hsl(var(--primary)); } + .lcars-lede { font-family: var(--font-body, "Antonio", system-ui, sans-serif); font-size: 1.25rem; line-height: 1.6; color: hsl(var(--muted-foreground)); } + .lcars-label { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.8125rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; color: hsl(var(--foreground)); } + .lcars-readout { font-family: var(--font-mono, "Share Tech Mono", monospace); font-variant-numeric: tabular-nums; letter-spacing: 0.1em; color: hsl(var(--accent)); text-transform: uppercase; } + .lcars-stardate { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 0.8125rem; font-weight: 400; letter-spacing: 0.12em; text-transform: uppercase; color: hsl(var(--accent)); } + + /* --- Pills / bars / marks (LCARS signature) --- */ + .lcars-pill { display: inline-block; height: 1.25rem; min-width: 3rem; border-radius: 0.625rem; background-color: hsl(var(--primary)); flex: none; } + .lcars-pill-secondary { background-color: hsl(var(--secondary)); } + .lcars-pill-accent { background-color: hsl(var(--accent)); } + .lcars-cap { display: inline-block; height: 1.5rem; width: 1.5rem; background-color: hsl(var(--primary)); flex: none; } + .lcars-cap-left { border-radius: 0.75rem 0 0 0.75rem; } + .lcars-cap-right { border-radius: 0 0.75rem 0.75rem 0; } + .lcars-bar-accent { height: 0.5rem; border-radius: 0.25rem; background: linear-gradient(to right, hsl(var(--primary)) 0%, hsl(var(--primary)) 34%, hsl(var(--secondary)) 34%, hsl(var(--secondary)) 67%, hsl(var(--accent)) 67%, hsl(var(--accent)) 100%); } + .lcars-rule { border: 0; border-top: 0.1875rem solid hsl(var(--primary)); margin: 0; } + + /* --- Buttons (pill, uppercase, right-rounded) --- */ + .lcars-btn-primary { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1.25rem; background-color: hsl(var(--primary)); color: hsl(var(--background)); border: 0; border-radius: 0 1.25rem 1.25rem 0; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; text-decoration: none; line-height: 1.3; box-shadow: none; background-image: none; transition: filter 150ms ease; cursor: pointer; } + .lcars-btn-primary:hover { filter: brightness(1.15); } + .lcars-btn-primary:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; } + .lcars-btn-secondary { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1.25rem; background-color: hsl(var(--secondary)); color: hsl(var(--background)); border: 0; border-radius: 0 1.25rem 1.25rem 0; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; text-decoration: none; line-height: 1.3; transition: filter 150ms ease; cursor: pointer; } + .lcars-btn-secondary:hover { filter: brightness(1.15); } + .lcars-btn-outline { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1.25rem; background-color: transparent; color: hsl(var(--secondary)); border: 0.125rem solid hsl(var(--secondary)); border-radius: 0 1.25rem 1.25rem 0; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; text-decoration: none; line-height: 1.3; transition: background-color 150ms ease, color 150ms ease; cursor: pointer; } + .lcars-btn-outline:hover { background-color: hsl(var(--secondary)); color: hsl(var(--background)); } + .lcars-btn-outline:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; } + .lcars-btn-ghost { display: inline-flex; align-items: center; gap: 0.375rem; color: hsl(var(--accent)); font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; text-decoration: none; line-height: 1.3; transition: gap 150ms ease; } + .lcars-btn-ghost:hover { gap: 0.625rem; } + .lcars-btn-sm { padding: 0.375rem 0.875rem; font-size: 0.8125rem; } + .lcars-btn-lg { padding: 0.75rem 1.75rem; font-size: 1.0625rem; } + .lcars-btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; } + .lcars-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; } + + /* --- Panels / cards / surfaces --- */ + .lcars-panel { background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border-left: 0.5rem solid hsl(var(--primary)); border-radius: 0 0.5rem 0.5rem 0; padding: 1.25rem 1.5rem; box-shadow: none; } + .lcars-panel-secondary { border-left-color: hsl(var(--secondary)); } + .lcars-panel-accent { border-left-color: hsl(var(--accent)); } + .lcars-panel-header { display: flex; align-items: center; gap: 0.625rem; margin-bottom: 0.75rem; } + .lcars-panel-title { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.9375rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.15em; color: hsl(var(--foreground)); white-space: nowrap; } + .lcars-panel-header-bar { flex: 1; height: 0.125rem; background-color: hsl(var(--border)); } + .lcars-panel-body { } + .lcars-card { background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border-top: 0.375rem solid hsl(var(--primary)); border-radius: 0 0 0.75rem 0.75rem; padding: 1.5rem; box-shadow: none; overflow: hidden; } + .lcars-card-accent { border-top-color: hsl(var(--accent)); } + .lcars-surface-muted { background-color: hsl(var(--muted)); } + .lcars-surface-card { background-color: hsl(var(--card)); } + .lcars-media-frame { display: block; width: 100%; overflow: hidden; background-color: hsl(var(--muted)); border-radius: 0.5rem; } + .lcars-media-frame img { display: block; width: 100%; height: 100%; object-fit: cover; } + .lcars-img-empty { display: block; width: 100%; aspect-ratio: 16 / 9; background-color: hsl(var(--muted)); border-radius: 0.5rem; } + + /* --- Responsive grids --- */ + .lcars-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .lcars-grid-2, .lcars-grid-3, .lcars-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .lcars-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .lcars-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } } + .lcars-leadership-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .lcars-leadership-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .lcars-leadership-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } } + + /* --- Page frame chrome (elbow rails, command sidebar) --- */ + .lcars-frame { display: flex; flex-direction: column; min-height: 100vh; padding: 0.5rem; gap: 0.375rem; background-color: hsl(var(--background)); } + .lcars-elbow-top, .lcars-elbow-bottom { display: flex; align-items: stretch; height: 2.25rem; gap: 0.375rem; } + .lcars-elbow-corner { width: 12rem; min-width: 12rem; background-color: hsl(var(--primary)); } + .lcars-elbow-tl { border-radius: 0 0 2rem 0; } + .lcars-elbow-bl { border-radius: 0 2rem 0 0; } + .lcars-bar { flex: 1; } + .lcars-bar-top { background-color: hsl(var(--secondary)); border-radius: 0 0 1rem 0; } + .lcars-bar-bottom { background-color: hsl(var(--secondary)); border-radius: 0 1rem 0 0; } + .lcars-frame-header { display: flex; align-items: center; gap: 0.625rem; } + .lcars-frame-caps { display: flex; align-items: center; gap: 0.375rem; } + .lcars-content-row { display: grid; grid-template-columns: 12rem 1fr; gap: 0.375rem; flex: 1; } + .lcars-rail { display: flex; flex-direction: column; gap: 0.375rem; } + .lcars-rail-btn { display: flex; align-items: center; justify-content: flex-end; text-align: right; padding: 0.625rem 1rem; min-height: 3rem; border-radius: 0 0 0 1.5rem; background-color: hsl(var(--secondary)); color: hsl(var(--background)); font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.1em; text-decoration: none; transition: filter 150ms ease; } + .lcars-rail-btn:nth-child(3n+1) { background-color: hsl(var(--primary)); } + .lcars-rail-btn:nth-child(3n+2) { background-color: hsl(var(--secondary)); } + .lcars-rail-btn:nth-child(3n) { background-color: hsl(var(--accent)); } + .lcars-rail-btn:hover { filter: brightness(1.15); } + .lcars-rail-cap { height: 2.25rem; background-color: hsl(var(--primary)); border-radius: 1rem 0 0 0; } + .lcars-rail-readout { padding: 0.5rem 1rem; text-align: right; } + .lcars-main-panel { min-width: 0; background-color: hsl(var(--background)); } + + /* --- Chrome: navbar --- */ + .lcars-navbar { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; padding: 0.5rem 0; } + .lcars-brand { display: inline-flex; align-items: center; gap: 0.625rem; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; font-size: 1.25rem; text-transform: uppercase; letter-spacing: 0.14em; color: hsl(var(--primary)); text-decoration: none; } + .lcars-brand-logo { height: 2rem; width: auto; } + .lcars-nav-links { display: none; align-items: center; gap: 0.375rem; } + @media (min-width: 768px) { .lcars-nav-links { display: flex; } } + .lcars-nav-link { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: hsl(var(--background)); background-color: hsl(var(--secondary)); padding: 0.375rem 1rem; border-radius: 0 1rem 1rem 0; text-decoration: none; transition: filter 150ms ease; } + .lcars-nav-link:hover { filter: brightness(1.15); } + .lcars-nav-toggle { display: inline-flex; } + @media (min-width: 768px) { .lcars-nav-toggle { display: none; } } + .lcars-nav-drawer { display: none; flex-direction: column; gap: 0.375rem; padding: 0.75rem 0 0.5rem; width: 100%; } + @media (min-width: 768px) { .lcars-nav-drawer { display: none !important; } } + .lcars-nav-drawer a { padding: 0.625rem 1rem; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: hsl(var(--background)); background-color: hsl(var(--secondary)); border-radius: 0 1rem 1rem 0; text-decoration: none; } + + /* --- Chrome: utility bar + announcement bar --- */ + .lcars-utility-bar { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 0.8125rem; letter-spacing: 0.08em; padding: 0.375rem 0; } + .lcars-utility-bar--muted { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); } + .lcars-utility-bar--primary { background-color: hsl(var(--primary)); color: hsl(var(--background)); } + .lcars-utility-bar--dark { background-color: hsl(var(--foreground)); color: hsl(var(--background)); } + .lcars-utility-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; } + .lcars-utility-group { display: flex; align-items: center; gap: 1.25rem; } + .lcars-utility-item { display: inline-flex; align-items: center; gap: 0.375rem; color: inherit; text-decoration: none; } + .lcars-announce { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; padding: 0.5rem 0; } + .lcars-announce--info { background-color: hsl(var(--secondary)); color: hsl(var(--background)); } + .lcars-announce--promo { background-color: hsl(var(--accent)); color: hsl(var(--background)); } + .lcars-announce--alert { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); } + .lcars-announce-row { display: flex; align-items: center; justify-content: center; gap: 0.75rem; } + .lcars-announce a { color: inherit; text-decoration: underline; text-underline-offset: 2px; } + .lcars-announce-close { margin-left: auto; background: none; border: 0; color: inherit; cursor: pointer; line-height: 1; padding: 0 0.25rem; } + + /* --- Chrome: footer --- */ + .lcars-footer { background-color: hsl(var(--background)); color: hsl(var(--foreground)); border-top: 0.375rem solid hsl(var(--primary)); border-radius: 0.75rem 0.75rem 0 0; padding: 2.5rem 0 2rem; } + .lcars-footer-cols { display: grid; gap: 2rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .lcars-footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .lcars-footer-cols { grid-template-columns: 2fr repeat(3, 1fr); } } + .lcars-footer-head { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.8125rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em; color: hsl(var(--secondary)); margin: 0 0 0.875rem; } + .lcars-footer-link { display: block; font-size: 0.9375rem; color: hsl(var(--foreground)); text-decoration: none; padding: 0.25rem 0; } + .lcars-footer-link:hover { color: hsl(var(--primary)); } + .lcars-footer-legal { color: hsl(var(--muted-foreground)); font-size: 0.8125rem; font-family: var(--font-mono, "Share Tech Mono", monospace); letter-spacing: 0.06em; border-top: 1px solid hsl(var(--border)); padding-top: 1.5rem; margin-top: 2.5rem; display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; } + + /* --- Marketing: hero --- */ + .lcars-hero { padding: 4rem 0; } + .lcars-hero-headline { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; line-height: 1.02; font-size: clamp(2.5rem, 6vw, 4.5rem); color: hsl(var(--primary)); margin: 0; } + .lcars-hero-sub { font-size: clamp(1.125rem, 2vw, 1.5rem); line-height: 1.5; color: hsl(var(--foreground)); margin: 1.25rem 0 0; } + .lcars-hero-split { display: grid; gap: 2.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); align-items: center; } + @media (min-width: 1024px) { .lcars-hero-split { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + + /* --- Marketing: logo strip --- */ + .lcars-logo { filter: grayscale(1); opacity: 0.7; transition: filter 200ms ease, opacity 200ms ease; } + .lcars-logo:hover { filter: grayscale(0); opacity: 1; } + .lcars-logo-row { display: flex; flex-wrap: wrap; align-items: center; gap: 2.5rem; } + + /* --- Marketing: feature grid --- */ + .lcars-feature { border-top: 0.25rem solid hsl(var(--accent)); border-radius: 0 0 0.5rem 0.5rem; padding: 1rem 1rem 1.25rem; background-color: hsl(var(--card)); } + .lcars-feature-title { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; font-size: 1.0625rem; color: hsl(var(--foreground)); margin: 0.75rem 0 0.5rem; } + + /* --- Marketing: stats / metrics --- */ + .lcars-metric { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: clamp(2.5rem, 5vw, 3.5rem); line-height: 1; font-variant-numeric: tabular-nums; color: hsl(var(--primary)); letter-spacing: 0.02em; } + .lcars-stat-figure { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: clamp(2.25rem, 4vw, 3rem); line-height: 1; font-variant-numeric: tabular-nums; color: hsl(var(--accent)); letter-spacing: 0.02em; } + .lcars-stat-label { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.8125rem; color: hsl(var(--muted-foreground)); text-transform: uppercase; letter-spacing: 0.1em; margin-top: 0.5rem; } + .lcars-numeral-index { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 2rem; line-height: 1; font-variant-numeric: tabular-nums; color: hsl(var(--accent)); } + + /* --- Marketing: pull quote / testimonial --- */ + .lcars-pull-quote { border-left: 0.375rem solid hsl(var(--accent)); border-radius: 0 0.5rem 0.5rem 0; padding: 1rem 1.5rem; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 1.5rem; line-height: 1.3; font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em; color: hsl(var(--foreground)); } + .lcars-quote-block { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 1.375rem; line-height: 1.4; font-weight: 700; color: hsl(var(--foreground)); } + .lcars-quote-cite { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 0.8125rem; letter-spacing: 0.08em; text-transform: uppercase; color: hsl(var(--muted-foreground)); margin-top: 0.75rem; } + .lcars-avatar { width: 2.75rem; height: 2.75rem; border-radius: 9999px; object-fit: cover; background-color: hsl(var(--muted)); } + + /* --- Marketing: pricing --- */ + .lcars-price { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 2.5rem; font-variant-numeric: tabular-nums; color: hsl(var(--primary)); letter-spacing: 0.02em; } + .lcars-price-period { font-family: var(--font-body, "Antonio", system-ui, sans-serif); font-size: 0.9375rem; color: hsl(var(--muted-foreground)); } + .lcars-price-tier { display: flex; flex-direction: column; } + .lcars-price-tier--featured { border-top: 0.5rem solid hsl(var(--accent)); } + .lcars-badge { display: inline-block; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; padding: 0.25rem 0.625rem; background-color: hsl(var(--accent)); color: hsl(var(--background)); border-radius: 0.625rem; } + .lcars-check { color: hsl(var(--accent)); flex: none; } + + /* --- Marketing: faq --- */ + .lcars-faq-item { border-top: 1px solid hsl(var(--border)); } + .lcars-faq-item summary { cursor: pointer; list-style: none; padding: 1.125rem 0; display: flex; align-items: center; justify-content: space-between; gap: 1rem; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: hsl(var(--foreground)); } + .lcars-faq-item summary::-webkit-details-marker { display: none; } + .lcars-faq-item[open] summary .lcars-faq-chevron { transform: rotate(180deg); } + .lcars-faq-chevron { transition: transform 150ms ease; color: hsl(var(--accent)); } + .lcars-faq-answer { padding: 0 0 1.125rem; color: hsl(var(--muted-foreground)); font-size: 1rem; line-height: 1.65; } + + /* --- Marketing: timeline --- */ + .lcars-timeline-step { display: grid; grid-template-columns: auto 1fr; gap: 1.25rem; padding-bottom: 2rem; } + .lcars-timeline-rail { position: relative; display: flex; flex-direction: column; align-items: center; } + .lcars-timeline-node { width: 2.75rem; height: 2.75rem; background-color: hsl(var(--accent)); border-radius: 0 1.375rem 1.375rem 0; display: flex; align-items: center; justify-content: center; font-family: var(--font-mono, "Share Tech Mono", monospace); color: hsl(var(--background)); flex: none; font-variant-numeric: tabular-nums; } + .lcars-timeline-line { flex: 1 1 auto; width: 0.1875rem; background-color: hsl(var(--border)); margin-top: 0.5rem; } + + /* --- Marketing: cta strip --- */ + .lcars-cta-strip { padding: 3rem 0; } + .lcars-cta-strip--band { background-color: hsl(var(--primary)); color: hsl(var(--background)); border-radius: 1rem; } + .lcars-cta-strip--band .lcars-cta-headline { color: hsl(var(--background)); } + .lcars-cta-strip--band .lcars-btn-primary { background-color: hsl(var(--background)); color: hsl(var(--primary)); } + .lcars-cta-strip--card { background-color: hsl(var(--muted)); border-radius: 1rem; } + .lcars-cta-headline { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; font-size: clamp(1.75rem, 3vw, 2.5rem); line-height: 1.1; margin: 0; color: hsl(var(--primary)); } + + /* --- Marketing: countdown --- */ + .lcars-count-unit { display: flex; flex-direction: column; align-items: center; } + .lcars-count-figure { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: clamp(2rem, 4vw, 3rem); font-variant-numeric: tabular-nums; color: hsl(var(--accent)); line-height: 1; } + .lcars-count-box { background-color: hsl(var(--card)); border-top: 0.25rem solid hsl(var(--primary)); padding: 1rem 1.25rem; border-radius: 0 0 0.5rem 0.5rem; min-width: 4.5rem; } + .lcars-count-label { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.1em; color: hsl(var(--muted-foreground)); margin-top: 0.375rem; } + + /* --- Primitives: divider / gallery / media --- */ + .lcars-divider-line { border: 0; border-top: 0.1875rem solid hsl(var(--border)); } + .lcars-divider-thick { border-top-width: 0.375rem; border-top-color: hsl(var(--primary)); } + .lcars-divider-ornament { display: flex; align-items: center; justify-content: center; gap: 0.5rem; color: hsl(var(--primary)); } + .lcars-gallery-grid { display: grid; gap: 0.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } + @media (min-width: 768px) { .lcars-gallery-grid.lcars-gallery-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .lcars-gallery-grid.lcars-gallery-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } } + .lcars-video-frame { position: relative; display: block; width: 100%; background-color: hsl(var(--muted)); border-radius: 0.5rem; overflow: hidden; } + .lcars-video-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: hsl(var(--background)); background: none; border: 0; cursor: pointer; } + .lcars-video-play-badge { width: 4rem; height: 4rem; border-radius: 9999px; background-color: hsl(var(--primary)); color: hsl(var(--background)); display: flex; align-items: center; justify-content: center; } + + /* --- Commerce / local --- */ + .lcars-menu-item { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 0.75rem 0; border-bottom: 1px solid hsl(var(--border)); } + .lcars-menu-price { font-family: var(--font-mono, "Share Tech Mono", monospace); font-variant-numeric: tabular-nums; color: hsl(var(--accent)); } + .lcars-channel { display: flex; align-items: flex-start; gap: 0.75rem; } + .lcars-channel-mark { width: 2.75rem; height: 2.75rem; background-color: hsl(var(--primary)); border-radius: 0 1.375rem 1.375rem 0; display: flex; align-items: center; justify-content: center; color: hsl(var(--background)); flex: none; } + .lcars-hours-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 0.375rem 0; border-bottom: 1px solid hsl(var(--border)); } + .lcars-social-row { display: flex; flex-wrap: wrap; gap: 0.5rem; } + .lcars-social-link { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.5rem 0.875rem; background-color: hsl(var(--secondary)); border-radius: 0 1rem 1rem 0; color: hsl(var(--background)); text-decoration: none; font-size: 0.8125rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; transition: filter 150ms ease; } + .lcars-social-link:hover { filter: brightness(1.15); } + + /* --- Forms --- */ + .lcars-form { display: grid; gap: 1.25rem; } + .lcars-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } + .lcars-field { display: flex; flex-direction: column; gap: 0.375rem; } + .lcars-field--full { grid-column: 1 / -1; } + .lcars-input { width: 100%; box-sizing: border-box; padding: 0.625rem 0.875rem; background-color: hsl(var(--background)); color: hsl(var(--foreground)); border: 0.125rem solid hsl(var(--input)); border-radius: 0.5rem; font-family: var(--font-body, "Antonio", system-ui, sans-serif); font-size: 1rem; } + .lcars-input:focus { outline: 2px solid hsl(var(--ring)); outline-offset: 1px; border-color: hsl(var(--ring)); } + .lcars-form-note { font-size: 0.8125rem; color: hsl(var(--muted-foreground)); } + .lcars-alert-ok { border-left: 0.375rem solid hsl(var(--primary)); background-color: hsl(var(--muted)); padding: 1rem 1.25rem; color: hsl(var(--foreground)); border-radius: 0 0.5rem 0.5rem 0; } + .lcars-alert-err { border-left: 0.375rem solid hsl(var(--destructive)); background-color: hsl(var(--muted)); padding: 1rem 1.25rem; color: hsl(var(--foreground)); border-radius: 0 0.5rem 0.5rem 0; } + + /* --- Blog family --- */ + .lcars-post-card { display: flex; flex-direction: column; gap: 0.75rem; background-color: hsl(var(--card)); border-top: 0.375rem solid hsl(var(--secondary)); border-radius: 0 0 0.5rem 0.5rem; padding: 1rem 1.25rem 1.25rem; } + .lcars-post-thumb { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; background-color: hsl(var(--muted)); border-radius: 0.375rem; } + .lcars-post-title { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; font-size: 1.1875rem; line-height: 1.2; color: hsl(var(--primary)); text-decoration: none; } + .lcars-post-title:hover { color: hsl(var(--secondary)); } + .lcars-post-meta { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: hsl(var(--muted-foreground)); display: flex; flex-wrap: wrap; gap: 0.75rem; } + .lcars-post-excerpt { color: hsl(var(--muted-foreground)); font-size: 0.9375rem; line-height: 1.6; } + .lcars-post-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .lcars-post-grid-2, .lcars-post-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .lcars-post-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } } + .lcars-tag { display: inline-block; font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.1875rem 0.625rem; background-color: hsl(var(--muted)); border-radius: 0.625rem; color: hsl(var(--secondary)); text-decoration: none; } + .lcars-tag:hover { background-color: hsl(var(--secondary)); color: hsl(var(--background)); } + .lcars-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 0.8125rem; letter-spacing: 0.06em; color: hsl(var(--muted-foreground)); } + .lcars-crumbs a { color: hsl(var(--secondary)); text-decoration: none; } + .lcars-crumbs a:hover { color: hsl(var(--primary)); } + .lcars-crumb-sep { color: hsl(var(--accent)); } + .lcars-sidebar-link { display: block; padding: 0.5rem 0.875rem; border-radius: 0 1rem 1rem 0; color: hsl(var(--foreground)); text-decoration: none; font-size: 0.9375rem; } + .lcars-sidebar-link:hover, .lcars-sidebar-link[aria-current="page"] { background-color: hsl(var(--secondary)); color: hsl(var(--background)); } + .lcars-rank { font-family: var(--font-mono, "Share Tech Mono", monospace); font-size: 1.25rem; color: hsl(var(--accent)); font-variant-numeric: tabular-nums; width: 2rem; flex: none; } + + /* --- Status readouts --- */ + .lcars-status { font-family: var(--font-heading, "Antonio", system-ui, sans-serif); font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.15em; padding: 0.25rem 0.75rem; border-radius: 0.75rem; } + .lcars-status-online { background-color: hsl(var(--primary)); color: hsl(var(--background)); animation: lcars-pulse 3s ease-in-out infinite; } + .lcars-status-standby { background-color: hsl(var(--secondary)); color: hsl(var(--background)); } + .lcars-status-alert { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); animation: lcars-blink 1s step-end infinite; } + .lcars-status-offline { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); } + + /* --- Auth --- */ + .lcars-auth-radio { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; } + .lcars-auth-panel { display: none; } + + /* --- Motion (GO BIG: canvas sweep on hero/landing only; CSS elsewhere) --- */ + .lcars-hero-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; z-index: 0; } + .lcars-hero-canvas-host { position: relative; overflow: hidden; } + .lcars-hero-canvas-host > *:not(.lcars-hero-canvas) { position: relative; z-index: 1; } + .lcars-reveal { opacity: 0; transform: translateY(0.5rem); animation: lcars-reveal 500ms ease forwards; } + @keyframes lcars-reveal { to { opacity: 1; transform: none; } } + @keyframes lcars-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } + @keyframes lcars-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } + @keyframes lcars-sweep { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } + @media (prefers-reduced-motion: reduce) { + .lcars-reveal { animation: none; opacity: 1; transform: none; } + .lcars-status-online, .lcars-status-alert { animation: none; } + .lcars-btn-ghost, .lcars-logo, .lcars-nav-link, .lcars-faq-chevron { transition: none; } } - - .lcars-prose a { - color: hsl(var(--secondary)); - text-decoration: underline; - text-underline-offset: 0.2em; - } - - .lcars-prose a:hover { - color: hsl(var(--primary)); - } - - .lcars-prose strong { - color: hsl(var(--primary)); - } - - .lcars-prose code { - background-color: hsl(var(--muted)); - padding: 0.125rem 0.375rem; - border-radius: 0.25rem; - font-size: 0.875em; - } - - /* --- Footer --- */ - - .lcars-footer-area { - padding: 0.5rem 0; - } - - /* --- Animations --- */ - - @keyframes lcars-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } - } - - @keyframes lcars-blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.3; } - } - - /* --- Responsive --- */ - + + /* --- Responsive: collapse the command rail on small screens --- */ @media (max-width: 768px) { - .lcars-content-area { - grid-template-columns: 1fr; - } - - .lcars-sidebar-area { - display: none; - } - - .lcars-divider { - display: none; - } - - .lcars-elbow-corner { - width: 4rem; - min-width: 4rem; - } - - .lcars-sidebar-btn { - border-radius: 1.5rem; - justify-content: center; - } - - .lcars-header-pill { - width: 3rem; - } - - .lcars-title { - font-size: 1.5rem; - } - - .lcars-main-area { - padding: 1rem; - } + .lcars-content-row { grid-template-columns: 1fr; } + .lcars-rail { flex-direction: row; flex-wrap: wrap; } + .lcars-elbow-corner { width: 4rem; min-width: 4rem; } + .lcars-rail-btn { border-radius: 0 1rem 1rem 0; justify-content: center; } + .lcars-form-grid { grid-template-columns: 1fr; } } + + /* --- Compatibility layer: classes the overrides carry over from the + shared builtin re-skin. Styled LCARS-native so every class resolves. --- */ + .lcars-content-well { width: 100%; max-width: 1280px; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; box-sizing: border-box; } + .lcars-swiss-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 1.25rem; row-gap: 1.25rem; } + .lcars-article-measure { max-width: 72ch; } + .lcars-hairline-top { border-top: 0.1875rem solid hsl(var(--border)); } + .lcars-hairline-bottom { border-bottom: 0.1875rem solid hsl(var(--border)); } + .lcars-accent-rule { border-top: 0.1875rem solid hsl(var(--primary)); } + .lcars-heading-h1 { font-size: clamp(2rem, 5vw, 2.75rem); } + .lcars-heading-h2 { font-size: clamp(1.625rem, 3.5vw, 2rem); } + .lcars-heading-h3 { font-size: 1.375rem; } + .lcars-heading-h4 { font-size: 1.125rem; } + .lcars-heading-h5 { font-size: 1rem; } + .lcars-heading-h6 { font-size: 0.875rem; } + /* LCARS marks: pills and caps replace the Bauhaus geometric marks. */ + .lcars-mark { display: inline-block; width: 1.5rem; height: 0.75rem; flex: none; background-color: hsl(var(--primary)); border-radius: 0.375rem; } + .lcars-mark-circle { border-radius: 9999px; width: 0.75rem; background-color: hsl(var(--primary)); } + .lcars-mark-square { border-radius: 0 0.375rem 0.375rem 0; background-color: hsl(var(--secondary)); } + .lcars-mark-triangle { border-radius: 0.375rem 0 0 0.375rem; background-color: hsl(var(--accent)); } + .lcars-mark-ring { border-radius: 9999px; width: 0.75rem; background: none; border: 2px solid hsl(var(--accent)); } diff --git a/master_pages.json b/master_pages.json index 413ca04..00c9303 100644 --- a/master_pages.json +++ b/master_pages.json @@ -4,18 +4,50 @@ "title": "LCARS Standard Display", "page_templates": ["default"], "blocks": [ - { "block_key": "lcars:lcars_header", "title": "LCARS Header", "content": { "title": "LCARS", "subtitle": "Library Computer Access/Retrieval System" }, "slot": "header", "sort_order": 0 }, - { "block_key": "lcars:lcars_sidebar", "title": "LCARS Sidebar", "content": {}, "slot": "sidebar", "sort_order": 0 }, - { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main", "placeholder": "Enter display data…" }, "slot": "main", "sort_order": 0 } + { "block_key": "navbar", "title": "Main Navigation", "content": { "variant": "standard", "menuName": "main", "companyName": "Subspace Signal" }, "slot": "header", "sort_order": 0 }, + { "block_key": "html", "title": "Command Rail", "content": { "html": "" }, "slot": "sidebar", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Subspace Signal", "tagline": "A weekly transmission on science fiction and the future.", "copyright": "Subspace Signal" }, "slot": "footer", "sort_order": 0 } ] }, { - "key": "lcars:full-display", - "title": "LCARS Full Display", - "page_templates": ["full-display"], + "key": "lcars:content-display", + "title": "LCARS Content Display", + "page_templates": ["full-width", "full-display", "article", "blog-index"], "blocks": [ - { "block_key": "lcars:lcars_header", "title": "LCARS Header", "content": { "title": "LCARS", "subtitle": "Main Viewer" }, "slot": "header", "sort_order": 0 }, - { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main", "placeholder": "Enter display data…" }, "slot": "main", "sort_order": 0 } + { "block_key": "navbar", "title": "Main Navigation", "content": { "variant": "standard", "menuName": "main", "companyName": "Subspace Signal" }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Subspace Signal", "tagline": "A weekly transmission on science fiction and the future.", "copyright": "Subspace Signal" }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "lcars:landing-display", + "title": "LCARS Landing Display", + "page_templates": ["landing"], + "blocks": [ + { "block_key": "navbar", "title": "Main Navigation", "content": { "variant": "standard", "menuName": "main", "companyName": "Subspace Signal" }, "slot": "header", "sort_order": 0 }, + { "block_key": "hero", "title": "Landing Hero", "content": { "template": "left-aligned", "eyebrow": "Now broadcasting", "headline": "Subspace Signal", "subheadline": "A weekly transmission on science fiction and the future." }, "slot": "hero", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "cta", "title": "Closing CTA", "content": { "background": "band", "heading": "Tune the frequency", "text": "New transmission every week. Subscribe on your channel of choice.", "primaryLabel": "Subscribe", "primaryUrl": "/episodes" }, "slot": "cta", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Subspace Signal", "copyright": "Subspace Signal" }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "lcars:contact-display", + "title": "LCARS Contact Display", + "page_templates": ["contact"], + "blocks": [ + { "block_key": "navbar", "title": "Main Navigation", "content": { "variant": "standard", "menuName": "main", "companyName": "Subspace Signal" }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Subspace Signal", "copyright": "Subspace Signal" }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "lcars:auth-display", + "title": "LCARS Auth Display", + "page_templates": ["auth"], + "blocks": [ + { "block_key": "slot", "title": "Auth Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 } ] } ] diff --git a/presets.json b/presets.json index 0fba260..a7fb6ce 100644 --- a/presets.json +++ b/presets.json @@ -1,202 +1,252 @@ [ { - "id": "federation", - "name": "Federation", - "description": "Classic LCARS — gold, periwinkle, and coral on black", + "id": "ops-amber", + "name": "Ops Amber", + "description": "Canonical LCARS. Black ops ground with orange, amber, and lilac panels. Light mode is daylight-ops cream with the colour bars kept vivid.", "theme": { + "mode": "both", "lightColors": { - "background": "0 0% 2%", - "foreground": "40 33% 92%", - "card": "0 0% 5%", - "cardForeground": "40 33% 92%", - "popover": "0 0% 5%", - "popoverForeground": "40 33% 92%", - "primary": "39 90% 65%", - "primaryForeground": "0 0% 0%", - "secondary": "230 55% 70%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "40 20% 55%", - "accent": "10 70% 65%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "0 0% 15%", - "input": "0 0% 15%", - "ring": "39 90% 65%" + "background": "40 38% 95%", + "foreground": "28 30% 12%", + "card": "40 30% 91%", + "cardForeground": "28 30% 12%", + "popover": "40 30% 91%", + "popoverForeground": "28 30% 12%", + "primary": "28 92% 48%", + "primaryForeground": "40 38% 96%", + "secondary": "38 88% 46%", + "secondaryForeground": "40 38% 96%", + "muted": "40 24% 88%", + "mutedForeground": "28 18% 38%", + "accent": "268 52% 52%", + "accentForeground": "40 38% 96%", + "destructive": "2 74% 46%", + "destructiveForeground": "40 38% 96%", + "border": "40 22% 82%", + "input": "40 22% 82%", + "ring": "28 92% 48%" }, "darkColors": { - "background": "0 0% 2%", - "foreground": "40 33% 92%", - "card": "0 0% 5%", - "cardForeground": "40 33% 92%", - "popover": "0 0% 5%", - "popoverForeground": "40 33% 92%", - "primary": "39 90% 65%", - "primaryForeground": "0 0% 0%", - "secondary": "230 55% 70%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "40 20% 55%", - "accent": "10 70% 65%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "0 0% 15%", - "input": "0 0% 15%", - "ring": "39 90% 65%" - }, - "mode": "dark" + "background": "240 8% 6%", + "foreground": "40 30% 92%", + "card": "240 8% 11%", + "cardForeground": "40 30% 92%", + "popover": "240 8% 11%", + "popoverForeground": "40 30% 92%", + "primary": "28 100% 58%", + "primaryForeground": "240 8% 6%", + "secondary": "40 96% 62%", + "secondaryForeground": "240 8% 6%", + "muted": "240 6% 16%", + "mutedForeground": "40 14% 66%", + "accent": "266 70% 76%", + "accentForeground": "240 8% 6%", + "destructive": "0 78% 58%", + "destructiveForeground": "240 8% 6%", + "border": "240 6% 20%", + "input": "240 6% 22%", + "ring": "28 100% 58%" + } } }, { "id": "red-alert", "name": "Red Alert", - "description": "Emergency operations — red and amber on black", + "description": "Condition-one console. Signal red and orange over black, with an amber standby channel. Light mode reads as an emergency daylight console.", "theme": { + "mode": "both", "lightColors": { - "background": "0 0% 2%", - "foreground": "0 0% 92%", - "card": "0 0% 5%", - "cardForeground": "0 0% 92%", - "popover": "0 0% 5%", - "popoverForeground": "0 0% 92%", - "primary": "0 80% 50%", - "primaryForeground": "0 0% 100%", - "secondary": "35 95% 55%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "0 15% 55%", - "accent": "35 95% 55%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "0 30% 18%", - "input": "0 30% 18%", - "ring": "0 80% 50%" + "background": "20 34% 95%", + "foreground": "8 34% 12%", + "card": "20 28% 91%", + "cardForeground": "8 34% 12%", + "popover": "20 28% 91%", + "popoverForeground": "8 34% 12%", + "primary": "4 78% 46%", + "primaryForeground": "20 34% 96%", + "secondary": "24 90% 48%", + "secondaryForeground": "20 34% 96%", + "muted": "20 22% 88%", + "mutedForeground": "8 18% 38%", + "accent": "40 92% 46%", + "accentForeground": "20 34% 96%", + "destructive": "0 80% 42%", + "destructiveForeground": "20 34% 96%", + "border": "20 20% 82%", + "input": "20 20% 82%", + "ring": "4 78% 46%" }, "darkColors": { - "background": "0 0% 2%", - "foreground": "0 0% 92%", - "card": "0 0% 5%", - "cardForeground": "0 0% 92%", - "popover": "0 0% 5%", - "popoverForeground": "0 0% 92%", - "primary": "0 80% 50%", - "primaryForeground": "0 0% 100%", - "secondary": "35 95% 55%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "0 15% 55%", - "accent": "35 95% 55%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "0 30% 18%", - "input": "0 30% 18%", - "ring": "0 80% 50%" - }, - "mode": "dark" + "background": "0 12% 6%", + "foreground": "20 30% 92%", + "card": "0 12% 11%", + "cardForeground": "20 30% 92%", + "popover": "0 12% 11%", + "popoverForeground": "20 30% 92%", + "primary": "4 86% 60%", + "primaryForeground": "0 12% 6%", + "secondary": "24 100% 58%", + "secondaryForeground": "0 12% 6%", + "muted": "0 8% 16%", + "mutedForeground": "20 14% 66%", + "accent": "42 98% 60%", + "accentForeground": "0 12% 6%", + "destructive": "0 84% 62%", + "destructiveForeground": "0 12% 6%", + "border": "0 8% 20%", + "input": "0 8% 22%", + "ring": "4 86% 60%" + } } }, { - "id": "sickbay", - "name": "Sickbay", - "description": "Medical operations — teal, cyan, and blue on black", + "id": "blue-console", + "name": "Blue Console", + "description": "24th-century sciences layout. Periwinkle blue and cyan panels with an orange action channel over black. Light mode is a bright bridge console.", "theme": { + "mode": "both", "lightColors": { - "background": "0 0% 2%", - "foreground": "180 20% 90%", - "card": "0 0% 5%", - "cardForeground": "180 20% 90%", - "popover": "0 0% 5%", - "popoverForeground": "180 20% 90%", - "primary": "174 72% 50%", - "primaryForeground": "0 0% 0%", - "secondary": "199 80% 55%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "180 15% 50%", - "accent": "199 80% 55%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "180 15% 15%", - "input": "180 15% 15%", - "ring": "174 72% 50%" + "background": "215 34% 96%", + "foreground": "220 32% 14%", + "card": "215 28% 92%", + "cardForeground": "220 32% 14%", + "popover": "215 28% 92%", + "popoverForeground": "220 32% 14%", + "primary": "222 62% 48%", + "primaryForeground": "215 34% 97%", + "secondary": "190 70% 40%", + "secondaryForeground": "215 34% 97%", + "muted": "215 22% 89%", + "mutedForeground": "220 16% 38%", + "accent": "28 92% 50%", + "accentForeground": "215 34% 97%", + "destructive": "2 74% 46%", + "destructiveForeground": "215 34% 97%", + "border": "215 20% 83%", + "input": "215 20% 83%", + "ring": "222 62% 48%" }, "darkColors": { - "background": "0 0% 2%", - "foreground": "180 20% 90%", - "card": "0 0% 5%", - "cardForeground": "180 20% 90%", - "popover": "0 0% 5%", - "popoverForeground": "180 20% 90%", - "primary": "174 72% 50%", - "primaryForeground": "0 0% 0%", - "secondary": "199 80% 55%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "180 15% 50%", - "accent": "199 80% 55%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "180 15% 15%", - "input": "180 15% 15%", - "ring": "174 72% 50%" - }, - "mode": "dark" + "background": "222 20% 7%", + "foreground": "210 28% 92%", + "card": "222 20% 12%", + "cardForeground": "210 28% 92%", + "popover": "222 20% 12%", + "popoverForeground": "210 28% 92%", + "primary": "220 78% 66%", + "primaryForeground": "222 20% 7%", + "secondary": "188 78% 52%", + "secondaryForeground": "222 20% 7%", + "muted": "222 16% 17%", + "mutedForeground": "210 16% 66%", + "accent": "30 100% 60%", + "accentForeground": "222 20% 7%", + "destructive": "0 80% 60%", + "destructiveForeground": "222 20% 7%", + "border": "222 14% 22%", + "input": "222 14% 24%", + "ring": "220 78% 66%" + } } }, { - "id": "engineering", - "name": "Engineering", - "description": "Engineering deck — orange, amber, and tan on black", + "id": "nebula", + "name": "Nebula", + "description": "Deep-space media suite. Lilac and magenta panels with a peach action channel over indigo-black. Light mode keeps the nebula hues on soft violet paper.", "theme": { + "mode": "both", "lightColors": { - "background": "0 0% 2%", - "foreground": "35 30% 88%", - "card": "0 0% 5%", - "cardForeground": "35 30% 88%", - "popover": "0 0% 5%", - "popoverForeground": "35 30% 88%", - "primary": "25 95% 55%", - "primaryForeground": "0 0% 0%", - "secondary": "40 80% 60%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "30 20% 50%", - "accent": "33 65% 70%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "25 20% 16%", - "input": "25 20% 16%", - "ring": "25 95% 55%" + "background": "280 30% 96%", + "foreground": "278 30% 14%", + "card": "280 24% 92%", + "cardForeground": "278 30% 14%", + "popover": "280 24% 92%", + "popoverForeground": "278 30% 14%", + "primary": "270 56% 50%", + "primaryForeground": "280 30% 97%", + "secondary": "322 66% 48%", + "secondaryForeground": "280 30% 97%", + "muted": "280 20% 89%", + "mutedForeground": "278 16% 40%", + "accent": "22 88% 54%", + "accentForeground": "280 30% 97%", + "destructive": "2 74% 46%", + "destructiveForeground": "280 30% 97%", + "border": "280 18% 83%", + "input": "280 18% 83%", + "ring": "270 56% 50%" }, "darkColors": { - "background": "0 0% 2%", - "foreground": "35 30% 88%", - "card": "0 0% 5%", - "cardForeground": "35 30% 88%", - "popover": "0 0% 5%", - "popoverForeground": "35 30% 88%", - "primary": "25 95% 55%", - "primaryForeground": "0 0% 0%", - "secondary": "40 80% 60%", - "secondaryForeground": "0 0% 0%", - "muted": "0 0% 10%", - "mutedForeground": "30 20% 50%", - "accent": "33 65% 70%", - "accentForeground": "0 0% 0%", - "destructive": "0 85% 55%", - "destructiveForeground": "0 0% 98%", - "border": "25 20% 16%", - "input": "25 20% 16%", - "ring": "25 95% 55%" + "background": "268 24% 7%", + "foreground": "285 26% 92%", + "card": "268 24% 12%", + "cardForeground": "285 26% 92%", + "popover": "268 24% 12%", + "popoverForeground": "285 26% 92%", + "primary": "268 74% 74%", + "primaryForeground": "268 24% 7%", + "secondary": "322 82% 64%", + "secondaryForeground": "268 24% 7%", + "muted": "268 18% 17%", + "mutedForeground": "285 16% 66%", + "accent": "24 96% 64%", + "accentForeground": "268 24% 7%", + "destructive": "0 80% 62%", + "destructiveForeground": "268 24% 7%", + "border": "268 16% 22%", + "input": "268 16% 24%", + "ring": "268 74% 74%" + } + } + }, + { + "id": "medbay-green", + "name": "Medbay Green", + "description": "Sickbay diagnostic layout. Teal and green panels with an amber alert channel over black. Light mode is a clean daylight medbay readout.", + "theme": { + "mode": "both", + "lightColors": { + "background": "150 26% 96%", + "foreground": "165 30% 12%", + "card": "150 22% 92%", + "cardForeground": "165 30% 12%", + "popover": "150 22% 92%", + "popoverForeground": "165 30% 12%", + "primary": "168 62% 34%", + "primaryForeground": "150 26% 97%", + "secondary": "142 58% 40%", + "secondaryForeground": "150 26% 97%", + "muted": "150 18% 89%", + "mutedForeground": "165 16% 38%", + "accent": "40 92% 48%", + "accentForeground": "150 26% 97%", + "destructive": "2 74% 46%", + "destructiveForeground": "150 26% 97%", + "border": "150 16% 83%", + "input": "150 16% 83%", + "ring": "168 62% 34%" }, - "mode": "dark" + "darkColors": { + "background": "168 18% 6%", + "foreground": "150 24% 92%", + "card": "168 18% 11%", + "cardForeground": "150 24% 92%", + "popover": "168 18% 11%", + "popoverForeground": "150 24% 92%", + "primary": "168 72% 52%", + "primaryForeground": "168 18% 6%", + "secondary": "142 66% 56%", + "secondaryForeground": "168 18% 6%", + "muted": "168 14% 16%", + "mutedForeground": "150 14% 66%", + "accent": "42 98% 60%", + "accentForeground": "168 18% 6%", + "destructive": "0 80% 60%", + "destructiveForeground": "168 18% 6%", + "border": "168 12% 21%", + "input": "168 12% 23%", + "ring": "168 72% 52%" + } } } ] diff --git a/seed/seed.json b/seed/seed.json new file mode 100644 index 0000000..e804541 --- /dev/null +++ b/seed/seed.json @@ -0,0 +1,120 @@ +{ + "demo": true, + "settings": { + "merge": { + "site_name": "Subspace Signal", + "tagline": "A weekly transmission on science fiction and the future." + } + }, + "data_tables": [ + { + "key": "hail_signals", + "name": "Hail signals", + "description": "Messages sent from the Subspace Signal contact console.", + "primary_key": "id", + "columns": [ + { "key": "name", "label": "Name", "type": "text", "required": true }, + { "key": "email", "label": "Email", "type": "email", "required": true }, + { "key": "subject", "label": "Subject", "type": "text", "required": false }, + { "key": "message", "label": "Message", "type": "text", "required": true } + ] + } + ], + "pages": [ + { + "slug": "/", + "title": "Subspace Signal", + "template_key": "full-width", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 1, "content": { "template": "left-aligned", "eyebrow": "Now broadcasting", "headline": "Subspace Signal", "subheadline": "A weekly transmission on science fiction, deep space, and the people building the future. Two hosts. One open channel.", "ctaPrimary": { "text": "Listen now", "url": "/episodes" }, "ctaSecondary": { "text": "Meet the crew", "url": "/about" } } }, + { "block_key": "lcars:episode_console", "title": "Featured episode", "slot": "main", "sort_order": 2, "content": { "eyebrow": "Latest transmission", "title": "First contact was a podcast", "episodeNumber": "048", "duration": "58:12", "stardate": "78412.3", "description": "We open the season with a simple question. If a signal reached us tomorrow, who would answer, and what would we say back.", "listenUrl": "/episodes/first-contact", "listenLabel": "Listen now", "platforms": [ { "label": "Apple", "url": "/episodes" }, { "label": "Spotify", "url": "/episodes" }, { "label": "RSS", "url": "/episodes" } ] } }, + { "block_key": "stats", "title": "Signal log", "slot": "main", "sort_order": 3, "content": { "heading": "Signal log", "columns": 3, "countUp": false, "items": [ { "value": "48", "label": "Episodes aired" }, { "value": "4", "label": "Seasons on air" }, { "value": "26", "label": "Star systems covered" } ] } }, + { "block_key": "lcars:crew_roster", "title": "Crew", "slot": "main", "sort_order": 4, "content": { "eyebrow": "On the bridge", "heading": "Your crew", "members": [ { "name": "Vega Okonkwo", "role": "Host", "callsign": "Bridge-1", "bio": "Reads too much hard science fiction and asks the questions engineers dread." }, { "name": "Rell Tavras", "role": "Co-host", "callsign": "Science-2", "bio": "Keeps the physics honest and the metaphors from getting away from us." } ] } }, + { "block_key": "cta", "title": "Closing CTA", "slot": "main", "sort_order": 5, "content": { "background": "band", "heading": "Tune the frequency", "text": "New transmission every week. Subscribe on your channel of choice.", "primaryLabel": "Subscribe", "primaryUrl": "/episodes" } } + ] + }, + { + "slug": "/about", + "title": "About", + "template_key": "default", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "About the signal", "level": 1 } }, + { "block_key": "text", "title": "Intro", "slot": "main", "sort_order": 2, "content": { "text": "

Subspace Signal is a weekly show about science fiction and the real science it borrows from. We read the books, watch the shows, and talk to the people who build the future for a living. Every episode is one open channel between two hosts and you.

" } }, + { "block_key": "lcars:transmission_status", "title": "Systems", "slot": "main", "sort_order": 3, "content": { "eyebrow": "Console", "heading": "Channel status", "systems": [ { "label": "Weekly uplink", "status": "online", "detail": "Every Tuesday" }, { "label": "Back catalogue", "status": "online", "detail": "48 episodes" }, { "label": "Listener comms", "status": "standby", "detail": "Open for hails" }, { "label": "Live recordings", "status": "standby", "detail": "Next season" } ] } }, + { "block_key": "lcars:stardate_log", "title": "Log", "slot": "main", "sort_order": 4, "content": { "eyebrow": "Ship's log", "heading": "How we got here", "entries": [ { "stardate": "74001.0", "title": "Signal first opened", "text": "Two friends started recording arguments about warp drives in a spare room." }, { "stardate": "76210.5", "title": "First guest aboard", "text": "A working propulsion engineer joined to explain why the arguments were wrong." }, { "stardate": "78412.3", "title": "Season four begins", "text": "New gear, same open channel, more star systems to cover." } ] } } + ] + }, + { + "slug": "/episodes", + "title": "Episodes", + "template_key": "blog-index", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "card", "title": "Episode 48", "slot": "main", "sort_order": 1, "content": { "title": "First contact was a podcast", "text": "

If a signal reached us tomorrow, who would answer, and what would we say back.

", "link": "/episodes/first-contact", "linkLabel": "Listen" } }, + { "block_key": "card", "title": "Episode 47", "slot": "main", "sort_order": 2, "content": { "title": "The physics of warp, honestly", "text": "

A propulsion engineer walks us through what the math actually allows.

", "link": "/episodes/warp-honestly", "linkLabel": "Listen" } }, + { "block_key": "card", "title": "Episode 46", "slot": "main", "sort_order": 3, "content": { "title": "Terraforming on a budget", "text": "

What it would really take to make one small world a little more livable.

", "link": "/episodes/terraforming", "linkLabel": "Listen" } } + ] + }, + { + "slug": "/episodes/first-contact", + "parent_slug": "/episodes", + "title": "First contact was a podcast", + "template_key": "article", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "First contact was a podcast", "level": 1 } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "

We open the season with a simple question. If a signal reached us tomorrow, who would answer, and what would we say back. The honest answer is that nobody is really in charge of the reply.

So we build the reply ourselves. We talk through who we would want on the line, what we would lead with, and why the first message matters more than the last.

" } } + ] + }, + { + "slug": "/episodes/warp-honestly", + "parent_slug": "/episodes", + "title": "The physics of warp, honestly", + "template_key": "article", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "The physics of warp, honestly", "level": 1 } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "

Warp drive makes for a great story and a hard equation. We asked a working propulsion engineer to walk us through what the math actually allows and where the fiction quietly cheats.

The short version is that the energy budget is the whole story. Everything else is set dressing until you solve for that.

" } } + ] + }, + { + "slug": "/episodes/terraforming", + "parent_slug": "/episodes", + "title": "Terraforming on a budget", + "template_key": "article", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Terraforming on a budget", "level": 1 } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "

Making a whole planet livable is a project measured in centuries. So we asked a smaller question. What is the cheapest first step that would make one small world a little more forgiving.

The answer is not domes or rockets. It is patience, pressure, and a very long supply of the right microbes.

" } } + ] + }, + { + "slug": "/contact", + "title": "Hail us", + "template_key": "contact", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "contact-form", "title": "Hail console", "slot": "main", "sort_order": 1, "content": { "heading": "Open a channel", "description": "Send a hail. We read every signal and reply within a few days.", "submitLabel": "Transmit", "successMessage": "Signal received. We will reply on this channel shortly.", "formConfig": { "targetTable": "hail_signals", "enabled": true, "captchaEnabled": false }, "fields": [ { "name": "name", "label": "Name", "type": "text", "required": true, "width": "half" }, { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" }, { "name": "subject", "label": "Subject", "type": "text", "required": false, "width": "full" }, { "name": "message", "label": "Message", "type": "textarea", "required": true, "width": "full" } ] } }, + { "block_key": "contact-card", "title": "Direct channels", "slot": "aside", "sort_order": 1, "content": { "heading": "Direct channels", "columns": 1, "channels": [ { "icon": "lucide:mail", "label": "Email", "value": "hail@subspace.example", "link": "mailto:hail@subspace.example" }, { "icon": "lucide:radio", "label": "Studio line", "value": "+61 2 5550 0148", "link": "tel:+61255500148" } ] } }, + { "block_key": "hours-location", "title": "Broadcast hours", "slot": "aside", "sort_order": 2, "content": { "heading": "Broadcast hours", "address": "Deck 12, Signal Studios, Sydney NSW 2000", "hours": [ { "day": "New episode", "hours": "Tuesdays 06:00" }, { "day": "Listener comms", "hours": "Monday to Friday 09:00 to 17:00" }, { "day": "Weekend", "closed": true } ] } } + ] + }, + { + "slug": "/login", + "title": "Access terminal", + "template_key": "auth", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } } + ] + } + ], + "menu_items": [ + { "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 }, + { "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 2 }, + { "menu": "main", "label": "Episodes", "page_slug": "/episodes", "sort_order": 3 }, + { "menu": "main", "label": "Hail us", "page_slug": "/contact", "sort_order": 4 } + ] +} diff --git a/seed/workflows.json b/seed/workflows.json new file mode 100644 index 0000000..f5302b5 --- /dev/null +++ b/seed/workflows.json @@ -0,0 +1,19 @@ +{ + "workflows": [ + { + "key": "hail_notify_admins", + "name": "Notify admins on hail signal", + "description": "Emails site admins whenever the Subspace Signal contact console receives a hail.", + "enabled": true, + "trigger": { "type": "row_inserted", "table": "hail_signals" }, + "steps": [ + { + "type": "email", + "name": "Email admins", + "template": "Form Submission Notification", + "recipients": { "mode": "admins", "scope": "all" } + } + ] + } + ] +} diff --git a/templates/email/lcars.ninjatpl b/templates/email/lcars.ninjatpl index 7fba0a6..2b54da2 100644 --- a/templates/email/lcars.ninjatpl +++ b/templates/email/lcars.ninjatpl @@ -1,43 +1,66 @@ - - - - - - {% if preview_text %} - {{ preview_text }} - {% endif %} - - - -
-
- - - -
{{ site_name }}
-
- -
- -
- {{ body|safe }} -
- - -
- + + + + + + + + {{ site_name }} + + + + {% if preview_text %}
{{ preview_text }}
{% endif %} + + + + +
+ + + + + + + + + + + + + + + + + + +
+ diff --git a/templates/lcars/article.ninjatpl b/templates/lcars/article.ninjatpl new file mode 100644 index 0000000..a7ed7c4 --- /dev/null +++ b/templates/lcars/article.ninjatpl @@ -0,0 +1,18 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
+
{% if slots.main %}{{ slots.main|safe }}{% else %}

No transcript on file.

{% endif %}
+ {% if slots.aside %}{% endif %} +
+
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/lcars/auth.ninjatpl b/templates/lcars/auth.ninjatpl new file mode 100644 index 0000000..5a64195 --- /dev/null +++ b/templates/lcars/auth.ninjatpl @@ -0,0 +1,18 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+
+ {{ site_settings.Title|default:"LCARS" }} +
+ {% if slots.main %}{{ slots.main|safe }}{% else %}

No access console assigned.

{% endif %} +
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} +
+
+ {{ body_end_html|safe }} + + diff --git a/templates/lcars/blog-index.ninjatpl b/templates/lcars/blog-index.ninjatpl new file mode 100644 index 0000000..bf2ab82 --- /dev/null +++ b/templates/lcars/blog-index.ninjatpl @@ -0,0 +1,19 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
+ {% if title %}

Broadcast Log

{{ title }}

{% if page_meta.MetaDescription %}

{{ page_meta.MetaDescription }}

{% endif %}
{% endif %} + {% if slots.featured %}
{{ slots.featured|safe }}
{% endif %} + {% if slots.main %}
{{ slots.main|safe }}
{% else %}

No episodes logged yet.

{% endif %} +
+
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/lcars/contact.ninjatpl b/templates/lcars/contact.ninjatpl new file mode 100644 index 0000000..8028aef --- /dev/null +++ b/templates/lcars/contact.ninjatpl @@ -0,0 +1,25 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
+ {% if slots.aside %} +
+
{{ slots.main|safe }}
+ +
+ + {% else %} +
{% if slots.main %}{{ slots.main|safe }}{% else %}

No channel configured.

{% endif %}
+ {% endif %} +
+
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/lcars/default.ninjatpl b/templates/lcars/default.ninjatpl index 8d9c694..67ddb53 100644 --- a/templates/lcars/default.ninjatpl +++ b/templates/lcars/default.ninjatpl @@ -1,45 +1,22 @@ -{% extends "base.html" %} - -{% block body_class %}lcars-page lcars-default bg-background text-foreground antialiased min-h-screen{% endblock %} - -{% block body %} -
-
- {{ slots.header|safe }} -
- -
-
-
-
-
- -
- - -
- -
- {% if slots.main %} - {{ slots.main|safe }} - {% else %} -
-

No data on file

-
- {% endif %} -
-
- -
-
-
-
-
- -
- {{ slots.footer|safe }} -
-
-{% endblock %} + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ {% if slots.header %}
{{ slots.header|safe }}
{% endif %} + {% if slots.sidebar %} +
+ +
{% if slots.main %}{{ slots.main|safe }}{% else %}

No data on file.

{% endif %}
+
+ {% else %} +
{% if slots.main %}{{ slots.main|safe }}{% else %}

No data on file.

{% endif %}
+ {% endif %} +
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/lcars/full-display.ninjatpl b/templates/lcars/full-display.ninjatpl index 4b7e4d0..8cf9ea5 100644 --- a/templates/lcars/full-display.ninjatpl +++ b/templates/lcars/full-display.ninjatpl @@ -1,31 +1,15 @@ -{% extends "base.html" %} - -{% block body_class %}lcars-page lcars-full-display bg-background text-foreground antialiased min-h-screen{% endblock %} - -{% block body %} -
-
- {{ slots.header|safe }} -
- -
-
- -
- {% if slots.main %} - {{ slots.main|safe }} - {% else %} -
-

No data on file

-
- {% endif %} -
- -
-
- -
- {{ slots.footer|safe }} -
-
-{% endblock %} + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
{% if slots.main %}{{ slots.main|safe }}{% else %}

No data on file.

{% endif %}
+
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/lcars/full-width.ninjatpl b/templates/lcars/full-width.ninjatpl new file mode 100644 index 0000000..8cf9ea5 --- /dev/null +++ b/templates/lcars/full-width.ninjatpl @@ -0,0 +1,15 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
{% if slots.main %}{{ slots.main|safe }}{% else %}

No data on file.

{% endif %}
+
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/lcars/landing.ninjatpl b/templates/lcars/landing.ninjatpl new file mode 100644 index 0000000..7f8e9ac --- /dev/null +++ b/templates/lcars/landing.ninjatpl @@ -0,0 +1,17 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
+
+ {% if slots.header %}
{{ slots.header|safe }}
{% endif %} +
{{ slots.hero|safe }}
+
{% if slots.main %}
{{ slots.main|safe }}
{% endif %}
+
{{ slots.cta|safe }}
+
+
+ {% if slots.footer %}
{{ slots.footer|safe }}
{% endif %} + {{ body_end_html|safe }} + + diff --git a/templates/overrides/lcars/announcement-bar.ninjatpl b/templates/overrides/lcars/announcement-bar.ninjatpl new file mode 100644 index 0000000..54d3ab8 --- /dev/null +++ b/templates/overrides/lcars/announcement-bar.ninjatpl @@ -0,0 +1,8 @@ +{# announcement-bar override — centered notice; carries data-bn-announcement / data-bn-annkey and a data-bn-ann-dismiss close button so the host dismissal JS binds. #} +{% if variant == "promo" %}{% set annvar = "lcars-announce--promo" %}{% set annsty = "background:hsl(var(--primary));color:hsl(var(--primary-foreground));" %}{% elif variant == "alert" %}{% set annvar = "lcars-announce--alert" %}{% set annsty = "background:hsl(var(--destructive));color:hsl(var(--destructive-foreground));" %}{% else %}{% set annvar = "lcars-announce--info" %}{% set annsty = "background:hsl(var(--accent));color:hsl(var(--accent-foreground));" %}{% endif %} +
+
+

{{ message }}{% if linkText and linkUrl %} {{ linkText }}{% endif %}

+{% if dismissable %}{% endif %} +
+
diff --git a/templates/overrides/lcars/audio-embed.ninjatpl b/templates/overrides/lcars/audio-embed.ninjatpl new file mode 100644 index 0000000..3f5425c --- /dev/null +++ b/templates/overrides/lcars/audio-embed.ninjatpl @@ -0,0 +1,14 @@ +{# audio-embed override — hairline card row; carries data-bn-audio / data-audio-url / data-audio-media + data-bn-audio-slot so the host facade JS binds. #} +
+
+{% if artwork %}
{% img artwork alt=title|default:"" class="lcars-audio-art" %}
{% endif %} +
+{% if title %}

{{ title }}

{% endif %} +{% if artist %}

{{ artist }}

{% endif %} +
+ +{% if download %}::lucide:download:sm:: Download{% endif %} +
+
+
+
diff --git a/templates/overrides/lcars/auth-form.ninjatpl b/templates/overrides/lcars/auth-form.ninjatpl new file mode 100644 index 0000000..7fd1a8c --- /dev/null +++ b/templates/overrides/lcars/auth-form.ninjatpl @@ -0,0 +1,22 @@ +
+ + +
+ + +
+
+ +
+
+
+
+ +
+ +
diff --git a/templates/overrides/lcars/auth-status.ninjatpl b/templates/overrides/lcars/auth-status.ninjatpl new file mode 100644 index 0000000..06b283f --- /dev/null +++ b/templates/overrides/lcars/auth-status.ninjatpl @@ -0,0 +1 @@ +{% if context.isPublicLoggedIn %}
{{ context.publicDisplayName|default:context.publicUsername }}AccountSign out
{% else %}{% endif %} diff --git a/templates/overrides/lcars/author-bio-hero.ninjatpl b/templates/overrides/lcars/author-bio-hero.ninjatpl new file mode 100644 index 0000000..894f9e0 --- /dev/null +++ b/templates/overrides/lcars/author-bio-hero.ninjatpl @@ -0,0 +1 @@ +{% if context.currentAuthor %}
{% if showAvatar and context.currentAuthor.avatarUrl %}{{ context.currentAuthor.displayName }}{% else %}{% endif %}

{{ context.currentAuthor.displayName }}

{% if context.currentAuthor.bio %}

{{ context.currentAuthor.bio }}

{% endif %}{% if showSocial %}
{% if context.currentAuthor.websiteUrl %}{% endif %}{% if context.currentAuthor.twitterHandle %}{% endif %}{% if context.currentAuthor.linkedinUrl %}{% endif %}
{% endif %}
{% endif %} diff --git a/templates/overrides/lcars/author-bio.ninjatpl b/templates/overrides/lcars/author-bio.ninjatpl new file mode 100644 index 0000000..d6855eb --- /dev/null +++ b/templates/overrides/lcars/author-bio.ninjatpl @@ -0,0 +1 @@ +{% if context.currentPost.authorName %}{% endif %} diff --git a/templates/overrides/lcars/blog-hero.ninjatpl b/templates/overrides/lcars/blog-hero.ninjatpl new file mode 100644 index 0000000..1fbcfcb --- /dev/null +++ b/templates/overrides/lcars/blog-hero.ninjatpl @@ -0,0 +1 @@ +
{% if title %}

{{ title }}

{% endif %}{% if subtitle %}

{{ subtitle }}

{% endif %}
{% if posts %}
{% for post in posts %}
{% if post.featured_image_url %}{{ post.title }}{% endif %}

{{ post.title }}

{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %}
{% endfor %}
{% endif %}
diff --git a/templates/overrides/lcars/breadcrumbs.ninjatpl b/templates/overrides/lcars/breadcrumbs.ninjatpl new file mode 100644 index 0000000..f46eb8b --- /dev/null +++ b/templates/overrides/lcars/breadcrumbs.ninjatpl @@ -0,0 +1 @@ + diff --git a/templates/overrides/lcars/button.ninjatpl b/templates/overrides/lcars/button.ninjatpl new file mode 100644 index 0000000..25c69b2 --- /dev/null +++ b/templates/overrides/lcars/button.ninjatpl @@ -0,0 +1,6 @@ +{% set btnStyle = style|default:"primary" %} +{% set btnClass = "lcars-btn-primary" %} +{% if btnStyle == "outline" or btnStyle == "secondary" %}{% set btnClass = "lcars-btn-outline" %}{% elif btnStyle == "ghost" or btnStyle == "link" %}{% set btnClass = "lcars-btn-ghost" %}{% endif %} +{% set sizeClass = "" %} +{% if size == "sm" %}{% set sizeClass = "lcars-btn-sm" %}{% elif size == "lg" %}{% set sizeClass = "lcars-btn-lg" %}{% endif %} + diff --git a/templates/overrides/lcars/card.ninjatpl b/templates/overrides/lcars/card.ninjatpl new file mode 100644 index 0000000..8fba679 --- /dev/null +++ b/templates/overrides/lcars/card.ninjatpl @@ -0,0 +1 @@ +
{% if media %}
{% img media alt=title|default:"" class="lcars-card-media" %}
{% endif %}{% if title %}

{{ title }}

{% endif %}{% if text %}
{{ text|safe }}
{% endif %}{% if linkLabel and link %}{{ linkLabel }}::lucide:arrow-right:sm::{% endif %}
diff --git a/templates/overrides/lcars/category-list.ninjatpl b/templates/overrides/lcars/category-list.ninjatpl new file mode 100644 index 0000000..aac8315 --- /dev/null +++ b/templates/overrides/lcars/category-list.ninjatpl @@ -0,0 +1 @@ +
{% if heading %}

{{ heading }}

{% endif %}{% if style == "pills" %}
{% for cat in categories %}{{ cat.name }}{% if showCount %} {{ cat.post_count }}{% endif %}{% endfor %}
{% else %}{% endif %}
diff --git a/templates/overrides/lcars/contact-card.ninjatpl b/templates/overrides/lcars/contact-card.ninjatpl new file mode 100644 index 0000000..7cb8140 --- /dev/null +++ b/templates/overrides/lcars/contact-card.ninjatpl @@ -0,0 +1,14 @@ +{# contact-card override — grouped contact channels; each channel a hairline mark + label + value. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if description %}

{{ description }}

{% endif %} +
+{% for channel in channels %}
+{% if channel.icon %}::{{ channel.icon }}:sm::{% endif %} +
+{% if channel.label %}
{{ channel.label }}
{% endif %} +{% if channel.value %}
{% if channel.link %}{{ channel.value }}{% else %}{{ channel.value }}{% endif %}
{% endif %} +
+
{% endfor %} +
+
diff --git a/templates/overrides/lcars/contact-form.ninjatpl b/templates/overrides/lcars/contact-form.ninjatpl new file mode 100644 index 0000000..98790df --- /dev/null +++ b/templates/overrides/lcars/contact-form.ninjatpl @@ -0,0 +1,34 @@ +{# contact-form override — server-driven; block-id resolution, {% form %} wiring, honeypot, and field loop copied verbatim from the builtin, reskinned with lcars-form primitives. #} +{% with bid=_block_id|default:context.blockId %} +
+{% if form.submitted %} +
+ +

{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}

+
+{% else %} +{% if heading %}

{{ heading }}

{% endif %} +{% if description %}

{{ description }}

{% endif %} +{% if form.errors._form %}
{{ form.errors._form }}
{% endif %} +{% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %} +{% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="lcars-form-grid" %} +{% for field in fields %} +
+{% if field.label %}{% endif %} +{% if field.type == "textarea" %} + +{% elif field.type == "select" %} + +{% else %} + +{% endif %} +
+{% endfor %} + +
+{% endform %} +{% endwith %} +{% endif %} +
+ +{% endwith %} diff --git a/templates/overrides/lcars/countdown.ninjatpl b/templates/overrides/lcars/countdown.ninjatpl new file mode 100644 index 0000000..055ee0c --- /dev/null +++ b/templates/overrides/lcars/countdown.ninjatpl @@ -0,0 +1,44 @@ +{# countdown override — oversized mono unit boxes; host countdown JS binds via data-* attrs. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +
+
+
--{{ labelDays|default:"Days" }}
+
--{{ labelHours|default:"Hours" }}
+
--{{ labelMinutes|default:"Minutes" }}
+
--{{ labelSeconds|default:"Seconds" }}
+
+

{{ expiredMessage|default:"This has started." }}

{% if expiredUrl and expiredLabel %}{{ expiredLabel }}{% endif %}
+
+ +
+ diff --git a/templates/overrides/lcars/cta.ninjatpl b/templates/overrides/lcars/cta.ninjatpl new file mode 100644 index 0000000..ed52675 --- /dev/null +++ b/templates/overrides/lcars/cta.ninjatpl @@ -0,0 +1,8 @@ +{# cta override — full-width strip; band / card / split variants. #} +
+
+{% if heading %}

{{ heading }}

{% endif %} +{% if text %}

{{ text }}

{% endif %} +
+{% if primaryLabel or secondaryLabel %}
{% if primaryUrl and primaryLabel %}{{ primaryLabel }}{% endif %}{% if secondaryUrl and secondaryLabel %}{{ secondaryLabel }}{% endif %}
{% endif %} +
diff --git a/templates/overrides/lcars/divider.ninjatpl b/templates/overrides/lcars/divider.ninjatpl new file mode 100644 index 0000000..ad9e2b5 --- /dev/null +++ b/templates/overrides/lcars/divider.ninjatpl @@ -0,0 +1,2 @@ +{# divider override — plain hairline / thick spacer rule / centered ornament between two hairlines. #} +{% if variant == "ornament" %}{% elif variant == "spacer-rule" %}{% else %}{% endif %} diff --git a/templates/overrides/lcars/event-list.ninjatpl b/templates/overrides/lcars/event-list.ninjatpl new file mode 100644 index 0000000..4d6f77a --- /dev/null +++ b/templates/overrides/lcars/event-list.ninjatpl @@ -0,0 +1,31 @@ +{# event-list override — dates shown VERBATIM in each event's own timezone (never converted); list rows or card grid. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +{% if layout == "cards" %} +
+{% for event in events %}{% if not (hidePast and event.past) %}
+{% if event.media %}
{% img event.media alt=event.title|default:"" class="lcars-event-media" %}
{% endif %} +
+
{{ event.start }}{% if event.end %} – {{ event.end }}{% endif %}{% if event.timezone %} {{ event.timezone }}{% endif %}
+

{{ event.title }}

+{% if event.venue %}

{{ event.venue }}

{% endif %} +{% if event.description %}

{{ event.description }}

{% endif %} +{% if event.link %}{{ event.linkLabel|default:"Details" }}::lucide:arrow-right:sm::{% endif %} +
+
{% endif %}{% endfor %} +
+{% else %} +
    +{% for event in events %}{% if not (hidePast and event.past) %}
  • +
    {{ event.start }}{% if event.end %}
    to {{ event.end }}
    {% endif %}{% if event.timezone %}
    {{ event.timezone }}
    {% endif %}
    +
    +

    {{ event.title }}

    +{% if event.venue %}

    {{ event.venue }}

    {% endif %} +{% if event.description %}

    {{ event.description }}

    {% endif %} +
    +{% if event.link %}{{ event.linkLabel|default:"Details" }}{% endif %} +
  • {% endif %}{% endfor %} +
+{% endif %} +
diff --git a/templates/overrides/lcars/faq.ninjatpl b/templates/overrides/lcars/faq.ninjatpl new file mode 100644 index 0000000..0d8feaa --- /dev/null +++ b/templates/overrides/lcars/faq.ninjatpl @@ -0,0 +1,6 @@ +{# faq override — native hairline-divided disclosure list. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +{% if items %}
{% for item in items %}
{{ item.question }}
{{ item.answer|safe }}
{% endfor %}
{% endif %} +
diff --git a/templates/overrides/lcars/feature-grid.ninjatpl b/templates/overrides/lcars/feature-grid.ninjatpl new file mode 100644 index 0000000..b1544de --- /dev/null +++ b/templates/overrides/lcars/feature-grid.ninjatpl @@ -0,0 +1,7 @@ +{# feature-grid override — Swiss cells, each led by a Bauhaus square mark. #} +
+{% if eyebrow %}

{{ eyebrow }}

{% endif %} +{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +{% if items %}
{% for item in items %}
{% if item.icon %}{% else %}{% endif %}{% if item.title %}

{% if item.linkUrl %}{{ item.title }}{% else %}{{ item.title }}{% endif %}

{% endif %}{% if item.text %}

{{ item.text }}

{% endif %}{% if item.linkUrl and item.linkLabel %}{{ item.linkLabel }}::lucide:arrow-right:sm::{% endif %}
{% endfor %}
{% endif %} +
diff --git a/templates/overrides/lcars/featured-posts.ninjatpl b/templates/overrides/lcars/featured-posts.ninjatpl new file mode 100644 index 0000000..1256369 --- /dev/null +++ b/templates/overrides/lcars/featured-posts.ninjatpl @@ -0,0 +1 @@ +
{% if heading %}

{{ heading }}

{% endif %}
{% for post in posts %}
{% if showFeaturedImage and post.featured_image_url %}{{ post.title }}{% endif %}

{{ post.title }}

{% if showExcerpt and post.excerpt %}

{{ post.excerpt }}

{% endif %}
{% endfor %}
diff --git a/templates/overrides/lcars/footer.ninjatpl b/templates/overrides/lcars/footer.ninjatpl new file mode 100644 index 0000000..91a65f5 --- /dev/null +++ b/templates/overrides/lcars/footer.ninjatpl @@ -0,0 +1,17 @@ +{# footer override — Swiss-grid footer chrome from CONTENT fields (brand, contact, newsletter, legal); menu columns / social / legal rows render from the footer providers (empty today, forward-correct). #} + diff --git a/templates/overrides/lcars/gallery.ninjatpl b/templates/overrides/lcars/gallery.ninjatpl new file mode 100644 index 0000000..af43a71 --- /dev/null +++ b/templates/overrides/lcars/gallery.ninjatpl @@ -0,0 +1,6 @@ +{# gallery override — Swiss grid of hairline-framed images; carries data-bn-gallery / data-lightbox / data-bn-gallery-item so the host lightbox JS binds. #} +{% if columns == 4 %}{% set gcols = "lcars-gallery-4" %}{% set gn = 4 %}{% elif columns == 2 %}{% set gcols = "" %}{% set gn = 2 %}{% else %}{% set gcols = "lcars-gallery-3" %}{% set gn = 3 %}{% endif %} +{% if gap == "sm" %}{% set ggap = "0.5rem" %}{% elif gap == "lg" %}{% set ggap = "1.5rem" %}{% else %}{% set ggap = "1rem" %}{% endif %} + diff --git a/templates/overrides/lcars/heading.ninjatpl b/templates/overrides/lcars/heading.ninjatpl index 8d59fbd..2bd3c41 100644 --- a/templates/overrides/lcars/heading.ninjatpl +++ b/templates/overrides/lcars/heading.ninjatpl @@ -1 +1 @@ -<{{ tag|default:"h2" }} class="lcars-heading lcars-heading-{{ level|default:2 }}">{{ text }} +{% if level|integer == 1 %}

{{ text }}

{% elif level|integer == 3 %}

{{ text }}

{% elif level|integer == 4 %}

{{ text }}

{% elif level|integer == 5 %}
{{ text }}
{% elif level|integer == 6 %}
{{ text }}
{% else %}

{{ text }}

{% endif %} diff --git a/templates/overrides/lcars/hero.ninjatpl b/templates/overrides/lcars/hero.ninjatpl new file mode 100644 index 0000000..c783d05 --- /dev/null +++ b/templates/overrides/lcars/hero.ninjatpl @@ -0,0 +1,60 @@ +{# hero override — LCARS command-deck hero. GO BIG motion tier: one dependency-free + canvas panel-sweep behind the content, lazy-inited, prefers-reduced-motion honored, + with the fully-styled hero as the static no-JS fallback. All content fields preserved + verbatim from the builtin. Styled-text fields accept {text,color} OR a bare string. #} +{% set eyebrowText = eyebrow.text|default:eyebrow %} +{% set headlineText = headline.text|default:headline %} +{% set subText = subheadline.text|default:subheadline %} +{% set descText = description.text|default:description %} +{% set pText = ctaPrimary.text %} +{% set pUrl = ctaPrimary.url %} +{% set sText = ctaSecondary.text %} +{% set sUrl = ctaSecondary.url %} +{% set variant = template|default:"centered" %} +{% macro herobody() %} +{% if eyebrowText %}

{{ eyebrowText }}

{% endif %} +{% if headlineText %}

{{ headlineText }}

{% endif %} +{% if subText %}

{{ subText }}

{% endif %} +{% if descText %}

{{ descText }}

{% endif %} +{% if pText or sText %}
{% if pText and pUrl %}{{ pText }}{% endif %}{% if sText and sUrl %}{{ sText }}{% endif %}
{% endif %} +{% if trustLogos %}

Trusted by

{% for logo in trustLogos %}{% if logo.url %}{% img logo.image alt=logo.alt class="lcars-logo" %}{% else %}{% img logo.image alt=logo.alt class="lcars-logo" %}{% endif %}{% endfor %}
{% endif %} +{% endmacro %} +{% if variant == "split" %} +
{{ herobody() }}
{% if backgroundImage %}{% img backgroundImage alt=headlineText class="lcars-hero-media" %}{% endif %}
+{% else %} +
{{ herobody() }}
+{% endif %} + diff --git a/templates/overrides/lcars/hours-location.ninjatpl b/templates/overrides/lcars/hours-location.ninjatpl new file mode 100644 index 0000000..2b7a76a --- /dev/null +++ b/templates/overrides/lcars/hours-location.ninjatpl @@ -0,0 +1,18 @@ +{# hours-location override — hairline hours table + address; click-to-load map preserves the builtin data-map-src / onclick wiring. #} +
+{% if heading %}

{{ heading }}

{% endif %} +
+
+{% if hours %}{% for row in hours %}{% endfor %}
{{ row.day }}{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}
{% endif %} +{% if specialHoursNote %}

{{ specialHoursNote }}

{% endif %} +{% if address %}
Address
{{ address }}
{% endif %} +{% if phone %}{% endif %} +{% if directionsUrl %}::lucide:navigation:sm::Get directions{% endif %} +
+{% if mapEmbedUrl %} +
+ +
+{% endif %} +
+
diff --git a/templates/overrides/lcars/image.ninjatpl b/templates/overrides/lcars/image.ninjatpl new file mode 100644 index 0000000..8a735bf --- /dev/null +++ b/templates/overrides/lcars/image.ninjatpl @@ -0,0 +1,5 @@ +{# image override — hairline-framed figure with alignment and an optional caption. #} +{% if src %}{% if align == "left" %}{% set ialign = "flex-start" %}{% elif align == "right" %}{% set ialign = "flex-end" %}{% else %}{% set ialign = "center" %}{% endif %}
+
{% img src alt=alt|default:"" class="lcars-image-el" %}
+{% if caption %}
{{ caption }}
{% endif %} +
{% endif %} diff --git a/templates/overrides/lcars/logo-strip.ninjatpl b/templates/overrides/lcars/logo-strip.ninjatpl new file mode 100644 index 0000000..bb32a9d --- /dev/null +++ b/templates/overrides/lcars/logo-strip.ninjatpl @@ -0,0 +1,5 @@ +{# logo-strip override — quiet greyscale client row under a kicker. #} +
+{% if heading %}

{{ heading }}

{% endif %} +
{% for logo in logos %}{% if logo.url %}{% img logo.image alt=logo.alt class="lcars-logo" %}{% else %}{% img logo.image alt=logo.alt class="lcars-logo" %}{% endif %}{% endfor %}
+
diff --git a/templates/overrides/lcars/menu-list.ninjatpl b/templates/overrides/lcars/menu-list.ninjatpl new file mode 100644 index 0000000..8536299 --- /dev/null +++ b/templates/overrides/lcars/menu-list.ninjatpl @@ -0,0 +1,21 @@ +{# menu-list override — sectioned price list; tabular-numeral prices right-aligned, dietary badges as hairline pills. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +
+{% for section in sections %}
+{% if section.title %}

{{ section.title }}

{% endif %} +{% if section.note %}

{{ section.note }}

{% endif %} +
    +{% for item in section.items %}
  • +
    +
    {{ item.name }}{% for badge in item.badges %}{% if badge.label %}{{ badge.label }}{% endif %}{% endfor %}
    +{% if item.description %}

    {{ item.description }}

    {% endif %} +
    +{% if item.price %}{% if currency %}{{ currency }}{% endif %}{{ item.price }}{% endif %} +
  • {% endfor %} +
+
{% endfor %} +
+{% if footnote %}

{{ footnote }}

{% endif %} +
diff --git a/templates/overrides/lcars/navbar.ninjatpl b/templates/overrides/lcars/navbar.ninjatpl new file mode 100644 index 0000000..48a71aa --- /dev/null +++ b/templates/overrides/lcars/navbar.ninjatpl @@ -0,0 +1 @@ + diff --git a/templates/overrides/lcars/newsletter-signup.ninjatpl b/templates/overrides/lcars/newsletter-signup.ninjatpl new file mode 100644 index 0000000..7329d23 --- /dev/null +++ b/templates/overrides/lcars/newsletter-signup.ninjatpl @@ -0,0 +1 @@ +
{% if title %}

{{ title }}

{% endif %}{% if description %}

{{ description }}

{% endif %}
{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}
diff --git a/templates/overrides/lcars/page-suggestions.ninjatpl b/templates/overrides/lcars/page-suggestions.ninjatpl new file mode 100644 index 0000000..dc46e2b --- /dev/null +++ b/templates/overrides/lcars/page-suggestions.ninjatpl @@ -0,0 +1 @@ +

{{ title|default:"Were you looking for..." }}

{% if suggestions %}{% else %}{% if emptyMessage %}

{{ emptyMessage }}

{% endif %}{% endif %}
diff --git a/templates/overrides/lcars/password-reset-form.ninjatpl b/templates/overrides/lcars/password-reset-form.ninjatpl new file mode 100644 index 0000000..fac3360 --- /dev/null +++ b/templates/overrides/lcars/password-reset-form.ninjatpl @@ -0,0 +1,11 @@ +
+ +

Reset password

+

Enter your email and we will send a link to reset your password.

+
+
+
+ +
+ ::lucide:arrow-left:sm::Back to sign in +
diff --git a/templates/overrides/lcars/popular-posts.ninjatpl b/templates/overrides/lcars/popular-posts.ninjatpl new file mode 100644 index 0000000..7c2df73 --- /dev/null +++ b/templates/overrides/lcars/popular-posts.ninjatpl @@ -0,0 +1 @@ +
{% if heading %}

{{ heading }}

{% endif %}
    {% for post in posts %}
  1. {% if showRank %}{{ forloop.Counter }}{% endif %}{% if showThumbnail and post.featured_image_url %}{{ post.title }}{% endif %}

    {{ post.title }}

    {% if post.published_at %}{% endif %}
  2. {% endfor %}
diff --git a/templates/overrides/lcars/post-hero.ninjatpl b/templates/overrides/lcars/post-hero.ninjatpl new file mode 100644 index 0000000..695e43f --- /dev/null +++ b/templates/overrides/lcars/post-hero.ninjatpl @@ -0,0 +1 @@ +
{% if context.currentPost.featuredImageUrl %}
{{ context.currentPost.featuredImageAlt|default:context.currentPost.title }}
{% endif %}

{{ context.currentPost.title }}

{% if showExcerpt and context.currentPost.excerpt %}

{{ context.currentPost.excerpt }}

{% endif %}{% if showMeta %}{% endif %}
diff --git a/templates/overrides/lcars/post-metadata.ninjatpl b/templates/overrides/lcars/post-metadata.ninjatpl new file mode 100644 index 0000000..94eee5d --- /dev/null +++ b/templates/overrides/lcars/post-metadata.ninjatpl @@ -0,0 +1 @@ + diff --git a/templates/overrides/lcars/pricing.ninjatpl b/templates/overrides/lcars/pricing.ninjatpl new file mode 100644 index 0000000..ac0ab4f --- /dev/null +++ b/templates/overrides/lcars/pricing.ninjatpl @@ -0,0 +1,14 @@ +{# pricing override — flat hairline tiers, mono price figures, accent-top featured tier. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +{% if tiers %}
{% for tier in tiers %}{% endfor %}
{% endif %} +{% if billingNote %}

{{ billingNote }}

{% endif %} +
diff --git a/templates/overrides/lcars/quote.ninjatpl b/templates/overrides/lcars/quote.ninjatpl new file mode 100644 index 0000000..5873a59 --- /dev/null +++ b/templates/overrides/lcars/quote.ninjatpl @@ -0,0 +1,6 @@ +{# quote override — pull quote (accent top rule) / block quote (accent left rule) / inline card. #} +{% if style == "pull" %}{% set qcls = "lcars-pull-quote" %}{% set qsty = "margin:2rem 0;" %}{% elif style == "inline-card" %}{% set qcls = "lcars-quote-block lcars-card lcars-card-accent" %}{% set qsty = "margin:2rem 0;" %}{% else %}{% set qcls = "lcars-quote-block" %}{% set qsty = "margin:2rem 0;border-left:2px solid hsl(var(--accent));padding-left:1.25rem;" %}{% endif %} +
+{{ quote|safe }} +{% if attribution or role or cite %}
{% if attribution %}{{ attribution }}{% endif %}{% if role %}{{ role }}{% endif %}{% if cite %}Source{% endif %}
{% endif %} +
diff --git a/templates/overrides/lcars/related-posts.ninjatpl b/templates/overrides/lcars/related-posts.ninjatpl new file mode 100644 index 0000000..5c26362 --- /dev/null +++ b/templates/overrides/lcars/related-posts.ninjatpl @@ -0,0 +1 @@ +

{{ heading|default:"Related posts" }}

{% for post in posts %}
{% if post.featured_image_url %}{{ post.title }}{% endif %}

{{ post.title }}

{% if post.published_at %}{% endif %}
{% endfor %}
diff --git a/templates/overrides/lcars/rich-section.ninjatpl b/templates/overrides/lcars/rich-section.ninjatpl new file mode 100644 index 0000000..baa7b17 --- /dev/null +++ b/templates/overrides/lcars/rich-section.ninjatpl @@ -0,0 +1,10 @@ +{# rich-section override — two-column media + rich text, hairline surface options. #} +
+
+{% if eyebrow %}

{{ eyebrow }}

{% endif %} +{% if heading %}

{{ heading }}

{% endif %} +{% if body %}
{{ body|safe }}
{% endif %} +{% if ctaUrl and ctaLabel %}{{ ctaLabel }}::lucide:arrow-right:sm::{% endif %} +
+
{% if media %}{% img media alt=mediaAlt class="lcars-rich-media" %}{% endif %}
+
diff --git a/templates/overrides/lcars/rich-text.ninjatpl b/templates/overrides/lcars/rich-text.ninjatpl new file mode 100644 index 0000000..e4f5d33 --- /dev/null +++ b/templates/overrides/lcars/rich-text.ninjatpl @@ -0,0 +1,2 @@ +{# rich-text override — raw imported HTML inside the Swiss reading measure. #} +{% if html %}
{{ html|safe }}
{% endif %} diff --git a/templates/overrides/lcars/sidebar-nav.ninjatpl b/templates/overrides/lcars/sidebar-nav.ninjatpl new file mode 100644 index 0000000..423bdf8 --- /dev/null +++ b/templates/overrides/lcars/sidebar-nav.ninjatpl @@ -0,0 +1,3 @@ + diff --git a/templates/overrides/lcars/social-links.ninjatpl b/templates/overrides/lcars/social-links.ninjatpl new file mode 100644 index 0000000..efc81ac --- /dev/null +++ b/templates/overrides/lcars/social-links.ninjatpl @@ -0,0 +1,7 @@ +{# social-links override — icon row or labelled stack; each link a hairline lcars-social-link (rendered only when it has a URL). #} +
+{% if heading %}

{{ heading }}

{% endif %} + +
diff --git a/templates/overrides/lcars/stats.ninjatpl b/templates/overrides/lcars/stats.ninjatpl new file mode 100644 index 0000000..435b6e2 --- /dev/null +++ b/templates/overrides/lcars/stats.ninjatpl @@ -0,0 +1,6 @@ +{# stats override — oversized tabular figures over a hairline, no count-up JS. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +{% if items %}
{% for item in items %}
{% if item.prefix %}{{ item.prefix }}{% endif %}{{ item.value }}{% if item.suffix %}{{ item.suffix }}{% endif %}
{% if item.label %}
{{ item.label }}
{% endif %}
{% endfor %}
{% endif %} +
diff --git a/templates/overrides/lcars/team.ninjatpl b/templates/overrides/lcars/team.ninjatpl new file mode 100644 index 0000000..3f1616b --- /dev/null +++ b/templates/overrides/lcars/team.ninjatpl @@ -0,0 +1,12 @@ +{# team override — leadership grid, square-framed portraits, restrained social row. #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +{% if members %}
{% for member in members %}
+{% if member.photo %}
{% img member.photo alt=member.name class="lcars-team-photo" %}
{% endif %} +{% if member.name %}

{{ member.name }}

{% endif %} +{% if member.role %}
{{ member.role }}
{% endif %} +{% if member.bio %}

{{ member.bio }}

{% endif %} +{% if member.socials %}
{% for social in member.socials %}{% if social.url %}::{{ social.icon }}:sm::{% endif %}{% endfor %}
{% endif %} +
{% endfor %}
{% endif %} +
diff --git a/templates/overrides/lcars/testimonial.ninjatpl b/templates/overrides/lcars/testimonial.ninjatpl new file mode 100644 index 0000000..c1ff75f --- /dev/null +++ b/templates/overrides/lcars/testimonial.ninjatpl @@ -0,0 +1,9 @@ +{# testimonial override — accent-ruled pull quotes, optional 5-star scale. #} +
+{% if heading %}

{{ heading }}

{% endif %} +
{% for q in quotes %}
+{% if q.rating %}
::lucide:star:sm::::lucide:star:sm::::lucide:star:sm::::lucide:star:sm::::lucide:star:sm::
{% endif %} +{% if q.quote %}
{{ q.quote }}
{% endif %} +
{% if q.avatar %}{% img q.avatar alt=q.name class="lcars-avatar-img" %}{% endif %}{% if q.name %}{{ q.name }}{% endif %}{% if q.role %}{{ q.role }}{% endif %}
+
{% endfor %}
+
diff --git a/templates/overrides/lcars/text.ninjatpl b/templates/overrides/lcars/text.ninjatpl index 71c0f22..ce69e06 100644 --- a/templates/overrides/lcars/text.ninjatpl +++ b/templates/overrides/lcars/text.ninjatpl @@ -1 +1 @@ -
{{ html|safe }}
+
{{ text|safe }}
diff --git a/templates/overrides/lcars/timeline.ninjatpl b/templates/overrides/lcars/timeline.ninjatpl new file mode 100644 index 0000000..c06e29c --- /dev/null +++ b/templates/overrides/lcars/timeline.ninjatpl @@ -0,0 +1,6 @@ +{# timeline override — numbered Swiss rail (mirrors engagement_process). #} +
+{% if heading %}

{{ heading }}

{% endif %} +{% if intro %}

{{ intro }}

{% endif %} +{% if steps %}
{% for step in steps %}
{{ forloop.Counter }}{% if not forloop.Last %}{% endif %}
{% if step.label %}

{{ step.label }}

{% endif %}{% if step.title %}

{{ step.title }}

{% endif %}{% if step.text %}

{{ step.text }}

{% endif %}{% if step.media %}
{% img step.media alt=step.title class="lcars-timeline-media" %}
{% endif %}
{% endfor %}
{% else %}

No steps configured.

{% endif %} +
diff --git a/templates/overrides/lcars/utility-bar.ninjatpl b/templates/overrides/lcars/utility-bar.ninjatpl new file mode 100644 index 0000000..b8f2771 --- /dev/null +++ b/templates/overrides/lcars/utility-bar.ninjatpl @@ -0,0 +1,9 @@ +{# utility-bar override — thin Swiss top strip; left/right item groups; icon-picker fields render via ::pack:name:: shorthand. #} +{% if variant == "primary" %}{% set ubvar = "lcars-utility-bar--primary" %}{% set ubsty = "background:hsl(var(--primary));color:hsl(var(--primary-foreground));" %}{% elif variant == "dark" %}{% set ubvar = "lcars-utility-bar--dark" %}{% set ubsty = "background:hsl(var(--foreground));color:hsl(var(--background));" %}{% else %}{% set ubvar = "lcars-utility-bar--muted" %}{% set ubsty = "background:hsl(var(--muted));color:hsl(var(--muted-foreground));border-bottom:1px solid hsl(var(--border));" %}{% endif %} +{% macro ubitem(it) %}{% if it.link %}{% if it.icon %}::{{ it.icon }}:sm::{% endif %}{% if it.label %}{{ it.label }}{% endif %}{% else %}{% if it.icon %}::{{ it.icon }}:sm::{% endif %}{% if it.label %}{{ it.label }}{% endif %}{% endif %}{% endmacro %} +
+
+
{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}
+
{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}
+
+
diff --git a/templates/overrides/lcars/video-embed.ninjatpl b/templates/overrides/lcars/video-embed.ninjatpl new file mode 100644 index 0000000..28e4a14 --- /dev/null +++ b/templates/overrides/lcars/video-embed.ninjatpl @@ -0,0 +1,11 @@ +{# video-embed override — click-to-load facade; carries data-bn-video / data-video-url / data-video-media so the host facade JS binds. #} +{% if aspect == "4:3" %}{% set vratio = "4 / 3" %}{% elif aspect == "1:1" %}{% set vratio = "1 / 1" %}{% elif aspect == "21:9" %}{% set vratio = "21 / 9" %}{% else %}{% set vratio = "16 / 9" %}{% endif %} +
+
+ +
+{% if caption %}
{{ caption }}
{% endif %} +