refactor: Implement mobile-friendly control panel toggle and remove keyboard shortcuts hint.

This commit is contained in:
syntaxbullet
2026-02-10 13:02:41 +01:00
parent bb4ca0610d
commit faa9609254
10 changed files with 1160 additions and 366 deletions

View File

@@ -6,226 +6,336 @@ import TuiButton from "./TuiButton.astro";
---
<footer id="tui-controls" class="control-panel">
<div class="control-panel-content">
<!-- Sliders Section -->
<div class="panel-section sliders-section">
<div class="section-header">ADJUSTMENTS</div>
<div class="sliders-grid">
<TuiSlider
id="exposure"
label="EXP"
min={0}
max={3}
step={0.01}
value={1.0}
title="Exposure / Brightness"
description="Adjusts the overall brightness level of the input image before processing."
/>
<TuiSlider
id="contrast"
label="CON"
min={0}
max={3}
step={0.01}
value={1.0}
title="Contrast"
description="Increases or decreases the difference between light and dark areas."
/>
<TuiSlider
id="saturation"
label="SAT"
min={0}
max={3}
step={0.01}
value={1.2}
title="Saturation"
description="Controls color intensity. Higher values make colors more vibrant in Color Mode."
/>
<TuiSlider
id="gamma"
label="GAM"
min={0}
max={3}
step={0.01}
value={1.0}
title="Gamma Correction"
description="Non-linear brightness adjustment. useful for correcting washed-out or too dark images."
/>
<TuiSlider
id="overlayStrength"
label="OVL"
min={0}
max={1}
step={0.01}
value={0.3}
title="Overlay Blend Strength"
description="Blends the original image over the ASCII output. 0 is pure ASCII, 1 is original image."
/>
<TuiSlider
id="resolution"
label="RES"
min={0.1}
max={2}
step={0.01}
value={1.0}
title="Resolution Scale"
description="Adjusts the density of characters. Higher values give more detail but may reduce performance."
/>
<TuiSlider
id="dither"
label="DTH"
min={0}
max={1}
step={0.01}
value={0}
title="Dither Strength"
description="Applies ordered dithering to simulate shading. Useful for low-contrast areas."
/>
</div>
</div>
<div class="mobile-controls-header">
<span class="mobile-controls-title">CONTROLS</span>
<svg
class="mobile-toggle-icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
<div class="panel-divider-v"></div>
<!-- Toggles & Segments Section -->
<div class="panel-section effects-section">
<div class="sub-section">
<div class="section-header">EFFECTS</div>
<div class="toggles-row">
<TuiToggle
id="toggle-color"
label="CLR"
title="Color Output (HTML)"
description="Toggles between monochrome text and colored HTML spans."
<div class="control-panel-inner">
<div class="control-panel-content">
<!-- Sliders Section -->
<div class="panel-section sliders-section">
<div class="section-header">ADJUSTMENTS</div>
<div class="sliders-grid">
<TuiSlider
id="exposure"
label="EXP"
min={0}
max={3}
step={0.01}
value={1.0}
title="Exposure / Brightness"
description="Adjusts the overall brightness level of the input image before processing."
/>
<TuiToggle
id="toggle-denoise"
label="DNZ"
title="Denoise Pre-processing"
description="Applies a bilateral filter to reduce image noise while preserving edges."
<TuiSlider
id="contrast"
label="CON"
min={0}
max={3}
step={0.01}
value={1.0}
title="Contrast"
description="Increases or decreases the difference between light and dark areas."
/>
<TuiSlider
id="shadows"
label="SHD"
min={0}
max={1}
step={0.01}
value={0.0}
title="Shadow Lift"
description="Brightens dark areas to recover details in shadows."
/>
<TuiSlider
id="highlights"
label="HLT"
min={0}
max={1}
step={0.01}
value={0.0}
title="Highlight Dim"
description="Darkens bright areas to recover details in highlights."
/>
<TuiSlider
id="saturation"
label="SAT"
min={0}
max={3}
step={0.01}
value={1.2}
title="Saturation"
description="Controls color intensity. Higher values make colors more vibrant in Color Mode."
/>
<TuiSlider
id="gamma"
label="GAM"
min={0}
max={3}
step={0.01}
value={1.0}
title="Gamma Correction"
description="Non-linear brightness adjustment. useful for correcting washed-out or too dark images."
/>
<TuiSlider
id="sharpen"
label="SHP"
min={0}
max={2}
step={0.01}
value={0.0}
title="Sharpen"
description="Enhances edges and fine details using an unsharp mask filter."
/>
<TuiSlider
id="overlayStrength"
label="OVL"
min={0}
max={1}
step={0.01}
value={0.3}
title="Overlay Blend Strength"
description="Blends the original image over the ASCII output. 0 is pure ASCII, 1 is original image."
/>
<TuiSlider
id="resolution"
label="RES"
min={0.1}
max={2}
step={0.01}
value={1.0}
title="Resolution Scale"
description="Adjusts the density of characters. Higher values give more detail but may reduce performance."
/>
<TuiSlider
id="dither"
label="DTH"
min={0}
max={1}
step={0.01}
value={0}
title="Dither Strength"
description="Applies ordered dithering to simulate shading. Useful for low-contrast areas."
/>
<TuiSlider
id="edgeThreshold"
label="THR"
min={0}
max={1}
step={0.01}
value={0.5}
title="Edge Threshold"
description="Sets the sensitivity for edge detection. Higher values detect only strong edges."
/>
<TuiSlider
id="scanlines"
label="SCN"
min={0}
max={1}
step={0.01}
value={0.0}
title="Scanlines"
description="Adds CRT-style scanline effect."
/>
<TuiSlider
id="vignette"
label="VIG"
min={0}
max={1}
step={0.01}
value={0.0}
title="Vignette"
description="Darkens the corners of the image."
/>
</div>
</div>
<div class="sub-section">
<div class="section-header">OUTPUT</div>
<div class="segments-col">
<TuiSegment
id="segment-invert"
label="INV"
options={["AUTO", "ON", "OFF"]}
value="AUTO"
title="Invert Colors"
description="Inverts brightness mapping. AUTO detects dark/light mode."
/>
<TuiSegment
id="segment-edge"
label="EDG"
options={["OFF", "SPL", "SOB", "CNY"]}
value="OFF"
title="Edge Detection Mode"
description="Algorithm used to detect edges. SPL: Simple, SOB: Sobel, CNY: Canny."
/>
<TuiSegment
id="segment-charset"
label="SET"
options={["STD", "EXT", "BLK", "MIN", "DOT", "SHP"]}
value="STD"
title="Character Set"
description="The set of characters used for mapping brightness levels."
/>
<div class="panel-divider-v"></div>
<!-- Toggles & Segments Section -->
<div class="panel-section effects-section">
<div class="sub-section">
<div class="section-header">EFFECTS</div>
<div class="toggles-row">
<TuiToggle
id="toggle-color"
label="CLR"
title="Color Output (HTML)"
description="Toggles between monochrome text and colored HTML spans."
/>
<!-- Color Picker for Monochrome -->
<div
class="tui-color-btn"
title="Monochrome Tint Color"
>
<input
type="color"
id="input-mono-color"
value="#ffffff"
/>
<span id="color-swatch-display" class="color-swatch"
></span>
<span class="label">TINT</span>
</div>
<TuiToggle
id="toggle-denoise"
label="DNZ"
title="Denoise Pre-processing"
description="Applies a bilateral filter to reduce image noise while preserving edges."
/>
</div>
</div>
</div>
</div>
<div class="panel-divider-v"></div>
<!-- Right Column: Actions & Export -->
<div class="panel-section actions-section">
<div class="sub-section">
<div class="section-header">ACTIONS</div>
<div class="actions-grid">
<TuiButton
id="btn-reset"
label="RESET"
shortcut="R"
title="Reset to Auto-detected Settings"
description="Resets all sliders and toggles to their default values."
/>
<TuiButton
id="btn-next"
label="NEXT"
shortcut="N"
variant="primary"
title="Load Next Image"
description="Discards current image and loads a new one from the queue."
/>
<div
class="queue-display"
data-tooltip-title="Buffered Images"
data-tooltip-desc="Number of images pre-loaded in background queue."
>
<span class="queue-label">Q:</span>
<span id="val-queue" class="queue-value">0</span>
<div class="sub-section">
<div class="section-header">OUTPUT</div>
<div class="segments-col">
<TuiSegment
id="segment-invert"
label="INV"
options={["AUTO", "ON", "OFF"]}
value="AUTO"
title="Invert Colors"
description="Inverts brightness mapping. AUTO detects dark/light mode."
/>
<TuiSegment
id="segment-edge"
label="EDG"
options={["OFF", "SPL", "SOB", "CNY"]}
value="OFF"
title="Edge Detection Mode"
description="Algorithm used to detect edges. SPL: Simple, SOB: Sobel, CNY: Canny."
/>
<TuiSegment
id="segment-charset"
label="SET"
options={["STD", "EXT", "BLK", "MIN", "DOT", "SHP"]}
value="STD"
title="Character Set"
description="The set of characters used for mapping brightness levels."
/>
</div>
</div>
</div>
<div class="panel-divider-h"></div>
<div class="panel-divider-v"></div>
<div class="sub-section">
<div class="section-header">IMPORT / EXPORT</div>
<div class="actions-grid">
<!-- Hidden File Input -->
<input
type="file"
id="file-upload"
accept="image/*"
style="display: none;"
/>
<TuiButton
id="btn-import"
label="IMP"
title="Import Image"
description="Upload your own image from your device."
/>
<TuiButton
id="btn-save-png"
label="PNG"
title="Save as Image"
description="Download high-res PNG capture of the current view."
/>
<TuiButton
id="btn-copy-text"
label="TXT"
title="Save as Text"
description="Download raw ASCII text file."
/>
<TuiButton
id="btn-copy-html"
label="HTML"
title="Save as HTML"
description="Download colored HTML file."
/>
<!-- Right Column: Actions & Export -->
<div class="panel-section actions-section">
<div class="sub-section">
<div class="section-header">ACTIONS</div>
<div class="actions-grid">
<TuiButton
id="btn-reset"
label="RESET"
shortcut="R"
title="Reset to Auto-detected Settings"
description="Resets all sliders and toggles to their default values."
/>
<TuiButton
id="btn-next"
label="NEXT"
shortcut="N"
variant="primary"
title="Load Next Image"
description="Discards current image and loads a new one from the queue."
/>
<div
class="queue-display"
data-tooltip-title="Buffered Images"
data-tooltip-desc="Number of images pre-loaded in background queue."
>
<span class="queue-label">Q:</span>
<span id="val-queue" class="queue-value">0</span>
</div>
</div>
</div>
<div class="panel-divider-h"></div>
<div class="sub-section">
<div class="section-header">IMPORT / EXPORT</div>
<div class="actions-grid">
<!-- Hidden File Input -->
<input
type="file"
id="file-upload"
accept="image/*"
style="display: none;"
/>
<TuiButton
id="btn-import"
label="IMP"
title="Import Image"
description="Upload your own image from your device."
/>
<TuiButton
id="btn-save-png"
label="PNG"
title="Save as Image"
description="Download high-res PNG capture of the current view."
/>
<TuiButton
id="btn-copy-text"
label="TXT"
title="Save as Text"
description="Download raw ASCII text file."
/>
<TuiButton
id="btn-copy-html"
label="HTML"
title="Save as HTML"
description="Download colored HTML file."
/>
</div>
</div>
</div>
</div>
</div>
<!-- Keyboard shortcuts hint -->
<div class="shortcuts-hint">
<span><kbd>N</kbd> Next</span>
<span><kbd>R</kbd> Reset</span>
<span><kbd>I</kbd> Invert</span>
<span><kbd>C</kbd> Color</span>
<span><kbd>D</kbd> Dither</span>
<span><kbd>E</kbd> Edges</span>
<span><kbd>S</kbd> Charset</span>
<!-- Keyboard shortcuts hint -->
<div class="shortcuts-hint">
<span><kbd>N</kbd> Next</span>
<span><kbd>R</kbd> Reset</span>
<span><kbd>I</kbd> Invert</span>
<span><kbd>C</kbd> Color</span>
<span><kbd>D</kbd> Dither</span>
<span><kbd>E</kbd> Edges</span>
<span><kbd>S</kbd> Charset</span>
</div>
</div>
</footer>
<script>
const controlPanel = document.getElementById("tui-controls");
const toggleBtn = controlPanel?.querySelector(".mobile-controls-header");
if (controlPanel && toggleBtn) {
// Default to collapsed on mobile
if (window.innerWidth <= 1200) {
controlPanel.classList.add("collapsed");
}
toggleBtn.addEventListener("click", () => {
controlPanel.classList.toggle("collapsed");
});
}
</script>
<style>
/* Main Container matching Sidebar visual style */
.control-panel {
@@ -384,10 +494,62 @@ import TuiButton from "./TuiButton.astro";
color: #fff;
}
.mobile-controls-header {
display: none;
}
/* Responsive Design */
@media (max-width: 1200px) {
.control-panel {
padding: 0; /* Let inner content handle padding or just reset */
}
.mobile-controls-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
cursor: pointer;
background: #000;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.mobile-controls-title {
font-family: var(--font-mono);
font-weight: 700;
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.8);
letter-spacing: 1px;
}
.mobile-toggle-icon {
color: rgba(255, 255, 255, 0.5);
transition: transform 0.3s ease;
}
.control-panel.collapsed .mobile-toggle-icon {
transform: rotate(-90deg);
}
.control-panel-inner {
padding: 1.5rem;
overflow: hidden;
transition:
max-height 0.4s ease,
opacity 0.4s ease,
padding 0.4s ease;
max-height: 2000px;
opacity: 1;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.control-panel.collapsed .control-panel-inner {
max-height: 0;
opacity: 0;
padding: 0 1.5rem;
pointer-events: none;
}
.control-panel-content {
@@ -408,4 +570,55 @@ import TuiButton from "./TuiButton.astro";
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
}
}
.tui-color-btn {
position: relative;
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.7);
font-family: var(--font-mono);
font-size: 11px;
padding: 4px 10px;
cursor: pointer;
border-radius: 2px;
transition: all 0.2s;
height: 29px; /* Match toggle/button height approx */
box-sizing: border-box;
}
.tui-color-btn:hover {
color: #fff;
border-color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.08);
}
.tui-color-btn input[type="color"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
padding: 0;
border: none;
z-index: 2;
}
.color-swatch {
width: 12px;
height: 12px;
background-color: #ffffff;
border-radius: 2px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}
.tui-color-btn .label {
font-weight: 600;
letter-spacing: 0.5px;
}
</style>

View File

@@ -3,6 +3,22 @@
---
<aside class="sidebar">
<div class="mobile-header">
<span class="mobile-brand">SYNTAXBULLET</span>
<svg
class="mobile-toggle-icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
<div class="sidebar-content">
<div class="brand-group">
<a href="/" class="brand-link">
@@ -10,24 +26,24 @@
</a>
<div class="brand-subtitle">
FULL STACK ENGINEER
<span class="muted">//</span>
<span class="muted">|</span>
CREATIVE TECHNOLOGIST
</div>
</div>
<p class="brand-bio">
Crafting high-performance digital experiences at the intersection of
code, art, and artificial intelligence.
engineering, design, and artificial intelligence.
</p>
<div class="sidebar-actions">
<a href="/projects" class="sidebar-link">
<span class="icon">⚡</span> PROJECTS
<a href="/" class="sidebar-link">
<span class="icon">⚡</span> GENERATE
</a>
<a href="/blog" class="sidebar-link">
<span class="icon">📝</span> BLOG
</a>
<a href="mailto:contact@syntaxbullet.me" class="sidebar-link">
<a href="mailto:me@syntaxbullet.com" class="sidebar-link">
<span class="icon">✉️</span> CONTACT
</a>
</div>
@@ -116,6 +132,10 @@
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.5);
}
.mobile-header {
display: none;
}
.sidebar-content {
padding: 4rem 3rem;
display: flex;
@@ -224,13 +244,52 @@
min-width: 0;
border-right: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding: 3rem 0; /* Increased padding */
padding: 0; /* Remove padding from container, move to toggle */
}
.mobile-header {
display: flex; /* Show on mobile */
justify-content: space-between;
align-items: center;
padding: 1.5rem;
cursor: pointer;
background: #000;
}
.mobile-brand {
font-size: 1.25rem;
font-weight: 900;
color: #fff;
letter-spacing: -0.5px;
}
.mobile-toggle-icon {
color: rgba(255, 255, 255, 0.5);
transition: transform 0.3s ease;
}
.sidebar.collapsed .mobile-toggle-icon {
transform: rotate(-90deg);
}
.sidebar-content {
padding: 1rem 3rem; /* Increased horizontal padding */
padding: 0 3rem 3rem 3rem; /* Adjust padding */
align-items: center;
text-align: center;
overflow: hidden;
transition:
max-height 0.4s ease,
opacity 0.4s ease,
padding 0.4s ease;
max-height: 1000px; /* Arbitrary large height */
opacity: 1;
}
.sidebar.collapsed .sidebar-content {
max-height: 0;
opacity: 0;
padding: 0 3rem; /* Collapse padding */
pointer-events: none;
}
.brand-subtitle,
@@ -238,5 +297,29 @@
justify-content: center;
align-items: center;
}
/* Hide the large title in the content on mobile since we have the header,
or keep it? The user might want the full bio etc.
Let's keep the content as is, but maybe hide the "SYNTAXBULLET" big text in content if it's redundant?
Actually, the design seems to desire the big brand text. I'll leave it. */
}
</style>
<script>
const sidebar = document.querySelector(".sidebar");
const toggleBtn = document.querySelector(".mobile-header");
if (sidebar && toggleBtn) {
// Default to collapsed on mobile initially?
// User didn't specify, but usually "collapsible" implies starting collapsed or having the ability.
// Let's start expanded or collapsed? "Make... collapsible".
// I'll default to collapsed to save space as requested "mobile friendly".
if (window.innerWidth <= 1024) {
sidebar.classList.add("collapsed");
}
toggleBtn.addEventListener("click", () => {
sidebar.classList.toggle("collapsed");
});
}
</script>