feat: Enhance UI components with full labels and abbreviations, and add port mapping to dev docker-compose.

This commit is contained in:
syntaxbullet
2026-02-10 20:07:00 +01:00
parent a9d2c43bfd
commit 5cd52f2785
11 changed files with 332 additions and 252 deletions

View File

@@ -20,7 +20,8 @@ import { ChevronDown } from "@lucide/astro";
<div class="sliders-grid">
<TuiSlider
id="exposure"
label="EXP"
label="Exposure"
abbr="EXP"
min={0}
max={3}
step={0.01}
@@ -30,7 +31,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="contrast"
label="CON"
label="Contrast"
abbr="CON"
min={0}
max={3}
step={0.01}
@@ -40,7 +42,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="shadows"
label="SHD"
label="Shadows"
abbr="SHD"
min={0}
max={1}
step={0.01}
@@ -50,7 +53,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="highlights"
label="HLT"
label="Highlights"
abbr="HLT"
min={0}
max={1}
step={0.01}
@@ -60,7 +64,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="saturation"
label="SAT"
label="Saturation"
abbr="SAT"
min={0}
max={3}
step={0.01}
@@ -70,7 +75,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="gamma"
label="GAM"
label="Gamma"
abbr="GAM"
min={0}
max={3}
step={0.01}
@@ -80,7 +86,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="sharpen"
label="SHP"
label="Sharpen"
abbr="SHP"
min={0}
max={10}
step={0.01}
@@ -90,7 +97,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="overlayStrength"
label="OVL"
label="Overlay"
abbr="OVL"
min={0}
max={1}
step={0.01}
@@ -100,7 +108,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="resolution"
label="RES"
label="Resolution"
abbr="RES"
min={0.1}
max={2}
step={0.01}
@@ -110,7 +119,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="dither"
label="DTH"
label="Dither"
abbr="DTH"
min={0}
max={1}
step={0.01}
@@ -120,7 +130,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="edgeThreshold"
label="THR"
label="Threshold"
abbr="THR"
min={0}
max={20}
step={0.1}
@@ -130,7 +141,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="scanlines"
label="SCN"
label="Scanlines"
abbr="SCN"
min={0}
max={2}
step={0.01}
@@ -140,7 +152,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSlider
id="vignette"
label="VIG"
label="Vignette"
abbr="VIG"
min={0}
max={1}
step={0.01}
@@ -160,7 +173,8 @@ import { ChevronDown } from "@lucide/astro";
<div class="toggles-row">
<TuiToggle
id="toggle-color"
label="CLR"
label="Color"
abbr="CLR"
title="Color Output (HTML)"
description="Toggles between monochrome text and colored HTML spans."
/>
@@ -182,7 +196,8 @@ import { ChevronDown } from "@lucide/astro";
<TuiToggle
id="toggle-denoise"
label="DNZ"
label="Denoise"
abbr="DNZ"
title="Denoise Pre-processing"
description="Applies a bilateral filter to reduce image noise while preserving edges."
/>
@@ -194,7 +209,8 @@ import { ChevronDown } from "@lucide/astro";
<div class="segments-col">
<TuiSegment
id="segment-invert"
label="INV"
label="Invert"
abbr="INV"
options={["AUTO", "ON", "OFF"]}
value="AUTO"
title="Invert Colors"
@@ -202,7 +218,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSegment
id="segment-edge"
label="EDG"
label="Edges"
abbr="EDG"
options={["OFF", "SPL", "SOB", "CNY"]}
value="OFF"
title="Edge Detection Mode"
@@ -210,7 +227,8 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiSegment
id="segment-charset"
label="SET"
label="Charset"
abbr="SET"
options={["STD", "EXT", "BLK", "MIN", "DOT", "SHP"]}
value="STD"
title="Character Set"
@@ -230,6 +248,7 @@ import { ChevronDown } from "@lucide/astro";
<TuiButton
id="btn-reset"
label="RESET"
abbr="RST"
shortcut="R"
title="Reset to Auto-detected Settings"
description="Resets all sliders and toggles to their default values."
@@ -238,6 +257,7 @@ import { ChevronDown } from "@lucide/astro";
<TuiButton
id="btn-next"
label="NEXT"
abbr="NXT"
shortcut="N"
variant="primary"
title="Load Next Image"
@@ -269,25 +289,29 @@ import { ChevronDown } from "@lucide/astro";
/>
<TuiButton
id="btn-import"
label="IMP"
label="Import"
abbr="IMP"
title="Import Image"
description="Upload your own image from your device."
/>
<TuiButton
id="btn-save-png"
label="PNG"
label="Save PNG"
abbr="PNG"
title="Save as Image"
description="Download high-res PNG capture of the current view."
/>
<TuiButton
id="btn-copy-text"
label="TXT"
label="Save TXT"
abbr="TXT"
title="Save as Text"
description="Download raw ASCII text file."
/>
<TuiButton
id="btn-copy-html"
label="HTML"
label="Save HTML"
abbr="HTML"
title="Save as HTML"
description="Download colored HTML file."
/>

View File

@@ -2,6 +2,7 @@
interface Props {
id: string;
label: string;
abbr?: string;
shortcut?: string;
variant?: "default" | "primary" | "subtle";
title?: string;
@@ -11,6 +12,7 @@ interface Props {
const {
id,
label,
abbr,
shortcut,
variant = "default",
title = "",
@@ -26,7 +28,10 @@ const {
data-tooltip-desc={description}
>
{shortcut && <span class="tui-button-shortcut">{shortcut}</span>}
<span class="tui-button-label">{label}</span>
<span class:list={["tui-button-label", { "has-abbr": !!abbr }]}>
<span class="full">{label}</span>
{abbr && <span class="abbr">{abbr}</span>}
</span>
</button>
<style>
@@ -36,7 +41,8 @@ const {
gap: 6px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.7);
color: #fff;
opacity: 0.8;
font-family: inherit;
font-size: 11px;
padding: 4px 10px;
@@ -47,9 +53,10 @@ const {
}
.tui-button:hover {
color: #fff;
border-color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.08);
color: var(--accent-color);
opacity: 1;
border-color: var(--accent-color);
background: color-mix(in srgb, var(--accent-color), transparent 95%);
transform: translateY(-1px);
}
@@ -59,14 +66,21 @@ const {
}
.tui-button--primary {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
background: var(--accent-color);
border-color: var(--accent-color);
color: #fff;
opacity: 1;
font-weight: 700;
box-shadow: 0 0 15px
color-mix(in srgb, var(--accent-color), transparent 80%);
}
.tui-button--primary:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.4);
background: color-mix(in srgb, var(--accent-color), black 10%);
border-color: color-mix(in srgb, var(--accent-color), black 10%);
color: #fff;
box-shadow: 0 0 20px
color-mix(in srgb, var(--accent-color), transparent 60%);
}
.tui-button--subtle {
@@ -98,5 +112,19 @@ const {
.tui-button-label {
font-weight: 500;
letter-spacing: 0.5px;
display: flex;
}
.tui-button-label .abbr {
display: none;
}
@media (max-width: 1400px) {
.tui-button-label.has-abbr .full {
display: none;
}
.tui-button-label.has-abbr .abbr {
display: inline;
}
}
</style>

View File

@@ -2,6 +2,7 @@
interface Props {
id: string;
label: string;
abbr?: string;
options: string[];
value?: string;
title?: string;
@@ -11,6 +12,7 @@ interface Props {
const {
id,
label,
abbr,
options,
value = options[0],
title = "",
@@ -24,7 +26,10 @@ const {
data-tooltip-title={title}
data-tooltip-desc={description}
>
<span class="tui-segment-label">{label}</span>
<span class:list={["tui-segment-label", { "has-abbr": !!abbr }]}>
<span class="full">{label}</span>
{abbr && <span class="abbr">{abbr}</span>}
</span>
<div class="tui-segment-options" id={id} data-value={value}>
{
options.map((opt) => (
@@ -56,8 +61,23 @@ const {
min-width: 3ch;
font-weight: 700;
font-family: var(--font-mono);
color: rgba(255, 255, 255, 0.4);
color: #fff;
opacity: 0.7;
display: flex;
transition: all 0.2s;
}
.tui-segment-label .abbr {
display: none;
}
@media (max-width: 1400px) {
.tui-segment-label.has-abbr .full {
display: none;
}
.tui-segment-label.has-abbr .abbr {
display: inline;
}
}
.tui-segment-options {
@@ -72,7 +92,8 @@ const {
background: transparent;
border: none;
border-right: 1px solid rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.5);
color: #fff;
opacity: 0.6;
font-family: inherit;
font-size: inherit;
padding: 4px 10px;
@@ -87,24 +108,26 @@ const {
}
.tui-segment-option:hover {
color: #fff;
background: rgba(255, 255, 255, 0.05);
color: var(--accent-color);
opacity: 1;
background: color-mix(in srgb, var(--accent-color), transparent 95%);
}
.tui-segment-option.active {
background: rgba(255, 255, 255, 0.15);
background: var(--accent-color);
color: #fff;
font-weight: 600;
font-weight: 700;
opacity: 1;
}
/* Hover the whole group */
.tui-segment:hover .tui-segment-label {
opacity: 1;
color: rgba(255, 255, 255, 0.8);
color: var(--accent-color);
}
.tui-segment:hover .tui-segment-options {
border-color: rgba(255, 255, 255, 0.2);
border-color: var(--accent-color);
}
</style>

View File

@@ -2,6 +2,7 @@
interface Props {
id: string;
label: string;
abbr?: string;
min?: number;
max?: number;
step?: number;
@@ -13,6 +14,7 @@ interface Props {
const {
id,
label,
abbr,
min = 0,
max = 5,
step = 0.1,
@@ -28,10 +30,18 @@ const segments = 12;
<div
class="tui-slider"
data-slider-id={id}
data-default-value={value}
data-tooltip-title={title}
data-tooltip-desc={description}
>
<span class="tui-slider-label">{label}</span>
<div class="tui-slider-header">
<span class:list={["tui-slider-label", { "has-abbr": !!abbr }]}>
<span class="full">{label}</span>
{abbr && <span class="abbr">{abbr}</span>}
</span>
<span class="tui-slider-value" id={`val-${id}`}>{value.toFixed(2)}</span
>
</div>
<div class="tui-slider-track-wrapper">
<div class="tui-slider-visual">
<span class="tui-slider-track" data-for={id}>
@@ -56,26 +66,50 @@ const segments = 12;
value={value}
/>
</div>
<span class="tui-slider-value" id={`val-${id}`}>{value.toFixed(2)}</span>
</div>
<style>
.tui-slider {
display: flex;
align-items: center;
gap: 8px;
flex-direction: column;
gap: 4px;
font-size: 11px;
user-select: none;
color: rgba(255, 255, 255, 0.6);
}
.tui-slider-header {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.tui-slider-label {
min-width: 3ch;
font-weight: 700;
opacity: 0.5;
opacity: 0.7;
font-family: var(--font-mono);
color: rgba(255, 255, 255, 0.4);
transition: opacity 0.2s;
color: #fff;
transition: all 0.2s;
display: flex;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.tui-slider-label .abbr {
display: none;
}
@media (max-width: 1400px) {
.tui-slider-label.has-abbr .full {
display: none;
}
.tui-slider-label.has-abbr .abbr {
display: inline;
}
}
.tui-slider-track-wrapper {
@@ -113,6 +147,36 @@ const segments = 12;
scale: 1.2;
}
.tui-slider:hover .tui-slider-segment.filled {
color: var(--accent-color);
opacity: 0.8;
}
.tui-slider:hover .tui-slider-segment.thumb {
color: var(--accent-color);
text-shadow: 0 0 8px var(--accent-color);
}
/* Modified state (moved from default) */
.tui-slider.modified .tui-slider-segment.filled {
color: var(--accent-color);
opacity: 0.8;
}
.tui-slider.modified .tui-slider-segment.thumb {
color: var(--accent-color);
text-shadow: 0 0 8px var(--accent-color);
}
.tui-slider.modified .tui-slider-label {
color: var(--accent-color);
opacity: 0.6;
}
.tui-slider.modified .tui-slider-value {
color: var(--accent-color);
}
.tui-slider-input {
position: absolute;
top: 0;
@@ -126,30 +190,27 @@ const segments = 12;
}
.tui-slider-value {
min-width: 4ch;
text-align: right;
font-weight: 400;
opacity: 0.8;
font-family: var(--font-mono);
color: rgba(255, 255, 255, 0.8);
color: #fff;
font-size: 10px;
transition: all 0.2s;
}
/* Hover effect */
.tui-slider:hover .tui-slider-label {
opacity: 1;
color: #fff;
color: var(--accent-color);
}
.tui-slider:hover .tui-slider-value {
opacity: 1;
color: var(--accent-color);
}
.tui-slider:hover .tui-slider-segment {
color: rgba(255, 255, 255, 0.2);
}
.tui-slider:hover .tui-slider-segment.filled {
color: rgba(255, 255, 255, 0.8);
}
.tui-slider:hover .tui-slider-segment.thumb {
color: #fff;
color: rgba(255, 255, 255, 0.3);
}
</style>
@@ -166,6 +227,9 @@ const segments = 12;
const valueDisplay = sliderContainer.querySelector(
".tui-slider-value",
) as HTMLElement;
const defaultValue = parseFloat(
sliderContainer.getAttribute("data-default-value") || "0",
);
if (!input || !track || !valueDisplay) return;
@@ -196,6 +260,10 @@ const segments = 12;
});
valueDisplay.textContent = val.toFixed(2);
// Add modified class if value differs from default
const isModified = Math.abs(val - defaultValue) > 0.001;
sliderContainer.classList.toggle("modified", isModified);
}
input.addEventListener("input", updateVisual);

View File

@@ -2,6 +2,7 @@
interface Props {
id: string;
label: string;
abbr?: string;
checked?: boolean;
title?: string;
description?: string;
@@ -10,6 +11,7 @@ interface Props {
const {
id,
label,
abbr,
checked = false,
title = "",
description = "",
@@ -24,7 +26,10 @@ const {
data-tooltip-title={title}
data-tooltip-desc={description}
>
<span class="tui-toggle-label">{label}</span>
<span class:list={["tui-toggle-label", { "has-abbr": !!abbr }]}>
<span class="full">{label}</span>
{abbr && <span class="abbr">{abbr}</span>}
</span>
</button>
<style>
@@ -34,7 +39,8 @@ const {
justify-content: center;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.5);
color: #fff;
opacity: 0.8;
font-family: inherit;
font-size: 11px;
padding: 4px 12px;
@@ -47,21 +53,39 @@ const {
}
.tui-toggle:hover {
color: #fff;
border-color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.08);
color: var(--accent-color);
opacity: 1;
border-color: var(--accent-color);
background: color-mix(in srgb, var(--accent-color), transparent 95%);
}
.tui-toggle.active {
background: rgba(255, 255, 255, 0.15);
background: var(--accent-color);
color: #fff;
border-color: rgba(255, 255, 255, 0.4);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.05);
opacity: 1;
border-color: var(--accent-color);
font-weight: 700;
box-shadow: 0 0 15px
color-mix(in srgb, var(--accent-color), transparent 80%);
}
.tui-toggle-label {
font-weight: 600;
letter-spacing: 0.5px;
display: flex;
}
.tui-toggle-label .abbr {
display: none;
}
@media (max-width: 1400px) {
.tui-toggle-label.has-abbr .full {
display: none;
}
.tui-toggle-label.has-abbr .abbr {
display: inline;
}
}
</style>