agnes-the-ai-analyst/webapp/templates/activity_center.html
Petr d438438e33 Add configurable white-label theming via instance.yaml
Extend theming from 3 CSS variables (primary colors only) to 14
configurable properties covering colors, fonts, borders, and shape.
All values are optional with sensible defaults.

- New _theme.html include replaces duplicated inline injection
- Wire theme include into all 7 templates (base, login, dashboard,
  catalog, admin_tables, activity_center, corporate_memory)
- Conditional font loading: skip default Inter when custom font_url set
- Config.theme_overrides() classmethod generates CSS variable dict
- Visual theme-reference.html guide for instance configurators
- Document all theme keys in instance.yaml.example
2026-03-11 13:58:58 +01:00

2571 lines
94 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Activity Center - Data Analyst Portal</title>
{% if not config.THEME_FONT_URL %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
{% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='style-custom.css') }}">
<style>
/* Activity Center Page Styles */
.container-activity {
max-width: 1200px;
margin: 0 auto;
padding: var(--space-6);
}
/* Top Header Bar (matches Data Catalog) */
.top-header {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 0 32px;
height: 72px;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 100;
}
.top-header-left {
display: flex;
align-items: center;
gap: 16px;
}
.header-back {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
color: var(--text-secondary);
text-decoration: none;
transition: all 0.15s ease;
}
.header-back:hover {
background: var(--border-light);
color: var(--text-primary);
}
.header-logo-group {
display: flex;
flex-direction: column;
justify-content: center;
gap: 2px;
}
.header-logo svg {
display: block;
}
.header-subtitle {
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
letter-spacing: 0.4px;
text-transform: uppercase;
margin-top: 2px;
display: flex;
align-items: center;
gap: 8px;
}
.top-header-right {
display: flex;
align-items: center;
gap: var(--space-3);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.demo-badge {
display: inline-flex;
align-items: center;
padding: 3px 10px;
font-size: var(--text-xs);
font-weight: var(--font-bold);
border-radius: var(--radius-full);
background: rgba(245, 159, 10, 0.15);
color: #b45309;
border: 1px solid rgba(245, 159, 10, 0.4);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Executive Pulse Stats Bar */
.exec-stats-bar {
display: flex;
align-items: center;
gap: var(--space-6);
padding: var(--space-4) var(--space-6);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
margin-bottom: var(--space-4);
}
.exec-stat {
text-align: center;
flex: 1;
}
.exec-stat .value {
font-size: var(--text-lg);
font-weight: var(--font-bold);
color: var(--text-primary);
}
.exec-stat .value .suffix {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-secondary);
}
.exec-stat .value.success {
color: var(--success);
}
.exec-stat .label {
font-size: var(--text-xs);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.3px;
margin-top: 2px;
}
.exec-divider {
width: 1px;
height: 36px;
background: var(--border);
flex-shrink: 0;
}
.exec-summary-sentence {
font-size: var(--text-sm);
color: var(--text-secondary);
text-align: center;
margin-bottom: var(--space-2);
}
/* ========== Tab Navigation ========== */
.tab-nav {
display: flex;
gap: var(--space-1);
border-bottom: 2px solid var(--border);
margin-bottom: var(--space-6);
padding: 0;
}
.tab-btn {
padding: var(--space-3) var(--space-5);
font-size: var(--text-base);
font-weight: var(--font-medium);
color: var(--text-secondary);
background: none;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
cursor: pointer;
transition: all 0.15s;
display: flex;
align-items: center;
gap: var(--space-2);
white-space: nowrap;
}
.tab-btn:hover {
color: var(--text-primary);
background: var(--background);
border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.tab-btn.active {
color: var(--primary);
border-bottom-color: var(--primary);
font-weight: var(--font-semibold);
}
.tab-btn .tab-count {
font-size: var(--text-xs);
font-weight: var(--font-medium);
background: var(--border);
color: var(--text-secondary);
padding: 1px 7px;
border-radius: var(--radius-full);
}
.tab-btn.active .tab-count {
background: var(--primary-light);
color: var(--primary);
}
.tab-panel {
display: none;
}
.tab-panel.active {
display: block;
}
/* ========== Section titles ========== */
.section {
margin-bottom: var(--space-8);
}
.section-header {
margin-bottom: var(--space-5);
}
.section-header h2 {
font-size: var(--text-lg);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: var(--space-1);
}
.section-header p {
font-size: var(--text-sm);
color: var(--text-secondary);
}
/* ========== Business Process Map ========== */
.process-category {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
margin-bottom: var(--space-4);
overflow: hidden;
}
.process-category-header {
padding: var(--space-4) var(--space-5);
background: var(--background);
border-bottom: 1px solid var(--border);
font-size: var(--text-md);
font-weight: var(--font-semibold);
color: var(--text-primary);
display: flex;
align-items: center;
gap: var(--space-2);
}
.process-category-count {
font-size: var(--text-xs);
font-weight: var(--font-medium);
color: var(--text-secondary);
background: var(--border);
padding: 2px 8px;
border-radius: var(--radius-full);
}
.process-row {
padding: var(--space-4) var(--space-5);
border-bottom: 1px solid var(--border-light);
display: flex;
align-items: center;
gap: var(--space-3);
cursor: pointer;
transition: background 0.15s;
}
.process-row:last-child {
border-bottom: none;
}
.process-row:hover {
background: var(--background);
}
.process-status-dot {
width: 10px;
height: 10px;
border-radius: var(--radius-full);
flex-shrink: 0;
}
.process-status-dot.optimized { background: var(--success); }
.process-status-dot.mature { background: var(--primary); }
.process-status-dot.developing { background: var(--warning); }
.process-status-dot.early {
background: var(--border);
border: 2px solid var(--text-secondary);
box-sizing: border-box;
}
.process-name {
font-size: var(--text-base);
font-weight: var(--font-medium);
color: var(--text-primary);
flex: 1;
}
.maturity-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: var(--text-xs);
font-weight: var(--font-medium);
border-radius: var(--radius-full);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.maturity-badge.optimized { background: rgba(16, 183, 127, 0.1); color: var(--success); }
.maturity-badge.mature { background: var(--primary-light); color: var(--primary); }
.maturity-badge.developing { background: rgba(245, 159, 10, 0.1); color: #b45309; }
.maturity-badge.early { background: var(--background); color: var(--text-secondary); }
.process-queries {
font-size: var(--text-sm);
color: var(--text-secondary);
white-space: nowrap;
}
.process-teams {
font-size: var(--text-xs);
color: var(--text-secondary);
white-space: nowrap;
}
.process-expand-icon {
color: var(--text-secondary);
transition: transform 0.2s;
flex-shrink: 0;
}
.process-expand-icon.expanded {
transform: rotate(180deg);
}
.process-detail {
display: none;
padding: var(--space-4) var(--space-5) var(--space-4) calc(var(--space-5) + 22px);
background: var(--background);
border-bottom: 1px solid var(--border-light);
}
.process-detail.visible {
display: block;
}
.process-detail-section {
margin-bottom: var(--space-3);
}
.process-detail-section:last-child {
margin-bottom: 0;
}
.process-detail-label {
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.3px;
margin-bottom: var(--space-1);
}
.process-detail-text {
font-size: var(--text-sm);
color: var(--text-primary);
line-height: 1.6;
}
.process-detail-tags {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.process-detail-tag {
font-size: var(--text-xs);
background: var(--surface);
border: 1px solid var(--border);
padding: 2px 8px;
border-radius: var(--radius-sm);
color: var(--text-secondary);
}
/* ========== Team Maturity Leaderboard ========== */
.leaderboard-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
padding: var(--space-5);
margin-bottom: var(--space-6);
}
.leaderboard-card h3 {
font-size: var(--text-md);
font-weight: var(--font-semibold);
margin-bottom: var(--space-4);
}
.maturity-distribution {
display: flex;
height: 8px;
border-radius: var(--radius-full);
overflow: hidden;
margin-bottom: var(--space-4);
background: var(--background);
}
.maturity-segment { height: 100%; transition: width 0.3s ease; }
.maturity-segment.optimized { background: var(--success); }
.maturity-segment.mature { background: var(--primary); }
.maturity-segment.developing { background: var(--warning); }
.maturity-segment.early { background: var(--border); }
.maturity-legend {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
margin-bottom: var(--space-5);
font-size: var(--text-xs);
color: var(--text-secondary);
}
.maturity-legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.maturity-legend-dot {
width: 8px;
height: 8px;
border-radius: var(--radius-full);
}
.maturity-legend-dot.optimized { background: var(--success); }
.maturity-legend-dot.mature { background: var(--primary); }
.maturity-legend-dot.developing { background: var(--warning); }
.maturity-legend-dot.early { background: var(--border); }
/* Team rows in leaderboard - 2-column grid for full-width layout */
.team-rows-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0 var(--space-8);
}
.team-row {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) 0;
border-bottom: 1px solid var(--border-light);
cursor: pointer;
transition: background 0.15s;
}
.team-row:hover {
background: var(--background);
margin: 0 calc(-1 * var(--space-3));
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
}
.team-row-name {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-primary);
min-width: 140px;
flex-shrink: 0;
}
.team-row-bar-wrap {
flex: 1;
height: 6px;
background: var(--background);
border-radius: var(--radius-full);
overflow: hidden;
}
.team-row-bar {
height: 100%;
border-radius: var(--radius-full);
transition: width 0.3s ease;
}
.team-row-bar.optimized { background: var(--success); }
.team-row-bar.mature { background: var(--primary); }
.team-row-bar.developing { background: var(--warning); }
.team-row-bar.early { background: var(--border); }
.team-row-score {
font-size: var(--text-sm);
font-weight: var(--font-semibold);
color: var(--text-primary);
min-width: 28px;
text-align: right;
}
.team-row-trend {
font-size: var(--text-sm);
min-width: 16px;
text-align: center;
}
.team-row-trend.up { color: var(--success); }
.team-row-trend.stable { color: var(--text-secondary); }
.team-row-trend.down { color: var(--error); }
/* ========== Live Activity Feed ========== */
.feed-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
display: flex;
flex-direction: column;
}
.feed-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-5);
border-bottom: 1px solid var(--border);
}
.feed-header h3 {
font-size: var(--text-md);
font-weight: var(--font-semibold);
display: flex;
align-items: center;
gap: var(--space-2);
}
.feed-filters {
display: flex;
gap: var(--space-2);
}
.feed-filter-btn {
padding: var(--space-1) var(--space-3);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-size: var(--text-xs);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
}
.feed-filter-btn:hover { border-color: var(--text-secondary); }
.feed-filter-btn.active {
background: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
.feed-list {
padding: var(--space-3);
}
/* Feed items in 2-column grid for full-width layout */
.feed-list-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0 var(--space-4);
}
.feed-item {
display: flex;
gap: var(--space-3);
padding: var(--space-3);
border-radius: var(--radius-md);
transition: background 0.15s;
}
.feed-item:hover {
background: var(--background);
}
.feed-avatar {
width: 32px;
height: 32px;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: white;
flex-shrink: 0;
}
.feed-body {
flex: 1;
min-width: 0;
}
.feed-person {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-primary);
}
.feed-team {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-left: var(--space-1);
}
.feed-process {
font-size: var(--text-sm);
font-weight: var(--font-medium);
margin-top: 2px;
}
.feed-process.optimized { color: var(--success); }
.feed-process.mature { color: var(--primary); }
.feed-process.developing { color: #b45309; }
.feed-process.early { color: var(--text-secondary); }
.feed-query {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.feed-meta {
display: flex;
align-items: center;
gap: var(--space-2);
margin-top: var(--space-1);
}
.feed-time {
font-size: var(--text-xs);
color: var(--text-secondary);
}
.feed-status {
width: 8px;
height: 8px;
border-radius: var(--radius-full);
flex-shrink: 0;
}
.feed-status.success { background: var(--success); }
.feed-status.partial { background: var(--warning); }
/* ========== Data Opportunities ========== */
.opportunity-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
padding: var(--space-5);
margin-bottom: var(--space-4);
transition: all 0.15s;
}
.opportunity-card:hover {
border-color: var(--primary);
box-shadow: var(--shadow-md);
}
.opportunity-header {
display: flex;
align-items: flex-start;
gap: var(--space-3);
margin-bottom: var(--space-3);
}
.priority-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: var(--text-xs);
font-weight: var(--font-bold);
border-radius: var(--radius-full);
text-transform: uppercase;
letter-spacing: 0.3px;
flex-shrink: 0;
}
.priority-badge.high { background: rgba(234, 88, 12, 0.1); color: var(--error); }
.priority-badge.medium { background: rgba(245, 159, 10, 0.1); color: #b45309; }
.priority-badge.low { background: var(--background); color: var(--text-secondary); }
.opportunity-title {
font-size: var(--text-md);
font-weight: var(--font-semibold);
color: var(--text-primary);
}
.opportunity-meta {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
margin-bottom: var(--space-3);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.opportunity-meta-label {
font-weight: var(--font-medium);
color: var(--text-secondary);
}
.opportunity-team-pills {
display: flex;
flex-wrap: wrap;
gap: var(--space-1);
}
.team-pill {
font-size: var(--text-xs);
background: var(--primary-light);
color: var(--primary);
padding: 1px 6px;
border-radius: var(--radius-full);
font-weight: var(--font-medium);
}
.opportunity-business-case {
font-size: var(--text-sm);
color: var(--text-secondary);
line-height: 1.6;
}
.opportunity-card {
cursor: pointer;
}
.opportunity-expand-icon {
color: var(--text-secondary);
transition: transform 0.2s;
flex-shrink: 0;
margin-left: auto;
}
.opportunity-expand-icon.expanded {
transform: rotate(180deg);
}
/* Opportunity Detail Panel */
.opportunity-detail {
display: none;
margin-top: var(--space-4);
padding-top: var(--space-4);
border-top: 1px solid var(--border);
}
.opportunity-detail.visible {
display: block;
}
.opp-detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-5);
margin-bottom: var(--space-4);
}
.opp-detail-section {
margin-bottom: var(--space-4);
}
.opp-detail-section:last-child {
margin-bottom: 0;
}
.opp-detail-label {
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--space-2);
display: flex;
align-items: center;
gap: var(--space-2);
}
.opp-detail-label svg {
color: var(--primary);
}
.opp-detail-text {
font-size: var(--text-sm);
color: var(--text-primary);
line-height: 1.6;
}
/* Mermaid diagram container */
.opp-mermaid-wrap {
background: var(--background);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-4);
margin-bottom: var(--space-4);
overflow-x: auto;
}
.opp-mermaid-wrap .mermaid {
display: flex;
justify-content: center;
}
.opp-mermaid-wrap svg {
max-width: 100%;
height: auto;
}
.opp-diagram-legend {
display: flex;
gap: var(--space-4);
justify-content: center;
margin-top: var(--space-2);
font-size: var(--text-xs);
color: var(--text-secondary);
}
.opp-legend-item {
display: flex;
align-items: center;
gap: var(--space-1);
}
.opp-legend-dot {
width: 10px;
height: 10px;
border-radius: 2px;
}
.opp-legend-dot.existing {
background: #dbeafe;
border: 1px solid #2563eb;
}
.opp-legend-dot.new {
background: #fed7aa;
border: 1px solid #ea580c;
}
/* Join keys table */
.opp-join-table {
width: 100%;
border-collapse: collapse;
font-size: var(--text-sm);
margin-bottom: var(--space-2);
}
.opp-join-table th {
text-align: left;
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.3px;
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--border);
background: var(--background);
}
.opp-join-table td {
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--border-light);
color: var(--text-primary);
}
.opp-join-table .col-name {
font-family: var(--font-mono);
font-size: var(--text-xs);
background: var(--background);
padding: 2px 6px;
border-radius: var(--radius-sm);
color: var(--primary-dark);
}
.opp-join-table .tbl-new {
color: #ea580c;
font-weight: var(--font-medium);
}
.opp-join-table .tbl-existing {
color: #2563eb;
font-weight: var(--font-medium);
}
.opp-join-arrow {
color: var(--text-secondary);
text-align: center;
}
/* Team impact list */
.opp-beneficiary {
display: flex;
gap: var(--space-3);
padding: var(--space-3) 0;
border-bottom: 1px solid var(--border-light);
}
.opp-beneficiary:last-child {
border-bottom: none;
}
.opp-beneficiary-team {
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: var(--primary);
background: var(--primary-light);
padding: 2px 8px;
border-radius: var(--radius-full);
white-space: nowrap;
height: fit-content;
flex-shrink: 0;
}
.opp-beneficiary-impact {
font-size: var(--text-sm);
color: var(--text-primary);
line-height: 1.5;
}
/* Enabled queries */
.opp-query-example {
display: flex;
align-items: flex-start;
gap: var(--space-2);
padding: var(--space-2) 0;
font-size: var(--text-sm);
color: var(--text-secondary);
}
.opp-query-example::before {
content: ">";
font-family: var(--font-mono);
color: var(--success);
font-weight: var(--font-bold);
flex-shrink: 0;
}
@media (max-width: 1024px) {
.opp-detail-grid {
grid-template-columns: 1fr;
}
}
/* ========== Team Detail Accordion ========== */
.team-accordion {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
margin-bottom: var(--space-3);
overflow: hidden;
}
.team-detail-header {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-4) var(--space-5);
cursor: pointer;
transition: background 0.15s;
}
.team-detail-header:hover {
background: var(--background);
}
.team-detail-name {
font-size: var(--text-base);
font-weight: var(--font-semibold);
color: var(--text-primary);
min-width: 140px;
}
.team-detail-stat {
font-size: var(--text-sm);
color: var(--text-secondary);
white-space: nowrap;
}
.team-detail-stat strong {
color: var(--text-primary);
}
.team-detail-highlight {
font-size: var(--text-xs);
color: var(--text-secondary);
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.team-detail-chevron {
color: var(--text-secondary);
transition: transform 0.2s;
flex-shrink: 0;
}
.team-detail-chevron.expanded {
transform: rotate(180deg);
}
.team-detail-body {
display: none;
border-top: 1px solid var(--border);
padding: var(--space-4) var(--space-5);
}
.team-detail-body.visible {
display: block;
}
.member-row {
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-3) 0;
border-bottom: 1px solid var(--border-light);
}
.member-row:last-child {
border-bottom: none;
}
.member-status-dot {
width: 8px;
height: 8px;
border-radius: var(--radius-full);
margin-top: 5px;
flex-shrink: 0;
}
.member-status-dot.active { background: var(--success); }
.member-status-dot.idle { background: var(--warning); }
.member-status-dot.offline { background: var(--border); }
.member-info {
flex: 1;
min-width: 0;
}
.member-name {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-primary);
}
.member-role {
font-size: var(--text-xs);
color: var(--text-secondary);
}
.member-stats {
display: flex;
gap: var(--space-4);
font-size: var(--text-xs);
color: var(--text-secondary);
white-space: nowrap;
}
.member-recent-queries {
margin-top: var(--space-2);
}
.member-query-item {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-1) 0;
font-size: var(--text-xs);
}
.member-query-process {
padding: 1px 6px;
border-radius: var(--radius-sm);
font-weight: var(--font-medium);
flex-shrink: 0;
}
.member-query-process.optimized { background: rgba(16, 183, 127, 0.1); color: var(--success); }
.member-query-process.mature { background: var(--primary-light); color: var(--primary); }
.member-query-process.developing { background: rgba(245, 159, 10, 0.1); color: #b45309; }
.member-query-process.early { background: var(--background); color: var(--text-secondary); }
.member-query-text {
color: var(--text-secondary);
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.member-query-time {
color: var(--text-secondary);
white-space: nowrap;
flex-shrink: 0;
}
.member-query-status {
width: 6px;
height: 6px;
border-radius: var(--radius-full);
flex-shrink: 0;
}
.member-query-status.success { background: var(--success); }
.member-query-status.partial { background: var(--warning); }
/* ========== Footer ========== */
.page-footer {
margin-top: var(--space-8);
padding-top: var(--space-5);
border-top: 1px solid var(--border);
text-align: center;
color: var(--text-secondary);
font-size: var(--text-sm);
}
/* ========== Overview Tab ========== */
.overview-hero {
display: grid;
grid-template-columns: auto 1fr auto;
gap: var(--space-6);
align-items: center;
padding: var(--space-6);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
margin-bottom: var(--space-6);
}
.overview-score-ring {
position: relative;
width: 120px;
height: 120px;
flex-shrink: 0;
}
.overview-score-ring svg {
transform: rotate(-90deg);
width: 120px;
height: 120px;
}
.overview-score-ring .ring-bg {
fill: none;
stroke: var(--border);
stroke-width: 8;
}
.overview-score-ring .ring-fill {
fill: none;
stroke-width: 8;
stroke-linecap: round;
transition: stroke-dashoffset 0.8s ease;
}
.overview-score-ring .ring-fill.level-optimized { stroke: var(--success); }
.overview-score-ring .ring-fill.level-mature { stroke: var(--primary); }
.overview-score-ring .ring-fill.level-developing { stroke: var(--warning); }
.overview-score-value {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.overview-score-value .number {
font-size: 32px;
font-weight: 700;
color: var(--text-primary);
line-height: 1;
}
.overview-score-value .label {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-top: 2px;
}
.overview-level-counts {
display: flex;
gap: var(--space-5);
}
.overview-level-count {
text-align: center;
}
.overview-level-count .count {
font-size: 28px;
font-weight: 700;
line-height: 1;
}
.overview-level-count .count.optimized { color: var(--success); }
.overview-level-count .count.mature { color: var(--primary); }
.overview-level-count .count.developing { color: var(--warning); }
.overview-level-count .level-label {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-top: 4px;
}
.overview-total-value {
text-align: right;
padding-left: var(--space-4);
border-left: 1px solid var(--border);
}
.overview-total-value .value {
font-size: 22px;
font-weight: 700;
color: var(--success);
line-height: 1;
}
.overview-total-value .label {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-top: 4px;
}
/* Roadmap Lanes */
.roadmap-section {
margin-bottom: var(--space-6);
}
.roadmap-section-title {
font-size: var(--text-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-4);
}
.roadmap-lanes {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: var(--space-4);
}
.roadmap-lane {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-4);
min-height: 120px;
}
.roadmap-lane-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
padding-bottom: var(--space-3);
border-bottom: 2px solid var(--border);
font-size: var(--text-sm);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.roadmap-lane-header.developing { border-bottom-color: var(--warning); color: var(--warning); }
.roadmap-lane-header.mature { border-bottom-color: var(--primary); color: var(--primary); }
.roadmap-lane-header.optimized { border-bottom-color: var(--success); color: var(--success); }
.roadmap-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-3);
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-md);
margin-bottom: var(--space-2);
cursor: pointer;
transition: all 0.15s ease;
}
.roadmap-card:hover {
border-color: var(--primary);
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.roadmap-card:last-child {
margin-bottom: 0;
}
.roadmap-card-name {
font-size: var(--text-sm);
font-weight: 500;
color: var(--text-primary);
}
.roadmap-card-score {
font-size: var(--text-xs);
font-weight: 600;
color: var(--text-secondary);
white-space: nowrap;
}
.roadmap-card-arrow {
color: var(--text-tertiary);
margin-left: var(--space-2);
font-size: var(--text-xs);
}
/* Category Detail Cards */
.overview-details-title {
font-size: var(--text-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-4);
}
.overview-category-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
margin-bottom: var(--space-3);
overflow: hidden;
}
.overview-category-header {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-4);
cursor: pointer;
transition: background 0.15s ease;
}
.overview-category-header:hover {
background: var(--bg);
}
.overview-cat-level {
display: inline-flex;
align-items: center;
padding: 3px 10px;
font-size: var(--text-xs);
font-weight: 600;
border-radius: var(--radius-full);
text-transform: capitalize;
}
.overview-cat-level.optimized { background: rgba(16, 185, 129, 0.12); color: var(--success); }
.overview-cat-level.mature { background: rgba(59, 130, 246, 0.12); color: var(--primary); }
.overview-cat-level.developing { background: rgba(245, 159, 10, 0.12); color: var(--warning); }
.overview-cat-name {
font-size: var(--text-base);
font-weight: 600;
color: var(--text-primary);
flex: 1;
}
.overview-cat-meta {
display: flex;
align-items: center;
gap: var(--space-4);
font-size: var(--text-sm);
color: var(--text-secondary);
}
.overview-cat-chevron {
transition: transform 0.2s ease;
color: var(--text-tertiary);
}
.overview-cat-chevron.expanded {
transform: rotate(180deg);
}
.overview-category-body {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.overview-category-body.visible {
max-height: 800px;
}
.overview-category-content {
padding: 0 var(--space-4) var(--space-4);
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-4);
}
.overview-processes-list {
list-style: none;
padding: 0;
margin: 0 0 var(--space-3) 0;
}
.overview-processes-list li {
display: flex;
align-items: center;
gap: var(--space-2);
padding: 4px 0;
font-size: var(--text-sm);
color: var(--text-primary);
}
.overview-process-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.overview-process-dot.optimized { background: var(--success); }
.overview-process-dot.mature { background: var(--primary); }
.overview-process-dot.developing { background: var(--warning); }
.overview-process-dot.early { background: var(--text-tertiary); }
.overview-process-status {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-left: auto;
text-transform: capitalize;
}
.overview-actions-section h4,
.overview-left-col h4 {
font-size: var(--text-sm);
font-weight: 600;
color: var(--text-primary);
margin: 0 0 var(--space-2) 0;
}
.overview-actions-list {
list-style: none;
padding: 0;
margin: 0 0 var(--space-3) 0;
}
.overview-actions-list li {
display: flex;
align-items: flex-start;
gap: var(--space-2);
padding: 4px 0;
font-size: var(--text-sm);
color: var(--text-secondary);
}
.overview-actions-list li::before {
content: "\2610";
color: var(--text-tertiary);
flex-shrink: 0;
}
.overview-effort-timeline {
display: flex;
gap: var(--space-3);
font-size: var(--text-xs);
color: var(--text-secondary);
margin-bottom: var(--space-3);
}
.overview-effort-timeline span {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: var(--bg);
border-radius: var(--radius-sm);
}
/* Timeline Comparison Widget */
.timeline-compare {
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-3);
}
.timeline-compare-header {
font-size: var(--text-xs);
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--space-3);
}
.timeline-compare-row {
display: grid;
grid-template-columns: 100px 1fr 80px;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-2);
}
.timeline-compare-row:last-of-type {
margin-bottom: 0;
}
.timeline-compare-label {
font-size: var(--text-xs);
color: var(--text-secondary);
font-weight: 500;
}
.timeline-bar-track {
height: 22px;
background: var(--surface);
border-radius: var(--radius-sm);
overflow: hidden;
position: relative;
}
.timeline-bar-fill {
height: 100%;
border-radius: var(--radius-sm);
transition: width 0.6s ease;
}
.timeline-bar-fill.traditional {
background: linear-gradient(90deg, rgba(245, 159, 10, 0.3), rgba(245, 159, 10, 0.15));
border: 1px solid rgba(245, 159, 10, 0.4);
}
.timeline-bar-fill.ai-accelerated {
background: linear-gradient(90deg, rgba(99, 91, 255, 0.35), rgba(99, 91, 255, 0.15));
border: 1px solid rgba(99, 91, 255, 0.5);
}
.timeline-compare-value {
font-size: var(--text-sm);
font-weight: 600;
text-align: right;
}
.timeline-compare-value.traditional {
color: var(--warning);
}
.timeline-compare-value.ai-accelerated {
color: #635BFF;
}
.timeline-ai-note {
display: flex;
align-items: flex-start;
gap: var(--space-2);
margin-top: var(--space-3);
padding-top: var(--space-2);
border-top: 1px solid var(--border);
}
.timeline-ai-note svg {
flex-shrink: 0;
margin-top: 1px;
}
.timeline-ai-note-text {
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.5;
}
.timeline-ai-note-text strong {
color: #635BFF;
font-weight: 600;
}
.timeline-speedup-badge {
display: inline-flex;
align-items: center;
gap: 3px;
padding: 1px 6px;
background: rgba(99, 91, 255, 0.1);
color: #635BFF;
font-size: 10px;
font-weight: 700;
border-radius: var(--radius-full);
margin-left: var(--space-2);
}
.overview-blockers {
font-size: var(--text-xs);
color: var(--text-secondary);
padding: var(--space-2) var(--space-3);
background: rgba(245, 159, 10, 0.06);
border-radius: var(--radius-sm);
border-left: 3px solid var(--warning);
margin-bottom: var(--space-3);
}
.overview-business-value {
padding: var(--space-3);
background: rgba(16, 185, 129, 0.06);
border-radius: var(--radius-md);
border-left: 3px solid var(--success);
}
.overview-business-value h4 {
font-size: var(--text-sm);
font-weight: 600;
color: var(--success);
margin: 0 0 4px 0;
}
.overview-business-value p {
font-size: var(--text-sm);
color: var(--text-primary);
margin: 0;
line-height: 1.5;
}
/* ========== Responsive ========== */
@media (max-width: 1024px) {
.team-rows-grid {
grid-template-columns: 1fr;
}
.feed-list-grid {
grid-template-columns: 1fr;
}
.roadmap-lanes {
grid-template-columns: 1fr;
}
.overview-hero {
grid-template-columns: auto 1fr;
}
.overview-total-value {
grid-column: 1 / -1;
text-align: left;
border-left: none;
padding-left: 0;
padding-top: var(--space-3);
border-top: 1px solid var(--border);
}
}
@media (max-width: 768px) {
.container-activity {
padding: var(--space-4);
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: var(--space-3);
}
.exec-stats-bar {
flex-wrap: wrap;
gap: var(--space-4);
padding: var(--space-4);
}
.exec-stat {
min-width: calc(50% - var(--space-4));
}
.exec-divider {
display: none;
}
.tab-nav {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.process-row {
flex-wrap: wrap;
}
.process-teams {
width: 100%;
margin-top: var(--space-1);
}
.team-detail-header {
flex-wrap: wrap;
}
.member-stats {
flex-wrap: wrap;
}
.feed-header {
flex-direction: column;
align-items: flex-start;
gap: var(--space-3);
}
.opportunity-header {
flex-direction: column;
}
.overview-hero {
grid-template-columns: 1fr;
text-align: center;
}
.overview-score-ring {
margin: 0 auto;
}
.overview-level-counts {
justify-content: center;
}
.overview-total-value {
text-align: center;
}
.overview-category-content {
grid-template-columns: 1fr;
}
}
</style>
{% include '_theme.html' %}
</head>
<body>
<!-- Top Header Bar (matches Data Catalog pattern) -->
<header class="top-header">
<div class="top-header-left">
<a href="{{ url_for('dashboard') }}" class="header-back" title="Back to Dashboard">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</a>
<div class="header-logo-group">
<div class="header-logo">
{{ config.LOGO_SVG | safe }}
</div>
<span class="header-subtitle">Activity Center <span class="demo-badge">DEMO</span></span>
</div>
</div>
<div class="top-header-right">
{% if session.user.picture %}
<img src="{{ session.user.picture }}" alt="Profile" class="avatar-v2">
{% endif %}
{{ session.user.email }}
</div>
</header>
<div class="container-activity">
<!-- Executive Pulse - always visible -->
<div class="exec-stats-bar">
<div class="exec-stat">
<div class="value">{{ activity.executive_summary.active_today }}</div>
<div class="label">Active Today</div>
</div>
<div class="exec-divider"></div>
<div class="exec-stat">
<div class="value">{{ activity.executive_summary.business_processes_identified }}</div>
<div class="label">Business Processes</div>
</div>
<div class="exec-divider"></div>
<div class="exec-stat">
<div class="value">{{ activity.executive_summary.decisions_supported_this_week }}<span class="suffix">/wk</span></div>
<div class="label">Decisions Supported</div>
</div>
<div class="exec-divider"></div>
<div class="exec-stat">
<div class="value">{{ activity.executive_summary.avg_success_rate }}<span class="suffix">%</span></div>
<div class="label">Success Rate</div>
</div>
<div class="exec-divider"></div>
<div class="exec-stat">
<div class="value success">{{ activity.executive_summary.adoption_trend }}</div>
<div class="label">Adoption Trend</div>
</div>
</div>
<div class="exec-summary-sentence">
{{ activity.executive_summary.active_this_week }} analysts across {{ activity.executive_summary.teams_active }} teams powering {{ activity.executive_summary.business_processes_identified }} business processes with data-driven decisions
</div>
<!-- Tab Navigation -->
<nav class="tab-nav" role="tablist">
<button class="tab-btn active" role="tab" aria-selected="true" data-tab="overview" onclick="switchTab('overview', this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 000 20 14.5 14.5 0 000-20"/><path d="M2 12h20"/></svg>
Overview
</button>
<button class="tab-btn" role="tab" aria-selected="false" data-tab="processes" onclick="switchTab('processes', this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
Processes
<span class="tab-count">{{ activity.business_processes | length }}</span>
</button>
<button class="tab-btn" role="tab" aria-selected="false" data-tab="teams" onclick="switchTab('teams', this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
Teams
<span class="tab-count">{{ activity.teams | length }}</span>
</button>
<button class="tab-btn" role="tab" aria-selected="false" data-tab="activity" onclick="switchTab('activity', this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
Activity
<span class="tab-count">{{ activity.activity_feed | length }}</span>
</button>
<button class="tab-btn" role="tab" aria-selected="false" data-tab="opportunities" onclick="switchTab('opportunities', this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Opportunities
<span class="tab-count">{{ activity.data_opportunities | length }}</span>
</button>
</nav>
<!-- Tab Panel: Overview -->
<div class="tab-panel active" id="tab-overview" role="tabpanel">
<div class="section">
<!-- Hero: Score + Counts + Value -->
<div class="overview-hero">
<div class="overview-score-ring">
{% set score = activity.maturity_roadmap.summary.overall_score %}
{% set circumference = 2 * 3.14159 * 48 %}
{% set dash_offset = circumference * (1 - score / 100) %}
{% if score >= 80 %}{% set ring_class = "level-optimized" %}
{% elif score >= 60 %}{% set ring_class = "level-mature" %}
{% else %}{% set ring_class = "level-developing" %}{% endif %}
<svg viewBox="0 0 120 120">
<circle class="ring-bg" cx="60" cy="60" r="48"/>
<circle class="ring-fill {{ ring_class }}" cx="60" cy="60" r="48"
stroke-dasharray="{{ circumference }}"
stroke-dashoffset="{{ dash_offset }}"/>
</svg>
<div class="overview-score-value">
<div class="number">{{ score }}</div>
<div class="label">Maturity</div>
</div>
</div>
<div class="overview-level-counts">
<div class="overview-level-count">
<div class="count optimized">{{ activity.maturity_roadmap.summary.optimized_count }}</div>
<div class="level-label">Optimized</div>
</div>
<div class="overview-level-count">
<div class="count mature">{{ activity.maturity_roadmap.summary.mature_count }}</div>
<div class="level-label">Mature</div>
</div>
<div class="overview-level-count">
<div class="count developing">{{ activity.maturity_roadmap.summary.developing_count }}</div>
<div class="level-label">Developing</div>
</div>
</div>
<div class="overview-total-value">
<div class="value">{{ activity.maturity_roadmap.summary.total_potential_value }}</div>
<div class="label">Estimated annual value</div>
</div>
</div>
<!-- Roadmap Lanes -->
<div class="roadmap-section">
<h2 class="roadmap-section-title">Maturity Roadmap</h2>
<div class="roadmap-lanes">
<div class="roadmap-lane">
<div class="roadmap-lane-header developing">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
Developing
</div>
{% for cat in activity.maturity_roadmap.categories %}
{% if cat.current_level == 'developing' %}
<div class="roadmap-card" onclick="scrollToOverviewCategory('{{ cat.id }}')">
<div>
<div class="roadmap-card-name">{{ cat.name }}</div>
<div class="roadmap-card-score">{{ cat.score }}/100</div>
</div>
<span class="roadmap-card-arrow">&#8594;</span>
</div>
{% endif %}
{% endfor %}
</div>
<div class="roadmap-lane">
<div class="roadmap-lane-header mature">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
Mature
</div>
{% for cat in activity.maturity_roadmap.categories %}
{% if cat.current_level == 'mature' %}
<div class="roadmap-card" onclick="scrollToOverviewCategory('{{ cat.id }}')">
<div>
<div class="roadmap-card-name">{{ cat.name }}</div>
<div class="roadmap-card-score">{{ cat.score }}/100</div>
</div>
<span class="roadmap-card-arrow">&#8594;</span>
</div>
{% endif %}
{% endfor %}
</div>
<div class="roadmap-lane">
<div class="roadmap-lane-header optimized">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
Optimized
</div>
{% for cat in activity.maturity_roadmap.categories %}
{% if cat.current_level == 'optimized' %}
<div class="roadmap-card" onclick="scrollToOverviewCategory('{{ cat.id }}')">
<div>
<div class="roadmap-card-name">{{ cat.name }}</div>
<div class="roadmap-card-score">{{ cat.score }}/100</div>
</div>
<span class="roadmap-card-arrow">&#9733;</span>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<!-- Category Details -->
<div class="overview-details-section">
<h2 class="overview-details-title">Category Details</h2>
{% for cat in activity.maturity_roadmap.categories %}
<div class="overview-category-card" id="overview-cat-{{ cat.id }}">
<div class="overview-category-header" onclick="toggleOverviewCategory('{{ cat.id }}')">
<span class="overview-cat-level {{ cat.current_level }}">{{ cat.current_level }}</span>
<span class="overview-cat-name">{{ cat.name }}</span>
<span class="overview-cat-meta">
<span>{{ cat.score }}/100</span>
<span>{{ cat.process_count }} processes</span>
</span>
<svg class="overview-cat-chevron" id="overview-chevron-{{ cat.id }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="overview-category-body" id="overview-body-{{ cat.id }}">
<div class="overview-category-content">
<div class="overview-left-col">
<h4>Current Processes</h4>
<ul class="overview-processes-list">
{% for proc in cat.processes %}
<li>
<span class="overview-process-dot {{ proc.status }}"></span>
{{ proc.name }}
<span class="overview-process-status">{{ proc.status }}</span>
</li>
{% endfor %}
</ul>
{% if cat.advancement %}
<h4>Actions to reach {{ cat.advancement.target_level|capitalize }}</h4>
<ul class="overview-actions-list">
{% for action in cat.advancement.actions %}
<li>{{ action }}</li>
{% endfor %}
</ul>
<div class="overview-effort-timeline">
<span>Effort: {{ cat.advancement.effort|capitalize }}</span>
</div>
{% if cat.advancement.blockers %}
<div class="overview-blockers">
{{ cat.advancement.blockers }}
</div>
{% endif %}
<!-- Timeline Comparison -->
<div class="timeline-compare">
<div class="timeline-compare-header">Implementation Timeline</div>
<div class="timeline-compare-row">
<span class="timeline-compare-label">Traditional</span>
<div class="timeline-bar-track">
<div class="timeline-bar-fill traditional" style="width: 100%"></div>
</div>
<span class="timeline-compare-value traditional">{{ cat.advancement.timeline }}</span>
</div>
<div class="timeline-compare-row">
<span class="timeline-compare-label">With AI Data Analyst</span>
<div class="timeline-bar-track">
<div class="timeline-bar-fill ai-accelerated" style="width: 8%"></div>
</div>
<span class="timeline-compare-value ai-accelerated">
{{ cat.advancement.ai_timeline }}
<span class="timeline-speedup-badge">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10"/></svg>
Fast
</span>
</span>
</div>
<div class="timeline-ai-note">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#635BFF" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
<span class="timeline-ai-note-text">
<strong>AI Data Platform:</strong> {{ cat.advancement.ai_description }}
</span>
</div>
</div>
{% endif %}
</div>
<div class="overview-right-col">
<div class="overview-business-value">
<h4>Business Value</h4>
<p>{{ cat.business_value }}</p>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Tab Panel: Processes -->
<div class="tab-panel" id="tab-processes" role="tabpanel">
<div class="section">
<div class="section-header">
<h2>Business Process Intelligence</h2>
<p>Mapping data queries to active business processes across the organization</p>
</div>
{% set ns = namespace(cat_processes={}) %}
{% for process in activity.business_processes %}
{% if process.category not in ns.cat_processes %}
{% set _ = ns.cat_processes.update({process.category: []}) %}
{% endif %}
{% set _ = ns.cat_processes[process.category].append(process) %}
{% endfor %}
{% for category, processes in ns.cat_processes.items() %}
{% set cat_idx = loop.index %}
<div class="process-category">
<div class="process-category-header">
{{ category }}
<span class="process-category-count">{{ processes | length }}</span>
</div>
{% for process in processes %}
<div class="process-row" onclick="toggleProcessDetail('process-{{ cat_idx }}-{{ loop.index }}')">
<span class="process-status-dot {{ process.status }}"></span>
<span class="process-name">{{ process.name }}</span>
<span class="maturity-badge {{ process.status }}">{{ process.status }}</span>
<span class="process-queries">{{ process.queries_this_week }} queries/wk</span>
<span class="process-teams">{{ process.teams_involved | join(', ') }}</span>
<svg class="process-expand-icon" id="icon-process-{{ cat_idx }}-{{ loop.index }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 9l6 6 6-6"/>
</svg>
</div>
<div class="process-detail" id="process-{{ cat_idx }}-{{ loop.index }}">
{% if process.description %}
<div class="process-detail-section">
<div class="process-detail-label">Description</div>
<div class="process-detail-text">{{ process.description }}</div>
</div>
{% endif %}
{% if process.sample_queries %}
<div class="process-detail-section">
<div class="process-detail-label">Sample Queries</div>
<div class="process-detail-tags">
{% for query in process.sample_queries %}
<span class="process-detail-tag">{{ query }}</span>
{% endfor %}
</div>
</div>
{% endif %}
{% if process.data_sources %}
<div class="process-detail-section">
<div class="process-detail-label">Data Sources</div>
<div class="process-detail-tags">
{% for source in process.data_sources %}
<span class="process-detail-tag">{{ source }}</span>
{% endfor %}
</div>
</div>
{% endif %}
{% if process.impact %}
<div class="process-detail-section">
<div class="process-detail-label">Impact</div>
<div class="process-detail-text">{{ process.impact }}</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
<!-- Tab Panel: Teams -->
<div class="tab-panel" id="tab-teams" role="tabpanel">
<!-- Team Maturity Leaderboard -->
<div class="leaderboard-card">
<h3>Team Maturity</h3>
{% set optimized_count = activity.teams | selectattr('maturity', 'equalto', 'optimized') | list | length %}
{% set mature_count = activity.teams | selectattr('maturity', 'equalto', 'mature') | list | length %}
{% set developing_count = activity.teams | selectattr('maturity', 'equalto', 'developing') | list | length %}
{% set early_count = activity.teams | selectattr('maturity', 'equalto', 'early') | list | length %}
{% set total_teams = activity.teams | length %}
<div class="maturity-distribution">
{% if optimized_count > 0 %}
<div class="maturity-segment optimized" style="width: {{ (optimized_count / total_teams * 100) | round(1) }}%"></div>
{% endif %}
{% if mature_count > 0 %}
<div class="maturity-segment mature" style="width: {{ (mature_count / total_teams * 100) | round(1) }}%"></div>
{% endif %}
{% if developing_count > 0 %}
<div class="maturity-segment developing" style="width: {{ (developing_count / total_teams * 100) | round(1) }}%"></div>
{% endif %}
{% if early_count > 0 %}
<div class="maturity-segment early" style="width: {{ (early_count / total_teams * 100) | round(1) }}%"></div>
{% endif %}
</div>
<div class="maturity-legend">
<span class="maturity-legend-item"><span class="maturity-legend-dot optimized"></span> Optimized ({{ optimized_count }})</span>
<span class="maturity-legend-item"><span class="maturity-legend-dot mature"></span> Mature ({{ mature_count }})</span>
<span class="maturity-legend-item"><span class="maturity-legend-dot developing"></span> Developing ({{ developing_count }})</span>
<span class="maturity-legend-item"><span class="maturity-legend-dot early"></span> Early ({{ early_count }})</span>
</div>
<div class="team-rows-grid">
{% for team in activity.teams | sort(attribute='maturity_score', reverse=True) %}
<div class="team-row" onclick="scrollToTeam('team-detail-{{ loop.index }}')">
<span class="team-row-name">{{ team.name }}</span>
<div class="team-row-bar-wrap">
<div class="team-row-bar {{ team.maturity }}" style="width: {{ team.maturity_score }}%"></div>
</div>
<span class="team-row-score">{{ team.maturity_score }}</span>
<span class="team-row-trend {% if team.trend == 'up' %}up{% elif team.trend == 'down' %}down{% else %}stable{% endif %}">
{% if team.trend == 'up' %}&#9650;{% elif team.trend == 'down' %}&#9660;{% else %}&#8212;{% endif %}
</span>
</div>
{% endfor %}
</div>
</div>
<!-- Team Detail Accordion -->
<div class="section">
<div class="section-header">
<h2>Team Details</h2>
</div>
{% for team in activity.teams | sort(attribute='maturity_score', reverse=True) %}
<div class="team-accordion" id="team-detail-{{ loop.index }}">
<div class="team-detail-header" onclick="toggleTeam('team-detail-{{ loop.index }}')">
<span class="team-detail-name">{{ team.name }}</span>
<span class="maturity-badge {{ team.maturity }}">{{ team.maturity }}</span>
<span class="team-detail-stat"><strong>{{ team.members | length }}</strong> members</span>
<span class="team-detail-stat"><strong>{{ team.active_count }}</strong> active</span>
<span class="team-detail-stat">{{ team.success_rate }}% success</span>
<span class="team-detail-highlight">{{ team.highlight }}</span>
<svg class="team-detail-chevron" id="chevron-team-detail-{{ loop.index }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 9l6 6 6-6"/>
</svg>
</div>
<div class="team-detail-body" id="body-team-detail-{{ loop.index }}">
{% for member in team.members %}
<div class="member-row">
<span class="member-status-dot {{ member.status | lower }}"></span>
<div class="member-info">
<div class="member-name">{{ member.name }}</div>
<div class="member-role">{{ member.role }}</div>
</div>
<div class="member-stats">
<span>Last active: {{ member.last_active }}</span>
<span>{{ member.queries_today }} queries today</span>
</div>
{% if member.recent_queries %}
<div class="member-recent-queries">
{% for query in member.recent_queries %}
<div class="member-query-item">
<span class="member-query-process">{{ query.process }}</span>
<span class="member-query-text" title="{{ query.text }}">{{ query.text }}</span>
<span class="member-query-time">{{ query.timestamp }}</span>
<span class="member-query-status {{ query.status | lower }}"></span>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Tab Panel: Activity -->
<div class="tab-panel" id="tab-activity" role="tabpanel">
<div class="feed-card">
<div class="feed-header">
<h3>
<span class="live-dot"></span>
Live Activity
</h3>
<div class="feed-filters">
<button class="feed-filter-btn active" onclick="filterActivity('all', this)">All</button>
<button class="feed-filter-btn" onclick="filterActivity('today', this)">Today</button>
<button class="feed-filter-btn" onclick="filterActivity('hour', this)">This Hour</button>
</div>
</div>
<div class="feed-list" id="activityFeed">
<div class="feed-list-grid">
{% for item in activity.activity_feed %}
<div class="feed-item" data-index="{{ loop.index }}">
<div class="feed-avatar" style="background: {{ ['#0073D1', '#10B77F', '#F59F0A', '#EA580C', '#7c3aed', '#0d9668', '#b45309', '#005BA3'][loop.index0 % 8] }}">
{{ item.person_name[:2] | upper }}
</div>
<div class="feed-body">
<div>
<span class="feed-person">{{ item.person_name }}</span>
<span class="feed-team">{{ item.team }}</span>
</div>
<div class="feed-process">{{ item.process_name }}</div>
<div class="feed-query" title="{{ item.query_text }}">{{ item.query_text }}</div>
<div class="feed-meta">
<span class="feed-time">{{ item.timestamp }}</span>
<span class="feed-status {{ item.status | lower }}"></span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- Tab Panel: Opportunities -->
<div class="tab-panel" id="tab-opportunities" role="tabpanel">
<div class="section">
<div class="section-header">
<h2>Unmet Data Needs</h2>
<p>Strategic opportunities where additional data would unlock new capabilities. Click any card to see integration details.</p>
</div>
{% for opp in activity.data_opportunities %}
<div class="opportunity-card" onclick="toggleOpportunity('opp-{{ loop.index }}')">
<div class="opportunity-header">
<span class="priority-badge {{ opp.priority | lower }}">{{ opp.priority }}</span>
<span class="opportunity-title">{{ opp.title }}</span>
<svg class="opportunity-expand-icon" id="icon-opp-{{ loop.index }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 9l6 6 6-6"/>
</svg>
</div>
<div class="opportunity-meta">
<div>
<span class="opportunity-meta-label">Requested by:</span>
<span class="opportunity-team-pills">
{% for team in opp.requested_by %}
<span class="team-pill">{{ team }}</span>
{% endfor %}
</span>
</div>
{% if opp.potential_processes %}
<div>
<span class="opportunity-meta-label">Related:</span>
{{ opp.potential_processes | join(', ') }}
</div>
{% endif %}
</div>
<div class="opportunity-business-case">{{ opp.business_case }}</div>
<!-- Expandable detail panel -->
<div class="opportunity-detail" id="opp-{{ loop.index }}" onclick="event.stopPropagation()">
{% if opp.mermaid %}
<div class="opp-detail-section">
<div class="opp-detail-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>
Data Integration Map
</div>
<div class="opp-mermaid-wrap">
<pre class="mermaid" data-opp-id="opp-{{ loop.index }}">{{ opp.mermaid }}</pre>
<div class="opp-diagram-legend">
<span class="opp-legend-item"><span class="opp-legend-dot existing"></span> Existing table</span>
<span class="opp-legend-item"><span class="opp-legend-dot new"></span> New data source</span>
</div>
</div>
</div>
{% endif %}
<div class="opp-detail-grid">
<!-- Left column: Integration + Join Keys -->
<div>
{% if opp.integration_description %}
<div class="opp-detail-section">
<div class="opp-detail-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
Integration Path
</div>
<div class="opp-detail-text">{{ opp.integration_description }}</div>
</div>
{% endif %}
{% if opp.join_keys %}
<div class="opp-detail-section">
<div class="opp-detail-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
Join Keys
</div>
<table class="opp-join-table">
<thead>
<tr>
<th>New Table</th>
<th></th>
<th>Column</th>
<th></th>
<th>Existing Table</th>
</tr>
</thead>
<tbody>
{% for jk in opp.join_keys %}
<tr>
<td><span class="tbl-new">{{ jk.new_table }}</span></td>
<td class="opp-join-arrow">.</td>
<td><span class="col-name">{{ jk.column }}</span></td>
<td class="opp-join-arrow">&rarr;</td>
<td><span class="tbl-existing">{{ jk.existing_table }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<!-- Right column: Team Impact + Enabled Queries -->
<div>
{% if opp.beneficiaries %}
<div class="opp-detail-section">
<div class="opp-detail-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
Team Impact
</div>
{% for ben in opp.beneficiaries %}
<div class="opp-beneficiary">
<span class="opp-beneficiary-team">{{ ben.team }}</span>
<span class="opp-beneficiary-impact">{{ ben.impact }}</span>
</div>
{% endfor %}
</div>
{% endif %}
{% if opp.enabled_queries %}
<div class="opp-detail-section">
<div class="opp-detail-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
Enabled Queries
</div>
{% for q in opp.enabled_queries %}
<div class="opp-query-example">{{ q }}</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Footer -->
<footer class="page-footer">
{{ config.INSTANCE_COPYRIGHT or 'AI Data Analyst' }} | Activity Center
</footer>
</div>
<script>
// Tab switching with URL hash support
function switchTab(tabId, btn) {
// Deactivate all tabs and panels
document.querySelectorAll('.tab-btn').forEach(function(b) {
b.classList.remove('active');
b.setAttribute('aria-selected', 'false');
});
document.querySelectorAll('.tab-panel').forEach(function(p) {
p.classList.remove('active');
});
// Activate selected tab and panel
btn.classList.add('active');
btn.setAttribute('aria-selected', 'true');
document.getElementById('tab-' + tabId).classList.add('active');
// Update URL hash without scrolling
history.replaceState(null, null, '#' + tabId);
}
// Restore tab from URL hash on page load
(function() {
var hash = window.location.hash.replace('#', '');
var validTabs = ['overview', 'processes', 'teams', 'activity', 'opportunities'];
if (hash && validTabs.indexOf(hash) !== -1) {
var btn = document.querySelector('.tab-btn[data-tab="' + hash + '"]');
if (btn) {
switchTab(hash, btn);
}
}
})();
// Filter activity feed by time period
function filterActivity(period, btn) {
document.querySelectorAll('.feed-filter-btn').forEach(function(b) {
b.classList.remove('active');
});
btn.classList.add('active');
var items = document.querySelectorAll('.feed-item');
items.forEach(function(item, index) {
if (period === 'hour') {
item.style.display = (index < 5) ? 'flex' : 'none';
} else {
item.style.display = 'flex';
}
});
}
// Toggle team accordion
function toggleTeam(teamId) {
var body = document.getElementById('body-' + teamId);
var chevron = document.getElementById('chevron-' + teamId);
if (body.classList.contains('visible')) {
body.classList.remove('visible');
chevron.classList.remove('expanded');
} else {
body.classList.add('visible');
chevron.classList.add('expanded');
}
}
// Scroll from leaderboard to team detail
function scrollToTeam(teamId) {
var element = document.getElementById(teamId);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Expand the team if not already expanded
var body = document.getElementById('body-' + teamId);
var chevron = document.getElementById('chevron-' + teamId);
if (body && !body.classList.contains('visible')) {
body.classList.add('visible');
if (chevron) {
chevron.classList.add('expanded');
}
}
// Brief highlight effect
element.style.transition = 'box-shadow 0.3s ease';
element.style.boxShadow = '0 0 0 2px var(--primary)';
setTimeout(function() {
element.style.boxShadow = '';
}, 1500);
}
}
// Toggle process detail expansion
function toggleProcessDetail(processId) {
var detail = document.getElementById(processId);
var icon = document.getElementById('icon-' + processId);
if (detail.classList.contains('visible')) {
detail.classList.remove('visible');
if (icon) icon.classList.remove('expanded');
} else {
detail.classList.add('visible');
if (icon) icon.classList.add('expanded');
}
}
// Toggle opportunity detail with lazy Mermaid rendering
var renderedOpps = {};
function toggleOpportunity(oppId) {
var detail = document.getElementById(oppId);
var icon = document.getElementById('icon-' + oppId);
if (detail.classList.contains('visible')) {
detail.classList.remove('visible');
if (icon) icon.classList.remove('expanded');
} else {
detail.classList.add('visible');
if (icon) icon.classList.add('expanded');
// Lazy-render Mermaid diagram on first expand
if (!renderedOpps[oppId] && typeof mermaid !== 'undefined') {
var mermaidEl = detail.querySelector('.mermaid');
if (mermaidEl && !mermaidEl.getAttribute('data-processed')) {
mermaid.run({ nodes: [mermaidEl] });
renderedOpps[oppId] = true;
}
}
}
}
// Toggle overview category detail
function toggleOverviewCategory(catId) {
var body = document.getElementById('overview-body-' + catId);
var chevron = document.getElementById('overview-chevron-' + catId);
if (body.classList.contains('visible')) {
body.classList.remove('visible');
chevron.classList.remove('expanded');
} else {
body.classList.add('visible');
chevron.classList.add('expanded');
}
}
// Scroll from roadmap lane card to category detail
function scrollToOverviewCategory(catId) {
var element = document.getElementById('overview-cat-' + catId);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Expand if not already expanded
var body = document.getElementById('overview-body-' + catId);
var chevron = document.getElementById('overview-chevron-' + catId);
if (body && !body.classList.contains('visible')) {
body.classList.add('visible');
if (chevron) chevron.classList.add('expanded');
}
// Brief highlight
element.style.transition = 'box-shadow 0.3s ease';
element.style.boxShadow = '0 0 0 2px var(--primary)';
setTimeout(function() {
element.style.boxShadow = '';
}, 1500);
}
}
</script>
<!-- Mermaid.js for data integration diagrams -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({
startOnLoad: false,
theme: 'base',
themeVariables: {
fontFamily: 'Inter, system-ui, sans-serif',
fontSize: '13px',
primaryColor: '#dbeafe',
primaryBorderColor: '#2563eb',
primaryTextColor: '#1e40af',
lineColor: '#94a3b8',
secondaryColor: '#f1f5f9',
tertiaryColor: '#f5f7fa',
edgeLabelBackground: '#f1f5f9',
},
flowchart: {
curve: 'basis',
padding: 12,
htmlLabels: true,
useMaxWidth: true,
},
});
// Expose mermaid globally for lazy rendering
window.mermaid = mermaid;
</script>
</body>
</html>