diff --git a/gcovr-templates/html/base.html b/gcovr-templates/html/base.html
index fbd411a..cc5fe53 100644
--- a/gcovr-templates/html/base.html
+++ b/gcovr-templates/html/base.html
@@ -9,13 +9,10 @@
+
diff --git a/gcovr-templates/html/functions_page.html b/gcovr-templates/html/functions_page.html
index d133f4c..5bc415b 100644
--- a/gcovr-templates/html/functions_page.html
+++ b/gcovr-templates/html/functions_page.html
@@ -3,7 +3,7 @@
{% block html_title %}Functions - {{info.head}}{% endblock %}
-{% block breadcrumb %}Root / Functions{% endblock %}
+{% block breadcrumb %}Index / Functions{% endblock %}
{% block sidebar_tree %}
diff --git a/gcovr-templates/html/gcovr.js b/gcovr-templates/html/gcovr.js
index 53ac063..f22423f 100644
--- a/gcovr-templates/html/gcovr.js
+++ b/gcovr-templates/html/gcovr.js
@@ -8,6 +8,7 @@
initTheme();
initFontSize();
initSidebar();
+ initSidebarResize();
initMobileMenu();
initFileTree();
initBreadcrumbs();
@@ -21,34 +22,42 @@
// Breadcrumb Links
// ===========================================
+ // Find a node in the tree by its link (HTML filename) and return
+ // the full ancestor path as an array of nodes from root to target.
+ function findPathInTree(nodes, targetLink) {
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ if (node.link === targetLink) {
+ return [node];
+ }
+ if (node.children) {
+ var childPath = findPathInTree(node.children, targetLink);
+ if (childPath) {
+ return [node].concat(childPath);
+ }
+ }
+ }
+ return null;
+ }
+
function initBreadcrumbs() {
var currentSpan = document.querySelector('.breadcrumb .current');
if (!currentSpan || !window.GCOVR_TREE_DATA) return;
- var pathText = currentSpan.textContent.trim();
- var segments = pathText.split(' / ');
- if (segments.length <= 1) return;
-
- // Auto-detect and skip prefix segments not in tree (e.g., "boost", "json")
- // Find the first segment that matches a root node in the tree
- var startIndex = 0;
- var rootNames = window.GCOVR_TREE_DATA.map(function(n) { return n.name; });
- for (var i = 0; i < segments.length; i++) {
- if (rootNames.indexOf(segments[i]) !== -1) {
- startIndex = i;
- break;
- }
- }
- // Skip prefix segments
- segments = segments.slice(startIndex);
- if (segments.length === 0) return;
+ // Find current page in tree by its HTML filename — this is unambiguous
+ // since each page only appears once in the tree.
+ var currentPage = window.location.pathname.split('/').pop() || 'index.html';
+ var treePath = findPathInTree(window.GCOVR_TREE_DATA, currentPage);
- // Create linked breadcrumb by traversing tree
+ if (!treePath || treePath.length === 0) return;
+
+ // Build breadcrumb from the tree path (ancestor nodes → current node)
var fragment = document.createDocumentFragment();
- var currentNodes = window.GCOVR_TREE_DATA;
+ var matchedSegments = [];
- for (var i = 0; i < segments.length; i++) {
- var segment = segments[i];
+ for (var i = 0; i < treePath.length; i++) {
+ var node = treePath[i];
+ var isLast = (i === treePath.length - 1);
if (i > 0) {
var sep = document.createElement('span');
@@ -57,37 +66,29 @@
fragment.appendChild(sep);
}
- // Find this segment in current level of tree
- var foundNode = null;
- for (var j = 0; j < currentNodes.length; j++) {
- if (currentNodes[j].name === segment) {
- foundNode = currentNodes[j];
- break;
- }
- }
+ matchedSegments.push(node.name);
- if (foundNode && foundNode.link && i < segments.length - 1) {
- // Directory segment with link - make it clickable
+ if (node.link && !isLast) {
var a = document.createElement('a');
- a.href = foundNode.link;
- a.textContent = segment;
+ a.href = node.link;
+ a.textContent = node.name;
fragment.appendChild(a);
- // Move to children for next iteration
- currentNodes = foundNode.children || [];
} else {
- // Last segment (current file) or no link found
var span = document.createElement('span');
span.className = 'current-file';
- span.textContent = segment;
+ span.textContent = node.name;
fragment.appendChild(span);
- if (foundNode && foundNode.children) {
- currentNodes = foundNode.children;
- }
}
}
currentSpan.innerHTML = '';
currentSpan.appendChild(fragment);
+
+ // Update source-filename to match breadcrumb path
+ var sourceFilename = document.querySelector('.source-filename');
+ if (sourceFilename) {
+ sourceFilename.textContent = matchedSegments.join('/');
+ }
}
// ===========================================
@@ -95,68 +96,52 @@
// ===========================================
function initTheme() {
- const selector = document.getElementById('theme-selector');
const toggle = document.getElementById('theme-toggle');
- const menu = document.getElementById('theme-menu');
const label = toggle ? toggle.querySelector('.theme-label') : null;
- const options = menu ? menu.querySelectorAll('.theme-option') : [];
- const savedTheme = localStorage.getItem('gcovr-theme') || 'system';
+ const iconSun = toggle ? toggle.querySelector('.icon-sun') : null;
+ const iconMoon = toggle ? toggle.querySelector('.icon-moon') : null;
// Get system preference
function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
}
+ // Get effective theme: saved preference or OS default
+ function getEffectiveTheme() {
+ var saved = localStorage.getItem('gcovr-theme');
+ return (saved === 'light' || saved === 'dark') ? saved : getSystemTheme();
+ }
+
// Apply theme to document
function applyTheme(theme) {
- const effectiveTheme = theme === 'system' ? getSystemTheme() : theme;
- document.documentElement.setAttribute('data-theme', effectiveTheme);
- document.documentElement.setAttribute('data-theme-setting', theme);
+ document.documentElement.setAttribute('data-theme', theme);
if (label) {
label.textContent = theme.charAt(0).toUpperCase() + theme.slice(1);
}
- // Update active state in menu
- options.forEach(function(opt) {
- opt.classList.toggle('active', opt.getAttribute('data-theme') === theme);
- });
+ if (iconSun) iconSun.style.display = (theme === 'light') ? 'block' : 'none';
+ if (iconMoon) iconMoon.style.display = (theme === 'dark') ? 'block' : 'none';
}
- // Apply saved theme
- applyTheme(savedTheme);
+ // Apply current theme
+ applyTheme(getEffectiveTheme());
- // Listen for system theme changes
+ // Listen for system theme changes — only apply if no stored preference
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function() {
- const current = localStorage.getItem('gcovr-theme') || 'system';
- if (current === 'system') {
- applyTheme('system');
+ var saved = localStorage.getItem('gcovr-theme');
+ if (saved !== 'light' && saved !== 'dark') {
+ applyTheme(getSystemTheme());
}
});
- // Toggle menu
- if (toggle && selector) {
- toggle.addEventListener('click', function(e) {
- e.stopPropagation();
- selector.classList.toggle('open');
+ // Toggle between light and dark on click
+ if (toggle) {
+ toggle.addEventListener('click', function() {
+ var current = getEffectiveTheme();
+ var next = (current === 'dark') ? 'light' : 'dark';
+ localStorage.setItem('gcovr-theme', next);
+ applyTheme(next);
});
}
-
- // Handle option selection
- options.forEach(function(option) {
- option.addEventListener('click', function(e) {
- e.stopPropagation();
- const theme = option.getAttribute('data-theme');
- localStorage.setItem('gcovr-theme', theme);
- applyTheme(theme);
- selector.classList.remove('open');
- });
- });
-
- // Close menu when clicking outside
- document.addEventListener('click', function(e) {
- if (selector && !selector.contains(e.target)) {
- selector.classList.remove('open');
- }
- });
}
// ===========================================
@@ -271,6 +256,7 @@
if (toggle) toggle.textContent = '−';
}
});
+ saveExpandedFolders();
});
}
@@ -281,6 +267,7 @@
var toggle = item.querySelector(':scope > .tree-item-header > .tree-folder-toggle');
if (toggle) toggle.textContent = '+';
});
+ saveExpandedFolders();
});
}
}
@@ -307,7 +294,15 @@
toggle.addEventListener('click', function() {
sidebar.classList.toggle('collapsed');
sidebar.classList.remove('hover-expand');
- localStorage.setItem('sidebar-collapsed', sidebar.classList.contains('collapsed'));
+ var isNowCollapsed = sidebar.classList.contains('collapsed');
+ localStorage.setItem('sidebar-collapsed', isNowCollapsed);
+ // Restore custom width when un-collapsing
+ if (!isNowCollapsed) {
+ var savedWidth = localStorage.getItem('gcovr-sidebar-width');
+ if (savedWidth) {
+ document.documentElement.style.setProperty('--sidebar-width', savedWidth + 'px');
+ }
+ }
});
}
@@ -371,6 +366,67 @@
});
}
+ // ===========================================
+ // Sidebar Resize
+ // ===========================================
+
+ function initSidebarResize() {
+ var sidebar = document.getElementById('sidebar');
+ var handle = document.getElementById('sidebar-resize-handle');
+ if (!sidebar || !handle) return;
+
+ var MIN_WIDTH = 200;
+ var startX, startWidth;
+
+ // Restore saved width
+ var savedWidth = localStorage.getItem('gcovr-sidebar-width');
+ if (savedWidth && !sidebar.classList.contains('collapsed')) {
+ var w = parseInt(savedWidth, 10);
+ if (w >= MIN_WIDTH) {
+ document.documentElement.style.setProperty('--sidebar-width', w + 'px');
+ }
+ }
+
+ function getMaxWidth() {
+ return Math.floor(window.innerWidth * 0.5);
+ }
+
+ function onMouseMove(e) {
+ var newWidth = startWidth + (e.clientX - startX);
+ var maxW = getMaxWidth();
+ if (newWidth < MIN_WIDTH) newWidth = MIN_WIDTH;
+ if (newWidth > maxW) newWidth = maxW;
+ document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');
+ }
+
+ function onMouseUp() {
+ document.body.classList.remove('sidebar-resizing');
+ document.removeEventListener('mousemove', onMouseMove);
+ document.removeEventListener('mouseup', onMouseUp);
+ // Save the current width
+ var computed = parseInt(getComputedStyle(sidebar).width, 10);
+ localStorage.setItem('gcovr-sidebar-width', computed);
+ }
+
+ handle.addEventListener('mousedown', function(e) {
+ if (sidebar.classList.contains('collapsed')) return;
+ e.preventDefault();
+ startX = e.clientX;
+ startWidth = parseInt(getComputedStyle(sidebar).width, 10);
+ document.body.classList.add('sidebar-resizing');
+ document.addEventListener('mousemove', onMouseMove);
+ document.addEventListener('mouseup', onMouseUp);
+ });
+
+ // Double-click to reset to default width
+ var DEFAULT_WIDTH = 320;
+ handle.addEventListener('dblclick', function() {
+ if (sidebar.classList.contains('collapsed')) return;
+ document.documentElement.style.setProperty('--sidebar-width', DEFAULT_WIDTH + 'px');
+ localStorage.setItem('gcovr-sidebar-width', DEFAULT_WIDTH);
+ });
+ }
+
// ===========================================
// Mobile Menu
// ===========================================
@@ -419,6 +475,10 @@
// Check for embedded tree data first (works for local file:// access)
if (window.GCOVR_TREE_DATA) {
+ window.GCOVR_TREE_DATA = normalizeTree(window.GCOVR_TREE_DATA);
+ deduplicateTree(window.GCOVR_TREE_DATA);
+ collapseSingleChildDirs(window.GCOVR_TREE_DATA);
+ deduplicateTree(window.GCOVR_TREE_DATA);
renderTree(treeContainer, window.GCOVR_TREE_DATA);
return;
}
@@ -430,7 +490,11 @@
return response.json();
})
.then(function(tree) {
- renderTree(treeContainer, tree);
+ window.GCOVR_TREE_DATA = normalizeTree(tree);
+ deduplicateTree(window.GCOVR_TREE_DATA);
+ collapseSingleChildDirs(window.GCOVR_TREE_DATA);
+ deduplicateTree(window.GCOVR_TREE_DATA);
+ renderTree(treeContainer, window.GCOVR_TREE_DATA);
})
.catch(function(err) {
console.log('tree.json not found, using static sidebar');
@@ -438,6 +502,126 @@
});
}
+ // Collapse single-child directory chains: if a directory has exactly
+ // one child and that child is also a directory, absorb the grandchildren.
+ // e.g. include > boost > capy > [items] becomes include > [items]
+ function collapseSingleChildDirs(nodes) {
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ if (!node.isDirectory || !node.children) continue;
+ while (node.children.length === 1 && node.children[0].isDirectory &&
+ node.children[0].children && node.children[0].children.length > 0) {
+ var child = node.children[0];
+ if (!node.link && child.link) node.link = child.link;
+ node.children = child.children;
+ }
+ collapseSingleChildDirs(node.children);
+ }
+ }
+
+ // Deduplicate tree: when a node has a child with the same name
+ // (e.g. include > include), merge the child's children upward.
+ // This happens when gcovr directory pages list entries with paths
+ // that include the parent directory name.
+ function deduplicateTree(nodes) {
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ if (!node.children || node.children.length === 0) continue;
+ for (var j = node.children.length - 1; j >= 0; j--) {
+ var child = node.children[j];
+ if (child.name === node.name && child.isDirectory) {
+ node.children.splice(j, 1);
+ if (!node.link && child.link) node.link = child.link;
+ if (!node.coverage && child.coverage) node.coverage = child.coverage;
+ if (!node.coverageClass && child.coverageClass) node.coverageClass = child.coverageClass;
+ if (child.children) {
+ for (var k = 0; k < child.children.length; k++) {
+ node.children.push(child.children[k]);
+ }
+ }
+ }
+ }
+ deduplicateTree(node.children);
+ }
+ }
+
+ // Normalize tree: expand multi-segment node names (e.g. "capy/buffers")
+ // into proper nested directory structures so the tree and breadcrumbs
+ // display correctly.
+ function normalizeTree(nodes) {
+ if (!nodes || nodes.length === 0) return nodes;
+
+ var groups = {};
+ var order = [];
+
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ var slashIdx = node.name.indexOf('/');
+
+ if (slashIdx === -1) {
+ // Simple name — add directly or merge with existing group
+ if (groups[node.name]) {
+ var existing = groups[node.name];
+ if (node.link) existing.link = node.link;
+ if (node.coverage) existing.coverage = node.coverage;
+ if (node.coverageClass) existing.coverageClass = node.coverageClass;
+ if (node.children && node.children.length > 0) {
+ existing.children = (existing.children || []).concat(node.children);
+ }
+ } else {
+ var copy = {};
+ for (var key in node) {
+ if (node.hasOwnProperty(key)) copy[key] = node[key];
+ }
+ groups[node.name] = copy;
+ order.push(node.name);
+ }
+ } else {
+ // Multi-segment name — split on first '/' and group
+ var prefix = node.name.substring(0, slashIdx);
+ var rest = node.name.substring(slashIdx + 1);
+
+ if (!groups[prefix]) {
+ groups[prefix] = {
+ name: prefix,
+ isDirectory: true,
+ children: []
+ };
+ order.push(prefix);
+ }
+ if (!groups[prefix].children) groups[prefix].children = [];
+
+ // Create child node with remaining path as name
+ var childNode = {};
+ for (var key in node) {
+ if (node.hasOwnProperty(key)) childNode[key] = node[key];
+ }
+ childNode.name = rest;
+ groups[prefix].children.push(childNode);
+ }
+ }
+
+ // Build result with recursive normalization
+ var result = [];
+ for (var i = 0; i < order.length; i++) {
+ var node = groups[order[i]];
+ if (node.children && node.children.length > 0) {
+ node.children = normalizeTree(node.children);
+ }
+ result.push(node);
+ }
+ return result;
+ }
+
+ // Save expanded folder paths to localStorage
+ function saveExpandedFolders() {
+ var paths = [];
+ document.querySelectorAll('.tree-item.expanded[data-tree-path]').forEach(function(el) {
+ paths.push(el.getAttribute('data-tree-path'));
+ });
+ localStorage.setItem('gcovr-expanded-folders', JSON.stringify(paths));
+ }
+
function renderTree(container, tree) {
container.innerHTML = '';
@@ -447,7 +631,7 @@
}
tree.forEach(function(item) {
- container.appendChild(createTreeItem(item));
+ container.appendChild(createTreeItem(item, ''));
});
// Auto-expand to current file and highlight it
@@ -460,29 +644,48 @@
// Find the link matching current page
var currentLink = container.querySelector('a[href="' + currentPage + '"]');
- if (!currentLink) return;
-
- // Mark as active
- var treeItem = currentLink.closest('.tree-item');
- if (treeItem) {
- treeItem.classList.add('active');
- }
-
- // Expand all parent folders
- var parent = currentLink.closest('.tree-children');
- while (parent) {
- var parentItem = parent.closest('.tree-item');
- if (parentItem) {
- parentItem.classList.add('expanded');
- var toggle = parentItem.querySelector(':scope > .tree-item-header > .tree-folder-toggle');
- if (toggle) toggle.textContent = '−';
+
+ if (currentLink) {
+ // Mark as active
+ var treeItem = currentLink.closest('.tree-item');
+ if (treeItem) {
+ treeItem.classList.add('active');
+ }
+
+ // Expand all parent folders
+ var parent = currentLink.closest('.tree-children');
+ while (parent) {
+ var parentItem = parent.closest('.tree-item');
+ if (parentItem) {
+ parentItem.classList.add('expanded');
+ var toggle = parentItem.querySelector(':scope > .tree-item-header > .tree-folder-toggle');
+ if (toggle) toggle.textContent = '−';
+ }
+ parent = parentItem ? parentItem.parentElement.closest('.tree-children') : null;
+ }
+ }
+
+ // Restore previously expanded folders from localStorage
+ try {
+ var saved = localStorage.getItem('gcovr-expanded-folders');
+ if (saved) {
+ var paths = JSON.parse(saved);
+ paths.forEach(function(path) {
+ var el = container.querySelector('.tree-item[data-tree-path="' + CSS.escape(path) + '"]');
+ if (el && !el.classList.contains('no-children')) {
+ el.classList.add('expanded');
+ var toggle = el.querySelector(':scope > .tree-item-header > .tree-folder-toggle');
+ if (toggle) toggle.textContent = '−';
+ }
+ });
}
- parent = parentItem ? parentItem.parentElement.closest('.tree-children') : null;
+ } catch (e) {
+ // Ignore localStorage errors
}
- // Scroll into view
+ // Scroll active item into view instantly
if (currentLink) {
- currentLink.scrollIntoView({ block: 'center', behavior: 'smooth' });
+ currentLink.scrollIntoView({ block: 'center', behavior: 'instant' });
}
}
@@ -507,12 +710,15 @@
return lastSlash >= 0 ? cleaned.substring(lastSlash + 1) : cleaned;
}
- function createTreeItem(item) {
+ function createTreeItem(item, parentPath) {
var hasChildren = item.children && item.children.length > 0;
var isDirectory = item.isDirectory || hasChildren;
+ var cleanedName = cleanPathName(item.name);
+ var treePath = parentPath ? (parentPath + '/' + cleanedName) : cleanedName;
var div = document.createElement('div');
div.className = 'tree-item' + (isDirectory ? ' is-folder' : '') + (hasChildren ? '' : ' no-children');
+ div.setAttribute('data-tree-path', treePath);
var header = document.createElement('div');
header.className = 'tree-item-header';
@@ -529,6 +735,7 @@
e.preventDefault();
var isExpanded = div.classList.toggle('expanded');
toggle.textContent = isExpanded ? '−' : '+';
+ saveExpandedFolders();
});
header.appendChild(toggle);
@@ -540,6 +747,7 @@
e.preventDefault();
var isExpanded = div.classList.toggle('expanded');
toggle.textContent = isExpanded ? '−' : '+';
+ saveExpandedFolders();
});
} else {
var spacer = document.createElement('span');
@@ -590,7 +798,7 @@
var childrenInner = document.createElement('div');
childrenInner.className = 'tree-children-inner';
item.children.forEach(function(child) {
- childrenInner.appendChild(createTreeItem(child));
+ childrenInner.appendChild(createTreeItem(child, treePath));
});
childrenWrapper.appendChild(childrenInner);
diff --git a/gcovr-templates/html/source_page.html b/gcovr-templates/html/source_page.html
index f0e25f7..58861e7 100644
--- a/gcovr-templates/html/source_page.html
+++ b/gcovr-templates/html/source_page.html
@@ -3,7 +3,7 @@
{% block html_title %}{{filename}} - {{info.head}}{% endblock %}
-{% block breadcrumb %}
Root/{{filename | replace("/", " / ")}}{% endblock %}
+{% block breadcrumb %}
Index/{{filename | replace("/", " / ")}}{% endblock %}
{% block sidebar_tree %}
{# Show current file in sidebar #}
diff --git a/gcovr-templates/html/style.css b/gcovr-templates/html/style.css
index 4073163..cd70c58 100644
--- a/gcovr-templates/html/style.css
+++ b/gcovr-templates/html/style.css
@@ -244,6 +244,37 @@ a:hover {
width: var(--sidebar-width);
}
+/* Sidebar Resize Handle */
+.sidebar-resize-handle {
+ position: absolute;
+ top: 0;
+ right: -3px;
+ width: 6px;
+ height: 100%;
+ cursor: col-resize;
+ z-index: 101;
+ background: transparent;
+ transition: background var(--transition-fast);
+}
+
+.sidebar-resize-handle:hover {
+ background: var(--accent-brand);
+}
+
+.sidebar.collapsed .sidebar-resize-handle {
+ display: none;
+}
+
+body.sidebar-resizing {
+ user-select: none;
+ cursor: col-resize;
+}
+
+body.sidebar-resizing .sidebar,
+body.sidebar-resizing .main-content {
+ transition: none;
+}
+
/* Sidebar Header */
.sidebar-header {
display: flex;
@@ -278,7 +309,7 @@ a:hover {
}
.sidebar-logo .boost-wordmark {
- height: 18px;
+ height: 32px;
width: auto;
flex-shrink: 0;
transition: transform var(--transition-fast);
@@ -299,7 +330,7 @@ a:hover {
}
.sidebar-library {
- font-size: var(--font-size-lg);
+ font-size: var(--font-size-xl);
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
@@ -307,7 +338,7 @@ a:hover {
}
.sidebar-subtitle {
- font-size: var(--font-size-xs);
+ font-size: var(--font-size-sm);
font-weight: 400;
color: var(--text-muted);
flex-shrink: 0;
@@ -442,21 +473,6 @@ a:hover {
width: 100%;
}
-/* Position theme menu to open to the right when collapsed */
-.sidebar.collapsed .theme-menu {
- bottom: 0;
- left: 100%;
- right: auto;
- top: auto;
- transform: translateX(8px) translateY(0);
- margin-bottom: 0;
- min-width: 120px;
-}
-
-.sidebar.collapsed .theme-selector.open .theme-menu {
- transform: translateX(8px) translateY(0);
-}
-
.sidebar.collapsed .theme-toggle {
width: 36px;
height: 36px;
@@ -512,17 +528,10 @@ a:hover {
flex-shrink: 0;
}
-/* Theme icons - show based on theme setting, not effective theme */
-.theme-toggle .icon-system { display: block; }
+/* Theme icons - visibility managed by JS */
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: none; }
-[data-theme-setting="light"] .theme-toggle .icon-system { display: none; }
-[data-theme-setting="light"] .theme-toggle .icon-sun { display: block; }
-
-[data-theme-setting="dark"] .theme-toggle .icon-system { display: none; }
-[data-theme-setting="dark"] .theme-toggle .icon-moon { display: block; }
-
/* ===========================================
Font Size Control
=========================================== */
@@ -768,78 +777,6 @@ a:hover {
}
/* Theme Selector Dropdown */
-.theme-selector {
- position: relative;
-}
-
-.theme-menu {
- position: absolute;
- bottom: 100%;
- left: 0;
- right: 0;
- margin-bottom: 8px;
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: var(--radius-md);
- padding: 4px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
- opacity: 0;
- visibility: hidden;
- transform: translateY(4px);
- transition: opacity var(--transition-fast), transform var(--transition-fast), visibility var(--transition-fast);
- z-index: 200;
-}
-
-.theme-selector.open .theme-menu {
- opacity: 1;
- visibility: visible;
- transform: translateY(0);
-}
-
-.theme-option {
- display: flex;
- align-items: center;
- gap: 8px;
- width: 100%;
- padding: 8px 10px;
- background: transparent;
- border: none;
- border-radius: var(--radius-sm);
- color: var(--text-primary);
- font-size: var(--font-size-sm);
- cursor: pointer;
- transition: background var(--transition-fast);
-}
-
-.theme-option:hover {
- background: var(--bg-hover);
-}
-
-.theme-option svg {
- width: 16px;
- height: 16px;
- flex-shrink: 0;
- color: var(--text-secondary);
-}
-
-.theme-option:hover svg {
- color: var(--text-primary);
-}
-
-.theme-option span {
- flex: 1;
- text-align: left;
-}
-
-.theme-option .check {
- opacity: 0;
- color: var(--accent-brand);
-}
-
-.theme-option.active .check {
- opacity: 1;
-}
-
.theme-label {
font-size: var(--font-size-xs);
white-space: nowrap;
@@ -1079,7 +1016,7 @@ a:hover {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- font-size: var(--font-size-sm);
+ font-size: var(--font-size-md);
color: var(--text-primary);
}
@@ -1212,7 +1149,7 @@ a:hover {
.content-wrapper {
flex: 1;
padding: 24px;
- max-width: 1400px;
+ max-width: 1600px;
width: 100%;
margin: 0 auto;
}
@@ -1693,8 +1630,8 @@ a:hover {
}
.source-table td {
- padding: 0 12px;
- vertical-align: top;
+ padding: 4px 12px;
+ vertical-align: middle;
border-bottom: 1px solid var(--border-muted);
}
@@ -1703,14 +1640,16 @@ a:hover {
border-right: 1px solid var(--border-color);
text-align: right;
user-select: none;
- width: 60px;
+ white-space: nowrap;
+ width: 1%;
+ padding: 4px 6px;
}
.source-table .col-lineno a {
color: var(--text-muted);
text-decoration: none;
display: block;
- padding: 2px 0;
+ padding: 0;
}
.source-table .col-lineno a:hover {
@@ -1724,13 +1663,15 @@ a:hover {
background: var(--bg-tertiary);
border-right: 1px solid var(--border-color);
text-align: center;
- width: 60px;
+ width: 50px;
}
.source-table .col-count {
text-align: right;
- width: 60px;
+ white-space: nowrap;
+ width: 1%;
color: var(--text-muted);
+ padding: 4px 6px;
}
.source-table .col-source {
@@ -2026,11 +1967,15 @@ a:hover {
display: flex;
}
- /* Hide sidebar toggle (pin) button */
+ /* Hide sidebar toggle (pin) button and resize handle */
.sidebar-toggle {
display: none;
}
+ .sidebar-resize-handle {
+ display: none;
+ }
+
/* Show backdrop when sidebar open */
.sidebar.mobile-open ~ .sidebar-backdrop {
opacity: 1;