From 5a7c1d1d5ec994d91be630ad24eb9ea82bc760f3 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 10:16:26 -0400 Subject: [PATCH 01/32] docs: add Concept 1 The Zen Scene Editor with floating Action Island - Add the initial Concept 1 mockup for the menu editor, exploring a scene-first approach. - Help the team visualize an editor that feels more like a composition tool than a property sheet. --- mockups/yuli-menu-concept-1.html | 308 +++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 mockups/yuli-menu-concept-1.html diff --git a/mockups/yuli-menu-concept-1.html b/mockups/yuli-menu-concept-1.html new file mode 100644 index 0000000..a19f398 --- /dev/null +++ b/mockups/yuli-menu-concept-1.html @@ -0,0 +1,308 @@ + + + + + + Spindle — Concept 1: The "Zen" Scene Editor + + + + + +
+ +
/ Wedding Highlights / Main Menu
+
+ +
+
+ +
+ +
+
+ Authored Scene + +
+
+ + Background Audio +
+
+ + Title Image +
+
+ + Main Title Text +
+
+ Interactive Nodes +
+
+ + Play Ceremony +
+
+ + Play Reception +
+
+ + Chapter Select +
+
+ + +
+
+
+
DVD (4:3 Safe)
+
Blu-ray (16:9)
+
+
+ +
+ +
+ + +
+
+ SELECTED: PLAY CEREMONY +
+
+
+ Action: + +
+
+
+ + +
+
+
+
+ + \ No newline at end of file From 718e783c48abce18a15ed2ae9e5d71fa077f7cef Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 10:27:37 -0400 Subject: [PATCH 02/32] docs: add Concept 2 The Component Boutique - Add the second menu editor mockup concept, focusing on component reuse. - Explore how a library-driven approach might speed up authoring for complex discs. --- mockups/yuli-menu-concept-2.html | 326 +++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 mockups/yuli-menu-concept-2.html diff --git a/mockups/yuli-menu-concept-2.html b/mockups/yuli-menu-concept-2.html new file mode 100644 index 0000000..e11d949 --- /dev/null +++ b/mockups/yuli-menu-concept-2.html @@ -0,0 +1,326 @@ + + + + + + Spindle — Concept 2: The Component "Boutique" + + + + + +
+ +
/ Wedding Highlights / Chapter Select
+
+ + +
+
+ +
+ + + + +
+ +
+
+ + \ No newline at end of file From e8b19ae23a5e056510fcd82a3eb339fe1e7b658c Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 10:28:17 -0400 Subject: [PATCH 03/32] docs: add Concept 3 The Transit Map for Navigation - Add a new concept mockup focusing on the multi-menu navigation map. - Provide a bird's-eye view of how menus and titles connect to help users understand disc flow. --- mockups/yuli-menu-concept-3.html | 337 +++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 mockups/yuli-menu-concept-3.html diff --git a/mockups/yuli-menu-concept-3.html b/mockups/yuli-menu-concept-3.html new file mode 100644 index 0000000..e432d59 --- /dev/null +++ b/mockups/yuli-menu-concept-3.html @@ -0,0 +1,337 @@ + + + + + + Spindle — Concept 3: The "Transit" Map + + + + + +
+ +
/ Wedding Highlights / Main Menu
+
+ +
+
+ +
+ +
+
+
+
+ + Design +
+
+ + Routing +
+
+
+ +
+ +
+
+ + +
+
Routing Diagnostics
+ +
+ +
+
One-Way Route Detected
+
"Special Features" has no 'Up' route. A user navigating here may get stuck if they press Up on their remote.
+ +
+
+ +
+
Legend
+
+
+ Active Routes +
+
+
+ Inactive Routes +
+
+
+ Drag to connect +
+
+
+
+ + \ No newline at end of file From 7eef195c2948ad210e71d98e550b64d5bea5f1e9 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 10:28:57 -0400 Subject: [PATCH 04/32] docs: add Concept 4 The Timeline for Motion-Aware Architecture - Add a mockup exploring a timeline-focused interface for motion menus. - Ensure the architecture can handle time-based events and transitions gracefully. --- mockups/yuli-menu-concept-4.html | 358 +++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 mockups/yuli-menu-concept-4.html diff --git a/mockups/yuli-menu-concept-4.html b/mockups/yuli-menu-concept-4.html new file mode 100644 index 0000000..69b275f --- /dev/null +++ b/mockups/yuli-menu-concept-4.html @@ -0,0 +1,358 @@ + + + + + + Spindle — Concept 4: The "Timeline" + + + + + +
+ +
/ Wedding Highlights / Main Menu (Motion)
+
+ +
+
+ +
+ +
+ +
+ + +
+
+
+ + + +
+
00:00:08;14
+
+
Total Duration: 30s | Loop Count: ∞
+
+ +
+
+
+
+ + Background Video +
+
+ + Audio Stream +
+
+ + Button Animations +
+
+ +
+ +
+
0s
+
5s
+
10s
+
15s
+
20s
+
25s
+
30s
+
+ + +
+
Intro
+
+
+
Loop Segment
+
+ + +
+ + +
+
hero_bg_motion.m2v
+
+
+
ambient_swell.ac3
+
+
+
Buttons Fade In
+
Interactive Area (Buttons Active)
+
+
+
+
+
+ + \ No newline at end of file From 95170bb9ff0be6bbf325aefe3f37da3f78d1a109 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 10:29:35 -0400 Subject: [PATCH 05/32] docs: add Concept 5 The Honest Compile Preview for Diagnostics - Add a mockup for the compile preview and diagnostics overlay. - Help users visualize how their Blu-ray designs will degrade on DVD hardware before compiling. --- mockups/yuli-menu-concept-5.html | 345 +++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 mockups/yuli-menu-concept-5.html diff --git a/mockups/yuli-menu-concept-5.html b/mockups/yuli-menu-concept-5.html new file mode 100644 index 0000000..87f6726 --- /dev/null +++ b/mockups/yuli-menu-concept-5.html @@ -0,0 +1,345 @@ + + + + + + Spindle — Concept 5: The "Honest" Compile Preview + + + + + +
+ +
/ Wedding Highlights / Main Menu
+
+ +
+
+ +
+ +
+
+
+
+ + Design +
+
+ + Compile Preview +
+
+
+ +
+ + +
+
Authored Scene Intent
+ +
+ + + + +
+
DVD Compiled Output
+ +
+ +
+
+ + +
+
Compile Downgrade Report
+ +
+ +
+
Highlight Gradient Lost
+
The authored scene uses a complex gradient for the button highlight. DVD overlays only support a strict 4-color palette. The highlight has been flattened to a solid color block.
+
DVD Subpicture Limit
+
+
+ +
+ +
+
Geometry Simplification
+
The rounded corners and drop shadows of the highlight state cannot be represented in a DVD subpicture overlay. They have been converted to hard rectangular bounds.
+
DVD Geometry Constraint
+
+
+ +
+
+ Why does this happen?
+ Spindle lets you author scenes with rich, modern graphics. However, when compiling for a target like DVD, the format's legacy hardware constraints apply. This honest preview ensures you know exactly how the final disc will look before you build. +
+
+
+
+ + \ No newline at end of file From e37c2d50a694a900d3341ea3add0cb58f32bf763 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 10:33:29 -0400 Subject: [PATCH 06/32] style: remove all caps and uppercase styling from headings and titles - Update styling across multiple mockups to remove all-caps text treatments. - Improve readability and adhere to the project's calm computing aesthetic. --- mockups/yuli-menu-concept-1.html | 3 +-- mockups/yuli-menu-concept-2.html | 6 ++---- mockups/yuli-menu-concept-3.html | 1 - mockups/yuli-menu-concept-4.html | 1 - mockups/yuli-menu-concept-5.html | 2 -- 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/mockups/yuli-menu-concept-1.html b/mockups/yuli-menu-concept-1.html index a19f398..ef047dc 100644 --- a/mockups/yuli-menu-concept-1.html +++ b/mockups/yuli-menu-concept-1.html @@ -37,7 +37,6 @@ .hierarchy-header { font-size: var(--text-xs); font-weight: 600; - text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: var(--space-4); @@ -279,7 +278,7 @@
- SELECTED: PLAY CEREMONY + Selected: Play Ceremony
diff --git a/mockups/yuli-menu-concept-2.html b/mockups/yuli-menu-concept-2.html index e11d949..3ab8f69 100644 --- a/mockups/yuli-menu-concept-2.html +++ b/mockups/yuli-menu-concept-2.html @@ -38,7 +38,6 @@ .gallery-header { font-size: var(--text-xs); font-weight: 600; - text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: var(--space-4); @@ -58,7 +57,6 @@ .theme-selector label { font-size: var(--text-xs); color: var(--text-muted); - text-transform: uppercase; letter-spacing: 0.06em; display: block; margin-bottom: var(--space-2); @@ -256,7 +254,7 @@
-
BACK
+
Back
Text Pill Button
Simple text-only navigation
@@ -323,4 +321,4 @@
- \ No newline at end of file +tml> \ No newline at end of file diff --git a/mockups/yuli-menu-concept-3.html b/mockups/yuli-menu-concept-3.html index e432d59..e46ffab 100644 --- a/mockups/yuli-menu-concept-3.html +++ b/mockups/yuli-menu-concept-3.html @@ -183,7 +183,6 @@ .panel-header { font-size: var(--text-xs); font-weight: 600; - text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: var(--space-4); diff --git a/mockups/yuli-menu-concept-4.html b/mockups/yuli-menu-concept-4.html index 69b275f..70cb500 100644 --- a/mockups/yuli-menu-concept-4.html +++ b/mockups/yuli-menu-concept-4.html @@ -249,7 +249,6 @@ font-size: 10px; font-weight: 600; color: var(--text-muted); - text-transform: uppercase; letter-spacing: 0.05em; } diff --git a/mockups/yuli-menu-concept-5.html b/mockups/yuli-menu-concept-5.html index 87f6726..0151ab8 100644 --- a/mockups/yuli-menu-concept-5.html +++ b/mockups/yuli-menu-concept-5.html @@ -101,7 +101,6 @@ font-size: 11px; color: var(--text-muted); letter-spacing: 0.1em; - text-transform: uppercase; } .menu-canvas { @@ -189,7 +188,6 @@ .panel-header { font-size: var(--text-xs); font-weight: 600; - text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: var(--space-4); From fb34dc5b65b3004ae5eb3e9029af7bac5b0366b8 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 11:13:31 -0400 Subject: [PATCH 07/32] chore: move Yuli mockups into their own folder - Relocate Yuli's initial mockups into a dedicated `mockups/yuli` directory. - Update all CSS references and create an index page to keep the workspace organized. --- mockups/yuli/design-system.css | 833 ++++++++++++++++++++ mockups/yuli/index.html | 131 +++ mockups/{ => yuli}/yuli-menu-concept-1.html | 2 +- mockups/{ => yuli}/yuli-menu-concept-2.html | 2 +- mockups/{ => yuli}/yuli-menu-concept-3.html | 2 +- mockups/{ => yuli}/yuli-menu-concept-4.html | 2 +- mockups/{ => yuli}/yuli-menu-concept-5.html | 2 +- 7 files changed, 969 insertions(+), 5 deletions(-) create mode 100644 mockups/yuli/design-system.css create mode 100644 mockups/yuli/index.html rename mockups/{ => yuli}/yuli-menu-concept-1.html (99%) rename mockups/{ => yuli}/yuli-menu-concept-2.html (99%) rename mockups/{ => yuli}/yuli-menu-concept-3.html (99%) rename mockups/{ => yuli}/yuli-menu-concept-4.html (99%) rename mockups/{ => yuli}/yuli-menu-concept-5.html (99%) diff --git a/mockups/yuli/design-system.css b/mockups/yuli/design-system.css new file mode 100644 index 0000000..c3bcc9e --- /dev/null +++ b/mockups/yuli/design-system.css @@ -0,0 +1,833 @@ +/* ============================================================ + Liminal Spindle — Design System + Desktop Authoring Workstation · Dark Mode + + Colour palette drawn from Liminal HQ brand identity: + liminalhq.ca, smdu, Flow, Threshold, Emoji Nook + ============================================================ */ + +/* --- Reset & Base ------------------------------------------ */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + /* ── Brand Colours ─────────────────────────────────────── */ + --brand-orange: #ffaa40; + --brand-pink: #f43f5e; + --brand-purple: #a78bfa; + --brand-cyan: #22d3ee; + --brand-blue: #60a5fa; + --brand-green: #2ec66a; + --brand-amber: #fbbf24; + + /* ── Brand Gradient (hero / accent) ────────────────────── */ + --brand-gradient: linear-gradient(135deg, #ffaa40, #f43f5e, #a78bfa); + + /* ── Surfaces ──────────────────────────────────────────── */ + --bg-app: #050507; + --bg-sidebar: #0c0c10; + --bg-panel: #111116; + --bg-card: rgba(20, 20, 25, 0.55); + --bg-card-hover: rgba(30, 30, 38, 0.7); + --bg-input: rgba(20, 20, 28, 0.6); + --bg-modal: #16161c; + --bg-tooltip: #1e1e26; + + /* ── Borders ───────────────────────────────────────────── */ + --border-subtle: rgba(255, 255, 255, 0.08); + --border-default: rgba(255, 255, 255, 0.14); + --border-strong: rgba(255, 255, 255, 0.22); + --border-focus: #ffaa40; + + /* ── Text ──────────────────────────────────────────────── */ + --text-primary: #e0e0e0; + --text-secondary: #b4bfce; + --text-muted: #7c8796; + --text-inverse: #0a0a0c; + + /* ── Semantic Colours ──────────────────────────────────── */ + --colour-success: #2ec66a; + --colour-warning: #fbbf24; + --colour-error: #f43f5e; + --colour-info: #60a5fa; + + /* ── Compatibility Badges (from SPEC) ──────────────────── */ + --badge-remux: #2ec66a; + --badge-light: #60a5fa; + --badge-reencode: #fbbf24; + --badge-unsupported: #f43f5e; + + /* ── File-Type Colours (from smdu) ─────────────────────── */ + --file-media: #ffb454; + --file-text: #b0f2c2; + --file-docs: #ffd166; + --file-code: #7dd3fc; + --file-archive: #c4a7e7; + + /* ── Typography ────────────────────────────────────────── */ + --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-heading: 'Space Grotesk', var(--font-body); + --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; + + --text-xs: 0.6875rem; /* 11px */ + --text-sm: 0.75rem; /* 12px */ + --text-base: 0.8125rem; /* 13px */ + --text-md: 0.875rem; /* 14px */ + --text-lg: 1rem; /* 16px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + + /* ── Spacing ───────────────────────────────────────────── */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* ── Radii ─────────────────────────────────────────────── */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 20px; + + /* ── Shadows ───────────────────────────────────────────── */ + --shadow-card: 0 2px 12px rgba(0, 0, 0, 0.35); + --shadow-panel: 0 4px 24px rgba(0, 0, 0, 0.45); + --shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.6); + --shadow-glow: 0 0 20px rgba(255, 170, 64, 0.15); + + /* ── Layout ────────────────────────────────────────────── */ + --sidebar-width: 220px; + --topbar-height: 40px; + --statusbar-height: 28px; + --panel-gap: 1px; + + /* ── Transitions ───────────────────────────────────────── */ + --transition-fast: 0.15s ease; + --transition-base: 0.25s ease; + --transition-slow: 0.4s ease; +} + +/* --- Animations -------------------------------------------- */ +@keyframes portal-pulse { + 0%, + 100% { + opacity: 0.4; + } + 50% { + opacity: 1; + } +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes progress-stripe { + 0% { + background-position: 0 0; + } + 100% { + background-position: 40px 0; + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* --- Base Styles ------------------------------------------- */ +html, +body { + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.5; + color: var(--text-primary); + background: var(--bg-app); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, +h2, +h3, +h4 { + font-family: var(--font-heading); + font-weight: 600; + letter-spacing: -0.01em; +} + +h1 { + font-size: var(--text-2xl); +} +h2 { + font-size: var(--text-xl); +} +h3 { + font-size: var(--text-lg); +} +h4 { + font-size: var(--text-md); +} + +a { + color: var(--brand-blue); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +code, +pre { + font-family: var(--font-mono); + font-size: var(--text-sm); +} + +/* --- Utility Classes --------------------------------------- */ +.text-muted { + color: var(--text-muted); +} +.text-secondary { + color: var(--text-secondary); +} +.text-gradient { + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.mono { + font-family: var(--font-mono); +} +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* --- Component: App Shell ---------------------------------- */ +.app-shell { + display: grid; + grid-template-rows: var(--topbar-height) 1fr var(--statusbar-height); + grid-template-columns: var(--sidebar-width) 1fr; + grid-template-areas: + 'topbar topbar' + 'sidebar main' + 'status status'; + height: 100vh; + overflow: hidden; +} + +.topbar { + grid-area: topbar; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-bottom: 1px solid var(--border-subtle); + gap: var(--space-3); + -webkit-app-region: drag; +} + +.topbar__logo { + display: flex; + align-items: center; + gap: var(--space-2); + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 700; + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.topbar__actions { + margin-left: auto; + display: flex; + align-items: center; + gap: var(--space-2); + -webkit-app-region: no-drag; +} + +.sidebar { + grid-area: sidebar; + background: var(--bg-sidebar); + border-right: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + overflow-y: auto; + padding: var(--space-3) 0; +} + +.sidebar__section { + padding: var(--space-2) var(--space-4); +} + +.sidebar__label { + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); + margin-bottom: var(--space-2); +} + +.sidebar__item { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-1) var(--space-4); + font-size: var(--text-sm); + color: var(--text-secondary); + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-fast); + margin-bottom: 1px; +} + +.sidebar__item:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.sidebar__item--active { + background: rgba(255, 170, 64, 0.1); + color: var(--brand-orange); + font-weight: 500; +} + +.sidebar__item__icon { + width: 16px; + height: 16px; + opacity: 0.7; + flex-shrink: 0; +} + +.sidebar__item--active .sidebar__item__icon { + opacity: 1; +} + +.sidebar__item__badge { + margin-left: auto; + font-size: var(--text-xs); + padding: 1px 6px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.08); + color: var(--text-muted); +} + +.main-content { + grid-area: main; + overflow-y: auto; + padding: var(--space-6); + animation: fade-in 0.2s ease; +} + +.statusbar { + grid-area: status; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-top: 1px solid var(--border-subtle); + font-size: var(--text-xs); + color: var(--text-muted); + gap: var(--space-4); +} + +.statusbar__segment { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.statusbar__dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--colour-success); +} + +.statusbar__dot--warning { + background: var(--colour-warning); +} +.statusbar__dot--error { + background: var(--colour-error); +} + +/* --- Component: Cards -------------------------------------- */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-5); + transition: all var(--transition-base); +} + +.card:hover { + background: var(--bg-card-hover); + border-color: var(--border-default); + box-shadow: var(--shadow-card); +} + +.card--glow:hover { + box-shadow: var(--shadow-glow); + border-color: rgba(255, 170, 64, 0.2); +} + +.card__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-3); +} + +.card__title { + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 600; +} + +/* --- Component: Buttons ------------------------------------ */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: 500; + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + background: var(--bg-card); + color: var(--text-primary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn:hover { + background: var(--bg-card-hover); + border-color: var(--border-strong); +} + +.btn--primary { + background: var(--brand-orange); + border-color: var(--brand-orange); + color: var(--text-inverse); +} + +.btn--primary:hover { + background: #e89930; + box-shadow: 0 0 16px rgba(255, 170, 64, 0.3); +} + +.btn--danger { + border-color: var(--colour-error); + color: var(--colour-error); +} + +.btn--ghost { + background: transparent; + border-color: transparent; + color: var(--text-secondary); +} + +.btn--ghost:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.btn--sm { + padding: 2px var(--space-2); + font-size: var(--text-xs); +} + +/* --- Component: Badges ------------------------------------- */ +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + font-weight: 600; + border-radius: 10px; + letter-spacing: 0.02em; +} + +.badge--remux { + background: rgba(46, 198, 106, 0.15); + color: var(--badge-remux); +} +.badge--light { + background: rgba(96, 165, 250, 0.15); + color: var(--badge-light); +} +.badge--reencode { + background: rgba(251, 191, 36, 0.15); + color: var(--badge-reencode); +} +.badge--unsupported { + background: rgba(244, 63, 94, 0.15); + color: var(--badge-unsupported); +} +.badge--neutral { + background: rgba(255, 255, 255, 0.06); + color: var(--text-muted); +} + +/* --- Component: Progress Bar ------------------------------- */ +.progress { + height: 6px; + background: rgba(255, 255, 255, 0.06); + border-radius: 3px; + overflow: hidden; +} + +.progress__fill { + height: 100%; + border-radius: 3px; + transition: width var(--transition-base); +} + +.progress__fill--ok { + background: var(--brand-gradient); +} +.progress__fill--warning { + background: var(--colour-warning); +} +.progress__fill--error { + background: var(--colour-error); +} + +.progress--striped .progress__fill { + background-image: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + rgba(255, 255, 255, 0.08) 10px, + rgba(255, 255, 255, 0.08) 20px + ); + background-size: 40px 40px; + animation: progress-stripe 1s linear infinite; +} + +/* --- Component: Table -------------------------------------- */ +.table-wrap { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); +} + +th { + text-align: left; + padding: var(--space-2) var(--space-3); + font-weight: 600; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + border-bottom: 1px solid var(--border-default); +} + +td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--border-subtle); + color: var(--text-secondary); +} + +tr:hover td { + background: rgba(255, 255, 255, 0.02); +} + +/* --- Component: Input / Form ------------------------------- */ +.input { + padding: var(--space-2) var(--space-3); + font-family: var(--font-body); + font-size: var(--text-sm); + background: var(--bg-input); + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + color: var(--text-primary); + transition: border-color var(--transition-fast); + width: 100%; +} + +.input:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 2px rgba(255, 170, 64, 0.15); +} + +.input--select { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%237c8796' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 28px; +} + +.form-group { + margin-bottom: var(--space-4); +} + +.form-label { + display: block; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: var(--space-1); +} + +/* --- Component: Tabs --------------------------------------- */ +.tabs { + display: flex; + gap: var(--space-1); + border-bottom: 1px solid var(--border-subtle); + margin-bottom: var(--space-5); +} + +.tab { + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + font-weight: 500; + color: var(--text-muted); + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all var(--transition-fast); +} + +.tab:hover { + color: var(--text-primary); +} + +.tab--active { + color: var(--brand-orange); + border-bottom-color: var(--brand-orange); +} + +/* --- Component: Tag / Chip --------------------------------- */ +.tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + border-radius: var(--radius-sm); + background: rgba(255, 255, 255, 0.06); + color: var(--text-secondary); + border: 1px solid var(--border-subtle); +} + +/* --- Component: Timeline ----------------------------------- */ +.timeline { + position: relative; + height: 48px; + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + overflow: hidden; +} + +.timeline__track { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 4px; + background: rgba(255, 255, 255, 0.08); + transform: translateY(-50%); +} + +.timeline__marker { + position: absolute; + top: 4px; + bottom: 4px; + width: 2px; + background: var(--brand-orange); + border-radius: 1px; +} + +.timeline__marker::after { + content: attr(data-label); + position: absolute; + top: -2px; + left: 6px; + font-size: var(--text-xs); + color: var(--text-muted); + white-space: nowrap; +} + +/* --- Component: Modal / Dialog ----------------------------- */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal { + background: var(--bg-modal); + border: 1px solid var(--border-default); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-modal); + padding: var(--space-8); + max-width: 560px; + width: 90%; + animation: fade-in 0.2s ease; +} + +/* --- Component: Capacity Bar ------------------------------- */ +.capacity-bar { + display: flex; + height: 28px; + border-radius: var(--radius-md); + overflow: hidden; + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.03); +} + +.capacity-bar__segment { + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-inverse); + transition: width var(--transition-base); + min-width: 2px; +} + +/* --- Layout Helpers ---------------------------------------- */ +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-6); +} + +.page-title { + font-family: var(--font-heading); + font-size: var(--text-xl); + font-weight: 700; +} + +.grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); +} +.grid-3 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--space-4); +} +.grid-4 { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: var(--space-4); +} + +.flex { + display: flex; +} +.flex-col { + flex-direction: column; +} +.items-center { + align-items: center; +} +.justify-between { + justify-content: space-between; +} +.gap-1 { + gap: var(--space-1); +} +.gap-2 { + gap: var(--space-2); +} +.gap-3 { + gap: var(--space-3); +} +.gap-4 { + gap: var(--space-4); +} + +.mt-2 { + margin-top: var(--space-2); +} +.mt-4 { + margin-top: var(--space-4); +} +.mt-6 { + margin-top: var(--space-6); +} +.mb-2 { + margin-bottom: var(--space-2); +} +.mb-4 { + margin-bottom: var(--space-4); +} + +.w-full { + width: 100%; +} + +/* --- Scrollbar Styling ------------------------------------- */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.18); +} diff --git a/mockups/yuli/index.html b/mockups/yuli/index.html new file mode 100644 index 0000000..31a62a6 --- /dev/null +++ b/mockups/yuli/index.html @@ -0,0 +1,131 @@ + + + + + + Spindle — Yuli's Menu Editor Concepts + + + + + +
+
+

Spindle Menu Editor Concepts

+

A UX exploration by Yuli focusing on calm design and progressive disclosure.

+
+ +
+
+ Concept 1: The "Zen" Scene Editor +
Calm Defaults
+
+
+ Instead of overwhelming the user with a massive right-hand inspector panel, this concept uses a floating, contextual Action Island at the bottom of the screen. When you select a button, the island surfaces only what you need (Action routing, Style edit). +
+ Open Concept 1 +
+ +
+
+ Concept 2: The Component "Boutique" +
Themes & Generation
+
+
+ Professional tools don't have to look like spreadsheets. This concept reimagines the left sidebar as a curated "Component Boutique". Users drag-and-drop pre-defined components like Hero Title Buttons or Chapter Tiles. +
+ Open Concept 2 +
+ +
+
+ Concept 3: The "Transit" Map +
Navigation Routing
+
+
+ To make the DVD remote-control routing explicit and understandable, this concept introduces a "Routing Mode". It overlays a transit-style map of SVG arrows on the canvas, showing exactly how focus moves between buttons. +
+ Open Concept 3 +
+ +
+
+ Concept 4: The "Timeline" +
Motion Architecture
+
+
+ Bringing motion into the still-menu editor without causing clutter. This mockup introduces a clean, collapsible timeline panel at the bottom. It visually separates the "Intro" segment from the "Loop Segment". +
+ Open Concept 4 +
+ +
+
+ Concept 5: The "Honest" Compile Preview +
Diagnostics & Trust
+
+
+ Spindle shouldn't pretend DVD is more capable than it is. This concept provides a split-screen "Compile Preview" comparing the rich "Authored Scene Intent" with the "DVD Compiled Output". +
+ Open Concept 5 +
+
+ + \ No newline at end of file diff --git a/mockups/yuli-menu-concept-1.html b/mockups/yuli/yuli-menu-concept-1.html similarity index 99% rename from mockups/yuli-menu-concept-1.html rename to mockups/yuli/yuli-menu-concept-1.html index ef047dc..8a064a5 100644 --- a/mockups/yuli-menu-concept-1.html +++ b/mockups/yuli/yuli-menu-concept-1.html @@ -4,7 +4,7 @@ Spindle — Concept 1: The "Zen" Scene Editor - + Spindle — Concept 2: The Component "Boutique" - + Spindle — Concept 3: The "Transit" Map - + Spindle — Concept 4: The "Timeline" - + Spindle — Concept 5: The "Honest" Compile Preview - + Date: Mon, 6 Apr 2026 11:15:56 -0400 Subject: [PATCH 08/32] chore: move Yuli mockups to set-1 subdirectory - Move the first set of mockups into a `set-1` subfolder. - Prepare the directory structure for iterative design sets. --- mockups/yuli/{ => set-1}/design-system.css | 0 mockups/yuli/{ => set-1}/index.html | 0 mockups/yuli/{ => set-1}/yuli-menu-concept-1.html | 0 mockups/yuli/{ => set-1}/yuli-menu-concept-2.html | 0 mockups/yuli/{ => set-1}/yuli-menu-concept-3.html | 0 mockups/yuli/{ => set-1}/yuli-menu-concept-4.html | 0 mockups/yuli/{ => set-1}/yuli-menu-concept-5.html | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename mockups/yuli/{ => set-1}/design-system.css (100%) rename mockups/yuli/{ => set-1}/index.html (100%) rename mockups/yuli/{ => set-1}/yuli-menu-concept-1.html (100%) rename mockups/yuli/{ => set-1}/yuli-menu-concept-2.html (100%) rename mockups/yuli/{ => set-1}/yuli-menu-concept-3.html (100%) rename mockups/yuli/{ => set-1}/yuli-menu-concept-4.html (100%) rename mockups/yuli/{ => set-1}/yuli-menu-concept-5.html (100%) diff --git a/mockups/yuli/design-system.css b/mockups/yuli/set-1/design-system.css similarity index 100% rename from mockups/yuli/design-system.css rename to mockups/yuli/set-1/design-system.css diff --git a/mockups/yuli/index.html b/mockups/yuli/set-1/index.html similarity index 100% rename from mockups/yuli/index.html rename to mockups/yuli/set-1/index.html diff --git a/mockups/yuli/yuli-menu-concept-1.html b/mockups/yuli/set-1/yuli-menu-concept-1.html similarity index 100% rename from mockups/yuli/yuli-menu-concept-1.html rename to mockups/yuli/set-1/yuli-menu-concept-1.html diff --git a/mockups/yuli/yuli-menu-concept-2.html b/mockups/yuli/set-1/yuli-menu-concept-2.html similarity index 100% rename from mockups/yuli/yuli-menu-concept-2.html rename to mockups/yuli/set-1/yuli-menu-concept-2.html diff --git a/mockups/yuli/yuli-menu-concept-3.html b/mockups/yuli/set-1/yuli-menu-concept-3.html similarity index 100% rename from mockups/yuli/yuli-menu-concept-3.html rename to mockups/yuli/set-1/yuli-menu-concept-3.html diff --git a/mockups/yuli/yuli-menu-concept-4.html b/mockups/yuli/set-1/yuli-menu-concept-4.html similarity index 100% rename from mockups/yuli/yuli-menu-concept-4.html rename to mockups/yuli/set-1/yuli-menu-concept-4.html diff --git a/mockups/yuli/yuli-menu-concept-5.html b/mockups/yuli/set-1/yuli-menu-concept-5.html similarity index 100% rename from mockups/yuli/yuli-menu-concept-5.html rename to mockups/yuli/set-1/yuli-menu-concept-5.html From fb3ff732a4de994ab1f88f28dd09da672486b2b1 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 19:41:05 -0400 Subject: [PATCH 09/32] docs: add Concept 1 The Waystation hub-and-spoke unified editor A complete four-pane workspace inspired by airport terminals: layers/components on the left, adaptive inspector on the right, central canvas with mode switcher (Design/Bind/Route/Compile), and a collapsible motion timeline at the bottom. Includes the shared `design-system.css` for set-2 mockups. Co-Authored-By: Claude Opus 4.6 --- .../yuli/set-2/concept-1-the-waystation.html | 1163 +++++++++++++++++ mockups/yuli/set-2/design-system.css | 833 ++++++++++++ 2 files changed, 1996 insertions(+) create mode 100644 mockups/yuli/set-2/concept-1-the-waystation.html create mode 100644 mockups/yuli/set-2/design-system.css diff --git a/mockups/yuli/set-2/concept-1-the-waystation.html b/mockups/yuli/set-2/concept-1-the-waystation.html new file mode 100644 index 0000000..3bdb1a2 --- /dev/null +++ b/mockups/yuli/set-2/concept-1-the-waystation.html @@ -0,0 +1,1163 @@ + + + + + + Spindle — Concept 1: The Waystation + + + + + + +
+ +
/ Wedding Highlights / Main Menu
+
+
+ DVD 4:3 NTSC + Valid +
+ + +
+
+ +
+ +
+
+
Layers
+
Components
+
Themes
+
+ +
+ +
+ Scene Graph + +
+ +
+ + Background + +
+ +
+ + poster_bg.jpg + +
+ +
+ + Title Text + +
+ +
+ + Subtitle Text + +
+ +
+ Interactive Nodes + +
+ +
+ + Play Ceremony + +
+ +
+ + Play Reception +
+ +
+ + Chapter Select +
+ +
+ + Audio Setup +
+ +
+ Guides +
+ +
+ + Title Safe Area +
+
+ + Action Safe Area +
+
+
+ + +
+
+ +
+ + + + +
+ + +
+ + + + +
+ + +
+
+ +
+ + +
100%
+
+
+ + +
+
+ +
+
Play Ceremony
+
Button Node
+
+
+ +
+ +
+
+ Transform + +
+
+ Position +
+
+ X + + Y + +
+
+
+
+ Size +
+
+ W + + H + +
+
+
+
+ + +
+
+ Visual States + +
+
+
+
+
Normal
+
Focus
+
Activate
+
+
+
+
+ + +
+
+ Action + +
+
+ On Press +
+
+ + playTitle: "Ceremony" +
+
+
+
+ + +
+
+ Navigation + Auto +
+
+ Up +
+ +
+
+
+ Down +
+ +
+
+
+ Left +
+ +
+
+
+ Right +
+ +
+
+
+ + +
+ + All navigation routes valid. No unreachable nodes detected. +
+ +
+ + Button is near the edge of the action-safe area. Text may be cropped on some displays. +
+
+
+ + +
+
+
Motion Timeline
+
Motion Menu
+
+ + + +
+
00:06;12
+ 30s loop | Count: 3 | Timeout: Play Title 1 +
+ +
+
+
+
+ + Background Video +
+
+ + Audio +
+
+ + Highlight Anim +
+
+ +
+
+ 0s + 5s + 10s + 15s + 20s + 25s + 30s +
+ +
+
Loop Start
+
+ +
+
hero_bg_motion.m2v
+
+
+
ceremony_ambient.ac3
+
+
+
Fade In
+
Buttons Active
+
+ +
+
+
+
+
+ + +
+
+ Ready + | + 4 buttons + | + Menu 1 of 3 + | + VMGM Domain +
+ 720 x 480 NTSC +
+ + diff --git a/mockups/yuli/set-2/design-system.css b/mockups/yuli/set-2/design-system.css new file mode 100644 index 0000000..c3bcc9e --- /dev/null +++ b/mockups/yuli/set-2/design-system.css @@ -0,0 +1,833 @@ +/* ============================================================ + Liminal Spindle — Design System + Desktop Authoring Workstation · Dark Mode + + Colour palette drawn from Liminal HQ brand identity: + liminalhq.ca, smdu, Flow, Threshold, Emoji Nook + ============================================================ */ + +/* --- Reset & Base ------------------------------------------ */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + /* ── Brand Colours ─────────────────────────────────────── */ + --brand-orange: #ffaa40; + --brand-pink: #f43f5e; + --brand-purple: #a78bfa; + --brand-cyan: #22d3ee; + --brand-blue: #60a5fa; + --brand-green: #2ec66a; + --brand-amber: #fbbf24; + + /* ── Brand Gradient (hero / accent) ────────────────────── */ + --brand-gradient: linear-gradient(135deg, #ffaa40, #f43f5e, #a78bfa); + + /* ── Surfaces ──────────────────────────────────────────── */ + --bg-app: #050507; + --bg-sidebar: #0c0c10; + --bg-panel: #111116; + --bg-card: rgba(20, 20, 25, 0.55); + --bg-card-hover: rgba(30, 30, 38, 0.7); + --bg-input: rgba(20, 20, 28, 0.6); + --bg-modal: #16161c; + --bg-tooltip: #1e1e26; + + /* ── Borders ───────────────────────────────────────────── */ + --border-subtle: rgba(255, 255, 255, 0.08); + --border-default: rgba(255, 255, 255, 0.14); + --border-strong: rgba(255, 255, 255, 0.22); + --border-focus: #ffaa40; + + /* ── Text ──────────────────────────────────────────────── */ + --text-primary: #e0e0e0; + --text-secondary: #b4bfce; + --text-muted: #7c8796; + --text-inverse: #0a0a0c; + + /* ── Semantic Colours ──────────────────────────────────── */ + --colour-success: #2ec66a; + --colour-warning: #fbbf24; + --colour-error: #f43f5e; + --colour-info: #60a5fa; + + /* ── Compatibility Badges (from SPEC) ──────────────────── */ + --badge-remux: #2ec66a; + --badge-light: #60a5fa; + --badge-reencode: #fbbf24; + --badge-unsupported: #f43f5e; + + /* ── File-Type Colours (from smdu) ─────────────────────── */ + --file-media: #ffb454; + --file-text: #b0f2c2; + --file-docs: #ffd166; + --file-code: #7dd3fc; + --file-archive: #c4a7e7; + + /* ── Typography ────────────────────────────────────────── */ + --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-heading: 'Space Grotesk', var(--font-body); + --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; + + --text-xs: 0.6875rem; /* 11px */ + --text-sm: 0.75rem; /* 12px */ + --text-base: 0.8125rem; /* 13px */ + --text-md: 0.875rem; /* 14px */ + --text-lg: 1rem; /* 16px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + + /* ── Spacing ───────────────────────────────────────────── */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* ── Radii ─────────────────────────────────────────────── */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 20px; + + /* ── Shadows ───────────────────────────────────────────── */ + --shadow-card: 0 2px 12px rgba(0, 0, 0, 0.35); + --shadow-panel: 0 4px 24px rgba(0, 0, 0, 0.45); + --shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.6); + --shadow-glow: 0 0 20px rgba(255, 170, 64, 0.15); + + /* ── Layout ────────────────────────────────────────────── */ + --sidebar-width: 220px; + --topbar-height: 40px; + --statusbar-height: 28px; + --panel-gap: 1px; + + /* ── Transitions ───────────────────────────────────────── */ + --transition-fast: 0.15s ease; + --transition-base: 0.25s ease; + --transition-slow: 0.4s ease; +} + +/* --- Animations -------------------------------------------- */ +@keyframes portal-pulse { + 0%, + 100% { + opacity: 0.4; + } + 50% { + opacity: 1; + } +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes progress-stripe { + 0% { + background-position: 0 0; + } + 100% { + background-position: 40px 0; + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* --- Base Styles ------------------------------------------- */ +html, +body { + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.5; + color: var(--text-primary); + background: var(--bg-app); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, +h2, +h3, +h4 { + font-family: var(--font-heading); + font-weight: 600; + letter-spacing: -0.01em; +} + +h1 { + font-size: var(--text-2xl); +} +h2 { + font-size: var(--text-xl); +} +h3 { + font-size: var(--text-lg); +} +h4 { + font-size: var(--text-md); +} + +a { + color: var(--brand-blue); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +code, +pre { + font-family: var(--font-mono); + font-size: var(--text-sm); +} + +/* --- Utility Classes --------------------------------------- */ +.text-muted { + color: var(--text-muted); +} +.text-secondary { + color: var(--text-secondary); +} +.text-gradient { + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.mono { + font-family: var(--font-mono); +} +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* --- Component: App Shell ---------------------------------- */ +.app-shell { + display: grid; + grid-template-rows: var(--topbar-height) 1fr var(--statusbar-height); + grid-template-columns: var(--sidebar-width) 1fr; + grid-template-areas: + 'topbar topbar' + 'sidebar main' + 'status status'; + height: 100vh; + overflow: hidden; +} + +.topbar { + grid-area: topbar; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-bottom: 1px solid var(--border-subtle); + gap: var(--space-3); + -webkit-app-region: drag; +} + +.topbar__logo { + display: flex; + align-items: center; + gap: var(--space-2); + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 700; + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.topbar__actions { + margin-left: auto; + display: flex; + align-items: center; + gap: var(--space-2); + -webkit-app-region: no-drag; +} + +.sidebar { + grid-area: sidebar; + background: var(--bg-sidebar); + border-right: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + overflow-y: auto; + padding: var(--space-3) 0; +} + +.sidebar__section { + padding: var(--space-2) var(--space-4); +} + +.sidebar__label { + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); + margin-bottom: var(--space-2); +} + +.sidebar__item { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-1) var(--space-4); + font-size: var(--text-sm); + color: var(--text-secondary); + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-fast); + margin-bottom: 1px; +} + +.sidebar__item:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.sidebar__item--active { + background: rgba(255, 170, 64, 0.1); + color: var(--brand-orange); + font-weight: 500; +} + +.sidebar__item__icon { + width: 16px; + height: 16px; + opacity: 0.7; + flex-shrink: 0; +} + +.sidebar__item--active .sidebar__item__icon { + opacity: 1; +} + +.sidebar__item__badge { + margin-left: auto; + font-size: var(--text-xs); + padding: 1px 6px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.08); + color: var(--text-muted); +} + +.main-content { + grid-area: main; + overflow-y: auto; + padding: var(--space-6); + animation: fade-in 0.2s ease; +} + +.statusbar { + grid-area: status; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-top: 1px solid var(--border-subtle); + font-size: var(--text-xs); + color: var(--text-muted); + gap: var(--space-4); +} + +.statusbar__segment { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.statusbar__dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--colour-success); +} + +.statusbar__dot--warning { + background: var(--colour-warning); +} +.statusbar__dot--error { + background: var(--colour-error); +} + +/* --- Component: Cards -------------------------------------- */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-5); + transition: all var(--transition-base); +} + +.card:hover { + background: var(--bg-card-hover); + border-color: var(--border-default); + box-shadow: var(--shadow-card); +} + +.card--glow:hover { + box-shadow: var(--shadow-glow); + border-color: rgba(255, 170, 64, 0.2); +} + +.card__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-3); +} + +.card__title { + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 600; +} + +/* --- Component: Buttons ------------------------------------ */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: 500; + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + background: var(--bg-card); + color: var(--text-primary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn:hover { + background: var(--bg-card-hover); + border-color: var(--border-strong); +} + +.btn--primary { + background: var(--brand-orange); + border-color: var(--brand-orange); + color: var(--text-inverse); +} + +.btn--primary:hover { + background: #e89930; + box-shadow: 0 0 16px rgba(255, 170, 64, 0.3); +} + +.btn--danger { + border-color: var(--colour-error); + color: var(--colour-error); +} + +.btn--ghost { + background: transparent; + border-color: transparent; + color: var(--text-secondary); +} + +.btn--ghost:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.btn--sm { + padding: 2px var(--space-2); + font-size: var(--text-xs); +} + +/* --- Component: Badges ------------------------------------- */ +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + font-weight: 600; + border-radius: 10px; + letter-spacing: 0.02em; +} + +.badge--remux { + background: rgba(46, 198, 106, 0.15); + color: var(--badge-remux); +} +.badge--light { + background: rgba(96, 165, 250, 0.15); + color: var(--badge-light); +} +.badge--reencode { + background: rgba(251, 191, 36, 0.15); + color: var(--badge-reencode); +} +.badge--unsupported { + background: rgba(244, 63, 94, 0.15); + color: var(--badge-unsupported); +} +.badge--neutral { + background: rgba(255, 255, 255, 0.06); + color: var(--text-muted); +} + +/* --- Component: Progress Bar ------------------------------- */ +.progress { + height: 6px; + background: rgba(255, 255, 255, 0.06); + border-radius: 3px; + overflow: hidden; +} + +.progress__fill { + height: 100%; + border-radius: 3px; + transition: width var(--transition-base); +} + +.progress__fill--ok { + background: var(--brand-gradient); +} +.progress__fill--warning { + background: var(--colour-warning); +} +.progress__fill--error { + background: var(--colour-error); +} + +.progress--striped .progress__fill { + background-image: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + rgba(255, 255, 255, 0.08) 10px, + rgba(255, 255, 255, 0.08) 20px + ); + background-size: 40px 40px; + animation: progress-stripe 1s linear infinite; +} + +/* --- Component: Table -------------------------------------- */ +.table-wrap { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); +} + +th { + text-align: left; + padding: var(--space-2) var(--space-3); + font-weight: 600; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + border-bottom: 1px solid var(--border-default); +} + +td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--border-subtle); + color: var(--text-secondary); +} + +tr:hover td { + background: rgba(255, 255, 255, 0.02); +} + +/* --- Component: Input / Form ------------------------------- */ +.input { + padding: var(--space-2) var(--space-3); + font-family: var(--font-body); + font-size: var(--text-sm); + background: var(--bg-input); + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + color: var(--text-primary); + transition: border-color var(--transition-fast); + width: 100%; +} + +.input:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 2px rgba(255, 170, 64, 0.15); +} + +.input--select { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%237c8796' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 28px; +} + +.form-group { + margin-bottom: var(--space-4); +} + +.form-label { + display: block; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: var(--space-1); +} + +/* --- Component: Tabs --------------------------------------- */ +.tabs { + display: flex; + gap: var(--space-1); + border-bottom: 1px solid var(--border-subtle); + margin-bottom: var(--space-5); +} + +.tab { + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + font-weight: 500; + color: var(--text-muted); + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all var(--transition-fast); +} + +.tab:hover { + color: var(--text-primary); +} + +.tab--active { + color: var(--brand-orange); + border-bottom-color: var(--brand-orange); +} + +/* --- Component: Tag / Chip --------------------------------- */ +.tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + border-radius: var(--radius-sm); + background: rgba(255, 255, 255, 0.06); + color: var(--text-secondary); + border: 1px solid var(--border-subtle); +} + +/* --- Component: Timeline ----------------------------------- */ +.timeline { + position: relative; + height: 48px; + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + overflow: hidden; +} + +.timeline__track { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 4px; + background: rgba(255, 255, 255, 0.08); + transform: translateY(-50%); +} + +.timeline__marker { + position: absolute; + top: 4px; + bottom: 4px; + width: 2px; + background: var(--brand-orange); + border-radius: 1px; +} + +.timeline__marker::after { + content: attr(data-label); + position: absolute; + top: -2px; + left: 6px; + font-size: var(--text-xs); + color: var(--text-muted); + white-space: nowrap; +} + +/* --- Component: Modal / Dialog ----------------------------- */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal { + background: var(--bg-modal); + border: 1px solid var(--border-default); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-modal); + padding: var(--space-8); + max-width: 560px; + width: 90%; + animation: fade-in 0.2s ease; +} + +/* --- Component: Capacity Bar ------------------------------- */ +.capacity-bar { + display: flex; + height: 28px; + border-radius: var(--radius-md); + overflow: hidden; + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.03); +} + +.capacity-bar__segment { + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-inverse); + transition: width var(--transition-base); + min-width: 2px; +} + +/* --- Layout Helpers ---------------------------------------- */ +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-6); +} + +.page-title { + font-family: var(--font-heading); + font-size: var(--text-xl); + font-weight: 700; +} + +.grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); +} +.grid-3 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--space-4); +} +.grid-4 { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: var(--space-4); +} + +.flex { + display: flex; +} +.flex-col { + flex-direction: column; +} +.items-center { + align-items: center; +} +.justify-between { + justify-content: space-between; +} +.gap-1 { + gap: var(--space-1); +} +.gap-2 { + gap: var(--space-2); +} +.gap-3 { + gap: var(--space-3); +} +.gap-4 { + gap: var(--space-4); +} + +.mt-2 { + margin-top: var(--space-2); +} +.mt-4 { + margin-top: var(--space-4); +} +.mt-6 { + margin-top: var(--space-6); +} +.mb-2 { + margin-bottom: var(--space-2); +} +.mb-4 { + margin-bottom: var(--space-4); +} + +.w-full { + width: 100%; +} + +/* --- Scrollbar Styling ------------------------------------- */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.18); +} From 15847d6792e3c359ead22b0dec4b766f51b12816 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 19:45:02 -0400 Subject: [PATCH 10/32] docs: add Concept 2 The Greenhouse template-first progressive editor A growth-oriented editor where beginners choose a seed template (Classic Main Menu, Chapter Grid, Audio Setup, etc.) and progressively unlock customisation steps: Theme, Layout, Bind Actions, Motion, Compile. Each step reveals new options as the design matures. Generated content stays editable. Co-Authored-By: Claude Opus 4.6 --- .../yuli/set-2/concept-2-the-greenhouse.html | 1031 +++++++++++++++++ 1 file changed, 1031 insertions(+) create mode 100644 mockups/yuli/set-2/concept-2-the-greenhouse.html diff --git a/mockups/yuli/set-2/concept-2-the-greenhouse.html b/mockups/yuli/set-2/concept-2-the-greenhouse.html new file mode 100644 index 0000000..5f64a83 --- /dev/null +++ b/mockups/yuli/set-2/concept-2-the-greenhouse.html @@ -0,0 +1,1031 @@ + + + + + + Spindle — Concept 2: The Greenhouse + + + + + + +
+ +
/ Nature Documentary / Main Menu
+
+ DVD Ready + + +
+
+ +
+ +
+
+
Menu Garden
+
Choose a seed template or start from a blank canvas
+
+ + + +
+
+
Quick Start Templates
+ +
+
+
+
+
+
+
+
+
+
Classic Main Menu
+
Title, subtitle, and vertically stacked navigation buttons
+
+ 3-5 buttons + Auto-wire +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
Chapter Grid
+
Auto-paginated thumbnail grid with Next/Back navigation
+
+ 6-18 per page + Auto-paginate +
+
+
+ +
+
+
+
+
+
+
+
+
Audio / Subtitle Setup
+
Stream selection menu with audio and subtitle choices
+
+ setAudio + setSubtitle +
+
+
+
+ +
+
Advanced Layouts
+ +
+
+
+
+
+
+
+
+
+
+
Poster Shelf
+
Large hero image with title cards below
+
+ Cinematic + 2-6 titles +
+
+
+ +
+
+
+
+
+
+
+
+
Blank Canvas
+
Start from nothing — full creative control
+
+ Advanced +
+
+
+
+
+ + +
+
+
+ Choose Template +
+
+
+
+ Pick Theme +
+
+
+
3
+ Customise Layout +
+
+
+
4
+ Bind Actions +
+
+
+
5
+ Verify & Build +
+
+
+ + +
+
+ +
+ Menu 1 of 4 +
+ + +
+
+ +
+ + + +
+ +
+ Template applied. Click any element to customise, or drag to reposition. +
+ +
+
+
+ + +
+
+
+ + Grow Your Menu +
+
Each step unlocks new options as your design evolves
+
+ +
+ +
+
+ + Theme + Applied +
+
+
+
+
+
Cinematic Dark
+
+
+
+
Warm Light
+
+
+
+
Ocean Blue
+
+
+
+
+ + +
+
+ + Layout & Content + Editing +
+
+
+ Menu Title + +
+
+ Subtitle + +
+
+ Background +
+
+ +
+
+
+ Button Count +
+ + Max 36 (DVD limit) +
+
+
+
+ + +
+
+ + Bind Actions + Next +
+
+
+
+ Play Film + + +
+
+ Select Chapter + + +
+
+ Audio & Subtitles + + +
+
+ Special Features + + +
+
+
+
+ + +
+
+ + Motion & Timing + Optional +
+
+ + +
+
+ + Compile Settings + Optional +
+
+ + +
+ + 4 buttons, all actions bound. Navigation auto-generated. No DVD constraint warnings. +
+ +
+ + No background asset selected. A solid-colour background will be used during compilation. +
+
+
+
+ +
+
+ Template: Classic Main Menu + | + Theme: Cinematic Dark + | + Step 3 of 5: Customise Layout + + 720 x 480 NTSC +
+ + From f1bfe528aa006aec8a5b5dd2471aeddbfd7763dd Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 19:48:42 -0400 Subject: [PATCH 11/32] docs: add Concept 3 The Cartographer multi-menu navigation map A bird's-eye transit-map view of the entire disc's menu system. All menus shown as thumbnail cards with SVG connection lines visualising showMenu, playTitle, and return routes. Click any menu to see its details in the right panel. Supports VMGM/VTS domain badges, auto-pagination indicators, and full diagnostics per menu. Co-Authored-By: Claude Opus 4.6 --- .../set-2/concept-3-the-cartographer.html | 831 ++++++++++++++++++ 1 file changed, 831 insertions(+) create mode 100644 mockups/yuli/set-2/concept-3-the-cartographer.html diff --git a/mockups/yuli/set-2/concept-3-the-cartographer.html b/mockups/yuli/set-2/concept-3-the-cartographer.html new file mode 100644 index 0000000..80c081f --- /dev/null +++ b/mockups/yuli/set-2/concept-3-the-cartographer.html @@ -0,0 +1,831 @@ + + + + + + Spindle — Concept 3: The Cartographer + + + + + + +
+ +
/ Concert Archive / Menu System Map
+
+ + + +
+
+ +
+ +
+
+
+ + Disc Navigation Map +
+ 5 menus, 3 titles +
+ + + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + showMenu + + + + showMenu + + + + playTitle + + + + Next Page + + + + + + + + + +
+ + First Play +
+ + +
+
+
Concert Archive
+
+
+
+
+
+
Main Menu
+
+ VMGM + 3 buttons + Default focus +
+
+
+ +
+
+
Chapters
+
+
+
+
+
+
+
+
+
Chapter Select (Page 1)
+
+ VTS 1 + 6 buttons + +2 nav +
+
+
+ +
+
+
Chapters p2
+
+
+
+
+
+
Chapter Select (Page 2)
+
+ VTS 1 + 3 + nav +
+
+
+ +
+
+
Audio & Subs
+
+
+
+
+
+
Audio Setup
+
+ VTS 1 + 3 buttons + setAudio +
+
+
+ + +
+ + Title 1: Full Concert (12 chapters) +
+ +
+ + Title 2: Backstage Documentary +
+ +
+ + Title 3: Photo Slideshow +
+ + +
+
Legend
+
+
+ showMenu link +
+
+
+ playTitle action +
+
+
+ Return / post-action +
+
+ VMGM + Global menu domain +
+
+ VTS + Titleset menu domain +
+
+ + +
+
+
+
-
+
Fit
+
+
+
+ + +
+
+
+ VMGM + Main Menu +
+
+ 3 buttons + Still menu + 720 x 480 +
+
+ + +
+
Preview
+
+
Concert Archive
+
+ ▶ Play Concert +
+
+ ◆ Chapter Select +
+
+ ♫ Audio & Subtitles +
+
+
+ + +
+
+ + +
+
Buttons & Actions
+
+
+
+
Play Concert
+
playTitle 1
+
+
+
+
Chapter Select
+
showMenu "Ch. Select"
+
+
+
+
Audio & Subtitles
+
showMenu "Audio Setup"
+
+
+
+ + +
+
Navigation Connections
+
+
+ Outgoing + Chapter Select + showMenu +
+
+ Outgoing + Audio Setup + showMenu +
+
+ Outgoing + Title 1: Full Concert + playTitle +
+
+ Incoming + First Play + entry +
+
+ Incoming + Title 1 (post) + return +
+
+
+ + +
+
Diagnostics
+
+ + All action targets valid. Navigation fully connected. No unreachable menus. +
+
+ + Chapter Select uses 2 pages. Verify Next/Back navigation works correctly with the remote simulator. +
+
+
+
+ +
+
+ Map View + | + 5 menus, 3 titles, 1 titleset + | + All routes verified + + DVD 4:3 NTSC +
+ + From 9e254664ad1ba125e8bc3da8c3310faf9942146f Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 19:52:02 -0400 Subject: [PATCH 12/32] docs: add Concept 4 The Darkroom state-focused visual editor A side-by-side triple-canvas view showing Normal, Focus, and Activate states for each button. Left panel exposes the DVD 4-colour CLUT palette. Right inspector lets users edit per-state visual treatments and shows exactly how authored styles compile down to DVD subpicture overlays. Includes highlight animation keyframe editor for motion menus. Co-Authored-By: Claude Opus 4.6 --- .../yuli/set-2/concept-4-the-darkroom.html | 919 ++++++++++++++++++ 1 file changed, 919 insertions(+) create mode 100644 mockups/yuli/set-2/concept-4-the-darkroom.html diff --git a/mockups/yuli/set-2/concept-4-the-darkroom.html b/mockups/yuli/set-2/concept-4-the-darkroom.html new file mode 100644 index 0000000..aaf1ab2 --- /dev/null +++ b/mockups/yuli/set-2/concept-4-the-darkroom.html @@ -0,0 +1,919 @@ + + + + + + Spindle — Concept 4: The Darkroom + + + + + + +
+ +
/ Wedding Highlights / Main Menu
+
+ DVD 4-colour palette + + +
+
+ +
+ +
+
Interactive Nodes
+ +
+
+
+ Play Ceremony + Default +
+ +
+
+ Play Reception +
+ +
+
+ Chapter Select +
+ +
+
+ Audio Setup +
+
+ + +
+
DVD Subpicture Palette
+
+
+
Focus Colour
+
#FF4444
+
+
+
Focus Opacity
+
60%
+
+
+
Activate Colour
+
#44FF44
+
+
+
Activate Opacity
+
80%
+
+
+ +
+ DVD limit: Subpicture overlays use a strict 4-colour CLUT. These colours apply to all buttons simultaneously. +
+
+
+ + +
+
+
+ + + + +
+ + + Viewing: Play Ceremony in all states +
+ +
+ +
+
Normal
+
+
Wedding Highlights
+ +
+ ▶ Play Ceremony +
+
+ ▶ Play Reception +
+
+ ◆ Chapter Select +
+
+ ♫ Audio Setup +
+
+
+ + +
+ +
Remote Focus
+
+ + +
+
Focus (Highlight)
+
+
Wedding Highlights
+ + +
+
+
+ +
+ ▶ Play Ceremony +
+
+ ▶ Play Reception +
+
+ ◆ Chapter Select +
+
+ ♫ Audio Setup +
+
+
+ + +
+ +
Enter / OK
+
+ + +
+
Activate (Select)
+
+
Wedding Highlights
+ + +
+
+
+ +
+ ▶ Play Ceremony +
+
+ ▶ Play Reception +
+
+ ◆ Chapter Select +
+
+ ♫ Audio Setup +
+
+
+
+
+ + +
+
+
Play Ceremony
+
Button Node — Default Focus
+
+ + +
+
+ + Authored Appearance per State +
+ +
+
+
+ Normal +
+
+
+ Border +
+ +
+
+ Fill +
+ +
+
+ Text +
+ +
+
+
+ +
+
+
+ Focus (Highlight) +
+
+
+ Border +
+ +
+
+ Fill +
+ +
+
+ Glow +
+ +
+
+
+ +
+
+
+ Activate (Select) +
+
+
+ Border +
+ +
+
+ Fill +
+ +
+
+
+
+ + +
+
+
+ + DVD Overlay Mapping +
+
+ Authored focus glow + + Solid rect overlay (#FF4444, 60%) +
+
+ Authored activate fill + + Solid rect overlay (#44FF44, 80%) +
+
+ Rounded corners + + Dropped (DVD rects only) +
+
+
+ + +
+
+
+ + Highlight Animation + Motion Only +
+
+ Keyframes define how highlight colour changes over the motion loop +
+
+
+
+
+
+
+
+ 0s + 10s + 20s + 30s +
+
+
+ + +
+
+ + All three states compile cleanly into DVD subpicture overlays. +
+
+ + Authored glow effect will be dropped in DVD output. The subpicture overlay uses a solid rectangle instead. +
+
+
+
+ +
+ + State Editor + | + Viewing: Play Ceremony + | + 4 buttons, 3 states each + + 720 x 480 NTSC | 4-colour CLUT +
+ + From 59d281b086e0a55bf7be76034af77edd070f9a24 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 21:47:22 -0400 Subject: [PATCH 13/32] docs: add Yuli set 3 menu editor foundations **What** - Add a shared visual system for Yuli set 3 menu editor explorations. - Add the set index plus the Concourse, Atelier, and Observatory concepts. - Frame the concepts around scene authoring, routing clarity, motion awareness, and compile honesty. **Why** - Establish a coherent foundation for comparing multiple full-featured menu editor directions. - Ground the mockups in the current Spindle menu-system spec and the calm-computing direction used across Liminal HQ. --- .../yuli/set-3/concept-1-the-concourse.html | 218 +++++ mockups/yuli/set-3/concept-2-the-atelier.html | 220 +++++ .../yuli/set-3/concept-3-the-observatory.html | 197 +++++ mockups/yuli/set-3/design-system.css | 827 ++++++++++++++++++ mockups/yuli/set-3/index.html | 135 +++ 5 files changed, 1597 insertions(+) create mode 100644 mockups/yuli/set-3/concept-1-the-concourse.html create mode 100644 mockups/yuli/set-3/concept-2-the-atelier.html create mode 100644 mockups/yuli/set-3/concept-3-the-observatory.html create mode 100644 mockups/yuli/set-3/design-system.css create mode 100644 mockups/yuli/set-3/index.html diff --git a/mockups/yuli/set-3/concept-1-the-concourse.html b/mockups/yuli/set-3/concept-1-the-concourse.html new file mode 100644 index 0000000..2bb669b --- /dev/null +++ b/mockups/yuli/set-3/concept-1-the-concourse.html @@ -0,0 +1,218 @@ + + + + + + Spindle — The Concourse + + + + + + +
+
+ + + + + +
+
+ + diff --git a/mockups/yuli/set-3/concept-2-the-atelier.html b/mockups/yuli/set-3/concept-2-the-atelier.html new file mode 100644 index 0000000..2507366 --- /dev/null +++ b/mockups/yuli/set-3/concept-2-the-atelier.html @@ -0,0 +1,220 @@ + + + + + + Spindle — The Atelier + + + + + + +
+
+ + + + + +
+
+ + diff --git a/mockups/yuli/set-3/concept-3-the-observatory.html b/mockups/yuli/set-3/concept-3-the-observatory.html new file mode 100644 index 0000000..c684b78 --- /dev/null +++ b/mockups/yuli/set-3/concept-3-the-observatory.html @@ -0,0 +1,197 @@ + + + + + + Spindle — The Observatory + + + + + + +
+
+ + + + + +
+
+ + diff --git a/mockups/yuli/set-3/design-system.css b/mockups/yuli/set-3/design-system.css new file mode 100644 index 0000000..9c98176 --- /dev/null +++ b/mockups/yuli/set-3/design-system.css @@ -0,0 +1,827 @@ +/* Shared visual system for Yuli's third menu-editor exploration set. + * + * (c) Copyright 2026 Liminal HQ, Scott Morris + * SPDX-License-Identifier: MIT + */ + +:root { + --bg-ink: #08131d; + --bg-deep: #102130; + --bg-panel: rgba(11, 26, 39, 0.82); + --bg-panel-strong: rgba(8, 22, 33, 0.94); + --bg-glass: rgba(236, 245, 243, 0.08); + --line-soft: rgba(205, 229, 224, 0.15); + --line-strong: rgba(205, 229, 224, 0.32); + --text-main: #f2f7f4; + --text-soft: #c4d6d0; + --text-dim: #89a39b; + --mint: #7fe4c3; + --teal: #42c5b4; + --sun: #ffca78; + --coral: #ff8f6b; + --rose: #ffb7a0; + --sky: #76b8ff; + --danger: #ff7b8f; + --shadow-xl: 0 30px 80px rgba(0, 0, 0, 0.32); + --shadow-md: 0 14px 34px rgba(0, 0, 0, 0.24); + --radius-xl: 28px; + --radius-lg: 22px; + --radius-md: 16px; + --radius-sm: 12px; + --font-display: 'Fraunces', Georgia, serif; + --font-body: 'Sora', 'Avenir Next', sans-serif; + --font-mono: 'IBM Plex Mono', monospace; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + min-height: 100%; +} + +body { + font-family: var(--font-body); + color: var(--text-main); + background: + radial-gradient(circle at top left, rgba(127, 228, 195, 0.18), transparent 28%), + radial-gradient(circle at top right, rgba(118, 184, 255, 0.14), transparent 24%), + linear-gradient(160deg, #08131d 0%, #0e1f2d 48%, #15283b 100%); +} + +a { + color: inherit; + text-decoration: none; +} + +.set-shell { + min-height: 100vh; + padding: 32px; +} + +.index-shell { + max-width: 1180px; + margin: 0 auto; +} + +.hero-card, +.concept-card, +.surface, +.menu-shell, +.panel, +.artboard, +.floating-card { + backdrop-filter: blur(22px); + -webkit-backdrop-filter: blur(22px); +} + +.hero-card { + padding: 36px; + border-radius: 32px; + background: linear-gradient(180deg, rgba(12, 29, 42, 0.9), rgba(8, 19, 29, 0.82)); + border: 1px solid var(--line-soft); + box-shadow: var(--shadow-xl); + margin-bottom: 28px; + position: relative; + overflow: hidden; +} + +.hero-card::after { + content: ''; + position: absolute; + inset: auto -80px -80px auto; + width: 280px; + height: 280px; + border-radius: 50%; + background: radial-gradient(circle, rgba(255, 202, 120, 0.34), transparent 70%); +} + +.eyebrow { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.24em; + color: var(--mint); + margin-bottom: 12px; +} + +.hero-title, +.page-title { + font-family: var(--font-display); + font-size: clamp(2.8rem, 5vw, 4.8rem); + line-height: 0.98; + margin: 0 0 16px; + max-width: 720px; +} + +.hero-copy, +.page-copy { + max-width: 780px; + color: var(--text-soft); + font-size: 1rem; + line-height: 1.8; +} + +.pill-row, +.chip-row { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.pill, +.chip, +.badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + border-radius: 999px; + border: 1px solid var(--line-soft); + background: rgba(255, 255, 255, 0.05); + color: var(--text-soft); + font-size: 12px; + letter-spacing: 0.03em; +} + +.pill--warm, +.badge--warm { + background: rgba(255, 202, 120, 0.12); + color: #ffe2b0; +} + +.pill--mint, +.badge--mint { + background: rgba(127, 228, 195, 0.14); + color: #d3fff0; +} + +.pill--sky, +.badge--sky { + background: rgba(118, 184, 255, 0.14); + color: #d9ebff; +} + +.concept-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; +} + +.concept-card { + padding: 24px; + border-radius: 26px; + background: linear-gradient(180deg, rgba(11, 27, 39, 0.88), rgba(8, 20, 30, 0.78)); + border: 1px solid var(--line-soft); + box-shadow: var(--shadow-md); + min-height: 250px; + display: flex; + flex-direction: column; + gap: 14px; +} + +.concept-card h2, +.section-title, +.panel-title { + font-size: 1.1rem; + margin: 0; +} + +.concept-card p, +.panel-copy, +.quiet-copy, +.meta-list, +.legend, +.list { + color: var(--text-soft); + line-height: 1.7; + font-size: 0.94rem; +} + +.concept-card .cta-row, +.toolbar, +.header-row, +.stack-row { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; +} + +.cta { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 12px 18px; + border-radius: 999px; + border: 1px solid transparent; + font-weight: 600; + font-size: 0.9rem; + transition: + transform 180ms ease, + background 180ms ease, + border-color 180ms ease; +} + +.cta:hover { + transform: translateY(-1px); +} + +.cta--primary { + background: linear-gradient(90deg, var(--mint), var(--teal)); + color: #05201a; +} + +.cta--secondary { + border-color: var(--line-strong); + background: rgba(255, 255, 255, 0.04); +} + +.notes-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 16px; + margin-top: 24px; +} + +.note-card { + padding: 18px; + border-radius: 20px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.06); +} + +.note-card strong { + display: block; + margin-bottom: 8px; + color: var(--text-main); +} + +.page-shell { + min-height: 100vh; + padding: 24px; +} + +.page-frame { + max-width: 1480px; + margin: 0 auto; +} + +.page-header { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + justify-content: space-between; + gap: 20px; + margin-bottom: 22px; +} + +.page-header .page-title { + font-size: clamp(2rem, 4vw, 3.4rem); + max-width: none; + margin-bottom: 10px; +} + +.page-subnav { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 16px; +} + +.menu-shell { + padding: 18px; + border: 1px solid var(--line-soft); + background: linear-gradient(180deg, rgba(10, 23, 34, 0.92), rgba(8, 19, 29, 0.8)); + border-radius: 34px; + box-shadow: var(--shadow-xl); + position: relative; + overflow: hidden; +} + +.menu-shell::before { + content: ''; + position: absolute; + inset: 0; + background: + radial-gradient(circle at 88% 12%, rgba(118, 184, 255, 0.14), transparent 20%), + radial-gradient(circle at 12% 6%, rgba(127, 228, 195, 0.12), transparent 18%); + pointer-events: none; +} + +.workspace { + display: grid; + gap: 18px; + position: relative; + z-index: 1; +} + +.workspace--three { + grid-template-columns: 280px minmax(480px, 1fr) 320px; +} + +.workspace--wide-right { + grid-template-columns: 260px minmax(520px, 1fr) 380px; +} + +.workspace--top-bottom { + grid-template-columns: 1fr 340px; + grid-template-rows: minmax(520px, auto) auto; +} + +.workspace--top-bottom .artboard-wrap { + grid-column: 1; + grid-row: 1 / span 2; +} + +.panel { + padding: 18px; + border-radius: 26px; + background: var(--bg-panel); + border: 1px solid var(--line-soft); + box-shadow: var(--shadow-md); +} + +.panel--solid { + background: var(--bg-panel-strong); +} + +.panel h3, +.panel h4, +.panel h5 { + margin: 0; +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin-bottom: 16px; +} + +.panel-kicker { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.2em; + color: var(--text-dim); +} + +.list { + display: grid; + gap: 10px; +} + +.list-item, +.route-card, +.mini-card, +.property-card, +.diagnostic, +.timeline-row, +.generator-card { + padding: 14px 15px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.07); +} + +.list-item--selected, +.route-card--selected, +.generator-card--selected { + border-color: rgba(127, 228, 195, 0.45); + background: rgba(127, 228, 195, 0.11); +} + +.list-item strong, +.route-card strong, +.mini-card strong, +.property-card strong, +.generator-card strong { + display: block; + font-size: 0.92rem; + margin-bottom: 6px; +} + +.meta { + font-size: 0.8rem; + color: var(--text-dim); + line-height: 1.6; +} + +.artboard-wrap { + display: flex; + flex-direction: column; + gap: 16px; +} + +.artboard-toolbar, +.status-strip, +.footer-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + padding: 14px 16px; + border-radius: 20px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.06); +} + +.artboard { + position: relative; + aspect-ratio: 720 / 480; + border-radius: 28px; + overflow: hidden; + background: + linear-gradient(140deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.36)), + linear-gradient(135deg, #234055 0%, #15293e 36%, #19374b 62%, #0f1c28 100%); + border: 1px solid rgba(255, 255, 255, 0.09); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.artboard--warm { + background: + linear-gradient(140deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.32)), + linear-gradient(145deg, #5a3b29 0%, #20394e 45%, #1b2636 100%); +} + +.artboard--cool { + background: + linear-gradient(140deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.32)), + linear-gradient(145deg, #143347 0%, #194d5c 38%, #10242f 100%); +} + +.artboard--night { + background: + linear-gradient(140deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.4)), + linear-gradient(145deg, #09121d 0%, #17324c 40%, #211b37 100%); +} + +.safe-zone, +.grid-overlay, +.flow-line, +.selection-ring, +.button-node, +.text-node, +.image-node, +.video-node, +.floating-card { + position: absolute; +} + +.safe-zone { + border: 1px dashed rgba(255, 255, 255, 0.22); + border-radius: 18px; +} + +.safe-zone--title { + inset: 10%; +} + +.safe-zone--action { + inset: 5%; + border-color: rgba(127, 228, 195, 0.25); +} + +.grid-overlay { + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px); + background-size: 9% 12%; + opacity: 0.65; +} + +.canvas-label { + position: absolute; + font-size: 11px; + letter-spacing: 0.18em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.44); +} + +.canvas-label--title { + top: 8%; + left: 11%; +} + +.canvas-label--action { + top: 3%; + left: 6%; + color: rgba(127, 228, 195, 0.55); +} + +.button-node, +.text-node, +.image-node, +.video-node { + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.14); + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.22); +} + +.button-node { + display: flex; + align-items: center; + justify-content: center; + padding: 12px; + background: rgba(6, 16, 25, 0.52); + color: var(--text-main); + text-align: center; + font-weight: 600; + font-size: 0.92rem; +} + +.button-node--focus { + border-color: rgba(255, 202, 120, 0.9); + box-shadow: + 0 0 0 2px rgba(255, 202, 120, 0.25), + 0 20px 28px rgba(0, 0, 0, 0.24); +} + +.button-node--mint { + background: linear-gradient(180deg, rgba(127, 228, 195, 0.18), rgba(16, 46, 45, 0.42)); +} + +.button-node--sky { + background: linear-gradient(180deg, rgba(118, 184, 255, 0.22), rgba(17, 37, 67, 0.38)); +} + +.button-node--coral { + background: linear-gradient(180deg, rgba(255, 143, 107, 0.22), rgba(63, 28, 31, 0.38)); +} + +.text-node { + padding: 14px 18px; + background: rgba(255, 255, 255, 0.04); + font-family: var(--font-display); +} + +.image-node, +.video-node { + background: + linear-gradient(130deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.02)), + linear-gradient(145deg, rgba(118, 184, 255, 0.2), rgba(127, 228, 195, 0.12)); +} + +.video-node::after { + content: 'Motion tile'; + position: absolute; + right: 12px; + bottom: 10px; + font-size: 11px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.72); +} + +.flow-line { + height: 2px; + background: linear-gradient(90deg, var(--sky), transparent); + transform-origin: left center; +} + +.flow-line--warm { + background: linear-gradient(90deg, var(--sun), transparent); +} + +.floating-card { + padding: 14px 16px; + border-radius: 18px; + background: rgba(8, 20, 30, 0.86); + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: var(--shadow-md); +} + +.property-grid, +.stats-grid, +.diag-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.property-card span, +.stat span { + display: block; + font-size: 0.76rem; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--text-dim); + margin-bottom: 5px; +} + +.property-card strong, +.stat strong { + margin: 0; +} + +.diagnostic { + display: grid; + grid-template-columns: auto 1fr; + gap: 12px; + align-items: start; +} + +.diagnostic .badge { + padding: 7px 10px; + font-weight: 700; +} + +.diagnostic--warning .badge { + background: rgba(255, 202, 120, 0.14); + color: #ffe2ab; +} + +.diagnostic--error .badge { + background: rgba(255, 123, 143, 0.16); + color: #ffd1d9; +} + +.diagnostic--info .badge { + background: rgba(118, 184, 255, 0.14); + color: #d7e9ff; +} + +.timeline { + display: grid; + gap: 12px; +} + +.timeline-row { + display: grid; + grid-template-columns: 120px 1fr 54px; + align-items: center; + gap: 12px; +} + +.timeline-bar { + height: 16px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + position: relative; + overflow: hidden; +} + +.timeline-fill { + position: absolute; + inset: 0 auto 0 0; + border-radius: inherit; +} + +.timeline-fill--mint { + background: linear-gradient(90deg, var(--mint), var(--teal)); +} + +.timeline-fill--sun { + background: linear-gradient(90deg, var(--sun), var(--coral)); +} + +.timeline-fill--sky { + background: linear-gradient(90deg, var(--sky), #4fd8ff); +} + +.remote { + display: grid; + grid-template-columns: repeat(3, 52px); + grid-template-rows: repeat(3, 52px); + gap: 8px; + justify-content: center; +} + +.remote button, +.mode-chip, +.tiny-pill { + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.05); + color: var(--text-main); + border-radius: 16px; + font: inherit; +} + +.remote button { + width: 52px; + height: 52px; +} + +.remote .is-active { + background: rgba(127, 228, 195, 0.18); + border-color: rgba(127, 228, 195, 0.4); +} + +.mode-chip, +.tiny-pill { + padding: 8px 12px; + font-size: 0.8rem; +} + +.menu-map, +.route-grid { + display: grid; + gap: 12px; +} + +.route-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.split-preview { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.preview-card { + padding: 16px; + border-radius: 20px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.04); +} + +.mini-preview { + margin-top: 14px; + aspect-ratio: 16 / 10; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.08); + position: relative; + overflow: hidden; +} + +.mini-preview::before { + content: ''; + position: absolute; + inset: 0; + background: + linear-gradient(150deg, rgba(118, 184, 255, 0.14), transparent 44%), + linear-gradient(180deg, rgba(8, 18, 28, 0.12), rgba(8, 18, 28, 0.52)), + linear-gradient(130deg, #214058, #162939, #0d1925); +} + +.mini-preview--compiled::before { + filter: saturate(0.62) contrast(0.94); + background: + linear-gradient(180deg, rgba(8, 18, 28, 0.18), rgba(8, 18, 28, 0.6)), + linear-gradient(135deg, #42556a, #283749, #1a2430); +} + +.mini-outline, +.mini-highlight { + position: absolute; + border-radius: 14px; +} + +.mini-outline { + border: 1px solid rgba(255, 255, 255, 0.24); +} + +.mini-highlight { + background: rgba(255, 202, 120, 0.32); + border: 1px solid rgba(255, 202, 120, 0.9); +} + +.footer-note { + margin-top: 20px; + color: var(--text-dim); + font-size: 0.84rem; +} + +@media (max-width: 1240px) { + .workspace--three, + .workspace--wide-right, + .workspace--top-bottom { + grid-template-columns: 1fr; + } + + .workspace--top-bottom .artboard-wrap { + grid-column: auto; + grid-row: auto; + } +} + +@media (max-width: 760px) { + .set-shell, + .page-shell { + padding: 16px; + } + + .hero-card, + .menu-shell, + .panel, + .concept-card { + border-radius: 24px; + } + + .split-preview, + .route-grid, + .property-grid, + .stats-grid, + .diag-grid { + grid-template-columns: 1fr; + } + + .timeline-row { + grid-template-columns: 1fr; + } +} diff --git a/mockups/yuli/set-3/index.html b/mockups/yuli/set-3/index.html new file mode 100644 index 0000000..bb90c29 --- /dev/null +++ b/mockups/yuli/set-3/index.html @@ -0,0 +1,135 @@ + + + + + + Spindle — Yuli Set 3 + + + + + + +
+
+
+
Yuli / Set 3 / Menu Editor Alternatives
+

Calm authoring rooms for a menu system that can grow from simple DVD pages to ambitious scene-driven discs.

+

+ This set interprets Spindle’s menu editor as a local-first design workstation rather than a button spreadsheet. Each concept keeps the same core promises from the spec: honest remote behaviour, explicit actions, generated-but-editable menu sets, motion timing, stream-selection menus, reusable themes, and compiler diagnostics that explain DVD limits in human terms. +

+
+
Local-first calm computing
+
Honest DVD constraints
+
Progressive disclosure for advanced work
+
Global menus, titleset menus, chapter sets, audio and subtitle menus
+
+
+
+ What informed these layouts + Spindle’s scene-document menu spec, the live `MenusPage` rebuild, motion-menu notes, and Liminal HQ’s public emphasis on user agency, privacy, and calm computing. +
+
+ What changes from earlier Yuli sets + Set 3 makes every concept cover the full authoring journey instead of exploring only one facet at a time. The differences are in information architecture, not missing capability. +
+
+ How to review + Open each page in a browser and compare how it handles scene design, generated structures, remote routing, motion, and compile honesty. +
+
+
+ +
+
+
+
Concept 1
+
Guided studio
+
+

The Concourse

+

+ A welcoming operations hall for users who want one primary canvas, a clear left-to-right journey, and helpful travel-signage cues for layers, actions, motion, menu-set generation, and compile readiness. +

+

+ Best for: first-time authors, fast chapter-grid work, and teams that want a gentle default without hiding the real interaction graph. +

+ +
+ +
+
+
Concept 2
+
Craft-focused
+
+

The Atelier

+

+ A more editorial workspace that makes themes, components, and generated menu families feel like curated materials. The inspector behaves like a craft table with swatches, motion cues, and route recipes. +

+

+ Best for: art-forward menu design, reusable button systems, and users shaping a distinctive visual language across many menus. +

+ +
+ +
+
+
Concept 3
+
Navigation-first
+
+

The Observatory

+

+ A control-room layout where remote simulation, route integrity, and timeout flows are constantly visible. Instead of treating navigation as a side panel, it becomes part of the design surface. +

+

+ Best for: discs with dense chapter menus, branching returns, and setup pages where audio and subtitle actions need to stay understandable. +

+ +
+ +
+
+
Concept 4
+
Menu-set orchestration
+
+

The Atlas

+

+ A cartographic take on menu authoring: page families, auto-pagination, titleset boundaries, and generated menu sets sit alongside the live scene. It keeps multi-menu projects feeling organised instead of sprawling. +

+

+ Best for: complex discs with global menus, titleset menus, chapter shelves, and setup trees that need clear lineage. +

+ +
+ +
+
+
Concept 5
+
Compilation trust
+
+

The Screening Room

+

+ A cinematic review environment where authored intent, DVD-safe output, motion timing, and downgrade diagnostics live in the same room. The central idea is to make technical compromise feel visible and manageable. +

+

+ Best for: final polishing, troubleshooting palette pressure, and building confidence before authoring a disc image. +

+ +
+
+
+
+ + From 20d058a91575ed5377290aeca47e355dd67cce1d Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 21:47:42 -0400 Subject: [PATCH 14/32] docs: add final Yuli set 3 menu editor concepts **What** - Add the Atlas concept for multi-menu orchestration, pagination, and titleset-aware wayfinding. - Add the Screening Room concept for authored-versus-compiled review, motion timing, and downgrade transparency. - Complete the five-mockup set with stronger coverage of generation, routing, and compile trust. **Why** - Round out the concept set so it covers both early authoring and late-stage verification needs. - Give Spindle a broader range of calm, user-friendly menu editor directions to compare. --- mockups/yuli/set-3/concept-4-the-atlas.html | 219 ++++++++++++++++++ .../set-3/concept-5-the-screening-room.html | 199 ++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 mockups/yuli/set-3/concept-4-the-atlas.html create mode 100644 mockups/yuli/set-3/concept-5-the-screening-room.html diff --git a/mockups/yuli/set-3/concept-4-the-atlas.html b/mockups/yuli/set-3/concept-4-the-atlas.html new file mode 100644 index 0000000..7e1c1c6 --- /dev/null +++ b/mockups/yuli/set-3/concept-4-the-atlas.html @@ -0,0 +1,219 @@ + + + + + + Spindle — The Atlas + + + + + + +
+
+ + + + + +
+
+ + diff --git a/mockups/yuli/set-3/concept-5-the-screening-room.html b/mockups/yuli/set-3/concept-5-the-screening-room.html new file mode 100644 index 0000000..037e0f3 --- /dev/null +++ b/mockups/yuli/set-3/concept-5-the-screening-room.html @@ -0,0 +1,199 @@ + + + + + + Spindle — The Screening Room + + + + + + +
+
+ + + + + +
+
+ + From b558ddf74068b2b5623ad7dbd650eb1e8747d84a Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 21:55:46 -0400 Subject: [PATCH 15/32] docs: add app-native Yuli set 3a foundations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **What** - Add a set-3a support stylesheet that imports the live Spindle app design system and extends it for mockup layouts. - Add the set index plus the Dockyard, Rail, and Signal concepts. - Keep the concepts grounded in the current app shell, surfaces, typography, and accent colours. **Why** - Explore menu editor alternatives that feel shippable within today’s Spindle visual system. - Give the team grounded variants to compare without requiring a full design-language reset. --- .../yuli/set-3a/concept-1-the-dockyard.html | 147 +++++ mockups/yuli/set-3a/concept-2-the-rail.html | 120 ++++ mockups/yuli/set-3a/concept-3-the-signal.html | 141 ++++ mockups/yuli/set-3a/design-system.css | 605 ++++++++++++++++++ mockups/yuli/set-3a/index.html | 96 +++ 5 files changed, 1109 insertions(+) create mode 100644 mockups/yuli/set-3a/concept-1-the-dockyard.html create mode 100644 mockups/yuli/set-3a/concept-2-the-rail.html create mode 100644 mockups/yuli/set-3a/concept-3-the-signal.html create mode 100644 mockups/yuli/set-3a/design-system.css create mode 100644 mockups/yuli/set-3a/index.html diff --git a/mockups/yuli/set-3a/concept-1-the-dockyard.html b/mockups/yuli/set-3a/concept-1-the-dockyard.html new file mode 100644 index 0000000..278984d --- /dev/null +++ b/mockups/yuli/set-3a/concept-1-the-dockyard.html @@ -0,0 +1,147 @@ + + + + + + Spindle — The Dockyard + + + + + + +
+
+ + +
+
+ + +
+
+
Design
+
Bind
+
Remote
+
Compile
+
NTSC 720 × 480
+
Safe area visible
+
+ +
+
+
+
+
Action-safe
+
Title-safe
+ +
Wedding Highlights
+
Play feature
+
Scenes
+
Set-up
+
Audio tracks
+
Subtitle menu
+
+
+
+
Hover summary
+
“Scenes” opens generated Chapter Set A. Default focus is valid. No broken neighbours.
+
+
+ +
+
Navigation valid
+
1 generated menu family attached
+
Theme: Warm glass
+
Background mode: still
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-3a/concept-2-the-rail.html b/mockups/yuli/set-3a/concept-2-the-rail.html new file mode 100644 index 0000000..a643dd6 --- /dev/null +++ b/mockups/yuli/set-3a/concept-2-the-rail.html @@ -0,0 +1,120 @@ + + + + + + Spindle — The Rail + + + + + + +
+
+ + +
+
+
+
Design
+
Bind
+
Remote
+
Compile
+
Current rail: Scene composition
+
Next suggested task: Assign actions
+
+
+ +
+ + +
+
+
+
+
+
Disc set-up
+
Audio
+
Subtitles
+
Playback
+
Return
+
Resume title
+
+
+
6 nodes
+
Guides on
+
No overlap detected
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-3a/concept-3-the-signal.html b/mockups/yuli/set-3a/concept-3-the-signal.html new file mode 100644 index 0000000..87949af --- /dev/null +++ b/mockups/yuli/set-3a/concept-3-the-signal.html @@ -0,0 +1,141 @@ + + + + + + Spindle — The Signal + + + + + + +
+
+ + +
+
+ + +
+
+
Remote
+
Links visible
+
Default focus visible
+
Broken routes highlighted
+
+
+
+
+
Wedding Highlights
+
Play ceremony
+
Scenes
+
Set-up
+
Back
+
Next page
+ +
+
+
+
+
Route hint
+
Down from “Scenes” lands on “Next page”, not “Back”, because the authored geometry favours the right column.
+
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-3a/design-system.css b/mockups/yuli/set-3a/design-system.css new file mode 100644 index 0000000..ab30212 --- /dev/null +++ b/mockups/yuli/set-3a/design-system.css @@ -0,0 +1,605 @@ +/* App-aligned support styles for Yuli set 3a menu editor mockups. + * + * (c) Copyright 2026 Liminal HQ, Scott Morris + * SPDX-License-Identifier: MIT + */ + +@import url("../../../apps/spindle/src/design-system.css"); + +html, +body { + overflow: auto; + background: + radial-gradient(circle at top right, rgba(167, 139, 250, 0.08), transparent 20%), + radial-gradient(circle at top left, rgba(255, 170, 64, 0.08), transparent 24%), + var(--bg-app); +} + +body { + min-height: 100vh; +} + +.set-shell { + min-height: 100vh; + padding: var(--space-6); +} + +.set-width { + max-width: 1480px; + margin: 0 auto; +} + +.hero, +.surface, +.panel, +.artboard, +.concept-card, +.mini-card, +.insight-card, +.preview-card { + box-shadow: var(--shadow-panel); +} + +.hero { + background: linear-gradient(180deg, rgba(17, 17, 22, 0.94), rgba(12, 12, 16, 0.92)); + border: 1px solid var(--border-default); + border-radius: var(--radius-xl); + padding: var(--space-8); + margin-bottom: var(--space-6); + position: relative; + overflow: hidden; +} + +.hero::after { + content: ''; + position: absolute; + width: 340px; + height: 340px; + right: -120px; + top: -120px; + background: radial-gradient(circle, rgba(255, 170, 64, 0.16), transparent 70%); + pointer-events: none; +} + +.eyebrow { + color: var(--brand-orange); + font-size: var(--text-xs); + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; + margin-bottom: var(--space-3); +} + +.hero h1, +.page-title { + font-size: clamp(2rem, 4vw, 3.4rem); + line-height: 1.05; + max-width: 960px; + margin-bottom: var(--space-4); +} + +.hero p, +.page-copy, +.meta, +.quiet-copy, +.list-copy { + color: var(--text-secondary); + line-height: 1.7; +} + +.chip-row, +.button-row, +.page-nav, +.toolbar-row, +.status-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + align-items: center; +} + +.chip, +.badge, +.tiny-pill { + display: inline-flex; + align-items: center; + padding: 6px 10px; + border-radius: 999px; + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.04); + font-size: var(--text-xs); + color: var(--text-secondary); +} + +.badge--accent { + color: var(--brand-orange); + border-color: rgba(255, 170, 64, 0.22); + background: rgba(255, 170, 64, 0.08); +} + +.badge--info { + color: var(--colour-info); + border-color: rgba(96, 165, 250, 0.18); + background: rgba(96, 165, 250, 0.08); +} + +.badge--success { + color: var(--colour-success); + border-color: rgba(46, 198, 106, 0.18); + background: rgba(46, 198, 106, 0.08); +} + +.concept-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-4); +} + +.concept-card, +.panel, +.surface, +.preview-card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); +} + +.concept-card { + padding: var(--space-5); + display: flex; + flex-direction: column; + gap: var(--space-3); + min-height: 260px; +} + +.concept-card:hover { + border-color: var(--border-default); + background: var(--bg-card-hover); +} + +.concept-card h2, +.panel h2, +.panel h3, +.panel h4, +.preview-card h3 { + margin-bottom: var(--space-2); +} + +.cta { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: 10px 14px; + border-radius: var(--radius-md); + border: 1px solid var(--border-default); + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); + font-weight: 600; + font-size: var(--text-sm); + text-decoration: none; +} + +.cta:hover { + text-decoration: none; + background: rgba(255, 255, 255, 0.08); +} + +.cta--primary { + background: linear-gradient(135deg, rgba(255, 170, 64, 0.22), rgba(244, 63, 94, 0.12)); + border-color: rgba(255, 170, 64, 0.28); + color: var(--brand-orange); +} + +.page-shell { + min-height: 100vh; + padding: var(--space-6); +} + +.page-frame { + max-width: 1520px; + margin: 0 auto; +} + +.page-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: flex-end; + gap: var(--space-5); + margin-bottom: var(--space-6); +} + +.surface { + padding: var(--space-4); + border-radius: 18px; +} + +.workspace { + display: grid; + gap: var(--space-4); +} + +.workspace--three { + grid-template-columns: 260px minmax(480px, 1fr) 320px; +} + +.workspace--wide { + grid-template-columns: 260px minmax(520px, 1fr) 380px; +} + +.workspace--review { + grid-template-columns: minmax(720px, 1fr) 360px; +} + +.panel { + padding: var(--space-4); +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-4); +} + +.panel-kicker { + color: var(--text-muted); + font-size: var(--text-xs); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.14em; + margin-bottom: 4px; +} + +.stack { + display: grid; + gap: var(--space-3); +} + +.list-item, +.mini-card, +.insight-card, +.stat-card, +.route-card, +.timeline-row, +.diag { + padding: var(--space-3); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.list-item--selected, +.route-card--selected, +.mini-card--selected { + border-color: rgba(255, 170, 64, 0.22); + background: rgba(255, 170, 64, 0.08); +} + +.list-item strong, +.mini-card strong, +.insight-card strong, +.stat-card strong, +.route-card strong { + display: block; + font-size: var(--text-sm); + margin-bottom: 4px; +} + +.artboard-wrap { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.artboard-toolbar, +.status-strip, +.subpanel { + background: var(--bg-sidebar); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-3) var(--space-4); +} + +.artboard { + position: relative; + aspect-ratio: 720 / 480; + background: + radial-gradient(circle at 50% 0%, rgba(255, 170, 64, 0.08), transparent 30%), + linear-gradient(145deg, #151520 0%, #0f1217 45%, #0a0a0d 100%); + overflow: hidden; +} + +.artboard--blue { + background: + radial-gradient(circle at 50% 0%, rgba(96, 165, 250, 0.12), transparent 34%), + linear-gradient(145deg, #121827 0%, #0d1016 45%, #0a0a0d 100%); +} + +.artboard--green { + background: + radial-gradient(circle at 50% 0%, rgba(46, 198, 106, 0.12), transparent 34%), + linear-gradient(145deg, #101b18 0%, #0d1113 45%, #090a0d 100%); +} + +.grid-overlay, +.safe-area, +.canvas-label, +.node, +.connector, +.floating, +.preview-node { + position: absolute; +} + +.grid-overlay { + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px); + background-size: 8% 12%; +} + +.safe-area { + border: 1px dashed rgba(255, 255, 255, 0.12); +} + +.safe-area--action { + inset: 5%; + border-color: rgba(46, 198, 106, 0.18); +} + +.safe-area--title { + inset: 10%; + border-color: rgba(96, 165, 250, 0.18); +} + +.canvas-label { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted); +} + +.canvas-label--action { + top: 3%; + left: 5%; + color: rgba(46, 198, 106, 0.55); +} + +.canvas-label--title { + top: 8%; + left: 10%; + color: rgba(96, 165, 250, 0.55); +} + +.node { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: var(--space-2); + background: rgba(17, 17, 22, 0.8); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--text-sm); + font-weight: 600; +} + +.node--focus { + border-color: var(--brand-orange); + box-shadow: + 0 0 0 1px rgba(255, 170, 64, 0.3), + 0 0 18px rgba(255, 170, 64, 0.18); +} + +.node--soft { + background: rgba(255, 255, 255, 0.06); +} + +.node--blue { + border-color: rgba(96, 165, 250, 0.28); + background: rgba(96, 165, 250, 0.08); +} + +.node--green { + border-color: rgba(46, 198, 106, 0.28); + background: rgba(46, 198, 106, 0.08); +} + +.node--pink { + border-color: rgba(244, 63, 94, 0.28); + background: rgba(244, 63, 94, 0.08); +} + +.connector { + height: 2px; + background: linear-gradient(90deg, rgba(255, 170, 64, 0.55), transparent); + transform-origin: left center; +} + +.connector--blue { + background: linear-gradient(90deg, rgba(96, 165, 250, 0.55), transparent); +} + +.floating { + padding: var(--space-3); + border-radius: var(--radius-md); + background: rgba(12, 12, 16, 0.88); + border: 1px solid var(--border-subtle); + max-width: 240px; +} + +.stats-grid, +.route-grid, +.diag-grid, +.preview-grid { + display: grid; + gap: var(--space-3); +} + +.stats-grid, +.route-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.preview-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.stat-card span, +.diag span { + display: block; + font-size: var(--text-xs); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 4px; +} + +.diag { + display: grid; + grid-template-columns: auto 1fr; + gap: var(--space-3); + align-items: start; +} + +.diag-badge { + padding: 4px 8px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.08em; +} + +.diag--info .diag-badge { + background: rgba(96, 165, 250, 0.12); + color: var(--colour-info); +} + +.diag--warning .diag-badge { + background: rgba(251, 191, 36, 0.12); + color: var(--colour-warning); +} + +.diag--error .diag-badge { + background: rgba(244, 63, 94, 0.14); + color: var(--colour-error); +} + +.timeline { + display: grid; + gap: var(--space-3); +} + +.timeline-row { + display: grid; + grid-template-columns: 120px 1fr 60px; + align-items: center; + gap: var(--space-3); +} + +.timeline-bar { + height: 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.06); + overflow: hidden; +} + +.timeline-fill { + height: 100%; + border-radius: inherit; +} + +.timeline-fill--orange { + background: linear-gradient(90deg, rgba(255, 170, 64, 0.9), rgba(244, 63, 94, 0.6)); +} + +.timeline-fill--blue { + background: linear-gradient(90deg, rgba(96, 165, 250, 0.9), rgba(34, 211, 238, 0.6)); +} + +.timeline-fill--green { + background: linear-gradient(90deg, rgba(46, 198, 106, 0.9), rgba(96, 165, 250, 0.4)); +} + +.remote { + display: grid; + grid-template-columns: repeat(3, 40px); + grid-template-rows: repeat(3, 40px); + gap: 8px; + justify-content: center; +} + +.remote button { + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); + border-radius: var(--radius-md); + font: inherit; +} + +.remote .is-active { + border-color: rgba(255, 170, 64, 0.24); + background: rgba(255, 170, 64, 0.1); + color: var(--brand-orange); +} + +.preview-card { + padding: var(--space-4); +} + +.preview-window { + position: relative; + aspect-ratio: 16 / 10; + border-radius: var(--radius-lg); + border: 1px solid var(--border-subtle); + background: + linear-gradient(135deg, rgba(96, 165, 250, 0.08), transparent 40%), + linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(0, 0, 0, 0.14)), + var(--bg-sidebar); + overflow: hidden; + margin-top: var(--space-3); +} + +.preview-window--compiled { + filter: saturate(0.7) contrast(0.94); +} + +.preview-node { + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.16); +} + +@media (max-width: 1280px) { + .workspace--three, + .workspace--wide, + .workspace--review { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .set-shell, + .page-shell { + padding: var(--space-4); + } + + .hero { + padding: var(--space-6); + } + + .stats-grid, + .route-grid, + .preview-grid { + grid-template-columns: 1fr; + } + + .timeline-row { + grid-template-columns: 1fr; + } +} diff --git a/mockups/yuli/set-3a/index.html b/mockups/yuli/set-3a/index.html new file mode 100644 index 0000000..e4564e2 --- /dev/null +++ b/mockups/yuli/set-3a/index.html @@ -0,0 +1,96 @@ + + + + + + Spindle — Yuli Set 3A + + + + + + +
+
+
+
Yuli / Set 3A / App-Native Explorations
+

Five menu-editor alternatives that stay inside Spindle’s live design language.

+

+ This set keeps the current app’s dark workstation feel, orange-led accents, glassy cards, and practical panel rhythm from + `apps/spindle/src/design-system.css`. The concepts still play a little, but they are intentionally grounded enough that each one could influence the real product without a full visual reset. +

+
+
Current Spindle palette
+
Current typography and surface treatment
+
Grounded in MenusPage and scene-editor architecture
+
+
+ +
+
+
+
Concept 1
+
Workbench
+
+

The Dockyard

+

+ A sturdier three-pane editor that makes generated sets, scene layout, and compile readiness feel like one continuous workstation. +

+ Open mockup +
+ +
+
+
Concept 2
+
Focus mode
+
+

The Rail

+

+ A compact, inspector-forward variant that keeps the live app shell feel but reduces noise by turning each editing mode into a guided rail. +

+ Open mockup +
+ +
+
+
Concept 3
+
Navigation-first
+
+

The Signal

+

+ A route-aware variant that overlays remote behaviour directly on the current canvas model instead of pushing navigation into a hidden sub-mode. +

+ Open mockup +
+ +
+
+
Concept 4
+
Set builder
+
+

The Foundry

+

+ A practical generator-focused view for chapter grids, setup menus, and titleset-aware menu families that still looks like today’s app. +

+ Open mockup +
+ +
+
+
Concept 5
+
Compile trust
+
+

The Booth

+

+ A more grounded version of compile review that fits naturally beside Build and Logs, with authored-versus-DVD comparisons and clear repair actions. +

+ Open mockup +
+
+
+
+ + From a818159254565064f0db28df0523251690587037 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 21:56:11 -0400 Subject: [PATCH 16/32] docs: add final app-native Yuli set 3a concepts **What** - Add the Foundry concept for generated menu families, pagination, and titleset-aware preset workflows. - Add the Booth concept for app-native compile review, motion timing, and actionable DVD diagnostics. - Complete the set-3a series with concepts that cover both generation and final verification. **Why** - Round out the app-aligned concept set so it supports both upstream authoring and downstream compile trust. - Give the team grounded alternatives that can inform near-term product decisions. --- .../yuli/set-3a/concept-4-the-foundry.html | 144 +++++++++++++++++ mockups/yuli/set-3a/concept-5-the-booth.html | 149 ++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 mockups/yuli/set-3a/concept-4-the-foundry.html create mode 100644 mockups/yuli/set-3a/concept-5-the-booth.html diff --git a/mockups/yuli/set-3a/concept-4-the-foundry.html b/mockups/yuli/set-3a/concept-4-the-foundry.html new file mode 100644 index 0000000..0c9abc2 --- /dev/null +++ b/mockups/yuli/set-3a/concept-4-the-foundry.html @@ -0,0 +1,144 @@ + + + + + + Spindle — The Foundry + + + + + + +
+
+ + +
+
+ + +
+
+
Generation
+
Preset: Chapter grid
+
Source: Title 1
+
Pages: 2
+
Theme-linked
+
+
+
+
+
Chapter Select
+
01 Opening
+
02 Vows
+
03 Kiss
+
04
+
05 Toasts
+
06 Dance
+
07 Exit
+
Back
+
Next
+
+
Generator state
+
Custom labels preserved on chapters 02 and 05. Re-run will keep those edits unless explicitly reset.
+
+
+ +
+
How output behaves
+
+
+ Page capacity + 8 of recommended 12 to 18 +
+
+ Pagination + Authored nodes, not hidden template logic +
+
+ Regeneration + Safe with preserved manual labels +
+
+ Scope + Titleset 1 only +
+
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-3a/concept-5-the-booth.html b/mockups/yuli/set-3a/concept-5-the-booth.html new file mode 100644 index 0000000..dd2be11 --- /dev/null +++ b/mockups/yuli/set-3a/concept-5-the-booth.html @@ -0,0 +1,149 @@ + + + + + + Spindle — The Booth + + + + + + +
+
+ + +
+
+
+
+
Compile
+
Authored vs DVD
+
Motion loop review
+
Remote-safe focus graph
+
+
+
+
Authored scene
+

Creative intent

+
+
+
+
+
+
+

+ Shows the scene as designed, including richer background tone, component styling, and motion-aware composition. +

+
+ +
+
DVD-safe output
+

What the player gets

+
+
+
+
+
+
+

+ Shows palette reduction and flatter highlight extraction so the user can judge whether the authored design still reads well after compilation. +

+
+
+ +
+
+
+
Motion timing
+

Loop review

+
+
Motion menu
+
+
+
+
Intro
+
+
2.2 s
+
+
+
Loop
+
+
10 s
+
+
+
Audio
+
+
AC-3
+
+
+
+
+ + +
+
+
+
+ + From f6c12d764f74bbc48f1b4c373b7661ec1ee89ef9 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 23:30:20 -0400 Subject: [PATCH 17/32] docs: add integrated Yuli set 3b menu editor concepts **What** - Add a smaller set-3b exploration with three app-native menu editor concepts. - Remove separate Bind and Remote views from the concepts and fold those behaviours into the main editor through trays, contextual peeks, and inline interaction rails. - Add an index and support stylesheet for the Harbour, Lantern, and Junction variants. **Why** - Explore whether Spindle can keep users in one primary scene-editing surface without hiding route logic or semantic actions. - Give the team more opinionated, lower-mode-overhead directions to compare against the earlier sets. --- .../yuli/set-3b/concept-1-the-harbour.html | 159 +++++ .../yuli/set-3b/concept-2-the-lantern.html | 112 ++++ .../yuli/set-3b/concept-3-the-junction.html | 157 +++++ mockups/yuli/set-3b/design-system.css | 628 ++++++++++++++++++ mockups/yuli/set-3b/index.html | 71 ++ 5 files changed, 1127 insertions(+) create mode 100644 mockups/yuli/set-3b/concept-1-the-harbour.html create mode 100644 mockups/yuli/set-3b/concept-2-the-lantern.html create mode 100644 mockups/yuli/set-3b/concept-3-the-junction.html create mode 100644 mockups/yuli/set-3b/design-system.css create mode 100644 mockups/yuli/set-3b/index.html diff --git a/mockups/yuli/set-3b/concept-1-the-harbour.html b/mockups/yuli/set-3b/concept-1-the-harbour.html new file mode 100644 index 0000000..1055613 --- /dev/null +++ b/mockups/yuli/set-3b/concept-1-the-harbour.html @@ -0,0 +1,159 @@ + + + + + + Spindle — The Harbour + + + + + + +
+
+ + +
+
+ + +
+
+
Scene editor
+
Actions inline
+
Route hints inline
+
Compile notes inline
+
+ +
+
+
+
+
Action-safe
+
Title-safe
+ +
Wedding Highlights
+
Play feature
+
Scenes
+
Set-up
+
Audio menu
+
Subtitle menu
+ +
+
+ +
+
Selection peek
+
This button opens a generated chapter family and is reachable from default focus in one move.
+
+
+ +
+
+
+
Scenes button tray
+

Everything needed for this node, without changing views

+
+
Selected node
+
+
+
+ Action +
+
Show menu
+
Chapter Set A
+
+
+
+ Default focus +
+
Not default
+
Reachable from Play feature
+
+
+
+ Neighbours +
+
Up: Play feature
+
Down: Set-up
+
Right: Audio menu
+
+
+
+ Compile note +
+
DVD-safe
+
Generated family under 18 buttons per page
+
+
+
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-3b/concept-2-the-lantern.html b/mockups/yuli/set-3b/concept-2-the-lantern.html new file mode 100644 index 0000000..2a764ef --- /dev/null +++ b/mockups/yuli/set-3b/concept-2-the-lantern.html @@ -0,0 +1,112 @@ + + + + + + Spindle — The Lantern + + + + + + +
+
+ + +
+
+
+
+
Unified editor
+
Behaviour peeks on selection
+
Remote path shown only near active node
+
Warnings appear in place
+
+ +
+
+
+
Language Set-up
+
English 5.1
+
Commentary
+
French stereo
+
Subtitles
+
Return
+ +
+
Set audio stream 1
+
Stay on menu
+
+ +
+
Right neighbour
+
Commentary
+
+ +
+
Default focus
+
+ +
+
Local warning
+
If this page adds more audio options, switch to a generated setup family instead of hand-placing every button.
+
+
+ +
+
Selection reveals active
+
No separate Bind mode
+
No separate Remote mode
+
Current selection: English 5.1
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-3b/concept-3-the-junction.html b/mockups/yuli/set-3b/concept-3-the-junction.html new file mode 100644 index 0000000..5cd6262 --- /dev/null +++ b/mockups/yuli/set-3b/concept-3-the-junction.html @@ -0,0 +1,157 @@ + + + + + + Spindle — The Junction + + + + + + +
+
+ + +
+
+ + +
+
+
Integrated scene
+
Routing always visible
+
Action stack always visible
+
Compile risk strip always visible
+
+ +
+
+
+
+ +
Chapter Select
+
01 Opening
+
02 Vows
+
03 Kiss
+
04
+
05 Toasts
+
06 Dance
+
07 Exit
+
Back
+
Next
+ +
+
+
+
+ +
+
+
+
Interaction rail
+

Selected tile: 01 Opening

+
+
Everything in one place
+
+
+
+ Action stack +
+
Play chapter
+
Title 1 / Chapter 01
+
+
+
+ Focus path +
+
Right → 02 Vows
+
Down → 05 Toasts
+
+
+
+ Generator lineage +
+
Chapter grid preset
+
Page 1 of 2
+
Custom label preserved
+
+
+
+ Compile risk +
+
Button count safe
+
No unreachable nodes
+
+
+
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-3b/design-system.css b/mockups/yuli/set-3b/design-system.css new file mode 100644 index 0000000..7dba02f --- /dev/null +++ b/mockups/yuli/set-3b/design-system.css @@ -0,0 +1,628 @@ +/* App-aligned support styles for Yuli set 3b integrated menu editor mockups. + * + * (c) Copyright 2026 Liminal HQ, Scott Morris + * SPDX-License-Identifier: MIT + */ + +@import url("../../../apps/spindle/src/design-system.css"); + +html, +body { + overflow: auto; + background: + radial-gradient(circle at top right, rgba(167, 139, 250, 0.08), transparent 20%), + radial-gradient(circle at top left, rgba(255, 170, 64, 0.08), transparent 24%), + var(--bg-app); +} + +body { + min-height: 100vh; +} + +.set-shell, +.page-shell { + min-height: 100vh; + padding: var(--space-6); +} + +.set-width, +.page-frame { + max-width: 1500px; + margin: 0 auto; +} + +.hero, +.surface, +.panel, +.artboard, +.concept-card, +.mini-card, +.peek-card, +.preview-card, +.floating-tray, +.inline-strip { + box-shadow: var(--shadow-panel); +} + +.hero { + background: linear-gradient(180deg, rgba(17, 17, 22, 0.94), rgba(12, 12, 16, 0.92)); + border: 1px solid var(--border-default); + border-radius: var(--radius-xl); + padding: var(--space-8); + margin-bottom: var(--space-6); + position: relative; + overflow: hidden; +} + +.hero::after { + content: ''; + position: absolute; + width: 340px; + height: 340px; + right: -120px; + top: -120px; + background: radial-gradient(circle, rgba(255, 170, 64, 0.16), transparent 70%); + pointer-events: none; +} + +.eyebrow { + color: var(--brand-orange); + font-size: var(--text-xs); + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; + margin-bottom: var(--space-3); +} + +.hero h1, +.page-title { + font-size: clamp(2rem, 4vw, 3.35rem); + line-height: 1.05; + max-width: 980px; + margin-bottom: var(--space-4); +} + +.hero p, +.page-copy, +.meta, +.quiet-copy, +.list-copy { + color: var(--text-secondary); + line-height: 1.7; +} + +.chip-row, +.button-row, +.page-nav, +.toolbar-row, +.status-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + align-items: center; +} + +.chip, +.badge, +.tiny-pill { + display: inline-flex; + align-items: center; + padding: 6px 10px; + border-radius: 999px; + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.04); + font-size: var(--text-xs); + color: var(--text-secondary); +} + +.badge--accent { + color: var(--brand-orange); + border-color: rgba(255, 170, 64, 0.22); + background: rgba(255, 170, 64, 0.08); +} + +.badge--info { + color: var(--colour-info); + border-color: rgba(96, 165, 250, 0.18); + background: rgba(96, 165, 250, 0.08); +} + +.badge--success { + color: var(--colour-success); + border-color: rgba(46, 198, 106, 0.18); + background: rgba(46, 198, 106, 0.08); +} + +.concept-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-4); +} + +.concept-card, +.panel, +.surface, +.preview-card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); +} + +.concept-card { + padding: var(--space-5); + display: flex; + flex-direction: column; + gap: var(--space-3); + min-height: 270px; +} + +.concept-card:hover { + border-color: var(--border-default); + background: var(--bg-card-hover); +} + +.concept-card h2, +.panel h2, +.panel h3, +.panel h4, +.preview-card h3 { + margin-bottom: var(--space-2); +} + +.cta { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: 10px 14px; + border-radius: var(--radius-md); + border: 1px solid var(--border-default); + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); + font-weight: 600; + font-size: var(--text-sm); + text-decoration: none; +} + +.cta:hover { + text-decoration: none; + background: rgba(255, 255, 255, 0.08); +} + +.cta--primary { + background: linear-gradient(135deg, rgba(255, 170, 64, 0.22), rgba(244, 63, 94, 0.12)); + border-color: rgba(255, 170, 64, 0.28); + color: var(--brand-orange); +} + +.page-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: flex-end; + gap: var(--space-5); + margin-bottom: var(--space-6); +} + +.surface { + padding: var(--space-4); + border-radius: 18px; +} + +.workspace { + display: grid; + gap: var(--space-4); +} + +.workspace--three { + grid-template-columns: 250px minmax(560px, 1fr) 320px; +} + +.workspace--wide { + grid-template-columns: 280px minmax(620px, 1fr) 340px; +} + +.workspace--focus { + grid-template-columns: minmax(760px, 1fr) 360px; +} + +.panel { + padding: var(--space-4); +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-4); +} + +.panel-kicker { + color: var(--text-muted); + font-size: var(--text-xs); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.14em; + margin-bottom: 4px; +} + +.stack { + display: grid; + gap: var(--space-3); +} + +.list-item, +.mini-card, +.peek-card, +.stat-card, +.route-card, +.timeline-row, +.diag, +.slot-card { + padding: var(--space-3); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.list-item--selected, +.mini-card--selected, +.route-card--selected, +.slot-card--selected { + border-color: rgba(255, 170, 64, 0.22); + background: rgba(255, 170, 64, 0.08); +} + +.list-item strong, +.mini-card strong, +.peek-card strong, +.stat-card strong, +.route-card strong, +.slot-card strong { + display: block; + font-size: var(--text-sm); + margin-bottom: 4px; +} + +.artboard-wrap { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.artboard-toolbar, +.status-strip, +.subpanel, +.inline-strip { + background: var(--bg-sidebar); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-3) var(--space-4); +} + +.artboard { + position: relative; + aspect-ratio: 720 / 480; + background: + radial-gradient(circle at 50% 0%, rgba(255, 170, 64, 0.08), transparent 28%), + linear-gradient(145deg, #151520 0%, #0f1217 45%, #0a0a0d 100%); + overflow: hidden; +} + +.artboard--blue { + background: + radial-gradient(circle at 50% 0%, rgba(96, 165, 250, 0.12), transparent 34%), + linear-gradient(145deg, #121827 0%, #0d1016 45%, #0a0a0d 100%); +} + +.artboard--green { + background: + radial-gradient(circle at 50% 0%, rgba(46, 198, 106, 0.12), transparent 34%), + linear-gradient(145deg, #101b18 0%, #0d1113 45%, #090a0d 100%); +} + +.artboard--purple { + background: + radial-gradient(circle at 50% 0%, rgba(167, 139, 250, 0.12), transparent 34%), + linear-gradient(145deg, #171323 0%, #0f1016 45%, #0a0a0d 100%); +} + +.grid-overlay, +.safe-area, +.canvas-label, +.node, +.connector, +.floating-tray, +.peek-inline, +.preview-node, +.inline-anchor { + position: absolute; +} + +.grid-overlay { + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px); + background-size: 8% 12%; +} + +.safe-area { + border: 1px dashed rgba(255, 255, 255, 0.12); +} + +.safe-area--action { + inset: 5%; + border-color: rgba(46, 198, 106, 0.18); +} + +.safe-area--title { + inset: 10%; + border-color: rgba(96, 165, 250, 0.18); +} + +.canvas-label { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted); +} + +.canvas-label--action { + top: 3%; + left: 5%; + color: rgba(46, 198, 106, 0.55); +} + +.canvas-label--title { + top: 8%; + left: 10%; + color: rgba(96, 165, 250, 0.55); +} + +.node { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: var(--space-2); + background: rgba(17, 17, 22, 0.8); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--text-sm); + font-weight: 600; +} + +.node--focus { + border-color: var(--brand-orange); + box-shadow: + 0 0 0 1px rgba(255, 170, 64, 0.3), + 0 0 18px rgba(255, 170, 64, 0.18); +} + +.node--soft { + background: rgba(255, 255, 255, 0.06); +} + +.node--blue { + border-color: rgba(96, 165, 250, 0.28); + background: rgba(96, 165, 250, 0.08); +} + +.node--green { + border-color: rgba(46, 198, 106, 0.28); + background: rgba(46, 198, 106, 0.08); +} + +.node--pink { + border-color: rgba(244, 63, 94, 0.28); + background: rgba(244, 63, 94, 0.08); +} + +.node--purple { + border-color: rgba(167, 139, 250, 0.28); + background: rgba(167, 139, 250, 0.08); +} + +.connector { + height: 2px; + background: linear-gradient(90deg, rgba(255, 170, 64, 0.55), transparent); + transform-origin: left center; +} + +.connector--blue { + background: linear-gradient(90deg, rgba(96, 165, 250, 0.55), transparent); +} + +.connector--green { + background: linear-gradient(90deg, rgba(46, 198, 106, 0.55), transparent); +} + +.floating-tray, +.peek-inline { + padding: var(--space-3); + border-radius: var(--radius-md); + background: rgba(12, 12, 16, 0.92); + border: 1px solid var(--border-subtle); +} + +.floating-tray { + max-width: 260px; +} + +.peek-inline { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + align-items: center; +} + +.inline-anchor { + width: 1px; + height: 1px; +} + +.action-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 999px; + font-size: var(--text-xs); + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); +} + +.action-chip--accent { + color: var(--brand-orange); + border-color: rgba(255, 170, 64, 0.22); + background: rgba(255, 170, 64, 0.08); +} + +.action-chip--info { + color: var(--colour-info); + border-color: rgba(96, 165, 250, 0.18); + background: rgba(96, 165, 250, 0.08); +} + +.action-chip--success { + color: var(--colour-success); + border-color: rgba(46, 198, 106, 0.18); + background: rgba(46, 198, 106, 0.08); +} + +.stats-grid, +.route-grid, +.diag-grid, +.preview-grid, +.slot-grid { + display: grid; + gap: var(--space-3); +} + +.stats-grid, +.route-grid, +.slot-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.preview-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.stat-card span, +.diag span { + display: block; + font-size: var(--text-xs); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 4px; +} + +.diag { + display: grid; + grid-template-columns: auto 1fr; + gap: var(--space-3); + align-items: start; +} + +.diag-badge { + padding: 4px 8px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.08em; +} + +.diag--info .diag-badge { + background: rgba(96, 165, 250, 0.12); + color: var(--colour-info); +} + +.diag--warning .diag-badge { + background: rgba(251, 191, 36, 0.12); + color: var(--colour-warning); +} + +.diag--error .diag-badge { + background: rgba(244, 63, 94, 0.14); + color: var(--colour-error); +} + +.timeline { + display: grid; + gap: var(--space-3); +} + +.timeline-row { + display: grid; + grid-template-columns: 120px 1fr 60px; + align-items: center; + gap: var(--space-3); +} + +.timeline-bar { + height: 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.06); + overflow: hidden; +} + +.timeline-fill { + height: 100%; + border-radius: inherit; +} + +.timeline-fill--orange { + background: linear-gradient(90deg, rgba(255, 170, 64, 0.9), rgba(244, 63, 94, 0.6)); +} + +.timeline-fill--blue { + background: linear-gradient(90deg, rgba(96, 165, 250, 0.9), rgba(34, 211, 238, 0.6)); +} + +.timeline-fill--green { + background: linear-gradient(90deg, rgba(46, 198, 106, 0.9), rgba(96, 165, 250, 0.4)); +} + +.slot-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-2); +} + +@media (max-width: 1280px) { + .workspace--three, + .workspace--wide, + .workspace--focus { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .set-shell, + .page-shell { + padding: var(--space-4); + } + + .hero { + padding: var(--space-6); + } + + .stats-grid, + .route-grid, + .preview-grid, + .slot-grid { + grid-template-columns: 1fr; + } + + .timeline-row { + grid-template-columns: 1fr; + } +} diff --git a/mockups/yuli/set-3b/index.html b/mockups/yuli/set-3b/index.html new file mode 100644 index 0000000..6050e6a --- /dev/null +++ b/mockups/yuli/set-3b/index.html @@ -0,0 +1,71 @@ + + + + + + Spindle — Yuli Set 3B + + + + + + +
+
+
+
Yuli / Set 3B / Integrated Editor Concepts
+

Three tighter concepts where action binding, route logic, and DVD honesty live inside the main editing surface.

+

+ This set deliberately removes separate Bind and Remote views. Instead, the editor reveals those concepts inline: on-canvas trays, contextual strips, hover peeks, and persistent micro-audits. The goal is to keep the user in one primary scene-editing space while still making interaction logic explicit and trustworthy. +

+
+
Uses `apps/spindle/src/design-system.css`
+
No separate Bind or Remote tabs
+
Inline reveals for actions, routing, and compile safety
+
+
+ +
+
+
+
Concept 1
+
Selection trays
+
+

The Harbour

+

+ A scene editor where selecting a node opens a docked action tray directly beneath the canvas. Route targets, playback actions, and compile notes appear in one localised strip instead of moving the user to another mode. +

+ Open mockup +
+ +
+
+
Concept 2
+
On-canvas peeks
+
+

The Lantern

+

+ A quieter concept where behaviour mostly stays hidden until needed, then appears as contextual lantern cards beside the selected node: action summary, neighbour links, default focus, and warnings. +

+ Open mockup +
+ +
+
+
Concept 3
+
Persistent inline audit
+
+

The Junction

+

+ A denser workstation for power users where the main editor includes a permanent interaction rail: current focus path, action stack, generator lineage, and compile risk strip all visible without leaving the page. +

+ Open mockup +
+
+
+
+ + From e6aeb28769638b3af1e1895b151b5e6a57b9a542 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Mon, 6 Apr 2026 23:41:37 -0400 Subject: [PATCH 18/32] docs: add merged Yuli set 2m menu editor concept **What** - Add a new `set-2m` folder with a merged Greenhouse and Cartographer concept. - Combine the left-side menu and template rail with a content-area editor that can switch between Design Studio and Menu Map views. - Reframe templates as optional seeds while preserving full manual control over menu layout and linking. **Why** - Capture the strongest parts of concepts 2 and 3 in a more practical editor direction. - Keep menu switching fast inside the editor while giving users a clear way to inspect how menus connect. --- .../concept-1-the-conservatory-map.html | 292 +++++++++ mockups/yuli/set-2m/design-system.css | 588 ++++++++++++++++++ mockups/yuli/set-2m/index.html | 48 ++ 3 files changed, 928 insertions(+) create mode 100644 mockups/yuli/set-2m/concept-1-the-conservatory-map.html create mode 100644 mockups/yuli/set-2m/design-system.css create mode 100644 mockups/yuli/set-2m/index.html diff --git a/mockups/yuli/set-2m/concept-1-the-conservatory-map.html b/mockups/yuli/set-2m/concept-1-the-conservatory-map.html new file mode 100644 index 0000000..dcca4cb --- /dev/null +++ b/mockups/yuli/set-2m/concept-1-the-conservatory-map.html @@ -0,0 +1,292 @@ + + + + + + Spindle — The Conservatory Map + + + + +
+
+ + +
+
+ + + + + +
+
+
+ + +
+
Current menu: Main Menu
+
Template seed applied, then customised
+
Linked menus: 3
+
+ +
+
+
+
+
+
+
Action-safe
+
Title-safe
+ +
Wedding Highlights
+
Play feature
+
Scenes
+
Set-up
+
Audio menu
+
Subtitle menu
+ +
+
+ +
+
Template freedom
+
+ This menu began from a hero-title seed, but nodes have already been repositioned and new linked buttons added. The seed helped start the page; it does not own it. +
+
+
+ +
+
+
+
Selection strip
+

Scenes button

+
+
Route-aware
+
+
+
+ Action +
+
Show menu
+
Chapter Select A
+
+
+
+ Links +
+
Connected on map
+
2 return paths
+
+
+
+ Template status +
+
Seed-derived
+
Customised
+
+
+
+ Quick jump +
+ +
Open linked menu
+
+
+
+
+
+ +
+
+
+
+
Menu Map view
+

Same workspace, different purpose

+
+
Switch when tracing flows
+
+ +
+
+ + + + +
+
+
+
+
+
+ Main Menu +
Global scope · current page
+
+ +
+
+
+
+
+
+ Chapter Select A +
Titleset 1 · page 1 of 2
+
+ +
+
+
+
+
+
+ Chapter Select B +
Titleset 1 · page 2 of 2
+
+ +
+
+
+
+
+
+ Set-up Menu +
Audio and subtitle flows
+
+
+ +
+
+
+ Current selection +
+
Main Menu
+ +
+
+
+ Linked from current page +
+
Chapter Select A
+
Set-up Menu
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + diff --git a/mockups/yuli/set-2m/design-system.css b/mockups/yuli/set-2m/design-system.css new file mode 100644 index 0000000..464a55a --- /dev/null +++ b/mockups/yuli/set-2m/design-system.css @@ -0,0 +1,588 @@ +/* Shared support styles for Yuli set 2m. + * + * (c) Copyright 2026 Liminal HQ, Scott Morris + * SPDX-License-Identifier: MIT + */ + +@import url("../set-2/design-system.css"); + +html, +body { + overflow: auto; + background: var(--bg-app); +} + +body { + min-height: 100vh; +} + +.set-shell, +.page-shell { + min-height: 100vh; + padding: var(--space-6); +} + +.set-width, +.page-frame { + max-width: 1460px; + margin: 0 auto; +} + +.hero, +.surface, +.panel, +.card, +.artboard, +.map-card, +.mini-card, +.mode-panel, +.floating-card { + box-shadow: var(--shadow-panel); +} + +.hero { + background: linear-gradient(180deg, rgba(18, 18, 24, 0.96), rgba(12, 12, 18, 0.92)); + border: 1px solid var(--border-default); + border-radius: 24px; + padding: var(--space-8); + margin-bottom: var(--space-6); + position: relative; + overflow: hidden; +} + +.hero::after { + content: ''; + position: absolute; + width: 360px; + height: 360px; + right: -120px; + top: -120px; + background: radial-gradient(circle, rgba(34, 211, 238, 0.14), transparent 68%); + pointer-events: none; +} + +.eyebrow { + color: var(--brand-cyan); + font-size: var(--text-xs); + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; + margin-bottom: var(--space-3); +} + +.hero h1, +.page-title { + font-size: clamp(2rem, 4vw, 3.3rem); + line-height: 1.04; + max-width: 980px; + margin-bottom: var(--space-4); +} + +.hero p, +.page-copy, +.meta, +.quiet-copy, +.list-copy { + color: var(--text-secondary); + line-height: 1.72; +} + +.chip-row, +.page-nav, +.toolbar-row, +.status-row, +.button-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + align-items: center; +} + +.chip, +.badge, +.tiny-pill { + display: inline-flex; + align-items: center; + padding: 6px 10px; + border-radius: 999px; + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.04); + font-size: var(--text-xs); + color: var(--text-secondary); +} + +.badge--cyan { + color: var(--brand-cyan); + border-color: rgba(34, 211, 238, 0.22); + background: rgba(34, 211, 238, 0.08); +} + +.badge--orange { + color: var(--brand-orange); + border-color: rgba(255, 170, 64, 0.22); + background: rgba(255, 170, 64, 0.08); +} + +.badge--green { + color: var(--brand-green); + border-color: rgba(46, 198, 106, 0.18); + background: rgba(46, 198, 106, 0.08); +} + +.cta { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: 10px 14px; + border-radius: var(--radius-md); + border: 1px solid var(--border-default); + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); + font-weight: 600; + font-size: var(--text-sm); + text-decoration: none; +} + +.cta:hover { + text-decoration: none; + background: rgba(255, 255, 255, 0.08); +} + +.cta--primary { + background: linear-gradient(135deg, rgba(34, 211, 238, 0.18), rgba(167, 139, 250, 0.12)); + border-color: rgba(34, 211, 238, 0.24); + color: var(--brand-cyan); +} + +.concept-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: var(--space-4); +} + +.card, +.panel, +.surface { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); +} + +.card { + padding: var(--space-5); + display: flex; + flex-direction: column; + gap: var(--space-3); + min-height: 240px; +} + +.page-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: flex-end; + gap: var(--space-5); + margin-bottom: var(--space-6); +} + +.surface { + padding: var(--space-4); + border-radius: 18px; +} + +.workspace { + display: grid; + gap: var(--space-4); +} + +.workspace--merged { + grid-template-columns: 280px minmax(560px, 1fr) 340px; +} + +.panel { + padding: var(--space-4); +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-4); +} + +.panel-kicker { + color: var(--text-muted); + font-size: var(--text-xs); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.14em; + margin-bottom: 4px; +} + +.stack { + display: grid; + gap: var(--space-3); +} + +.list-item, +.mini-card, +.mode-panel, +.map-card, +.template-card, +.slot-card { + padding: var(--space-3); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.list-item--selected, +.mini-card--selected, +.mode-panel--selected, +.template-card--selected, +.map-card--selected { + border-color: rgba(34, 211, 238, 0.24); + background: rgba(34, 211, 238, 0.08); +} + +.list-item strong, +.mini-card strong, +.mode-panel strong, +.template-card strong, +.map-card strong, +.slot-card strong { + display: block; + font-size: var(--text-sm); + margin-bottom: 4px; +} + +.editor-area { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.mode-state { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.mode-switch { + display: flex; + background: rgba(0, 0, 0, 0.26); + border: 1px solid var(--border-subtle); + border-radius: 16px; + padding: 3px; + gap: 3px; +} + +.mode-switch__item { + padding: 6px 14px; + border-radius: 13px; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-muted); + background: transparent; + border: none; +} + +.mode-switch__item--active { + background: var(--brand-cyan); + color: var(--text-inverse); +} + +.mode-switch__label { + padding: 6px 14px; + border-radius: 13px; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-muted); + cursor: pointer; + transition: all var(--transition-fast); +} + +.mode-switch__label:hover { + color: var(--text-primary); +} + +.artboard { + position: relative; + aspect-ratio: 720 / 480; + background: + radial-gradient(circle at 50% 0%, rgba(34, 211, 238, 0.1), transparent 30%), + linear-gradient(145deg, #161624 0%, #0f0f17 48%, #09090d 100%); + overflow: hidden; +} + +.grid-overlay, +.safe-area, +.canvas-label, +.node, +.connector, +.floating-card, +.map-link { + position: absolute; +} + +.grid-overlay { + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px); + background-size: 8% 12%; +} + +.safe-area { + border: 1px dashed rgba(255, 255, 255, 0.12); +} + +.safe-area--action { + inset: 5%; + border-color: rgba(255, 255, 0, 0.18); +} + +.safe-area--title { + inset: 10%; + border-color: rgba(34, 211, 238, 0.18); +} + +.canvas-label { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted); +} + +.canvas-label--action { + top: 3%; + left: 5%; +} + +.canvas-label--title { + top: 8%; + left: 10%; +} + +.node { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: var(--space-2); + background: rgba(17, 17, 22, 0.82); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: var(--text-sm); + font-weight: 600; +} + +.node--focus { + border-color: var(--brand-cyan); + box-shadow: + 0 0 0 1px rgba(34, 211, 238, 0.28), + 0 0 18px rgba(34, 211, 238, 0.15); +} + +.node--orange { + border-color: rgba(255, 170, 64, 0.28); + background: rgba(255, 170, 64, 0.08); +} + +.node--green { + border-color: rgba(46, 198, 106, 0.28); + background: rgba(46, 198, 106, 0.08); +} + +.node--purple { + border-color: rgba(167, 139, 250, 0.28); + background: rgba(167, 139, 250, 0.08); +} + +.connector, +.map-link { + height: 2px; + background: linear-gradient(90deg, rgba(34, 211, 238, 0.55), transparent); + transform-origin: left center; +} + +.connector--green, +.map-link--green { + background: linear-gradient(90deg, rgba(46, 198, 106, 0.55), transparent); +} + +.connector--purple, +.map-link--purple { + background: linear-gradient(90deg, rgba(167, 139, 250, 0.55), transparent); +} + +.floating-card { + padding: var(--space-3); + border-radius: var(--radius-md); + background: rgba(12, 12, 18, 0.9); + border: 1px solid var(--border-subtle); + max-width: 260px; +} + +.inline-strip, +.map-strip { + background: rgba(0, 0, 0, 0.18); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-3) var(--space-4); +} + +.slot-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-3); +} + +.slot-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-2); +} + +.action-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 999px; + font-size: var(--text-xs); + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); +} + +.action-chip--cyan { + color: var(--brand-cyan); + border-color: rgba(34, 211, 238, 0.22); + background: rgba(34, 211, 238, 0.08); +} + +.action-chip--green { + color: var(--brand-green); + border-color: rgba(46, 198, 106, 0.18); + background: rgba(46, 198, 106, 0.08); +} + +.action-chip--orange { + color: var(--brand-orange); + border-color: rgba(255, 170, 64, 0.22); + background: rgba(255, 170, 64, 0.08); +} + +.map-surface { + position: relative; + min-height: 520px; + border-radius: var(--radius-lg); + border: 1px solid var(--border-subtle); + background: + radial-gradient(circle at 30% 40%, rgba(34, 211, 238, 0.04) 0%, transparent 40%), + radial-gradient(circle at 70% 60%, rgba(167, 139, 250, 0.04) 0%, transparent 40%), + #09090e; + overflow: hidden; +} + +.map-grid { + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px); + background-size: 44px 44px; +} + +.map-card { + position: absolute; + width: 190px; + background: rgba(16, 16, 22, 0.92); +} + +.map-preview { + height: 92px; + border-radius: var(--radius-sm); + background: linear-gradient(135deg, #1a1828 0%, #0f0e1a 100%); + position: relative; + overflow: hidden; + margin-bottom: var(--space-2); +} + +.map-preview__btn { + position: absolute; + height: 8px; + border-radius: 2px; + background: rgba(255, 255, 255, 0.12); +} + +.template-grid { + display: grid; + gap: var(--space-3); +} + +.switch-stage { + display: grid; + gap: var(--space-4); +} + +.switch-panel { + display: none; +} + +#mode-design:checked ~ .editor-area .switch-panel--design { + display: grid; +} + +#mode-map:checked ~ .editor-area .switch-panel--map { + display: grid; +} + +#mode-design:checked ~ .editor-area .mode-switch__label[for='mode-design'], +#mode-map:checked ~ .editor-area .mode-switch__label[for='mode-map'] { + background: var(--brand-cyan); + color: var(--text-inverse); +} + +.template-preview { + height: 74px; + border-radius: var(--radius-sm); + background: linear-gradient(135deg, #1a1828 0%, #0f0e1a 100%); + position: relative; + overflow: hidden; + margin-bottom: var(--space-2); +} + +.template-preview__btn { + position: absolute; + height: 8px; + border-radius: 2px; + background: rgba(255, 255, 255, 0.12); +} + +@media (max-width: 1280px) { + .workspace--merged { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .set-shell, + .page-shell { + padding: var(--space-4); + } + + .hero { + padding: var(--space-6); + } + + .slot-grid { + grid-template-columns: 1fr; + } +} diff --git a/mockups/yuli/set-2m/index.html b/mockups/yuli/set-2m/index.html new file mode 100644 index 0000000..b4d28d4 --- /dev/null +++ b/mockups/yuli/set-2m/index.html @@ -0,0 +1,48 @@ + + + + + + Spindle — Yuli Set 2M + + + + +
+
+
+
Yuli / Set 2M / Greenhouse × Cartographer
+

A merged editor that combines the Greenhouse’s guided library with the Cartographer’s menu-link awareness, while keeping authors fully in control.

+

+ This set takes the left-side structure and softer onboarding from Concept 2, folds in the linked-menu visibility from Concept 3, and removes the sense that templates are the whole story. Templates become optional starting points inside a richer menu editor where users can freely design, switch between menus, and inspect how their pages connect. +

+
+
Content-area only, no full app chrome
+
Template-assisted, not template-driven
+
Inline menu switching and map awareness
+
+
+ +
+
+
+
Merged concept
+
Studio + map
+
+

The Conservatory Map

+

+ A single rich editor where the left rail shows all menus, a template and component library stays available as a helper, and the centre workspace can switch between a freeform design studio and a linked-menu map depending on the task. +

+

+ This keeps fast menu switching close at hand, surfaces menu relationships when needed, and avoids forcing the user into a template-first workflow. +

+ Open merged mockup +
+
+
+
+ + From eee146e553f568e06303ec8ac7339976bdbc8c40 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 00:01:41 -0400 Subject: [PATCH 19/32] docs: add Concept 5 The Switchboard action binding workspace A visual wiring board for connecting buttons to actions across all menus. Left panel lists all interactive nodes grouped by menu. Central viewport shows source-to-target wires with colour-coded action types (playTitle, showMenu, setAudio, sequence). Right inspector provides action builder with sequence chaining for setup menus, navigation grid, and live DVD command preview showing compiled VM instructions. Co-Authored-By: Claude Opus 4.6 --- .../yuli/set-2/concept-5-the-switchboard.html | 931 ++++++++++++++++++ 1 file changed, 931 insertions(+) create mode 100644 mockups/yuli/set-2/concept-5-the-switchboard.html diff --git a/mockups/yuli/set-2/concept-5-the-switchboard.html b/mockups/yuli/set-2/concept-5-the-switchboard.html new file mode 100644 index 0000000..226ac3f --- /dev/null +++ b/mockups/yuli/set-2/concept-5-the-switchboard.html @@ -0,0 +1,931 @@ + + + + + + Spindle — Concept 5: The Switchboard + + + + + + +
+ +
/ Documentary Collection / Bind Mode
+
+ + + +
+
+ +
+ +
+
+
Buttons
+
All interactive nodes across menus
+
+ +
+
By Menu
+
By Action
+
Unbound
+
+ +
+ +
+
+ Main Menu + VMGM +
+ +
+
+
+
Play Documentary
+
playTitle 1
+
+
+
+ +
+
+
+
Chapter Select
+
showMenu "Chapters"
+
+
+
+ +
+
+
+
Audio & Subtitles
+
showMenu "Audio Setup"
+
+
+
+
+ + +
+
+ Audio Setup + VTS 1 +
+ +
+
+
+
English 5.1
+
setAudio 0
+
+
+
+ +
+
+
+
Commentary
+
setAudio 1
+
+
+
+ +
+
+
+
English Subs
+
No action
+
+
+
+ +
+
+
+
Back
+
showMenu "Main Menu"
+
+
+
+
+
+
+ + +
+
+
Wiring Board
+ Drag to connect buttons to targets + + 7 of 8 bound +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Play Documentary +
+
+ +
+
+ Chapter Select +
+
+ +
+
+ Audio & Subtitles +
+
+ +
+
+ English 5.1 +
+
+ +
+
+ Commentary +
+
+ +
+
+ English Subs +
+
+ +
+
+ Back +
+
+ + +
+
+ Title + Main Documentary (8 chapters) +
+ +
+
+ Menu + Chapter Select +
+ +
+
+ Menu + Audio Setup +
+ +
+
+ Seq + setAudio 0 + showMenu "Main" +
+ +
+
+ Audio + Set Audio Stream 1 +
+ +
+
+ Sub + Set Subtitle Stream 0 +
+ +
+
+ Menu + Main Menu +
+ + +
+
Action Bindings
+
+
+
+ + +
+
+
Play Documentary
+
Main Menu / VMGM / Default Focus
+
+ + +
+
Action Binding
+
+
+ + + +
+
+
+ + +
+
+
+ + Sequence Actions + For Setup Menus +
+
+ Chain multiple actions on a single button press. Used for Audio/Subtitle setup menus. +
+
+
1
+ setAudioStream(0) + English 5.1 +
+
+
+
2
+ showMenu("Main Menu") + Return to main +
+ +
+
+ + +
+
Remote Navigation
+ +
+ Auto-Generated +
+
+ + +
+
DVD Command Preview
+
+
// VMGM Menu PGC, button 1
+
jump title 1;
+
// Compiled from:
+
playTitle("Main Documentary")
+
+
+ + +
+
Diagnostics
+
+ + Action target resolves to a valid title in the VMGM domain. +
+
+ + "English Subs" button has no action binding. It will be non-functional on the disc. +
+
+
+
+ +
+ + Switchboard / Bind Mode + | + 8 buttons across 2 menus + | + 1 unbound + + DVD 4:3 NTSC +
+ + From c184c66ca669a1ff3d3f1f24fcb5d99e92ae0960 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 00:02:38 -0400 Subject: [PATCH 20/32] docs: add index page for Yuli's set-2 menu editor concepts Links to all 5 concepts with descriptions, feature tags, and navigation back to set-1. Co-Authored-By: Claude Opus 4.6 --- mockups/yuli/set-2/index.html | 219 ++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 mockups/yuli/set-2/index.html diff --git a/mockups/yuli/set-2/index.html b/mockups/yuli/set-2/index.html new file mode 100644 index 0000000..5e73543 --- /dev/null +++ b/mockups/yuli/set-2/index.html @@ -0,0 +1,219 @@ + + + + + + Spindle — Yuli's Menu Editor Concepts (Set 2) + + + + + +
+ + + Back to Set 1 + + +
+

Spindle Menu Editor Concepts — Set 2

+

Where Set 1 explored individual ideas like gallery exhibits, Set 2 assembles those ideas into complete, integrated workspaces. Each concept is a holistic vision for how the full menu editor could work.

+
+ 5 concepts covering all spec features: scene documents, interaction graphs, themes, components, motion, compile preview, and diagnostics. +
+
+ +
+
+ Concept 1: The Waystation +
Unified Workspace
+
+
+ Like an airport terminal with a central concourse and branching gates. A four-pane layout with layers and components on the left, a central canvas with mode switcher (Design / Bind / Route / Compile), an adaptive inspector on the right, and a collapsible motion timeline at the bottom. Everything accessible, nothing hidden. +
+
+ Scene Graph + Inspector + Components + Timeline + Safe Areas + Mode Switcher +
+ Open Concept 1 +
+ +
+
+ Concept 2: The Greenhouse +
Template-First Progressive
+
+
+ A growth-oriented editor inspired by botanical gardens. Start with a seed template (Classic Main Menu, Chapter Grid, Audio Setup, Poster Shelf, or Blank Canvas), choose a theme, and the interface progressively unfurls to reveal layout customisation, action binding, motion options, and compile settings. Each step unlocks the next — beginners are guided, experts can skip ahead. +
+
+ Templates + Themes + Action Binding + Progressive Disclosure + Auto-Pagination + Diagnostics +
+ Open Concept 2 +
+ +
+
+ Concept 3: The Cartographer +
Multi-Menu Navigator
+
+
+ A bird's-eye transit map of the entire disc's menu system. Every menu is a card on the map with a thumbnail preview, every connection is a colour-coded line (showMenu, playTitle, return). Click any menu to see its details, buttons, and diagnostics in the right panel. Perfect for understanding and managing complex multi-menu, multi-titleset projects with auto-paginated chapter menus. +
+
+ Navigation Map + VMGM / VTS Domains + Auto-Pagination + Route Diagnostics + Menu Thumbnails +
+ Open Concept 3 +
+ +
+
+ Concept 4: The Darkroom +
State-Focused Editor
+
+
+ Like a photographer's darkroom where you control every exposure. A triple-canvas view showing Normal, Focus, and Activate states side by side for any selected button. The left panel exposes the DVD 4-colour CLUT palette. The right inspector lets you edit per-state visual treatments and shows exactly how authored styles (gradients, glows, rounded corners) compile down into DVD subpicture overlays. Includes a highlight animation keyframe editor for motion menus. +
+
+ Normal / Focus / Activate + DVD Overlay Mapping + CLUT Palette Editor + Highlight Animation + Downgrade Preview +
+ Open Concept 4 +
+ +
+
+ Concept 5: The Switchboard +
Action Binding Workspace
+
+
+ Like an old-fashioned telephone switchboard where operators physically connected calls. A visual wiring board showing all buttons on the left, all targets (titles, menus, audio streams, subtitle streams) on the right, with colour-coded wires connecting them. The right inspector provides an action builder with sequence chaining (for setup menus that need setAudio + showMenu in one press), a navigation grid, and a live DVD VM command preview showing the compiled instructions. +
+
+ Visual Wiring + Sequence Actions + setAudio / setSubtitle + DVD Command Preview + Navigation Grid + Cross-Menu Binding +
+ Open Concept 5 +
+
+ + From c1a74e48ded26bc2be3048d032efb8a90186ac43 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 00:12:11 -0400 Subject: [PATCH 21/32] docs: add set-2a blended Menu Editor mockup Content-area-only editor blending Concept 2's template-first approach with Concept 3's navigation map into a unified workspace. Features: - Left panel: scope-grouped menu list with thumbnails, mini navigation map showing menu connections, and collapsible template picker - Canvas viewport with draggable button nodes, resize handles, safe area guides, and floating tool palette (Select/Text/Button/Image) - Mode switcher: Design / Bind / Route / Compile with route overlay - Toggleable inspector: transform, visual states (Normal/Focus/Activate), action binding, navigation directions, diagnostics - Full JavaScript interactivity: drag-to-move with live inspector sync, keyboard shortcuts (V/T/B/I/S/Escape), state chip feedback on canvas Co-Authored-By: Claude Opus 4.6 --- mockups/yuli/set-2a/design-system.css | 833 +++++++++++++++ mockups/yuli/set-2a/menu-editor.html | 1419 +++++++++++++++++++++++++ 2 files changed, 2252 insertions(+) create mode 100644 mockups/yuli/set-2a/design-system.css create mode 100644 mockups/yuli/set-2a/menu-editor.html diff --git a/mockups/yuli/set-2a/design-system.css b/mockups/yuli/set-2a/design-system.css new file mode 100644 index 0000000..c3bcc9e --- /dev/null +++ b/mockups/yuli/set-2a/design-system.css @@ -0,0 +1,833 @@ +/* ============================================================ + Liminal Spindle — Design System + Desktop Authoring Workstation · Dark Mode + + Colour palette drawn from Liminal HQ brand identity: + liminalhq.ca, smdu, Flow, Threshold, Emoji Nook + ============================================================ */ + +/* --- Reset & Base ------------------------------------------ */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + /* ── Brand Colours ─────────────────────────────────────── */ + --brand-orange: #ffaa40; + --brand-pink: #f43f5e; + --brand-purple: #a78bfa; + --brand-cyan: #22d3ee; + --brand-blue: #60a5fa; + --brand-green: #2ec66a; + --brand-amber: #fbbf24; + + /* ── Brand Gradient (hero / accent) ────────────────────── */ + --brand-gradient: linear-gradient(135deg, #ffaa40, #f43f5e, #a78bfa); + + /* ── Surfaces ──────────────────────────────────────────── */ + --bg-app: #050507; + --bg-sidebar: #0c0c10; + --bg-panel: #111116; + --bg-card: rgba(20, 20, 25, 0.55); + --bg-card-hover: rgba(30, 30, 38, 0.7); + --bg-input: rgba(20, 20, 28, 0.6); + --bg-modal: #16161c; + --bg-tooltip: #1e1e26; + + /* ── Borders ───────────────────────────────────────────── */ + --border-subtle: rgba(255, 255, 255, 0.08); + --border-default: rgba(255, 255, 255, 0.14); + --border-strong: rgba(255, 255, 255, 0.22); + --border-focus: #ffaa40; + + /* ── Text ──────────────────────────────────────────────── */ + --text-primary: #e0e0e0; + --text-secondary: #b4bfce; + --text-muted: #7c8796; + --text-inverse: #0a0a0c; + + /* ── Semantic Colours ──────────────────────────────────── */ + --colour-success: #2ec66a; + --colour-warning: #fbbf24; + --colour-error: #f43f5e; + --colour-info: #60a5fa; + + /* ── Compatibility Badges (from SPEC) ──────────────────── */ + --badge-remux: #2ec66a; + --badge-light: #60a5fa; + --badge-reencode: #fbbf24; + --badge-unsupported: #f43f5e; + + /* ── File-Type Colours (from smdu) ─────────────────────── */ + --file-media: #ffb454; + --file-text: #b0f2c2; + --file-docs: #ffd166; + --file-code: #7dd3fc; + --file-archive: #c4a7e7; + + /* ── Typography ────────────────────────────────────────── */ + --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-heading: 'Space Grotesk', var(--font-body); + --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; + + --text-xs: 0.6875rem; /* 11px */ + --text-sm: 0.75rem; /* 12px */ + --text-base: 0.8125rem; /* 13px */ + --text-md: 0.875rem; /* 14px */ + --text-lg: 1rem; /* 16px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + + /* ── Spacing ───────────────────────────────────────────── */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* ── Radii ─────────────────────────────────────────────── */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 20px; + + /* ── Shadows ───────────────────────────────────────────── */ + --shadow-card: 0 2px 12px rgba(0, 0, 0, 0.35); + --shadow-panel: 0 4px 24px rgba(0, 0, 0, 0.45); + --shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.6); + --shadow-glow: 0 0 20px rgba(255, 170, 64, 0.15); + + /* ── Layout ────────────────────────────────────────────── */ + --sidebar-width: 220px; + --topbar-height: 40px; + --statusbar-height: 28px; + --panel-gap: 1px; + + /* ── Transitions ───────────────────────────────────────── */ + --transition-fast: 0.15s ease; + --transition-base: 0.25s ease; + --transition-slow: 0.4s ease; +} + +/* --- Animations -------------------------------------------- */ +@keyframes portal-pulse { + 0%, + 100% { + opacity: 0.4; + } + 50% { + opacity: 1; + } +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes progress-stripe { + 0% { + background-position: 0 0; + } + 100% { + background-position: 40px 0; + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* --- Base Styles ------------------------------------------- */ +html, +body { + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.5; + color: var(--text-primary); + background: var(--bg-app); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, +h2, +h3, +h4 { + font-family: var(--font-heading); + font-weight: 600; + letter-spacing: -0.01em; +} + +h1 { + font-size: var(--text-2xl); +} +h2 { + font-size: var(--text-xl); +} +h3 { + font-size: var(--text-lg); +} +h4 { + font-size: var(--text-md); +} + +a { + color: var(--brand-blue); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +code, +pre { + font-family: var(--font-mono); + font-size: var(--text-sm); +} + +/* --- Utility Classes --------------------------------------- */ +.text-muted { + color: var(--text-muted); +} +.text-secondary { + color: var(--text-secondary); +} +.text-gradient { + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.mono { + font-family: var(--font-mono); +} +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* --- Component: App Shell ---------------------------------- */ +.app-shell { + display: grid; + grid-template-rows: var(--topbar-height) 1fr var(--statusbar-height); + grid-template-columns: var(--sidebar-width) 1fr; + grid-template-areas: + 'topbar topbar' + 'sidebar main' + 'status status'; + height: 100vh; + overflow: hidden; +} + +.topbar { + grid-area: topbar; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-bottom: 1px solid var(--border-subtle); + gap: var(--space-3); + -webkit-app-region: drag; +} + +.topbar__logo { + display: flex; + align-items: center; + gap: var(--space-2); + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 700; + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.topbar__actions { + margin-left: auto; + display: flex; + align-items: center; + gap: var(--space-2); + -webkit-app-region: no-drag; +} + +.sidebar { + grid-area: sidebar; + background: var(--bg-sidebar); + border-right: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + overflow-y: auto; + padding: var(--space-3) 0; +} + +.sidebar__section { + padding: var(--space-2) var(--space-4); +} + +.sidebar__label { + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); + margin-bottom: var(--space-2); +} + +.sidebar__item { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-1) var(--space-4); + font-size: var(--text-sm); + color: var(--text-secondary); + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-fast); + margin-bottom: 1px; +} + +.sidebar__item:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.sidebar__item--active { + background: rgba(255, 170, 64, 0.1); + color: var(--brand-orange); + font-weight: 500; +} + +.sidebar__item__icon { + width: 16px; + height: 16px; + opacity: 0.7; + flex-shrink: 0; +} + +.sidebar__item--active .sidebar__item__icon { + opacity: 1; +} + +.sidebar__item__badge { + margin-left: auto; + font-size: var(--text-xs); + padding: 1px 6px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.08); + color: var(--text-muted); +} + +.main-content { + grid-area: main; + overflow-y: auto; + padding: var(--space-6); + animation: fade-in 0.2s ease; +} + +.statusbar { + grid-area: status; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-top: 1px solid var(--border-subtle); + font-size: var(--text-xs); + color: var(--text-muted); + gap: var(--space-4); +} + +.statusbar__segment { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.statusbar__dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--colour-success); +} + +.statusbar__dot--warning { + background: var(--colour-warning); +} +.statusbar__dot--error { + background: var(--colour-error); +} + +/* --- Component: Cards -------------------------------------- */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-5); + transition: all var(--transition-base); +} + +.card:hover { + background: var(--bg-card-hover); + border-color: var(--border-default); + box-shadow: var(--shadow-card); +} + +.card--glow:hover { + box-shadow: var(--shadow-glow); + border-color: rgba(255, 170, 64, 0.2); +} + +.card__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-3); +} + +.card__title { + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 600; +} + +/* --- Component: Buttons ------------------------------------ */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: 500; + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + background: var(--bg-card); + color: var(--text-primary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn:hover { + background: var(--bg-card-hover); + border-color: var(--border-strong); +} + +.btn--primary { + background: var(--brand-orange); + border-color: var(--brand-orange); + color: var(--text-inverse); +} + +.btn--primary:hover { + background: #e89930; + box-shadow: 0 0 16px rgba(255, 170, 64, 0.3); +} + +.btn--danger { + border-color: var(--colour-error); + color: var(--colour-error); +} + +.btn--ghost { + background: transparent; + border-color: transparent; + color: var(--text-secondary); +} + +.btn--ghost:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.btn--sm { + padding: 2px var(--space-2); + font-size: var(--text-xs); +} + +/* --- Component: Badges ------------------------------------- */ +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + font-weight: 600; + border-radius: 10px; + letter-spacing: 0.02em; +} + +.badge--remux { + background: rgba(46, 198, 106, 0.15); + color: var(--badge-remux); +} +.badge--light { + background: rgba(96, 165, 250, 0.15); + color: var(--badge-light); +} +.badge--reencode { + background: rgba(251, 191, 36, 0.15); + color: var(--badge-reencode); +} +.badge--unsupported { + background: rgba(244, 63, 94, 0.15); + color: var(--badge-unsupported); +} +.badge--neutral { + background: rgba(255, 255, 255, 0.06); + color: var(--text-muted); +} + +/* --- Component: Progress Bar ------------------------------- */ +.progress { + height: 6px; + background: rgba(255, 255, 255, 0.06); + border-radius: 3px; + overflow: hidden; +} + +.progress__fill { + height: 100%; + border-radius: 3px; + transition: width var(--transition-base); +} + +.progress__fill--ok { + background: var(--brand-gradient); +} +.progress__fill--warning { + background: var(--colour-warning); +} +.progress__fill--error { + background: var(--colour-error); +} + +.progress--striped .progress__fill { + background-image: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + rgba(255, 255, 255, 0.08) 10px, + rgba(255, 255, 255, 0.08) 20px + ); + background-size: 40px 40px; + animation: progress-stripe 1s linear infinite; +} + +/* --- Component: Table -------------------------------------- */ +.table-wrap { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); +} + +th { + text-align: left; + padding: var(--space-2) var(--space-3); + font-weight: 600; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + border-bottom: 1px solid var(--border-default); +} + +td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--border-subtle); + color: var(--text-secondary); +} + +tr:hover td { + background: rgba(255, 255, 255, 0.02); +} + +/* --- Component: Input / Form ------------------------------- */ +.input { + padding: var(--space-2) var(--space-3); + font-family: var(--font-body); + font-size: var(--text-sm); + background: var(--bg-input); + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + color: var(--text-primary); + transition: border-color var(--transition-fast); + width: 100%; +} + +.input:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 2px rgba(255, 170, 64, 0.15); +} + +.input--select { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%237c8796' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 28px; +} + +.form-group { + margin-bottom: var(--space-4); +} + +.form-label { + display: block; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: var(--space-1); +} + +/* --- Component: Tabs --------------------------------------- */ +.tabs { + display: flex; + gap: var(--space-1); + border-bottom: 1px solid var(--border-subtle); + margin-bottom: var(--space-5); +} + +.tab { + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + font-weight: 500; + color: var(--text-muted); + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all var(--transition-fast); +} + +.tab:hover { + color: var(--text-primary); +} + +.tab--active { + color: var(--brand-orange); + border-bottom-color: var(--brand-orange); +} + +/* --- Component: Tag / Chip --------------------------------- */ +.tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + border-radius: var(--radius-sm); + background: rgba(255, 255, 255, 0.06); + color: var(--text-secondary); + border: 1px solid var(--border-subtle); +} + +/* --- Component: Timeline ----------------------------------- */ +.timeline { + position: relative; + height: 48px; + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + overflow: hidden; +} + +.timeline__track { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 4px; + background: rgba(255, 255, 255, 0.08); + transform: translateY(-50%); +} + +.timeline__marker { + position: absolute; + top: 4px; + bottom: 4px; + width: 2px; + background: var(--brand-orange); + border-radius: 1px; +} + +.timeline__marker::after { + content: attr(data-label); + position: absolute; + top: -2px; + left: 6px; + font-size: var(--text-xs); + color: var(--text-muted); + white-space: nowrap; +} + +/* --- Component: Modal / Dialog ----------------------------- */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal { + background: var(--bg-modal); + border: 1px solid var(--border-default); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-modal); + padding: var(--space-8); + max-width: 560px; + width: 90%; + animation: fade-in 0.2s ease; +} + +/* --- Component: Capacity Bar ------------------------------- */ +.capacity-bar { + display: flex; + height: 28px; + border-radius: var(--radius-md); + overflow: hidden; + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.03); +} + +.capacity-bar__segment { + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-inverse); + transition: width var(--transition-base); + min-width: 2px; +} + +/* --- Layout Helpers ---------------------------------------- */ +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-6); +} + +.page-title { + font-family: var(--font-heading); + font-size: var(--text-xl); + font-weight: 700; +} + +.grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); +} +.grid-3 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--space-4); +} +.grid-4 { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: var(--space-4); +} + +.flex { + display: flex; +} +.flex-col { + flex-direction: column; +} +.items-center { + align-items: center; +} +.justify-between { + justify-content: space-between; +} +.gap-1 { + gap: var(--space-1); +} +.gap-2 { + gap: var(--space-2); +} +.gap-3 { + gap: var(--space-3); +} +.gap-4 { + gap: var(--space-4); +} + +.mt-2 { + margin-top: var(--space-2); +} +.mt-4 { + margin-top: var(--space-4); +} +.mt-6 { + margin-top: var(--space-6); +} +.mb-2 { + margin-bottom: var(--space-2); +} +.mb-4 { + margin-bottom: var(--space-4); +} + +.w-full { + width: 100%; +} + +/* --- Scrollbar Styling ------------------------------------- */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.18); +} diff --git a/mockups/yuli/set-2a/menu-editor.html b/mockups/yuli/set-2a/menu-editor.html new file mode 100644 index 0000000..75c9b83 --- /dev/null +++ b/mockups/yuli/set-2a/menu-editor.html @@ -0,0 +1,1419 @@ + + + + + + Spindle — Menu Editor (Blended Concept) + + + + + + + + + + From 9d27ec9ff78ac521668c45327f5e63cdd9abc2d0 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 00:13:18 -0400 Subject: [PATCH 22/32] docs: add index page for set-2a mockups Links to the blended Greenhouse + Cartographer menu editor with description of the blending philosophy and feature overview. Co-Authored-By: Claude Opus 4.6 --- mockups/yuli/set-2a/index.html | 180 +++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 mockups/yuli/set-2a/index.html diff --git a/mockups/yuli/set-2a/index.html b/mockups/yuli/set-2a/index.html new file mode 100644 index 0000000..583210f --- /dev/null +++ b/mockups/yuli/set-2a/index.html @@ -0,0 +1,180 @@ + + + + + + Spindle — Menu Editor Concepts (Set 2a) + + + + + +
+ + + Back to Set 2 + + +
+

Spindle Menu Editor — Set 2a

+

A blended concept that takes the best ideas from Set 2's Greenhouse and Cartographer and merges them into a single, interactive content-area editor. Designed to sit inside the existing Spindle app shell.

+
+ Content-area mockup only — no app chrome. Fits inside <main class="main-content"> in the Spindle UI. +
+
+ +
+ Blending philosophy: The Greenhouse's template-first onboarding and progressive customisation is preserved as an optional, collapsible panel — but the editor gives users full creative control from the start. The Cartographer's bird's-eye navigation map becomes a persistent mini-map in the left panel, showing how all menus connect and allowing quick jumps between them. +
+ +
+
+ Menu Editor +
Blended Workspace
+
+
+ From Greenhouse: Templates + Progressive Disclosure + From Cartographer: Navigation Map + Menu Jumping +
+
+ A unified menu editor content area with three zones: a left panel showing all menus grouped by scope (VMGM / VTS) with thumbnails, a persistent mini navigation map, and a collapsible template picker; a central canvas with a floating tool palette and draggable button nodes; and a toggleable inspector for transform, visual states, action binding, navigation, and diagnostics. Four editor modes (Design, Bind, Route, Compile) change the canvas overlay and inspector context. Fully interactive with drag-to-move, live inspector sync, keyboard shortcuts, and mode-sensitive overlays. +
+
+ Scope-Grouped Menu List + Mini Navigation Map + Templates (Optional) + 4 Editor Modes + Draggable Canvas + Safe Area Guides + Visual State Preview + Route Overlay + Live Diagnostics +
+ Open Menu Editor +
+
+ + From c1b162bbd07b24a83f313774e4e9b783ba12e639 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 10:12:04 -0400 Subject: [PATCH 23/32] feat: add set-2b unified editor with inline binding, preview, and nav map Refines set-2a by removing Bind and Route as separate modes: - Action binding is now an always-visible inspector section with inline type/target selects and action badges on canvas buttons - Navigation arrows are a canvas toggle (N key) instead of a mode - Compile becomes a Preview overlay showing DVD output stats (button count, CLUT usage, action resolution, safe area compliance) - Left nav menu items show incoming/outgoing connection counts; inspector has a Connections section with click-to-jump links - Editor/Map toggle provides a full bird's-eye navigation map view with colour-coded connection lines (`showMenu`, `playTitle`, `return`, `setAudio`) and a connection-focused inspector - Double-click map cards to jump into the editor for that menu Co-Authored-By: Claude Opus 4.6 --- mockups/yuli/set-2b/design-system.css | 833 ++++++++++ mockups/yuli/set-2b/index.html | 211 +++ mockups/yuli/set-2b/menu-editor.html | 2043 +++++++++++++++++++++++++ 3 files changed, 3087 insertions(+) create mode 100644 mockups/yuli/set-2b/design-system.css create mode 100644 mockups/yuli/set-2b/index.html create mode 100644 mockups/yuli/set-2b/menu-editor.html diff --git a/mockups/yuli/set-2b/design-system.css b/mockups/yuli/set-2b/design-system.css new file mode 100644 index 0000000..c3bcc9e --- /dev/null +++ b/mockups/yuli/set-2b/design-system.css @@ -0,0 +1,833 @@ +/* ============================================================ + Liminal Spindle — Design System + Desktop Authoring Workstation · Dark Mode + + Colour palette drawn from Liminal HQ brand identity: + liminalhq.ca, smdu, Flow, Threshold, Emoji Nook + ============================================================ */ + +/* --- Reset & Base ------------------------------------------ */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + /* ── Brand Colours ─────────────────────────────────────── */ + --brand-orange: #ffaa40; + --brand-pink: #f43f5e; + --brand-purple: #a78bfa; + --brand-cyan: #22d3ee; + --brand-blue: #60a5fa; + --brand-green: #2ec66a; + --brand-amber: #fbbf24; + + /* ── Brand Gradient (hero / accent) ────────────────────── */ + --brand-gradient: linear-gradient(135deg, #ffaa40, #f43f5e, #a78bfa); + + /* ── Surfaces ──────────────────────────────────────────── */ + --bg-app: #050507; + --bg-sidebar: #0c0c10; + --bg-panel: #111116; + --bg-card: rgba(20, 20, 25, 0.55); + --bg-card-hover: rgba(30, 30, 38, 0.7); + --bg-input: rgba(20, 20, 28, 0.6); + --bg-modal: #16161c; + --bg-tooltip: #1e1e26; + + /* ── Borders ───────────────────────────────────────────── */ + --border-subtle: rgba(255, 255, 255, 0.08); + --border-default: rgba(255, 255, 255, 0.14); + --border-strong: rgba(255, 255, 255, 0.22); + --border-focus: #ffaa40; + + /* ── Text ──────────────────────────────────────────────── */ + --text-primary: #e0e0e0; + --text-secondary: #b4bfce; + --text-muted: #7c8796; + --text-inverse: #0a0a0c; + + /* ── Semantic Colours ──────────────────────────────────── */ + --colour-success: #2ec66a; + --colour-warning: #fbbf24; + --colour-error: #f43f5e; + --colour-info: #60a5fa; + + /* ── Compatibility Badges (from SPEC) ──────────────────── */ + --badge-remux: #2ec66a; + --badge-light: #60a5fa; + --badge-reencode: #fbbf24; + --badge-unsupported: #f43f5e; + + /* ── File-Type Colours (from smdu) ─────────────────────── */ + --file-media: #ffb454; + --file-text: #b0f2c2; + --file-docs: #ffd166; + --file-code: #7dd3fc; + --file-archive: #c4a7e7; + + /* ── Typography ────────────────────────────────────────── */ + --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-heading: 'Space Grotesk', var(--font-body); + --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; + + --text-xs: 0.6875rem; /* 11px */ + --text-sm: 0.75rem; /* 12px */ + --text-base: 0.8125rem; /* 13px */ + --text-md: 0.875rem; /* 14px */ + --text-lg: 1rem; /* 16px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + + /* ── Spacing ───────────────────────────────────────────── */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* ── Radii ─────────────────────────────────────────────── */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 20px; + + /* ── Shadows ───────────────────────────────────────────── */ + --shadow-card: 0 2px 12px rgba(0, 0, 0, 0.35); + --shadow-panel: 0 4px 24px rgba(0, 0, 0, 0.45); + --shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.6); + --shadow-glow: 0 0 20px rgba(255, 170, 64, 0.15); + + /* ── Layout ────────────────────────────────────────────── */ + --sidebar-width: 220px; + --topbar-height: 40px; + --statusbar-height: 28px; + --panel-gap: 1px; + + /* ── Transitions ───────────────────────────────────────── */ + --transition-fast: 0.15s ease; + --transition-base: 0.25s ease; + --transition-slow: 0.4s ease; +} + +/* --- Animations -------------------------------------------- */ +@keyframes portal-pulse { + 0%, + 100% { + opacity: 0.4; + } + 50% { + opacity: 1; + } +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes progress-stripe { + 0% { + background-position: 0 0; + } + 100% { + background-position: 40px 0; + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* --- Base Styles ------------------------------------------- */ +html, +body { + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.5; + color: var(--text-primary); + background: var(--bg-app); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1, +h2, +h3, +h4 { + font-family: var(--font-heading); + font-weight: 600; + letter-spacing: -0.01em; +} + +h1 { + font-size: var(--text-2xl); +} +h2 { + font-size: var(--text-xl); +} +h3 { + font-size: var(--text-lg); +} +h4 { + font-size: var(--text-md); +} + +a { + color: var(--brand-blue); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +code, +pre { + font-family: var(--font-mono); + font-size: var(--text-sm); +} + +/* --- Utility Classes --------------------------------------- */ +.text-muted { + color: var(--text-muted); +} +.text-secondary { + color: var(--text-secondary); +} +.text-gradient { + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.mono { + font-family: var(--font-mono); +} +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* --- Component: App Shell ---------------------------------- */ +.app-shell { + display: grid; + grid-template-rows: var(--topbar-height) 1fr var(--statusbar-height); + grid-template-columns: var(--sidebar-width) 1fr; + grid-template-areas: + 'topbar topbar' + 'sidebar main' + 'status status'; + height: 100vh; + overflow: hidden; +} + +.topbar { + grid-area: topbar; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-bottom: 1px solid var(--border-subtle); + gap: var(--space-3); + -webkit-app-region: drag; +} + +.topbar__logo { + display: flex; + align-items: center; + gap: var(--space-2); + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 700; + background: var(--brand-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.topbar__actions { + margin-left: auto; + display: flex; + align-items: center; + gap: var(--space-2); + -webkit-app-region: no-drag; +} + +.sidebar { + grid-area: sidebar; + background: var(--bg-sidebar); + border-right: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + overflow-y: auto; + padding: var(--space-3) 0; +} + +.sidebar__section { + padding: var(--space-2) var(--space-4); +} + +.sidebar__label { + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); + margin-bottom: var(--space-2); +} + +.sidebar__item { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-1) var(--space-4); + font-size: var(--text-sm); + color: var(--text-secondary); + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-fast); + margin-bottom: 1px; +} + +.sidebar__item:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.sidebar__item--active { + background: rgba(255, 170, 64, 0.1); + color: var(--brand-orange); + font-weight: 500; +} + +.sidebar__item__icon { + width: 16px; + height: 16px; + opacity: 0.7; + flex-shrink: 0; +} + +.sidebar__item--active .sidebar__item__icon { + opacity: 1; +} + +.sidebar__item__badge { + margin-left: auto; + font-size: var(--text-xs); + padding: 1px 6px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.08); + color: var(--text-muted); +} + +.main-content { + grid-area: main; + overflow-y: auto; + padding: var(--space-6); + animation: fade-in 0.2s ease; +} + +.statusbar { + grid-area: status; + display: flex; + align-items: center; + padding: 0 var(--space-4); + background: var(--bg-sidebar); + border-top: 1px solid var(--border-subtle); + font-size: var(--text-xs); + color: var(--text-muted); + gap: var(--space-4); +} + +.statusbar__segment { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.statusbar__dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--colour-success); +} + +.statusbar__dot--warning { + background: var(--colour-warning); +} +.statusbar__dot--error { + background: var(--colour-error); +} + +/* --- Component: Cards -------------------------------------- */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-5); + transition: all var(--transition-base); +} + +.card:hover { + background: var(--bg-card-hover); + border-color: var(--border-default); + box-shadow: var(--shadow-card); +} + +.card--glow:hover { + box-shadow: var(--shadow-glow); + border-color: rgba(255, 170, 64, 0.2); +} + +.card__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-3); +} + +.card__title { + font-family: var(--font-heading); + font-size: var(--text-md); + font-weight: 600; +} + +/* --- Component: Buttons ------------------------------------ */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: 500; + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + background: var(--bg-card); + color: var(--text-primary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn:hover { + background: var(--bg-card-hover); + border-color: var(--border-strong); +} + +.btn--primary { + background: var(--brand-orange); + border-color: var(--brand-orange); + color: var(--text-inverse); +} + +.btn--primary:hover { + background: #e89930; + box-shadow: 0 0 16px rgba(255, 170, 64, 0.3); +} + +.btn--danger { + border-color: var(--colour-error); + color: var(--colour-error); +} + +.btn--ghost { + background: transparent; + border-color: transparent; + color: var(--text-secondary); +} + +.btn--ghost:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.btn--sm { + padding: 2px var(--space-2); + font-size: var(--text-xs); +} + +/* --- Component: Badges ------------------------------------- */ +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + font-weight: 600; + border-radius: 10px; + letter-spacing: 0.02em; +} + +.badge--remux { + background: rgba(46, 198, 106, 0.15); + color: var(--badge-remux); +} +.badge--light { + background: rgba(96, 165, 250, 0.15); + color: var(--badge-light); +} +.badge--reencode { + background: rgba(251, 191, 36, 0.15); + color: var(--badge-reencode); +} +.badge--unsupported { + background: rgba(244, 63, 94, 0.15); + color: var(--badge-unsupported); +} +.badge--neutral { + background: rgba(255, 255, 255, 0.06); + color: var(--text-muted); +} + +/* --- Component: Progress Bar ------------------------------- */ +.progress { + height: 6px; + background: rgba(255, 255, 255, 0.06); + border-radius: 3px; + overflow: hidden; +} + +.progress__fill { + height: 100%; + border-radius: 3px; + transition: width var(--transition-base); +} + +.progress__fill--ok { + background: var(--brand-gradient); +} +.progress__fill--warning { + background: var(--colour-warning); +} +.progress__fill--error { + background: var(--colour-error); +} + +.progress--striped .progress__fill { + background-image: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + rgba(255, 255, 255, 0.08) 10px, + rgba(255, 255, 255, 0.08) 20px + ); + background-size: 40px 40px; + animation: progress-stripe 1s linear infinite; +} + +/* --- Component: Table -------------------------------------- */ +.table-wrap { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); +} + +th { + text-align: left; + padding: var(--space-2) var(--space-3); + font-weight: 600; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + border-bottom: 1px solid var(--border-default); +} + +td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--border-subtle); + color: var(--text-secondary); +} + +tr:hover td { + background: rgba(255, 255, 255, 0.02); +} + +/* --- Component: Input / Form ------------------------------- */ +.input { + padding: var(--space-2) var(--space-3); + font-family: var(--font-body); + font-size: var(--text-sm); + background: var(--bg-input); + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + color: var(--text-primary); + transition: border-color var(--transition-fast); + width: 100%; +} + +.input:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 2px rgba(255, 170, 64, 0.15); +} + +.input--select { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%237c8796' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 28px; +} + +.form-group { + margin-bottom: var(--space-4); +} + +.form-label { + display: block; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: var(--space-1); +} + +/* --- Component: Tabs --------------------------------------- */ +.tabs { + display: flex; + gap: var(--space-1); + border-bottom: 1px solid var(--border-subtle); + margin-bottom: var(--space-5); +} + +.tab { + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + font-weight: 500; + color: var(--text-muted); + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all var(--transition-fast); +} + +.tab:hover { + color: var(--text-primary); +} + +.tab--active { + color: var(--brand-orange); + border-bottom-color: var(--brand-orange); +} + +/* --- Component: Tag / Chip --------------------------------- */ +.tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: var(--text-xs); + border-radius: var(--radius-sm); + background: rgba(255, 255, 255, 0.06); + color: var(--text-secondary); + border: 1px solid var(--border-subtle); +} + +/* --- Component: Timeline ----------------------------------- */ +.timeline { + position: relative; + height: 48px; + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + overflow: hidden; +} + +.timeline__track { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 4px; + background: rgba(255, 255, 255, 0.08); + transform: translateY(-50%); +} + +.timeline__marker { + position: absolute; + top: 4px; + bottom: 4px; + width: 2px; + background: var(--brand-orange); + border-radius: 1px; +} + +.timeline__marker::after { + content: attr(data-label); + position: absolute; + top: -2px; + left: 6px; + font-size: var(--text-xs); + color: var(--text-muted); + white-space: nowrap; +} + +/* --- Component: Modal / Dialog ----------------------------- */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal { + background: var(--bg-modal); + border: 1px solid var(--border-default); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-modal); + padding: var(--space-8); + max-width: 560px; + width: 90%; + animation: fade-in 0.2s ease; +} + +/* --- Component: Capacity Bar ------------------------------- */ +.capacity-bar { + display: flex; + height: 28px; + border-radius: var(--radius-md); + overflow: hidden; + border: 1px solid var(--border-subtle); + background: rgba(255, 255, 255, 0.03); +} + +.capacity-bar__segment { + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-inverse); + transition: width var(--transition-base); + min-width: 2px; +} + +/* --- Layout Helpers ---------------------------------------- */ +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-6); +} + +.page-title { + font-family: var(--font-heading); + font-size: var(--text-xl); + font-weight: 700; +} + +.grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); +} +.grid-3 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--space-4); +} +.grid-4 { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: var(--space-4); +} + +.flex { + display: flex; +} +.flex-col { + flex-direction: column; +} +.items-center { + align-items: center; +} +.justify-between { + justify-content: space-between; +} +.gap-1 { + gap: var(--space-1); +} +.gap-2 { + gap: var(--space-2); +} +.gap-3 { + gap: var(--space-3); +} +.gap-4 { + gap: var(--space-4); +} + +.mt-2 { + margin-top: var(--space-2); +} +.mt-4 { + margin-top: var(--space-4); +} +.mt-6 { + margin-top: var(--space-6); +} +.mb-2 { + margin-bottom: var(--space-2); +} +.mb-4 { + margin-bottom: var(--space-4); +} + +.w-full { + width: 100%; +} + +/* --- Scrollbar Styling ------------------------------------- */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.18); +} diff --git a/mockups/yuli/set-2b/index.html b/mockups/yuli/set-2b/index.html new file mode 100644 index 0000000..a521590 --- /dev/null +++ b/mockups/yuli/set-2b/index.html @@ -0,0 +1,211 @@ + + + + + + Spindle — Menu Editor Concepts (Set 2b) + + + + + +
+ + + Back to Set 2a + + +
+

Spindle Menu Editor — Set 2b

+

Refining 2a into a single unified design view. Bind and Route are no longer separate modes — their features live inline in the editor. Compile becomes a preview overlay. The navigation map is a full switchable view.

+
+ Content-area mockup. Fits inside <main class="main-content">. No app chrome. +
+
+ +
+ Changes from Set 2a: +
    +
  • Removed Bind and Route modes. Action binding is now an always-available inspector section — select a button, edit its action type and target inline. Navigation arrows are a canvas toggle (N key), not a separate mode.
  • +
  • Compile is now Preview. A single toggle in the toolbar overlays DVD output simulation stats on the canvas — button count, CLUT usage, action resolution, safe area compliance.
  • +
  • Connections restored. Each menu item in the left nav shows incoming/outgoing connection counts. The inspector has a dedicated Connections section listing all linked menus with click-to-jump. Action badges appear on hover/select on canvas buttons.
  • +
  • Navigation map is a full view. The Editor/Map toggle in the left panel header switches between the canvas editor and a full bird's-eye map showing all menus as cards with colour-coded connection lines and a dedicated inspector for connection details. Double-click a map card to jump to that menu's editor.
  • +
+
+ +
+
+ Menu Editor +
Unified Design + Map
+
+
+ A single design surface with inline action binding, navigation arrow overlay, and compile preview toggle. The left panel lists all menus with connection indicators, and the Editor/Map toggle switches to a full navigation map view with colour-coded connection lines and a connection-focused inspector. +
+
+ Unified Design View + Inline Action Binding + Navigation Arrow Toggle + Preview Overlay + Full Navigation Map + Connection Inspector + Action Badges on Canvas + Click-to-Jump Connections +
+ Open Menu Editor + + + + + + + + + + + + +
KeyAction
VSelect tool
TText tool
BButton tool
IImage tool
SToggle safe area guides
NToggle navigation arrows
PToggle compile preview
MSwitch between Editor and Map views
EscDeselect / exit preview
+
+
+ + diff --git a/mockups/yuli/set-2b/menu-editor.html b/mockups/yuli/set-2b/menu-editor.html new file mode 100644 index 0000000..a8eb085 --- /dev/null +++ b/mockups/yuli/set-2b/menu-editor.html @@ -0,0 +1,2043 @@ + + + + + + Spindle — Menu Editor (Set 2b) + + + + + + + + + + From 91bbd98dc47584e5aee78c84042de52c9a507dce Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 15:17:14 -0400 Subject: [PATCH 24/32] feat: revise set-2b to restore minimap, fix map arrows, add auto-nav MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three key additions and one fix: - **Mini navigation map restored** in the left nav between menu list and template picker (was present in 2a but missing in 2b). Shows menu nodes with connection lines and an "Expand" button to switch to the full map view. - **Map view arrows fixed** — SVG connection paths now route cleanly between card edge midpoints using gentle cubic beziers. Cards laid out with VMGM on the left, VTS stacked on the right, title targets below. No more overlapping or looping curves. - **Auto Navigation** — toolbar button (`Auto Nav`) and inspector section button (`Recalculate from positions`) that compute geometric Up/Down/Left/Right navigation for all buttons, mirroring the current `autoCalculateNavigation()` in Spindle. Auto/Manual toggle badge in the Navigation inspector section header. - **Comprehensive HTML comments** throughout for implementation planning: architecture overview, section markers, data model notes, card layout coordinate reference for SVG paths, JS section index, and implementation hints per feature area. Co-Authored-By: Claude Opus 4.6 --- mockups/yuli/set-2b/index.html | 16 +- mockups/yuli/set-2b/menu-editor.html | 1484 +++++++++++++++++++------- 2 files changed, 1114 insertions(+), 386 deletions(-) diff --git a/mockups/yuli/set-2b/index.html b/mockups/yuli/set-2b/index.html index a521590..ec8339b 100644 --- a/mockups/yuli/set-2b/index.html +++ b/mockups/yuli/set-2b/index.html @@ -166,10 +166,13 @@

Spindle Menu Editor — Set 2b

Changes from Set 2a:
    -
  • Removed Bind and Route modes. Action binding is now an always-available inspector section — select a button, edit its action type and target inline. Navigation arrows are a canvas toggle (N key), not a separate mode.
  • -
  • Compile is now Preview. A single toggle in the toolbar overlays DVD output simulation stats on the canvas — button count, CLUT usage, action resolution, safe area compliance.
  • +
  • Removed Bind and Route modes. Action binding is now an always-available inspector section — select a button, edit its action type and target inline. Navigation arrows are a canvas toggle (N), not a separate mode.
  • +
  • Compile is now Preview. A single toggle (P) overlays DVD output simulation stats on the canvas — button count, CLUT usage, action resolution, safe area compliance.
  • +
  • Mini navigation map restored. Persistent mini-map in the left nav between the menu list and template picker. Shows all menu connections at a glance. Click nodes to jump, "Expand" to switch to the full Map view.
  • Connections restored. Each menu item in the left nav shows incoming/outgoing connection counts. The inspector has a dedicated Connections section listing all linked menus with click-to-jump. Action badges appear on hover/select on canvas buttons.
  • -
  • Navigation map is a full view. The Editor/Map toggle in the left panel header switches between the canvas editor and a full bird's-eye map showing all menus as cards with colour-coded connection lines and a dedicated inspector for connection details. Double-click a map card to jump to that menu's editor.
  • +
  • Auto Navigation. Toolbar "Auto Nav" button (A) calculates geometric Up/Down/Left/Right navigation for all buttons, mirroring the current Spindle autoNav feature. Inspector Navigation section has an Auto/Manual toggle and a "Recalculate from positions" button.
  • +
  • Navigation map is a full view. Editor/Map toggle switches to a bird's-eye map with clean bezier-routed connection lines between card edges — showMenu (cyan), playTitle (green dashed), return (purple dotted). Double-click a card to jump to that menu's editor.
  • +
  • Well-commented HTML. Architecture overview, section markers, data model notes, and implementation hints throughout the source for planning.
@@ -186,10 +189,12 @@

Spindle Menu Editor — Set 2b

Inline Action Binding Navigation Arrow Toggle Preview Overlay + Mini Navigation Map Full Navigation Map - Connection Inspector + Auto Navigation Action Badges on Canvas - Click-to-Jump Connections + Connection Indicators + Click-to-Jump Connections
Open Menu Editor @@ -202,6 +207,7 @@

Spindle Menu Editor — Set 2b

SToggle safe area guides NToggle navigation arrows PToggle compile preview + ARun auto-navigation for all buttons MSwitch between Editor and Map views EscDeselect / exit preview diff --git a/mockups/yuli/set-2b/menu-editor.html b/mockups/yuli/set-2b/menu-editor.html index a8eb085..592d79c 100644 --- a/mockups/yuli/set-2b/menu-editor.html +++ b/mockups/yuli/set-2b/menu-editor.html @@ -1,5 +1,48 @@ + @@ -10,6 +53,9 @@ rel="stylesheet" /> - - - + diff --git a/mockups/yuli/set-2b/menu-editor.js b/mockups/yuli/set-2b/menu-editor.js new file mode 100644 index 0000000..d6010d2 --- /dev/null +++ b/mockups/yuli/set-2b/menu-editor.js @@ -0,0 +1,601 @@ +/* ═══════════════════════════════════════════════════════ + INTERACTIVE BEHAVIOURS + ───────────────────────────────────────────────────── + Vanilla JS for prototyping. In production, these would + be React components with state managed by the Spindle + project store. + + Sections: + 1. State variables + 2. View switching (Editor ↔ Map) + 3. Menu selection (left nav + map sync) + 4. Canvas button interaction (select + drag) + 5. Preview toggle (compile overlay) + 6. Tool switching + 7. Canvas overlays (safe areas, nav arrows) + 8. Inspector toggle + 9. Template picker + 10. Visual state chips + 11. Inspector ↔ canvas position sync + 12. Action editor sync + 13. Connection click-to-jump + 14. Auto-navigation calculation + 15. Dynamic map SVG connections + 16. Background editing + 17. Generate menus + 18. Keyboard shortcuts + ═══════════════════════════════════════════════════════ */ +(function() { + 'use strict'; + + // ── 1. State variables ────────────────────────────── + let currentView = 'editor'; // 'editor' | 'map' + let selectedBtn = 'btn-1'; // currently selected canvas button + let inspectorVisible = true; + let safeAreasVisible = false; + let navArrowsVisible = false; + let previewMode = false; + let templateOpen = false; + let activeTool = 'select'; + let autoNavEnabled = true; // auto vs manual navigation + + // Button metadata (would come from MenuDocument in production) + const btnData = { + 'btn-1': { name: 'Play Ceremony', action: 'playTitle', target: 'Ceremony', navDown: 'Play Recep...' }, + 'btn-2': { name: 'Play Reception', action: 'playTitle', target: 'Reception', navUp: 'Play Cerem...', navDown: 'Chapter Se...' }, + 'btn-3': { name: 'Chapter Select', action: 'showMenu', target: 'Chapter Select', navUp: 'Play Recep...', navDown: 'Audio Setup' }, + 'btn-4': { name: 'Audio Setup', action: 'showMenu', target: 'Audio Setup', navUp: 'Chapter Se...' } + }; + + // ── Element refs ──────────────────────────────────── + const editorView = document.getElementById('editorView'); + const navMapView = document.getElementById('navMapView'); + const canvas = document.getElementById('menuCanvas'); + const inspector = document.getElementById('inspector'); + const editorBody = document.getElementById('editorBody'); + const mapEditorBody = document.getElementById('mapEditorBody'); + const mapInspector = document.getElementById('mapInspector'); + const navArrowsOverlay = document.getElementById('navArrowsOverlay'); + const compileOverlay = document.getElementById('compileOverlay'); + const safeAction = document.getElementById('safeAction'); + const safeTitle = document.getElementById('safeTitle'); + const canvasTools = document.getElementById('canvasTools'); + + + // ── 2. View switching (Editor ↔ Map) ──────────────── + document.querySelectorAll('[data-nav-view]').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('[data-nav-view]').forEach(b => b.classList.remove('nav-view-btn--active')); + btn.classList.add('nav-view-btn--active'); + currentView = btn.dataset.navView; + + if (currentView === 'editor') { + editorView.style.display = ''; + navMapView.classList.remove('nav-map-view--visible'); + } else { + editorView.style.display = 'none'; + navMapView.classList.add('nav-map-view--visible'); + } + }); + }); + + // Mini-map "Expand" button → switch to full Map view + document.getElementById('expandMapBtn').addEventListener('click', () => { + document.querySelector('[data-nav-view="map"]').click(); + }); + + // Map inspector toggle (matches editor inspector toggle) + document.getElementById('toggleMapInspectorBtn').addEventListener('click', () => { + mapInspector.classList.toggle('inspector--hidden'); + mapEditorBody.classList.toggle('editor-body--no-inspector'); + // Redraw connections after layout change + setTimeout(drawMapConnections, 50); + }); + + + // ── 3. Menu selection (left nav + map sync) ───────── + document.querySelectorAll('.menu-item').forEach(item => { + item.addEventListener('click', () => { + // Update left nav selection + document.querySelectorAll('.menu-item').forEach(i => i.classList.remove('menu-item--selected')); + item.classList.add('menu-item--selected'); + + const menuId = item.dataset.menu; + const name = item.querySelector('.menu-item__name').textContent; + document.getElementById('menuNameInput').value = name; + + // Sync mini-map node selection + document.querySelectorAll('.map-node').forEach(n => n.classList.remove('map-node--selected')); + const mapNode = document.querySelector(`.map-node[data-menu="${menuId}"]`); + if (mapNode) mapNode.classList.add('map-node--selected'); + + // Sync full map card selection + document.querySelectorAll('.map-menu-card').forEach(c => c.classList.remove('map-menu-card--selected')); + const mapCard = document.querySelector(`.map-menu-card[data-map-menu="${menuId}"]`); + if (mapCard) mapCard.classList.add('map-menu-card--selected'); + + // Update map inspector + document.getElementById('mapInspectorName').textContent = name; + document.getElementById('mapInspName').textContent = name; + }); + }); + + // Mini-map node clicks → select that menu + document.querySelectorAll('.map-node').forEach(node => { + node.addEventListener('click', () => { + const menuItem = document.querySelector(`.menu-item[data-menu="${node.dataset.menu}"]`); + if (menuItem) menuItem.click(); + }); + }); + + // Full map card clicks → select that menu + document.querySelectorAll('.map-menu-card').forEach(card => { + card.addEventListener('click', () => { + const menuItem = document.querySelector(`.menu-item[data-menu="${card.dataset.mapMenu}"]`); + if (menuItem) menuItem.click(); + }); + + // Double-click → switch to editor for that menu + card.addEventListener('dblclick', () => { + document.querySelector('[data-nav-view="editor"]').click(); + }); + }); + + + // ── 4. Canvas button interaction ──────────────────── + document.querySelectorAll('.scene-btn').forEach(btn => { + // Click to select + btn.addEventListener('click', (e) => { + e.stopPropagation(); + document.querySelectorAll('.scene-btn').forEach(b => b.classList.remove('scene-btn--selected')); + btn.classList.add('scene-btn--selected'); + selectedBtn = btn.dataset.btn; + + // Update inspector with button data + const data = btnData[selectedBtn]; + document.getElementById('inspectorName').textContent = data.name; + document.getElementById('insp-x').value = parseInt(btn.style.left); + document.getElementById('insp-y').value = parseInt(btn.style.top); + document.getElementById('insp-w').value = parseInt(btn.style.width); + document.getElementById('insp-h').value = parseInt(btn.style.height); + document.getElementById('insp-action-type').value = data.action; + document.getElementById('insp-action-target').value = data.target; + document.getElementById('actionSummary').textContent = data.action + ': "' + data.target + '"'; + document.getElementById('actionBadge').textContent = data.action; + document.getElementById('navGridCenter').textContent = selectedBtn; + + // Update nav grid + const downBtn = document.querySelector('[data-dir="down"]'); + const upBtn = document.querySelector('[data-dir="up"]'); + downBtn.textContent = data.navDown || '(none)'; + upBtn.textContent = data.navUp || '(none)'; + }); + + // Drag to move + let dragging = false, startX, startY, origLeft, origTop; + + btn.addEventListener('mousedown', (e) => { + if (e.target.classList.contains('resize-handle')) return; + if (activeTool !== 'select') return; + dragging = true; + startX = e.clientX; + startY = e.clientY; + origLeft = parseInt(btn.style.left); + origTop = parseInt(btn.style.top); + btn.style.cursor = 'grabbing'; + e.preventDefault(); + }); + + document.addEventListener('mousemove', (e) => { + if (!dragging) return; + const dx = e.clientX - startX; + const dy = e.clientY - startY; + btn.style.left = (origLeft + dx) + 'px'; + btn.style.top = (origTop + dy) + 'px'; + + // Live-sync inspector position + if (btn.classList.contains('scene-btn--selected')) { + document.getElementById('insp-x').value = parseInt(btn.style.left); + document.getElementById('insp-y').value = parseInt(btn.style.top); + } + }); + + document.addEventListener('mouseup', () => { + if (dragging) { + dragging = false; + btn.style.cursor = 'move'; + } + }); + }); + + // Deselect on canvas background click + canvas.addEventListener('click', (e) => { + if (e.target === canvas || e.target.classList.contains('canvas-bg-layer')) { + document.querySelectorAll('.scene-btn').forEach(b => b.classList.remove('scene-btn--selected')); + document.getElementById('inspectorName').textContent = '(No selection)'; + } + }); + + + // ── 5. Preview toggle (compile overlay) ───────────── + document.getElementById('previewToggle').addEventListener('click', () => { + previewMode = !previewMode; + document.getElementById('previewToggle').classList.toggle('preview-toggle--active', previewMode); + canvas.classList.toggle('menu-canvas--preview', previewMode); + compileOverlay.classList.toggle('compile-overlay--visible', previewMode); + + // Dim tools in preview mode (not interactive) + canvasTools.style.opacity = previewMode ? '0.3' : ''; + canvasTools.style.pointerEvents = previewMode ? 'none' : ''; + }); + + + // ── 6. Tool switching ─────────────────────────────── + document.querySelectorAll('.canvas-tool-btn[data-tool]').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.canvas-tool-btn[data-tool]').forEach(b => b.classList.remove('canvas-tool-btn--active')); + btn.classList.add('canvas-tool-btn--active'); + activeTool = btn.dataset.tool; + }); + }); + + + // ── 7. Canvas overlays ────────────────────────────── + + // Safe area guides toggle + document.getElementById('toggleSafeBtn').addEventListener('click', () => { + safeAreasVisible = !safeAreasVisible; + safeAction.classList.toggle('safe-guide--visible', safeAreasVisible); + safeTitle.classList.toggle('safe-guide--visible', safeAreasVisible); + document.getElementById('toggleSafeBtn').classList.toggle('canvas-tool-btn--active', safeAreasVisible); + }); + + // Navigation arrows toggle + document.getElementById('toggleNavArrows').addEventListener('click', () => { + navArrowsVisible = !navArrowsVisible; + navArrowsOverlay.classList.toggle('nav-arrows-overlay--visible', navArrowsVisible); + document.getElementById('toggleNavArrows').classList.toggle('canvas-tool-btn--active', navArrowsVisible); + }); + + + // ── 8. Inspector toggle ───────────────────────────── + function toggleInspector() { + inspectorVisible = !inspectorVisible; + inspector.classList.toggle('inspector--hidden', !inspectorVisible); + editorBody.classList.toggle('editor-body--no-inspector', !inspectorVisible); + } + + document.getElementById('toggleInspectorBtn').addEventListener('click', toggleInspector); + document.getElementById('closeInspectorBtn').addEventListener('click', toggleInspector); + + // Map inspector close + document.getElementById('closeMapInspectorBtn').addEventListener('click', () => { + mapInspector.classList.toggle('inspector--hidden'); + mapEditorBody.classList.toggle('editor-body--no-inspector'); + }); + + + // ── 9. Template picker ────────────────────────────── + document.getElementById('templateToggle').addEventListener('click', () => { + templateOpen = !templateOpen; + document.getElementById('templateList').classList.toggle('template-list--open', templateOpen); + document.getElementById('templateChevron').classList.toggle('template-toggle__chevron--open', templateOpen); + }); + + document.querySelectorAll('.template-option').forEach(opt => { + opt.addEventListener('click', () => { + // Flash green to confirm selection + opt.style.background = 'rgba(46,198,106,0.1)'; + opt.style.color = 'var(--brand-green)'; + setTimeout(() => { opt.style.background = ''; opt.style.color = ''; }, 600); + }); + }); + + + // ── 10. Visual state chips ────────────────────────── + document.querySelectorAll('.state-chip').forEach(chip => { + chip.addEventListener('click', () => { + document.querySelectorAll('.state-chip').forEach(c => c.classList.remove('state-chip--active')); + chip.classList.add('state-chip--active'); + + const state = chip.dataset.state; + const selected = document.querySelector('.scene-btn--selected'); + if (!selected) return; + + // Update canvas button appearance to match state + if (state === 'normal') { + selected.style.borderColor = 'rgba(255,255,255,0.2)'; + selected.style.background = 'rgba(255,255,255,0.06)'; + selected.style.boxShadow = 'none'; + } else if (state === 'focus') { + selected.style.borderColor = 'var(--brand-orange)'; + selected.style.background = 'rgba(255,170,64,0.08)'; + selected.style.boxShadow = '0 0 0 1px var(--brand-orange), 0 0 16px rgba(255,170,64,0.12)'; + } else if (state === 'activate') { + selected.style.borderColor = 'var(--brand-green)'; + selected.style.background = 'rgba(46,198,106,0.1)'; + selected.style.boxShadow = '0 0 0 1px var(--brand-green), 0 0 16px rgba(46,198,106,0.12)'; + } + }); + }); + + + // ── 11. Inspector ↔ canvas position sync ──────────── + ['insp-x', 'insp-y', 'insp-w', 'insp-h'].forEach(id => { + const input = document.getElementById(id); + if (!input) return; + input.addEventListener('input', () => { + const selected = document.querySelector('.scene-btn--selected'); + if (!selected) return; + const prop = { 'insp-x': 'left', 'insp-y': 'top', 'insp-w': 'width', 'insp-h': 'height' }[id]; + selected.style[prop] = input.value + 'px'; + }); + }); + + + // ── 12. Action editor sync ────────────────────────── + document.getElementById('insp-action-type').addEventListener('change', updateActionSummary); + document.getElementById('insp-action-target').addEventListener('change', updateActionSummary); + + function updateActionSummary() { + const type = document.getElementById('insp-action-type').value; + const target = document.getElementById('insp-action-target').value; + document.getElementById('actionSummary').textContent = type + ': "' + target + '"'; + document.getElementById('actionBadge').textContent = type; + + // Update canvas button action badge + const selected = document.querySelector('.scene-btn--selected'); + if (selected) { + const badge = selected.querySelector('.scene-btn__action-badge'); + if (badge) { + badge.textContent = type; + badge.className = 'scene-btn__action-badge'; + if (type.startsWith('play')) badge.classList.add('action-badge--play'); + else if (type === 'showMenu') badge.classList.add('action-badge--menu'); + else badge.classList.add('action-badge--audio'); + } + } + } + + + // ── 13. Connection click-to-jump ──────────────────── + document.querySelectorAll('.conn-entry[data-target-menu]').forEach(entry => { + entry.addEventListener('click', () => { + const targetMenu = entry.dataset.targetMenu; + const menuItem = document.querySelector(`.menu-item[data-menu="${targetMenu}"]`); + if (menuItem) { + menuItem.click(); + // Flash highlight for feedback + entry.style.background = 'rgba(34,211,238,0.1)'; + setTimeout(() => { entry.style.background = ''; }, 400); + } + }); + }); + + + // ── 14. Auto-navigation calculation ───────────────── + // Mirrors Spindle's autoCalculateNavigation(): + // For each button, find the nearest button in each + // cardinal direction based on centre-point geometry. + + function flashButton(el, duration) { + el.classList.add('auto-nav-btn--flash'); + setTimeout(() => el.classList.remove('auto-nav-btn--flash'), duration); + } + + function flashRecalc(el, duration) { + el.classList.add('recalc-btn--flash'); + setTimeout(() => el.classList.remove('recalc-btn--flash'), duration); + } + + function runAutoNav() { + // In production: iterate all buttons, compute geometric + // neighbours for up/down/left/right, update interaction graph. + // For this mockup, just flash confirmation. + + flashButton(document.getElementById('autoNavAllBtn'), 800); + flashRecalc(document.getElementById('recalcNavBtn'), 800); + + // Show nav arrows briefly if not already visible + if (!navArrowsVisible) { + navArrowsOverlay.classList.add('nav-arrows-overlay--visible'); + setTimeout(() => { + if (!navArrowsVisible) { + navArrowsOverlay.classList.remove('nav-arrows-overlay--visible'); + } + }, 1500); + } + } + + // Toolbar "Auto Nav" button + document.getElementById('autoNavAllBtn').addEventListener('click', runAutoNav); + + // Inspector "Recalculate" button + document.getElementById('recalcNavBtn').addEventListener('click', runAutoNav); + + // Auto/Manual badge toggle + document.getElementById('autoNavBadge').addEventListener('click', () => { + autoNavEnabled = !autoNavEnabled; + const badge = document.getElementById('autoNavBadge'); + badge.textContent = autoNavEnabled ? 'Auto' : 'Manual'; + badge.classList.toggle('auto-badge--on', autoNavEnabled); + badge.classList.toggle('auto-badge--off', !autoNavEnabled); + }); + + + // ── 15. Dynamic map SVG connections ───────────────── + // Computes edge midpoints of actual card DOM elements + // and draws bezier curves between them. Called when + // map view is shown or layout changes. + + const mapConnections = [ + { from: 'main-menu', to: 'chapter-select', type: 'show', fromEdge: 'right', toEdge: 'left' }, + { from: 'main-menu', to: 'audio-setup', type: 'show', fromEdge: 'right', toEdge: 'left' }, + { from: 'main-menu', to: 'title-1', type: 'play', fromEdge: 'bottom', toEdge: 'top' }, + { from: 'chapter-select', to: 'main-menu', type: 'return', fromEdge: 'left', toEdge: 'right' }, + { from: 'chapter-select', to: 'title-1', type: 'play', fromEdge: 'bottom', toEdge: 'top' }, + { from: 'audio-setup', to: 'subtitle-setup', type: 'show', fromEdge: 'bottom', toEdge: 'top' }, + ]; + + function getCardEdge(menuId, edge) { + const card = document.querySelector(`.map-menu-card[data-map-menu="${menuId}"]`); + const wrap = document.getElementById('mapCanvasWrap'); + if (!card || !wrap) return { x: 0, y: 0 }; + + const cardRect = card.getBoundingClientRect(); + const wrapRect = wrap.getBoundingClientRect(); + + // Positions relative to the map container + const left = cardRect.left - wrapRect.left; + const top = cardRect.top - wrapRect.top; + const right = left + cardRect.width; + const bottom = top + cardRect.height; + const midX = left + cardRect.width / 2; + const midY = top + cardRect.height / 2; + + switch (edge) { + case 'left': return { x: left, y: midY }; + case 'right': return { x: right, y: midY }; + case 'top': return { x: midX, y: top }; + case 'bottom': return { x: midX, y: bottom }; + default: return { x: midX, y: midY }; + } + } + + function drawMapConnections() { + const svg = document.getElementById('mapConnSvg'); + // Clear existing paths (keep defs) + svg.querySelectorAll('path.map-conn-line').forEach(p => p.remove()); + + // For each connection, compute a bezier curve + mapConnections.forEach(conn => { + const from = getCardEdge(conn.from, conn.fromEdge); + const to = getCardEdge(conn.to, conn.toEdge); + + // Control point offset (determines curve shape) + const dx = Math.abs(to.x - from.x); + const dy = Math.abs(to.y - from.y); + const offset = Math.max(dx, dy) * 0.4; + + let cp1x, cp1y, cp2x, cp2y; + + if (conn.fromEdge === 'right' && conn.toEdge === 'left') { + cp1x = from.x + offset; cp1y = from.y; + cp2x = to.x - offset; cp2y = to.y; + } else if (conn.fromEdge === 'left' && conn.toEdge === 'right') { + // Return connections: curve upward to avoid overlap + cp1x = from.x - offset; cp1y = from.y - offset * 0.5; + cp2x = to.x + offset; cp2y = to.y - offset * 0.3; + } else if (conn.fromEdge === 'bottom' && conn.toEdge === 'top') { + cp1x = from.x; cp1y = from.y + offset; + cp2x = to.x; cp2y = to.y - offset; + } else { + cp1x = from.x + offset; cp1y = from.y; + cp2x = to.x - offset; cp2y = to.y; + } + + const d = `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${to.x} ${to.y}`; + + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('d', d); + path.setAttribute('class', `map-conn-line map-conn-line--${conn.type}`); + path.setAttribute('marker-end', `url(#arrow-${conn.type})`); + svg.appendChild(path); + }); + } + + // Redraw connections when map view becomes visible + const origMapClick = document.querySelector('[data-nav-view="map"]'); + origMapClick.addEventListener('click', () => { + // Wait for layout to settle + requestAnimationFrame(() => requestAnimationFrame(drawMapConnections)); + }); + + // Redraw on window resize + window.addEventListener('resize', () => { + if (currentView === 'map') drawMapConnections(); + }); + + + // ── 16. Background editing ────────────────────────── + document.querySelectorAll('[data-bg-mode]').forEach(chip => { + chip.addEventListener('click', () => { + document.querySelectorAll('[data-bg-mode]').forEach(c => c.classList.remove('bg-mode-chip--active')); + chip.classList.add('bg-mode-chip--active'); + + const mode = chip.dataset.bgMode; + document.getElementById('bgModeBadge').textContent = mode.charAt(0).toUpperCase() + mode.slice(1); + + // Show/hide appropriate controls + document.getElementById('bgSolidControls').style.display = mode === 'solid' ? '' : 'none'; + document.getElementById('bgImageControls').style.display = mode === 'image' ? '' : 'none'; + document.getElementById('bgVideoControls').style.display = mode === 'video' ? '' : 'none'; + + // Update canvas background hint + if (mode === 'video') { + canvas.style.background = 'linear-gradient(160deg, #1a1828 0%, #0f0e1a 50%, #181520 100%)'; + canvas.style.boxShadow = '0 8px 40px rgba(0,0,0,0.5), inset 0 0 60px rgba(167,139,250,0.04)'; + } else { + canvas.style.boxShadow = ''; + } + }); + }); + + // Colour input sync + document.getElementById('bgColourInput')?.addEventListener('input', (e) => { + document.getElementById('bgColourSwatch').style.background = e.target.value; + }); + + + // ── 17. Generate menus ─────────────────────────────── + // Simulates auto-generation (GitHub issue #20). + // In production: calls Rust backend to slice project + // entities into paginated groups, then spawns + // MenuDocument scenes with pre-wired navigation. + + document.querySelectorAll('[data-generate]').forEach(btn => { + btn.addEventListener('click', () => { + const type = btn.dataset.generate; + btn.classList.add('generate-flash'); + setTimeout(() => btn.classList.remove('generate-flash'), 800); + + // Simulate adding a menu to the list (visual feedback) + const detail = btn.querySelector('.generate-btn__detail'); + if (detail) { + const origText = detail.textContent; + detail.textContent = 'Generated!'; + setTimeout(() => { detail.textContent = origText; }, 1200); + } + }); + }); + + + // ── 18. Keyboard shortcuts ────────────────────────── + document.addEventListener('keydown', (e) => { + if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return; + + switch(e.key.toLowerCase()) { + case 'v': document.querySelector('[data-tool="select"]')?.click(); break; + case 't': document.querySelector('[data-tool="text"]')?.click(); break; + case 'b': document.querySelector('[data-tool="button"]')?.click(); break; + case 'i': document.querySelector('[data-tool="image"]')?.click(); break; + case 's': document.getElementById('toggleSafeBtn')?.click(); break; + case 'n': document.getElementById('toggleNavArrows')?.click(); break; + case 'p': document.getElementById('previewToggle')?.click(); break; + case 'a': runAutoNav(); break; + case 'm': + // Toggle between Editor and Map views + const target = currentView === 'editor' ? 'map' : 'editor'; + document.querySelector(`[data-nav-view="${target}"]`)?.click(); + break; + case 'escape': + if (previewMode) { + document.getElementById('previewToggle')?.click(); + } else { + document.querySelectorAll('.scene-btn').forEach(b => b.classList.remove('scene-btn--selected')); + document.getElementById('inspectorName').textContent = '(No selection)'; + } + break; + } + }); +})(); From f1572a9a4649301c7a0cbaea92e8fa880ab64f32 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 16:14:37 -0400 Subject: [PATCH 27/32] feat: add button/text styles, collapsible inspector, video markers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inspector enhancements: - All sections independently collapsible with chevron toggles - Expand All / Collapse All buttons in inspector header - Button Style section: per-state (Normal/Focus/Activate) controls for background fill, border colour/width/radius, padding, shadow/glow - Text Style section: font family, size, weight, italic, colour, alignment, letter-spacing, text-shadow — for text nodes and button labels Background and motion menus: - Audio-only background mode for still menus with background music - Audio track sub-section on image backgrounds - Video segment markers: intro/loop regions with visual timeline bar and timecode inputs for intro start/duration and loop start/duration Missing features from codebase audit: - CLUT palette editor: DVD 4-colour subpicture (BG, E1, E2, Anti-alias) - Highlight mode: static vs animated with keyframe/easing controls - Layer order panel: z-index list with type icons and visibility toggles - Shape tool (`R` shortcut) in floating canvas palette - 8-directional resize handles (4 corners + 4 edge midpoint strips) matching `SceneCanvas.tsx` pattern Implementation notes added: - Transform section: snap guide and alignment toolbar comments referencing `getSnapTargets()`, `snapValue()`, `MenusPage.css` - Minimap: shared renderer comment (one `` component rendered at full size and minimap scale) Co-Authored-By: Claude Opus 4.6 --- mockups/yuli/set-2b/index.html | 26 +- mockups/yuli/set-2b/menu-editor.css | 336 +++++++++++++++- mockups/yuli/set-2b/menu-editor.html | 579 +++++++++++++++++++++++++-- mockups/yuli/set-2b/menu-editor.js | 68 +++- 4 files changed, 970 insertions(+), 39 deletions(-) diff --git a/mockups/yuli/set-2b/index.html b/mockups/yuli/set-2b/index.html index 9fe6917..8b8fa7c 100644 --- a/mockups/yuli/set-2b/index.html +++ b/mockups/yuli/set-2b/index.html @@ -178,7 +178,20 @@

Spindle Menu Editor — Set 2b

  • Map view inspector toggle. The map toolbar now has the same inspector toggle button as the editor toolbar.
  • Canvas re-centres properly when inspector is toggled (removed CSS transition that blocked grid reflow).
  • Well-commented HTML. Architecture overview, section markers, data model notes, and implementation hints throughout the source for planning.
  • -
  • Split into three files. CSS extracted to menu-editor.css (1476 lines), JS extracted to menu-editor.js (601 lines), leaving menu-editor.html as a clean structural shell (1064 lines). Each concern is independently editable.
  • +
  • Split into three files. CSS extracted to menu-editor.css, JS to menu-editor.js, leaving menu-editor.html as a clean structural shell. Each concern is independently editable.
  • +
  • Collapsible inspector sections. Every section independently collapsible with chevron toggle. Expand All / Collapse All buttons in the inspector header. Inspector body scrolls when content exceeds viewport.
  • +
  • Button style controls. New section with per-state styling (Normal/Focus/Activate tabs). Background fill, border colour/width/radius, horizontal/vertical padding, shadow/glow type with colour and blur controls.
  • +
  • Text style controls. New section with font family, size, line height, bold/italic/underline toggles, text colour, alignment buttons (left/centre/right), letter spacing, and text shadow presets. Applies to both standalone text nodes and button label text.
  • +
  • Video segment markers. Intro and loop regions shown as a visual timeline bar with purple (intro) and green (loop) segments. Timecode inputs for intro start/duration and loop start/duration.
  • +
  • Audio background mode. New "Audio" chip in the background section for still-image menus with background audio. Creates a motion menu with static frame + AC3 audio track.
  • +
  • Audio track on image backgrounds. Image background mode now also offers an optional audio track sub-section for menus that need background music over a still image.
  • +
  • CLUT palette editor. DVD 4-colour subpicture palette (BG, E1, E2, Anti-alias) with colour swatches. Maps authored styles to DVD overlay constraints.
  • +
  • Highlight mode. Static or Animated toggle. Animated mode reveals keyframe count and easing controls for motion menu highlight transitions.
  • +
  • Layer order panel. Lists all scene nodes in z-order with type icons (button/text), names, and per-layer visibility toggles. Supports drag-to-reorder.
  • +
  • Shape tool. New shape tool (R) in the floating canvas palette for decorative rectangles.
  • +
  • 8-directional resize handles. Buttons now have edge handles (N/S/E/W strips) in addition to corner handles, matching the production SceneCanvas.tsx pattern.
  • +
  • Alignment and snap notes. Transform section includes implementation comments for snap-to-grid (8px threshold, red guide lines) and alignment toolbar (align/distribute) from the current codebase.
  • +
  • Shared minimap renderer. Architecture comment notes that the mini-map and full Navigation Map should share the same React component, with the mini-map being a scaled-down target view.
  • @@ -205,6 +218,16 @@

    Spindle Menu Editor — Set 2b

    Motion Video Backgrounds Auto-Generate Menus Auto-Pagination + Button Style (per-state) + Text Style Controls + Video Segment Markers + Audio Background Mode + CLUT Palette Editor + Highlight Mode + Layer Order Panel + Collapsible Inspector + 8-Direction Resize + Shape Tool Open Menu Editor @@ -214,6 +237,7 @@

    Spindle Menu Editor — Set 2b

    TText tool BButton tool IImage tool + RShape tool SToggle safe area guides NToggle navigation arrows PToggle compile preview diff --git a/mockups/yuli/set-2b/menu-editor.css b/mockups/yuli/set-2b/menu-editor.css index d425787..ea26695 100644 --- a/mockups/yuli/set-2b/menu-editor.css +++ b/mockups/yuli/set-2b/menu-editor.css @@ -735,11 +735,18 @@ body { .action-badge--menu { background: rgba(34,211,238,0.15); color: var(--brand-cyan); } .action-badge--audio { background: rgba(167,139,250,0.15); color: var(--brand-purple); } -/* ── Resize handles (visible when button selected) ──── */ +/* ── Resize handles (visible when button selected) ──── + 8 directional handles: 4 corners (8×8 squares) and + 4 edge midpoints (strips). Matches the production + implementation in MenusPage.css: + - Corners: nw/ne/sw/se as 8px squares + - Edges: n/s as full-width 4px strips, e/w as + full-height 4px strips + See SceneCanvas.tsx for the resize handler that + constrains to MIN_BUTTON_SIZE=30 and canvas bounds. + ──────────────────────────────────────────────────── */ .resize-handle { position: absolute; - width: 8px; - height: 8px; background: var(--brand-orange); border: 1.5px solid #08080c; border-radius: 2px; @@ -748,11 +755,23 @@ body { } .scene-btn--selected .resize-handle { display: block; } + +/* Corner handles (8×8 squares) */ +.resize-handle--tl, .resize-handle--tr, +.resize-handle--bl, .resize-handle--br { width: 8px; height: 8px; } .resize-handle--tl { top: -4px; left: -4px; cursor: nw-resize; } .resize-handle--tr { top: -4px; right: -4px; cursor: ne-resize; } .resize-handle--bl { bottom: -4px; left: -4px; cursor: sw-resize; } .resize-handle--br { bottom: -4px; right: -4px; cursor: se-resize; } +/* Edge handles (full-width/height strips) */ +.resize-handle--n, .resize-handle--s { left: 0; right: 0; height: 4px; cursor: ns-resize; } +.resize-handle--n { top: -2px; } +.resize-handle--s { bottom: -2px; } +.resize-handle--e, .resize-handle--w { top: 0; bottom: 0; width: 4px; cursor: ew-resize; } +.resize-handle--e { right: -2px; } +.resize-handle--w { left: -2px; } + /* ── Navigation arrows overlay ───────────────────────── Shows Up/Down/Left/Right navigation arrows between buttons. Toggled with N key or toolbar button. @@ -910,13 +929,62 @@ body { color: var(--text-muted); display: flex; align-items: center; - justify-content: space-between; + gap: var(--space-2); cursor: pointer; + user-select: none; } .insp-section__header:hover { color: var(--text-primary); } -.insp-section__body { padding: var(--space-2) var(--space-3); } +/* Section title text takes available space, badges pushed right */ +.insp-section__title { flex: 1; } + +/* Collapse chevron — rotates when section is open */ +.insp-section__chevron { + width: 12px; + height: 12px; + color: var(--text-muted); + transition: transform var(--transition-fast); + flex-shrink: 0; +} + +.insp-section--open .insp-section__chevron { transform: rotate(180deg); } + +/* Body hidden when section is collapsed */ +.insp-section__body { + padding: var(--space-2) var(--space-3); +} + +.insp-section:not(.insp-section--open) .insp-section__body { + display: none; +} + +/* ── Inspector expand/collapse all toolbar ──────────── */ +.inspector__actions { + display: flex; + gap: 2px; + padding: 0 var(--space-4) var(--space-1); + flex-shrink: 0; +} + +.inspector__action-btn { + flex: 1; + padding: 3px 0; + font-size: 9px; + font-weight: 600; + text-align: center; + color: var(--text-muted); + background: none; + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-fast); +} + +.inspector__action-btn:hover { + color: var(--text-primary); + background: rgba(255,255,255,0.03); +} .insp-row { display: flex; @@ -1474,3 +1542,261 @@ body { color: var(--text-primary); font-weight: 600; } + + +/* ═══════════════════════════════════════════════════════ + BUTTON STYLE CONTROLS + ───────────────────────────────────────────────────── + Per-state styling for button nodes. Users can + customise background, border, padding, and effects + independently for Normal, Focus, and Activate states. + ═══════════════════════════════════════════════════════ */ + +/* Style sub-tab row (Normal / Focus / Activate) */ +.style-state-tabs { + display: flex; + gap: var(--space-1); + padding: 4px 0; + margin-bottom: 4px; +} + +.style-state-tab { + flex: 1; + padding: 4px 0; + text-align: center; + font-size: 9px; + font-weight: 600; + border-radius: var(--radius-sm); + cursor: pointer; + border: 1px solid var(--border-subtle); + background: rgba(255,255,255,0.02); + color: var(--text-muted); + transition: all var(--transition-fast); +} + +.style-state-tab:hover { color: var(--text-primary); } + +.style-state-tab--active { + border-color: rgba(255,170,64,0.3); + background: rgba(255,170,64,0.06); + color: var(--brand-orange); +} + +/* Colour picker + hex input pair (used in multiple sections) */ +.colour-pair { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.colour-swatch { + width: 22px; + height: 22px; + border-radius: var(--radius-sm); + border: 1.5px solid var(--border-default); + cursor: pointer; + flex-shrink: 0; +} + +.colour-swatch input[type="color"] { + opacity: 0; + width: 100%; + height: 100%; + cursor: pointer; +} + +/* Inline sub-label for grouped controls */ +.insp-sub-label { + font-size: 9px; + color: var(--text-muted); + padding: 6px 0 2px; + font-weight: 600; + letter-spacing: 0.03em; +} + +/* Compact two-column property grid */ +.insp-grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px; +} + + +/* ═══════════════════════════════════════════════════════ + TEXT STYLE CONTROLS + ───────────────────────────────────────────────────── + Typography controls for standalone text nodes and + button label text. Font family, size, weight, style, + colour, alignment, letter-spacing, text-shadow. + ═══════════════════════════════════════════════════════ */ + +/* Alignment button row */ +.align-row { + display: flex; + gap: 2px; +} + +.align-btn { + flex: 1; + padding: 4px 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255,255,255,0.02); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + color: var(--text-muted); + cursor: pointer; + transition: all var(--transition-fast); +} + +.align-btn:hover { color: var(--text-primary); } +.align-btn--active { background: rgba(255,170,64,0.06); border-color: rgba(255,170,64,0.3); color: var(--brand-orange); } + +/* Font weight / style toggle pills */ +.style-pill-row { + display: flex; + gap: 2px; +} + +.style-pill { + padding: 3px 8px; + font-size: 9px; + font-weight: 600; + border-radius: var(--radius-sm); + border: 1px solid var(--border-subtle); + background: rgba(255,255,255,0.02); + color: var(--text-muted); + cursor: pointer; + transition: all var(--transition-fast); +} + +.style-pill:hover { color: var(--text-primary); } +.style-pill--active { background: rgba(255,170,64,0.06); border-color: rgba(255,170,64,0.3); color: var(--brand-orange); } +.style-pill--bold { font-weight: 800; } +.style-pill--italic { font-style: italic; } + + +/* ═══════════════════════════════════════════════════════ + CLUT PALETTE EDITOR + ───────────────────────────────────────────────────── + DVD subpicture 4-colour CLUT. Maps authored styles + to the 4 overlay colours available for highlights. + ═══════════════════════════════════════════════════════ */ +.clut-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 4px; +} + +.clut-cell { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; +} + +.clut-swatch { + width: 100%; + aspect-ratio: 1; + border-radius: var(--radius-sm); + border: 1.5px solid var(--border-default); + cursor: pointer; + transition: border-color var(--transition-fast); +} + +.clut-swatch:hover { border-color: var(--border-strong); } + +.clut-label { + font-size: 8px; + color: var(--text-muted); + text-align: center; +} + + +/* ═══════════════════════════════════════════════════════ + LAYER ORDER + ───────────────────────────────────────────────────── + Z-index list of all scene nodes. Drag to reorder. + ═══════════════════════════════════════════════════════ */ +.layer-list { + display: flex; + flex-direction: column; + gap: 2px; +} + +.layer-item { + display: flex; + align-items: center; + gap: var(--space-2); + padding: 4px 6px; + border-radius: var(--radius-sm); + font-size: 10px; + color: var(--text-secondary); + cursor: grab; + transition: background var(--transition-fast); +} + +.layer-item:hover { background: rgba(255,255,255,0.03); } +.layer-item--selected { background: rgba(255,170,64,0.06); color: var(--brand-orange); } + +.layer-item__icon { + width: 14px; + text-align: center; + font-size: 9px; + color: var(--text-muted); + flex-shrink: 0; +} + +.layer-item__name { flex: 1; } + +.layer-item__visibility { + color: var(--text-muted); + cursor: pointer; + flex-shrink: 0; +} + +.layer-item__visibility:hover { color: var(--text-primary); } + + +/* ═══════════════════════════════════════════════════════ + VIDEO MARKER TIMELINE + ───────────────────────────────────────────────────── + Visual segment bar showing intro/loop regions for + motion menu background video. + ═══════════════════════════════════════════════════════ */ +.marker-bar { + height: 20px; + background: rgba(255,255,255,0.04); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + position: relative; + overflow: hidden; + margin: 4px 0; +} + +.marker-segment { + position: absolute; + top: 0; + bottom: 0; + border-radius: var(--radius-sm); + opacity: 0.6; +} + +.marker-segment--intro { + background: rgba(167,139,250,0.3); + border-right: 2px solid var(--brand-purple); +} + +.marker-segment--loop { + background: rgba(46,198,106,0.2); + border-left: 2px solid var(--brand-green); +} + +.marker-labels { + display: flex; + justify-content: space-between; + font-size: 8px; + color: var(--text-muted); + padding: 0 2px; +} diff --git a/mockups/yuli/set-2b/menu-editor.html b/mockups/yuli/set-2b/menu-editor.html index 1a7e04d..4c3ed9f 100644 --- a/mockups/yuli/set-2b/menu-editor.html +++ b/mockups/yuli/set-2b/menu-editor.html @@ -34,18 +34,39 @@ - Map SVG connections drawn dynamically from card DOM positions - Auto-navigation calculates geometric Up/Down/Left/Right routing - Connection indicators show incoming/outgoing links per menu - - Background editing: solid colour, image, or video (motion menus) + - Background editing: solid colour, image, video, or audio-only + - Video menu segment markers: intro start + duration, loop region + - Audio track option for still menus (AC3 with static frame) - Auto-generate menus from project data (issue #20): Chapter Grid, Audio Setup, Subtitle Setup with pagination + - Button style controls: bg, border, radius, padding, shadow/glow + with per-state overrides (Normal/Focus/Activate) + - Text style controls: font, size, weight, italic, colour, + alignment, letter-spacing, text-shadow — for text and button labels + - CLUT palette editor for DVD 4-colour subpicture overlay + - Highlight mode: static or animated (for motion menus) + - Layer order panel with z-index and visibility toggles + - Shape tool for decorative rectangles on canvas + - 8-directional resize handles (4 corners + 4 edges) IMPLEMENTATION NOTES ──────────────────── - Left nav menu list mirrors MenusPage.tsx layout (260px, scope groups) - Canvas matches DVD NTSC dimensions (720×480) - - Inspector sections are collapsible for space efficiency + - Inspector sections independently collapsible with Expand/Collapse All - Map view SVG connections are drawn between card edge midpoints + - Mini-map and full map share same renderer (minimap target pattern) - Auto-nav uses Y-centre comparison for up/down, X-centre for left/right + - Snap guides (8px threshold, red lines) noted in Transform section + - Resize handles: 8 directions matching SceneCanvas.tsx pattern - All interactive JS is vanilla for prototyping; would be React in prod + + FILE STRUCTURE + ────────────── + - menu-editor.html: structural HTML (this file) + - menu-editor.css: all styles + - menu-editor.js: all interaction logic + - design-system.css: shared brand tokens and component classes --> @@ -230,6 +251,18 @@ interaction graph. Nodes positioned via a simple force-directed or layered layout. Connection lines match the full map's colours. + + SHARED RENDERER NOTE: The mini-map and the + full Navigation Map view should share the same + rendering component, with the mini-map being a + scaled-down "target" view of the full map. + This means one source of truth for card layout, + connection routing, and colour-coding. The mini- + map simply applies a CSS transform: scale() and + disables interaction beyond click-to-select. + In React, this would be a component + rendered twice: once at full size in the map view, + once at minimap scale in the left nav. ────────────────────────────────────────────── -->
    @@ -420,6 +453,9 @@ +
    @@ -477,6 +517,10 @@
    +
    +
    +
    +
    ▶ Play Reception
    playTitle
    @@ -491,6 +535,10 @@
    +
    +
    +
    +
    ◆ Chapter Select
    showMenu
    @@ -505,6 +553,10 @@
    +
    +
    +
    +
    ♫ Audio Setup
    showMenu
    @@ -571,13 +623,24 @@
    @@ -587,6 +650,12 @@
    + +
    + + +
    +
    -
    +
    - Background + + Background Solid
    @@ -609,6 +679,7 @@
    Solid
    Image
    Video
    +
    Audio
    @@ -635,8 +706,31 @@
    + +
    Audio Track
    +
    + Asset + +
    +
    + Loop + +
    - + + +
    - -
    -
    Transform
    + +
    +
    + + Transform +
    X @@ -691,8 +872,11 @@
    -
    -
    Visual States
    +
    +
    + + Visual States +
    Normal
    @@ -702,10 +886,196 @@
    - -
    + +
    +
    + + Button Style +
    +
    + +
    +
    Normal
    +
    Focus
    +
    Activate
    +
    + + +
    Background
    +
    + Fill +
    +
    + +
    +
    + + +
    Border
    +
    +
    + Color +
    +
    + +
    +
    +
    + Width + + px +
    +
    +
    + Radius + + px +
    + + +
    Padding
    +
    + H + + V + + px +
    + + +
    Shadow / Glow
    +
    + Type + +
    +
    + Color +
    +
    + +
    +
    +
    +
    + Blur + +
    +
    + Spread + +
    +
    +
    +
    + + +
    - Action + + Text Style +
    +
    +
    + Font + +
    +
    +
    + Size + + px +
    +
    + Height + +
    +
    + +
    + Style +
    +
    B
    +
    I
    +
    U
    +
    +
    + +
    + Colour +
    +
    + +
    +
    + +
    + Align +
    + + + +
    +
    + +
    + Spacing + + em +
    + +
    + Shadow + +
    +
    +
    + + +
    +
    + + Action playTitle
    @@ -749,9 +1119,10 @@ Manual mode: user overrides individual directions via the dropdown buttons. ───────────────────────────────────── --> -
    +
    - Navigation + + Navigation Auto
    @@ -781,9 +1152,10 @@ current menu. Click an entry to jump to that menu (replaces "Route" mode). ───────────────────────────────────── --> -
    +
    - Connections + + Connections Menu
    @@ -810,7 +1182,149 @@
    - + +
    +
    + + CLUT Palette + DVD +
    +
    +
    +
    +
    + BG +
    +
    +
    + E1 +
    +
    +
    + E2 +
    +
    +
    + Anti +
    +
    +
    + BG = transparent background, E1/E2 = emphasis, Anti = anti-alias. DVD allows 4 colours per subpicture. +
    +
    +
    + + +
    +
    + + Highlight Mode +
    +
    +
    + Mode + +
    + +
    +
    + + +
    +
    + + Layers + 6 +
    +
    +
    +
    + + Play Ceremony + + + +
    +
    + + Play Reception + + + +
    +
    + + Chapter Select + + + +
    +
    + + Audio Setup + + + +
    +
    + T + Wedding Highlights + + + +
    +
    + T + Emily & James + + + +
    +
    +
    +
    + +
    Navigation valid. All actions resolved. Within button limits. @@ -986,9 +1500,10 @@
    -
    +
    - Outgoing + + Outgoing 2
    @@ -1008,9 +1523,10 @@
    -
    +
    - Incoming + + Incoming 1
    @@ -1025,9 +1541,10 @@
    -
    +
    - Titles Played + + Titles Played 2
    diff --git a/mockups/yuli/set-2b/menu-editor.js b/mockups/yuli/set-2b/menu-editor.js index d6010d2..1614e4b 100644 --- a/mockups/yuli/set-2b/menu-editor.js +++ b/mockups/yuli/set-2b/menu-editor.js @@ -23,7 +23,11 @@ 15. Dynamic map SVG connections 16. Background editing 17. Generate menus - 18. Keyboard shortcuts + 18. Collapsible inspector sections + 19. Button style state tabs + 20. Text style alignment + toggles + 21. Highlight mode toggle + 22. Keyboard shortcuts ═══════════════════════════════════════════════════════ */ (function() { 'use strict'; @@ -530,6 +534,7 @@ document.getElementById('bgSolidControls').style.display = mode === 'solid' ? '' : 'none'; document.getElementById('bgImageControls').style.display = mode === 'image' ? '' : 'none'; document.getElementById('bgVideoControls').style.display = mode === 'video' ? '' : 'none'; + document.getElementById('bgAudioControls').style.display = mode === 'audio' ? '' : 'none'; // Update canvas background hint if (mode === 'video') { @@ -570,7 +575,65 @@ }); - // ── 18. Keyboard shortcuts ────────────────────────── + // ── 18. Collapsible inspector sections ────────────── + // Each section toggles independently via its header. + // Expand All / Collapse All buttons affect all sections + // in the currently visible inspector. + + document.querySelectorAll('.insp-section__header').forEach(header => { + header.addEventListener('click', (e) => { + // Don't toggle when clicking interactive children (badges, auto-nav toggle) + if (e.target.closest('.auto-badge') || e.target.closest('.badge')) return; + const section = header.closest('.insp-section'); + section.classList.toggle('insp-section--open'); + }); + }); + + document.getElementById('expandAllBtn')?.addEventListener('click', () => { + document.querySelectorAll('#inspectorBody .insp-section').forEach(s => s.classList.add('insp-section--open')); + }); + + document.getElementById('collapseAllBtn')?.addEventListener('click', () => { + document.querySelectorAll('#inspectorBody .insp-section').forEach(s => s.classList.remove('insp-section--open')); + }); + + + // ── 19. Button style state tabs ───────────────────── + document.querySelectorAll('.style-state-tab').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.style-state-tab').forEach(t => t.classList.remove('style-state-tab--active')); + tab.classList.add('style-state-tab--active'); + // In production: switch which state's styles are shown in the controls below + }); + }); + + + // ── 20. Text style alignment buttons ──────────────── + document.querySelectorAll('.align-btn').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.align-btn').forEach(b => b.classList.remove('align-btn--active')); + btn.classList.add('align-btn--active'); + }); + }); + + // Text style toggle pills (bold, italic, underline) + document.querySelectorAll('.style-pill').forEach(pill => { + pill.addEventListener('click', () => { + pill.classList.toggle('style-pill--active'); + }); + }); + + + // ── 21. Highlight mode toggle ─────────────────────── + document.getElementById('highlightModeSelect')?.addEventListener('change', (e) => { + const animControls = document.getElementById('highlightAnimControls'); + if (animControls) { + animControls.style.display = e.target.value === 'Animated' ? '' : 'none'; + } + }); + + + // ── 22. Keyboard shortcuts ────────────────────────── document.addEventListener('keydown', (e) => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return; @@ -579,6 +642,7 @@ case 't': document.querySelector('[data-tool="text"]')?.click(); break; case 'b': document.querySelector('[data-tool="button"]')?.click(); break; case 'i': document.querySelector('[data-tool="image"]')?.click(); break; + case 'r': document.querySelector('[data-tool="shape"]')?.click(); break; case 's': document.getElementById('toggleSafeBtn')?.click(); break; case 'n': document.getElementById('toggleNavArrows')?.click(); break; case 'p': document.getElementById('previewToggle')?.click(); break; From b572e4901e84abb630ee1027d7ceae42578ffd98 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 16:25:05 -0400 Subject: [PATCH 28/32] fix: canvas rendering off-screen due to missing flex height chain The `#editorView` wrapper div had no CSS, so `editor-body`'s `flex: 1` had no flex parent to grow into. The grid row height was set by the inspector's content height (which grew large after adding many sections), centering the 480px canvas far below the visible area. Fix: - `#editorView`: add `flex: 1; display: flex; flex-direction: column; overflow: hidden; min-height: 0` to properly participate in the `editor-area` flex layout and give `editor-body` a flex parent - `editor-body`: add `grid-template-rows: 1fr; min-height: 0` so the single grid row fills the flex space and can shrink - `canvas-viewport`, `.inspector`, `.nav-map-view`: add `min-height: 0` so they can shrink within their grid/flex cells without overflow Co-Authored-By: Claude Opus 4.6 --- mockups/yuli/set-2b/menu-editor.css | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/mockups/yuli/set-2b/menu-editor.css b/mockups/yuli/set-2b/menu-editor.css index ea26695..1008994 100644 --- a/mockups/yuli/set-2b/menu-editor.css +++ b/mockups/yuli/set-2b/menu-editor.css @@ -401,6 +401,22 @@ body { display: flex; flex-direction: column; overflow: hidden; + min-height: 0; +} + +/* + * #editorView and #navMapView are flex children of editor-area. + * They need flex: 1 to fill the available height and must be flex + * containers themselves so editor-body's flex: 1 has a flex parent + * to grow into. Without this, the grid row height is determined by + * content (inspector sections), which pushes the canvas off-screen. + */ +#editorView { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; } /* ── Editor toolbar ──────────────────────────────────── @@ -523,8 +539,10 @@ body { .editor-body { display: grid; grid-template-columns: 1fr 300px; + grid-template-rows: 1fr; /* single row that fills flex: 1 height */ flex: 1; overflow: hidden; + min-height: 0; } .editor-body--no-inspector { @@ -551,6 +569,7 @@ body { background-image: radial-gradient(circle at 50% 50%, rgba(255,170,64,0.015) 0%, transparent 60%); position: relative; overflow: hidden; + min-height: 0; /* allow shrink within grid row */ } /* ── Floating tool palette (left edge of canvas) ────── */ @@ -877,6 +896,7 @@ body { overflow-y: auto; display: flex; flex-direction: column; + min-height: 0; /* allow scroll within grid cell, not grow unbounded */ } .inspector--hidden { display: none; } @@ -1242,6 +1262,7 @@ body { flex: 1; overflow: hidden; flex-direction: column; + min-height: 0; } .nav-map-view--visible { display: flex; } From edac4be365108fd94642a8143ed05dc87be7c733 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 22:26:42 -0400 Subject: [PATCH 29/32] docs: formalize Blu-ray format scaling strategy and agent handoffs for Set 2b - Adopt Blu-ray (HDMV/IG) as primary authoring ceiling with graceful degradation for DVD. - Outline expectations for seamless branching (timecode jumping) and SPRM management. - Update Edward's memory to reflect the new format strategy and technical boundaries. - Add supplemental spec document and link it to the Set 2b review. - Create handoff messages for Franklin (architecture rollout) and Yuli (UI welcome). --- docs/agents/memory/edward-memory.md | 2 + docs/menu-editor-set-2b-format-scaling.md | 28 + docs/menu-editor-set-2b-spec.md | 865 ++++++++++++++++++++++ 3 files changed, 895 insertions(+) create mode 100644 docs/menu-editor-set-2b-format-scaling.md create mode 100644 docs/menu-editor-set-2b-spec.md diff --git a/docs/agents/memory/edward-memory.md b/docs/agents/memory/edward-memory.md index d48c551..5cdebf7 100644 --- a/docs/agents/memory/edward-memory.md +++ b/docs/agents/memory/edward-memory.md @@ -31,6 +31,8 @@ Prefer structural facts over narration. - **Technical Trap**: Placeholder-based development can lead to 'Glossy but Broken' UI releases. All authored modes (Bind, Compile, Design, Remote) must be functional before a UI milestone is marked as 'Verified.' - **Validation Oracle**: The `execute_build_plan_smoke_authors_titleset_menu_return_path` test in the Rust plugin is the primary proof of a working end-to-end authoring loop. - **Architectural Boundary**: The project model and state are intentionally "format-agnostic" to support future Blu-ray, but implementation logic must remain strictly DVD-Video compliant for now. +- **Product Strategy**: Blu-ray (HDMV/IG) is the primary authoring ceiling. DVD and VCD are targets for graceful degradation. The UI should hide complexity but honestly expose downsampling constraints via the Compile Preview. +- **Technical Trap**: Fluid UI transitions simulated via seamless branching (timecode jumping) require absolute precision with I-frame alignment on sector boundaries to avoid laser seek "clunks". ## Open Questions diff --git a/docs/menu-editor-set-2b-format-scaling.md b/docs/menu-editor-set-2b-format-scaling.md new file mode 100644 index 0000000..bac97a0 --- /dev/null +++ b/docs/menu-editor-set-2b-format-scaling.md @@ -0,0 +1,28 @@ +# Menu Editor Set 2b Format Scaling and Hardware Reality + +## Strategic Context + +The Spindle project targets Blu-ray (HDMV with Interactive Graphics/IG) as the primary authoring ceiling, and DVD (or VCD) as the graceful degradation floor. This approach shifts our design philosophy from "build for the lowest common denominator" to "design for premium and downsample gracefully." + +## 1. The Blu-ray Baseline +Yuli's Set 2b designs, particularly the `Button Style` and `Text Style` panels, are completely valid under the Blu-ray specification. HDMV Interactive Graphics streams can handle: +- 8-bit color depth +- 256 levels of alpha transparency +- Rich interactive states (Focus and Activate) +- Complex visual effects like soft drop shadows and glowing borders + +## 2. Graceful Degradation (The Preview Compass) +If DVD is the floor, the interface must honestly manage the constraints. +- **Format-Aware Interface:** When a user specifies they are authoring only for DVD, the UI should gracefully fold away 8-bit alpha controls and present the 4-color CLUT (Color Look-Up Table). +- **The Compile Preview Overlay:** When authoring for "Blu-ray + DVD Fallback", the `Compile Preview` overlay becomes the most vital compass in the application. Toggled via the `P` key, this overlay must show the user precisely how their rich Blu-ray UI will be downsampled (dithered into harsh blocks or flattened) when compiled to DVD subpictures. It translates BD ambition into DVD reality. + +## 3. Seamless Branching for UI States (The Timecode Jump Illusion) +To achieve modern, fluid UI states on constrained optical formats, Spindle will employ seamless branching and multiplexed Button Over Video (BOV). +- **The Magic Trick:** By jumping the playhead to a different timecode in the background multiplex when a button is pressed, we can simulate "transitions." +- **The Mechanical Reality:** This requires incredible precision with I-frame alignment. A timecode jump requires the laser to physically move across the platter. If jumps are not authored perfectly on sector boundaries, the user will experience a half-second black screen and a mechanical "clunk" instead of a fluid transition. Our multiplexing engine must be flawless. + +## 4. SPRM Management and the Workspace +The auto-generated menus feature actions like `setAudioStream` and `setSubtitleStream`. +- **The User Experience:** The UI makes this look like a simple dropdown, hiding the complexity. This is the correct approach for the majority of users. +- **The Under-the-Hood Reality:** Spindle must write `SetSystemStream` instructions to SPRM 1 and SPRM 2 in the virtual machine. The compiler logic must be ready to handle these new VM instructions before they are exposed. +- **Advanced Scripting Mode:** We should reserve architectural space for a low-level scripting "advanced mode" (a trapdoor in the basement) where power users can manually manipulate player registers, but this is an extension beyond the v1 workspace. diff --git a/docs/menu-editor-set-2b-spec.md b/docs/menu-editor-set-2b-spec.md new file mode 100644 index 0000000..322a63d --- /dev/null +++ b/docs/menu-editor-set-2b-spec.md @@ -0,0 +1,865 @@ +# Menu Editor Set 2b Specification + +This document reviews the `mockups/yuli/set-2b` prototype against the current Spindle menu editor and defines a practical specification for the next-generation menu editing system. + +It assumes the product direction stated by the team: + +- Bind, remote, and compile are no longer separate modes in the primary experience. +- Their capabilities should be folded into one calmer, more unified editor. +- Any capability that exists today but is not yet clearly preserved in `set-2b` must be identified so it can either be ported forward or explicitly retired. + +## Sources Reviewed + +Prototype: + +- `mockups/yuli/set-2b/index.html` +- `mockups/yuli/set-2b/menu-editor.html` +- `mockups/yuli/set-2b/menu-editor.css` +- `mockups/yuli/set-2b/menu-editor.js` + +Current implementation: + +- `apps/spindle/src/pages/MenusPage.tsx` +- `apps/spindle/src/components/menus/SceneCanvas.tsx` +- `apps/spindle/src/components/menus/InspectorPanel.tsx` +- `apps/spindle/src/components/menus/LayersPanel.tsx` +- `apps/spindle/src/components/menus/BindMode.tsx` +- `apps/spindle/src/components/menus/CompileMode.tsx` +- `apps/spindle/src/types/project.ts` +- `apps/spindle/src/store/project-store.ts` + +Supporting docs: + +- `docs/Spindle_Menu_System_Spec.md` +- `docs/menu-builder-and-authoring-pipeline.md` +- `docs/agents/personas/yuli-persona.md` + +## Executive Review + +`set-2b` is a strong upgrade in workflow design and information architecture. + +Its main strength is not that it adds more controls. Its strength is that it arranges the work more coherently: + +- menu selection, menu relationships, and generation live together in the left rail +- the design canvas stays primary +- action binding and navigation editing move beside the visual design instead of into a separate mode +- compile awareness becomes ambient and local instead of a hard context switch +- a full navigation map gives the system a proper multi-menu mental model + +This is much closer to how users actually think about authored menus: + +- what menu am I on? +- what does it look like? +- where does this button go? +- can the remote navigate it? +- how does it fit into the broader disc? +- will DVD compilation degrade this safely? + +That said, `set-2b` also introduces several capability gaps and several aspirational features that are not yet supported by the current Spindle data model or runtime. The biggest risk is not visual complexity. The biggest risk is accidentally removing concrete current behaviour while adopting a more elegant shell. + +## What The Current System Already Does + +The current editor is more capable than it first appears. It already provides: + +- menu creation grouped by `Global` and per-titleset scope +- a scene-based design canvas for buttons, text, image, and shape nodes +- drag, resize, snapping, safe-area guides, and navigation line overlays +- contextual inspector editing for buttons and non-button nodes +- deterministic auto-navigation backed by the plugin layer +- a dedicated bind workflow for per-button action assignment and directional navigation +- explicit default-focus selection +- a compile workflow with DVD preview, palette summary, compile-policy display, and downgrade diagnostics +- keyboard remote preview on the canvas +- undo and redo +- node deletion and menu deletion + +In other words, the old system is clunkier in flow, but it has several very concrete authoring and validation affordances that must survive the redesign. + +## Set 2b Design Intent + +The prototype defines a unified menu authoring surface with two top-level views: + +1. Editor +2. Map + +Inside the Editor view, the user does not switch between Design, Bind, and Compile. Instead: + +- design happens on the canvas +- action binding happens inline in the inspector +- navigation can be shown as an overlay and edited in the inspector +- compile feedback appears as a preview overlay and diagnostics section + +This is the correct strategic direction. + +## Folded Feature Stance + +For this redesign, the correct parity question is not: + +- does the new editor still have separate Bind, Remote, and Compile modes? + +The correct question is: + +- can the unified editor still perform the jobs those modes currently perform? + +For acceptance, the new system must preserve these folded capabilities: + +- assign and audit actions across all buttons +- edit and verify directional remote navigation +- set and verify default focus +- preview remote traversal behaviour +- show DVD-safe compile feedback +- surface downgrade diagnostics with enough detail to fix problems + +## Specification + +## 1. Product Goals + +The new menu editor must: + +- keep the canvas as the primary workspace +- keep users oriented across multiple menus, not just one menu at a time +- fold binding, remote navigation, and compile awareness into the same authoring flow +- preserve honest DVD constraints and diagnostics +- support both handcrafted menus and generated menus +- stay grounded in Spindle’s authored document model rather than becoming a disconnected mockup shell + +## 2. Primary Layout + +The editor should use a two-column application layout: + +- left rail: navigation and generation +- right workspace: editor or map view + +### Left Rail + +The left rail should contain four persistent blocks: + +1. Header with `Editor` / `Map` toggle +2. Scrollable grouped menu list +3. Mini navigation map +4. Templates and Generate Menus sections + +### Workspace + +The right side should switch between: + +- `Editor View`: toolbar + canvas + inspector +- `Map View`: map toolbar + connection canvas + map inspector + +This structure in `set-2b` is strong and should be treated as the locked layout direction. + +## 3. Menu List + +The menu list should remain grouped by authored scope: + +- `VMGM` / global menus +- titleset-scoped menus + +Each menu card should show: + +- menu name +- compact visual preview +- button count or summary +- menu type or background mode +- incoming and outgoing connection counts +- health/status indicator + +### Requirements + +- Selecting a menu updates the canvas or map selection. +- Scope-local add actions must remain present. +- Menu deletion must remain available somewhere obvious. +- Connection indicators must be computed from authored actions, not hard-coded labels. +- Status indicators must derive from real diagnostics. + +## 4. Mini Navigation Map + +The mini map is one of the most valuable additions in `set-2b`. + +It should: + +- persist in the left rail +- show menu-to-menu and menu-to-title relationships +- keep the current menu highlighted +- allow click-to-jump selection +- offer an `Expand` affordance into the full map + +### Requirements + +- The mini map and full map should share one renderer. +- Connection styles must encode semantic meaning: + - `showMenu` + - playback actions + - return links, if supported +- The renderer must handle dynamic layout and redraw on resize or inspector changes. + +## 5. Editor View + +### 5.1 Toolbar + +The editor toolbar should include: + +- editable menu name +- context summary such as domain, format, and button count +- `Preview` toggle +- `Auto Nav` action +- inspector visibility toggle + +Optional secondary actions may include: + +- menu duplication +- menu deletion +- generation refresh for generated menus + +### 5.2 Canvas + +The canvas remains the heart of the editor. + +It should support: + +- authored scene nodes +- first-class non-button scene authoring +- direct selection +- drag and resize +- safe-area overlays +- navigation-arrow overlays +- inline action badges on interactive nodes +- background rendering +- visual focus/default indicators + +The canvas tool palette should support at minimum: + +- select +- text +- button +- image +- shape + +Text, image, and shape nodes must be treated as first-class authored content. The new editor is a menu scene editor, not only a button editor with a few supporting objects. + +### 5.2a Safe Areas And Guides + +Safe areas and guides must remain first-class canvas features. + +The unified editor should support: + +- action-safe overlays +- title-safe overlays +- clear visual distinction between guide types +- simple on-canvas toggling +- compile-time status reporting when authored content violates safe regions + +Safe-area handling must not be reduced to a static visual reference. It should remain part of authored layout feedback and DVD-readiness review. + +### 5.2b Navigation Lines And Arrow Rendering + +Navigation lines and directional arrows must remain first-class editor features. + +They should support: + +- on-canvas rendering of directional navigation relationships +- clear directional arrowheads +- visual distinction between overlapping routes where possible +- toggling on demand without leaving the editor +- redraw when button positions or navigation assignments change + +These overlays are not just decorative diagnostics. They are one of the clearest ways to verify authored remote behaviour spatially. + +### 5.3 Preview Overlay + +Compile awareness should be folded into the Editor view via a preview overlay, but this must remain more than a cosmetic badge strip. + +The preview system should provide: + +- DVD-safe visual treatment +- button count versus DVD limits +- palette usage against DVD overlay limits +- action-resolution status +- navigation completeness status +- safe-area status +- downgrade and risk diagnostics + +The overlay may be summarised visually on-canvas, but detailed diagnostics must still be accessible in the inspector. + +## 6. Inspector + +The `set-2b` inspector is the centrepiece of the redesign. It turns the old modal workflow into progressive disclosure. + +Inspector sections should be collapsible and independently stateful. + +### Required inspector sections + +1. Background +2. Transform +3. Visual States +4. Button Style +5. Text Style +6. Action +7. Navigation +8. Connections +9. CLUT Palette +10. Highlight Mode +11. Layers +12. Diagnostics + +### 6.1 Background + +The background section should support: + +- solid colour +- still image +- motion video +- still image plus audio + +This section should be menu-level, not selection-level. + +It should also expose: + +- asset picker +- fit mode +- audio source +- loop policy +- motion duration +- intro and loop segment timing + +### 6.2 Transform + +The transform section should support: + +- X +- Y +- width +- height + +It should also preserve current production behaviours: + +- snapping +- edge and corner resizing +- safe-area awareness + +These transform controls must apply cleanly to both button and non-button nodes. + +### 6.3 Visual States and Button Style + +These sections introduce an authored styling system that the current editor does not yet really have. + +They should support separate button-state styling for: + +- normal +- focus +- activate + +Required style properties: + +- background fill +- border colour +- border width +- border radius +- horizontal and vertical padding +- shadow or glow + +Important constraint: + +- this richer authored styling must compile honestly into DVD-safe highlight overlays and background renders +- the authored control surface cannot imply that all per-state styling is native DVD behaviour + +### 6.4 Text Style + +The new system should provide proper text styling for: + +- standalone text nodes +- button labels + +Required text controls: + +- font family +- size +- line height +- bold +- italic +- underline +- colour +- alignment +- letter spacing +- text shadow + +Standalone text editing is a first-class workflow and must not be reduced to button-label formatting. + +### 6.4a Non-Button Node Editing + +Non-button node editing must be a first-class feature of the unified editor. + +This includes at minimum: + +- text nodes +- image nodes +- shape nodes + +These node types must support: + +- direct canvas selection +- inspector-driven editing +- transform controls +- layer participation +- deletion +- type-appropriate visual styling + +Required per-type editing coverage: + +- text nodes: content, typography, colour, alignment, spacing, and sizing +- image nodes: asset selection, placement, sizing, and fit behaviour where applicable +- shape nodes: fill, bounds, and future stroke or radius controls if those become part of the authored style model + +The unified editor should be understood as a scene editor for menus, not merely a button-routing tool. + +### 6.5 Action + +Action editing should remain inline in the inspector. + +It should support the current concrete actions: + +- `playTitle` +- `playChapter` +- `showMenu` +- `stop` + +The prototype also proposes: + +- `setAudioStream` +- `setSubtitleStream` +- `sequence` +- `return` + +These are useful design targets, but they are not currently supported end-to-end in the reviewed Spindle implementation and must be treated as planned extensions, not assumed parity. + +### 6.6 Navigation + +Navigation should combine auto-generated and manual control. + +Required behaviour: + +- explicit up/down/left/right neighbours +- auto/manual state +- recalculate-from-geometry action +- visual overlay on canvas +- default-focus editing + +Default focus must not be implicit-only. It needs an explicit control. + +### 6.7 Connections + +Connections should surface the broader authored graph without forcing a mode change. + +This section should show: + +- outgoing menu links +- incoming menu links +- title playback targets +- return targets, if supported + +Each connection entry should support click-to-jump. + +### 6.8 CLUT Palette + +The CLUT palette section is a strong addition because it names the DVD constraint directly. + +It should expose the authored-to-DVD mapping for the four DVD subpicture slots: + +- background / transparent +- emphasis 1 +- emphasis 2 +- anti-alias + +This should integrate with compile diagnostics and preview, not exist as an isolated cosmetic picker. + +### 6.9 Highlight Mode + +Highlight mode should support: + +- static +- animated + +Animated highlight controls should only be active when the menu can support motion output. + +Required fields: + +- mode +- keyframe count or timeline entry +- easing + +Important note: + +- the prototype implies animated highlight behaviour, but the production system currently only stores a lightweight keyframe structure +- a proper animation UI will need richer authoring and preview support than currently exists + +### 6.10 Layers + +The layer list should be moved into the inspector or an adjacent collapsible panel as in the prototype. + +Required behaviour: + +- ordered z-list +- type icon +- readable label +- select on click +- visibility toggle +- drag-to-reorder + +Current Spindle already lists layers, but does not yet provide visibility or reordering. + +### 6.11 Diagnostics + +Diagnostics must remain first-class. + +At minimum the unified editor must report: + +- too many buttons +- unbound actions +- missing default focus +- broken directional references +- unreachable buttons +- motion-menu informational warnings +- compile-policy warnings + +This section can be collapsed, but it cannot disappear. + +## 7. Map View + +The full navigation map is a major improvement over the old route mental model because it scales to multi-menu authoring. + +The map should show: + +- menu cards grouped spatially by scope +- title targets as distinct non-menu nodes +- colour-coded connection lines +- click-to-select +- double-click to open a menu in the editor +- a map inspector for outgoing, incoming, and title-play relationships + +### Requirements + +- The map must be data-driven from authored actions. +- Connection routing must redraw dynamically. +- The map must support both menu-to-menu and menu-to-title links. +- It should gracefully scale beyond the four-menu demo. + +## 8. Generation + +The left-rail `Generate Menus` section is directionally excellent and fits Spindle’s authored-project model very well. + +The new system should support generation for: + +- chapter grid menus +- audio setup menus +- subtitle setup menus + +Generation should create editable authored menus, not locked templates. + +### Required generation outputs + +- menu scenes +- button labels +- playback or setting actions +- directional navigation +- pagination for large chapter sets +- next and back links where needed + +### Important implementation note + +The prototype proposes audio and subtitle setup menus before those actions clearly exist in the current reviewed action model. This is a valid product direction, but it requires schema and compiler work before it is feature-complete. + +## 9. Data Model Requirements + +To support the unified editor cleanly, Spindle should continue leaning into the authored `MenuDocument` model. + +The production data model should clearly distinguish: + +- authored scene data +- authored interaction graph +- authored menu timing +- authored style and highlight intent +- compile policy and DVD adaptation + +### The current model already supports or partially supports + +- scene nodes +- interaction graph +- default focus +- timing skeleton +- highlight colours +- highlight mode +- highlight keyframes +- compile policy +- generation metadata placeholder + +### The new UI will require stronger model support for + +- richer button visual styling +- text styling +- layer visibility and ordering controls +- background fit policy +- audio-only still menu backgrounds +- richer motion segment timing +- extended action types +- explicit return semantics, if retained +- generated-menu provenance and refresh strategy + +## 10. Editing Safety + +Undo, redo, and deletion must be treated as first-class authoring guarantees. + +The more unified and canvas-centric the editor becomes, the more important it is that users can safely explore, recover, and remove authored changes without anxiety. + +### Required safety behaviours + +- undo +- redo +- delete selected node +- delete selected button +- delete menu +- clear visual selection state before destructive actions where appropriate + +### Undo And Redo + +The unified editor should preserve current undo and redo behaviour and make it easier to discover. + +Requirements: + +- keyboard shortcuts must remain supported +- undo and redo must work across canvas moves, inspector edits, action changes, navigation edits, and structural edits +- the UI should expose these actions in a visible place such as the editor toolbar or an overflow menu +- grouped operations should undo coherently rather than as noisy micro-steps where possible + +### Deletion + +Deletion must remain explicit, predictable, and type-aware. + +Requirements: + +- deleting a selected node must work from the keyboard when focus is not inside an input control +- deleting a button must also clean up associated interaction references safely +- deleting a menu must remain available from the main editor surface +- inspector-level remove actions should exist for the currently selected entity +- destructive actions should avoid surprising data loss and should always remain undoable + +### Editing Safety Principle + +The new editor should feel safer than the current one, not riskier. + +That means unified flow should not come at the cost of losing: + +- recoverability +- predictable destructive actions +- confidence during experimentation + +## 11. Implementation Stance + +The safest way to build `set-2b` is to preserve current behaviour first, then merge views, then add the richer authored features. + +Recommended implementation order: + +1. Replace mode switching with unified editor layout while preserving existing action, navigation, default-focus, and diagnostics capabilities. +2. Add the full map and mini-map as shared graph views. +3. Move compile reporting into preview plus inspector diagnostics without losing detail. +4. Add generation affordances backed by real project data. +5. Add richer styling and motion authoring only after authored-to-DVD compilation rules are explicit. + +## Gaps Against The Current System + +This section captures what the current editor offers today that `set-2b` does not yet preserve clearly enough. + +## 1. Batch Binding Overview Is Weaker + +The current `BindMode` gives a complete table of every button’s action and default-focus status, plus a grid of every button’s directional neighbours. + +That table view is operationally valuable because it lets users audit a whole menu at once. + +`set-2b` replaces that with selection-based inspector editing. That is calmer, but weaker for bulk verification. + +### Requirement + +The new unified editor should keep a batch-audit view somewhere, such as: + +- an expandable inspector subsection +- a popover summary table +- a secondary “All Buttons” sheet + +## 2. Explicit Default-Focus Editing Is Missing + +The current editor has an explicit default-focus radio selection. + +The prototype shows a visual default marker on the canvas, but it does not clearly expose how a different button becomes the default focus. + +### Requirement + +Add an explicit `Set as default focus` control in the inspector and surface the current default clearly in both Editor and Map views. + +## 3. Remote Preview Is Not Fully Preserved + +The current canvas supports keyboard navigation preview, which approximates DVD remote traversal inside the editor. + +`set-2b` shows navigation arrows and compile preview, but does not clearly preserve interactive remote traversal. + +### Requirement + +Keep keyboard remote preview in the unified editor, ideally as: + +- a `Preview navigation` toggle +- focus movement on arrow keys +- activation preview on Enter + +## 4. Compile Diagnostics Are Less Detailed + +The current compile mode reports concrete downgrade issues: + +- button-count limits +- unbound actions +- missing default focus +- broken nav references +- unreachable buttons +- motion-menu info +- compile policy + +The prototype’s preview overlay is excellent for at-a-glance status, but it is presently too summary-oriented. + +### Requirement + +Retain the detailed compile diagnostics list and compile-policy visibility inside the unified inspector. + +## 5. Menu Deletion And Node Deletion Are Not Clearly Carried Forward + +The current system exposes: + +- `Delete Menu` +- selected-node deletion by keyboard +- remove button or node actions in the inspector + +These removal paths are not clearly preserved in the prototype shell. + +### Requirement + +The unified editor must keep: + +- delete menu +- remove selected node +- remove selected button +- keyboard deletion where safe + +See also: `Editing Safety`. + +## 6. Undo And Redo Are Not Surfaced + +The current editor supports undo and redo. + +The prototype does not show undo and redo in either toolbar or shortcut notes. + +### Requirement + +Preserve undo and redo with visible shortcut support and ideally a toolbar affordance. + +See also: `Editing Safety`. + +## 7. Some Proposed Actions Exceed Current Runtime Support + +The prototype introduces: + +- `setAudioStream` +- `setSubtitleStream` +- `sequence` +- `return` + +These may be desirable, but they are not part of the reviewed currently-supported action set used by the present editor flow. + +### Requirement + +The spec must distinguish: + +- `supported in current implementation` +- `planned for the new system` + +The UI must not imply full runtime support before the schema, compiler, and authoring pipeline can actually honour it. + +## 8. Non-Button Node Editing Is Under-Specified + +The current editor already supports selecting and editing: + +- text nodes +- image nodes +- shape nodes + +The prototype strongly develops button styling and text styling, but image, shape, and general-node editing are still less explicit than the current production inspector model. + +### Requirement + +The new inspector needs clear per-node-type editing paths, not only button-centric controls. + +This should be treated as a core product requirement, not just a parity note. Non-button node authoring belongs in the mainline editor model and implementation plan. + +## 9. Current Asset-Backed Background Selection Is More Concrete + +The current editor already binds background choices to real project assets and real asset filtering. + +The prototype’s background section is richer, but it is still a mockup-level control surface. + +### Requirement + +Back the new background editor with: + +- real asset pickers +- asset validation +- image versus video eligibility +- audio compatibility checks + +## 10. Compile Policy And Authored Metadata Are Not Visible Enough + +The current model already contains: + +- compile policy +- timing +- generation metadata placeholder +- theme reference placeholder +- timeout action + +The prototype improves UI breadth but does not yet clearly decide where these lower-level authored fields live. + +### Requirement + +Decide explicitly which of these remain user-facing in the unified editor and which become advanced or generated fields. + +## 11. Layers Need Real Visibility And Ordering Behaviour + +The prototype correctly adds visibility toggles and reorder intent to Layers. + +However, the current production system does not yet implement these behaviours. That means the prototype is ahead of reality here, not at parity. + +### Requirement + +Treat layer visibility and reordering as implementation work, not as already-solved parity. + +## 12. Route Intelligence Must Stay Trustworthy + +The current auto-navigation is backed by the plugin layer and writes into the real model. + +The prototype currently mocks this behaviour visually. + +### Requirement + +The unified UI must keep a deterministic, testable navigation engine and not regress to a purely front-end-only effect. + +## Recommendation + +Adopt `set-2b` as the layout and workflow target. + +Do not treat it as feature-complete parity yet. + +The correct path is: + +1. preserve current binding, default-focus, remote-preview, deletion, undo/redo, and diagnostics capabilities +2. fold them into the new unified layout +3. then add the richer authored styling, motion, map, and generation features in grounded phases + +If we do that, `set-2b` can become the calm, spatial, Yuli-aligned version of the menu editor without quietly dropping the very production features that make the current editor dependable. + +--- + +**Supplemental Document:** See [Set 2b Format Scaling and Hardware Reality](menu-editor-set-2b-format-scaling.md) for details on targeting Blu-ray as the baseline and graceful degradation for DVD/VCD. From 7a0913c60cfc9fe0966a82044b3a84bdfa9a8211 Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 22:49:58 -0400 Subject: [PATCH 30/32] docs: update Franklin's memory with Set 2b architectural alignment - Record the decision to adopt Yuli's Set 2b menu editor design in Franklin's memory. - Document the new architectural constraints (Blu-ray ceiling, seamless branching, SPRM requirements) so they persist across sessions. --- docs/agents/memory/franklin-memory.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/agents/memory/franklin-memory.md b/docs/agents/memory/franklin-memory.md index 7b24b7c..0cb1846 100644 --- a/docs/agents/memory/franklin-memory.md +++ b/docs/agents/memory/franklin-memory.md @@ -34,6 +34,8 @@ Prefer durable, reusable context over narration. - Trust and support surfaces should be checked alongside release surfaces; for example, diagnostics export can drift from runtime versioning even when the main app UI is current. - The shared filesystem-based agent communication layer now lives under `docs/agents/shared/`, with `context/` for Franklin/Edward broadcast state and `handoffs//` inboxes for point-to-point JSON requests and responses. - The current laptop environment should be treated as container-first for verification work: when Rust tooling or mixed workspace validation is needed, prefer `ghcr.io/liminal-hq/tauri-dev-desktop:latest` rather than assuming host-installed Rust tools are available. +- We have adopted Yuli's Set 2b unified menu editor design, establishing Blu-ray (HDMV/IG) as the primary authoring ceiling and DVD/VCD as a graceful degradation floor managed via the Compile Preview overlay. +- Seamless branching (multiplexed BOV) for UI states and SPRM manipulation (`setAudioStream`, `setSubtitleStream`) are now confirmed backend requirements that the compiler and multiplexer must support reliably. ## Open Questions From 5365f9dab1fff68d188bdbe904e303512f33c77e Mon Sep 17 00:00:00 2001 From: Scott Morris Date: Tue, 7 Apr 2026 22:57:40 -0400 Subject: [PATCH 31/32] chore: apply prettier formatting to mockups - Run Prettier across all HTML and CSS files in the mockups directory. - Resolve CI formatting failures and ensure consistent code style. --- docs/menu-editor-set-2b-format-scaling.md | 8 + mockups/yuli/set-1/index.html | 43 +- mockups/yuli/set-1/yuli-menu-concept-1.html | 121 +- mockups/yuli/set-1/yuli-menu-concept-2.html | 95 +- mockups/yuli/set-1/yuli-menu-concept-3.html | 190 ++- mockups/yuli/set-1/yuli-menu-concept-4.html | 247 +++- mockups/yuli/set-1/yuli-menu-concept-5.html | 152 ++- .../yuli/set-2/concept-1-the-waystation.html | 833 +++++++++--- .../yuli/set-2/concept-2-the-greenhouse.html | 454 +++++-- .../set-2/concept-3-the-cartographer.html | 432 ++++-- .../yuli/set-2/concept-4-the-darkroom.html | 320 +++-- .../yuli/set-2/concept-5-the-switchboard.html | 480 +++++-- mockups/yuli/set-2/index.html | 115 +- mockups/yuli/set-2a/index.html | 106 +- mockups/yuli/set-2a/menu-editor.html | 828 +++++++++--- mockups/yuli/set-2b/index.html | 298 ++++- mockups/yuli/set-2b/menu-editor.css | 673 +++++++--- mockups/yuli/set-2b/menu-editor.html | 1162 ++++++++++++++--- mockups/yuli/set-2b/menu-editor.js | 322 +++-- .../concept-1-the-conservatory-map.html | 203 ++- mockups/yuli/set-2m/design-system.css | 5 +- mockups/yuli/set-2m/index.html | 21 +- .../yuli/set-3/concept-1-the-concourse.html | 89 +- mockups/yuli/set-3/concept-2-the-atelier.html | 71 +- .../yuli/set-3/concept-3-the-observatory.html | 144 +- mockups/yuli/set-3/concept-4-the-atlas.html | 82 +- .../set-3/concept-5-the-screening-room.html | 141 +- mockups/yuli/set-3/index.html | 60 +- .../yuli/set-3a/concept-1-the-dockyard.html | 47 +- mockups/yuli/set-3a/concept-2-the-rail.html | 55 +- mockups/yuli/set-3a/concept-3-the-signal.html | 46 +- .../yuli/set-3a/concept-4-the-foundry.html | 75 +- mockups/yuli/set-3a/concept-5-the-booth.html | 144 +- mockups/yuli/set-3a/design-system.css | 8 +- mockups/yuli/set-3a/index.html | 25 +- .../yuli/set-3b/concept-1-the-harbour.html | 58 +- .../yuli/set-3b/concept-2-the-lantern.html | 62 +- .../yuli/set-3b/concept-3-the-junction.html | 68 +- mockups/yuli/set-3b/design-system.css | 5 +- mockups/yuli/set-3b/index.html | 26 +- 40 files changed, 6506 insertions(+), 1808 deletions(-) diff --git a/docs/menu-editor-set-2b-format-scaling.md b/docs/menu-editor-set-2b-format-scaling.md index bac97a0..622248f 100644 --- a/docs/menu-editor-set-2b-format-scaling.md +++ b/docs/menu-editor-set-2b-format-scaling.md @@ -5,24 +5,32 @@ The Spindle project targets Blu-ray (HDMV with Interactive Graphics/IG) as the primary authoring ceiling, and DVD (or VCD) as the graceful degradation floor. This approach shifts our design philosophy from "build for the lowest common denominator" to "design for premium and downsample gracefully." ## 1. The Blu-ray Baseline + Yuli's Set 2b designs, particularly the `Button Style` and `Text Style` panels, are completely valid under the Blu-ray specification. HDMV Interactive Graphics streams can handle: + - 8-bit color depth - 256 levels of alpha transparency - Rich interactive states (Focus and Activate) - Complex visual effects like soft drop shadows and glowing borders ## 2. Graceful Degradation (The Preview Compass) + If DVD is the floor, the interface must honestly manage the constraints. + - **Format-Aware Interface:** When a user specifies they are authoring only for DVD, the UI should gracefully fold away 8-bit alpha controls and present the 4-color CLUT (Color Look-Up Table). - **The Compile Preview Overlay:** When authoring for "Blu-ray + DVD Fallback", the `Compile Preview` overlay becomes the most vital compass in the application. Toggled via the `P` key, this overlay must show the user precisely how their rich Blu-ray UI will be downsampled (dithered into harsh blocks or flattened) when compiled to DVD subpictures. It translates BD ambition into DVD reality. ## 3. Seamless Branching for UI States (The Timecode Jump Illusion) + To achieve modern, fluid UI states on constrained optical formats, Spindle will employ seamless branching and multiplexed Button Over Video (BOV). + - **The Magic Trick:** By jumping the playhead to a different timecode in the background multiplex when a button is pressed, we can simulate "transitions." - **The Mechanical Reality:** This requires incredible precision with I-frame alignment. A timecode jump requires the laser to physically move across the platter. If jumps are not authored perfectly on sector boundaries, the user will experience a half-second black screen and a mechanical "clunk" instead of a fluid transition. Our multiplexing engine must be flawless. ## 4. SPRM Management and the Workspace + The auto-generated menus feature actions like `setAudioStream` and `setSubtitleStream`. + - **The User Experience:** The UI makes this look like a simple dropdown, hiding the complexity. This is the correct approach for the majority of users. - **The Under-the-Hood Reality:** Spindle must write `SetSystemStream` instructions to SPRM 1 and SPRM 2 in the virtual machine. The compiler logic must be ready to handle these new VM instructions before they are exposed. - **Advanced Scripting Mode:** We should reserve architectural space for a low-level scripting "advanced mode" (a trapdoor in the basement) where power users can manually manipulate player registers, but this is an extension beyond the v1 workspace. diff --git a/mockups/yuli/set-1/index.html b/mockups/yuli/set-1/index.html index 31a62a6..8a908a6 100644 --- a/mockups/yuli/set-1/index.html +++ b/mockups/yuli/set-1/index.html @@ -78,9 +78,14 @@

    Spindle Menu Editor Concepts

    Calm Defaults
    - Instead of overwhelming the user with a massive right-hand inspector panel, this concept uses a floating, contextual Action Island at the bottom of the screen. When you select a button, the island surfaces only what you need (Action routing, Style edit). + Instead of overwhelming the user with a massive right-hand inspector panel, this concept + uses a floating, contextual Action Island at the bottom of the screen. + When you select a button, the island surfaces only what you need (Action routing, Style + edit).
    - Open Concept 1 + Open Concept 1
    @@ -89,9 +94,13 @@

    Spindle Menu Editor Concepts

    Themes & Generation
    - Professional tools don't have to look like spreadsheets. This concept reimagines the left sidebar as a curated "Component Boutique". Users drag-and-drop pre-defined components like Hero Title Buttons or Chapter Tiles. + Professional tools don't have to look like spreadsheets. This concept reimagines the left + sidebar as a curated "Component Boutique". Users drag-and-drop pre-defined components like + Hero Title Buttons or Chapter Tiles.
    - Open Concept 2 + Open Concept 2
    @@ -100,9 +109,13 @@

    Spindle Menu Editor Concepts

    Navigation Routing
    - To make the DVD remote-control routing explicit and understandable, this concept introduces a "Routing Mode". It overlays a transit-style map of SVG arrows on the canvas, showing exactly how focus moves between buttons. + To make the DVD remote-control routing explicit and understandable, this concept + introduces a "Routing Mode". It overlays a transit-style map of SVG arrows on the canvas, + showing exactly how focus moves between buttons.
    - Open Concept 3 + Open Concept 3
    @@ -111,9 +124,13 @@

    Spindle Menu Editor Concepts

    Motion Architecture
    - Bringing motion into the still-menu editor without causing clutter. This mockup introduces a clean, collapsible timeline panel at the bottom. It visually separates the "Intro" segment from the "Loop Segment". + Bringing motion into the still-menu editor without causing clutter. This mockup introduces + a clean, collapsible timeline panel at the bottom. It visually separates the "Intro" + segment from the "Loop Segment".
    - Open Concept 4 + Open Concept 4
    @@ -122,10 +139,14 @@

    Spindle Menu Editor Concepts

    Diagnostics & Trust
    - Spindle shouldn't pretend DVD is more capable than it is. This concept provides a split-screen "Compile Preview" comparing the rich "Authored Scene Intent" with the "DVD Compiled Output". + Spindle shouldn't pretend DVD is more capable than it is. This concept provides a + split-screen "Compile Preview" comparing the rich "Authored Scene Intent" with the "DVD + Compiled Output".
    - Open Concept 5 + Open Concept 5
    - \ No newline at end of file + diff --git a/mockups/yuli/set-1/yuli-menu-concept-1.html b/mockups/yuli/set-1/yuli-menu-concept-1.html index 8a064a5..f16fa5d 100644 --- a/mockups/yuli/set-1/yuli-menu-concept-1.html +++ b/mockups/yuli/set-1/yuli-menu-concept-1.html @@ -219,33 +219,102 @@
    Authored Scene - + + +
    - + + + Background Audio
    - + + + Title Image
    - + + + + + Main Title Text
    Interactive Nodes
    - + + + + Play Ceremony
    - + + + + Play Reception
    - + + + + + Chapter Select
    @@ -262,14 +331,17 @@