feat: update warm multimodal composer ui

#6
Files changed (6) hide show
  1. DESIGN.md +36 -29
  2. app.py +1016 -70
  3. design-qa.md +24 -0
  4. docs/handoff.md +28 -16
  5. dream_customs/app_logic.py +4 -0
  6. dream_customs/render.py +26 -30
DESIGN.md CHANGED
@@ -2,17 +2,18 @@
2
  name: Dream Customs
3
  description: A nocturnal dream customs workbench for turning dream evidence into a next-day pact.
4
  colors:
5
- bg-night: "oklch(0.145 0.030 238)"
6
- bg-void: "oklch(0.080 0.010 250)"
7
- surface-ink: "oklch(0.205 0.035 238)"
8
- surface-raised: "oklch(0.275 0.045 236)"
9
- primary-cobalt: "oklch(0.690 0.105 232)"
10
- primary-cobalt-deep: "oklch(0.510 0.115 234)"
11
- accent-coral-stamp: "oklch(0.705 0.160 28)"
12
- accent-aurora: "oklch(0.820 0.110 185)"
13
- ink-main: "oklch(0.965 0.010 235)"
14
- ink-muted: "oklch(0.760 0.030 232)"
15
- border-dream: "oklch(0.430 0.055 235)"
 
16
  warning: "oklch(0.760 0.150 75)"
17
  typography:
18
  display:
@@ -20,7 +21,7 @@ typography:
20
  fontSize: "2.25rem"
21
  fontWeight: 760
22
  lineHeight: 1.05
23
- letterSpacing: "-0.02em"
24
  headline:
25
  fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
26
  fontSize: "1.5rem"
@@ -54,7 +55,7 @@ spacing:
54
  xl: "32px"
55
  components:
56
  button-primary:
57
- backgroundColor: "{colors.primary-cobalt-deep}"
58
  textColor: "{colors.ink-main}"
59
  rounded: "{rounded.md}"
60
  padding: "12px 16px"
@@ -74,15 +75,17 @@ components:
74
 
75
  ## 1. Overview
76
 
77
- **Creative North Star: "The Night Desk"**
78
 
79
- Dream Customs should feel like a working customs desk that appears inside a dream at 4:17 a.m. The surface is dark because the user often arrives from bed or after waking, but it is not a generic terminal. Cobalt, aurora cyan, and coral stamp ink carry the strange color. Familiar controls carry trust.
80
 
81
- This is product UI first. The user needs to type, upload, record, answer, and decide what happens next. The dream atmosphere belongs in the background wash, pact inspector, stamps, and evidence chips, while controls stay clear and predictable.
 
 
82
 
83
  **Key Characteristics:**
84
 
85
- - Bottom composer as the primary input, modeled after Codex app behavior.
86
  - Conversation timeline for declarations, questions, and pact drafts.
87
  - Evidence tray for text, image, audio, mood, and model status.
88
  - Pact inspector that can be drafted, revised, or sealed.
@@ -94,22 +97,23 @@ The palette is nocturnal and atmospheric: neutral dark surfaces, cobalt primary
94
 
95
  ### Primary
96
 
97
- - **Cobalt Gate** (`oklch(0.690 0.105 232)`): primary selection, active step, focus accents, and subtle interactive outlines.
98
- - **Deep Cobalt Gate** (`oklch(0.510 0.115 234)`): filled primary buttons and selected composer controls with near-white text.
99
 
100
  ### Secondary
101
 
102
- - **Coral Stamp Ink** (`oklch(0.705 0.160 28)`): final seal action, permit stamp, alert edge for safety notes, and one memorable highlight per screen.
103
- - **Aurora Evidence Light** (`oklch(0.820 0.110 185)`): image/audio evidence status, successful extraction, and soft dream-glow details.
 
104
 
105
  ### Neutral
106
 
107
- - **Night Background** (`oklch(0.145 0.030 238)`): app body and mobile shell.
108
- - **Void Rail** (`oklch(0.080 0.010 250)`): app outer edge, header underlay, and screenshot-safe negative space.
109
- - **Ink Surface** (`oklch(0.205 0.035 238)`): composer, timeline items, and tool panels.
110
- - **Raised Surface** (`oklch(0.275 0.045 236)`): active inspector panels and output cards.
111
- - **Main Ink** (`oklch(0.965 0.010 235)`): body text on dark surfaces.
112
- - **Muted Ink** (`oklch(0.760 0.030 232)`): secondary labels that still remain readable.
113
 
114
  ### Named Rules
115
 
@@ -194,7 +198,8 @@ The pact inspector is a live customs file, not just a final HTML card. It shows
194
 
195
  ### Do:
196
 
197
- - **Do** make the bottom composer the most obvious control on mobile and desktop.
 
198
  - **Do** keep every phase actionable with one clear next step.
199
  - **Do** use generated raster imagery for the dream atmosphere, header state, or empty state.
200
  - **Do** verify dark-mode contrast for body text, labels, placeholders, chips, and disabled controls.
@@ -205,6 +210,8 @@ The pact inspector is a live customs file, not just a final HTML card. It shows
205
  - **Don't** ship a plain Gradio two-column form as the primary experience.
206
  - **Don't** use beige parchment, tarot, faux therapy, or generic purple AI visuals.
207
  - **Don't** use one-shot generation as the whole flow.
208
- - **Don't** hide `Add material`, `Continue negotiation`, or `Seal pact` below the fold.
 
 
209
  - **Don't** pair a 1px border with a large soft shadow on every card.
210
  - **Don't** use side-stripe borders, gradient text, or decorative glass panels.
 
2
  name: Dream Customs
3
  description: A nocturnal dream customs workbench for turning dream evidence into a next-day pact.
4
  colors:
5
+ bg-night: "oklch(0.185 0.018 55)"
6
+ bg-void: "oklch(0.130 0.012 55)"
7
+ surface-ink: "oklch(0.235 0.018 58)"
8
+ surface-raised: "oklch(0.295 0.020 62)"
9
+ primary-sage: "oklch(0.690 0.105 150)"
10
+ primary-sage-deep: "oklch(0.455 0.090 155)"
11
+ accent-coral-stamp: "oklch(0.700 0.140 32)"
12
+ accent-amber-lamp: "oklch(0.830 0.120 76)"
13
+ accent-aurora: "oklch(0.745 0.080 185)"
14
+ ink-main: "oklch(0.960 0.010 78)"
15
+ ink-muted: "oklch(0.760 0.018 78)"
16
+ border-dream: "oklch(0.465 0.024 70)"
17
  warning: "oklch(0.760 0.150 75)"
18
  typography:
19
  display:
 
21
  fontSize: "2.25rem"
22
  fontWeight: 760
23
  lineHeight: 1.05
24
+ letterSpacing: "0"
25
  headline:
26
  fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
27
  fontSize: "1.5rem"
 
55
  xl: "32px"
56
  components:
57
  button-primary:
58
+ backgroundColor: "{colors.primary-sage-deep}"
59
  textColor: "{colors.ink-main}"
60
  rounded: "{rounded.md}"
61
  padding: "12px 16px"
 
75
 
76
  ## 1. Overview
77
 
78
+ **Creative North Star: "The Morning Customs Desk"**
79
 
80
+ Dream Customs should feel like a small warm customs desk that appears after waking: quiet, slightly strange, and easy enough to use before the day fully starts. The surface can stay dark like Codex, but it should read as warm charcoal and lamplight rather than a cold technical dashboard. Sage action color, amber light, muted coral seal ink, and a little aurora evidence color carry the identity.
81
 
82
+ This is product UI first. The user needs to type, attach, optionally add voice, answer, and decide what happens next. The dream atmosphere belongs in the background wash, pact inspector, stamps, and evidence chips, while controls stay clear and predictable.
83
+
84
+ **V4 Unified Composer Rule.** The dream input should behave like a Codex-style multimodal composer, not like a Gradio form. Text, image upload, voice upload, mood, add-material, and send belong visually inside one rounded input surface. Technical model routes and advanced workflow shortcuts should be collapsed by default.
85
 
86
  **Key Characteristics:**
87
 
88
+ - Unified composer as the primary input, modeled after Codex app behavior.
89
  - Conversation timeline for declarations, questions, and pact drafts.
90
  - Evidence tray for text, image, audio, mood, and model status.
91
  - Pact inspector that can be drafted, revised, or sealed.
 
97
 
98
  ### Primary
99
 
100
+ - **Sage Gate** (`oklch(0.690 0.105 150)`): primary selection, active step, focus accents, and subtle interactive outlines.
101
+ - **Deep Sage Gate** (`oklch(0.455 0.090 155)`): filled primary buttons and selected composer controls with near-white text.
102
 
103
  ### Secondary
104
 
105
+ - **Coral Stamp Ink** (`oklch(0.700 0.140 32)`): final seal action, permit stamp, alert edge for safety notes, and one memorable highlight per screen.
106
+ - **Amber Lamp** (`oklch(0.830 0.120 76)`): warmth, trust cue, and section highlights.
107
+ - **Aurora Evidence Light** (`oklch(0.745 0.080 185)`): image/audio evidence status, successful extraction, and soft dream-glow details.
108
 
109
  ### Neutral
110
 
111
+ - **Warm Night Background** (`oklch(0.185 0.018 55)`): app body and mobile shell.
112
+ - **Soft Void Rail** (`oklch(0.130 0.012 55)`): app outer edge, header underlay, and screenshot-safe negative space.
113
+ - **Charcoal Ink Surface** (`oklch(0.235 0.018 58)`): composer, timeline items, and tool panels.
114
+ - **Raised Warm Surface** (`oklch(0.295 0.020 62)`): active inspector panels and output cards.
115
+ - **Main Ink** (`oklch(0.960 0.010 78)`): body text on dark surfaces.
116
+ - **Muted Ink** (`oklch(0.760 0.018 78)`): secondary labels that still remain readable.
117
 
118
  ### Named Rules
119
 
 
198
 
199
  ### Do:
200
 
201
+ - **Do** make the unified composer the most obvious control on mobile and desktop.
202
+ - **Do** keep the first declaration controls above the timeline so a tired user can start immediately.
203
  - **Do** keep every phase actionable with one clear next step.
204
  - **Do** use generated raster imagery for the dream atmosphere, header state, or empty state.
205
  - **Do** verify dark-mode contrast for body text, labels, placeholders, chips, and disabled controls.
 
210
  - **Don't** ship a plain Gradio two-column form as the primary experience.
211
  - **Don't** use beige parchment, tarot, faux therapy, or generic purple AI visuals.
212
  - **Don't** use one-shot generation as the whole flow.
213
+ - **Don't** expose technical model routes as first-level controls unless the user opens model settings.
214
+ - **Don't** render navigation-looking text that cannot be clicked.
215
+ - **Don't** reserve tall empty panels for future content on the first screen.
216
  - **Don't** pair a 1px border with a large soft shadow on every card.
217
  - **Don't** use side-stripe borders, gradient text, or decorative glass panels.
app.py CHANGED
@@ -624,6 +624,891 @@ body,
624
  transition-duration: 0.001ms !important;
625
  }
626
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  """
628
 
629
 
@@ -645,87 +1530,148 @@ def build_demo() -> gr.Blocks:
645
  with gr.Column(elem_classes=["dc-app-shell"]):
646
  status_html = gr.HTML()
647
 
648
- with gr.Row(elem_classes=["dc-main-grid"]):
649
- with gr.Column(scale=7, elem_classes=["dc-timeline-column"]):
650
- timeline_html = gr.HTML()
651
- with gr.Column(scale=4, elem_classes=["dc-inspector-column"]):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
  inspector_html = gr.HTML()
 
 
 
 
653
 
654
- with gr.Column(elem_classes=["dc-composer-panel"]):
655
- notice_html = gr.HTML()
656
- dream_text = gr.Textbox(
657
- label="Dream material",
658
- lines=4,
659
- value="我梦见一部迟到的电梯,按钮都融化了,我一直到不了 14 楼。",
660
- placeholder="Type the dream, a fragment, or a new piece of material.",
661
- )
662
- with gr.Row():
663
- image_input = gr.Image(label="Attach image", type="filepath", height=150)
664
- audio_input = gr.Audio(label="Record voice", type="filepath")
665
- mood = gr.Dropdown(
666
- label="Mood chip",
667
- choices=["foggy", "anxious", "curious", "tired", "restless", "calm"],
668
- value="foggy",
669
- )
670
- with gr.Row():
671
- answer_text = gr.Textbox(
672
- label="Answer to the clerk",
673
- lines=2,
674
- value="我想和它结盟,但今天只想完成一件很小的事。",
675
- placeholder="Answer a customs question, or leave blank and skip.",
676
- )
677
- revision_request = gr.Textbox(
678
- label="Revision request",
679
- lines=2,
680
- value="Make it gentler and more specific for today.",
681
- placeholder="Try: make it stranger, make it gentler, or make the action smaller.",
682
- )
683
- with gr.Row():
684
- text_backend = gr.Dropdown(
685
- label="Text route",
686
- choices=["demo", "model", "ollama"],
687
- value="demo",
688
- )
689
- vision_backend = gr.Dropdown(
690
- label="Vision route",
691
- choices=["demo", "model", "ollama"],
692
- value="demo",
693
- )
694
- with gr.Row(elem_classes=["dc-action-row"]):
695
- send_btn = gr.Button("Send to customs", elem_classes=["dc-primary"])
696
- add_btn = gr.Button("Add material")
697
- ask_btn = gr.Button("Ask another question")
698
- answer_btn = gr.Button("Answer question")
699
- skip_btn = gr.Button("Skip question")
700
- draft_btn = gr.Button("Draft pact")
701
- revise_btn = gr.Button("Revise pact")
702
- seal_btn = gr.Button("Seal today's pact", elem_classes=["dc-seal-button"])
703
- new_btn = gr.Button("Start a new declaration")
704
-
705
- sealed_html = gr.HTML(elem_classes=["dc-sealed-output"])
706
 
707
  with gr.Accordion("Diagnostics", open=False, elem_classes=["dc-diagnostics"]):
708
  debug_json = gr.Code(label="Session state", language="json")
709
 
710
- gr.Examples(
711
- examples=[
712
- [
713
- "梦见一间便利店漂在海上,收银员让我用旧日历付款。",
714
- "curious",
715
- "我想知道它到底在保护什么。",
716
- ],
717
- [
718
- "I found a tiny border checkpoint inside my pillow. The officer stamped my hand with blue ink.",
719
- "restless",
720
- "I want a small action that makes tomorrow less loud.",
 
 
721
  ],
722
- ],
723
- inputs=[dream_text, mood, answer_text],
724
- )
725
 
726
  outputs = _outputs(session_state, status_html, timeline_html, inspector_html, sealed_html, debug_json, notice_html)
727
  demo.load(initial_workbench_state, outputs=outputs, api_name=False)
728
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
729
  send_btn.click(
730
  start_declaration_action,
731
  inputs=[session_state, dream_text, image_input, audio_input, mood, text_backend, vision_backend],
 
624
  transition-duration: 0.001ms !important;
625
  }
626
  }
627
+
628
+ /* V3 rescue: first-screen trust, real phase actions, and no empty panel cliffs. */
629
+ :root {
630
+ --dc-lamp: oklch(0.825 0.105 78);
631
+ --dc-plum: oklch(0.265 0.055 312);
632
+ --dc-warm-line: oklch(0.680 0.070 62);
633
+ }
634
+
635
+ .gradio-container {
636
+ min-height: auto !important;
637
+ padding-left: 0 !important;
638
+ padding-right: 0 !important;
639
+ }
640
+
641
+ .dc-app-shell {
642
+ gap: 12px !important;
643
+ min-height: auto;
644
+ padding: 14px clamp(10px, 2vw, 22px) 18px;
645
+ }
646
+
647
+ .dc-statusbar {
648
+ background:
649
+ linear-gradient(110deg, oklch(0.080 0.010 250 / 0.92), oklch(0.265 0.055 312 / 0.70)),
650
+ url("/file=docs/design/assets/dream-customs-probe-night-desk.png");
651
+ background-position: center 42%;
652
+ background-size: cover;
653
+ border-color: oklch(0.680 0.070 62 / 0.42);
654
+ grid-template-columns: minmax(260px, 1fr) minmax(260px, auto);
655
+ min-height: auto;
656
+ padding: 14px 16px;
657
+ }
658
+
659
+ .dc-statusbar::before {
660
+ background:
661
+ radial-gradient(circle at 18% 18%, oklch(0.825 0.105 78 / 0.16), transparent 18rem),
662
+ linear-gradient(90deg, oklch(0.080 0.010 250 / 0.90), oklch(0.145 0.030 238 / 0.66));
663
+ }
664
+
665
+ .dc-brand-mark {
666
+ background: linear-gradient(135deg, var(--dc-primary-deep), oklch(0.265 0.055 312));
667
+ border-color: oklch(0.825 0.105 78 / 0.58);
668
+ }
669
+
670
+ .dc-system-status span {
671
+ background: oklch(0.080 0.010 250 / 0.72);
672
+ border-color: oklch(0.680 0.070 62 / 0.34);
673
+ }
674
+
675
+ .dc-stage-nav {
676
+ align-items: stretch !important;
677
+ background: oklch(0.145 0.030 238 / 0.92);
678
+ border: 1px solid oklch(0.430 0.055 235 / 0.70);
679
+ border-radius: var(--dc-radius-lg);
680
+ display: grid !important;
681
+ gap: 8px !important;
682
+ grid-template-columns: repeat(4, minmax(0, 1fr));
683
+ padding: 8px;
684
+ }
685
+
686
+ .dc-stage-nav button {
687
+ background: oklch(0.205 0.035 238) !important;
688
+ border: 1px solid oklch(0.430 0.055 235 / 0.78) !important;
689
+ border-radius: var(--dc-radius-md) !important;
690
+ color: var(--dc-ink-main) !important;
691
+ font-weight: 760 !important;
692
+ min-height: 42px !important;
693
+ white-space: normal !important;
694
+ }
695
+
696
+ .dc-stage-nav button:hover {
697
+ background: oklch(0.275 0.045 236) !important;
698
+ border-color: var(--dc-primary) !important;
699
+ }
700
+
701
+ .dc-stage-nav .dc-stage-seal button {
702
+ border-color: oklch(0.705 0.160 28 / 0.72) !important;
703
+ }
704
+
705
+ .dc-focus-grid {
706
+ align-items: start !important;
707
+ display: grid !important;
708
+ gap: 14px !important;
709
+ grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.65fr);
710
+ }
711
+
712
+ .dc-declaration-column,
713
+ .dc-side-rail {
714
+ min-width: 0 !important;
715
+ }
716
+
717
+ .dc-side-rail {
718
+ gap: 12px !important;
719
+ }
720
+
721
+ .dc-composer-panel {
722
+ background:
723
+ radial-gradient(circle at 0% 0%, oklch(0.825 0.105 78 / 0.10), transparent 18rem),
724
+ linear-gradient(145deg, oklch(0.205 0.035 238 / 0.98), oklch(0.265 0.055 312 / 0.38));
725
+ border-color: oklch(0.680 0.070 62 / 0.38);
726
+ bottom: auto;
727
+ box-shadow: 0 18px 40px rgba(0, 0, 0, 0.24);
728
+ padding: 16px;
729
+ position: relative;
730
+ }
731
+
732
+ .dc-composer-head {
733
+ align-items: end;
734
+ display: flex;
735
+ gap: 12px;
736
+ justify-content: space-between;
737
+ margin-bottom: 12px;
738
+ }
739
+
740
+ .dc-composer-head h2 {
741
+ color: var(--dc-ink-main);
742
+ font-size: 1.18rem;
743
+ line-height: 1.18;
744
+ margin: 0;
745
+ }
746
+
747
+ .dc-composer-head p {
748
+ color: var(--dc-ink-muted);
749
+ font-size: 0.92rem;
750
+ line-height: 1.45;
751
+ margin: 4px 0 0;
752
+ }
753
+
754
+ .dc-trust-pill {
755
+ background: oklch(0.825 0.105 78 / 0.12);
756
+ border: 1px solid oklch(0.825 0.105 78 / 0.40);
757
+ border-radius: 999px;
758
+ color: oklch(0.920 0.055 78);
759
+ flex: 0 0 auto;
760
+ font-size: 0.78rem;
761
+ font-weight: 760;
762
+ padding: 8px 10px;
763
+ }
764
+
765
+ .dc-declaration-row {
766
+ align-items: stretch !important;
767
+ display: grid !important;
768
+ gap: 10px !important;
769
+ grid-template-columns: minmax(0, 1fr) minmax(168px, 210px);
770
+ }
771
+
772
+ .dc-primary-stack {
773
+ display: grid !important;
774
+ gap: 8px !important;
775
+ }
776
+
777
+ .dc-primary-stack button {
778
+ background: oklch(0.205 0.035 238) !important;
779
+ border: 1px solid oklch(0.430 0.055 235 / 0.78) !important;
780
+ color: var(--dc-ink-main) !important;
781
+ min-height: 52px !important;
782
+ white-space: normal !important;
783
+ }
784
+
785
+ .dc-primary-stack .dc-primary,
786
+ .dc-primary-stack .dc-primary button {
787
+ background: var(--dc-primary-deep) !important;
788
+ border-color: var(--dc-primary) !important;
789
+ color: var(--dc-ink-main) !important;
790
+ }
791
+
792
+ .dc-primary-stack .dc-seal-button,
793
+ .dc-primary-stack .dc-seal-button button {
794
+ background: var(--dc-coral) !important;
795
+ border-color: var(--dc-coral) !important;
796
+ color: var(--dc-bg-void) !important;
797
+ }
798
+
799
+ .dc-evidence-grid {
800
+ align-items: stretch !important;
801
+ display: grid !important;
802
+ gap: 10px !important;
803
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(170px, 220px);
804
+ }
805
+
806
+ .dc-route-row,
807
+ .dc-followup-grid {
808
+ display: grid !important;
809
+ gap: 10px !important;
810
+ grid-template-columns: repeat(2, minmax(0, 1fr));
811
+ }
812
+
813
+ .dc-secondary-actions {
814
+ align-items: stretch !important;
815
+ display: flex !important;
816
+ flex-wrap: wrap !important;
817
+ gap: 8px !important;
818
+ }
819
+
820
+ .dc-secondary-actions > * {
821
+ flex: 1 1 140px !important;
822
+ min-width: 132px !important;
823
+ }
824
+
825
+ .dc-secondary-actions button {
826
+ background: oklch(0.205 0.035 238) !important;
827
+ border: 1px solid oklch(0.430 0.055 235 / 0.78) !important;
828
+ color: var(--dc-ink-main) !important;
829
+ min-height: 42px !important;
830
+ white-space: normal !important;
831
+ }
832
+
833
+ .dc-secondary-actions button:hover {
834
+ background: oklch(0.275 0.045 236) !important;
835
+ border-color: var(--dc-primary) !important;
836
+ }
837
+
838
+ .dc-timeline-shell,
839
+ .dc-inspector {
840
+ min-height: auto;
841
+ }
842
+
843
+ .dc-timeline-shell {
844
+ padding: 16px;
845
+ }
846
+
847
+ .dc-inspector {
848
+ background:
849
+ radial-gradient(circle at 100% 0%, oklch(0.825 0.105 78 / 0.07), transparent 14rem),
850
+ oklch(0.205 0.035 238 / 0.96);
851
+ padding: 16px;
852
+ }
853
+
854
+ .dc-inspector dl {
855
+ margin: 12px 0;
856
+ }
857
+
858
+ .dc-timeline-list {
859
+ gap: 8px;
860
+ }
861
+
862
+ .dc-sealed-output:empty {
863
+ display: none !important;
864
+ }
865
+
866
+ .dc-composer-panel .block,
867
+ .dc-composer-panel .container,
868
+ .dc-composer-panel .input-container,
869
+ .dc-composer-panel .wrap,
870
+ .dc-composer-panel .wrap.default,
871
+ .dc-composer-panel .wrap.svelte-1cl284s {
872
+ background: oklch(0.130 0.024 238) !important;
873
+ border-color: oklch(0.680 0.070 62 / 0.34) !important;
874
+ }
875
+
876
+ .dc-composer-panel textarea,
877
+ .dc-composer-panel input,
878
+ .dc-composer-panel select,
879
+ .dc-composer-panel [role="combobox"] {
880
+ background: oklch(0.105 0.018 245) !important;
881
+ border-color: oklch(0.430 0.055 235 / 0.82) !important;
882
+ color: var(--dc-ink-main) !important;
883
+ }
884
+
885
+ .dc-composer-panel .upload-container,
886
+ .dc-composer-panel .image-container,
887
+ .dc-composer-panel .audio-container,
888
+ .dc-composer-panel .upload-container button,
889
+ .dc-composer-panel .audio-container button {
890
+ background: oklch(0.105 0.018 245) !important;
891
+ border-color: oklch(0.680 0.070 62 / 0.28) !important;
892
+ color: var(--dc-ink-muted) !important;
893
+ }
894
+
895
+ .dc-composer-panel .image-container,
896
+ .dc-composer-panel .audio-container {
897
+ min-height: 112px !important;
898
+ }
899
+
900
+ .dc-composer-panel label,
901
+ .dc-composer-panel .label-wrap span,
902
+ .dc-composer-panel [data-testid="block-label"] {
903
+ color: oklch(0.900 0.020 235) !important;
904
+ }
905
+
906
+ .dc-composer-panel .label-wrap {
907
+ background: oklch(0.130 0.024 238) !important;
908
+ border: 1px solid oklch(0.680 0.070 62 / 0.30) !important;
909
+ border-radius: 999px !important;
910
+ }
911
+
912
+ .dc-composer-panel label.float {
913
+ background: oklch(0.130 0.024 238) !important;
914
+ border-color: oklch(0.680 0.070 62 / 0.34) !important;
915
+ color: oklch(0.900 0.020 235) !important;
916
+ }
917
+
918
+ .dc-composer-panel .label-wrap svg {
919
+ color: var(--dc-aurora) !important;
920
+ }
921
+
922
+ .options,
923
+ .option,
924
+ ul.options,
925
+ .dropdown-options {
926
+ background: oklch(0.130 0.024 238) !important;
927
+ color: var(--dc-ink-main) !important;
928
+ }
929
+
930
+ .options li,
931
+ .option {
932
+ color: var(--dc-ink-main) !important;
933
+ }
934
+
935
+ .dc-diagnostics,
936
+ .dc-examples {
937
+ margin-top: 2px;
938
+ }
939
+
940
+ @media (max-width: 980px) {
941
+ .dc-focus-grid {
942
+ grid-template-columns: 1fr;
943
+ }
944
+
945
+ .dc-stage-nav {
946
+ grid-template-columns: repeat(4, minmax(0, 1fr));
947
+ }
948
+
949
+ .dc-declaration-row {
950
+ grid-template-columns: 1fr;
951
+ }
952
+
953
+ .dc-primary-stack {
954
+ grid-template-columns: repeat(3, minmax(0, 1fr));
955
+ }
956
+
957
+ .dc-evidence-grid {
958
+ grid-template-columns: 1fr;
959
+ }
960
+ }
961
+
962
+ @media (max-width: 620px) {
963
+ *,
964
+ *::before,
965
+ *::after {
966
+ box-sizing: border-box !important;
967
+ }
968
+
969
+ html,
970
+ body,
971
+ .gradio-container,
972
+ .dc-app-shell {
973
+ max-width: 100vw !important;
974
+ overflow-x: hidden !important;
975
+ }
976
+
977
+ .gradio-container,
978
+ .gradio-container .main,
979
+ .gradio-container .wrap,
980
+ .gradio-container .contain {
981
+ margin-left: 0 !important;
982
+ margin-right: 0 !important;
983
+ padding-left: 0 !important;
984
+ padding-right: 0 !important;
985
+ }
986
+
987
+ .dc-app-shell {
988
+ margin: 0 !important;
989
+ padding: 10px !important;
990
+ width: 100vw !important;
991
+ }
992
+
993
+ .dc-statusbar,
994
+ .dc-stage-nav,
995
+ .dc-focus-grid,
996
+ .dc-composer-panel,
997
+ .dc-timeline-shell,
998
+ .dc-inspector {
999
+ max-width: 100% !important;
1000
+ min-width: 0 !important;
1001
+ width: 100% !important;
1002
+ }
1003
+
1004
+ .dc-statusbar {
1005
+ grid-template-columns: 1fr;
1006
+ }
1007
+
1008
+ .dc-brand-lockup,
1009
+ .dc-brand-lockup > div,
1010
+ .dc-system-status {
1011
+ min-width: 0 !important;
1012
+ }
1013
+
1014
+ .dc-brand-lockup p {
1015
+ max-width: 100% !important;
1016
+ overflow-wrap: anywhere;
1017
+ }
1018
+
1019
+ .dc-brand-lockup h1 {
1020
+ font-size: 1rem;
1021
+ overflow-wrap: anywhere;
1022
+ }
1023
+
1024
+ .dc-system-status {
1025
+ justify-content: flex-start;
1026
+ }
1027
+
1028
+ .dc-system-status span {
1029
+ font-size: 0.74rem;
1030
+ padding: 7px 8px;
1031
+ white-space: normal;
1032
+ }
1033
+
1034
+ .dc-stage-nav button {
1035
+ min-width: 0 !important;
1036
+ }
1037
+
1038
+ .dc-composer-head {
1039
+ align-items: flex-start;
1040
+ flex-direction: column;
1041
+ }
1042
+
1043
+ .dc-stage-nav {
1044
+ gap: 6px !important;
1045
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1046
+ padding: 6px;
1047
+ }
1048
+
1049
+ .dc-stage-nav button {
1050
+ font-size: 0.86rem !important;
1051
+ min-height: 40px !important;
1052
+ padding-left: 6px !important;
1053
+ padding-right: 6px !important;
1054
+ }
1055
+
1056
+ .dc-composer-panel {
1057
+ padding: 14px;
1058
+ }
1059
+
1060
+ .dc-primary-stack button {
1061
+ min-height: 48px !important;
1062
+ }
1063
+
1064
+ .dc-primary-stack .dc-draft-button,
1065
+ .dc-primary-stack .dc-seal-button {
1066
+ display: none !important;
1067
+ }
1068
+
1069
+ .dc-composer-panel textarea {
1070
+ min-height: 116px !important;
1071
+ }
1072
+
1073
+ .dc-primary-stack,
1074
+ .dc-route-row,
1075
+ .dc-followup-grid {
1076
+ grid-template-columns: 1fr;
1077
+ }
1078
+
1079
+ .dc-secondary-actions > * {
1080
+ flex-basis: 100% !important;
1081
+ }
1082
+ }
1083
+
1084
+ /* V4: warm, simple Codex-like multimodal composer. */
1085
+ :root {
1086
+ --dc-bg-night: oklch(0.185 0.018 55);
1087
+ --dc-bg-void: oklch(0.130 0.012 55);
1088
+ --dc-surface-ink: oklch(0.235 0.018 58);
1089
+ --dc-surface-raised: oklch(0.295 0.020 62);
1090
+ --dc-composer: oklch(0.255 0.014 52);
1091
+ --dc-composer-soft: oklch(0.315 0.015 58);
1092
+ --dc-primary: oklch(0.690 0.105 150);
1093
+ --dc-primary-deep: oklch(0.455 0.090 155);
1094
+ --dc-coral: oklch(0.700 0.140 32);
1095
+ --dc-aurora: oklch(0.745 0.080 185);
1096
+ --dc-lamp: oklch(0.830 0.120 76);
1097
+ --dc-plum: oklch(0.270 0.038 328);
1098
+ --dc-ink-main: oklch(0.960 0.010 78);
1099
+ --dc-ink-muted: oklch(0.760 0.018 78);
1100
+ --dc-border: oklch(0.465 0.024 70);
1101
+ --dc-radius-lg: 18px;
1102
+ --dc-radius-xl: 28px;
1103
+ }
1104
+
1105
+ html,
1106
+ body,
1107
+ .gradio-container {
1108
+ background:
1109
+ radial-gradient(circle at 18% -8%, oklch(0.830 0.120 76 / 0.16), transparent 24rem),
1110
+ radial-gradient(circle at 88% 10%, oklch(0.745 0.080 185 / 0.12), transparent 25rem),
1111
+ linear-gradient(180deg, oklch(0.150 0.012 48), var(--dc-bg-night) 48%, var(--dc-bg-void));
1112
+ }
1113
+
1114
+ .dc-app-shell {
1115
+ gap: 14px !important;
1116
+ max-width: 1260px;
1117
+ padding: 18px clamp(12px, 2.4vw, 28px) 24px;
1118
+ }
1119
+
1120
+ .dc-statusbar {
1121
+ background:
1122
+ linear-gradient(110deg, oklch(0.145 0.012 48 / 0.95), oklch(0.270 0.038 328 / 0.66)),
1123
+ url("/file=docs/design/assets/dream-customs-probe-morning-console.png");
1124
+ background-position: center 46%;
1125
+ background-size: cover;
1126
+ border-color: oklch(0.830 0.120 76 / 0.25);
1127
+ border-radius: 20px;
1128
+ box-shadow: none;
1129
+ grid-template-columns: minmax(0, 1fr) minmax(220px, auto);
1130
+ padding: 18px;
1131
+ }
1132
+
1133
+ .dc-statusbar::before {
1134
+ background:
1135
+ linear-gradient(90deg, oklch(0.145 0.012 48 / 0.92), oklch(0.235 0.018 58 / 0.76)),
1136
+ radial-gradient(circle at 12% 30%, oklch(0.830 0.120 76 / 0.12), transparent 18rem);
1137
+ }
1138
+
1139
+ .dc-brand-mark {
1140
+ background: oklch(0.830 0.120 76 / 0.14);
1141
+ border-color: oklch(0.830 0.120 76 / 0.42);
1142
+ border-radius: 14px;
1143
+ color: var(--dc-lamp);
1144
+ }
1145
+
1146
+ .dc-brand-lockup h1 {
1147
+ font-size: clamp(1.35rem, 2.4vw, 2rem);
1148
+ font-weight: 760;
1149
+ }
1150
+
1151
+ .dc-brand-lockup p {
1152
+ color: oklch(0.840 0.016 78);
1153
+ max-width: 58ch;
1154
+ }
1155
+
1156
+ .dc-system-status {
1157
+ align-content: center;
1158
+ max-width: 390px;
1159
+ }
1160
+
1161
+ .dc-system-status span {
1162
+ background: oklch(0.130 0.012 55 / 0.66);
1163
+ border-color: oklch(0.830 0.120 76 / 0.22);
1164
+ color: oklch(0.880 0.018 78);
1165
+ }
1166
+
1167
+ .dc-focus-grid {
1168
+ align-items: start !important;
1169
+ display: grid !important;
1170
+ gap: 16px !important;
1171
+ grid-template-columns: minmax(0, 1fr) minmax(330px, 390px);
1172
+ }
1173
+
1174
+ .dc-composer-panel {
1175
+ background:
1176
+ radial-gradient(circle at 4% 0%, oklch(0.830 0.120 76 / 0.08), transparent 18rem),
1177
+ linear-gradient(180deg, var(--dc-composer-soft), var(--dc-composer));
1178
+ border: 1px solid oklch(0.830 0.120 76 / 0.18);
1179
+ border-radius: var(--dc-radius-xl);
1180
+ box-shadow: 0 18px 50px rgba(0, 0, 0, 0.22);
1181
+ padding: 18px;
1182
+ }
1183
+
1184
+ .dc-composer-head {
1185
+ align-items: flex-start;
1186
+ margin-bottom: 10px;
1187
+ }
1188
+
1189
+ .dc-composer-head h2 {
1190
+ color: var(--dc-ink-main);
1191
+ font-size: clamp(1.08rem, 1.8vw, 1.32rem);
1192
+ font-weight: 760;
1193
+ }
1194
+
1195
+ .dc-composer-head p {
1196
+ color: var(--dc-ink-muted);
1197
+ max-width: 58ch;
1198
+ }
1199
+
1200
+ .dc-trust-pill {
1201
+ background: oklch(0.830 0.120 76 / 0.10);
1202
+ border-color: oklch(0.830 0.120 76 / 0.28);
1203
+ color: oklch(0.900 0.060 76);
1204
+ }
1205
+
1206
+ .dc-inline-notice {
1207
+ background: oklch(0.185 0.018 55 / 0.58);
1208
+ border-color: oklch(0.830 0.120 76 / 0.18);
1209
+ color: oklch(0.880 0.014 78);
1210
+ margin-bottom: 8px;
1211
+ }
1212
+
1213
+ .dc-dream-input,
1214
+ .dc-dream-input .block,
1215
+ .dc-dream-input .wrap,
1216
+ .dc-dream-input .input-container,
1217
+ .dc-dream-input .container {
1218
+ background: transparent !important;
1219
+ border: 0 !important;
1220
+ box-shadow: none !important;
1221
+ }
1222
+
1223
+ .dc-dream-input textarea {
1224
+ background: transparent !important;
1225
+ border: 0 !important;
1226
+ box-shadow: none !important;
1227
+ color: var(--dc-ink-main) !important;
1228
+ font-size: 1.08rem !important;
1229
+ line-height: 1.58 !important;
1230
+ min-height: 138px !important;
1231
+ padding: 8px 2px 12px !important;
1232
+ }
1233
+
1234
+ .dc-dream-input textarea::placeholder {
1235
+ color: oklch(0.690 0.016 78) !important;
1236
+ }
1237
+
1238
+ .dc-composer-tools {
1239
+ align-items: end !important;
1240
+ display: grid !important;
1241
+ gap: 8px !important;
1242
+ grid-template-columns: minmax(112px, 0.8fr) minmax(112px, 0.8fr) minmax(130px, 1fr) minmax(128px, 0.9fr) minmax(160px, 1fr);
1243
+ }
1244
+
1245
+ .dc-composer-tools > * {
1246
+ min-width: 0 !important;
1247
+ }
1248
+
1249
+ .dc-tool-tile,
1250
+ .dc-tool-select,
1251
+ .dc-tool-button,
1252
+ .dc-upload-button,
1253
+ .dc-voice-tool,
1254
+ .dc-send-button {
1255
+ min-width: 0 !important;
1256
+ }
1257
+
1258
+ .dc-tool-tile .block,
1259
+ .dc-tool-tile .wrap,
1260
+ .dc-tool-tile .container,
1261
+ .dc-tool-tile .input-container,
1262
+ .dc-tool-select .block,
1263
+ .dc-tool-select .wrap,
1264
+ .dc-tool-select .container,
1265
+ .dc-tool-select .input-container {
1266
+ background: oklch(0.190 0.012 55 / 0.86) !important;
1267
+ border-color: oklch(0.830 0.120 76 / 0.17) !important;
1268
+ border-radius: 16px !important;
1269
+ color: var(--dc-ink-main) !important;
1270
+ }
1271
+
1272
+ .dc-tool-tile .image-container,
1273
+ .dc-tool-tile .audio-container,
1274
+ .dc-tool-tile .upload-container,
1275
+ .dc-tool-tile .upload-container button,
1276
+ .dc-tool-tile .audio-container button {
1277
+ background: oklch(0.190 0.012 55 / 0.86) !important;
1278
+ border-color: oklch(0.830 0.120 76 / 0.18) !important;
1279
+ border-radius: 16px !important;
1280
+ color: var(--dc-ink-muted) !important;
1281
+ min-height: 68px !important;
1282
+ }
1283
+
1284
+ .dc-tool-tile .image-container,
1285
+ .dc-tool-tile .audio-container {
1286
+ min-height: 68px !important;
1287
+ }
1288
+
1289
+ .dc-tool-select select,
1290
+ .dc-tool-select input,
1291
+ .dc-tool-select [role="combobox"] {
1292
+ background: oklch(0.190 0.012 55 / 0.86) !important;
1293
+ border-color: transparent !important;
1294
+ color: var(--dc-ink-main) !important;
1295
+ min-height: 42px !important;
1296
+ }
1297
+
1298
+ .dc-upload-button,
1299
+ .dc-upload-button button,
1300
+ .dc-voice-tool,
1301
+ .dc-voice-tool .block,
1302
+ .dc-voice-tool .wrap,
1303
+ .dc-voice-tool .container,
1304
+ .dc-voice-tool .input-container,
1305
+ .dc-voice-tool .audio-container {
1306
+ background: oklch(0.190 0.012 55 / 0.86) !important;
1307
+ border-color: oklch(0.830 0.120 76 / 0.18) !important;
1308
+ border-radius: 999px !important;
1309
+ color: oklch(0.895 0.030 76) !important;
1310
+ min-height: 44px !important;
1311
+ }
1312
+
1313
+ .dc-upload-button button,
1314
+ .dc-voice-tool button {
1315
+ border-radius: 999px !important;
1316
+ min-height: 38px !important;
1317
+ }
1318
+
1319
+ .dc-voice-tool {
1320
+ max-height: 52px !important;
1321
+ overflow: hidden !important;
1322
+ }
1323
+
1324
+ .dc-voice-tool .audio-container {
1325
+ height: 44px !important;
1326
+ min-height: 44px !important;
1327
+ overflow: hidden !important;
1328
+ }
1329
+
1330
+ .dc-composer-panel label,
1331
+ .dc-composer-panel .label-wrap span,
1332
+ .dc-composer-panel [data-testid="block-label"] {
1333
+ color: oklch(0.865 0.018 78) !important;
1334
+ font-size: 0.76rem !important;
1335
+ font-weight: 690 !important;
1336
+ }
1337
+
1338
+ .dc-composer-panel .label-wrap,
1339
+ .dc-composer-panel label.float {
1340
+ background: oklch(0.190 0.012 55 / 0.86) !important;
1341
+ border-color: oklch(0.830 0.120 76 / 0.16) !important;
1342
+ }
1343
+
1344
+ .dc-tool-button,
1345
+ .dc-tool-button button {
1346
+ background: oklch(0.190 0.012 55) !important;
1347
+ border: 1px solid oklch(0.830 0.120 76 / 0.20) !important;
1348
+ border-radius: 999px !important;
1349
+ color: oklch(0.895 0.030 76) !important;
1350
+ min-height: 44px !important;
1351
+ white-space: normal !important;
1352
+ }
1353
+
1354
+ .dc-send-button,
1355
+ .dc-send-button button {
1356
+ background: var(--dc-primary-deep) !important;
1357
+ border: 1px solid var(--dc-primary) !important;
1358
+ border-radius: 999px !important;
1359
+ color: var(--dc-ink-main) !important;
1360
+ min-height: 48px !important;
1361
+ white-space: normal !important;
1362
+ }
1363
+
1364
+ .dc-send-button:hover,
1365
+ .dc-send-button button:hover,
1366
+ .dc-tool-button:hover,
1367
+ .dc-tool-button button:hover {
1368
+ transform: translateY(-1px);
1369
+ }
1370
+
1371
+ .dc-pact-actions {
1372
+ display: grid !important;
1373
+ gap: 8px !important;
1374
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1375
+ }
1376
+
1377
+ .dc-pact-actions button {
1378
+ min-height: 46px !important;
1379
+ white-space: normal !important;
1380
+ }
1381
+
1382
+ .dc-draft-button,
1383
+ .dc-draft-button button {
1384
+ background: oklch(0.295 0.020 62) !important;
1385
+ border: 1px solid oklch(0.830 0.120 76 / 0.20) !important;
1386
+ border-radius: 999px !important;
1387
+ color: var(--dc-ink-main) !important;
1388
+ }
1389
+
1390
+ .dc-seal-button,
1391
+ .dc-seal-button button {
1392
+ background: var(--dc-coral) !important;
1393
+ border: 1px solid var(--dc-coral) !important;
1394
+ border-radius: 999px !important;
1395
+ color: oklch(0.130 0.012 55) !important;
1396
+ }
1397
+
1398
+ .dc-followup-panel,
1399
+ .dc-workflow-shortcuts,
1400
+ .dc-model-routes,
1401
+ .dc-diagnostics,
1402
+ .dc-examples {
1403
+ background: oklch(0.185 0.018 55 / 0.72) !important;
1404
+ border: 1px solid oklch(0.830 0.120 76 / 0.14) !important;
1405
+ border-radius: 18px !important;
1406
+ color: var(--dc-ink-main) !important;
1407
+ overflow: hidden;
1408
+ }
1409
+
1410
+ .dc-stage-nav {
1411
+ background: transparent;
1412
+ border: 0;
1413
+ border-radius: 0;
1414
+ padding: 0;
1415
+ }
1416
+
1417
+ .dc-stage-nav button,
1418
+ .dc-secondary-actions button {
1419
+ background: oklch(0.235 0.018 58) !important;
1420
+ border-color: oklch(0.830 0.120 76 / 0.16) !important;
1421
+ border-radius: 999px !important;
1422
+ }
1423
+
1424
+ .dc-inspector,
1425
+ .dc-timeline-shell {
1426
+ background: oklch(0.235 0.018 58 / 0.92);
1427
+ border-color: oklch(0.830 0.120 76 / 0.15);
1428
+ border-radius: 20px;
1429
+ }
1430
+
1431
+ .dc-inspector {
1432
+ box-shadow: none;
1433
+ }
1434
+
1435
+ .dc-timeline-shell {
1436
+ margin-top: 2px;
1437
+ }
1438
+
1439
+ .dc-timeline-event {
1440
+ background: oklch(0.185 0.018 55 / 0.70);
1441
+ border-color: oklch(0.830 0.120 76 / 0.12);
1442
+ }
1443
+
1444
+ .dc-event-meta span,
1445
+ .dc-evidence-chip,
1446
+ .dc-inspector dl div {
1447
+ background: oklch(0.185 0.018 55 / 0.68);
1448
+ border-color: oklch(0.830 0.120 76 / 0.13);
1449
+ }
1450
+
1451
+ .dc-inspector dt,
1452
+ .dc-inspector h3,
1453
+ .dc-inspector-kicker,
1454
+ .dc-permit-row span,
1455
+ .dc-timeline-head > span {
1456
+ color: oklch(0.830 0.120 76);
1457
+ }
1458
+
1459
+ @media (max-width: 980px) {
1460
+ .dc-focus-grid {
1461
+ grid-template-columns: 1fr;
1462
+ }
1463
+
1464
+ .dc-composer-tools {
1465
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1466
+ }
1467
+
1468
+ .dc-send-button {
1469
+ grid-column: span 2;
1470
+ }
1471
+ }
1472
+
1473
+ @media (max-width: 620px) {
1474
+ .dc-app-shell {
1475
+ padding: 10px !important;
1476
+ }
1477
+
1478
+ .dc-statusbar,
1479
+ .dc-composer-panel,
1480
+ .dc-inspector,
1481
+ .dc-timeline-shell,
1482
+ .dc-followup-panel,
1483
+ .dc-workflow-shortcuts {
1484
+ border-radius: 18px;
1485
+ }
1486
+
1487
+ .dc-statusbar {
1488
+ padding: 14px;
1489
+ }
1490
+
1491
+ .dc-composer-panel {
1492
+ padding: 14px;
1493
+ }
1494
+
1495
+ .dc-dream-input textarea {
1496
+ min-height: 128px !important;
1497
+ }
1498
+
1499
+ .dc-composer-tools {
1500
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1501
+ }
1502
+
1503
+ .dc-tool-button,
1504
+ .dc-send-button {
1505
+ grid-column: span 1;
1506
+ }
1507
+
1508
+ .dc-pact-actions {
1509
+ grid-template-columns: 1fr;
1510
+ }
1511
+ }
1512
  """
1513
 
1514
 
 
1530
  with gr.Column(elem_classes=["dc-app-shell"]):
1531
  status_html = gr.HTML()
1532
 
1533
+ with gr.Row(elem_classes=["dc-focus-grid"]):
1534
+ with gr.Column(scale=7, elem_classes=["dc-declaration-column"]):
1535
+ with gr.Column(elem_classes=["dc-composer-panel"]):
1536
+ gr.HTML(
1537
+ """
1538
+ <div class="dc-composer-head">
1539
+ <div>
1540
+ <h2>Tell the customs desk what arrived in the night</h2>
1541
+ <p>One calm place for text, sketch, voice, mood, and model route.</p>
1542
+ </div>
1543
+ <span class="dc-trust-pill">Playful, not diagnostic</span>
1544
+ </div>
1545
+ """.strip()
1546
+ )
1547
+ notice_html = gr.HTML()
1548
+ dream_text = gr.Textbox(
1549
+ label="Dream material",
1550
+ show_label=False,
1551
+ lines=6,
1552
+ value="我梦见一部迟到的电梯,按钮都融化了,我一直到不了 14 楼。",
1553
+ placeholder="Describe a dream, drop in a fragment, or ask the clerk what to do with it.",
1554
+ elem_classes=["dc-dream-input"],
1555
+ )
1556
+ with gr.Row(elem_classes=["dc-composer-tools"]):
1557
+ image_input = gr.UploadButton(
1558
+ "Image",
1559
+ file_types=["image"],
1560
+ file_count="single",
1561
+ elem_classes=["dc-upload-button", "dc-image-tool"],
1562
+ )
1563
+ audio_input = gr.UploadButton(
1564
+ "Voice",
1565
+ file_types=["audio"],
1566
+ file_count="single",
1567
+ elem_classes=["dc-upload-button", "dc-audio-tool"],
1568
+ )
1569
+ mood = gr.Dropdown(
1570
+ label="Mood",
1571
+ show_label=False,
1572
+ choices=["foggy", "anxious", "curious", "tired", "restless", "calm"],
1573
+ value="foggy",
1574
+ elem_classes=["dc-tool-select"],
1575
+ )
1576
+ add_btn = gr.Button("+ Add", elem_classes=["dc-tool-button"])
1577
+ send_btn = gr.Button("Send", elem_classes=["dc-primary", "dc-send-button"])
1578
+ with gr.Accordion("Model routes", open=False, elem_classes=["dc-model-routes"]):
1579
+ with gr.Row(elem_classes=["dc-route-row"]):
1580
+ text_backend = gr.Dropdown(
1581
+ label="Text",
1582
+ choices=["demo", "model", "ollama"],
1583
+ value="demo",
1584
+ elem_classes=["dc-tool-select"],
1585
+ )
1586
+ vision_backend = gr.Dropdown(
1587
+ label="Vision",
1588
+ choices=["demo", "model", "ollama"],
1589
+ value="demo",
1590
+ elem_classes=["dc-tool-select"],
1591
+ )
1592
+ with gr.Accordion("Answer or refine the pact", open=False, elem_classes=["dc-followup-panel"]):
1593
+ with gr.Row(elem_classes=["dc-followup-grid"]):
1594
+ answer_text = gr.Textbox(
1595
+ label="Answer to the clerk",
1596
+ lines=2,
1597
+ value="我想和它结盟,但今天只想完成一件很小的事。",
1598
+ placeholder="Answer a customs question, or leave blank and skip.",
1599
+ )
1600
+ revision_request = gr.Textbox(
1601
+ label="Revision request",
1602
+ lines=2,
1603
+ value="Make it gentler and more specific for today.",
1604
+ placeholder="Try: make it stranger, make it gentler, or make the action smaller.",
1605
+ )
1606
+ with gr.Row(elem_classes=["dc-secondary-actions"]):
1607
+ ask_btn = gr.Button("Ask another question")
1608
+ answer_btn = gr.Button("Answer question")
1609
+ skip_btn = gr.Button("Skip question")
1610
+ revise_btn = gr.Button("Revise pact")
1611
+ new_btn = gr.Button("Start new")
1612
+ with gr.Accordion("Workflow shortcuts", open=False, elem_classes=["dc-workflow-shortcuts"]):
1613
+ with gr.Row(elem_classes=["dc-stage-nav"]):
1614
+ declare_phase_btn = gr.Button("Declare")
1615
+ inspect_phase_btn = gr.Button("Inspect")
1616
+ draft_phase_btn = gr.Button("Draft")
1617
+ seal_phase_btn = gr.Button("Seal", elem_classes=["dc-stage-seal"])
1618
+
1619
+ with gr.Column(scale=4, elem_classes=["dc-side-rail"]):
1620
  inspector_html = gr.HTML()
1621
+ with gr.Row(elem_classes=["dc-pact-actions"]):
1622
+ draft_btn = gr.Button("Draft pact", elem_classes=["dc-draft-button"])
1623
+ seal_btn = gr.Button("Seal today's pact", elem_classes=["dc-seal-button"])
1624
+ sealed_html = gr.HTML(elem_classes=["dc-sealed-output"])
1625
 
1626
+ timeline_html = gr.HTML()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1627
 
1628
  with gr.Accordion("Diagnostics", open=False, elem_classes=["dc-diagnostics"]):
1629
  debug_json = gr.Code(label="Session state", language="json")
1630
 
1631
+ with gr.Accordion("Examples", open=False, elem_classes=["dc-examples"]):
1632
+ gr.Examples(
1633
+ examples=[
1634
+ [
1635
+ "梦见一间便利店漂在海上,收银员让我用旧日历付款。",
1636
+ "curious",
1637
+ "我想知道它到底在保护什么。",
1638
+ ],
1639
+ [
1640
+ "I found a tiny border checkpoint inside my pillow. The officer stamped my hand with blue ink.",
1641
+ "restless",
1642
+ "I want a small action that makes tomorrow less loud.",
1643
+ ],
1644
  ],
1645
+ inputs=[dream_text, mood, answer_text],
1646
+ )
 
1647
 
1648
  outputs = _outputs(session_state, status_html, timeline_html, inspector_html, sealed_html, debug_json, notice_html)
1649
  demo.load(initial_workbench_state, outputs=outputs, api_name=False)
1650
 
1651
+ declare_phase_btn.click(
1652
+ add_material_action,
1653
+ inputs=[session_state, dream_text, image_input, audio_input, mood, text_backend, vision_backend],
1654
+ outputs=outputs,
1655
+ api_name=False,
1656
+ )
1657
+ inspect_phase_btn.click(
1658
+ ask_another_question_action,
1659
+ inputs=[session_state, text_backend, vision_backend],
1660
+ outputs=outputs,
1661
+ api_name=False,
1662
+ )
1663
+ draft_phase_btn.click(
1664
+ draft_pact_action,
1665
+ inputs=[session_state, text_backend, vision_backend],
1666
+ outputs=outputs,
1667
+ api_name=False,
1668
+ )
1669
+ seal_phase_btn.click(
1670
+ seal_pact_action,
1671
+ inputs=[session_state, text_backend, vision_backend],
1672
+ outputs=outputs,
1673
+ api_name=False,
1674
+ )
1675
  send_btn.click(
1676
  start_declaration_action,
1677
  inputs=[session_state, dream_text, image_input, audio_input, mood, text_backend, vision_backend],
design-qa.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dream Customs V4 Design QA
2
+
3
+ Date: 2026-06-05
4
+
5
+ Reference: user-provided Codex-style multimodal composer screenshot.
6
+
7
+ ## Checks
8
+
9
+ - First screen uses one rounded multimodal composer instead of separate form blocks.
10
+ - Visible composer tools are limited to Image, Voice, Mood, Add, and Send.
11
+ - Technical Text/Vision routes are collapsed under Model routes.
12
+ - Pact inspector remains visible beside the composer on desktop.
13
+ - Timeline, diagnostics, examples, workflow shortcuts, and refinement controls are secondary.
14
+ - Text-only Chrome flow completed: Send -> Draft pact -> Seal today's pact.
15
+ - Sealed output renders a screenshot-friendly Today's Pact card.
16
+ - No desktop horizontal overflow in Chrome at the inspected viewport.
17
+
18
+ ## Notes
19
+
20
+ - Image and voice use compact upload buttons to avoid large Gradio media drop zones in the first screen.
21
+ - Voice upload remains available as an input to the existing ASR adapter path.
22
+ - The UI keeps safety copy non-diagnostic.
23
+
24
+ final result: passed
docs/handoff.md CHANGED
@@ -4,7 +4,17 @@ Last updated: 2026-06-05
4
 
5
  ## Current State
6
 
7
- The project is in concept/spec stage. A visual HTML concept note already exists at:
 
 
 
 
 
 
 
 
 
 
8
 
9
  - `docs/dream-customs-concept/index.html`
10
 
@@ -14,12 +24,14 @@ Supporting images:
14
  - `docs/dream-customs-concept/assets/multimodal-intake.svg`
15
  - `docs/dream-customs-concept/assets/alliance-card.svg`
16
 
17
- This handoff adds project context documents for implementation:
18
 
19
  - `docs/spec.md`
20
  - `docs/prd.md`
21
  - `docs/handoff.md`
22
  - `docs/superpowers/plans/2026-06-05-dream-customs-mvp.md`
 
 
23
  - `AGENTS.md`
24
 
25
  ## Product Decision
@@ -79,7 +91,7 @@ The product must never present itself as therapy, medical advice, or diagnosis.
79
 
80
  ## Recommended First Implementation
81
 
82
- Create a minimal Python package:
83
 
84
  ```text
85
  app.py
@@ -100,26 +112,26 @@ tests/
100
  test_pipeline.py
101
  ```
102
 
103
- Start with mocked model clients and deterministic examples. Add real model loading only after schema, safety, rendering, and pipeline tests pass.
104
 
105
  ## Implementation Priorities
106
 
107
- 1. Text-only flow.
108
- 2. Card rendering.
109
- 3. Safety layer.
110
- 4. Image clue extraction.
111
- 5. Voice transcription adapter.
112
- 6. Demo examples.
113
- 7. Space packaging.
114
 
115
- ## Known Repository Issue
116
 
117
- The current directory is under git root `/Users/junhaocheng`, whose remote is `adjcjh777/vlarepo.git`. This is likely not the intended Build Small repository. Also, `.git/info/exclude` currently ignores paths beneath this workspace root.
118
 
119
- Do not push these files to the VLA remote by accident. Before implementation, either:
 
 
120
 
121
- 1. initialize a dedicated repo in `/Users/junhaocheng/working-dir/ai-competitions/build-small-hackthon`, or
122
- 2. move this context into the intended Build Small repository/Space repo.
123
 
124
  ## Open Questions For User
125
 
 
4
 
5
  ## Current State
6
 
7
+ The project has shipped a working Gradio MVP and a V4 local UI pass. The current interface centers a warm Codex-like multimodal composer: text, image upload, voice upload, mood, add-material, and send live inside one rounded input surface. Model routes, workflow shortcuts, diagnostics, and examples are collapsed by default.
8
+
9
+ Current V4 UX decisions:
10
+
11
+ - The first screen prioritizes the dream declaration composer and pact inspector.
12
+ - Image and voice use compact upload buttons to avoid large Gradio media drop zones in the main composer.
13
+ - `Draft pact` and `Seal today's pact` sit beside the live pact inspector.
14
+ - The timeline is a supporting "Story so far" section below the first-screen workflow.
15
+ - Technical model route controls are available but not first-level visual chrome.
16
+
17
+ The original visual HTML concept note remains useful background:
18
 
19
  - `docs/dream-customs-concept/index.html`
20
 
 
24
  - `docs/dream-customs-concept/assets/multimodal-intake.svg`
25
  - `docs/dream-customs-concept/assets/alliance-card.svg`
26
 
27
+ Project context documents:
28
 
29
  - `docs/spec.md`
30
  - `docs/prd.md`
31
  - `docs/handoff.md`
32
  - `docs/superpowers/plans/2026-06-05-dream-customs-mvp.md`
33
+ - `docs/superpowers/plans/2026-06-05-dream-customs-uiux-v2.md`
34
+ - `docs/superpowers/plans/2026-06-05-dream-customs-uiux-v3.md`
35
  - `AGENTS.md`
36
 
37
  ## Product Decision
 
91
 
92
  ## Recommended First Implementation
93
 
94
+ The minimal Python package already exists:
95
 
96
  ```text
97
  app.py
 
112
  test_pipeline.py
113
  ```
114
 
115
+ The current implementation keeps deterministic demo clients as the default and exposes optional model routes. For V3, do not rebuild the pipeline from scratch. Fix the app shell, control hierarchy, phase actions, and visual polish while preserving the existing `CustomsSession` state flow.
116
 
117
  ## Implementation Priorities
118
 
119
+ 1. First-screen trust and immediate declaration controls.
120
+ 2. Clickable phase actions.
121
+ 3. Mobile-readable composer and inspector.
122
+ 4. Removal of long blank page regions.
123
+ 5. Tests and local browser verification.
124
+ 6. Branch commit/push and Space update when credentials allow.
 
125
 
126
+ ## Repository Status
127
 
128
+ This directory is now a dedicated Dream Customs repository:
129
 
130
+ - Local path: `/Users/junhaocheng/working-dir/ai-competitions/build-small-hackthon`
131
+ - GitHub remote: `https://github.com/adjcjh777/dream-customs-build-small.git`
132
+ - Hugging Face Space remote: `https://huggingface.co/spaces/build-small-hackathon/dream-customs`
133
 
134
+ Continue to confirm `git remote -v` before pushing, but the earlier VLA remote warning is no longer the active state.
 
135
 
136
  ## Open Questions For User
137
 
dream_customs/app_logic.py CHANGED
@@ -39,6 +39,10 @@ def _file_path(value: Any) -> str:
39
  return value
40
  if isinstance(value, dict):
41
  return str(value.get("path") or value.get("name") or "")
 
 
 
 
42
  if isinstance(value, (list, tuple)) and value:
43
  return _file_path(value[0])
44
  return ""
 
39
  return value
40
  if isinstance(value, dict):
41
  return str(value.get("path") or value.get("name") or "")
42
+ if hasattr(value, "path"):
43
+ return str(getattr(value, "path") or "")
44
+ if hasattr(value, "name"):
45
+ return str(getattr(value, "name") or "")
46
  if isinstance(value, (list, tuple)) and value:
47
  return _file_path(value[0])
48
  return ""
dream_customs/render.py CHANGED
@@ -27,26 +27,20 @@ def _evidence_chip(item: EvidenceItem) -> str:
27
 
28
 
29
  def render_status_bar(session: CustomsSession, text_backend: str = "demo", vision_backend: str = "demo") -> str:
30
- safety = "Support note ready" if session.safety_flags else "Playful reflection, not medical advice"
31
  return f"""
32
  <header class="dc-statusbar">
33
  <div class="dc-brand-lockup">
34
  <span class="dc-brand-mark">DC</span>
35
  <div>
36
  <h1>Dream Customs / 梦境海关</h1>
37
- <p>Night desk for filing dream fragments into today's pact.</p>
38
  </div>
39
  </div>
40
- <nav class="dc-phase-rail" aria-label="Dream Customs phase">
41
- <span class="{'is-active' if session.phase in ['empty', 'declaring'] else ''}">Declare</span>
42
- <span class="{'is-active' if session.phase == 'negotiating' else ''}">Inspect</span>
43
- <span class="{'is-active' if session.phase == 'drafting' else ''}">Draft</span>
44
- <span class="{'is-active' if session.phase == 'sealed' else ''}">Seal</span>
45
- </nav>
46
  <div class="dc-system-status">
47
- <span>{escape(_phase_label(session.phase))}</span>
48
- <span>Text: {escape(text_backend or "demo")}</span>
49
- <span>Vision: {escape(vision_backend or "demo")}</span>
50
  <span>{escape(safety)}</span>
51
  </div>
52
  </header>
@@ -96,8 +90,9 @@ def render_timeline(session: CustomsSession) -> str:
96
  <section class="dc-timeline-shell" aria-label="Dream Customs timeline">
97
  <div class="dc-timeline-head">
98
  <div>
99
- <h2>Customs timeline</h2>
100
- <p>Evidence, questions, answers, and pact drafts stay in one file.</p>
 
101
  </div>
102
  <span>{session.evidence_count()} filed</span>
103
  </div>
@@ -117,7 +112,7 @@ def render_pact_inspector(session: CustomsSession) -> str:
117
  <aside class="dc-inspector is-waiting" aria-label="Pact inspector">
118
  <div class="dc-inspector-kicker">Pact inspector</div>
119
  <h2>No pact drafted yet</h2>
120
- <p>Add material, ask a customs question, then draft a pact when the file feels useful enough.</p>
121
  <dl>
122
  <div><dt>Evidence</dt><dd>{session.evidence_count()} filed</dd></div>
123
  <div><dt>Phase</dt><dd>{escape(_phase_label(session.phase))}</dd></div>
@@ -176,14 +171,14 @@ def render_pact_card(card: PactCard) -> str:
176
  return f"""
177
  <style>
178
  .pact-card {{
179
- color: oklch(0.965 0.010 235);
180
  background:
181
- radial-gradient(circle at 78% 0%, oklch(0.705 0.160 28 / 0.18), transparent 28%),
182
- linear-gradient(135deg, oklch(0.205 0.035 238), oklch(0.145 0.030 238));
183
- border: 2px solid oklch(0.705 0.160 28);
184
- border-radius: 14px;
185
- box-shadow: 0 8px 0 oklch(0.080 0.010 250 / 0.55);
186
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
187
  line-height: 1.55;
188
  margin: 8px 0;
189
  max-width: 860px;
@@ -191,7 +186,7 @@ def render_pact_card(card: PactCard) -> str:
191
  }}
192
  .pact-header {{
193
  align-items: center;
194
- border-bottom: 1px solid oklch(0.430 0.055 235);
195
  display: flex;
196
  font-weight: 720;
197
  justify-content: space-between;
@@ -199,14 +194,14 @@ def render_pact_card(card: PactCard) -> str:
199
  padding-bottom: 10px;
200
  }}
201
  .pact-card h2 {{
202
- color: oklch(0.965 0.010 235) !important;
203
  font-size: 2rem;
204
  line-height: 1.1;
205
  margin: 0 0 4px;
206
  text-wrap: balance;
207
  }}
208
  .pact-card h3 {{
209
- color: oklch(0.820 0.110 185) !important;
210
  font-size: 1.35rem;
211
  margin: 0 0 18px;
212
  }}
@@ -215,18 +210,19 @@ def render_pact_card(card: PactCard) -> str:
215
  .pact-card div,
216
  .pact-card span {{
217
  color: oklch(0.900 0.018 235) !important;
 
218
  }}
219
  .pact-header span {{
220
- color: oklch(0.965 0.010 235) !important;
221
  }}
222
  .dc-label {{
223
- color: oklch(0.820 0.110 185) !important;
224
  font-weight: 720;
225
  }}
226
  .dc-seal {{
227
- border: 2px solid oklch(0.705 0.160 28);
228
  border-radius: 999px;
229
- color: oklch(0.705 0.160 28) !important;
230
  display: inline-block;
231
  font-weight: 820;
232
  margin-top: 12px;
@@ -234,8 +230,8 @@ def render_pact_card(card: PactCard) -> str:
234
  transform: rotate(-4deg);
235
  }}
236
  .dc-sealed-safety {{
237
- background: oklch(0.705 0.160 28 / 0.13);
238
- border: 1px solid oklch(0.705 0.160 28 / 0.72);
239
  border-radius: 10px;
240
  padding: 12px 14px;
241
  }}
 
27
 
28
 
29
  def render_status_bar(session: CustomsSession, text_backend: str = "demo", vision_backend: str = "demo") -> str:
30
+ safety = "Support note ready" if session.safety_flags else "Playful reflection, not diagnosis"
31
  return f"""
32
  <header class="dc-statusbar">
33
  <div class="dc-brand-lockup">
34
  <span class="dc-brand-mark">DC</span>
35
  <div>
36
  <h1>Dream Customs / 梦境海关</h1>
37
+ <p>Turn last night's strange visitor into one gentle pact for the day: a practical suggestion, a small weird task, and a bedtime release.</p>
38
  </div>
39
  </div>
 
 
 
 
 
 
40
  <div class="dc-system-status">
41
+ <span>Current: {escape(_phase_label(session.phase))}</span>
42
+ <span>Text {escape(text_backend or "demo")}</span>
43
+ <span>Vision {escape(vision_backend or "demo")}</span>
44
  <span>{escape(safety)}</span>
45
  </div>
46
  </header>
 
90
  <section class="dc-timeline-shell" aria-label="Dream Customs timeline">
91
  <div class="dc-timeline-head">
92
  <div>
93
+ <span class="dc-section-kicker">Customs timeline</span>
94
+ <h2>Story so far</h2>
95
+ <p>Dream fragments, clerk questions, and pact drafts stay together so the ritual feels easy to follow.</p>
96
  </div>
97
  <span>{session.evidence_count()} filed</span>
98
  </div>
 
112
  <aside class="dc-inspector is-waiting" aria-label="Pact inspector">
113
  <div class="dc-inspector-kicker">Pact inspector</div>
114
  <h2>No pact drafted yet</h2>
115
+ <p>Your alliance card will appear here after the clerk has enough dream material. Text-only is enough to begin.</p>
116
  <dl>
117
  <div><dt>Evidence</dt><dd>{session.evidence_count()} filed</dd></div>
118
  <div><dt>Phase</dt><dd>{escape(_phase_label(session.phase))}</dd></div>
 
171
  return f"""
172
  <style>
173
  .pact-card {{
174
+ color: oklch(0.170 0.016 55);
175
  background:
176
+ radial-gradient(circle at 88% 0%, oklch(0.830 0.120 76 / 0.20), transparent 26%),
177
+ linear-gradient(135deg, oklch(0.965 0.014 82), oklch(0.900 0.028 72));
178
+ border: 1px solid oklch(0.700 0.140 32 / 0.42);
179
+ border-radius: 18px;
180
+ box-shadow: 0 18px 38px oklch(0.130 0.012 55 / 0.34);
181
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
182
  line-height: 1.55;
183
  margin: 8px 0;
184
  max-width: 860px;
 
186
  }}
187
  .pact-header {{
188
  align-items: center;
189
+ border-bottom: 1px solid oklch(0.700 0.140 32 / 0.22);
190
  display: flex;
191
  font-weight: 720;
192
  justify-content: space-between;
 
194
  padding-bottom: 10px;
195
  }}
196
  .pact-card h2 {{
197
+ color: oklch(0.170 0.016 55) !important;
198
  font-size: 2rem;
199
  line-height: 1.1;
200
  margin: 0 0 4px;
201
  text-wrap: balance;
202
  }}
203
  .pact-card h3 {{
204
+ color: oklch(0.455 0.090 155) !important;
205
  font-size: 1.35rem;
206
  margin: 0 0 18px;
207
  }}
 
210
  .pact-card div,
211
  .pact-card span {{
212
  color: oklch(0.900 0.018 235) !important;
213
+ color: oklch(0.230 0.018 55) !important;
214
  }}
215
  .pact-header span {{
216
+ color: oklch(0.280 0.020 55) !important;
217
  }}
218
  .dc-label {{
219
+ color: oklch(0.455 0.090 155) !important;
220
  font-weight: 720;
221
  }}
222
  .dc-seal {{
223
+ border: 2px solid oklch(0.700 0.140 32);
224
  border-radius: 999px;
225
+ color: oklch(0.700 0.140 32) !important;
226
  display: inline-block;
227
  font-weight: 820;
228
  margin-top: 12px;
 
230
  transform: rotate(-4deg);
231
  }}
232
  .dc-sealed-safety {{
233
+ background: oklch(0.700 0.140 32 / 0.10);
234
+ border: 1px solid oklch(0.700 0.140 32 / 0.34);
235
  border-radius: 10px;
236
  padding: 12px 14px;
237
  }}