lilblueyes commited on
Commit
35605b9
·
1 Parent(s): b1385df

Redesign SignSpeak demo interface

Browse files
Files changed (2) hide show
  1. app.py +90 -94
  2. assets/styles.css +291 -43
app.py CHANGED
@@ -12,6 +12,32 @@ from signspeak.tts import generate_tts
12
  APP_DIR = Path(__file__).resolve().parent
13
  CUSTOM_CSS = (APP_DIR / "assets" / "styles.css").read_text(encoding="utf-8")
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  def run_asl_brick(video_file: str | None) -> tuple[str, dict, str]:
17
  try:
@@ -56,81 +82,73 @@ def build_video_input(label: str) -> gr.Video:
56
  with gr.Blocks(title="SignSpeak Local") as demo:
57
  gr.HTML(
58
  """
59
- <section id="hero">
60
- <h1>SignSpeak Local</h1>
61
- <p>
62
- ASL video to expressive speech, with independent ASL, llama.cpp,
63
- and Qwen3-TTS bricks for controlled demo runs.
64
- </p>
65
- <div class="badge-row">
66
- <span class="badge">ASL video</span>
67
- <span class="badge">live camera</span>
68
- <span class="badge">llama.cpp</span>
69
- <span class="badge">Qwen3-TTS</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  </div>
71
- </section>
72
  """
73
  )
74
 
75
  with gr.Tabs():
76
- with gr.Tab("Full pipeline"):
77
- with gr.Row():
78
- with gr.Column(scale=1):
79
- gr.Markdown("### Input")
80
  full_video_input = build_video_input("Video or camera capture")
81
- full_language_input = gr.Dropdown(
82
- label="Language",
83
- choices=[
84
- "Auto",
85
- "Chinese",
86
- "English",
87
- "Japanese",
88
- "Korean",
89
- "German",
90
- "French",
91
- "Russian",
92
- "Portuguese",
93
- "Spanish",
94
- "Italian",
95
- ],
96
- value="English",
97
- )
98
- full_speaker_input = gr.Dropdown(
99
- label="Speaker",
100
- choices=[
101
- "Vivian",
102
- "Serena",
103
- "Uncle_Fu",
104
- "Dylan",
105
- "Eric",
106
- "Ryan",
107
- "Aiden",
108
- "Ono_Anna",
109
- "Sohee",
110
- ],
111
- value="Ryan",
112
- )
113
- run_full_button = gr.Button(
114
- "Run full pipeline",
115
- elem_id="run_full",
116
- )
117
-
118
- with gr.Column(scale=1):
119
- gr.Markdown("### Output")
120
  full_summary_output = gr.Textbox(label="ASL summary", lines=4)
121
  full_subtitle_output = gr.Textbox(label="Subtitle", lines=3)
122
  full_instruction_output = gr.Textbox(label="Voice instruction", lines=3)
123
- full_audio_output = gr.Audio(label="Generated audio", type="filepath")
124
 
125
- with gr.Row():
126
- full_intent_output = gr.Code(label="Intent JSON", language="json", lines=12)
127
- full_asl_json_output = gr.JSON(label="ASL structured output")
128
- full_llm_json_output = gr.JSON(label="LLM structured output")
 
129
 
130
- with gr.Tab("Brick tests"):
131
- with gr.Row():
132
- with gr.Column(scale=1):
133
- gr.Markdown("### ASL video")
134
  asl_video_input = build_video_input("Video or camera capture")
135
  run_asl_button = gr.Button("Run ASL brick", elem_id="run_asl")
136
  asl_summary_output = gr.Textbox(label="ASL summary", lines=4)
@@ -138,9 +156,9 @@ with gr.Blocks(title="SignSpeak Local") as demo:
138
  with gr.Column(scale=1):
139
  asl_json_output = gr.JSON(label="ASL structured output")
140
 
141
- with gr.Row():
142
- with gr.Column(scale=1):
143
- gr.Markdown("### llama.cpp")
144
  intent_input = gr.Code(
145
  label="Intent JSON",
146
  value=json_text(DEFAULT_INTENT),
@@ -156,39 +174,17 @@ with gr.Blocks(title="SignSpeak Local") as demo:
156
  instruction_output = gr.Textbox(label="Voice instruction", lines=3)
157
  llm_json_output = gr.JSON(label="LLM structured output")
158
 
159
- with gr.Row():
160
- with gr.Column(scale=1):
161
- gr.Markdown("### Qwen3-TTS")
162
  tts_language_input = gr.Dropdown(
163
  label="Language",
164
- choices=[
165
- "Auto",
166
- "Chinese",
167
- "English",
168
- "Japanese",
169
- "Korean",
170
- "German",
171
- "French",
172
- "Russian",
173
- "Portuguese",
174
- "Spanish",
175
- "Italian",
176
- ],
177
  value="English",
178
  )
179
  tts_speaker_input = gr.Dropdown(
180
  label="Speaker",
181
- choices=[
182
- "Vivian",
183
- "Serena",
184
- "Uncle_Fu",
185
- "Dylan",
186
- "Eric",
187
- "Ryan",
188
- "Aiden",
189
- "Ono_Anna",
190
- "Sohee",
191
- ],
192
  value="Ryan",
193
  )
194
  run_tts_button = gr.Button("Generate speech", elem_id="run_tts")
 
12
  APP_DIR = Path(__file__).resolve().parent
13
  CUSTOM_CSS = (APP_DIR / "assets" / "styles.css").read_text(encoding="utf-8")
14
 
15
+ LANGUAGE_CHOICES = [
16
+ "Auto",
17
+ "Chinese",
18
+ "English",
19
+ "Japanese",
20
+ "Korean",
21
+ "German",
22
+ "French",
23
+ "Russian",
24
+ "Portuguese",
25
+ "Spanish",
26
+ "Italian",
27
+ ]
28
+
29
+ SPEAKER_CHOICES = [
30
+ "Vivian",
31
+ "Serena",
32
+ "Uncle_Fu",
33
+ "Dylan",
34
+ "Eric",
35
+ "Ryan",
36
+ "Aiden",
37
+ "Ono_Anna",
38
+ "Sohee",
39
+ ]
40
+
41
 
42
  def run_asl_brick(video_file: str | None) -> tuple[str, dict, str]:
43
  try:
 
82
  with gr.Blocks(title="SignSpeak Local") as demo:
83
  gr.HTML(
84
  """
85
+ <main id="hero">
86
+ <div class="brand-lockup">
87
+ <span class="brand-mark" aria-hidden="true"></span>
88
+ <div>
89
+ <p class="eyebrow">Local ASL speech console</p>
90
+ <h1>SignSpeak Local</h1>
91
+ </div>
92
+ </div>
93
+ <div class="hero-grid">
94
+ <div>
95
+ <p class="hero-copy">
96
+ Translate camera or uploaded ASL clips into expressive speech with a
97
+ controllable ASL, llama.cpp, and Qwen3-TTS pipeline.
98
+ </p>
99
+ <div class="pipeline-rail" aria-label="Pipeline stages">
100
+ <span>Capture</span>
101
+ <span>ASL</span>
102
+ <span>Intent</span>
103
+ <span>Voice</span>
104
+ </div>
105
+ </div>
106
+ <div class="system-strip" aria-label="System capabilities">
107
+ <span>Camera ready</span>
108
+ <span>Offline LLM</span>
109
+ <span>Expressive TTS</span>
110
+ </div>
111
  </div>
112
+ </main>
113
  """
114
  )
115
 
116
  with gr.Tabs():
117
+ with gr.Tab("Run demo"):
118
+ with gr.Row(elem_classes=["demo-grid"]):
119
+ with gr.Column(scale=6, elem_classes=["panel-shell", "input-panel"]):
120
+ gr.HTML('<div class="section-kicker">01 Capture</div>')
121
  full_video_input = build_video_input("Video or camera capture")
122
+ with gr.Row(elem_classes=["control-row"]):
123
+ full_language_input = gr.Dropdown(
124
+ label="Language",
125
+ choices=LANGUAGE_CHOICES,
126
+ value="English",
127
+ )
128
+ full_speaker_input = gr.Dropdown(
129
+ label="Speaker",
130
+ choices=SPEAKER_CHOICES,
131
+ value="Ryan",
132
+ )
133
+ run_full_button = gr.Button("Run full pipeline", elem_id="run_full")
134
+
135
+ with gr.Column(scale=5, elem_classes=["panel-shell", "output-panel"]):
136
+ gr.HTML('<div class="section-kicker">02 Speech output</div>')
137
+ full_audio_output = gr.Audio(label="Generated audio", type="filepath")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  full_summary_output = gr.Textbox(label="ASL summary", lines=4)
139
  full_subtitle_output = gr.Textbox(label="Subtitle", lines=3)
140
  full_instruction_output = gr.Textbox(label="Voice instruction", lines=3)
 
141
 
142
+ with gr.Accordion("Pipeline diagnostics", open=False):
143
+ with gr.Row(elem_classes=["diagnostic-grid"]):
144
+ full_intent_output = gr.Code(label="Intent JSON", language="json", lines=12)
145
+ full_asl_json_output = gr.JSON(label="ASL structured output")
146
+ full_llm_json_output = gr.JSON(label="LLM structured output")
147
 
148
+ with gr.Tab("Inspect bricks"):
149
+ with gr.Row(elem_classes=["brick-grid"]):
150
+ with gr.Column(scale=1, elem_classes=["panel-shell"]):
151
+ gr.HTML('<div class="section-kicker">ASL brick</div>')
152
  asl_video_input = build_video_input("Video or camera capture")
153
  run_asl_button = gr.Button("Run ASL brick", elem_id="run_asl")
154
  asl_summary_output = gr.Textbox(label="ASL summary", lines=4)
 
156
  with gr.Column(scale=1):
157
  asl_json_output = gr.JSON(label="ASL structured output")
158
 
159
+ with gr.Row(elem_classes=["brick-grid"]):
160
+ with gr.Column(scale=1, elem_classes=["panel-shell"]):
161
+ gr.HTML('<div class="section-kicker">llama.cpp brick</div>')
162
  intent_input = gr.Code(
163
  label="Intent JSON",
164
  value=json_text(DEFAULT_INTENT),
 
174
  instruction_output = gr.Textbox(label="Voice instruction", lines=3)
175
  llm_json_output = gr.JSON(label="LLM structured output")
176
 
177
+ with gr.Row(elem_classes=["brick-grid"]):
178
+ with gr.Column(scale=1, elem_classes=["panel-shell"]):
179
+ gr.HTML('<div class="section-kicker">Qwen3-TTS brick</div>')
180
  tts_language_input = gr.Dropdown(
181
  label="Language",
182
+ choices=LANGUAGE_CHOICES,
 
 
 
 
 
 
 
 
 
 
 
 
183
  value="English",
184
  )
185
  tts_speaker_input = gr.Dropdown(
186
  label="Speaker",
187
+ choices=SPEAKER_CHOICES,
 
 
 
 
 
 
 
 
 
 
188
  value="Ryan",
189
  )
190
  run_tts_button = gr.Button("Generate speech", elem_id="run_tts")
assets/styles.css CHANGED
@@ -1,64 +1,228 @@
1
  :root {
2
- --bg: #080a12;
3
- --panel: rgba(255, 255, 255, 0.08);
4
- --panel-strong: rgba(255, 255, 255, 0.12);
5
- --panel-border: rgba(255, 255, 255, 0.16);
 
 
6
  --text: #f8fafc;
7
- --muted: #a8b3c7;
8
- --accent: #2dd4bf;
9
- --accent-2: #818cf8;
10
- --warm: #f59e0b;
11
- --danger: #f43f5e;
 
 
 
 
 
12
  }
13
 
14
  .gradio-container {
15
  background:
16
- linear-gradient(135deg, #080a12 0%, #101827 52%, #111322 100%) !important;
 
17
  color: var(--text) !important;
18
- font-family: Inter, ui-sans-serif, system-ui, sans-serif !important;
 
 
 
 
 
19
  }
20
 
21
  #hero {
 
 
 
 
 
 
22
  padding: 24px;
23
- border: 1px solid var(--panel-border);
 
 
 
 
 
 
 
 
 
 
 
24
  border-radius: 8px;
25
- background: linear-gradient(135deg, rgba(45, 212, 191, 0.14), rgba(129, 140, 248, 0.10));
26
- box-shadow: 0 18px 52px rgba(0, 0, 0, 0.28);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
  #hero h1 {
30
- font-size: 38px;
31
- line-height: 1.08;
32
- margin-bottom: 8px;
 
33
  letter-spacing: 0;
34
  }
35
 
36
- #hero p {
37
- color: var(--muted);
38
- font-size: 16px;
 
 
 
 
 
 
39
  max-width: 760px;
 
 
 
 
40
  }
41
 
42
- .badge-row {
43
- display: flex;
44
- flex-wrap: wrap;
45
  gap: 8px;
46
- margin-top: 14px;
47
  }
48
 
49
- .badge {
50
- padding: 7px 10px;
 
 
 
 
51
  border-radius: 8px;
52
- background: rgba(255, 255, 255, 0.08);
53
- border: 1px solid rgba(255, 255, 255, 0.16);
54
- color: #dbeafe;
55
- font-weight: 700;
56
  font-size: 13px;
 
57
  }
58
 
59
- .stage-title {
60
- margin: 8px 0 4px;
61
- color: #e2e8f0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
 
64
  .block,
@@ -67,52 +231,136 @@
67
  border-radius: 8px !important;
68
  }
69
 
 
 
 
 
 
 
 
 
 
 
 
70
  textarea,
71
  input,
72
  select {
73
- background: rgba(15, 23, 42, 0.78) !important;
74
  color: var(--text) !important;
75
- border-color: rgba(255, 255, 255, 0.14) !important;
 
 
 
 
 
 
 
 
 
 
76
  }
77
 
78
  button.primary,
79
  button {
80
  border-radius: 8px !important;
81
- font-weight: 800 !important;
82
- min-height: 44px !important;
 
 
 
 
 
 
 
 
 
 
 
83
  }
84
 
85
  #run_asl {
86
- background: linear-gradient(135deg, var(--accent), #22c55e) !important;
87
  color: #04111a !important;
88
  border: none !important;
89
  }
90
 
91
  #run_llm {
92
- background: linear-gradient(135deg, var(--accent-2), #3b82f6) !important;
93
  color: white !important;
94
  border: none !important;
95
  }
96
 
97
  #run_tts,
98
  #run_full {
99
- background: linear-gradient(135deg, var(--warm), #ec4899) !important;
100
- color: white !important;
101
  border: none !important;
102
  }
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  .footer-note {
105
  color: var(--muted);
106
  font-size: 13px;
107
  text-align: center;
 
108
  }
109
 
110
- @media (max-width: 720px) {
 
 
 
 
 
 
 
 
 
 
111
  #hero {
112
  padding: 18px;
113
  }
114
 
115
  #hero h1 {
116
- font-size: 30px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
  }
 
1
  :root {
2
+ --app-bg: #080b10;
3
+ --surface: #101722;
4
+ --surface-raised: #151f2c;
5
+ --surface-soft: rgba(255, 255, 255, 0.055);
6
+ --line: rgba(226, 232, 240, 0.14);
7
+ --line-strong: rgba(226, 232, 240, 0.24);
8
  --text: #f8fafc;
9
+ --text-soft: #cbd5e1;
10
+ --muted: #8ea0b8;
11
+ --teal: #2dd4bf;
12
+ --teal-dark: #0f766e;
13
+ --indigo: #818cf8;
14
+ --blue: #38bdf8;
15
+ --amber: #f59e0b;
16
+ --rose: #fb7185;
17
+ --green: #22c55e;
18
+ --shadow: 0 18px 54px rgba(0, 0, 0, 0.34);
19
  }
20
 
21
  .gradio-container {
22
  background:
23
+ linear-gradient(180deg, rgba(45, 212, 191, 0.08) 0, transparent 240px),
24
+ linear-gradient(135deg, #080b10 0%, #0d1420 48%, #111827 100%) !important;
25
  color: var(--text) !important;
26
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
27
+ }
28
+
29
+ .gradio-container > .main {
30
+ max-width: 1280px !important;
31
+ margin: 0 auto !important;
32
  }
33
 
34
  #hero {
35
+ border: 1px solid var(--line);
36
+ border-radius: 8px;
37
+ background:
38
+ linear-gradient(135deg, rgba(45, 212, 191, 0.16), rgba(129, 140, 248, 0.10) 52%, rgba(245, 158, 11, 0.08)),
39
+ rgba(16, 23, 34, 0.86);
40
+ box-shadow: var(--shadow);
41
  padding: 24px;
42
+ margin: 8px 0 18px;
43
+ }
44
+
45
+ .brand-lockup {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 14px;
49
+ }
50
+
51
+ .brand-mark {
52
+ width: 42px;
53
+ height: 42px;
54
  border-radius: 8px;
55
+ background:
56
+ linear-gradient(135deg, var(--teal), var(--indigo));
57
+ border: 1px solid rgba(255, 255, 255, 0.36);
58
+ box-shadow: 0 12px 32px rgba(45, 212, 191, 0.22);
59
+ position: relative;
60
+ flex: 0 0 auto;
61
+ }
62
+
63
+ .brand-mark::after {
64
+ content: "";
65
+ position: absolute;
66
+ inset: 11px;
67
+ border: 2px solid rgba(8, 11, 16, 0.76);
68
+ border-radius: 6px;
69
+ }
70
+
71
+ .eyebrow {
72
+ margin: 0 0 4px;
73
+ color: var(--teal);
74
+ font-size: 12px;
75
+ line-height: 1.2;
76
+ font-weight: 800;
77
+ text-transform: uppercase;
78
+ letter-spacing: 0.08em;
79
  }
80
 
81
  #hero h1 {
82
+ margin: 0;
83
+ color: var(--text);
84
+ font-size: 42px;
85
+ line-height: 1.05;
86
  letter-spacing: 0;
87
  }
88
 
89
+ .hero-grid {
90
+ display: grid;
91
+ grid-template-columns: minmax(0, 1fr) auto;
92
+ gap: 24px;
93
+ align-items: end;
94
+ margin-top: 18px;
95
+ }
96
+
97
+ .hero-copy {
98
  max-width: 760px;
99
+ color: var(--text-soft);
100
+ font-size: 16px;
101
+ line-height: 1.6;
102
+ margin: 0 0 16px;
103
  }
104
 
105
+ .pipeline-rail {
106
+ display: grid;
107
+ grid-template-columns: repeat(4, minmax(0, 1fr));
108
  gap: 8px;
109
+ max-width: 680px;
110
  }
111
 
112
+ .pipeline-rail span {
113
+ min-height: 44px;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ border: 1px solid var(--line);
118
  border-radius: 8px;
119
+ background: rgba(8, 11, 16, 0.42);
120
+ color: #e2e8f0;
 
 
121
  font-size: 13px;
122
+ font-weight: 800;
123
  }
124
 
125
+ .pipeline-rail span:nth-child(1) {
126
+ border-color: rgba(45, 212, 191, 0.40);
127
+ }
128
+
129
+ .pipeline-rail span:nth-child(2) {
130
+ border-color: rgba(34, 197, 94, 0.36);
131
+ }
132
+
133
+ .pipeline-rail span:nth-child(3) {
134
+ border-color: rgba(129, 140, 248, 0.40);
135
+ }
136
+
137
+ .pipeline-rail span:nth-child(4) {
138
+ border-color: rgba(245, 158, 11, 0.40);
139
+ }
140
+
141
+ .system-strip {
142
+ min-width: 220px;
143
+ border: 1px solid var(--line);
144
+ border-radius: 8px;
145
+ background: rgba(8, 11, 16, 0.38);
146
+ padding: 10px;
147
+ display: grid;
148
+ gap: 8px;
149
+ }
150
+
151
+ .system-strip span {
152
+ color: var(--text-soft);
153
+ font-size: 13px;
154
+ font-weight: 700;
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ }
159
+
160
+ .system-strip span::before {
161
+ content: "";
162
+ width: 8px;
163
+ height: 8px;
164
+ border-radius: 999px;
165
+ background: var(--green);
166
+ box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.12);
167
+ }
168
+
169
+ .tabs {
170
+ border-radius: 8px !important;
171
+ }
172
+
173
+ .tab-nav {
174
+ border-bottom: 1px solid var(--line) !important;
175
+ }
176
+
177
+ .tab-nav button {
178
+ color: var(--muted) !important;
179
+ }
180
+
181
+ .tab-nav button.selected {
182
+ color: var(--text) !important;
183
+ border-color: var(--teal) !important;
184
+ }
185
+
186
+ .demo-grid,
187
+ .brick-grid,
188
+ .diagnostic-grid {
189
+ gap: 16px !important;
190
+ }
191
+
192
+ .panel-shell {
193
+ border: 1px solid var(--line) !important;
194
+ border-radius: 8px !important;
195
+ background: rgba(16, 23, 34, 0.72) !important;
196
+ box-shadow: 0 12px 34px rgba(0, 0, 0, 0.22);
197
+ padding: 14px !important;
198
+ }
199
+
200
+ .input-panel {
201
+ border-color: rgba(45, 212, 191, 0.22) !important;
202
+ }
203
+
204
+ .output-panel {
205
+ border-color: rgba(245, 158, 11, 0.24) !important;
206
+ }
207
+
208
+ .section-kicker {
209
+ display: inline-flex;
210
+ align-items: center;
211
+ min-height: 32px;
212
+ padding: 0 10px;
213
+ margin-bottom: 10px;
214
+ border-radius: 8px;
215
+ border: 1px solid var(--line);
216
+ background: rgba(255, 255, 255, 0.055);
217
+ color: var(--teal);
218
+ font-size: 12px;
219
+ font-weight: 900;
220
+ text-transform: uppercase;
221
+ letter-spacing: 0.08em;
222
+ }
223
+
224
+ .control-row {
225
+ align-items: end !important;
226
  }
227
 
228
  .block,
 
231
  border-radius: 8px !important;
232
  }
233
 
234
+ .block {
235
+ border-color: var(--line) !important;
236
+ background: rgba(15, 23, 42, 0.60) !important;
237
+ }
238
+
239
+ label,
240
+ .label-wrap span {
241
+ color: var(--text-soft) !important;
242
+ font-weight: 800 !important;
243
+ }
244
+
245
  textarea,
246
  input,
247
  select {
248
+ background: rgba(8, 11, 16, 0.68) !important;
249
  color: var(--text) !important;
250
+ border-color: rgba(226, 232, 240, 0.14) !important;
251
+ border-radius: 8px !important;
252
+ font-size: 15px !important;
253
+ }
254
+
255
+ textarea:focus,
256
+ input:focus,
257
+ select:focus,
258
+ button:focus-visible {
259
+ outline: 2px solid rgba(45, 212, 191, 0.90) !important;
260
+ outline-offset: 2px !important;
261
  }
262
 
263
  button.primary,
264
  button {
265
  border-radius: 8px !important;
266
+ font-weight: 900 !important;
267
+ min-height: 46px !important;
268
+ transition: transform 160ms ease, border-color 160ms ease, filter 160ms ease !important;
269
+ touch-action: manipulation;
270
+ }
271
+
272
+ button:hover {
273
+ transform: translateY(-1px);
274
+ filter: brightness(1.04);
275
+ }
276
+
277
+ button:active {
278
+ transform: translateY(0);
279
  }
280
 
281
  #run_asl {
282
+ background: linear-gradient(135deg, var(--teal), var(--green)) !important;
283
  color: #04111a !important;
284
  border: none !important;
285
  }
286
 
287
  #run_llm {
288
+ background: linear-gradient(135deg, var(--indigo), var(--blue)) !important;
289
  color: white !important;
290
  border: none !important;
291
  }
292
 
293
  #run_tts,
294
  #run_full {
295
+ background: linear-gradient(135deg, var(--amber), var(--rose)) !important;
296
+ color: #111827 !important;
297
  border: none !important;
298
  }
299
 
300
+ .video-container,
301
+ video {
302
+ border-radius: 8px !important;
303
+ }
304
+
305
+ audio {
306
+ width: 100%;
307
+ }
308
+
309
+ .json-holder,
310
+ pre,
311
+ code {
312
+ border-radius: 8px !important;
313
+ }
314
+
315
  .footer-note {
316
  color: var(--muted);
317
  font-size: 13px;
318
  text-align: center;
319
+ margin: 18px 0 4px;
320
  }
321
 
322
+ @media (prefers-reduced-motion: reduce) {
323
+ button {
324
+ transition: none !important;
325
+ }
326
+
327
+ button:hover {
328
+ transform: none;
329
+ }
330
+ }
331
+
332
+ @media (max-width: 900px) {
333
  #hero {
334
  padding: 18px;
335
  }
336
 
337
  #hero h1 {
338
+ font-size: 32px;
339
+ }
340
+
341
+ .hero-grid {
342
+ grid-template-columns: 1fr;
343
+ }
344
+
345
+ .system-strip {
346
+ min-width: 0;
347
+ }
348
+
349
+ .pipeline-rail {
350
+ grid-template-columns: repeat(2, minmax(0, 1fr));
351
+ }
352
+ }
353
+
354
+ @media (max-width: 560px) {
355
+ #hero h1 {
356
+ font-size: 28px;
357
+ }
358
+
359
+ .brand-lockup {
360
+ align-items: flex-start;
361
+ }
362
+
363
+ .pipeline-rail {
364
+ grid-template-columns: 1fr;
365
  }
366
  }