Spaces:
Sleeping
Sleeping
Pointf5ive commited on
Commit ·
9cc52b4
1
Parent(s): 5432dfe
stage 4: sidebar and topbar
Browse filesScope: implement render_sidebar(active='Home') and render_topbar(state) with required navigation labels and topbar chips/search/workspace visuals.
Validation: compile/import + label/chip/home-active checks passed; tabs/callback wiring unchanged.
app.py
CHANGED
|
@@ -331,9 +331,10 @@ TOTEM_CSS = """
|
|
| 331 |
|
| 332 |
.totem-topbar {
|
| 333 |
min-height: 64px;
|
| 334 |
-
display:
|
|
|
|
| 335 |
align-items: center;
|
| 336 |
-
|
| 337 |
padding: 0 var(--space-5);
|
| 338 |
}
|
| 339 |
|
|
@@ -371,6 +372,28 @@ TOTEM_CSS = """
|
|
| 371 |
margin-bottom: var(--space-6);
|
| 372 |
}
|
| 373 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
.totem-shell .totem-nav-item {
|
| 375 |
height: 48px;
|
| 376 |
display: flex;
|
|
@@ -393,6 +416,62 @@ TOTEM_CSS = """
|
|
| 393 |
font-family: var(--font-ui);
|
| 394 |
}
|
| 395 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
.totem-shell .totem-placeholder {
|
| 397 |
border: 1px dashed rgba(174, 183, 204, 0.45);
|
| 398 |
border-radius: var(--radius-card);
|
|
@@ -410,6 +489,10 @@ TOTEM_CSS = """
|
|
| 410 |
}
|
| 411 |
|
| 412 |
@media (max-width: 1200px) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
.totem-shell .totem-row {
|
| 414 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 415 |
}
|
|
@@ -426,6 +509,9 @@ TOTEM_CSS = """
|
|
| 426 |
width: 100%;
|
| 427 |
flex-basis: auto;
|
| 428 |
}
|
|
|
|
|
|
|
|
|
|
| 429 |
.totem-shell .totem-row {
|
| 430 |
grid-template-columns: 1fr;
|
| 431 |
}
|
|
@@ -661,34 +747,75 @@ def _clean_path(uploaded_file) -> Path:
|
|
| 661 |
return workbook_path(uploaded_file)
|
| 662 |
|
| 663 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
def render_dashboard(state: dict) -> str:
|
| 665 |
"""
|
| 666 |
-
Stage 3 static shell wrapper.
|
| 667 |
-
|
| 668 |
"""
|
| 669 |
s = normalize_dashboard_state(state)
|
| 670 |
-
project_name = esc(s.get("project_name", "Editorial Workspace"))
|
| 671 |
-
analysis_status = esc(s.get("analysis_status", "idle")).upper()
|
| 672 |
signal = int(s.get("totem_signal", 0) or 0)
|
| 673 |
return f"""
|
| 674 |
<div class="totem-shell">
|
| 675 |
-
|
| 676 |
-
<div class="totem-brand">TOTEM Studio</div>
|
| 677 |
-
<div class="totem-nav-item active">Home</div>
|
| 678 |
-
<div class="totem-nav-item">Projects</div>
|
| 679 |
-
<div class="totem-nav-item">Workbook Upload</div>
|
| 680 |
-
<div class="totem-nav-item">TOTEM Analytics</div>
|
| 681 |
-
<div class="totem-nav-item">Revision Queue</div>
|
| 682 |
-
<div class="totem-nav-item">Risk Clusters</div>
|
| 683 |
-
<div class="totem-nav-item">Codex Extractor</div>
|
| 684 |
-
<div class="totem-nav-item">Export</div>
|
| 685 |
-
</aside>
|
| 686 |
<main class="totem-main">
|
| 687 |
<div class="totem-main-inner">
|
| 688 |
-
|
| 689 |
-
<div>{project_name}</div>
|
| 690 |
-
<div class="totem-muted">STATUS: {analysis_status}</div>
|
| 691 |
-
</section>
|
| 692 |
<section class="totem-hero">
|
| 693 |
<h2 style="margin:0;font-family:var(--font-display);font-size:44px;color:var(--totem-gold);">TOTEM Studio</h2>
|
| 694 |
<p style="margin:10px 0 0 0;font-size:22px;color:rgba(246,241,232,.92);font-family:var(--font-ui);">
|
|
|
|
| 331 |
|
| 332 |
.totem-topbar {
|
| 333 |
min-height: 64px;
|
| 334 |
+
display: grid;
|
| 335 |
+
grid-template-columns: auto auto minmax(260px, 1fr) auto;
|
| 336 |
align-items: center;
|
| 337 |
+
gap: var(--space-3);
|
| 338 |
padding: 0 var(--space-5);
|
| 339 |
}
|
| 340 |
|
|
|
|
| 372 |
margin-bottom: var(--space-6);
|
| 373 |
}
|
| 374 |
|
| 375 |
+
.totem-shell .totem-brand small {
|
| 376 |
+
display: block;
|
| 377 |
+
margin-top: var(--space-2);
|
| 378 |
+
font-family: var(--font-ui);
|
| 379 |
+
font-size: 12px;
|
| 380 |
+
letter-spacing: 1.5px;
|
| 381 |
+
color: rgba(174, 183, 204, 0.9);
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.totem-shell .totem-quote-card {
|
| 385 |
+
margin-top: calc(var(--space-8) + var(--space-8));
|
| 386 |
+
padding: var(--space-5);
|
| 387 |
+
border-radius: var(--radius-card);
|
| 388 |
+
border: 1px solid rgba(242, 193, 78, 0.28);
|
| 389 |
+
background: radial-gradient(circle at 82% 70%, rgba(138, 77, 255, 0.22), transparent 45%),
|
| 390 |
+
linear-gradient(180deg, rgba(16, 26, 58, 0.95), rgba(11, 23, 51, 0.96));
|
| 391 |
+
color: rgba(246, 241, 232, 0.95);
|
| 392 |
+
font-family: var(--font-display);
|
| 393 |
+
font-size: 24px;
|
| 394 |
+
line-height: 1.25;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
.totem-shell .totem-nav-item {
|
| 398 |
height: 48px;
|
| 399 |
display: flex;
|
|
|
|
| 416 |
font-family: var(--font-ui);
|
| 417 |
}
|
| 418 |
|
| 419 |
+
.totem-shell .totem-chip {
|
| 420 |
+
min-height: 36px;
|
| 421 |
+
border-radius: 10px;
|
| 422 |
+
border: 1px solid rgba(174, 183, 204, 0.3);
|
| 423 |
+
background: rgba(7, 18, 45, 0.5);
|
| 424 |
+
color: rgba(246, 241, 232, 0.9);
|
| 425 |
+
display: inline-flex;
|
| 426 |
+
align-items: center;
|
| 427 |
+
padding: 0 var(--space-4);
|
| 428 |
+
font-family: var(--font-ui);
|
| 429 |
+
margin-right: var(--space-2);
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.totem-shell .totem-chip.active {
|
| 433 |
+
border-color: rgba(242, 193, 78, 0.5);
|
| 434 |
+
background: linear-gradient(90deg, rgba(242, 193, 78, 0.18), rgba(138, 77, 255, 0.18));
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.totem-shell .totem-search {
|
| 438 |
+
min-height: 38px;
|
| 439 |
+
border-radius: 10px;
|
| 440 |
+
border: 1px solid rgba(174, 183, 204, 0.35);
|
| 441 |
+
background: rgba(7, 18, 45, 0.42);
|
| 442 |
+
color: rgba(174, 183, 204, 0.95);
|
| 443 |
+
display: flex;
|
| 444 |
+
align-items: center;
|
| 445 |
+
padding: 0 var(--space-4);
|
| 446 |
+
font-family: var(--font-ui);
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.totem-shell .totem-workspace {
|
| 450 |
+
display: inline-flex;
|
| 451 |
+
align-items: center;
|
| 452 |
+
gap: var(--space-3);
|
| 453 |
+
color: rgba(246, 241, 232, 0.96);
|
| 454 |
+
font-family: var(--font-ui);
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
.totem-shell .totem-avatar {
|
| 458 |
+
width: 34px;
|
| 459 |
+
height: 34px;
|
| 460 |
+
border-radius: 999px;
|
| 461 |
+
border: 1px solid rgba(242, 193, 78, 0.45);
|
| 462 |
+
display: grid;
|
| 463 |
+
place-items: center;
|
| 464 |
+
background: rgba(242, 193, 78, 0.16);
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
.totem-shell .totem-badge {
|
| 468 |
+
border: 1px solid rgba(242, 193, 78, 0.45);
|
| 469 |
+
border-radius: 8px;
|
| 470 |
+
padding: 3px 7px;
|
| 471 |
+
color: var(--totem-gold);
|
| 472 |
+
font-size: 12px;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
.totem-shell .totem-placeholder {
|
| 476 |
border: 1px dashed rgba(174, 183, 204, 0.45);
|
| 477 |
border-radius: var(--radius-card);
|
|
|
|
| 489 |
}
|
| 490 |
|
| 491 |
@media (max-width: 1200px) {
|
| 492 |
+
.totem-topbar {
|
| 493 |
+
grid-template-columns: auto minmax(200px, 1fr);
|
| 494 |
+
row-gap: var(--space-2);
|
| 495 |
+
}
|
| 496 |
.totem-shell .totem-row {
|
| 497 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 498 |
}
|
|
|
|
| 509 |
width: 100%;
|
| 510 |
flex-basis: auto;
|
| 511 |
}
|
| 512 |
+
.totem-topbar {
|
| 513 |
+
grid-template-columns: 1fr;
|
| 514 |
+
}
|
| 515 |
.totem-shell .totem-row {
|
| 516 |
grid-template-columns: 1fr;
|
| 517 |
}
|
|
|
|
| 747 |
return workbook_path(uploaded_file)
|
| 748 |
|
| 749 |
|
| 750 |
+
def render_sidebar(active: str = "Home") -> str:
|
| 751 |
+
nav_labels = [
|
| 752 |
+
"Home",
|
| 753 |
+
"Projects",
|
| 754 |
+
"Workbook Upload",
|
| 755 |
+
"TOTEM Analytics",
|
| 756 |
+
"Revision Queue",
|
| 757 |
+
"Risk Clusters",
|
| 758 |
+
"Codex Extractor",
|
| 759 |
+
"Export",
|
| 760 |
+
]
|
| 761 |
+
active_label = str(active or "").strip()
|
| 762 |
+
items = []
|
| 763 |
+
for label in nav_labels:
|
| 764 |
+
classes = ["totem-nav-item"]
|
| 765 |
+
attrs = []
|
| 766 |
+
if label == active_label:
|
| 767 |
+
classes.append("active")
|
| 768 |
+
if label == "Codex Extractor":
|
| 769 |
+
classes.append("js-open-codex")
|
| 770 |
+
attrs.append('role="button"')
|
| 771 |
+
attrs.append('tabindex="0"')
|
| 772 |
+
attrs.append('aria-label="Open Codex Extractor tab"')
|
| 773 |
+
class_attr = " ".join(classes)
|
| 774 |
+
extra_attrs = f" {' '.join(attrs)}" if attrs else ""
|
| 775 |
+
items.append(f'<div class="{class_attr}"{extra_attrs}>{esc(label)}</div>')
|
| 776 |
+
|
| 777 |
+
return f"""
|
| 778 |
+
<aside class="totem-sidebar">
|
| 779 |
+
<div class="totem-brand">TOTEM Studio<small>Story intelligence OS</small></div>
|
| 780 |
+
{''.join(items)}
|
| 781 |
+
<div class="totem-quote-card">Story intelligence turns inspiration into impact.</div>
|
| 782 |
+
</aside>
|
| 783 |
+
"""
|
| 784 |
+
|
| 785 |
+
|
| 786 |
+
def render_topbar(state: dict) -> str:
|
| 787 |
+
project_name = esc(state.get("project_name", "Editorial Workspace"))
|
| 788 |
+
return f"""
|
| 789 |
+
<section class="totem-topbar">
|
| 790 |
+
<div>
|
| 791 |
+
<span class="totem-chip active">Dashboard</span>
|
| 792 |
+
<span class="totem-chip">Codex Extractor</span>
|
| 793 |
+
<span class="totem-chip">Smoke Signal</span>
|
| 794 |
+
</div>
|
| 795 |
+
<div class="totem-search">Search projects, workbooks, blocks...</div>
|
| 796 |
+
<div class="totem-workspace">
|
| 797 |
+
<span>{project_name}</span>
|
| 798 |
+
<span aria-hidden="true">🔔</span>
|
| 799 |
+
<span class="totem-avatar">T</span>
|
| 800 |
+
<span class="totem-badge">TOTEM Studio</span>
|
| 801 |
+
</div>
|
| 802 |
+
</section>
|
| 803 |
+
"""
|
| 804 |
+
|
| 805 |
+
|
| 806 |
def render_dashboard(state: dict) -> str:
|
| 807 |
"""
|
| 808 |
+
Stage 3/4 static shell wrapper.
|
| 809 |
+
Callback wiring remains untouched until later stages.
|
| 810 |
"""
|
| 811 |
s = normalize_dashboard_state(state)
|
|
|
|
|
|
|
| 812 |
signal = int(s.get("totem_signal", 0) or 0)
|
| 813 |
return f"""
|
| 814 |
<div class="totem-shell">
|
| 815 |
+
{render_sidebar(active='Home')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 816 |
<main class="totem-main">
|
| 817 |
<div class="totem-main-inner">
|
| 818 |
+
{render_topbar(s)}
|
|
|
|
|
|
|
|
|
|
| 819 |
<section class="totem-hero">
|
| 820 |
<h2 style="margin:0;font-family:var(--font-display);font-size:44px;color:var(--totem-gold);">TOTEM Studio</h2>
|
| 821 |
<p style="margin:10px 0 0 0;font-size:22px;color:rgba(246,241,232,.92);font-family:var(--font-ui);">
|