/* ========================================================================== Flamegraph Viewer - CSS Python-branded profiler with dark/light theme support ========================================================================== */ /* -------------------------------------------------------------------------- CSS Variables & Theme System -------------------------------------------------------------------------- */ :root { /* Typography */ --font-sans: "Source Sans Pro", "Lucida Grande", "Lucida Sans Unicode", "Geneva", "Verdana", sans-serif; --font-mono: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', monospace; /* Python brand colors (theme-independent) */ --python-blue: #3776ab; --python-blue-light: #4584bb; --python-blue-lighter: #5592cc; --python-gold: #ffd43b; --python-gold-dark: #ffcd02; --python-gold-light: #ffdc5c; /* Heat palette - defined per theme below */ /* Layout */ --sidebar-width: 280px; --sidebar-collapsed: 44px; --topbar-height: 52px; --statusbar-height: 32px; /* Transitions */ --transition-fast: 0.15s ease; --transition-normal: 0.25s ease; } /* Light theme (default) - Python yellow-to-blue heat palette */ :root, [data-theme="light"] { --bg-primary: #ffffff; --bg-secondary: #f8f9fa; --bg-tertiary: #e9ecef; --border: #e9ecef; --border-subtle: #f0f2f5; --text-primary: #2e3338; --text-secondary: #5a6c7d; --text-muted: #8b949e; --accent: #3776ab; --accent-hover: #2d5aa0; --accent-glow: rgba(55, 118, 171, 0.15); --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1); --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15); --header-gradient: linear-gradient(135deg, #3776ab 0%, #4584bb 100%); /* Light mode heat palette - blue to yellow to orange to red (cold to hot) */ --heat-1: #d6e9f8; --heat-2: #a8d0ef; --heat-3: #7ba3d1; --heat-4: #ffe6a8; --heat-5: #ffd43b; --heat-6: #ffb84d; --heat-7: #ff9966; --heat-8: #ff6347; } /* Dark theme - teal-to-orange heat palette */ [data-theme="dark"] { --bg-primary: #0d1117; --bg-secondary: #161b22; --bg-tertiary: #21262d; --border: #30363d; --border-subtle: #21262d; --text-primary: #e6edf3; --text-secondary: #8b949e; --text-muted: #6e7681; --accent: #58a6ff; --accent-hover: #79b8ff; --accent-glow: rgba(88, 166, 255, 0.15); --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4); --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5); --header-gradient: linear-gradient(135deg, #21262d 0%, #30363d 100%); /* Dark mode heat palette - dark blue to teal to yellow to orange (cold to hot) */ --heat-1: #1e3a5f; --heat-2: #2d5580; --heat-3: #4a7ba7; --heat-4: #5a9fa8; --heat-5: #7ec488; --heat-6: #c4de6a; --heat-7: #f4d44d; --heat-8: #ff6b35; } /* -------------------------------------------------------------------------- Base Styles -------------------------------------------------------------------------- */ *, *::before, *::after { box-sizing: border-box; } html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; } body { font-family: var(--font-sans); font-size: 14px; line-height: 1.6; color: var(--text-primary); background: var(--bg-primary); transition: background var(--transition-normal), color var(--transition-normal); } /* -------------------------------------------------------------------------- Layout Structure -------------------------------------------------------------------------- */ .app-layout { display: flex; flex-direction: column; height: 100vh; } .main-content { display: flex; flex: 1; min-height: 0; } /* -------------------------------------------------------------------------- Top Bar -------------------------------------------------------------------------- */ .top-bar { height: 56px; background: var(--header-gradient); display: flex; align-items: center; padding: 0 16px; gap: 16px; flex-shrink: 0; box-shadow: 0 2px 10px rgba(55, 118, 171, 0.25); border-bottom: 2px solid var(--python-gold); } /* Brand / Logo */ .brand { display: flex; align-items: center; gap: 12px; color: white; text-decoration: none; flex-shrink: 0; } .brand-logo { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; } /* Style the inlined SVG/img inside brand-logo */ .brand-logo svg, .brand-logo img { width: 28px; height: 28px; display: block; object-fit: contain; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); } .brand-info { display: flex; flex-direction: column; line-height: 1.15; } .brand-text { font-weight: 700; font-size: 16px; letter-spacing: -0.3px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); } .brand-subtitle { font-weight: 500; font-size: 10px; opacity: 0.9; text-transform: uppercase; letter-spacing: 0.5px; } .brand-divider { width: 1px; height: 16px; background: rgba(255, 255, 255, 0.3); } /* Search */ .search-wrapper { flex: 1; max-width: 360px; position: relative; } .search-input { width: 100%; padding: 8px 36px 8px 14px; font-family: var(--font-sans); font-size: 13px; color: #2e3338; background: rgba(255, 255, 255, 0.95); border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 20px; outline: none; transition: all var(--transition-fast); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .search-input::placeholder { color: #6c757d; } .search-input:focus { border-color: rgba(255, 255, 255, 0.8); background: white; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } /* Dark theme search input */ [data-theme="dark"] .search-input { color: #e6edf3; background: rgba(33, 38, 45, 0.95); border: 2px solid rgba(88, 166, 255, 0.3); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } [data-theme="dark"] .search-input::placeholder { color: #8b949e; } [data-theme="dark"] .search-input:focus { border-color: rgba(88, 166, 255, 0.6); background: rgba(33, 38, 45, 1); box-shadow: 0 4px 16px rgba(88, 166, 255, 0.2); } .search-input.has-matches { border-color: rgba(40, 167, 69, 0.8); box-shadow: 0 4px 16px rgba(40, 167, 69, 0.2); } .search-input.no-matches { border-color: rgba(220, 53, 69, 0.8); box-shadow: 0 4px 16px rgba(220, 53, 69, 0.2); } .search-clear { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; padding: 0; display: none; align-items: center; justify-content: center; font-size: 14px; line-height: 1; color: #6c757d; background: transparent; border: none; border-radius: 50%; cursor: pointer; transition: color var(--transition-fast); } .search-clear:hover { color: #2e3338; } [data-theme="dark"] .search-clear { color: #8b949e; } [data-theme="dark"] .search-clear:hover { color: #e6edf3; } .search-wrapper.has-value .search-clear { display: flex; } /* Toolbar */ .toolbar { display: flex; align-items: center; gap: 6px; margin-left: auto; } .toolbar-btn { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; font-size: 15px; color: white; background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 6px; cursor: pointer; transition: all var(--transition-fast); } .toolbar-btn:hover { background: rgba(255, 255, 255, 0.22); border-color: rgba(255, 255, 255, 0.35); } .toolbar-btn:active { transform: scale(0.95); } /* -------------------------------------------------------------------------- Sidebar -------------------------------------------------------------------------- */ .sidebar { width: var(--sidebar-width); background: var(--bg-secondary); border-right: 1px solid var(--border); display: flex; flex-direction: column; flex-shrink: 0; overflow: hidden; position: relative; } .sidebar.collapsed { width: var(--sidebar-collapsed) !important; transition: width var(--transition-normal); } .sidebar-toggle { position: absolute; top: 12px; right: 10px; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: var(--text-muted); background: var(--bg-primary); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; transition: all var(--transition-fast); z-index: 10; } .sidebar-toggle svg { transition: transform var(--transition-fast); } .sidebar-toggle:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-glow); } .sidebar.collapsed .sidebar-toggle { right: 9px; } .sidebar.collapsed .sidebar-toggle svg { transform: rotate(180deg); } .sidebar-content { flex: 1; overflow-y: auto; padding: 44px 14px 14px; } .sidebar.collapsed .sidebar-content { display: none; } .sidebar-resize-handle { position: absolute; top: 0; right: 0; width: 6px; height: 100%; cursor: col-resize; background: transparent; transition: background var(--transition-fast); z-index: 11; } .sidebar-resize-handle:hover, .sidebar-resize-handle.resizing { background: var(--python-gold); } .sidebar-resize-handle::before { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 2px; height: 40px; background: var(--border); border-radius: 1px; opacity: 0; transition: opacity var(--transition-fast); } .sidebar-resize-handle:hover::before { opacity: 1; } .sidebar.collapsed .sidebar-resize-handle { display: none; } body.resizing-sidebar { cursor: col-resize; user-select: none; } /* Sidebar Logo */ .sidebar-logo { display: flex; justify-content: center; margin-bottom: 16px; } .sidebar-logo-img { width: 90px; height: 90px; display: flex; align-items: center; justify-content: center; } .sidebar-logo-img svg, .sidebar-logo-img img { width: 100%; height: 100%; object-fit: contain; } /* Sidebar sections */ .sidebar-section { margin-bottom: 20px; } .sidebar-section:last-child { margin-bottom: 0; } .section-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: var(--accent); margin: 0; flex: 1; } /* Collapsible sections */ .collapsible .section-header { display: flex; align-items: center; width: 100%; padding: 0 0 8px 0; margin-bottom: 10px; background: none; border: none; border-bottom: 2px solid var(--python-gold); cursor: pointer; transition: all var(--transition-fast); } .collapsible .section-header:hover { opacity: 0.8; } .section-chevron { color: var(--text-muted); transition: transform var(--transition-fast); } .collapsible.collapsed .section-chevron { transform: rotate(-90deg); } .section-content { overflow: hidden; transition: max-height var(--transition-normal), opacity var(--transition-normal); max-height: 1000px; opacity: 1; } .collapsible.collapsed .section-content { max-height: 0; opacity: 0; margin-bottom: -10px; } /* -------------------------------------------------------------------------- Profile Summary Cards -------------------------------------------------------------------------- */ .summary-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; } .summary-card { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--bg-primary); border: 1px solid var(--border); border-radius: 8px; transition: all var(--transition-fast); animation: slideUp 0.4s ease-out backwards; animation-delay: calc(var(--i, 0) * 0.05s); overflow: hidden; } .summary-card:nth-child(1) { --i: 0; } .summary-card:nth-child(2) { --i: 1; } .summary-card:nth-child(3) { --i: 2; } .summary-card:nth-child(4) { --i: 3; } .summary-card:hover { border-color: var(--accent); background: var(--accent-glow); } .summary-icon { font-size: 16px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; background: var(--bg-tertiary); border-radius: 6px; flex-shrink: 0; } .summary-data { min-width: 0; flex: 1; overflow: hidden; } .summary-value { font-family: var(--font-mono); font-size: 13px; font-weight: 700; color: var(--accent); line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .summary-label { font-size: 8px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Efficiency Bar */ .efficiency-section { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); } .efficiency-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } .efficiency-label { font-size: 9px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.2px; } .efficiency-value { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--accent); } .efficiency-bar { height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden; } .efficiency-fill { height: 100%; background: linear-gradient(90deg, #28a745 0%, #20c997 50%, #17a2b8 100%); border-radius: 3px; transition: width 0.6s ease-out; position: relative; overflow: hidden; } .efficiency-fill::after { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient( 90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100% ); animation: shimmer 2s ease-in-out infinite; } @keyframes shimmer { 0% { left: -100%; } 100% { left: 100%; } } /* -------------------------------------------------------------------------- Thread Stats Grid (in Sidebar) -------------------------------------------------------------------------- */ .thread-stats-section { display: block; } .stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } .stat-tile { background: var(--bg-primary); border-radius: 8px; padding: 10px; text-align: center; border: 2px solid var(--border); transition: all var(--transition-fast); animation: fadeIn 0.4s ease-out backwards; animation-delay: calc(var(--i, 0) * 0.05s); } .stat-tile:nth-child(1) { --i: 0; } .stat-tile:nth-child(2) { --i: 1; } .stat-tile:nth-child(3) { --i: 2; } .stat-tile:nth-child(4) { --i: 3; } .stat-tile:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); } .stat-tile-value { font-family: var(--font-mono); font-size: 16px; font-weight: 700; color: var(--text-primary); line-height: 1.2; } .stat-tile-label { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; color: var(--text-muted); margin-top: 2px; } /* Stat tile color variants */ .stat-tile--green { --tile-color: 40, 167, 69; --tile-text: #28a745; } .stat-tile--red { --tile-color: 220, 53, 69; --tile-text: #dc3545; } .stat-tile--yellow { --tile-color: 255, 193, 7; --tile-text: #d39e00; } .stat-tile--purple { --tile-color: 111, 66, 193; --tile-text: #6f42c1; } .stat-tile[class*="--"] { border-color: rgba(var(--tile-color), 0.4); background: linear-gradient(135deg, rgba(var(--tile-color), 0.08) 0%, var(--bg-primary) 100%); } .stat-tile[class*="--"] .stat-tile-value { color: var(--tile-text); } /* -------------------------------------------------------------------------- Hotspot Cards -------------------------------------------------------------------------- */ .hotspot { display: flex; align-items: flex-start; gap: 10px; padding: 10px; margin-bottom: 8px; background: var(--bg-primary); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; transition: all var(--transition-fast); opacity: 0; transform: translateY(8px); box-shadow: var(--shadow-sm); } .hotspot.visible { opacity: 1; transform: translateY(0); } .hotspot:hover { border-color: var(--accent); box-shadow: var(--shadow-md); transform: translateY(-2px); } .hotspot.active { border-color: var(--python-gold); background: var(--accent-glow); box-shadow: 0 0 0 3px var(--accent-glow); } .hotspot:last-child { margin-bottom: 0; } .hotspot-rank { width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; flex-shrink: 0; background: linear-gradient(135deg, var(--python-blue) 0%, var(--python-blue-light) 100%); color: white; box-shadow: 0 2px 4px rgba(55, 118, 171, 0.3); } .hotspot-rank--1 { background: linear-gradient(135deg, #d4af37, #f4d03f); color: #5a4a00; } .hotspot-rank--2 { background: linear-gradient(135deg, #a8a8a8, #c0c0c0); color: #4a4a4a; } .hotspot-rank--3 { background: linear-gradient(135deg, #cd7f32, #e6a55a); color: #5a3d00; } .hotspot-info { flex: 1; min-width: 0; } .hotspot-func { font-family: var(--font-mono); font-size: 11px; font-weight: 600; color: var(--text-primary); line-height: 1.3; word-break: break-word; margin-bottom: 2px; } .hotspot-file { font-family: var(--font-mono); font-size: 10px; color: var(--text-muted); margin-bottom: 3px; word-break: break-all; } .hotspot-stats { font-family: var(--font-mono); font-size: 10px; color: var(--text-secondary); } .hotspot-percent { color: var(--accent); font-weight: 600; } /* -------------------------------------------------------------------------- Legend -------------------------------------------------------------------------- */ .legend-section { margin-top: auto; padding-top: 12px; } .legend { display: flex; flex-direction: column; gap: 4px; } .legend-item { display: flex; align-items: center; gap: 8px; padding: 5px 8px; background: var(--bg-primary); border-radius: 4px; border: 1px solid var(--border-subtle); font-size: 11px; } .legend-color { width: 20px; height: 10px; border-radius: 2px; flex-shrink: 0; border: 1px solid rgba(0, 0, 0, 0.08); } .legend-label { color: var(--text-primary); font-weight: 500; flex: 1; } .legend-range { font-family: var(--font-mono); font-size: 9px; color: var(--text-muted); } /* -------------------------------------------------------------------------- Thread Filter -------------------------------------------------------------------------- */ .filter-section { padding-top: 12px; border-top: 1px solid var(--border); } .filter-label { display: block; font-size: 10px; font-weight: 600; color: var(--text-muted); margin-bottom: 6px; } .filter-select { width: 100%; padding: 7px 28px 7px 10px; font-family: var(--font-mono); font-size: 11px; color: var(--text-primary); background: var(--bg-primary); border: 2px solid var(--accent); border-radius: 6px; cursor: pointer; outline: none; appearance: none; background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233776ab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right 6px center; background-size: 14px; transition: all var(--transition-fast); } .filter-select:hover { border-color: var(--accent-hover); box-shadow: 0 2px 6px var(--accent-glow); } .filter-select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } /* -------------------------------------------------------------------------- Chart Area -------------------------------------------------------------------------- */ .chart-area { flex: 1; min-width: 0; overflow: hidden; background: var(--bg-primary); position: relative; } #chart { width: 100%; height: 100%; padding: 16px; overflow: auto; } /* D3 Flamegraph overrides */ .d3-flame-graph rect { stroke: rgba(55, 118, 171, 0.3); stroke-width: 1px; cursor: pointer; transition: filter 0.1s ease; } .d3-flame-graph rect:hover { stroke: var(--python-blue); stroke-width: 2px; filter: brightness(1.08); } .d3-flame-graph text { font-family: var(--font-sans); font-size: 12px; font-weight: 500; fill: var(--text-primary); pointer-events: none; } /* Search highlight */ .d3-flame-graph rect.search-match { stroke: #ff6b35 !important; stroke-width: 2px !important; stroke-dasharray: 4 2; } .d3-flame-graph rect.search-dim { opacity: 0.25; } /* -------------------------------------------------------------------------- Status Bar -------------------------------------------------------------------------- */ .status-bar { height: var(--statusbar-height); background: var(--bg-secondary); border-top: 1px solid var(--border); display: flex; align-items: center; padding: 0 16px; gap: 16px; font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary); flex-shrink: 0; } .status-item { display: flex; align-items: center; gap: 5px; } .status-item::before { content: ''; width: 4px; height: 4px; background: var(--python-gold); border-radius: 50%; } .status-item:first-child::before { display: none; } .status-label { color: var(--text-muted); } .status-value { color: var(--text-primary); font-weight: 500; } .status-value.accent { color: var(--accent); font-weight: 600; } /* -------------------------------------------------------------------------- Tooltip -------------------------------------------------------------------------- */ .python-tooltip { position: absolute; z-index: 1000; pointer-events: none; background: var(--bg-primary); border: 1px solid var(--border); border-radius: 8px; padding: 14px; max-width: 480px; box-shadow: var(--shadow-lg); font-family: var(--font-sans); font-size: 13px; color: var(--text-primary); word-wrap: break-word; overflow-wrap: break-word; line-height: 1.5; } .tooltip-header { margin-bottom: 10px; } .tooltip-title { font-size: 14px; font-weight: 600; color: var(--accent); line-height: 1.3; word-break: break-word; margin-bottom: 4px; } .tooltip-location { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); background: var(--bg-tertiary); padding: 4px 8px; border-radius: 4px; word-break: break-all; } .tooltip-stats { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; font-size: 12px; } .tooltip-stat-label { color: var(--text-secondary); font-weight: 500; } .tooltip-stat-value { color: var(--text-primary); font-weight: 600; } .tooltip-stat-value.accent { color: var(--accent); } .tooltip-source { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); } .tooltip-source-title { font-size: 11px; font-weight: 600; color: var(--accent); margin-bottom: 6px; } .tooltip-source-code { font-family: var(--font-mono); font-size: 10px; line-height: 1.5; background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 6px; padding: 8px; max-height: 140px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; } .tooltip-source-line { color: var(--text-secondary); padding: 1px 0; } .tooltip-source-line.current { color: var(--accent); font-weight: 600; } .tooltip-hint { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); font-size: 11px; color: var(--text-muted); text-align: center; } /* -------------------------------------------------------------------------- Animations -------------------------------------------------------------------------- */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } /* -------------------------------------------------------------------------- Focus States (Accessibility) -------------------------------------------------------------------------- */ button:focus-visible, select:focus-visible, input:focus-visible { outline: 2px solid var(--python-gold); outline-offset: 2px; } /* -------------------------------------------------------------------------- Responsive -------------------------------------------------------------------------- */ @media (max-width: 900px) { .sidebar { position: fixed; left: 0; top: var(--topbar-height); bottom: var(--statusbar-height); z-index: 100; box-shadow: var(--shadow-lg); } .sidebar.collapsed { width: var(--sidebar-collapsed); } .brand-subtitle { display: none; } .search-wrapper { max-width: 220px; } } @media (max-width: 600px) { .toolbar-btn:not(.theme-toggle) { display: none; } .search-wrapper { max-width: 160px; } .brand-info { display: none; } .stats-grid { grid-template-columns: 1fr; } }