From cd111dfb54efb0b692f39ff1edccff7d2a245825 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 16 Dec 2025 00:17:12 +0000 Subject: [PATCH 1/2] Improve Aliki visuals --- .../generator/template/aliki/css/rdoc.css | 298 +++++++++++++++++- lib/rdoc/generator/template/aliki/js/aliki.js | 6 +- 2 files changed, 290 insertions(+), 14 deletions(-) diff --git a/lib/rdoc/generator/template/aliki/css/rdoc.css b/lib/rdoc/generator/template/aliki/css/rdoc.css index 7d129d07c3..8db3d76fce 100644 --- a/lib/rdoc/generator/template/aliki/css/rdoc.css +++ b/lib/rdoc/generator/template/aliki/css/rdoc.css @@ -142,6 +142,7 @@ /* Shadows */ --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 10%), 0 1px 2px -1px rgb(0 0 0 / 10%); + --shadow-md: 0 2px 8px rgb(0 0 0 / 10%); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 10%), 0 4px 6px -4px rgb(0 0 0 / 10%); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 10%), 0 8px 10px -6px rgb(0 0 0 / 10%); @@ -158,6 +159,18 @@ /* Transitions */ --transition-fast: 150ms ease-in-out; --transition-base: 200ms ease-in-out; + --transition-slow: 350ms ease-in-out; + --ease-out-smooth: cubic-bezier(0.4, 0, 0.2, 1); + + /* Animation Durations */ + --duration-fast: 250ms; + --duration-base: 300ms; + --duration-medium: 350ms; + --duration-slow: 400ms; + + /* Animation Colors */ + --color-success-pulse: rgb(34 197 94 / 40%); + --color-success-pulse-fade: rgb(34 197 94 / 0%); /* Z-Index Scale */ --z-fixed: 300; @@ -202,6 +215,7 @@ --color-accent-primary: var(--color-primary-500); --color-accent-hover: var(--color-primary-400); --color-accent-subtle: rgb(235 84 79 / 10%); + --color-accent-subtle-hover: rgb(235 84 79 / 20%); --color-code-bg: var(--color-neutral-800); --color-code-border: var(--color-neutral-700); --color-nav-bg: var(--color-neutral-900); @@ -211,6 +225,7 @@ /* Dark theme shadows (slightly more subtle) */ --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 40%), 0 1px 2px -1px rgb(0 0 0 / 40%); + --shadow-md: 0 2px 8px rgb(0 0 0 / 40%); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 40%), 0 4px 6px -4px rgb(0 0 0 / 40%); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 40%), 0 8px 10px -6px rgb(0 0 0 / 40%); @@ -291,7 +306,12 @@ pre { border-radius: var(--radius-sm); cursor: pointer; opacity: 0.6; - transition: opacity var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast); + transition: + opacity var(--transition-fast), + background var(--transition-fast), + border-color var(--transition-fast), + transform var(--transition-fast), + box-shadow var(--transition-fast); display: flex; align-items: center; justify-content: center; @@ -305,10 +325,18 @@ pre { opacity: 1; background: var(--color-background-tertiary); border-color: var(--color-border-emphasis); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.copy-code-button:focus { + outline: none; + box-shadow: 0 0 0 3px var(--color-accent-subtle); } .copy-code-button:active { - transform: scale(0.95); + transform: scale(0.92); + box-shadow: none; } .copy-code-button svg { @@ -320,22 +348,130 @@ pre { stroke-linecap: round; stroke-linejoin: round; color: var(--color-text-secondary); - transition: color var(--transition-fast); + transition: color var(--transition-fast), transform var(--transition-base); } .copy-code-button:hover svg { color: var(--color-text-primary); } -/* Copied state - subtle green checkmark */ +/* Copied state - success animation with pulse */ .copy-code-button.copied { background: var(--color-success-bg); border-color: var(--color-green-500); opacity: 1; + animation: copy-success-pulse var(--duration-slow) ease-out; } .copy-code-button.copied svg { color: var(--color-green-600); + animation: copy-checkmark-pop var(--duration-base) ease-out; +} + +@keyframes copy-success-pulse { + 0% { + transform: scale(1); + box-shadow: 0 0 0 0 var(--color-success-pulse); + } + + 50% { + transform: scale(1.08); + box-shadow: 0 0 0 8px var(--color-success-pulse-fade); + } + + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 var(--color-success-pulse-fade); + } +} + +@keyframes copy-checkmark-pop { + 0% { + transform: scale(0.5); + opacity: 0; + } + + 50% { + transform: scale(1.2); + } + + 100% { + transform: scale(1); + opacity: 1; + } +} + +/* Tooltip for copy button - positioned below to avoid Source button overlap */ +.copy-code-button::after { + content: attr(data-tooltip); + position: absolute; + top: calc(100% + 6px); + left: 50%; + transform: translateX(-50%) translateY(-4px); + padding: var(--space-1) var(--space-2); + background: var(--color-neutral-800); + color: var(--color-neutral-50); + font-size: var(--font-size-xs); + font-family: var(--font-primary); + white-space: nowrap; + border-radius: var(--radius-sm); + opacity: 0; + visibility: hidden; + transition: opacity var(--transition-fast), transform var(--transition-fast), visibility var(--transition-fast); + pointer-events: none; + z-index: 20; +} + +.copy-code-button::before { + content: ''; + position: absolute; + top: calc(100% + 2px); + left: 50%; + transform: translateX(-50%); + border: 4px solid transparent; + border-bottom-color: var(--color-neutral-800); + opacity: 0; + visibility: hidden; + transition: opacity var(--transition-fast), visibility var(--transition-fast); + pointer-events: none; + z-index: 20; +} + +.copy-code-button[data-tooltip]:hover::after, +.copy-code-button[data-tooltip]:focus::after { + opacity: 1; + visibility: visible; + transform: translateX(-50%) translateY(0); +} + +.copy-code-button[data-tooltip]:hover::before, +.copy-code-button[data-tooltip]:focus::before { + opacity: 1; + visibility: visible; +} + +/* Copied tooltip state */ +.copy-code-button.copied::after { + content: 'Copied!'; + background: var(--color-green-600); + opacity: 1; + visibility: visible; + transform: translateX(-50%) translateY(0); +} + +.copy-code-button.copied::before { + border-bottom-color: var(--color-green-600); + opacity: 1; + visibility: visible; +} + +[data-theme="dark"] .copy-code-button::after { + background: var(--color-neutral-100); + color: var(--color-neutral-900); +} + +[data-theme="dark"] .copy-code-button::before { + border-bottom-color: var(--color-neutral-100); } [data-theme="dark"] .copy-code-button.copied { @@ -346,6 +482,15 @@ pre { color: var(--color-green-400); } +[data-theme="dark"] .copy-code-button.copied::after { + background: var(--color-green-500); + color: white; +} + +[data-theme="dark"] .copy-code-button.copied::before { + border-bottom-color: var(--color-green-500); +} + /* Mobile adjustments */ @media (hover: none) { .copy-code-button { @@ -457,7 +602,11 @@ header.top-navbar .navbar-search form { border-radius: var(--radius-md); color: var(--color-text-primary); cursor: pointer; - transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast); + transition: + background var(--transition-fast), + border-color var(--transition-fast), + color var(--transition-fast), + transform var(--transition-fast); font-size: var(--font-size-lg); line-height: 1; width: 2.5rem; @@ -468,6 +617,7 @@ header.top-navbar .navbar-search form { background: var(--color-background-secondary); border-color: var(--color-accent-primary); color: var(--color-accent-primary); + transform: scale(1.05); } .theme-toggle:focus { @@ -476,13 +626,17 @@ header.top-navbar .navbar-search form { box-shadow: 0 0 0 3px var(--color-accent-subtle); } +.theme-toggle:active { + transform: scale(0.95); +} + .theme-toggle-icon { display: inline-block; - transition: transform var(--transition-base); + transition: transform var(--duration-base) var(--ease-out-smooth); } .theme-toggle:hover .theme-toggle-icon { - transform: rotate(20deg); + transform: rotate(15deg) scale(1.1); } /* Mobile navbar */ @@ -537,11 +691,33 @@ nav { background: var(--color-nav-bg); color: var(--color-nav-text); overflow: hidden auto; + overscroll-behavior: contain; display: flex; flex-direction: column; position: sticky; top: var(--layout-header-height); height: calc(100vh - var(--layout-header-height)); + scrollbar-width: thin; + scrollbar-color: var(--color-border-default) transparent; +} + +/* Custom scrollbar for WebKit browsers */ +nav::-webkit-scrollbar { + width: 6px; +} + +nav::-webkit-scrollbar-track { + background: transparent; +} + +nav::-webkit-scrollbar-thumb { + background: var(--color-border-default); + border-radius: var(--radius-sm); + transition: background var(--transition-fast); +} + +nav::-webkit-scrollbar-thumb:hover { + background: var(--color-border-emphasis); } /* Mobile navigation */ @@ -615,6 +791,18 @@ nav ul li { line-height: var(--line-height-relaxed); } +nav ul li a { + padding: var(--space-1) 0; + transition: + color var(--transition-fast), + transform var(--transition-fast), + padding var(--transition-fast); +} + +nav ul li a:hover { + padding-left: var(--space-1); +} + nav ul ul { padding-left: var(--space-5); margin-top: var(--space-2); @@ -632,6 +820,17 @@ nav a { text-decoration: none; } +/* Truncation for direct nav links (not links inside code tags) */ +nav .nav-list > li > a, +nav .nav-section > ul > li > a, +nav .nav-section > dl > dd > a { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + nav footer { padding: var(--space-4); border-top: 1px solid var(--color-border-default); @@ -735,6 +934,11 @@ nav .nav-section-title { font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); color: inherit; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } nav .nav-section-chevron { @@ -1113,19 +1317,54 @@ main .anchor-link:target { main .method-source-code { visibility: hidden; max-height: 0; - overflow: auto; - transition: max-height var(--transition-base), visibility var(--transition-base); + overflow: hidden; + opacity: 0; + transform: translateY(-8px); + transition: + max-height var(--duration-medium) var(--ease-out-smooth), + visibility var(--duration-medium), + opacity var(--duration-fast) ease-out, + transform var(--duration-fast) ease-out; } main .method-source-code pre { border-color: var(--color-accent-hover); + border-left: 3px solid var(--color-accent-primary); width: 100%; box-sizing: border-box; + transition: border-color var(--transition-fast); + scrollbar-width: thin; + scrollbar-color: var(--color-border-default) transparent; +} + +main .method-source-code pre::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +main .method-source-code pre::-webkit-scrollbar-track { + background: transparent; +} + +main .method-source-code pre::-webkit-scrollbar-thumb { + background: var(--color-border-default); + border-radius: var(--radius-sm); +} + +main .method-source-code pre::-webkit-scrollbar-thumb:hover { + background: var(--color-border-emphasis); +} + +main .method-source-code pre::-webkit-scrollbar-corner { + background: transparent; } main .method-source-code.active-menu { visibility: visible; max-height: 100vh; + overflow: auto; + opacity: 1; + transform: translateY(0); } main .method-description .method-calls-super { @@ -1165,10 +1404,47 @@ main .method-heading .method-args { } main .method-controls { - line-height: 20px; float: right; - color: var(--color-accent-hover); +} + +main .method-controls summary { + display: inline-block; + line-height: 20px; + color: var(--color-accent-primary); cursor: pointer; + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-sm); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + background: var(--color-accent-subtle); + border: 1px solid transparent; + transition: + color var(--transition-fast), + background var(--transition-fast), + border-color var(--transition-fast), + transform var(--transition-fast); + user-select: none; + -webkit-user-select: none; + list-style: none; +} + +main .method-controls summary::-webkit-details-marker { + display: none; +} + +main .method-controls summary:hover { + background: var(--color-primary-100); + border-color: var(--color-primary-300); + transform: translateY(-1px); +} + +main .method-controls summary:active { + transform: scale(0.96); +} + +[data-theme="dark"] main .method-controls summary:hover { + background: var(--color-accent-subtle-hover); + border-color: var(--color-primary-500); } main .method-description, diff --git a/lib/rdoc/generator/template/aliki/js/aliki.js b/lib/rdoc/generator/template/aliki/js/aliki.js index 6f0dce8951..a335c3399b 100644 --- a/lib/rdoc/generator/template/aliki/js/aliki.js +++ b/lib/rdoc/generator/template/aliki/js/aliki.js @@ -377,7 +377,7 @@ function createCopyButton() { button.className = 'copy-code-button'; button.type = 'button'; button.setAttribute('aria-label', 'Copy code to clipboard'); - button.setAttribute('title', 'Copy code'); + button.setAttribute('data-tooltip', 'Copy'); // Create clipboard icon SVG const clipboardIcon = ` @@ -457,14 +457,14 @@ function showCopySuccess(button) { button.innerHTML = button.dataset.checkIcon; button.classList.add('copied'); button.setAttribute('aria-label', 'Copied!'); - button.setAttribute('title', 'Copied!'); + button.setAttribute('data-tooltip', 'Copied!'); // Revert back after 2 seconds setTimeout(() => { button.innerHTML = button.dataset.clipboardIcon; button.classList.remove('copied'); button.setAttribute('aria-label', 'Copy code to clipboard'); - button.setAttribute('title', 'Copy code'); + button.setAttribute('data-tooltip', 'Copy'); }, 2000); } From b921b96f521815f441b62ba18b8c26a49ef81bd6 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 17 Dec 2025 15:13:18 +0000 Subject: [PATCH 2/2] Revert copy button changes --- .../generator/template/aliki/css/rdoc.css | 124 +----------------- lib/rdoc/generator/template/aliki/js/aliki.js | 6 +- 2 files changed, 4 insertions(+), 126 deletions(-) diff --git a/lib/rdoc/generator/template/aliki/css/rdoc.css b/lib/rdoc/generator/template/aliki/css/rdoc.css index 8db3d76fce..dc8afbccc4 100644 --- a/lib/rdoc/generator/template/aliki/css/rdoc.css +++ b/lib/rdoc/generator/template/aliki/css/rdoc.css @@ -166,11 +166,6 @@ --duration-fast: 250ms; --duration-base: 300ms; --duration-medium: 350ms; - --duration-slow: 400ms; - - /* Animation Colors */ - --color-success-pulse: rgb(34 197 94 / 40%); - --color-success-pulse-fade: rgb(34 197 94 / 0%); /* Z-Index Scale */ --z-fixed: 300; @@ -355,123 +350,15 @@ pre { color: var(--color-text-primary); } -/* Copied state - success animation with pulse */ +/* Copied state - subtle green checkmark */ .copy-code-button.copied { background: var(--color-success-bg); border-color: var(--color-green-500); opacity: 1; - animation: copy-success-pulse var(--duration-slow) ease-out; } .copy-code-button.copied svg { color: var(--color-green-600); - animation: copy-checkmark-pop var(--duration-base) ease-out; -} - -@keyframes copy-success-pulse { - 0% { - transform: scale(1); - box-shadow: 0 0 0 0 var(--color-success-pulse); - } - - 50% { - transform: scale(1.08); - box-shadow: 0 0 0 8px var(--color-success-pulse-fade); - } - - 100% { - transform: scale(1); - box-shadow: 0 0 0 0 var(--color-success-pulse-fade); - } -} - -@keyframes copy-checkmark-pop { - 0% { - transform: scale(0.5); - opacity: 0; - } - - 50% { - transform: scale(1.2); - } - - 100% { - transform: scale(1); - opacity: 1; - } -} - -/* Tooltip for copy button - positioned below to avoid Source button overlap */ -.copy-code-button::after { - content: attr(data-tooltip); - position: absolute; - top: calc(100% + 6px); - left: 50%; - transform: translateX(-50%) translateY(-4px); - padding: var(--space-1) var(--space-2); - background: var(--color-neutral-800); - color: var(--color-neutral-50); - font-size: var(--font-size-xs); - font-family: var(--font-primary); - white-space: nowrap; - border-radius: var(--radius-sm); - opacity: 0; - visibility: hidden; - transition: opacity var(--transition-fast), transform var(--transition-fast), visibility var(--transition-fast); - pointer-events: none; - z-index: 20; -} - -.copy-code-button::before { - content: ''; - position: absolute; - top: calc(100% + 2px); - left: 50%; - transform: translateX(-50%); - border: 4px solid transparent; - border-bottom-color: var(--color-neutral-800); - opacity: 0; - visibility: hidden; - transition: opacity var(--transition-fast), visibility var(--transition-fast); - pointer-events: none; - z-index: 20; -} - -.copy-code-button[data-tooltip]:hover::after, -.copy-code-button[data-tooltip]:focus::after { - opacity: 1; - visibility: visible; - transform: translateX(-50%) translateY(0); -} - -.copy-code-button[data-tooltip]:hover::before, -.copy-code-button[data-tooltip]:focus::before { - opacity: 1; - visibility: visible; -} - -/* Copied tooltip state */ -.copy-code-button.copied::after { - content: 'Copied!'; - background: var(--color-green-600); - opacity: 1; - visibility: visible; - transform: translateX(-50%) translateY(0); -} - -.copy-code-button.copied::before { - border-bottom-color: var(--color-green-600); - opacity: 1; - visibility: visible; -} - -[data-theme="dark"] .copy-code-button::after { - background: var(--color-neutral-100); - color: var(--color-neutral-900); -} - -[data-theme="dark"] .copy-code-button::before { - border-bottom-color: var(--color-neutral-100); } [data-theme="dark"] .copy-code-button.copied { @@ -482,15 +369,6 @@ pre { color: var(--color-green-400); } -[data-theme="dark"] .copy-code-button.copied::after { - background: var(--color-green-500); - color: white; -} - -[data-theme="dark"] .copy-code-button.copied::before { - border-bottom-color: var(--color-green-500); -} - /* Mobile adjustments */ @media (hover: none) { .copy-code-button { diff --git a/lib/rdoc/generator/template/aliki/js/aliki.js b/lib/rdoc/generator/template/aliki/js/aliki.js index a335c3399b..6f0dce8951 100644 --- a/lib/rdoc/generator/template/aliki/js/aliki.js +++ b/lib/rdoc/generator/template/aliki/js/aliki.js @@ -377,7 +377,7 @@ function createCopyButton() { button.className = 'copy-code-button'; button.type = 'button'; button.setAttribute('aria-label', 'Copy code to clipboard'); - button.setAttribute('data-tooltip', 'Copy'); + button.setAttribute('title', 'Copy code'); // Create clipboard icon SVG const clipboardIcon = ` @@ -457,14 +457,14 @@ function showCopySuccess(button) { button.innerHTML = button.dataset.checkIcon; button.classList.add('copied'); button.setAttribute('aria-label', 'Copied!'); - button.setAttribute('data-tooltip', 'Copied!'); + button.setAttribute('title', 'Copied!'); // Revert back after 2 seconds setTimeout(() => { button.innerHTML = button.dataset.clipboardIcon; button.classList.remove('copied'); button.setAttribute('aria-label', 'Copy code to clipboard'); - button.setAttribute('data-tooltip', 'Copy'); + button.setAttribute('title', 'Copy code'); }, 2000); }