// Custom DOM-based Scrollbar Styles
//
// This is a manually created scrollbar that doesn't use native webkit scrollbar.
// It uses a layered DOM structure to keep the track fixed while content scrolls.
//
// DOM Structure:
// container (relative, padding: 0)
// └── wrapper (flex, row) ← separation layer
// ├── content_layer (flex:1, column, padding: original) ← scrollable content
// └── track (absolute, right: 0) ← fixed scrollbar track
// └── thumb (absolute) ← draggable thumb
// ============================================================================
// IMPORTANT: Hide native scrollbars IMMEDIATELY on page load
// This prevents the flash of native scrollbar before custom one is set up
// ============================================================================
// Target elements that will get custom scrollbars
.hi-aside-content,
.hi-layout-aside-content,
.hi-layout-content,
.hi-layout-scrollable,
.hi-tree-virtual,
.hi-tabs-nav,
.hi-table-container,
.hi-sidebar,
.sidebar-nav,
.showcase-table-container,
.custom-scrollbar-content-vdom {
// Hide native scrollbar IMMEDIATELY (before JS runs)
// This prevents the flash of native scrollbar on page load
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
-ms-overflow-style: none; // IE/Edge
scrollbar-width: none; // Firefox
}
// ============================================================================
// Custom scrollbar styles (applied after JS wraps elements)
// ============================================================================
// Container with custom scrollbar
.custom-scrollbar-container {
position: relative;
// Hide native scrollbar
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
}
// Wrapper layer (flex container, provides positioning context for track)
.custom-scrollbar-wrapper {
position: relative; // Provide positioning context for absolute track
pointer-events: none; // Let events pass through to children
}
// Content layer (scrollable content)
.custom-scrollbar-content {
pointer-events: auto; // Re-enable events for content
// Hide native scrollbar in content layer
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
}
// Scrollbar track (the rail) - absolutely positioned inside wrapper's right edge
.custom-scrollbar-track {
position: absolute; // Absolute positioning inside wrapper
top: 0;
right: 0; // 贴着容器边缘
bottom: 0;
// width is set dynamically via JavaScript (4px → 8px)
width: 4px; // Initial width
border-radius: 4px;
pointer-events: none; // Don't capture clicks - let them pass through to content
z-index: 100;
transition: width 300ms cubic-bezier(0.25, 0.1, 0.25, 1),
opacity 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
// Gradient background: dark at ends, light in middle
// 16px - auto - (100% - 16px)
background: linear-gradient(
to bottom,
var(--hi-scrollbar-track-dark) 16px,
var(--hi-scrollbar-track-light) calc(50% - 8px),
var(--hi-scrollbar-track-dark) calc(100% - 16px)
);
}
// Hover state: background darkens (width is handled by JavaScript state machine)
.custom-scrollbar-track:hover {
background: linear-gradient(
to bottom,
var(--hi-scrollbar-track-dark) 16px,
var(--hi-scrollbar-track-light) calc(50% - 8px),
var(--hi-scrollbar-track-dark) calc(100% - 16px)
);
// Add slight opacity increase on hover
filter: brightness(1.1);
}
// Scrollbar thumb (the draggable handle)
.custom-scrollbar-thumb {
position: absolute;
right: 0;
width: 100%;
min-height: 20px;
background: var(--hi-scrollbar-thumb);
border-radius: 4px;
cursor: pointer;
pointer-events: auto; // Re-enable clicks for thumb (overrides track's none)
// Disable top transition to prevent drag lag
transition: background 300ms cubic-bezier(0.25, 0.1, 0.25, 1),
box-shadow 300ms cubic-bezier(0.25, 0.1, 0.25, 1),
top 0s linear;
}
// Hover state: thumb glows
.custom-scrollbar-thumb:hover {
background: var(--hi-scrollbar-thumb-hover);
box-shadow: 0 0 14px var(--hi-scrollbar-thumb-hover);
}
// Active state: thumb being dragged (theme-aware filter)
.custom-scrollbar-thumb:active {
background: var(--hi-scrollbar-thumb-hover);
box-shadow: 0 0 16px var(--hi-scrollbar-thumb-hover);
// Apply theme-aware brightness filter
filter: var(--hi-scrollbar-thumb-active-filter);
}
// Hidden state (when content doesn't need scrolling)
.custom-scrollbar-track.custom-scrollbar-hidden {
opacity: 0;
pointer-events: none;
}
// Scroll-hover active state: wide track that auto-retracts after ~1s
// Uses CSS animation so no JS timer is needed (works reliably in WASM)
.custom-scrollbar-track.scrollbar-scrolling {
animation: scrollbar-retract 1.3s cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
// Override the base transition during animation
transition: none;
}
@keyframes scrollbar-retract {
0%,
70% {
width: 8px;
}
100% {
width: 4px;
}
}