diff --git a/app.php b/app.php index df6c949..9e9194c 100644 --- a/app.php +++ b/app.php @@ -27,7 +27,6 @@ function ensure_recliner_schema(): void angle_deg TINYINT UNSIGNED NOT NULL, intensity_pct TINYINT UNSIGNED NOT NULL, pattern_mode VARCHAR(20) NOT NULL, - duration_ms SMALLINT UNSIGNED NOT NULL, notes VARCHAR(255) DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" @@ -41,7 +40,6 @@ function default_preset(): array 'angle_deg' => 112, 'intensity_pct' => 44, 'pattern_mode' => 'continuous', - 'duration_ms' => 1400, 'notes' => 'Balanced demo profile for a calm showroom preview.', ]; } @@ -69,13 +67,6 @@ function validate_preset_input(array $input): array $errors['intensity_pct'] = 'Intensity must be between 0% and 100%.'; } - $duration = filter_var($input['duration_ms'] ?? null, FILTER_VALIDATE_INT, [ - 'options' => ['min_range' => 100, 'max_range' => 5000], - ]); - if ($duration === false) { - $errors['duration_ms'] = 'Duration must be between 100 ms and 5000 ms.'; - } - $pattern = (string) ($input['pattern_mode'] ?? 'continuous'); if (!in_array($pattern, RECLINER_PATTERNS, true)) { $errors['pattern_mode'] = 'Choose a supported vibration pattern.'; @@ -93,7 +84,6 @@ function validate_preset_input(array $input): array 'angle_deg' => $angle === false ? 0 : (int) $angle, 'intensity_pct' => $intensity === false ? 0 : (int) $intensity, 'pattern_mode' => $pattern, - 'duration_ms' => $duration === false ? 1000 : (int) $duration, 'notes' => $notes, ], ]; @@ -102,15 +92,14 @@ function validate_preset_input(array $input): array function save_preset(array $data): int { $stmt = db()->prepare( - 'INSERT INTO recliner_presets (name, angle_deg, intensity_pct, pattern_mode, duration_ms, notes) - VALUES (:name, :angle_deg, :intensity_pct, :pattern_mode, :duration_ms, :notes)' + 'INSERT INTO recliner_presets (name, angle_deg, intensity_pct, pattern_mode, notes) + VALUES (:name, :angle_deg, :intensity_pct, :pattern_mode, :notes)' ); $stmt->bindValue(':name', $data['name'], PDO::PARAM_STR); $stmt->bindValue(':angle_deg', $data['angle_deg'], PDO::PARAM_INT); $stmt->bindValue(':intensity_pct', $data['intensity_pct'], PDO::PARAM_INT); $stmt->bindValue(':pattern_mode', $data['pattern_mode'], PDO::PARAM_STR); - $stmt->bindValue(':duration_ms', $data['duration_ms'], PDO::PARAM_INT); $stmt->bindValue(':notes', $data['notes'] !== '' ? $data['notes'] : null, $data['notes'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL); $stmt->execute(); @@ -121,7 +110,7 @@ function get_recent_presets(int $limit = 8): array { $limit = max(1, min(24, $limit)); $stmt = db()->query( - 'SELECT id, name, angle_deg, intensity_pct, pattern_mode, duration_ms, notes, created_at + 'SELECT id, name, angle_deg, intensity_pct, pattern_mode, notes, created_at FROM recliner_presets ORDER BY created_at DESC, id DESC LIMIT ' . $limit @@ -133,7 +122,7 @@ function get_recent_presets(int $limit = 8): array function get_preset(int $id): ?array { $stmt = db()->prepare( - 'SELECT id, name, angle_deg, intensity_pct, pattern_mode, duration_ms, notes, created_at + 'SELECT id, name, angle_deg, intensity_pct, pattern_mode, notes, created_at FROM recliner_presets WHERE id = :id LIMIT 1' @@ -163,4 +152,4 @@ function preset_tone(int $angle): string function e(?string $value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); -} +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index f6781e6..b1c94e6 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -8,7 +8,6 @@ document.addEventListener('DOMContentLoaded', () => { angle: document.getElementById('angle_deg'), intensity: document.getElementById('intensity_pct'), pattern: document.getElementById('pattern_mode'), - duration: document.getElementById('duration_ms'), name: document.getElementById('name'), notes: document.getElementById('notes') }; @@ -18,19 +17,15 @@ document.addEventListener('DOMContentLoaded', () => { angleValue: document.getElementById('angle-value'), anglePill: document.getElementById('angle-pill'), intensityPill: document.getElementById('intensity-pill'), - durationPill: document.getElementById('duration-pill'), statAngle: document.getElementById('stat-angle'), statIntensity: document.getElementById('stat-intensity'), statPattern: document.getElementById('stat-pattern'), - statDuration: document.getElementById('stat-duration'), saveAngle: document.getElementById('save-angle'), saveIntensity: document.getElementById('save-intensity'), savePattern: document.getElementById('save-pattern'), - saveDuration: document.getElementById('save-duration'), summaryAngle: document.getElementById('summary-angle'), summaryIntensity: document.getElementById('summary-intensity'), summaryPattern: document.getElementById('summary-pattern'), - summaryTone: document.getElementById('summary-tone'), reclineMode: document.getElementById('recline-mode'), gamepadState: document.getElementById('gamepad-state'), gamepadSelect: document.getElementById('gamepad-select'), @@ -40,6 +35,7 @@ document.addEventListener('DOMContentLoaded', () => { let knownGamepads = []; let scanTimer = null; + let continuousVibrationInterval = null; const notify = (message) => { if (!message) { @@ -63,8 +59,7 @@ document.addEventListener('DOMContentLoaded', () => { const currentState = () => ({ angle: Number(controls.angle?.value || 0), intensity: Number(controls.intensity?.value || 0), - pattern: controls.pattern?.value || 'continuous', - duration: Number(controls.duration?.value || 1000) + pattern: controls.pattern?.value || 'continuous' }); const updateVisualization = () => { @@ -79,19 +74,15 @@ document.addEventListener('DOMContentLoaded', () => { angleValue: `${state.angle}`, anglePill: `${state.angle}°`, intensityPill: `${state.intensity}%`, - durationPill: `${state.duration} ms`, statAngle: `${state.angle}°`, statIntensity: `${state.intensity}%`, statPattern: capitalize(state.pattern), - statDuration: `${state.duration} ms`, saveAngle: `${state.angle}°`, saveIntensity: `${state.intensity}%`, savePattern: capitalize(state.pattern), - saveDuration: `${state.duration} ms`, summaryAngle: `${state.angle}°`, summaryIntensity: `${state.intensity}%`, summaryPattern: capitalize(state.pattern), - summaryTone: tone, reclineMode: tone }; @@ -109,7 +100,6 @@ document.addEventListener('DOMContentLoaded', () => { if (controls.angle && preset.angle_deg !== undefined) controls.angle.value = preset.angle_deg; if (controls.intensity && preset.intensity_pct !== undefined) controls.intensity.value = preset.intensity_pct; if (controls.pattern && preset.pattern_mode) controls.pattern.value = preset.pattern_mode; - if (controls.duration && preset.duration_ms !== undefined) controls.duration.value = preset.duration_ms; if (controls.name && preset.name !== undefined) controls.name.value = preset.name; if (controls.notes && preset.notes !== undefined) controls.notes.value = preset.notes; updateVisualization(); @@ -211,32 +201,38 @@ document.addEventListener('DOMContentLoaded', () => { return; } - const { intensity, duration, pattern } = currentState(); + const { intensity, pattern } = currentState(); const normalizedIntensity = Math.max(0, Math.min(1, intensity / 100)); - ui.testButton.disabled = true; - ui.testButton.textContent = 'Testing…'; + + if (continuousVibrationInterval) { + window.clearInterval(continuousVibrationInterval); + continuousVibrationInterval = null; + ui.testButton.textContent = 'Test vibration (continuous)'; + notify('Vibration stopped.'); + return; + } + + ui.testButton.textContent = 'Stop vibration'; try { if (pattern === 'pulse') { - let remaining = duration; - while (remaining > 0) { - const burst = Math.min(160, remaining); - await playEffect(actuator, normalizedIntensity, burst); - remaining -= burst; - if (remaining > 0) { - await new Promise((resolve) => window.setTimeout(resolve, 120)); - } - } + const pulse = async () => { + await playEffect(actuator, normalizedIntensity, 500); + }; + pulse(); + continuousVibrationInterval = window.setInterval(pulse, 800); } else { - await playEffect(actuator, normalizedIntensity, duration); + // For continuous, we re-trigger every 100ms + await playEffect(actuator, normalizedIntensity, 150); + continuousVibrationInterval = window.setInterval(async () => { + await playEffect(actuator, normalizedIntensity, 150); + }, 100); } - notify(`Running ${pattern} vibration at ${intensity}% for ${duration} ms.`); + notify(`Running ${pattern} vibration at ${intensity}%.`); } catch (error) { console.error(error); notify('The browser detected the controller, but vibration could not start.'); - } finally { - ui.testButton.textContent = 'Test vibration'; - refreshGamepads(); + ui.testButton.textContent = 'Test vibration (continuous)'; } }; @@ -248,7 +244,6 @@ document.addEventListener('DOMContentLoaded', () => { angle_deg: button.dataset.angle || 0, intensity_pct: button.dataset.intensity || 0, pattern_mode: button.dataset.pattern || 'continuous', - duration_ms: button.dataset.duration || 1000, notes: button.dataset.notes || '' }); window.location.hash = 'simulator'; @@ -268,7 +263,7 @@ document.addEventListener('DOMContentLoaded', () => { }); } - [controls.angle, controls.intensity, controls.pattern, controls.duration].forEach((element) => { + [controls.angle, controls.intensity, controls.pattern].forEach((element) => { if (element) { element.addEventListener('input', updateVisualization); element.addEventListener('change', updateVisualization); @@ -306,5 +301,8 @@ document.addEventListener('DOMContentLoaded', () => { if (scanTimer) { window.clearInterval(scanTimer); } + if (continuousVibrationInterval) { + window.clearInterval(continuousVibrationInterval); + } }); -}); +}); \ No newline at end of file diff --git a/index.php b/index.php index 862418a..21fafe1 100644 --- a/index.php +++ b/index.php @@ -36,7 +36,6 @@ if ($presetId) { 'angle_deg' => (int) $selectedPreset['angle_deg'], 'intensity_pct' => (int) $selectedPreset['intensity_pct'], 'pattern_mode' => (string) $selectedPreset['pattern_mode'], - 'duration_ms' => (int) $selectedPreset['duration_ms'], 'notes' => (string) ($selectedPreset['notes'] ?? ''), ]; } @@ -125,7 +124,6 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) {
Angle°
Intensity%
Pattern
-
Tone

Tip: the browser requires a user interaction before triggering vibration. Press Test vibration after connecting the controller.

@@ -164,18 +162,15 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) {
-
+
Recline°
-
+
Rumble%
-
+
Pattern
-
-
Duration ms
-
@@ -197,7 +192,7 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) {
- +
@@ -210,18 +205,12 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) {
%
-
-
- - -
-
-
ms
- -
+
+ +
@@ -261,7 +250,6 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) {
Angle°
Intensity%
Pattern
-
Duration ms
@@ -295,7 +283,6 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) {
% intensity - ms

@@ -309,7 +296,6 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) { data-angle="" data-intensity="" data-pattern="" - data-duration="" data-notes="" >Apply in simulator View detail @@ -347,4 +333,4 @@ if (isset($_GET['saved']) && ctype_digit((string) $_GET['saved'])) { - + \ No newline at end of file diff --git a/preset.php b/preset.php index f472ed2..300ab29 100644 --- a/preset.php +++ b/preset.php @@ -64,14 +64,13 @@ $pageDescription = $preset ° angle % intensity rumble - ms
Profile tone
-

Saved on UTC.

+

Saved on UTC.

@@ -85,7 +84,6 @@ $pageDescription = $preset
Recline angle°
Vibration intensity%
Pattern
-
Duration ms
Saved at

@@ -111,4 +109,4 @@ $pageDescription = $preset - + \ No newline at end of file