39282-vm/app.php
2026-03-23 19:22:21 +00:00

155 lines
4.7 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/db/config.php';
const RECLINER_PATTERNS = ['continuous', 'pulse'];
function project_meta(): array
{
$projectName = $_SERVER['PROJECT_NAME'] ?? getenv('PROJECT_NAME') ?: 'Recliner Haptics Studio';
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? getenv('PROJECT_DESCRIPTION') ?: 'Browser-based recliner simulator with gamepad vibration controls, saved presets, and a live recline angle visualizer.';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? getenv('PROJECT_IMAGE_URL') ?: '';
return [
'name' => (string) $projectName,
'description' => (string) $projectDescription,
'image' => (string) $projectImageUrl,
];
}
function ensure_recliner_schema(): void
{
db()->exec(
"CREATE TABLE IF NOT EXISTS recliner_presets (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
angle_deg TINYINT UNSIGNED NOT NULL,
intensity_pct TINYINT UNSIGNED NOT NULL,
pattern_mode VARCHAR(20) NOT NULL,
notes VARCHAR(255) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
}
function default_preset(): array
{
return [
'name' => 'Demo Lounge',
'angle_deg' => 112,
'intensity_pct' => 44,
'pattern_mode' => 'continuous',
'notes' => 'Balanced demo profile for a calm showroom preview.',
];
}
function validate_preset_input(array $input): array
{
$errors = [];
$name = trim((string) ($input['name'] ?? ''));
if ($name === '' || strlen($name) < 2 || strlen($name) > 120) {
$errors['name'] = 'Preset name must be between 2 and 120 characters.';
}
$angle = filter_var($input['angle_deg'] ?? null, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 160],
]);
if ($angle === false) {
$errors['angle_deg'] = 'Angle must be between 0° and 160°.';
}
$intensity = filter_var($input['intensity_pct'] ?? null, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 100],
]);
if ($intensity === false) {
$errors['intensity_pct'] = 'Intensity must be between 0% and 100%.';
}
$pattern = (string) ($input['pattern_mode'] ?? 'continuous');
if (!in_array($pattern, RECLINER_PATTERNS, true)) {
$errors['pattern_mode'] = 'Choose a supported vibration pattern.';
}
$notes = trim((string) ($input['notes'] ?? ''));
if (strlen($notes) > 255) {
$errors['notes'] = 'Notes must stay under 255 characters.';
}
return [
'errors' => $errors,
'data' => [
'name' => $name,
'angle_deg' => $angle === false ? 0 : (int) $angle,
'intensity_pct' => $intensity === false ? 0 : (int) $intensity,
'pattern_mode' => $pattern,
'notes' => $notes,
],
];
}
function save_preset(array $data): int
{
$stmt = db()->prepare(
'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(':notes', $data['notes'] !== '' ? $data['notes'] : null, $data['notes'] !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
$stmt->execute();
return (int) db()->lastInsertId();
}
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, notes, created_at
FROM recliner_presets
ORDER BY created_at DESC, id DESC
LIMIT ' . $limit
);
return $stmt->fetchAll() ?: [];
}
function get_preset(int $id): ?array
{
$stmt = db()->prepare(
'SELECT id, name, angle_deg, intensity_pct, pattern_mode, notes, created_at
FROM recliner_presets
WHERE id = :id
LIMIT 1'
);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$preset = $stmt->fetch();
return $preset ?: null;
}
function preset_tone(int $angle): string
{
if ($angle >= 135) {
return 'Deep recline';
}
if ($angle >= 90) {
return 'Lounge';
}
if ($angle >= 45) {
return 'Reading';
}
return 'Upright';
}
function e(?string $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}