themes-lcars/docs/superpowers/specs/2026-06-06-lcars-card-redesign-design.md
Alex Dunmow 563ba4a37c docs(spec): add LCARS card redesign brainstorming spec
Locks the geometry, schemas, and architecture decisions from the
brainstorming session: redesigned lcars_panel (elbow/strip frames),
new lcars_rail compound block, new lcars_readout tile, plus bundled
font-token + palette + outer-frame refinements.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 20:27:43 +08:00

13 KiB

LCARS Card Redesign

Date: 2026-06-06 Scope: ~/src/blockninja/themes/lcars theme plugin Status: Design

Problem

The current LCARS theme has the outer page frame (elbows, sidebar, header) reading correctly, but its in-content blocks are generic:

  • lcars_panel renders as a thin coloured left-border with a small pill and title bar — visually closer to a Bootstrap callout than to LCARS.
  • There is no way for adjacent panels to share frame chrome (the defining "L wrapping multiple readouts" pattern of canonical LCARS).
  • There is no small-tile equivalent for dashboard-style numeric readouts.
  • Existing CSS hardcodes font-family: 'Antonio', sans-serif, which now violates the project-wide font-token rule introduced in themes/CLAUDE.md (must go through var(--font-heading|body|mono, <fallback>)).

This spec redesigns the panel system so cards read as LCARS and so that grouped cards visually share a single L-frame.

Visual outcome

The brainstorming session locked the following geometry (mockups in .superpowers/brainstorm/3782075-1780722894/content/08-wider-bigger-curve.html):

  • Outer page corners of every framed block are rounded; the inner L bend stays a sharp 90° (the corner block has border-top-left-radius on its outer corner only).
  • Column 1 (rail / corner) is 5.5rem wide.
  • Top bar / top corner row is 4rem tall; bottom bar / bottom corner row is 3rem tall — deliberate asymmetric chrome weight, top heavier than bottom.
  • Outer top corners use 1.75rem radius; outer bottom corners use 1.25rem radius.
  • Grid gap between cells is 4px, producing the slim dark separators between LCARS colour segments.
  • Identifier text in the corner blocks uses padding: 0 1.25rem 0.85rem 1rem (top, right, bottom, left) so it sits clear of all four edges.

Three block additions / changes

1. lcars_panel — redesigned (existing key)

A single self-contained framed panel. Replaces the current thin-left-border implementation.

Schema (schemas/lcars_panel.schema.json):

Property Type Default Notes
frame enum "elbow" | "strip" "elbow" Renders mode A or mode B from the brainstorm
title text "" Goes in the top bar
top_label text "" Corner-block identifier (e.g. RM-47-A); placeholder shows an LCARSy default
bottom_label text "" Bottom corner identifier (e.g. 28-301)
top_meta text "" Right-aligned bar-top text (e.g. STARDATE 47634.4)
bottom_meta text "" Right-aligned bar-bot text
status enum online | standby | alert | offline none Optional status dot in bar-top; reuses existing .lcars-status-* classes
accent_color enum primary | secondary | accent | muted primary Picks which theme token drives the top L colour
bottom_accent_color enum (same) secondary Bottom L colour — defaults differ from top so the panel reads as two-tone
content richtext "" Panel body

Render targets:

  • frame="elbow": 2-column / 3-row CSS grid as locked in the mockup (see § Geometry).
  • frame="strip": a single segmented colour bar header + bordered body. No corner blocks, no rail; the colour band carries the labels.

2. lcars_rail — new compound block

Multiple sibling content cells share a single L-frame. The defining "how cards interact with each other" answer.

Architecture choice: the rail is not a true container (HasInternalSlot: true). The CMS's RenderSlot pipeline returns all children as one concatenated HTML blob with no per-child wrappers, which makes it impossible to pair each child with its own rail segment in a single CSS grid. Instead, the rail is a compound block whose cells are defined inline in its own schema — each cell has the rail-segment colour/label AND the cell content together. This keeps v1 implementable and avoids inventing a per-child-wrapper extension to the SDK. A future iteration can add a true container variant if needed.

Schema (schemas/lcars_rail.schema.json):

Property Type Default Notes
title text "" Top-bar title
top_label / bottom_label text "" Same role as on lcars_panel
top_accent_color / bottom_accent_color enum primary / primary Colour the top and bottom corner blocks
cells array of {label, color, title, content} required, min 1 Each entry renders one rail segment + one paired body cell. color enum maps to theme tokens; default rotates mauve → gold → blue → mauve… when user leaves it blank. content is richtext.
rail_side enum left | right left Flips the L for right-handed layouts (schema field reserved; CSS implements "left" only)

3. lcars_readout — new tile block

A small dashboard tile for numeric/status displays. Designed for grids of mini-readouts.

Schema (schemas/lcars_readout.schema.json):

Property Type Default Notes
label text required Small uppercase label above the value
value text required Big numeric/text value
unit text "" Smaller unit suffix (%, LY, etc.)
accent_color enum primary Colours the top border and the value text
pulse bool false If true, the value gets the existing lcars-pulse animation

Render target: a <div> with a 4px coloured top border, padding: 0.5rem 0.75rem, label in muted small caps, big value, optional unit. Multiple readouts work in any standard grid (CSS grid, columns block, etc.) the user already has.

Geometry (locked)

.lcars-elbow {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  grid-template-rows: 4rem 1fr 3rem;
  gap: 4px;
  background: hsl(var(--background));
}
.lcars-elbow__tl     { grid-area: 1/1; border-top-left-radius:     1.75rem; }
.lcars-elbow__bar-t  { grid-area: 1/2; border-top-right-radius:    1.75rem; }
.lcars-elbow__rail   { grid-area: 2/1; }
.lcars-elbow__body   { grid-area: 2/2; background: hsl(var(--background)); }
.lcars-elbow__bl     { grid-area: 3/1; border-bottom-left-radius:  1.25rem; }
.lcars-elbow__bar-b  { grid-area: 3/2; border-bottom-right-radius: 1.25rem; }

/* Identifier text inside the corner blocks */
.lcars-elbow__tl, .lcars-elbow__bl {
  display: flex; justify-content: flex-end;
  font: 800 0.75rem/1 var(--font-heading, 'Antonio', sans-serif);
  letter-spacing: 0.08em;
  color: hsl(var(--background));
}
.lcars-elbow__tl { align-items: flex-end;   padding: 0   1.25rem 0.85rem 1rem; }
.lcars-elbow__bl { align-items: flex-start; padding: 0.75rem 1.25rem 0      1rem; }

The rail block reuses the same column/gap rules but the row template becomes 4rem repeat(N, var(--lcars-rail-row, 3.5rem)) 3rem, with one paired (rail-segment, body-cell) per cells[] entry. Segment and cell are placed in the same row via explicit grid-row in the pongo {% for %} loop — no reliance on auto-flow.

Refinements bundled in this spec

These are small but in-scope because the changes touch the same files:

  1. Font tokens. All font-family: 'Antonio', sans-serif in assets/style.css rewritten as font-family: var(--font-heading, 'Antonio', sans-serif). No bundled fonts change. fonts.json shipped as [] (per CLAUDE.md guidance) and a new RECOMMENDED_FONTS.md instructs admins to assign Antonio (or similar geometric sans) to --font-heading.
  2. Palette expansion in presets.json. Add the canonical LCARS roles beyond primary/secondary/accent: the file gains entries for peach, mauve, gold, blue, red so panel accent-colour pickers map to actual theme tokens rather than the generic three. Existing token names stay; the new ones are additive.
  3. Outer page-frame refinement. The existing page-template elbow widths and radii are re-tuned to the same 5.5rem column / 1.75rem outer radius vocabulary so the page outer chrome and the in-content panel chrome share visual language. Adds optional stardate and identifier slots on the existing lcars_header block (already partly there — just makes the chrome richer).
  4. Sidebar identifier codes. lcars_sidebar items gain an optional code field (e.g. SCI 01) rendered as small right-aligned text inside each button, so the sidebar visually rhymes with the new rail block.
  5. required_icon_packs declaration. plugin.mod gets required_icon_packs = ["lucide"] (forward-declared per CLAUDE.md). No icons are required for the new blocks today; this is staged for future status-indicator iconography.
  6. RECOMMENDED_ICONS.md added with one line (Lucide) for parity with RECOMMENDED_FONTS.md.

Out of scope

  • rail_side: "right" rendering — schema field accepted, CSS only implements "left".
  • SVG-based frames or arbitrarily large inner-bend curves — the locked design uses CSS border-radius and a sharp inner bend.
  • Icon rendering inside panels — staged via required_icon_packs but no panel uses an icon yet.
  • Touch / mobile rail-collapse behaviour — existing responsive rules in style.css already collapse the page sidebar at 768px; the new blocks inherit the same breakpoint via a single block of rules at the bottom of the stylesheet.
  • Changes to gotham or any other theme.

File-by-file impact

File Change
blocks.go Add LCARSRailMeta, LCARSReadoutMeta. Update LCARSPanelMeta description. New headerDefaults / panelDefaults updated.
register.go Register the two new blocks. Update lcars_panel registration to use the new template (blocks/panel.html rewritten). Schema load remains in place.
schemas/lcars_panel.schema.json Rewritten per § 1 above.
schemas/lcars_rail.schema.json NEW.
schemas/lcars_readout.schema.json NEW.
schemas/lcars_sidebar.schema.json Add optional code field on each item.
templates/blocks/panel.html Rewritten to render frame="elbow" and frame="strip" per the locked geometry.
templates/blocks/rail.html NEW.
templates/blocks/readout.html NEW.
templates/blocks/sidebar.html Render the new optional code field.
templates/blocks/header.html Minor refinement so it shares the new 5.5rem / 1.75rem vocabulary with panel chrome.
templates/default.html Re-tune outer-frame widths/radii to match.
assets/style.css Replace 'Antonio', sans-serif with var(--font-heading, 'Antonio', sans-serif) throughout. Add .lcars-elbow*, .lcars-strip*, .lcars-rail*, .lcars-readout* rule blocks. Re-tune .lcars-elbow-* outer-frame rules to new dimensions. Delete the old .lcars-panel* rules.
presets.json Add peach / mauve / gold / blue / red colour roles to each preset; keep primary / secondary / accent / background / foreground etc. as the canonical tokens.
plugin.mod Add required_icon_packs = ["lucide"]. Bump version via make bump-minor after implementation (this is a feature).
fonts.json Set to []. (Currently lists bundled woff2s; per new CLAUDE.md rule fonts should be admin-assigned.)
RECOMMENDED_FONTS.md NEW. Antonio for heading; system sans for body; system mono for mono.
RECOMMENDED_ICONS.md NEW. Lucide pack.

Validation

Before commit:

  • make — compiles the .so.
  • cd ~/src/blockninja/check-safety && go run . ~/src/blockninja/themes/lcars — passes the import-boundary and schema-vs-block-key checks.
  • make rebuild against a dev CMS instance — visually confirm an lcars_panel with frame="elbow", an lcars_rail with three lcars_readout children, and the existing page chrome all read as one consistent LCARS frame.

Decisions made during brainstorming (for reference)

  1. Inner L bend is sharp, outer page corners are rounded. Confirmed against the user's Lcars_wallpaper.svg.png reference and an explicit AskUserQuestion choice ("Curve at the page corner (outside the panel)").
  2. Top bar 4rem, bottom bar 3rem, asymmetric. Authentic LCARS chrome is top-heavy.
  3. Curve ratio matches v1 mockups, not v6. 1.75rem top / 1.25rem bottom on a 5.5rem column 1 — visible but not dominating the corner block.
  4. lcars_rail is a separate block, not a panel variant. It exists as its own block (not just an lcars_panel mode) because it has fundamentally different content shape: a list of paired (segment, body-cell) entries, vs. the panel's single body. One mode-switch would have over-loaded the schema with mutually-exclusive fields.
  5. lcars_rail is a compound schema-driven block, not a HasInternalSlot container. Investigated during spec self-review: pongo blocks have no per-child wrapper hook in RenderSlot, so children of a true container come back as one concatenated HTML blob — incompatible with the goal of pairing each child with its own rail segment in a single CSS grid. Inline cells[] in the schema sidesteps this. (themes/CLAUDE.md lists an AllowedChildren []string field on BlockMeta which does not exist in core/blocks/types.go — the actual field is HasInternalSlot bool; CLAUDE.md should be corrected separately.)
  6. lcars_readout is a separate block, not a lcars_panel mode. Readouts are meant to be tiled; panels are meant to stand alone. Conflating them would have forced one of those uses to dominate.
  7. Font tokens, not bundled font, going forward. Aligns with the project-wide rule introduced in themes/CLAUDE.md during this session.