Add a Production dashboard with an animated knowledge graph

The sidebar now groups Production under Dashboard and the four existing
pages under Settings, and Production is the landing page. It shows content
in progress, material, content made, approved and LMS-ready counts, plus a
knowledge graph linking Brain documents, results, found sources and
schedules, with the schedule and what is being made right now beside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-27 15:57:10 +07:00
parent 6d4850f4ce
commit ddbdbc7d44
8 changed files with 608 additions and 16 deletions

View File

@@ -281,3 +281,99 @@ dialog::backdrop{background:rgba(16,24,40,.45);backdrop-filter:blur(4px)}
.app main{animation:page-in .28s ease-out}
@keyframes page-in{from{opacity:0;transform:translateY(6px)}}
@media (prefers-reduced-motion:reduce){.skel,.app main{animation:none}}
/* Sidebar groups: Dashboard, then Settings */
.sidebar nav .nav-label{margin:0 12px 6px}
.sidebar nav .nav-label~.nav-label{margin-top:18px}
@media(max-width:700px){.sidebar nav .nav-label{display:none}}
/* Production: metric row with the in-progress count as the hero, then the knowledge graph */
.prod-metrics{grid-template-columns:repeat(5,minmax(0,1fr))}
.prod-metrics .metric>span{display:flex;align-items:center;gap:8px}
.live-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#4ade80;box-shadow:0 0 0 0 rgba(74,222,128,.6);animation:live-pulse 1.8s ease-out infinite;flex-shrink:0}
@keyframes live-pulse{70%{box-shadow:0 0 0 8px rgba(74,222,128,0)}100%{box-shadow:0 0 0 0 rgba(74,222,128,0)}}
.graph-stage{--g-sources:#6ce9a6;--g-made:#fde272;--g-working:#c3b5fd;--g-research:#84caff;--g-schedules:#fdb022;
position:relative;min-height:640px;border-radius:22px;overflow:hidden;color:#e4e7ec;
background:radial-gradient(ellipse at 45% 50%,#0f2a4d 0%,#0a1628 55%,#060d1a 100%);box-shadow:0 24px 60px -28px rgba(16,24,40,.6)}
.graph-svg{position:absolute;inset:0;width:calc(100% - 300px);height:100%;display:block}
.graph-loading{position:absolute;inset:0 300px 0 0;display:grid;place-items:center;color:#98a2b3;font-size:13px}
.graph-stars circle{fill:#fff;opacity:.25;animation:twinkle 4s ease-in-out infinite}
@keyframes twinkle{50%{opacity:.04}}
.graph-glow{transform-origin:500px 320px;animation:glow-breathe 7s ease-in-out infinite}
@keyframes glow-breathe{50%{transform:scale(1.08);opacity:.75}}
.graph-orbit{fill:none;stroke:rgba(132,202,255,.12);stroke-dasharray:2 7;transform-origin:500px 320px;animation:orbit-spin 90s linear infinite}
.graph-orbit.slow{stroke:rgba(132,202,255,.07);animation-duration:160s;animation-direction:reverse}
@keyframes orbit-spin{to{transform:rotate(360deg)}}
.graph-links path{fill:none;transition:opacity .3s,stroke .3s,stroke-width .3s}
.graph-links path.hub{stroke:var(--c);stroke-opacity:.16;stroke-width:.8}
.graph-links path.cross{stroke:rgba(132,202,255,.22);stroke-width:.9;stroke-dasharray:4 5;animation:dash-flow 3s linear infinite}
@keyframes dash-flow{to{stroke-dashoffset:-18}}
.graph-links path.lit{stroke:var(--c);stroke-opacity:.9;stroke-width:1.6}
.graph-links path.dim{opacity:.12}
.graph-particles circle{fill:var(--c);filter:drop-shadow(0 0 3px var(--c));opacity:0;transition:opacity .3s}
.graph-particles circle.dim{visibility:hidden}
.graph-node{cursor:pointer;transition:opacity .3s}
.graph-node.dim{opacity:.2!important}
.graph-node .hit{fill:transparent}
.graph-node .core{fill:var(--c);transition:transform .25s cubic-bezier(.3,1.6,.5,1)}
.graph-node:is(.hover,.sel) .core{transform:scale(1.7)}
.graph-node.hub .core{fill:var(--c);opacity:.9}
.graph-node.hub:is(.hover,.sel) .core{transform:scale(1.25)}
.graph-node .dot{fill:url(#graph-core);pointer-events:none}
.graph-node .halo{fill:var(--c);opacity:.1;animation:halo-breathe 4s ease-in-out infinite}
@keyframes halo-breathe{50%{opacity:.2;transform:scale(1.15)}}
.graph-node .ring{fill:none;stroke:var(--c);stroke-width:1.2;opacity:0;animation:ring-out 3.2s ease-out infinite}
.graph-node .ring.late{animation-delay:1.6s}
@keyframes ring-out{0%{opacity:.7;transform:scale(1)}100%{opacity:0;transform:scale(3.2)}}
.graph-node :is(.core,.halo,.ring){transform-box:fill-box;transform-origin:center}
.graph-node text{fill:#fff;font-size:13px;font-weight:700;letter-spacing:.01em;text-anchor:middle;paint-order:stroke;stroke:#08142a;stroke-width:4px;stroke-linejoin:round;pointer-events:none}
.graph-node:not(.hub) text{font-size:11.5px;font-weight:600;opacity:0;transform:translateY(4px);transition:opacity .2s,transform .2s}
.graph-node:not(.hub):is(.hover,.sel) text{opacity:1;transform:none}
.graph-node.c-working:not(.hub) .core{animation:node-pulse 1.6s ease-in-out infinite}
@keyframes node-pulse{50%{opacity:.4}}
.c-sources{--c:var(--g-sources)}.c-made{--c:var(--g-made)}.c-working{--c:var(--g-working)}.c-research{--c:var(--g-research)}.c-schedules{--c:var(--g-schedules)}
.graph-note{position:absolute;top:16px;left:16px;z-index:2;display:flex;align-items:center;gap:8px;max-width:min(460px,calc(100% - 340px));
padding:8px 14px;border-radius:999px;background:rgba(10,22,40,.8);border:1px solid rgba(255,255,255,.12);font-size:12.5px;backdrop-filter:blur(8px)}
.graph-note b{color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.graph-note span{color:#98a2b3;white-space:nowrap}
.graph-note button{display:grid;place-items:center;border:0;background:none;color:#98a2b3;padding:0;cursor:pointer}
.graph-note svg{width:14px;height:14px}
.graph-legend{position:absolute;left:16px;bottom:16px;z-index:2;display:flex;flex-wrap:wrap;gap:6px 14px;list-style:none;margin:0;padding:0;font-size:11.5px;color:#98a2b3}
.graph-legend li{display:flex;align-items:center;gap:6px}
.node-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--c);box-shadow:0 0 6px var(--c);flex-shrink:0}
.node-dot.sources{--c:var(--g-sources)}.node-dot.made{--c:var(--g-made)}.node-dot.working{--c:var(--g-working)}.node-dot.research{--c:var(--g-research)}.node-dot.schedules{--c:var(--g-schedules)}
.graph-panel{position:absolute;top:16px;right:16px;bottom:16px;width:284px;z-index:2;overflow:auto;padding:14px;border-radius:18px;
background:rgba(10,22,40,.72);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(10px)}
.graph-panel h3{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#98a2b3;margin:18px 4px 8px;font-weight:650}
.graph-stats{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.graph-stats>div{padding:12px;border-radius:12px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08)}
.graph-stats small{display:block;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:#98a2b3}
.graph-stats b{display:block;font-size:28px;font-weight:650;color:#fff;margin:6px 0 2px;font-variant-numeric:tabular-nums}
.graph-stats span{font-size:11px;color:#98a2b3}
.graph-list{list-style:none;margin:0;padding:0;display:grid}
.graph-list li{display:grid;grid-template-columns:auto 1fr auto;gap:4px 10px;align-items:start;padding:9px 4px;border-bottom:1px solid rgba(255,255,255,.06);font-size:12.5px;line-height:1.45}
.graph-list li:last-child{border-bottom:0}
.graph-list time{font:600 11px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;color:#84caff;white-space:nowrap}
.graph-list span{color:#e4e7ec;min-width:0;overflow-wrap:anywhere}
.graph-list em{font-style:normal;font-size:10.5px;color:#98a2b3;border:1px solid rgba(255,255,255,.14);border-radius:999px;padding:1px 8px;white-space:nowrap}
.graph-list em.on{color:#6ce9a6;border-color:rgba(108,233,166,.4)}
.graph-list.now li{align-items:center}
.graph-list.now em{border:0;padding:0}
.graph-empty{font-size:12.5px;color:#98a2b3;margin:0 4px}
.graph-empty a,.graph-more{color:#84caff}
.graph-more{display:block;font-size:12px;margin:8px 4px 0}
@media(max-width:1120px){.prod-metrics{grid-template-columns:repeat(4,minmax(0,1fr))}.prod-metrics .metric:first-child{grid-column:1/-1}}
@media(max-width:900px){
.graph-stage{min-height:0;display:flex;flex-direction:column}
.graph-svg{position:relative;width:100%;height:auto;aspect-ratio:1000/640}
.graph-loading{position:relative;inset:auto;min-height:280px}
.graph-note{position:relative;top:auto;left:auto;margin:16px 16px 0;max-width:none;align-self:flex-start}
.graph-legend{position:relative;left:auto;bottom:auto;margin:0 16px 12px}
.graph-panel{position:relative;inset:auto;width:auto;margin:0 16px 16px}
}
@media(max-width:700px){.prod-metrics{grid-template-columns:1fr 1fr}.prod-metrics .metric:first-child{grid-column:1/-1}}
@media (prefers-reduced-motion:reduce){.live-dot,.graph-stars circle,.graph-glow,.graph-orbit,.graph-links path,.graph-node *{animation:none!important}}