Spaces:
Running
Running
Space: brand title in Fraunces w/ red dot + 'small minds, big dreams' subtitle; CHARACTERS title small+red; stage fixed-fills full height like main sidebar
Browse files- app.py +19 -9
- web/shell/nav.json +1 -1
- web/shell/sidebar.css +18 -17
- web/shell/spriteScene.css +5 -2
app.py
CHANGED
|
@@ -43,7 +43,7 @@ HIDE_TABS = ('<style>.tab-container[role="tablist"]{position:absolute!important;
|
|
| 43 |
FONTS = ('<link rel="preconnect" href="https://fonts.googleapis.com">'
|
| 44 |
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'
|
| 45 |
'<link rel="stylesheet" href="https://fonts.googleapis.com/css2?'
|
| 46 |
-
'family=JetBrains+Mono:wght@400;500;700&'
|
| 47 |
'family=Space+Grotesk:wght@400;500;600;700&display=swap">')
|
| 48 |
# Parchment theme, applied THEME-INDEPENDENTLY: we override Gradio's own colour +
|
| 49 |
# font CSS variables with `!important`, so the app renders identically whether the
|
|
@@ -69,13 +69,17 @@ THEME = ('<style>'
|
|
| 69 |
f'body,gradio-app,.gradio-container,.gradio-container.dark,.dark{{{PALETTE_IMP}}}'
|
| 70 |
"body,gradio-app,.gradio-container{background:#f3ebdc !important;color:#141821 !important;"
|
| 71 |
"font-family:'Space Grotesk',-apple-system,sans-serif !important;}"
|
| 72 |
-
# Sprite tab fills the content area
|
| 73 |
-
#
|
| 74 |
-
#
|
| 75 |
-
#
|
|
|
|
| 76 |
'.gradio-container .tabitem{padding:0 !important;}'
|
| 77 |
'.gradio-container .tabs{border:0 !important;}'
|
| 78 |
-
'#sprite-stage{
|
|
|
|
|
|
|
|
|
|
| 79 |
'</style>')
|
| 80 |
HEAD = ('<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">'
|
| 81 |
+ HIDE_TABS + FONTS + THEME +
|
|
@@ -96,10 +100,16 @@ def build_sidebar(nav):
|
|
| 96 |
# sidebar.js against the tab buttons). React-only sandbox items (href but no
|
| 97 |
# `space`) and sections with no space-items are skipped.
|
| 98 |
b = nav.get("brand", {})
|
|
|
|
|
|
|
| 99 |
out = ['<aside class="tac-sidebar">',
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
f'<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
first = True # mark the default page active (matches Gradio's first/open tab)
|
| 104 |
for sec in nav.get("sections", []):
|
| 105 |
items = [it for it in sec.get("items", []) if it.get("space")]
|
|
|
|
| 43 |
FONTS = ('<link rel="preconnect" href="https://fonts.googleapis.com">'
|
| 44 |
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'
|
| 45 |
'<link rel="stylesheet" href="https://fonts.googleapis.com/css2?'
|
| 46 |
+
'family=Fraunces:opsz,wght@9..144,400..900&family=JetBrains+Mono:wght@400;500;700&'
|
| 47 |
'family=Space+Grotesk:wght@400;500;600;700&display=swap">')
|
| 48 |
# Parchment theme, applied THEME-INDEPENDENTLY: we override Gradio's own colour +
|
| 49 |
# font CSS variables with `!important`, so the app renders identically whether the
|
|
|
|
| 69 |
f'body,gradio-app,.gradio-container,.gradio-container.dark,.dark{{{PALETTE_IMP}}}'
|
| 70 |
"body,gradio-app,.gradio-container{background:#f3ebdc !important;color:#141821 !important;"
|
| 71 |
"font-family:'Space Grotesk',-apple-system,sans-serif !important;}"
|
| 72 |
+
# Sprite tab fills the WHOLE content area, like the main sidebar. The
|
| 73 |
+
# picker lives in Gradio's flow (pushed ~110px down, bounded by the tab
|
| 74 |
+
# box) so it can't reach full height there — so we lift the stage out of
|
| 75 |
+
# flow with position:fixed to span top→bottom, right of the main sidebar.
|
| 76 |
+
# Gradio still hides it (display:none on the inactive tab's ancestor).
|
| 77 |
'.gradio-container .tabitem{padding:0 !important;}'
|
| 78 |
'.gradio-container .tabs{border:0 !important;}'
|
| 79 |
+
'#sprite-stage{position:fixed !important;top:0;bottom:0;right:0;'
|
| 80 |
+
'left:var(--tac-w,240px);height:auto !important;z-index:1;}'
|
| 81 |
+
'body.tac-collapsed #sprite-stage{left:0;}'
|
| 82 |
+
'@media (max-width:768px){#sprite-stage{left:0;}}'
|
| 83 |
'</style>')
|
| 84 |
HEAD = ('<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">'
|
| 85 |
+ HIDE_TABS + FONTS + THEME +
|
|
|
|
| 100 |
# sidebar.js against the tab buttons). React-only sandbox items (href but no
|
| 101 |
# `space`) and sections with no space-items are skipped.
|
| 102 |
b = nav.get("brand", {})
|
| 103 |
+
# Brand block mirrors the app's .sidebar-title: a big display-font title with a
|
| 104 |
+
# red dot, plus a small uppercase subtitle.
|
| 105 |
out = ['<aside class="tac-sidebar">',
|
| 106 |
+
'<div class="tac-brand">'
|
| 107 |
+
'<div class="tac-brand-row">'
|
| 108 |
+
f'<strong class="tac-title">{b.get("title","")}</strong>'
|
| 109 |
+
'<button class="tac-collapse tac-toggle" title="Collapse">‹</button>'
|
| 110 |
+
'</div>'
|
| 111 |
+
f'<p class="tac-subtitle">{b.get("subtitle","")}</p>'
|
| 112 |
+
'</div>']
|
| 113 |
first = True # mark the default page active (matches Gradio's first/open tab)
|
| 114 |
for sec in nav.get("sections", []):
|
| 115 |
items = [it for it in sec.get("items", []) if it.get("space")]
|
web/shell/nav.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"_doc": "Shared navigation IR — ONE source for both the React app (src/layout/Sidebar.jsx) and the Gradio Space (tiny-army app.py build_sidebar). Each item carries routing for both hosts: `href` + `view`/`page` drive React's hash router and active state; `space` is the Gradio tab label the item navigates to. React renders items with an `href`; the Space renders items with a `space`. `brand` is used by the Space only (React uses config.name).",
|
| 3 |
-
"brand": { "title": "Tiny Army", "icon": "⚔️" },
|
| 4 |
"sections": [
|
| 5 |
{
|
| 6 |
"title": "World",
|
|
|
|
| 1 |
{
|
| 2 |
"_doc": "Shared navigation IR — ONE source for both the React app (src/layout/Sidebar.jsx) and the Gradio Space (tiny-army app.py build_sidebar). Each item carries routing for both hosts: `href` + `view`/`page` drive React's hash router and active state; `space` is the Gradio tab label the item navigates to. React renders items with an `href`; the Space renders items with a `space`. `brand` is used by the Space only (React uses config.name).",
|
| 3 |
+
"brand": { "title": "Tiny Army", "icon": "⚔️", "subtitle": "small minds, big dreams" },
|
| 4 |
"sections": [
|
| 5 |
{
|
| 6 |
"title": "World",
|
web/shell/sidebar.css
CHANGED
|
@@ -39,26 +39,27 @@ body.tac-collapsed .tac-sidebar { transform: translateX(-100%); }
|
|
| 39 |
.gradio-container { transition: margin-left .22s ease; }
|
| 40 |
body:not(.tac-collapsed) .gradio-container { margin-left: var(--tac-w); }
|
| 41 |
|
| 42 |
-
.
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
-
.tac-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
* so it renders identically with or without the host theme. */
|
| 52 |
-
.tac-sidebar .tac-brand .tac-collapse {
|
| 53 |
-
margin-left: auto; cursor: pointer; line-height: 1; font-size: 16px;
|
| 54 |
-
display: inline-flex !important; align-items: center; justify-content: center;
|
| 55 |
-
width: 28px; height: 28px; padding: 0 !important;
|
| 56 |
-
background: var(--tac-bg-2) !important; color: var(--tac-muted) !important;
|
| 57 |
-
border: 1.5px solid var(--tac-border) !important; border-radius: 6px !important;
|
| 58 |
}
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
|
|
|
| 62 |
|
| 63 |
.tac-section { padding: 12px 8px 0; }
|
| 64 |
/* Section headers: red (transmit) with a short ink line prefix, like the app's
|
|
|
|
| 39 |
.gradio-container { transition: margin-left .22s ease; }
|
| 40 |
body:not(.tac-collapsed) .gradio-container { margin-left: var(--tac-w); }
|
| 41 |
|
| 42 |
+
/* Brand block mirrors the app's `.sidebar-title`: a big display-font title with a
|
| 43 |
+
* red dot, plus a small uppercase subtitle. */
|
| 44 |
+
.tac-brand { padding: 16px 14px 12px; border-bottom: 2px solid var(--tac-border); }
|
| 45 |
+
.tac-brand-row { display: flex; align-items: flex-start; justify-content: space-between; }
|
| 46 |
+
.tac-sidebar .tac-title {
|
| 47 |
+
display: block; font-family: 'Fraunces', Georgia, serif; font-weight: 900;
|
| 48 |
+
font-size: 28px; line-height: .92; letter-spacing: -.03em; color: var(--tac-ink) !important;
|
| 49 |
}
|
| 50 |
+
.tac-sidebar .tac-title::after { content: "·"; color: var(--tac-accent); }
|
| 51 |
+
.tac-subtitle {
|
| 52 |
+
margin: 8px 0 0 !important; font-family: var(--tac-mono); font-size: 9px;
|
| 53 |
+
color: var(--tac-muted) !important; letter-spacing: .14em; text-transform: uppercase; line-height: 1.5;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
+
/* Plain ‹ collapse control (like the app's .sidebar-collapse); defend against the
|
| 56 |
+
* host button reset so it stays a borderless glyph. */
|
| 57 |
+
.tac-sidebar .tac-brand .tac-collapse {
|
| 58 |
+
cursor: pointer; line-height: 1; font-size: 18px; margin-left: 8px; flex-shrink: 0;
|
| 59 |
+
background: none !important; color: var(--tac-muted) !important;
|
| 60 |
+
border: 0 !important; padding: 0 !important; box-shadow: none !important;
|
| 61 |
}
|
| 62 |
+
.tac-sidebar .tac-brand .tac-collapse:hover { color: var(--tac-ink) !important; }
|
| 63 |
|
| 64 |
.tac-section { padding: 12px 8px 0; }
|
| 65 |
/* Section headers: red (transmit) with a short ink line prefix, like the app's
|
web/shell/spriteScene.css
CHANGED
|
@@ -26,9 +26,12 @@
|
|
| 26 |
width: 240px; flex-shrink: 0; border-right: 2px solid var(--mv-ink);
|
| 27 |
background: var(--mv-paper-2); overflow-y: auto; padding: 12px;
|
| 28 |
}
|
|
|
|
|
|
|
| 29 |
.movement-picker-title {
|
| 30 |
-
margin: 0 0 8px; font-family: var(--mv-mono); font-size: 11px
|
| 31 |
-
|
|
|
|
| 32 |
}
|
| 33 |
.movement-pack { margin-top: 8px; border-top: 1px solid var(--mv-paper); }
|
| 34 |
.movement-pack > summary {
|
|
|
|
| 26 |
width: 240px; flex-shrink: 0; border-right: 2px solid var(--mv-ink);
|
| 27 |
background: var(--mv-paper-2); overflow-y: auto; padding: 12px;
|
| 28 |
}
|
| 29 |
+
/* It's an <h2>, so the host (Gradio's `.prose h2`) blows it up and blackens it —
|
| 30 |
+
* defend size + colour with `!important`. */
|
| 31 |
.movement-picker-title {
|
| 32 |
+
margin: 0 0 8px !important; font-family: var(--mv-mono); font-size: 11px !important;
|
| 33 |
+
font-weight: 500 !important; letter-spacing: .2em; line-height: 1.4 !important;
|
| 34 |
+
text-transform: uppercase; color: var(--mv-transmit) !important;
|
| 35 |
}
|
| 36 |
.movement-pack { margin-top: 8px; border-top: 1px solid var(--mv-paper); }
|
| 37 |
.movement-pack > summary {
|