@yeongjaeyou/claude-code-config 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/code-review-handler.md +47 -171
- package/.claude/commands/gh/auto-review-loop.md +107 -130
- package/.claude/guidelines/work-guidelines.md +29 -0
- package/.claude/skills/gradio-cv-app/SKILL.md +170 -0
- package/.claude/skills/gradio-cv-app/references/github-references.md +134 -0
- package/.claude/skills/gradio-cv-app/references/i18n-patterns.md +416 -0
- package/.claude/skills/gradio-cv-app/references/refined-theme.md +403 -0
- package/.claude/skills/gradio-cv-app/references/task-templates.md +433 -0
- package/package.json +1 -1
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Refined Theme (Editorial Style)
|
|
2
|
+
|
|
3
|
+
A polished theme for Gradio applications with dark mode support. Avoids the generic AI aesthetic and delivers a professional tool-like experience.
|
|
4
|
+
|
|
5
|
+
## Theme Class
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from typing import Iterable
|
|
9
|
+
from gradio.themes import Soft
|
|
10
|
+
from gradio.themes.utils import colors, fonts, sizes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RefinedTheme(Soft):
|
|
14
|
+
"""Editorial/Documentation style theme with dark mode support
|
|
15
|
+
|
|
16
|
+
Features:
|
|
17
|
+
- No gradients, solid colors only
|
|
18
|
+
- Single accent color (Emerald)
|
|
19
|
+
- High contrast, professional look
|
|
20
|
+
- Pretendard font (Korean support)
|
|
21
|
+
- Built-in dark mode via _dark variants
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
primary_hue: colors.Color | str = colors.zinc,
|
|
28
|
+
secondary_hue: colors.Color | str = colors.emerald,
|
|
29
|
+
neutral_hue: colors.Color | str = colors.zinc,
|
|
30
|
+
text_size: sizes.Size | str = sizes.text_md,
|
|
31
|
+
font: fonts.Font | str | Iterable[fonts.Font | str] = (
|
|
32
|
+
fonts.GoogleFont("Pretendard"),
|
|
33
|
+
"Pretendard",
|
|
34
|
+
"-apple-system",
|
|
35
|
+
"BlinkMacSystemFont",
|
|
36
|
+
"system-ui",
|
|
37
|
+
"sans-serif",
|
|
38
|
+
),
|
|
39
|
+
font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
|
|
40
|
+
fonts.GoogleFont("JetBrains Mono"),
|
|
41
|
+
"ui-monospace",
|
|
42
|
+
"monospace",
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
super().__init__(
|
|
46
|
+
primary_hue=primary_hue,
|
|
47
|
+
secondary_hue=secondary_hue,
|
|
48
|
+
neutral_hue=neutral_hue,
|
|
49
|
+
text_size=text_size,
|
|
50
|
+
font=font,
|
|
51
|
+
font_mono=font_mono,
|
|
52
|
+
)
|
|
53
|
+
super().set(
|
|
54
|
+
# === Light Mode (Default) ===
|
|
55
|
+
body_background_fill="#fafafa",
|
|
56
|
+
background_fill_primary="#ffffff",
|
|
57
|
+
background_fill_secondary="#f4f4f5",
|
|
58
|
+
|
|
59
|
+
# === Dark Mode Variants ===
|
|
60
|
+
body_background_fill_dark="#18181b",
|
|
61
|
+
background_fill_primary_dark="#27272a",
|
|
62
|
+
background_fill_secondary_dark="#3f3f46",
|
|
63
|
+
|
|
64
|
+
# Text colors
|
|
65
|
+
body_text_color="*neutral_800",
|
|
66
|
+
body_text_color_dark="#fafafa",
|
|
67
|
+
block_title_text_color="*neutral_800",
|
|
68
|
+
block_title_text_color_dark="#fafafa",
|
|
69
|
+
|
|
70
|
+
# Buttons - solid colors (no gradients)
|
|
71
|
+
button_primary_background_fill="*secondary_600",
|
|
72
|
+
button_primary_background_fill_hover="*secondary_700",
|
|
73
|
+
button_primary_text_color="white",
|
|
74
|
+
button_primary_background_fill_dark="*secondary_500",
|
|
75
|
+
button_primary_background_fill_hover_dark="*secondary_600",
|
|
76
|
+
|
|
77
|
+
button_secondary_background_fill="*neutral_100",
|
|
78
|
+
button_secondary_background_fill_hover="*neutral_200",
|
|
79
|
+
button_secondary_background_fill_dark="*neutral_700",
|
|
80
|
+
button_secondary_background_fill_hover_dark="*neutral_600",
|
|
81
|
+
button_secondary_text_color_dark="#fafafa",
|
|
82
|
+
|
|
83
|
+
# Minimal styling
|
|
84
|
+
block_border_width="1px",
|
|
85
|
+
block_border_color="*neutral_200",
|
|
86
|
+
block_border_color_dark="*neutral_700",
|
|
87
|
+
block_shadow="none",
|
|
88
|
+
button_shadow="none",
|
|
89
|
+
button_primary_shadow="none",
|
|
90
|
+
|
|
91
|
+
# Margins and spacing
|
|
92
|
+
spacing_lg="1.5rem",
|
|
93
|
+
spacing_md="1rem",
|
|
94
|
+
|
|
95
|
+
# Title styling
|
|
96
|
+
block_title_text_weight="600",
|
|
97
|
+
block_title_text_size="*text_md",
|
|
98
|
+
|
|
99
|
+
# Input fields
|
|
100
|
+
input_background_fill="*neutral_50",
|
|
101
|
+
input_background_fill_dark="*neutral_800",
|
|
102
|
+
input_border_color="*neutral_300",
|
|
103
|
+
input_border_color_dark="*neutral_600",
|
|
104
|
+
input_border_width="1px",
|
|
105
|
+
|
|
106
|
+
# Accent colors - for tabs, links, and interactive elements
|
|
107
|
+
# Use secondary (emerald) instead of primary (zinc) for visibility
|
|
108
|
+
color_accent="*secondary_500",
|
|
109
|
+
color_accent_soft="*secondary_100",
|
|
110
|
+
color_accent_soft_dark="*secondary_800",
|
|
111
|
+
border_color_accent="*secondary_400",
|
|
112
|
+
border_color_accent_dark="*secondary_600",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Create theme instance
|
|
117
|
+
refined_theme = RefinedTheme()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## CSS Styles
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
css = """
|
|
124
|
+
/* Container */
|
|
125
|
+
#col-container {
|
|
126
|
+
margin: 0 auto;
|
|
127
|
+
max-width: 1000px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Title */
|
|
131
|
+
#main-title h1 {
|
|
132
|
+
font-size: 1.75rem !important;
|
|
133
|
+
font-weight: 600 !important;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Smooth theme transition */
|
|
137
|
+
body, .gradio-container {
|
|
138
|
+
transition: background-color 0.2s ease, color 0.2s ease;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Buttons */
|
|
142
|
+
.submit-btn {
|
|
143
|
+
font-weight: 500 !important;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Theme toggle button (native HTML button via gr.HTML) */
|
|
147
|
+
.theme-toggle-btn {
|
|
148
|
+
min-width: 40px;
|
|
149
|
+
height: 40px;
|
|
150
|
+
padding: 8px;
|
|
151
|
+
border: 1px solid var(--border-color-primary);
|
|
152
|
+
border-radius: 8px;
|
|
153
|
+
background-color: var(--background-fill-primary);
|
|
154
|
+
color: var(--body-text-color);
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
display: inline-flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
transition: border-color 0.2s ease, background-color 0.2s ease;
|
|
160
|
+
}
|
|
161
|
+
.theme-toggle-btn:hover {
|
|
162
|
+
border-color: var(--color-accent);
|
|
163
|
+
background-color: var(--background-fill-secondary);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Moon icon (light mode - shows moon to switch to dark) */
|
|
167
|
+
#theme-toggle .icon-moon { display: inline-flex; }
|
|
168
|
+
#theme-toggle .icon-sun { display: none; }
|
|
169
|
+
|
|
170
|
+
/* Sun icon (dark mode - shows sun to switch to light) */
|
|
171
|
+
.dark #theme-toggle .icon-moon { display: none; }
|
|
172
|
+
.dark #theme-toggle .icon-sun { display: inline-flex; }
|
|
173
|
+
|
|
174
|
+
/* Text areas */
|
|
175
|
+
textarea {
|
|
176
|
+
font-size: 0.9rem !important;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Labels */
|
|
180
|
+
.label-wrap {
|
|
181
|
+
font-weight: 500 !important;
|
|
182
|
+
}
|
|
183
|
+
"""
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Dark Mode Toggle
|
|
189
|
+
|
|
190
|
+
### Method 1: gr.HTML with Native Button (Recommended for SVG Icons)
|
|
191
|
+
|
|
192
|
+
**IMPORTANT**: In Gradio 5.x, `gr.Button` does NOT render HTML in the `value` parameter - it escapes HTML to text. Use `gr.HTML` with a native `<button>` element for SVG icon toggles.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
import gradio as gr
|
|
196
|
+
|
|
197
|
+
# Native HTML button with SVG icons (CSS controls visibility)
|
|
198
|
+
THEME_TOGGLE_HTML = """
|
|
199
|
+
<button id="theme-toggle" class="theme-toggle-btn" type="button" aria-label="Toggle theme">
|
|
200
|
+
<span class="icon-moon"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg></span>
|
|
201
|
+
<span class="icon-sun"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg></span>
|
|
202
|
+
</button>
|
|
203
|
+
"""
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### JavaScript for Theme Toggle
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# Initialize theme on page load AND attach click handler
|
|
210
|
+
INIT_THEME_JS = """
|
|
211
|
+
() => {
|
|
212
|
+
const saved = localStorage.getItem('theme');
|
|
213
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
214
|
+
const shouldBeDark = saved === 'dark' || (!saved && prefersDark);
|
|
215
|
+
|
|
216
|
+
if (shouldBeDark) {
|
|
217
|
+
document.documentElement.classList.add('dark');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Attach click handler to theme toggle button
|
|
221
|
+
const toggleBtn = document.getElementById('theme-toggle');
|
|
222
|
+
if (toggleBtn) {
|
|
223
|
+
toggleBtn.addEventListener('click', (e) => {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
document.documentElement.classList.toggle('dark');
|
|
226
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
227
|
+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
"""
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Usage Pattern
|
|
235
|
+
|
|
236
|
+
**Important**: Gradio Row wraps each child in a div with `flex: 1 1 0%` by default.
|
|
237
|
+
To push the toggle to the right edge, CSS wrapper override is required.
|
|
238
|
+
|
|
239
|
+
**Additional CSS for header layout:**
|
|
240
|
+
```css
|
|
241
|
+
/* Header row - prevent wrap, center vertically */
|
|
242
|
+
.row.header-row {
|
|
243
|
+
align-items: center !important;
|
|
244
|
+
flex-wrap: nowrap !important;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* Title wrapper - expand to fill space */
|
|
248
|
+
#main-title {
|
|
249
|
+
flex: 1 1 auto !important;
|
|
250
|
+
min-width: 0 !important;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Toggle wrapper - fixed size */
|
|
254
|
+
.header-row > .theme-toggle-container {
|
|
255
|
+
flex: 0 0 auto !important;
|
|
256
|
+
min-width: 0 !important;
|
|
257
|
+
width: auto !important;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Layout code:**
|
|
262
|
+
```python
|
|
263
|
+
import gradio as gr
|
|
264
|
+
|
|
265
|
+
with gr.Blocks(theme=refined_theme, css=css) as demo:
|
|
266
|
+
with gr.Column(elem_id="col-container"):
|
|
267
|
+
# Header with theme toggle (use elem_classes for CSS targeting)
|
|
268
|
+
with gr.Row(elem_classes=["header-row"]):
|
|
269
|
+
gr.Markdown("# App Title", elem_id="main-title")
|
|
270
|
+
gr.HTML(value=THEME_TOGGLE_HTML, elem_classes=["theme-toggle-container"])
|
|
271
|
+
|
|
272
|
+
# Your UI components here...
|
|
273
|
+
input_image = gr.Image(label="Input Image", type="pil")
|
|
274
|
+
submit_btn = gr.Button("Run", variant="primary")
|
|
275
|
+
|
|
276
|
+
# Initialize theme on load (includes click handler)
|
|
277
|
+
demo.load(fn=None, js=INIT_THEME_JS)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Common pitfalls:**
|
|
281
|
+
- `Row > Column(scale=0, min_width=0)` pattern can cause width=0px issues
|
|
282
|
+
- `margin-left: auto` on inner element won't work (targets wrong div)
|
|
283
|
+
- Without `flex-wrap: nowrap`, toggle may wrap to next line
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Method 2: Text-Based Toggle (Simplest)
|
|
288
|
+
|
|
289
|
+
For minimal implementation without icons:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
THEME_TOGGLE_JS_TEXT = """
|
|
293
|
+
() => {
|
|
294
|
+
const html = document.documentElement;
|
|
295
|
+
html.classList.toggle('dark');
|
|
296
|
+
const isDark = html.classList.contains('dark');
|
|
297
|
+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
298
|
+
|
|
299
|
+
const btn = document.querySelector('#theme-toggle button');
|
|
300
|
+
if (btn) {
|
|
301
|
+
btn.textContent = isDark ? 'Light' : 'Dark';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
# Usage
|
|
307
|
+
theme_btn = gr.Button("Dark", elem_id="theme-toggle", size="sm")
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Alternative Accent Colors
|
|
313
|
+
|
|
314
|
+
Single accent color options to use instead of Emerald:
|
|
315
|
+
|
|
316
|
+
| Color | Gradio Color | Use Case |
|
|
317
|
+
|-------|--------------|----------|
|
|
318
|
+
| Amber | `colors.amber` | Warm and energetic feel |
|
|
319
|
+
| Sky | `colors.sky` | Calm and trustworthy feel |
|
|
320
|
+
| Rose | `colors.rose` | Soft and approachable feel |
|
|
321
|
+
| Teal | `colors.teal` | Modern and sophisticated feel |
|
|
322
|
+
|
|
323
|
+
**Note**: Use only one accent color per application. Mixing multiple colors creates a generic AI aesthetic.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Complete Example
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
import gradio as gr
|
|
331
|
+
from gradio.themes import Soft
|
|
332
|
+
from gradio.themes.utils import colors, fonts
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class RefinedTheme(Soft):
|
|
336
|
+
def __init__(self):
|
|
337
|
+
super().__init__(
|
|
338
|
+
primary_hue=colors.zinc,
|
|
339
|
+
secondary_hue=colors.emerald,
|
|
340
|
+
neutral_hue=colors.zinc,
|
|
341
|
+
font=(fonts.GoogleFont("Pretendard"), "system-ui", "sans-serif"),
|
|
342
|
+
font_mono=(fonts.GoogleFont("JetBrains Mono"), "monospace"),
|
|
343
|
+
)
|
|
344
|
+
super().set(
|
|
345
|
+
body_background_fill="#fafafa",
|
|
346
|
+
body_background_fill_dark="#18181b",
|
|
347
|
+
background_fill_primary="#ffffff",
|
|
348
|
+
background_fill_primary_dark="#27272a",
|
|
349
|
+
body_text_color="*neutral_800",
|
|
350
|
+
body_text_color_dark="#fafafa",
|
|
351
|
+
button_primary_background_fill="*secondary_600",
|
|
352
|
+
button_primary_background_fill_dark="*secondary_500",
|
|
353
|
+
block_shadow="none",
|
|
354
|
+
button_shadow="none",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
THEME_ICONS = '''
|
|
359
|
+
<span class="icon-moon"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg></span>
|
|
360
|
+
<span class="icon-sun"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg></span>
|
|
361
|
+
'''
|
|
362
|
+
|
|
363
|
+
css = """
|
|
364
|
+
#col-container { margin: 0 auto; max-width: 1000px; }
|
|
365
|
+
#theme-toggle button { min-width: 40px !important; padding: 8px !important; }
|
|
366
|
+
#theme-toggle .icon-moon { display: inline-block; }
|
|
367
|
+
#theme-toggle .icon-sun { display: none; }
|
|
368
|
+
.dark #theme-toggle .icon-moon { display: none; }
|
|
369
|
+
.dark #theme-toggle .icon-sun { display: inline-block; }
|
|
370
|
+
body { transition: background-color 0.2s ease; }
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
TOGGLE_JS = """() => {
|
|
374
|
+
document.documentElement.classList.toggle('dark');
|
|
375
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
376
|
+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
377
|
+
}"""
|
|
378
|
+
|
|
379
|
+
INIT_JS = """() => {
|
|
380
|
+
const saved = localStorage.getItem('theme');
|
|
381
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
382
|
+
if (saved === 'dark' || (!saved && prefersDark)) {
|
|
383
|
+
document.documentElement.classList.add('dark');
|
|
384
|
+
}
|
|
385
|
+
}"""
|
|
386
|
+
|
|
387
|
+
theme = RefinedTheme()
|
|
388
|
+
|
|
389
|
+
with gr.Blocks(theme=theme, css=css) as demo:
|
|
390
|
+
with gr.Column(elem_id="col-container"):
|
|
391
|
+
with gr.Row():
|
|
392
|
+
gr.Markdown("# My App")
|
|
393
|
+
theme_btn = gr.Button(THEME_ICONS, elem_id="theme-toggle", size="sm")
|
|
394
|
+
|
|
395
|
+
image = gr.Image(label="Upload", type="pil")
|
|
396
|
+
btn = gr.Button("Run", variant="primary")
|
|
397
|
+
|
|
398
|
+
theme_btn.click(fn=None, js=TOGGLE_JS)
|
|
399
|
+
demo.load(fn=None, js=INIT_JS)
|
|
400
|
+
|
|
401
|
+
if __name__ == "__main__":
|
|
402
|
+
demo.queue(max_size=30).launch(mcp_server=True, ssr_mode=False, show_error=True)
|
|
403
|
+
```
|