feat: Implement image import functionality, restructure control panel layout, and apply glassmorphism styling to controls.

This commit is contained in:
syntaxbullet
2026-02-09 23:16:26 +01:00
parent 8dae3578b1
commit 36cb793048
3 changed files with 252 additions and 98 deletions

View File

@@ -55,8 +55,11 @@ const { pathname } = Astro.url;
left: 0; left: 0;
width: 100%; width: 100%;
height: 24px; height: 24px;
background: #000; background: rgba(10, 10, 10, 0.8);
border-bottom: 1px solid var(--text-color); -webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -81,30 +84,32 @@ const { pathname } = Astro.url;
align-items: center; align-items: center;
color: var(--text-color); color: var(--text-color);
text-decoration: none; text-decoration: none;
border-right: 1px solid rgba(255, 103, 0, 0.2); border-right: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.1s; transition: all 0.2s ease;
} }
.nav-link:hover { .nav-link:hover {
background: var(--text-color); background: rgba(255, 255, 255, 0.1);
color: #000; color: #fff;
text-decoration: none; text-decoration: none;
} }
.nav-link:hover .nav-index { .nav-link:hover .nav-index {
color: #000; color: #fff;
opacity: 1; opacity: 1;
} }
.status-item.active { .status-item.active {
background: var(--text-color); background: rgba(255, 255, 255, 0.15);
color: #000; color: #fff;
font-weight: bold; font-weight: bold;
box-shadow: inset 0 -2px 0 var(--text-color);
} }
.status-item.brand { .status-item.brand {
background: rgba(255, 103, 0, 0.1); background: rgba(255, 255, 255, 0.05);
font-weight: 900; font-weight: 900;
letter-spacing: 1px;
} }
.nav-index { .nav-index {
@@ -122,7 +127,7 @@ const { pathname } = Astro.url;
.status-right .status-item { .status-right .status-item {
border-right: none; border-right: none;
border-left: 1px solid rgba(255, 103, 0, 0.2); border-left: 1px solid rgba(255, 255, 255, 0.1);
} }
.prefix { .prefix {
@@ -134,6 +139,7 @@ const { pathname } = Astro.url;
#system-status { #system-status {
color: #0f0; color: #0f0;
font-weight: bold; font-weight: bold;
text-shadow: 0 0 5px rgba(0, 255, 0, 0.5);
} }
</style> </style>

View File

@@ -174,64 +174,80 @@ import Tooltip from "../components/Tooltip.astro";
<!-- Divider --> <!-- Divider -->
<div class="control-panel-divider"></div> <div class="control-panel-divider"></div>
<!-- Actions Section --> <!-- Right Column: Actions & Export -->
<div class="control-panel-section actions-section"> <div class="control-col-right">
<div class="section-header">ACTIONS</div> <div class="control-panel-section actions-section">
<div class="actions-row"> <div class="section-header">ACTIONS</div>
<TuiButton <div class="actions-row">
id="btn-reset" <TuiButton
label="RESET" id="btn-reset"
shortcut="R" label="RESET"
title="Reset to Auto-detected Settings" shortcut="R"
description="Resets all sliders and toggles to their default values." title="Reset to Auto-detected Settings"
/> description="Resets all sliders and toggles to their default values."
/>
<TuiButton <TuiButton
id="btn-next" id="btn-next"
label="NEXT" label="NEXT"
shortcut="N" shortcut="N"
variant="primary" variant="primary"
title="Load Next Image" title="Load Next Image"
description="Discards current image and loads a new one from the queue." description="Discards current image and loads a new one from the queue."
/> />
<div <div
class="queue-display" class="queue-display"
data-tooltip-title="Buffered Images" data-tooltip-title="Buffered Images"
data-tooltip-desc="Number of images pre-loaded in background queue." 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
> >
<span class="queue-label">Q:</span>
<span id="val-queue" class="queue-value"
>0</span
>
</div>
</div> </div>
</div> </div>
</div>
<!-- Divider --> <!-- Horizontal Divider for inside column -->
<div class="control-panel-divider"></div> <div class="control-panel-divider-h"></div>
<!-- Export Section --> <div class="control-panel-section export-section">
<div class="control-panel-section export-section"> <div class="section-header">
<div class="section-header">EXPORT</div> IMPORT / EXPORT
<div class="actions-row"> </div>
<TuiButton <div class="actions-row">
id="btn-save-png" <!-- Hidden File Input -->
label="PNG" <input
title="Save as Image" type="file"
description="Download high-res PNG capture of the current view." id="file-upload"
/> accept="image/*"
<TuiButton style="display: none;"
id="btn-copy-text" />
label="TXT" <TuiButton
title="Save as Text" id="btn-import"
description="Download raw ASCII text file." label="IMP"
/> title="Import Image"
<TuiButton description="Upload your own image from your device."
id="btn-copy-html" />
label="HTML" <TuiButton
title="Save as HTML" id="btn-save-png"
description="Download colored HTML file." 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>
</div> </div>
@@ -405,6 +421,12 @@ import Tooltip from "../components/Tooltip.astro";
font-size: 1.5rem; font-size: 1.5rem;
display: none; display: none;
z-index: 10; z-index: 10;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
background: rgba(0, 0, 0, 0.6);
padding: 10px 20px;
border-radius: 4px;
backdrop-filter: blur(4px);
} }
/* FOREGROUND LAYER */ /* FOREGROUND LAYER */
@@ -474,6 +496,7 @@ import Tooltip from "../components/Tooltip.astro";
max-width: 400px; max-width: 400px;
background: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
padding: 5px; padding: 5px;
border-radius: 2px;
} }
/* Footer / Controls */ /* Footer / Controls */
@@ -484,25 +507,40 @@ import Tooltip from "../components/Tooltip.astro";
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
position: relative; position: relative;
z-index: 50; /* Ensure on top */
} }
#tui-controls { #tui-controls {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: stretch; /* Stretch to same height */
gap: 0; gap: 0;
background: rgba(0, 0, 0, 0.95);
border: 1px solid var(--text-color); /* Glassmorphism update */
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8); background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
max-width: 100%; max-width: 100%;
border-radius: 8px; /* Slightly rounded for premium feel */
overflow: hidden; /* For border radius */
} }
.control-panel-section { .control-panel-section {
padding: 12px 16px; padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 10px;
flex-shrink: 0;
}
.control-col-right {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px;
} }
.control-panel-divider { .control-panel-divider {
@@ -510,32 +548,47 @@ import Tooltip from "../components/Tooltip.astro";
background: linear-gradient( background: linear-gradient(
to bottom, to bottom,
transparent 0%, transparent 0%,
rgba(255, 103, 0, 0.3) 20%, rgba(255, 255, 255, 0.1) 20%,
rgba(255, 103, 0, 0.3) 80%, rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.1) 80%,
transparent 100% transparent 100%
); );
align-self: stretch; align-self: stretch;
} }
.control-panel-divider-h {
height: 1px;
width: 100%;
background: linear-gradient(
to right,
transparent 0%,
rgba(255, 255, 255, 0.1) 20%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.1) 80%,
transparent 100%
);
}
.section-header { .section-header {
font-size: 9px; font-size: 10px;
font-weight: bold; font-weight: 800;
opacity: 0.5; opacity: 0.6;
letter-spacing: 2px; letter-spacing: 2px;
margin-bottom: 4px; margin-bottom: 2px;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-color);
} }
.sliders-grid { .sliders-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 6px 16px; gap: 8px 20px;
} }
.toggles-row { .toggles-row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 6px; gap: 8px;
} }
.segments-row { .segments-row {
@@ -547,7 +600,8 @@ import Tooltip from "../components/Tooltip.astro";
.actions-row { .actions-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 8px;
flex-wrap: wrap;
} }
.queue-display { .queue-display {
@@ -557,7 +611,7 @@ import Tooltip from "../components/Tooltip.astro";
font-size: 11px; font-size: 11px;
opacity: 0.6; opacity: 0.6;
padding: 0 8px; padding: 0 8px;
border-left: 1px solid rgba(255, 103, 0, 0.2); border-left: 1px solid rgba(255, 255, 255, 0.2);
} }
.queue-label { .queue-label {
@@ -575,34 +629,34 @@ import Tooltip from "../components/Tooltip.astro";
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
gap: 12px; gap: 16px;
font-size: 10px; font-size: 11px;
opacity: 0.4; opacity: 0.5;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
.shortcuts-hint:hover { .shortcuts-hint:hover {
opacity: 0.7; opacity: 0.8;
} }
.shortcuts-hint span { .shortcuts-hint span {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 6px;
} }
.shortcuts-hint kbd { .shortcuts-hint kbd {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 16px; min-width: 18px;
height: 16px; height: 18px;
padding: 0 4px; padding: 0 5px;
font-size: 9px; font-size: 10px;
font-family: inherit; font-family: inherit;
border: 1px solid rgba(255, 103, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 2px; border-radius: 4px;
background: rgba(255, 103, 0, 0.05); background: rgba(255, 255, 255, 0.1);
} }
/* Legacy styles kept for compatibility */ /* Legacy styles kept for compatibility */
@@ -627,7 +681,7 @@ import Tooltip from "../components/Tooltip.astro";
.tui-btn:hover { .tui-btn:hover {
opacity: 1; opacity: 1;
border-color: var(--text-color); border-color: var(--text-color);
background: rgba(255, 103, 0, 0.1); background: rgba(255, 255, 255, 0.1);
} }
.tui-val { .tui-val {
@@ -648,20 +702,45 @@ import Tooltip from "../components/Tooltip.astro";
align-items: stretch; align-items: stretch;
} }
.control-col-right {
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
}
.control-col-right > .control-panel-section {
flex: 1;
}
/* New vertical alignment for right col on mobile */
.control-panel-divider-h {
width: 1px;
height: auto;
align-self: stretch;
background: linear-gradient(
to bottom,
transparent 0%,
rgba(255, 255, 255, 0.2) 20%,
rgba(255, 255, 255, 0.2) 80%,
transparent 100%
);
}
.control-panel-divider { .control-panel-divider {
width: 100%; width: 100%;
height: 1px; height: 1px;
background: linear-gradient( background: linear-gradient(
to right, to right,
transparent 0%, transparent 0%,
rgba(255, 103, 0, 0.3) 20%, rgba(255, 255, 255, 0.1) 20%,
rgba(255, 103, 0, 0.3) 80%, rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.1) 80%,
transparent 100% transparent 100%
); );
} }
.control-panel-section { .control-panel-section {
padding: 10px 14px; padding: 12px 16px;
} }
} }
@@ -670,6 +749,21 @@ import Tooltip from "../components/Tooltip.astro";
font-size: 3rem; font-size: 3rem;
} }
.control-col-right {
flex-direction: column;
}
.control-panel-divider-h {
width: 100%;
height: 1px;
background: linear-gradient(
to right,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
}
.controls-footer { .controls-footer {
padding: 1rem; padding: 1rem;
} }
@@ -691,11 +785,11 @@ import Tooltip from "../components/Tooltip.astro";
@media (max-width: 480px) { @media (max-width: 480px) {
.control-panel-section { .control-panel-section {
padding: 8px 10px; padding: 10px 12px;
} }
.section-header { .section-header {
font-size: 8px; font-size: 9px;
} }
} }
</style> </style>

View File

@@ -67,6 +67,7 @@ export class UIBindings {
this.setupKeyboard(); this.setupKeyboard();
this.setupZoom(); this.setupZoom();
this.setupResize(); this.setupResize();
this.setupImport();
// Periodic queue update // Periodic queue update
this.queueInterval = window.setInterval(() => this.updateQueueDisplay(), 1000); this.queueInterval = window.setInterval(() => this.updateQueueDisplay(), 1000);
@@ -142,13 +143,20 @@ export class UIBindings {
} }
// Cleanup Export Buttons // Cleanup Export Buttons
['btn-save-png', 'btn-copy-text', 'btn-copy-html'].forEach(id => { ['btn-save-png', 'btn-copy-text', 'btn-copy-html', 'btn-import'].forEach(id => {
const el = document.getElementById(id); const el = document.getElementById(id);
const handler = this.buttonHandlers.get(id); const handler = this.buttonHandlers.get(id);
if (el && handler) { if (el && handler) {
el.removeEventListener('click', handler); el.removeEventListener('click', handler);
} }
}); });
// Cleanup File Input
const fileInput = document.getElementById('file-upload');
const fileHandler = this.buttonHandlers.get('file-upload');
if (fileInput && fileHandler) {
fileInput.removeEventListener('change', fileHandler);
}
} }
// ============= Sliders ============= // ============= Sliders =============
@@ -412,6 +420,52 @@ export class UIBindings {
window.addEventListener('resize', this.resizeHandler); window.addEventListener('resize', this.resizeHandler);
} }
// ============= Import =============
private setupImport(): void {
const btnImport = document.getElementById('btn-import');
const fileInput = document.getElementById('file-upload') as HTMLInputElement;
if (btnImport && fileInput) {
// Button triggers file input
const btnHandler = (e: Event) => {
e.stopPropagation();
fileInput.click();
};
this.buttonHandlers.set('btn-import', btnHandler);
btnImport.addEventListener('click', btnHandler);
// File input change
const fileHandler = async (e: Event) => {
const files = (e.target as HTMLInputElement).files;
if (files && files.length > 0) {
const file = files[0];
const url = URL.createObjectURL(file);
// Reset value so same file can be selected again
fileInput.value = '';
try {
this.controller.showLoading("LOADING IMPORT...");
// Use empty suggestions for user imports unless we want to auto-detect?
// For now keep existing settings or use defaults.
// Let's pass empty object to respect current user settings or controller defaults.
this.controller.setCurrentImage(url, {});
this.updateUI();
await this.controller.generate();
this.controller.hideLoading();
} catch (err) {
console.error("Import failed:", err);
this.controller.hideLoading();
alert("Failed to load image. Please try another file.");
}
}
};
this.buttonHandlers.set('file-upload', fileHandler);
fileInput.addEventListener('change', fileHandler);
}
}
// ============= UI Sync ============= // ============= UI Sync =============
updateUI(): void { updateUI(): void {