feat: add brightness slider with auto theme adaptation

This commit is contained in:
2026-03-21 20:39:59 +08:00
parent ca9aae75fb
commit c8a430b918
2 changed files with 195 additions and 0 deletions

View File

@@ -11,6 +11,16 @@
<div id="terminal-container">
<div class="terminal-header">
<div class="terminal-title">XCTerminal</div>
<div class="terminal-controls">
<button id="theme-toggle" title="Adjust Brightness"></button>
</div>
</div>
<div id="brightness-popup" class="brightness-popup hidden">
<div class="brightness-slider-container">
<span class="brightness-icon">🌙</span>
<input type="range" id="brightness-slider" min="0" max="100" value="0">
<span class="brightness-icon"></span>
</div>
</div>
<div id="terminal"></div>
<div class="terminal-status">
@@ -36,6 +46,112 @@
let ws;
let term;
let currentBrightness = 0;
function getThemeForBrightness(brightness) {
const isDark = brightness < 50;
const bgValue = Math.round((brightness / 100) * 255);
const bgColor = `rgb(${bgValue}, ${bgValue}, ${bgValue})`;
const headerBg = isDark ? 'rgb(10, 10, 10)' : 'rgb(245, 245, 245)';
const statusBg = isDark ? 'rgb(10, 10, 10)' : 'rgb(240, 240, 240)';
const textColor = isDark ? 'rgb(136, 136, 136)' : 'rgb(80, 80, 80)';
const borderColor = isDark ? 'rgb(26, 26, 26)' : 'rgb(220, 220, 220)';
document.documentElement.style.setProperty('--bg-primary', bgColor);
document.documentElement.style.setProperty('--bg-secondary', isDark ? 'rgb(10, 10, 10)' : 'rgb(245, 245, 245)');
document.documentElement.style.setProperty('--text-secondary', textColor);
document.documentElement.style.setProperty('--border', borderColor);
document.documentElement.style.setProperty('--bg-tertiary', isDark ? 'rgb(30, 30, 30)' : 'rgb(230, 230, 230)');
const terminalTheme = isDark ? {
background: bgColor,
foreground: '#cccccc',
cursor: '#ff6b6b',
cursorAccent: bgColor,
selection: 'rgba(100, 100, 100, 0.3)',
black: '#000000',
red: '#d35252',
green: '#859a5f',
yellow: '#d64c4c',
blue: '#7278b2',
magenta: '#9c6cb3',
cyan: '#6fb3b8',
white: '#cccccc',
brightBlack: '#666666',
brightRed: '#d35252',
brightGreen: '#859a5f',
brightYellow: '#d64c4c',
brightBlue: '#7278b2',
brightMagenta: '#9c6cb3',
brightCyan: '#6fb3b8',
brightWhite: '#ffffff'
} : {
background: bgColor,
foreground: '#1a1a1a',
cursor: '#d63031',
cursorAccent: bgColor,
selection: 'rgba(0, 0, 0, 0.2)',
black: '#000000',
red: '#c0392b',
green: '#27ae60',
yellow: '#d63031',
blue: '#2980b9',
magenta: '#8e44ad',
cyan: '#16a085',
white: '#1a1a1a',
brightBlack: '#555555',
brightRed: '#c0392b',
brightGreen: '#27ae60',
brightYellow: '#d63031',
brightBlue: '#2980b9',
brightMagenta: '#8e44ad',
brightCyan: '#16a085',
brightWhite: '#ffffff'
};
if (term) {
term.options.theme = terminalTheme;
}
return { bgColor, headerBg, statusBg, textColor, borderColor };
}
function updateBrightness(brightness) {
currentBrightness = brightness;
const { bgColor, headerBg, statusBg, borderColor } = getThemeForBrightness(brightness);
document.body.style.background = bgColor;
document.querySelector('.terminal-header').style.background = headerBg;
document.querySelector('.terminal-header').style.borderBottomColor = borderColor;
document.querySelector('.terminal-status').style.background = statusBg;
document.querySelector('.terminal-status').style.borderTopColor = borderColor;
const sliderThumb = document.querySelector('#brightness-slider::-webkit-slider-thumb');
if (sliderThumb) {
sliderThumb.style.background = brightness > 50 ? '#1a1a1a' : '#ffffff';
}
}
function setupBrightnessControl() {
const themeToggle = document.getElementById('theme-toggle');
const brightnessPopup = document.getElementById('brightness-popup');
const brightnessSlider = document.getElementById('brightness-slider');
themeToggle.addEventListener('click', (e) => {
e.stopPropagation();
brightnessPopup.classList.toggle('hidden');
});
brightnessSlider.addEventListener('input', (e) => {
updateBrightness(parseInt(e.target.value));
});
document.addEventListener('click', (e) => {
if (!brightnessPopup.contains(e.target) && e.target !== themeToggle) {
brightnessPopup.classList.add('hidden');
}
});
}
async function initGhosttyVT() {
const wasmUrl = urlParams.get('wasm') || 'ghostty-vt.wasm';
@@ -168,6 +284,7 @@
term.focus();
}
setupBrightnessControl();
connectTerminal();
initGhosttyVT();
</script>

View File

@@ -158,4 +158,82 @@ html, body {
#terminal {
padding: 4px;
}
}
#theme-toggle {
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
font-size: 16px;
transition: color 0.2s, background 0.2s;
-webkit-app-region: no-drag;
}
#theme-toggle:hover {
color: var(--text-primary);
background: var(--bg-tertiary);
}
.brightness-popup {
position: absolute;
top: 44px;
right: 16px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
}
.brightness-popup.hidden {
display: none;
}
.brightness-slider-container {
display: flex;
align-items: center;
gap: 10px;
}
.brightness-icon {
font-size: 14px;
color: var(--text-secondary);
}
#brightness-slider {
-webkit-appearance: none;
appearance: none;
width: 150px;
height: 6px;
border-radius: 3px;
background: linear-gradient(to right, #000000, #ffffff);
outline: none;
cursor: pointer;
}
#brightness-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #ffffff;
border: 2px solid #333;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
#brightness-slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: #ffffff;
border: 2px solid #333;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}