diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eda3ead..582458ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +- All New HTML Files! ### Hardware diff --git a/data/bluetoothscanner.html b/data/bluetoothscanner.html index 575663f3..0715ff43 100644 --- a/data/bluetoothscanner.html +++ b/data/bluetoothscanner.html @@ -1,238 +1,177 @@ - - + - + + SmartSpin2k Bluetooth Scanner - - + - -
- http://github.com/doudar/SmartSpin2k -

Main Index

-

-
Loading
-

-

Select Bluetooth Devices

-

-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

-
-

-
-

-
-

-
-

-
-

-
-
Power Correction FactorIncrease or decrease - to correct the power transmitted from your bike.
-
-
- 1x -
- - - -


-
- -
-

-
- - -

Page Help

-
-

- - - - - \ No newline at end of file + function loadCss() { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'style.css'; + document.head.appendChild(link); + } + + window.addEventListener('load', () => { + setTimeout(loadCss, 100); + requestConfigValues(); + + // Retry loading if values are still default + setInterval(() => { + if (document.getElementById('connectedPowerMeter').textContent === 'loading') { + requestConfigValues(); + } + }, 1000); + }); + + + diff --git a/data/btsimulator.html b/data/btsimulator.html index c362cfad..c65db1c2 100644 --- a/data/btsimulator.html +++ b/data/btsimulator.html @@ -1,327 +1,399 @@ - - + - - SmartSpin2k Web Server - -
-

-
Loading
-

- http://github.com/doudar/SmartSpin2k -

Main Index

-

BLE Device Simulator

- -

Sim Heart Rate

-

-

-

- -

Sim Power Output

-
-

- -   - - - - -

-

- -

-
-

- -

- -

Sim CAD Output

-
-

- -   - - - - -

-

- -

+
+
+ +
+ +
+ +

BLE Simulator

+
+
+ +

Simulate Heart Rate

+ +
+ +
+ -- BPM +
+
+
+ + +
+

Simulate Power Output

+ +
+ +
+ -- W + +
+
+
+ + +
+

Simulate Cadence

+ +
+ +
+ -- RPM + +
+
+
+ + +
+

Simulate ERG Mode

+ +
+ +
+ -- W +
+ +
+
+
+
+
-

- -

- -

Trainer Simulator

-

Enable ERG

-

- -

-

ERG Target Watts

-

-

-

- -

- - + \ No newline at end of file diff --git a/data/develop.html b/data/develop.html index da2d3955..36e617cf 100644 --- a/data/develop.html +++ b/data/develop.html @@ -1,48 +1,103 @@ - - + - + + + + + - - \ No newline at end of file + window.addEventListener('load', () => { + setTimeout(loadCss, 100); + }); + + + diff --git a/data/hrtowatts.html b/data/hrtowatts.html index bcf94fe3..c0d7ae26 100644 --- a/data/hrtowatts.html +++ b/data/hrtowatts.html @@ -1,126 +1,170 @@ - - - + - - SmartSpin2k Web Server - + + + SmartSpin2k Heart Rate to Power + - -
- http://github.com/doudar/SmartSpin2k -

Main Index

-

-
Loading
-

-

Physical Working Capacity

-

-

For the most accurate power estimation when not using a power meter, please submit the following information. -
Note: You can get estimated watts from any outdoor ride recorded in Strava with heart rate information. -

-
- - - - - - - - - - - - - - - - - - - - - - -
-

Easy Session Average HRSession 1 HR

Average - Heartrate over an easy 1 Hour course.

-
-

Easy Session Average PowerAverage Power over an easy 1 hour - course in watts.

-
-
-

Hard Session Average HRAverage HR over a hard 1 hour - course.

-
-
-

Hard Session Average PowerAverage Power over a hard 1 hour - course in watts.

-
-
-

HR->PWRAutomatically calculate watts using - heart rate when power meter not connected

-
-

- -

Page Help

-
-

- +
+
+ +
- - - \ No newline at end of file + + fetch('/send_settings?' + params.toString(), { method: 'GET' }) + .then(() => { + showSaveStatus('Settings saved successfully', 'success'); + }) + .catch(error => { + showSaveStatus('Failed to save settings', 'error'); + console.error('Error:', error); + }); + }); + + function showSaveStatus(message, type = 'info') { + saveStatus.textContent = message; + saveStatus.className = `status-message ${type}`; + setTimeout(() => { + saveStatus.textContent = ''; + saveStatus.className = 'status-message'; + }, 3000); + } + + function requestConfigValues() { + fetch('/PWCJSON') + .then(response => response.json()) + .then(data => { + document.getElementById('session1HR').value = data.session1HR; + document.getElementById('session1Pwr').value = data.session1Pwr; + document.getElementById('session2HR').value = data.session2HR; + document.getElementById('session2Pwr').value = data.session2Pwr; + document.getElementById('hr2Pwr').checked = data.hr2Pwr; + document.getElementById('loadingWatermark')?.remove(); + }) + .catch(error => console.error('Error:', error)); + } + + function loadCss() { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'style.css'; + document.head.appendChild(link); + } + + window.addEventListener('load', () => { + setTimeout(loadCss, 100); + requestConfigValues(); + + // Retry loading if values are still default + setInterval(() => { + if (document.getElementById('session1HR').value === '0') { + requestConfigValues(); + } + }, 1000); + }); + + + diff --git a/data/index.html b/data/index.html index 516e25c6..aaee8a58 100644 --- a/data/index.html +++ b/data/index.html @@ -1,67 +1,139 @@ - - + - - - SmartSpin2k Web Server - + + + SmartSpin2k + -
http://github.com/doudar/SmartSpin2k -

SmartSpin2k

-

-

Web Shifter

-

Heartrate to Watts Setup

-

Settings

-

Bluetooth Scanner

-

Developer Tools

-

SS2K Help

-

-
+
+
+ +
-

- +
+

SmartSpin2k

+ +
+ +
⚙️
+
+

Web Shifter

+

Control gear shifting manually

+
    +
  • Manual gear control
  • +
  • Real-time shifting
  • +
+
+
+
- + + + diff --git a/data/settings.html b/data/settings.html index bfdb371a..c9bd5368 100644 --- a/data/settings.html +++ b/data/settings.html @@ -1,380 +1,387 @@ - - + - - SmartSpin2k Web Server - + + + SmartSpin2k Settings + - -
- http://github.com/doudar/SmartSpin2k -

Main Index

-

-
Loading
-

-

Settings

-

-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

- SSID - - WiFi network name. -
- If it doesn't exist I will create a network with this name. -
-

-
- -
-

- Password - - Password for the WiFi network. - -

-
- -

- Show - - Show the password for the WiFi network. - -

-
-

- MDNS Name - - DNS Name the device will use on the network. - -

-
-
-

- Sim Mode
Shift Amount - - Amount to move stepper per gear shift.
Try to target ~30watt changes. -
-

-
-
- 0 -
- - - -
-

- Sim Mode
Incline Multiplier - - Increase to make incline changes more noticeable. -

- Adjust until hills feel realistic. -
-

-
-
- 0x -
- - - - -
-

ERG Mode
Sensitivity - - Increase to make ERG Mode more aggressive. - -

-
-
- 1.0 -
- - - -
-

Min Bike
Brake Watts - - Set the minimum watts until stepper stops.
0 disables check. -
-

-
-
- 0W -
- - - -
-

Max Bike
Brake Watts - - Set the most watts you've ever seen your bike absorb while you're pedaling.
0 disables check. -
-

-
-
- 0W -
- - - -
-

Stepper Motor
Power - - Amount in milli amps for stepper motor -

- Set to the minimum required to move knob smoothly -
-

-
-
- 0ma -
- - - -
-

Stepper StealthChop - - Make stepper silent at expense of torque - -

-
- -
-

Automatic Updates - - Check for new firmware on boot? - -

-
- -
-

Stepper Motor
Direction - - Change Stepper Direction - -

-
- -
-

Shifter DirectionChange Shifter Direction

-
- -
-

Enable UDP LoggingSending log-messages via UDP Port - 10.000

-
- -
- -
-

-
- -
-

-

Page Help

-

-
- -

-
- - - \ No newline at end of file + function showSaveStatus(message, type = 'info') { + saveStatus.textContent = message; + saveStatus.className = `status-message ${type}`; + setTimeout(() => { + saveStatus.textContent = ''; + saveStatus.className = 'status-message'; + }, 3000); + } + + function startConfigUpdate() { + setTimeout(() => { + if (document.getElementById('ssid').value === 'loading') { + requestConfigValues(); + } + }, 1500); + } + + function requestConfigValues() { + fetch('/configJSON') + .then(response => response.json()) + .then(data => { + Object.entries(data).forEach(([key, value]) => { + const element = document.getElementById(key); + if (element) { + if (element.type === 'checkbox') { + element.checked = !!value; + } else { + element.value = value; + const valueElement = document.getElementById(key + 'Value'); + if (valueElement) updateSlider(value, valueElement); + } + } + }); + document.getElementById('loadingWatermark')?.remove(); + }) + .catch(() => startConfigUpdate()); + } + + function updateSlider(value, valueElement) { + valueElement.textContent = value; + } + + function updateSliderAndSend(element, valueElement) { + updateSlider(element.value, valueElement); + sendSetting(element); + } + + function clickStep(element, direction) { + const step = parseFloat(element.step); + const newValue = parseFloat(element.value) + (direction === '+' ? step : -step); + element.value = newValue; + updateSliderAndSend(element, document.getElementById(element.name + 'Value')); + } + + function toggleShowPassword() { + const input = document.getElementById('password'); + input.type = input.type === 'password' ? 'text' : 'password'; + } + + function sendSetting(element) { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + const params = new URLSearchParams(); + + if (element.type === 'checkbox') { + if (element.checked) params.append(element.name, 'true'); + } else { + params.append(element.name, element.value); + } + + ['stealthChop', 'autoUpdate', 'stepperDir', 'shifterDir', 'udpLogEnabled'].forEach(id => { + if (document.getElementById(id).checked) { + params.append(id, 'true'); + } + }); + + fetch('/send_settings?' + params.toString(), { method: 'GET' }) + .then(() => showSaveStatus('Settings saved', 'success')) + .catch(error => showSaveStatus('Failed to save settings', 'error')); + }, 300); + } + + function loadCss() { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'style.css'; + document.head.appendChild(link); + } + + window.addEventListener('load', () => { + setTimeout(loadCss, 100); + startConfigUpdate(); + }); + + + diff --git a/data/shift.html b/data/shift.html index 079fc13a..9afc24e6 100644 --- a/data/shift.html +++ b/data/shift.html @@ -1,93 +1,98 @@ - - - + - - SmartSpin2k Web Server - + + + SmartSpin2k Web Shifter + - -
-

-
Loading
-

- http://github.com/doudar/SmartSpin2k -

Main Index

-

Shift

+
+
+ +
-

-

-

-

- Current Gear -
-

-
- - + function loadCss() { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'style.css'; + document.head.appendChild(link); + } - \ No newline at end of file + window.addEventListener('load', () => { + setTimeout(loadCss, 100); + setTimeout(requestConfigValues, 500); + startUpdate(); + }); + + + diff --git a/data/status.html b/data/status.html index 067dac5b..c127db7d 100644 --- a/data/status.html +++ b/data/status.html @@ -1,260 +1,270 @@ - + - - SmartSpin2k Web Server - -
- http://github.com/doudar/SmartSpin2k -

Main Index

-

Status

-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

-
-

-
-

-
-

-
-

-
-

-
- - -

-
-

-
- -

-
-

-
-

-
- -

-
-
- - - - - - - - - - -
-

Debugging Info:

-
-
- - -
-
-
Loading
-
-
-

- +
+
+ +
- + function loadCss() { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'style.css'; + document.head.appendChild(link); + } + + window.addEventListener('load', () => { + setTimeout(loadCss, 100); + requestConfigValues(); + setupLogging(); + setTimeout(requestRuntimeValues, 200); + startUpdate(); + }); + + document.getElementById('saveLogButton').addEventListener('click', () => { + const debugElement = document.getElementById('debug'); + logContent = debugElement.textContent; + logContent = logContent.replace(/(\[\s*\d+\])/g, '\n$1'); + const blob = new Blob([logContent], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'debug_log.txt'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }); + + + \ No newline at end of file diff --git a/data/streamfit.html b/data/streamfit.html index 72138f73..fe4b299d 100644 --- a/data/streamfit.html +++ b/data/streamfit.html @@ -1,34 +1,94 @@ - - - - SmartSpin2k Web Server + + + SmartSpin2k StreamFit + - -
- http://github.com/doudar/SmartSpin2k -

Main Index

-

StreamFit

- - - - -

-

-
REQUIRES INTERNET CONNECTION
-
-
-
-

-

- -

- -
- - - - \ No newline at end of file +
+
+ +
+ +
+

StreamFit

+ +
+
+

Upload a .fit file to simulate a recorded workout.

+

Requires internet connection for file processing.

+
+ +
+ + +
REQUIRES INTERNET CONNECTION
+
+ +
+
+
❤️
+
Heart Rate
+
--
+
BPM
+
+ +
+
+
Power
+
--
+
W
+
+ +
+
🔄
+
Cadence
+
--
+
RPM
+
+
+ +
+ +
+
+ + +
+
+ + + + + diff --git a/data/style.css b/data/style.css index d5e8b2a6..61690d36 100644 --- a/data/style.css +++ b/data/style.css @@ -1,237 +1,891 @@ -html { - font-family: sans-serif; - display: inline-block; - margin: 5px auto; - text-align: center; - background-color: #03245c; - line-height: 1em; -} - -label { - font-size: medium; +header, +nav { + display: flex; + justify-content: space-between; + padding: 1rem 0; +} +.brand, +.menu-item, +html, +nav a { + color: #fff; +} +.brand, +h1 { + font-weight: 700; +} +.menu-item, +.status-group { + background: rgba(3, 37, 76, 0.6); +} +.brand, +.gear-display, +.gear-value { + text-align: center; +} +.debug-console, +.gear-display, +.status-item input { + box-sizing: border-box; + width: 100%; +} +.dev-tool-card, +.menu-item, +a, +nav a { + text-decoration: none; +} +.switch, +.tooltip { + position: relative; } - -div { - font-size: medium; +html { + font-family: system-ui, -apple-system, sans-serif; + line-height: 1.4; + background: #03245c; + -webkit-text-size-adjust: 100%; } +body { + margin: 0; + min-height: 100vh; + background: #1167b1; +} +.page-container { + max-width: 1200px; + margin: 0 auto; + padding: 1rem; +} +header { + align-items: center; +} +.brand { + font-size: 2.5rem; + text-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); + margin: 1rem 0; + width: 100%; + letter-spacing: 1px; +} +nav { + align-items: center; + width: 100%; +} +.card-header, +.debug-header { + justify-content: space-between; +} +nav a { + padding: 0.5rem; +} +nav a:hover { + text-decoration: underline; +} +.menu-grid { + display: grid; + gap: 1.5rem; + padding: 2rem 0; + max-width: 800px; + margin: 0 auto; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); +} +.menu-item { + border-radius: 12px; + padding: 1.5rem; + transition: 0.3s; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + display: block; +} +.shift-button, +.shifter-container, -a { - color: #000000; +.dev-tool-card:hover, +.menu-item:hover { + background: rgba(42, 157, 244, 0.15); + border-color: rgba(42, 157, 244, 0.5); + transform: translateY(-2px); + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25); +} +.menu-content { + padding: 0.5rem; +} +.menu-content h2 { + margin: 0 0 0.75rem; + font-size: 1.3rem; + font-weight: 600; + color: #2a9df4; +} +.menu-content p { + margin: 0; + opacity: 0.9; + font-size: 1rem; + line-height: 1.5; + color: #fff; +} +a:hover, +h1, +h2 { + color: #2a9df4; } -a:visited { - color: #000000; +h1, +h2 { + margin: 0 0 1rem; } h1 { - color: #03245c; - padding: 0.5rem; - line-height: 1em; + font-size: 2rem; } h2 { - color: #000000; - font-size: 1.5rem; - font-weight: bold; + font-size: 1.2rem; + font-weight: 600; } p { - font-size: 1rem; -} -.button { - display: inline-block; - background-color: #2a9df4; - border: line; - border-radius: 4px; - color: #d0efff; - padding: 10px 40px; - text-decoration: none; - font-size: 20px; - margin: 0px; - cursor: pointer; -} -.button2 { - background-color: #f44336; - padding: 10px 35px; + margin: 0.5rem 0; + line-height: 1.6; } -.switch { - position: relative; - display: inline-block; - width: 80px; - height: 40px; +a { + color: #fff; + transition: color 0.2s; +} +.status-group { + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1.5rem; + border: 1px solid rgba(255, 255, 255, 0.1); +} +.device-status h2, +.status-group h2 { + margin: 0 0 1.5rem; + color: #2a9df4; + font-size: 1.2rem; + border-bottom: 1px solid rgba(42, 157, 244, 0.3); + padding-bottom: 0.5rem; +} +.follow-toggle, +.status-item label { + color: rgba(255, 255, 255, 0.7); + font-size: 0.9rem; +} +.status-grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); +} +.status-item { + background: rgba(0, 0, 0, 0.2); + padding: 1rem; + border-radius: 8px; +} +.status-item label { + display: block; + margin-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.status-item input { + padding: 0.75rem; + background: rgba(3, 37, 76, 0.6); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + color: #2a9df4; + font-size: 1.1rem; + font-weight: 500; + text-align: center; +} +.debug-section { + background: rgba(3, 37, 76, 0.6); + border-radius: 12px; + padding: 1.5rem; + margin-top: 2rem; +} +.debug-header { + display: flex; + align-items: center; + margin-bottom: 1rem; +} +.debug-header h2 { + margin: 0; + color: #2a9df4; + font-size: 1.2rem; +} +.follow-toggle { + display: flex; + align-items: center; + gap: 0.5rem; +} +.debug-console { + margin: 0; + padding: 1rem; + background-color: #000; + background-image: radial-gradient(rgba(0, 150, 0, 0.75), #000 120%); + height: 40vh; + resize: both; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: #fff; + font-family: Inconsolata, monospace; + font-size: 1.1rem; + line-height: 1.4; + overflow: auto; + text-shadow: 0 0 4px rgba(200, 200, 200, 0.5); } + +.shifter-container { + max-width: 600px; + margin: 2rem auto; + padding: 2rem; + background: rgba(3, 37, 76, 0.6); + border-radius: 12px; +} +.shift-controls { + display: flex; + flex-direction: column; + gap: 2rem; + align-items: center; +} +.shift-button { + width: 100%; + height: 80px; + border: none; + border-radius: 8px; + color: #fff; + font-size: 1.2rem; + font-weight: 600; + cursor: pointer; + transition: 0.2s; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 1rem; +} +.scan-button:active, +.shift-button.active, +.shift-button:active { + transform: translateY(1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} +.device-select:focus, +.number-input:focus, +input:focus + .slider { + box-shadow: 0 0 0 3px rgba(42, 157, 244, 0.4); +} +.shift-up { + background: linear-gradient(to bottom, #2ecc71, #27ae60); +} +.shift-up:hover { + background: linear-gradient(to bottom, #27ae60, #219a52); +} +.shift-down { + background: linear-gradient(to bottom, #e74c3c, #c0392b); +} +.shift-down:hover { + background: linear-gradient(to bottom, #c0392b, #a93224); +} +.shift-arrow { + font-size: 1.5rem; + line-height: 1; +} +.shift-label { + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 1px; +} +.metric-label, +.reset-defaults-button { + text-transform: uppercase; + letter-spacing: .5px; +} +.gear-display { + background: rgba(0, 0, 0, 0.2); + padding: 2rem; + border-radius: 8px; +} +.gear-label { + display: block; + font-size: 1.2rem; + margin-bottom: 1rem; + color: rgba(255, 255, 255, 0.9); +} +.gear-value { + font-size: 2.5rem; + font-weight: 700; + color: #2a9df4; + background: 0 0; + border: none; + width: 100%; + padding: 0; +} +.input-group { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.5rem; + background: rgba(0, 0, 0, 0.2); + padding: 2em; + border-radius: 4px; +} +.number-input { + width: 120px; + padding: 0rem; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + background: rgba(255, 255, 255, 0.1); + color: #fff; + font-size: 1.1rem; + text-align: center; + transition: .3s; +} +.unit, +.value-display { + font-weight: 500; + color: #2a9df4; +} +.device-select:focus, +.number-input:focus { + outline: 0; + border-color: #2a9df4; +} +.number-input::-webkit-inner-spin-button, +.number-input::-webkit-outer-spin-button { + opacity: 1; +} +.unit { + min-width: 40px; +} +.toggle-group { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} +.settings-grid { + display: grid; + gap: 2rem; + padding: 1rem 0; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + max-width: 100%; + margin: 0 auto; +} +.settings-section { + background: rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 1.5rem; + margin-bottom: 0.5rem; +} +.setting-group { + margin-bottom: 1.5rem; + background: rgba(3, 37, 76, 0.6); + border-radius: 6px; + padding: 1rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.1); +} +.setting-group input[type="password"], +.setting-group input[type="text"] { + width: 100%; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.219); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + color: #fff; + font-size: 1rem; + box-sizing: border-box; +} +.slider-group { + display: flex; + align-items: center; + gap: 1rem; + min-height: 60px; + padding: 1rem; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + justify-content: center; +} +.slider-container { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + max-width: 400px; + margin: 0 auto; +} +.slider-container input[type="range"] { + width: 100%; +} +.value-display { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 5.5rem; +} +.value-display span { + margin-left: 20px; +} +.adjust-button { + width: 50px; + height: 50px; + border: none; + background: #03254c; + color: #fff; + border-radius: 50%; + cursor: pointer; + font-size: 1.25rem; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; + flex-shrink: 0; +} +.adjust-button:hover, +input:checked + .slider { + background: #2a9df4; +} +.button-group { + display: flex; + gap: 1rem; + margin-top: 2rem; + justify-content: flex-end; +} +.reset-defaults-button { + font-size: 1.1em; + padding: 1rem 2rem; + border: 2px solid rgba(255, 255, 255, 0.2); + font-weight: 600; + animation: 2s infinite pulse; +} +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.4); + } + 70% { + box-shadow: 0 0 0 10px rgba(220, 53, 69, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); + } +} +.device-status { + background: rgba(3, 37, 76, 0.8); + border-radius: 8px; + padding: 1.5rem; + margin-bottom: 2rem; +} +.status-grid { + display: grid; + gap: 1.5rem; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); +} +.status-item { + display: flex; + flex-direction: column; + gap: 0.5rem; + font-weight: 500; + color: #2a9df4; +} +.device-select { + width: 100%; + padding: 0.75rem; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + background: rgba(255, 255, 255, 0.1); + color: #fff; + font-size: 1rem; + cursor: pointer; + transition: 0.2s; +} +.device-select:hover { + background: rgba(255, 255, 255, 0.15); +} +.device-select option { + background: #03245c; + color: #fff; + padding: 0.5rem; +} +.scan-section { + margin-top: 3rem; + text-align: center; + padding: 2rem; + background: rgba(3, 37, 76, 0.6); + border-radius: 8px; +} +.scan-button { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + width: 100%; + max-width: 300px; + margin: 0 auto; + padding: 1rem; + background: linear-gradient(to right, #2a9df4, #1b8fe3); + border: none; + border-radius: 8px; + color: #fff; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + transition: 0.3s; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} +.dev-tool-card, +.upload-container { + background: rgba(3, 37, 76, 0.6); + border-radius: 12px; +} +.scan-button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); + background: linear-gradient(to right, #1b8fe3, #0d7ac9); +} +.scan-icon { + font-size: 1.5rem; + animation: 2s linear infinite spin; + display: inline-block; +} +@keyframes spin { + from { + transform: rotate(0); + } + to { + transform: rotate(360deg); + } +} +.scan-note { + margin-top: 1.5rem; + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.8); + line-height: 1.6; +} +.scan-note em { + color: #2a9df4; + font-style: normal; +} +.upload-container { + text-align: center; + padding: 2rem; + margin-bottom: 2rem; +} +.file-upload { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + padding: 1rem; + border: 2px dashed rgba(255, 255, 255, 0.2); + border-radius: 8px; + cursor: pointer; + transition: 0.3s; +} +.file-upload:hover { + border-color: #2a9df4; + background: rgba(42, 157, 244, 0.1); +} +.upload-icon { + font-size: 2.5rem; + color: #2a9df4; +} +.file-input, .switch input { - display: none; -} -.slider { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - transition: 0.4s; - background-color: #03254c; - border-radius: 34px; -} -.slider:before { - position: absolute; - content: ""; - height: 37px; - width: 37px; - left: 2px; - bottom: 2px; - background-color: #d0efff; - -webkit-transition: 0.4s; - transition: 0.4s; - border-radius: 68px; + display: none; +} +.dev-tool-card, +.dev-tools-grid, +.metrics-grid { + display: grid; + gap: 1.5rem; +} +.metrics-grid { + grid-template-columns: repeat(auto-fit, minmax(100px, 0.5fr)); + margin: 0.5rem; +} +.metric-card { + background: rgba(3, 37, 76, 0.6); + border-radius: 8px; + padding: 0.5rem; + text-align: center; + border: 1px solid rgba(255, 255, 255, 0.1); +} +.metric-icon { + font-size: 2rem; + margin-bottom: 0.5rem; +} +.metric-label { + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.7); + margin-bottom: 0.5rem; +} +.metric-value { + font-size: 2rem; + font-weight: 600; + color: #2a9df4; + margin-bottom: 0.25rem; +} +.metric-unit { + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.7); +} +.dev-tools-grid { + padding: 2rem 0; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); +} +.dev-tool-card { + padding: 1.5rem; + color: #fff; + transition: 0.3s; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + grid-template-columns: auto 1fr auto; + align-items: start; +} +.info-box, +.tool-icon { + background: rgba(42, 157, 244, 0.1); +} +.tool-icon { + font-size: 2rem; + width: 3rem; + height: 3rem; + display: flex; + align-items: center; + justify-content: center; + border-radius: 12px; +} +.tool-content { + flex: 1; +} +.tool-features { + list-style: none; + padding: 0; + margin: 0; + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.7); +} +.tool-features li { + margin-bottom: 0.5rem; + display: flex; + align-items: center; +} +.tool-features li:before { + content: "•"; + color: #2a9df4; + margin-right: 0.5rem; + font-size: 1.2em; +} +.card-arrow { + font-size: 1.5rem; + color: rgba(255, 255, 255, 0.3); + transition: transform 0.2s; +} +.dev-tool-card:hover .card-arrow { + transform: translateX(4px); + color: #2a9df4; } -input:checked + .slider { - transition: 0.4s; - background-color: #2a9df4; +.switch { + display: inline-block; + width: 60px; + height: 34px; +} +.switch .slider { + position: absolute; + inset: 0; + background: #03254c; + border-radius: 34px; + transition: 0.4s; +} +.switch .slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background: #fff; + border-radius: 50%; + transition: 0.4s; } input:checked + .slider:before { - -webkit-transform: translateX(38px); - -ms-transform: translateX(38px); - transform: translateX(38px); -} -.slider2 { - -webkit-appearance: none; - margin: 5px; - width: 270px; - height: 20px; - background: #d0efff; - /*outline:8px ridge rgba(170,50,220, .6); - border-radius: 2rem;*/ - outline: none; - -webkit-transition: 0.2s; - transition: opacity 0.2s; -} -.slider2::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 30px; - height: 30px; - background: #03254c; - cursor: pointer; -} -.slider2::-moz-range-thumb { - width: 30px; - height: 30px; - background: #1167b1; - cursor: pointer; + transform: translateX(26px); } - -table.center { - margin-left: auto; - margin-right: auto; -} - .tooltip { - position: relative; - display: inline-block; - border-bottom: 1px dotted #03254c; + border-bottom: 1px dotted rgba(255, 255, 255, 0.3); + cursor: help; } - .tooltip .tooltiptext { - visibility: hidden; - width: 120px; - background-color: #03254c; - color: #d0efff; - text-align: center; - border-radius: 6px; - padding: 5px 0; - - /* Position the tooltip */ - position: absolute; - z-index: 1; + visibility: hidden; + width: 200px; + background: #03254c; + color: #fff; + text-align: center; + border-radius: 6px; + padding: 0.5rem; + position: absolute; + z-index: 1; + bottom: 125%; + left: 50%; + transform: translateX(-50%); + opacity: 0; + transition: opacity 0.3s; } - .tooltip:hover .tooltiptext { - visibility: visible; + visibility: visible; + opacity: 1; +} +.info-box { + border-left: 4px solid #2a9df4; + padding: 1.5rem; + margin-bottom: 2rem; + border-radius: 0 4px 4px 0; +} +.note { + font-style: italic; + color: rgba(255, 255, 255, 0.8); + font-size: 0.9rem; + margin-top: 1rem; +} +.button-container { + text-align: center; + margin-top: 2rem; +} +.primary-button, +.secondary-button, +.warning-button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 4px; + font-weight: 500; + cursor: pointer; + transition: 0.2s; +} +.primary-button { + background: #2a9df4; + color: #fff; +} +.primary-button:hover { + background: #1b8fe3; +} +.secondary-button, +.status-message.info { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} +.secondary-button:hover { + background: rgba(255, 255, 255, 0.2); +} +.status-message.error, +.warning-button { + background: #dc3545; + color: #fff; +} +.warning-button:hover { + background: #c82333; +} +.status-message { + padding: 1rem; + margin: 1rem 0; + border-radius: 4px; + animation: 0.3s fadeIn; +} +.status-message.success { + background: #28a745; + color: #fff; } - .watermark { - display: inline; - position: fixed; - top: 0px; - left: 0px; - transform: translate(calc(50vw - 200px), calc(50vh - 170px)) rotate(45deg); - transition: 0.4s ease-in-out; - opacity: 0.7; - z-index: 99; - color: grey; - font-size: 7rem; -} - -.watermark:hidden { - transition: visibility 0s 2s, opacity 2s linear; -} - -.shiftButton { - -webkit-appearance: none; - -webkit-text-stroke: 2px rgba(104, 104, 104, 0.412); - appearance: auto; - width: 16%; - height: 6rem; - background: #03254c; - color: white; - cursor: pointer; - font-weight: bold; - font-size: calc(1vw + 1vh); -} - -.shiftBox { - background: #2a9df4; - color: white; - font-weight: bold; - font-size: calc(1vw + 1vh); - width: 4%; - text-align: center; -} - -body { - display: block; - margin: 0 auto; - background-color: #1167b1; - opacity: 1; - transition: 0.5s ease-in-out; - height: 100%; - width: 100%; -} - -fieldset { - border: 10px solid; - border-color: #1e3252; - box-sizing: border-box; - grid-area: 1 / 1; - padding: 5px; - margin: 0 auto; - z-index: -1; -} - -.confirmation-dialog { - display: flex; - align-items: center; - justify-content: center; - position: fixed; - top: 0; - left: 0; - height: 100%; - width: 100%; - background-color: rgba(201, 201, 201, 0.7); - z-index: 100; -} - -.confirmation-dialog > .confirmation-panel { - background-color: #03245c; - color: white; - padding: 20px; - border-radius: 10px; -} - -.confirmation-panel > .confirmation-buttongroup { - padding-top: 10px; -} - -.confirmation-buttongroup > input[type="button"] { - width: 75px; - height: 40px; - font-size: 1em; - border-radius: 10px; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(45deg); + font-size: 5rem; + color: rgba(255, 255, 255, 0.2); + pointer-events: none; + z-index: 0; +} +footer { + margin-top: 2rem; + padding: 1rem 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + justify-content: space-between; + align-items: center; +} +.dev-links { + display: flex; + align-items: center; + gap: 1rem; +} +.dev-link { + color: rgba(255, 255, 255, 0.7); + font-size: 0.9rem; +} +.separator { + color: rgba(255, 255, 255, 0.3); +} +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +@media (max-width: 768px) { + .button-group, + .input-group { + flex-direction: column; + } + .dev-tools-grid, + .menu-grid, + .metrics-grid, + .status-grid { + grid-template-columns: 1fr; + } + .number-input, + .scan-button { + width: 100%; + } + .settings-section { + padding: 1rem; + } + .adjust-button { + min-width: 32px; + } + .slider-container { + min-width: 0; + } + .input-group { + align-items: stretch; + } + .unit { + text-align: right; + } + .shift-button { + height: 100px; + } + .menu-item, + .metric-card, + .status-group { + padding: 1.25rem; + } + .value-display { + flex-direction: column; + gap: 0.5rem; + text-align: center; + } + .auto-update { + justify-content: center; + } + .debug-console { + height: 50vh; + } + .dev-links { + flex-direction: column; + gap: 0.5rem; + } + .separator { + display: none; + } }