Files
Dreckshub/index.html
2025-11-17 13:53:14 +01:00

544 lines
27 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dreckshub</title>
<link rel="stylesheet" href="styles.css">
<!-- Tighter vertical spacing for customisation options -->
<style>
/* predictable sizing */
*, *::before, *::after { box-sizing: border-box; }
/* very tight vertical spacing */
.customisation-grid {
display: flex;
flex-direction: column;
gap: 2px; /* minimal vertical gap */
width: 100%;
max-width: 720px;
}
.customisation-column {
display: flex;
flex-direction: column;
gap: 2px; /* minimal gap between rows */
width: 100%;
}
/* compact horizontal row: label | color wheel | reset button */
.customisation-row {
display: flex;
align-items: center;
gap: 3px; /* tight horizontal spacing */
padding: 1px 0; /* minimal vertical padding */
min-height: 26px; /* compact row height */
}
.customisation-row label {
flex: 0 0 auto;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
font-size: 13px;
line-height: 1;
}
/* tighten spacing inside the control group so wheel is very close to name */
.color-control {
display: inline-flex;
align-items: center;
gap: 2px; /* very tight */
flex: 0 0 auto;
}
/* rectangular swatch with rounded corners (replaces circular variant) */
input[type="color"]{
-webkit-appearance: none;
appearance: none;
width: 40px; /* slightly wider rectangle */
height: 26px;
padding: 0;
border: none; /* remove default box */
background: transparent;
cursor: pointer;
border-radius: 6px; /* rounded rectangle (not pill) */
}
/* WebKit browsers: remove wrapper padding & match rounded corners */
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
border-radius: 6px;
}
input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 6px;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12); /* subtle inset ring */
}
/* Firefox swatch styling with rounded corners */
input[type="color"]::-moz-color-swatch {
border: none;
border-radius: 6px;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12);
}
/* nicer focus state (matches rectangle radius) */
input[type="color"]:focus {
outline: 2px solid rgba(15,102,214,0.9); /* example accent */
outline-offset: 2px;
border-radius: 6px;
}
/* slider accent: use CSS variable so we can update from JS */
input[type="range"],
.vertical-slider {
accent-color: var(--accent-color, #0f66d6);
}
/* media query variant (keep sizes in sync with tightened layout) */
@media (max-width: 520px) {
.customisation-grid { gap: 2px; }
.customisation-column { gap: 2px; }
.customisation-row { gap: 3px; min-height: 24px; padding: 1px 0; }
input[type="color"] { width: 32px; height: 24px; border-radius:5px; }
input[type="color"]::-webkit-color-swatch-wrapper { border-radius:5px; }
input[type="color"]::-webkit-color-swatch { border-radius:5px; }
input[type="color"]::-moz-color-swatch { border-radius:5px; }
.reset-default { width: 26px; height: 26px; font-size:11px; }
.reset-default .reset-arrow { font-size:14px; } /* increased from 13px */
}
/* General settings: Device, COM Port and Auto-connect — normal weight */
.settings-left-column .settings-row label[for="device-select"],
.settings-left-column .settings-row label[for="com-port-select"],
.settings-left-column .settings-row label[for="autoconnect"] {
font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 13px;
font-weight: 400; /* normal */
letter-spacing: 0;
}
/* ensure selects and option text match (not bold) */
.settings-left-column .settings-row .settings-select,
#device-select,
#com-port-select,
.settings-left-column .settings-row .settings-select option {
font-family: inherit;
font-size: 13px;
font-weight: 400;
letter-spacing: 0;
}
/* keep checkbox sizing unchanged but use the same font metrics for adjacent label */
.settings-left-column .settings-row .settings-checkbox {
vertical-align: middle;
}
/* ensure potentiometer ring color can be overridden by JS and isn't forced to blue
include pseudo-elements so gradient/overlay layers are covered */
.pot-ring,
.pot-ring::before,
.pot-ring::after {
background: var(--pot-ring-color, #007bff) !important;
background-image: none !important;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12) !important;
border-radius: 50% !important;
}
/* Ghostwire button-grid: ensure first-row spacing below the first 4 items */
.device-content[data-device="makropad ghostwire"] .button-grid {
display: grid;
grid-template-columns: repeat(4, auto); /* 3 buttons + pot in first row */
column-gap: 8px;
align-items: center;
}
/* clear default margins on children, then add vertical gap under first row only */
.device-content[data-device="makropad ghostwire"] .button-grid > * {
margin: 0;
}
.device-content[data-device="makropad ghostwire"] .button-grid > *:nth-child(-n+4) {
margin-bottom: 12px; /* increased space under first row */
}
/* responsive fallback: collapse to 2 columns on narrow widths */
@media (max-width: 480px) {
.device-content[data-device="makropad ghostwire"] .button-grid {
grid-template-columns: repeat(2, auto);
}
.device-content[data-device="makropad ghostwire"] .button-grid > *:nth-child(-n+2) {
margin-bottom: 10px;
}
}
</style>
</head>
<body>
<div class="app-wrapper">
<aside class="sidebar">
<div class="sidebar-top">
<div class="logo" role="banner" aria-label="Dreckshub">
<span class="logo-text">
<span class="logo-base">Drecks</span><span class="logo-highlight">hub</span>
</span>
</div>
<nav class="tabs">
<button class="tab-button active" data-target="tab-1">Main</button>
<button class="tab-button" data-target="tab-2">Placeholder 1</button>
<button class="tab-button" data-target="tab-3">Sliders</button>
</nav>
</div>
<div class="sidebar-bottom">
<button id="settings-btn" class="settings-btn">⚙ Settings</button>
</div>
</aside>
<main class="content-area">
<section id="tab-1" class="tab-content active">
<!-- Makropad-specific content -->
<div class="device-content" data-device="makropad">
<h2>Makropad — Main</h2>
<p>Content for Makropad on the first tab.</p>
</div>
<!-- Makropad Ghostwire-specific content -->
<div class="device-content" data-device="makropad ghostwire" style="display:none;">
<h2>Makropad Ghostwire — Main</h2>
<p>Alternate content for Makropad Ghostwire on the first tab.</p>
</div>
</section>
<section id="tab-2" class="tab-content">
<!-- Makropad-specific two-column layout -->
<div class="device-content" data-device="makropad">
<div class="makropad-two-col" aria-hidden="false">
<div class="left-group">
<div class="grid-3cols">
<button class="grid-button">b</button>
<button class="grid-button">b</button>
<button class="grid-button">b</button>
</div>
<!-- spacer row (empty) to match the requested layout) -->
<div class="grid-3cols spacer-row">
<div></div><div></div><div></div>
</div>
<div class="grid-3cols">
<button class="grid-button">b</button>
<button class="grid-button">b</button>
<button class="grid-button">b</button>
</div>
<div class="grid-3cols">
<button class="grid-button">b</button>
<button class="grid-button">b</button>
<button class="grid-button">b</button>
</div>
</div>
<div class="vertical-sep" aria-hidden="true"></div>
<div class="right-group">
<div class="grid-3cols">
<button class="grid-button">b</button>
<button class="grid-button">b</button>
<button class="grid-button">b</button>
</div>
<div class="grid-3cols">
<button class="grid-button">b</button>
<button class="grid-button">b</button>
<button class="grid-button">b</button>
</div>
<div class="grid-3cols">
<button class="grid-button">b</button>
<button class="grid-button">b</button>
<button class="grid-button">b</button>
</div>
<div class="grid-3cols">
<button class="grid-button">b</button>
<button class="grid-button">b</button>
<button class="grid-button">b</button>
</div>
</div>
</div>
</div>
<!-- Makropad Ghostwire-specific grid -->
<div class="device-content" data-device="makropad ghostwire" style="display:none;">
<div class="button-grid" aria-hidden="false">
<!-- different arrangement / labels for Ghostwire -->
<button class="grid-button">1</button>
<button class="grid-button">2</button>
<button class="grid-button">3</button>
<div class="potentiometer" role="img" aria-label="ghost pot" tabindex="-1">
<div class="pot-ring" style="--deg: 90deg;"></div>
<div class="pot-inner"><span class="pot-value">50</span></div>
</div>
<!-- remaining placeholders -->
<button class="grid-button">4</button>
<button class="grid-button">5</button>
<button class="grid-button">6</button>
<button class="grid-button">7</button>
<button class="grid-button">8</button>
<button class="grid-button">9</button>
<button class="grid-button">10</button>
<button class="grid-button">11</button>
<button class="grid-button">12</button>
<button class="grid-button">13</button>
<button class="grid-button">14</button>
<button class="grid-button">15</button>
</div>
</div>
</section>
<section id="tab-3" class="tab-content">
<div class="main-layout">
<!-- Sliders Panel for vertical sliders -->
<div class="sliders-panel">
<div class="slider-group">
<div class="slider-row">
<div class="slider-tag-generator">
<input type="text" id="tag-input-slider0" class="tag-input" placeholder="Enter Program Name">
<div id="tag-container-slider0" class="tag-container"></div>
</div>
<div class="slider-controls">
<input type="range" min="0" max="100" value="0" id="slider0" class="vertical-slider">
<span id="slider0-value-bubble" class="value-bubble" aria-hidden="true">50</span>
<button class="slider-btn" id="slider0-btn"></button>
</div>
<div class="slider-separator" aria-hidden="true"></div>
</div>
</div>
<div class="slider-group">
<div class="slider-row">
<div class="slider-tag-generator">
<input type="text" id="tag-input-slider1" class="tag-input" placeholder="Enter Program Name">
<div id="tag-container-slider1" class="tag-container"></div>
</div>
<div class="slider-controls">
<input type="range" min="0" max="100" value="0" id="slider1" class="vertical-slider">
<span id="slider1-value-bubble" class="value-bubble" aria-hidden="true">0</span>
<button class="slider-btn" id="slider1-btn"></button>
</div>
<div class="slider-separator" aria-hidden="true"></div>
</div>
</div>
<div class="slider-group">
<div class="slider-row">
<div class="slider-tag-generator">
<input type="text" id="tag-input-slider2" class="tag-input" placeholder="Enter Program Name">
<div id="tag-container-slider2" class="tag-container"></div>
</div>
<div class="slider-controls">
<input type="range" min="0" max="100" value="0" id="slider2" class="vertical-slider">
<span id="slider2-value-bubble" class="value-bubble" aria-hidden="true">0</span>
<button class="slider-btn" id="slider2-btn"></button>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Simple settings panel toggled by settings button -->
<div id="settings-overlay" class="settings-overlay" aria-hidden="true">
<div class="settings-window" role="dialog" aria-modal="true" aria-labelledby="settings-title">
<div class="settings-topbar">
<div id="settings-title" class="settings-title">Settings</div>
<nav class="settings-tabs">
<button class="settings-tab-button active" data-target="settings-tab-1">General</button>
<button class="settings-tab-button" data-target="settings-tab-2">Customisation</button>
<button class="settings-tab-button" data-target="settings-tab-3">Placeholder B</button>
</nav>
<button class="settings-close" id="close-settings"></button>
</div>
<div class="settings-content">
<section id="settings-tab-1" class="settings-tab-content active">
<!-- left column: keep both dropdowns on the left stacked -->
<div class="settings-left-column">
<div class="settings-row">
<label for="device-select">Device</label>
<select id="device-select" class="settings-select" aria-label="Device selection">
<option value="Makropad">Makropad</option>
<option value="Makropad Ghostwire">Makropad Ghostwire</option>
</select>
</div>
<div class="settings-row" style="margin-top:8px;">
<label for="com-port-select">COM Port</label>
<select id="com-port-select" class="settings-select" aria-label="COM port selection">
<option value="">(none)</option>
</select>
<button id="com-refresh-btn" class="settings-close" title="Refresh COM ports"></button>
<!-- Connect / Disconnect controls -->
<button id="connect-com-btn" class="assign-btn connect-state" style="margin-left:8px;">Connect</button>
</div>
<!-- new: autoconnect checkbox under the two dropdowns -->
<div class="settings-row" style="margin-top:8px;">
<label for="autoconnect">Auto-connect</label>
<input type="checkbox" id="autoconnect" class="settings-checkbox" />
</div>
</div>
<!-- right column (can hold extra controls later) -->
<div class="settings-right-column">
<div class="settings-checkbox-row">
<label class="checkbox-label">
<input type="checkbox" id="startup-with-system" class="settings-checkbox" />
Startup with system
</label>
</div>
<div class="settings-checkbox-row" style="margin-top:8px;">
<label class="checkbox-label">
<input type="checkbox" id="close-to-tray" class="settings-checkbox" />
Close to tray
</label>
</div>
</div>
</section>
<section id="settings-tab-2" class="settings-tab-content">
<div class="customisation-grid">
<!-- new layout: 3 columns for controls -->
<div class="customisation-column">
<div class="customisation-row">
<label for="cust-bg">Background</label>
<div class="color-control">
<input type="color" id="cust-bg" name="cust-bg" value="#0d0d0f" data-default="#0d0d0f" />
<button type="button" class="reset-default" data-target="cust-bg" title="Reset to default"><span class="reset-arrow">↩︎</span></button>
</div>
</div>
<div class="customisation-row">
<label for="cust-sidebar">Sidebar</label>
<div class="color-control">
<input type="color" id="cust-sidebar" name="cust-sidebar" value="#111111" data-default="#111111" />
<button type="button" class="reset-default" data-target="cust-sidebar" title="Reset to default"><span class="reset-arrow">↩︎</span></button>
</div>
</div>
</div>
<div class="customisation-column">
<div class="customisation-row">
<label for="cust-button">Button (grid)</label>
<div class="color-control">
<input type="color" id="cust-button" name="cust-button" value="#1b1b1b" data-default="#1b1b1b" />
<button type="button" class="reset-default" data-target="cust-button" title="Reset to default"><span class="reset-arrow">↩︎</span></button>
</div>
</div>
<div class="customisation-row">
<label for="cust-pot">Potentiometer ring</label>
<div class="color-control">
<input type="color" id="cust-pot" name="cust-pot" value="#007bff" data-default="#007bff" />
<button type="button" class="reset-default" data-target="cust-pot" title="Reset to default"><span class="reset-arrow">↩︎</span></button>
</div>
</div>
</div>
<div class="customisation-column">
<div class="customisation-row">
<label for="cust-accent">Accent (links / highlights)</label>
<div class="color-control">
<input type="color" id="cust-accent" name="cust-accent" value="#0f66d6" data-default="#0f66d6" />
<button type="button" class="reset-default" data-target="cust-accent" title="Reset to default"><span class="reset-arrow">↩︎</span></button>
</div>
</div>
</div>
</div>
</section>
<section id="settings-tab-3" class="settings-tab-content">
<h4>Placeholder B</h4>
<p>Content for placeholder B.</p>
</section>
</div>
</div>
</div>
</main>
</div>
<!-- wiring for colour inputs + reset buttons (applies cust-pot to .pot-ring elements) -->
<script>
(function () {
function applyColorToPotRing(color) {
// set on :root so pseudoelements that read the CSS var update
document.documentElement.style.setProperty('--pot-ring-color', color);
document.querySelectorAll('.pot-ring').forEach(el => {
// also set the custom property on the element itself (higher specificity)
el.style.setProperty('--pot-ring-color', color, '');
// clear any background-image and force a plain background color on the element
el.style.setProperty('background-image', 'none', 'important');
el.style.setProperty('background-color', color, 'important');
el.style.setProperty('background', color, 'important');
// ensure inline box-shadow as fallback
el.style.boxShadow = 'inset 0 0 0 1px rgba(0,0,0,0.12)';
// small toggle to force paint/reflow for stubborn cases
el.dataset.potRepaint = '1';
window.requestAnimationFrame(() => { delete el.dataset.potRepaint; });
});
}
function applyAccentColor(color) {
// set on :root so all inputs using var(--accent-color) update
document.documentElement.style.setProperty('--accent-color', color);
// also ensure existing range inputs get the accent color (fallback)
document.querySelectorAll('input[type="range"]').forEach(r => {
r.style.setProperty('accent-color', color);
});
}
function applyInputValue(input) {
if (!input) return;
const id = input.id;
const val = input.value;
if (id === 'cust-pot') {
applyColorToPotRing(val);
}
// add more mappings here if you want other inputs to affect other UI parts
}
// wire color inputs that include data-default
document.addEventListener('DOMContentLoaded', () => {
// initialize all color inputs
document.querySelectorAll('input[type="color"][id]').forEach(input => {
applyInputValue(input);
// also initialize accent if this is the cust-accent input
if (input.id === 'cust-accent') applyAccentColor(input.value);
input.addEventListener('input', () => applyInputValue(input));
input.addEventListener('change', () => applyInputValue(input));
});
// ensure range inputs reflect the accent variable initially
const custAccent = document.getElementById('cust-accent');
if (custAccent) applyAccentColor(custAccent.value);
// wire reset buttons
document.querySelectorAll('.reset-default[data-target]').forEach(btn => {
btn.addEventListener('click', (e) => {
const targetId = btn.getAttribute('data-target');
const input = document.getElementById(targetId);
if (!input) return;
const def = input.getAttribute('data-default') || '#000000';
input.value = def;
// trigger input/change handlers
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
});
});
// reapply cust-pot when new .pot-ring elements appear
const obs = new MutationObserver(() => {
const custPot = document.getElementById('cust-pot');
if (custPot) applyInputValue(custPot);
});
obs.observe(document.body, { childList: true, subtree: true });
});
})();
</script>
<script src="renderer.js"></script>
</body>
</html>