2 Commits

10 changed files with 698 additions and 165 deletions
@@ -0,0 +1,357 @@
# Theme Toggle Top-Bar Relocation Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Move the light/dark toggle from floating right-side UI into top-bar controls (left of settings), with consistent visibility and behavior across all user-facing views.
**Architecture:** Introduce one reusable controls partial (`topbar-controls.php`) that owns toggle/settings markup, then reuse it from each page header. Keep theme state logic centralized in `theme.php` while removing floating positioning and binding behavior to the top-bar toggle IDs. Update all templates in `views/*.php` to ensure complete coverage and consistent top-bar placement.
**Tech Stack:** PHP 8+, server-rendered views, Tailwind CDN utilities, Bootstrap Icons, cookie-based theme persistence.
---
## File Structure and Responsibilities
- Create: `views/partials/topbar-controls.php`
- Single responsibility: render top-bar controls (theme toggle always, settings link optionally).
- Modify: `views/partials/theme.php`
- Single responsibility: theme token CSS + initialization + toggle behavior script.
- Remove floating button markup/CSS; keep runtime toggle logic.
- Modify: `views/index.php`
- Use shared top-bar controls in existing header with settings visible.
- Modify: `views/vehicle.php`
- Use shared top-bar controls in existing header with settings visible.
- Modify: `views/settings.php`
- Use shared top-bar controls in existing header with settings hidden.
- Modify: `views/login.php`
- Add slim top header + controls; preserve centered login card in main content area.
- Modify: `views/setup.php`
- Add slim top header + controls; preserve centered setup card in main content area.
- Modify: `views/export.php`
- Add theme partial includes and slim top header + controls, preserving export content.
## Chunk 1: Shared Controls + Theme Engine Refactor
### Task 1: Add reusable top-bar controls partial
**Files:**
- Create: `views/partials/topbar-controls.php`
- [ ] **Step 1: Write `topbar-controls.php` with explicit include API defaults**
```php
<?php
$showSettings = isset($showSettings) ? (bool) $showSettings : false;
$settingsUrl = isset($settingsUrl) ? (string) $settingsUrl : url('/settings');
?>
<div class="flex items-center space-x-4">
<button
id="themeToggle"
type="button"
class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg shadow-sm transition"
aria-label="Switch to dark mode"
title="Switch to dark mode"
aria-pressed="false"
>
<i id="themeToggleIcon" class="bi bi-moon-stars-fill" aria-hidden="true"></i>
</button>
<?php if ($showSettings): ?>
<a href="<?php echo htmlspecialchars($settingsUrl); ?>" class="theme-text-muted hover:opacity-95">
<i class="bi bi-gear-fill text-2xl" aria-hidden="true"></i>
<span class="sr-only">Settings</span>
</a>
<?php endif; ?>
</div>
<noscript>
<style>
#themeToggle {
display: none !important;
}
</style>
</noscript>
```
- [ ] **Step 2: Run syntax check for new partial**
Run: `php -l views/partials/topbar-controls.php`
Expected: `No syntax errors detected in views/partials/topbar-controls.php`
- [ ] **Step 3: Commit partial addition**
```bash
git add views/partials/topbar-controls.php
git commit -m "feat: add reusable top-bar controls partial"
```
### Task 2: Refactor `theme.php` to stop rendering floating toggle
**Files:**
- Modify: `views/partials/theme.php`
- [ ] **Step 1: Remove floating toggle CSS rules and keep focus-visible styling**
Replace floating positioning block with only focus-visible rule:
```css
#themeToggle:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
```
- [ ] **Step 2: Remove body-section button markup and keep JS behavior block**
Delete the button HTML + `<noscript>` block from `if ($themeSection === 'body')` and keep only the script that binds to `#themeToggle` and `#themeToggleIcon`.
- [ ] **Step 3: Keep graceful missing-element guard and aria/icon sync**
Ensure this remains present near script start:
```js
if (!toggleButton || !toggleIcon) {
return;
}
```
- [ ] **Step 4: Run syntax check for theme partial**
Run: `php -l views/partials/theme.php`
Expected: `No syntax errors detected in views/partials/theme.php`
- [ ] **Step 5: Commit theme refactor**
```bash
git add views/partials/theme.php
git commit -m "refactor: detach theme toggle UI from theme partial"
```
## Chunk 2: View Integration + Cross-View Verification
### Task 3: Integrate shared controls into existing authenticated headers
**Files:**
- Modify: `views/index.php`
- Modify: `views/vehicle.php`
- Modify: `views/settings.php`
- [ ] **Step 1: Replace direct settings icon block in `views/index.php`**
Use shared controls in header:
```php
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
```
- [ ] **Step 2: Replace direct settings icon block in `views/vehicle.php`**
Use shared controls in header:
```php
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
```
- [ ] **Step 3: Replace direct settings icon block in `views/settings.php`**
Use shared controls in header without self-link:
```php
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
```
- [ ] **Step 4: Run syntax checks for updated authenticated templates**
Run: `php -l views/index.php && php -l views/vehicle.php && php -l views/settings.php`
Expected: three `No syntax errors detected` lines.
- [ ] **Step 5: Commit authenticated-header integration**
```bash
git add views/index.php views/vehicle.php views/settings.php
git commit -m "refactor: reuse top-bar controls in authenticated headers"
```
### Task 4: Add top headers and controls to login/setup/export
**Files:**
- Modify: `views/login.php`
- Modify: `views/setup.php`
- Modify: `views/export.php`
- [ ] **Step 1: Update `views/login.php` body layout for top bar + centered main**
Target structure:
```php
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<!-- existing login card -->
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
```
Must preserve unchanged in `views/login.php`:
- Existing flash message blocks.
- Existing login form action/inputs/button labels.
- Existing `themeSection = 'body'` include placement (moved below new main only if needed).
- [ ] **Step 2: Apply same top-bar pattern to `views/setup.php`**
Target structure:
```php
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<!-- existing setup card + requirement messages -->
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
```
Must preserve unchanged in `views/setup.php`:
- System requirements panel and PHP/SQLite checks.
- Setup form action/field names/validation markers.
- Existing error/validation message blocks.
- [ ] **Step 3: Add theme head include to `views/export.php`**
In `<head>`, add:
```php
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
```
- [ ] **Step 4: Add slim top header with shared controls in `views/export.php`**
Near start of `<body>`, add header:
```php
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
```
- [ ] **Step 5: Apply theme layout classes in `views/export.php`**
Make these concrete class updates while preserving content hierarchy:
- Change `<body class="bg-white p-8">` to `<body class="theme-page min-h-screen">`.
- Wrap export content in a `<main class="max-w-4xl mx-auto px-4 py-8">` container beneath the new header.
- Change the vehicle info panel container from `bg-gray-50` to `theme-surface` and add `theme-border` where bordered surfaces are used.
- Keep export typography/content layout unchanged unless required for theme contrast.
- [ ] **Step 6: Add theme body include to `views/export.php`**
Before `</body>`, add:
```php
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
```
- [ ] **Step 7: Run syntax checks for login/setup/export templates**
Run: `php -l views/login.php && php -l views/setup.php && php -l views/export.php`
Expected: three `No syntax errors detected` lines.
- [ ] **Step 8: Commit public/export header integration**
```bash
git add views/login.php views/setup.php views/export.php
git commit -m "feat: surface theme toggle in top bar across public and export views"
```
### Task 5: End-to-end verification of requirements
**Files:**
- Verify runtime behavior only (no file creation)
- [ ] **Step 1: Run full changed-file syntax sweep**
Run:
```bash
php -l views/partials/topbar-controls.php
php -l views/partials/theme.php
php -l views/index.php
php -l views/vehicle.php
php -l views/settings.php
php -l views/login.php
php -l views/setup.php
php -l views/export.php
```
Expected: all files report no syntax errors.
- [ ] **Step 2: Manual runtime verification checklist (JS enabled)**
Validate in browser:
- `/home`, `/vehicles/{id}`, `/settings`, `/login`, `/setup`, and `/vehicles/{id}/export/html` (export page in current routes)
- Toggle is visible in top bar in every view.
- In views with settings, toggle appears immediately left of settings icon.
- Theme changes immediately and persists after reload/navigation.
- Confirm on both desktop and mobile widths that header controls remain visible and usable.
- Confirm route-to-view coverage by checking all files in `views/*.php` render `topbar-controls.php` directly or through a shared header partial (`index`, `vehicle`, `settings`, `login`, `setup`, `export`).
- Confirm export route parity: top-bar controls are present without breaking export content rendering.
- Confirm settings icon is hidden on pages using `$showSettings = false` (`/settings`, `/login`, `/setup`, `/vehicles/{id}/export/html`).
- [ ] **Step 3: Deterministic coverage verification for templates**
Run:
```bash
grep -n "topbar-controls.php" views/index.php views/vehicle.php views/settings.php views/login.php views/setup.php views/export.php
```
Expected:
- Command prints one include line in each required template file.
- Each required template renders top-bar controls (direct include in this plan; if a shared header partial is introduced later, update this check accordingly).
- [ ] **Step 4: Manual runtime verification (JS disabled)**
Validate in browser devtools/no-script mode:
- `#themeToggle` is hidden (noscript rule works).
- Layout remains usable and header spacing is not broken.
- Expected: no major layout shift or overlap in header controls.
- [ ] **Step 5: Manual fallback verification for cookie-write failure**
Validate by blocking cookies in browser tools or private profile:
- Toggle still switches theme in current session/view.
- Reload may revert to resolved default/system theme when cookie persistence is unavailable.
- No JavaScript errors in console during toggle interaction.
- Expected: theme updates immediately on click even without persistence.
- [ ] **Step 6: Commit final polish/fixes from verification (only if changes were needed)**
```bash
git add views/partials/theme.php views/partials/topbar-controls.php views/index.php views/vehicle.php views/settings.php views/login.php views/setup.php views/export.php
git commit -m "fix: finalize top-bar theme toggle behavior and cross-view consistency"
```
If no files changed during verification, skip commit and mark this step complete.
## Implementation Notes
- Keep changes DRY and YAGNI: no additional theme modes, no storage changes, no route/controller changes.
- Use `@superpowers/test-driven-development` discipline where possible in this codebase by keeping each change small and verified immediately with syntax + targeted manual checks.
- Before claiming completion, run `@superpowers/verification-before-completion` checks (syntax + required manual coverage).
@@ -0,0 +1,161 @@
# Theme Toggle Top-Bar Relocation Design
## Context
The application currently renders the light/dark toggle as a floating fixed button from `views/partials/theme.php`. The requested behavior is to place this control in the top bar, positioned to the left of the settings button when settings is present, while keeping the toggle visible and usable across all views (including `login` and `setup`).
## Goals
- Replace the floating theme toggle with a top-bar-integrated control.
- Keep theme behavior unchanged (cookie persistence, icon/label updates, immediate visual switch).
- Ensure the toggle is visible and reachable in every application view.
- Preserve existing visual patterns (Tailwind + Bootstrap Icons, current card/header language).
Coverage rule:
- Every user-facing template in `views/*.php` must render `views/partials/topbar-controls.php` directly or through a shared header partial.
## Non-Goals
- Redesigning page layouts beyond what is needed to provide a consistent top bar.
- Changing theme color tokens or dark/light palette values.
- Altering authentication or route behavior.
## Proposed Approach
### 1) Shared top-bar controls partial
Create a reusable partial dedicated to top-bar action controls. This partial will render:
- Theme toggle button (always present).
- Settings link (conditionally present).
Ordering rule:
- Theme toggle appears immediately to the left of settings when settings is shown.
This keeps the placement contract explicit and reusable across all pages.
### Interface contract
- Partial path: `views/partials/topbar-controls.php`.
- Include API:
- `$showSettings` (bool): whether to render settings link in this header; default `false` inside partial.
- `$settingsUrl` (string): target URL when settings is rendered; default is applied inside partial as `url('/settings')` when caller does not set it.
- Required markup contract inside the partial:
- Toggle button id: `themeToggle`.
- Toggle icon id: `themeToggleIcon`.
- Server-rendered baseline attributes:
- `aria-label="Switch to dark mode"`
- `title="Switch to dark mode"`
- `aria-pressed="false"`
- icon class starts at `bi bi-moon-stars-fill`
- Toggle appears first in the controls row; settings link appears second when enabled.
- Script contract:
- `views/partials/theme.php` body script binds to `#themeToggle` and `#themeToggleIcon`.
- If either element is missing, script exits without errors.
### Responsibility split
- `views/partials/topbar-controls.php` owns toggle markup, baseline attributes, ordering, optional settings link, and `<noscript>` rule that hides `#themeToggle` when JS is disabled.
- `views/partials/theme.php` owns theme token CSS, initial theme resolution, cookie persistence, and click behavior synchronization.
### 2) Move toggle rendering responsibility
Adjust `views/partials/theme.php` so it no longer emits floating button markup and fixed-position CSS. Keep it focused on:
- Theme pre-hydration script in `<head>`.
- Theme utility classes/variables.
- Toggle behavior script in `<body>` that binds to the top-bar toggle element.
For robust reuse, the behavior script should target stable element IDs/classes used by the shared controls partial and fail gracefully if a toggle is temporarily absent.
### 3) Standardize top bars across all views
Use a top-bar/header region in every view and include the shared controls partial:
- Authenticated pages that already have a header (home/settings/vehicle-related views): insert shared controls in current header area.
- `login` and `setup`: add a slim header at the top with app branding on the left and shared controls on the right.
This ensures the toggle is always visible and consistently located.
Header wrapper ownership:
- To keep scope focused, slim header markup will be duplicated in `login`, `setup`, and `export` rather than introducing another new wrapper partial.
### View coverage matrix
- `views/index.php`: existing header; include `topbar-controls` with `$showSettings = true`.
- `views/vehicle.php`: existing header; include `topbar-controls` with `$showSettings = true`.
- `views/settings.php`: existing header; include `topbar-controls` with `$showSettings = false` (avoid self-link duplication).
- `views/login.php`: add slim header; include `topbar-controls` with `$showSettings = false`.
- `views/setup.php`: add slim header; include `topbar-controls` with `$showSettings = false`.
- `views/export.php`: add theme partial include + slim top bar with `topbar-controls` and `$showSettings = false`, so toggle remains available in export view too.
Login/setup/export layout contract:
- Body changes from full-screen centered container to vertical layout:
- top header (`theme-surface`, app name left, controls right)
- main content wrapper below header
- Main wrapper keeps current centered card behavior using a flex container with min-height calculated to preserve vertical centering beneath header.
## Data Flow and Interaction
Theme state flow remains unchanged:
1. Initial theme resolved from cookie or system preference in head script.
2. HTML root class/attribute updated (`dark`, `data-theme`).
3. Toggle click flips theme, writes cookie, and synchronizes icon + accessibility attributes.
Only UI location changes; no persistence or resolution logic changes are introduced.
## Accessibility and UX Requirements
- Keep `aria-label` and `aria-pressed` synchronized with active theme.
- Preserve keyboard focus visibility (`:focus-visible` ring).
- Keep descriptive button `title` for pointer users.
- No-JS rule: hide the toggle button via `<noscript>` style in `topbar-controls.php` (button is not shown without scripting). Visibility/usability requirements apply to standard JS-enabled runtime.
## Error Handling
- If cookie write fails (restricted storage), visual theme switching still works for current session.
- If a page omits controls unexpectedly, script exits safely without throwing.
Fallback contract for cookie failures:
- During current page/session: toggle applies visual theme immediately.
- After reload/navigation: persisted preference may be unavailable; theme resolves again from cookie/system preference.
- No blocking alert/toast is added in this change.
## Verification Plan
### Automated checks
- Run `php -l` on every changed PHP file.
### Manual checks
- Confirm top-bar toggle presence and operation on:
- Home (`/home`)
- Vehicle detail pages (`/vehicles/:id`)
- Settings (`/settings`)
- Login (`/login`)
- Setup (`/setup`)
- Export (`/vehicles/:id/export/html` via `/vehicles/:id/export/{format}` route)
- Confirm placement: toggle is left of settings where settings exists.
- Confirm responsive behavior on mobile and desktop.
- Confirm theme persistence after navigation and reload.
- Confirm cookie-write failure fallback expectation: in-session switch works; reload may revert when cookie cannot be stored.
- Confirm route-to-view coverage by checking all templates in `views/*.php` include `topbar-controls.php` (currently: `index`, `vehicle`, `settings`, `login`, `setup`, `export`).
## Risks and Mitigations
- **Risk:** Header markup variance across views could cause inconsistent placement.
- **Mitigation:** Centralize controls in one partial and include it in all headers.
- **Risk:** Existing fixed-position CSS may continue affecting layout.
- **Mitigation:** Remove obsolete `#themeToggle` fixed positioning rules during migration.
## Rollout Notes
- Scope is a UI placement refactor with no database changes.
- No migration steps required.
+35 -26
View File
@@ -6,50 +6,58 @@
<title><?php echo htmlspecialchars($vehicle['name']); ?> - Maintenance Export</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="bg-white p-8">
<div class="max-w-4xl mx-auto">
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="max-w-4xl mx-auto px-4 py-8">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2"><?php echo APP_NAME; ?></h1>
<h2 class="text-xl text-gray-600">Vehicle Maintenance Export</h2>
<h1 class="text-3xl font-bold theme-text mb-2"><?php echo APP_NAME; ?></h1>
<h2 class="text-xl theme-text-muted">Vehicle Maintenance Export</h2>
</div>
<!-- Vehicle Information -->
<div class="bg-gray-50 rounded-lg p-6 mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Vehicle Information</h3>
<div class="theme-surface rounded-lg shadow-sm p-6 mb-8">
<h3 class="text-xl font-semibold theme-text mb-4">Vehicle Information</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<span class="font-medium text-gray-700">Name:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['name']); ?></span>
<span class="font-medium theme-text">Name:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['name']); ?></span>
</div>
<?php if ($vehicle['year']): ?>
<div>
<span class="font-medium text-gray-700">Year:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['year']); ?></span>
<span class="font-medium theme-text">Year:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['year']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['make']): ?>
<div>
<span class="font-medium text-gray-700">Make:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['make']); ?></span>
<span class="font-medium theme-text">Make:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['make']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['model']): ?>
<div>
<span class="font-medium text-gray-700">Model:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['model']); ?></span>
<span class="font-medium theme-text">Model:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['model']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['color']): ?>
<div>
<span class="font-medium text-gray-700">Color:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['color']); ?></span>
<span class="font-medium theme-text">Color:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['color']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['license_plate']): ?>
<div>
<span class="font-medium text-gray-700">License Plate:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['license_plate']); ?></span>
<span class="font-medium theme-text">License Plate:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['license_plate']); ?></span>
</div>
<?php endif; ?>
</div>
@@ -57,16 +65,16 @@
<!-- Maintenance History -->
<div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">Maintenance History</h3>
<h3 class="text-xl font-semibold theme-text mb-4">Maintenance History</h3>
<?php if (empty($maintenanceItems)): ?>
<p class="text-gray-500 text-center py-8">No maintenance records available.</p>
<p class="theme-text-muted text-center py-8">No maintenance records available.</p>
<?php else: ?>
<div class="space-y-4">
<?php foreach ($maintenanceItems as $item): ?>
<div class="border border-gray-300 rounded-lg p-4">
<div class="theme-surface border theme-border rounded-lg p-4">
<div class="flex justify-between items-start mb-2">
<h4 class="text-lg font-semibold text-gray-800"><?php echo htmlspecialchars($item['name']); ?></h4>
<h4 class="text-lg font-semibold theme-text"><?php echo htmlspecialchars($item['name']); ?></h4>
<?php if ($item['cost']): ?>
<span class="bg-green-100 text-green-800 px-3 py-1 rounded">
$<?php echo number_format($item['cost'], 2); ?>
@@ -74,7 +82,7 @@
<?php endif; ?>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm text-gray-600 mb-2">
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm theme-text-muted mb-2">
<div>
<strong>Date:</strong> <?php echo date('M d, Y', strtotime($item['date'])); ?>
</div>
@@ -94,7 +102,7 @@
</div>
<?php if ($item['description']): ?>
<div class="mt-2 text-sm text-gray-600">
<div class="mt-2 text-sm theme-text-muted">
<strong>Description:</strong><br>
<?php echo nl2br(htmlspecialchars($item['description'])); ?>
</div>
@@ -105,10 +113,11 @@
<?php endif; ?>
</div>
<div class="mt-8 text-center text-sm text-gray-500">
<div class="mt-8 text-center text-sm theme-text-muted">
<p>Exported on <?php echo date('F j, Y \a\t g:i A'); ?></p>
<p>Generated by <?php echo APP_NAME; ?></p>
</div>
</div>
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
</html>
+1 -5
View File
@@ -13,11 +13,7 @@
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<div class="flex items-center space-x-4">
<a href="<?php echo url('/settings'); ?>" class="theme-text-muted hover:opacity-95">
<i class="bi bi-gear-fill text-2xl"></i>
</a>
</div>
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
+38 -29
View File
@@ -8,36 +8,45 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="theme-page min-h-screen flex items-center justify-center">
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?></h1>
<?php if (isset($_SESSION['error'])): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['success'])): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #dcfce7; background-color: color-mix(in srgb, #22c55e 14%, var(--theme-surface)); border-color: #86efac; border-color: color-mix(in srgb, #22c55e 40%, var(--theme-border)); color: #14532d; color: var(--theme-text);">
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
</div>
<?php endif; ?>
<form method="POST" action="<?php echo url('/login'); ?>" class="space-y-4">
<div>
<label for="password" class="block text-sm font-medium theme-text mb-1">Password</label>
<input type="password" id="password" name="password" required autofocus
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter your password">
</div>
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?></h1>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Login
</button>
</form>
</div>
<?php if (isset($_SESSION['error'])): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['success'])): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #dcfce7; background-color: color-mix(in srgb, #22c55e 14%, var(--theme-surface)); border-color: #86efac; border-color: color-mix(in srgb, #22c55e 40%, var(--theme-border)); color: #14532d; color: var(--theme-text);">
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
</div>
<?php endif; ?>
<form method="POST" action="<?php echo url('/login'); ?>" class="space-y-4">
<div>
<label for="password" class="block text-sm font-medium theme-text mb-1">Password</label>
<input type="password" id="password" name="password" required autofocus
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter your password">
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Login
</button>
</form>
</div>
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
</html>
-31
View File
@@ -106,19 +106,6 @@ if ($themeSection === 'head'):
color: var(--theme-text-muted);
}
#themeToggle {
position: fixed;
top: calc(1rem + env(safe-area-inset-top));
right: 1rem;
z-index: 50;
}
@media (max-width: 640px) {
#themeToggle {
top: calc(4.75rem + env(safe-area-inset-top));
}
}
#themeToggle:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
@@ -129,24 +116,6 @@ endif;
if ($themeSection === 'body'):
?>
<button
id="themeToggle"
type="button"
class="bg-blue-600 hover:bg-blue-700 text-white p-3 rounded-full shadow-md transition"
aria-label="Switch to dark mode"
title="Switch to dark mode"
>
<i id="themeToggleIcon" class="bi bi-moon-stars-fill" aria-hidden="true"></i>
</button>
<noscript>
<style>
#themeToggle {
display: none !important;
}
</style>
</noscript>
<script>
(function () {
var html = document.documentElement;
+31
View File
@@ -0,0 +1,31 @@
<?php
$showSettings = isset($showSettings) ? (bool) $showSettings : false;
$settingsUrl = isset($settingsUrl) ? (string) $settingsUrl : url('/settings');
?>
<div class="flex items-center space-x-4">
<button
id="themeToggle"
type="button"
class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg shadow-sm transition"
aria-label="Switch to dark mode"
title="Switch to dark mode"
aria-pressed="false"
>
<i id="themeToggleIcon" class="bi bi-moon-stars-fill" aria-hidden="true"></i>
</button>
<?php if ($showSettings): ?>
<a href="<?php echo htmlspecialchars($settingsUrl); ?>" class="theme-text-muted hover:opacity-95">
<i class="bi bi-gear-fill text-2xl" aria-hidden="true"></i>
<span class="sr-only">Settings</span>
</a>
<?php endif; ?>
</div>
<noscript>
<style>
#themeToggle {
display: none !important;
}
</style>
</noscript>
+1 -5
View File
@@ -13,11 +13,7 @@
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-90 transition"><?php echo APP_NAME; ?></a>
<div class="flex items-center space-x-4">
<a href="<?php echo url('/settings'); ?>" class="theme-text-muted hover:opacity-90">
<i class="bi bi-gear-fill text-2xl"></i>
</a>
</div>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
+73 -64
View File
@@ -8,73 +8,82 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="theme-page min-h-screen flex items-center justify-center">
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?> Setup</h1>
<?php if (isset($_SESSION['error'])): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<div class="mb-6 p-4 border rounded" style="background-color: #eff6ff; background-color: color-mix(in srgb, #3b82f6 12%, var(--theme-surface)); border-color: #bfdbfe; border-color: color-mix(in srgb, #3b82f6 35%, var(--theme-border)); color: #1e3a8a; color: var(--theme-text);">
<h2 class="font-semibold mb-2">System Requirements</h2>
<ul class="space-y-1 text-sm">
<li class="flex items-center">
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>PHP Version: <?php echo PHP_VERSION; ?>
<?php if (version_compare(PHP_VERSION, '8.0.0', '>=')): ?>
<span class="text-green-600">(✓ Meets requirement)</span>
<?php else: ?>
<span class="text-red-600">(✗ Requires 8.0+)</span>
<?php endif; ?>
</span>
</li>
<li class="flex items-center">
<?php if (extension_loaded('pdo_sqlite')): ?>
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>SQLite: <span class="text-green-600">Available (✓)</span></span>
<?php else: ?>
<i class="bi bi-x-circle-fill text-red-600 mr-2"></i>
<span>SQLite: <span class="text-red-600">Not Available (✗)</span></span>
<?php endif; ?>
</li>
</ul>
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
<?php
$canSetup = version_compare(PHP_VERSION, '8.0.0', '>=') && extension_loaded('pdo_sqlite');
?>
<?php if (!$canSetup): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fef9c3; background-color: color-mix(in srgb, #eab308 16%, var(--theme-surface)); border-color: #facc15; border-color: color-mix(in srgb, #eab308 38%, var(--theme-border)); color: #854d0e; color: var(--theme-text);">
<p class="font-semibold">System requirements not met!</p>
<p class="text-sm mt-1">Please ensure PHP 8.0+ and SQLite extension are available.</p>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?> Setup</h1>
<?php if (isset($_SESSION['error'])): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<div class="mb-6 p-4 border rounded" style="background-color: #eff6ff; background-color: color-mix(in srgb, #3b82f6 12%, var(--theme-surface)); border-color: #bfdbfe; border-color: color-mix(in srgb, #3b82f6 35%, var(--theme-border)); color: #1e3a8a; color: var(--theme-text);">
<h2 class="font-semibold mb-2">System Requirements</h2>
<ul class="space-y-1 text-sm">
<li class="flex items-center">
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>PHP Version: <?php echo PHP_VERSION; ?>
<?php if (version_compare(PHP_VERSION, '8.0.0', '>=')): ?>
<span class="text-green-600">(✓ Meets requirement)</span>
<?php else: ?>
<span class="text-red-600">(✗ Requires 8.0+)</span>
<?php endif; ?>
</span>
</li>
<li class="flex items-center">
<?php if (extension_loaded('pdo_sqlite')): ?>
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>SQLite: <span class="text-green-600">Available (✓)</span></span>
<?php else: ?>
<i class="bi bi-x-circle-fill text-red-600 mr-2"></i>
<span>SQLite: <span class="text-red-600">Not Available (✗)</span></span>
<?php endif; ?>
</li>
</ul>
</div>
<?php else: ?>
<form method="POST" action="<?php echo url('/setup'); ?>" class="space-y-4">
<div>
<label for="password" class="block text-sm font-medium theme-text-muted mb-1">Create Password</label>
<input type="password" id="password" name="password" required
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter password (min 6 characters)">
<?php
$canSetup = version_compare(PHP_VERSION, '8.0.0', '>=') && extension_loaded('pdo_sqlite');
?>
<?php if (!$canSetup): ?>
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fef9c3; background-color: color-mix(in srgb, #eab308 16%, var(--theme-surface)); border-color: #facc15; border-color: color-mix(in srgb, #eab308 38%, var(--theme-border)); color: #854d0e; color: var(--theme-text);">
<p class="font-semibold">System requirements not met!</p>
<p class="text-sm mt-1">Please ensure PHP 8.0+ and SQLite extension are available.</p>
</div>
<div>
<label for="confirm_password" class="block text-sm font-medium theme-text-muted mb-1">Confirm Password</label>
<input type="password" id="confirm_password" name="confirm_password" required
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Confirm password">
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Complete Setup
</button>
</form>
<?php endif; ?>
</div>
<?php else: ?>
<form method="POST" action="<?php echo url('/setup'); ?>" class="space-y-4">
<div>
<label for="password" class="block text-sm font-medium theme-text-muted mb-1">Create Password</label>
<input type="password" id="password" name="password" required
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter password (min 6 characters)">
</div>
<div>
<label for="confirm_password" class="block text-sm font-medium theme-text-muted mb-1">Confirm Password</label>
<input type="password" id="confirm_password" name="confirm_password" required
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Confirm password">
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Complete Setup
</button>
</form>
<?php endif; ?>
</div>
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
</html>
+1 -5
View File
@@ -13,11 +13,7 @@
<header class="theme-surface shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-90 transition"><?php echo APP_NAME; ?></a>
<div class="flex items-center space-x-4">
<a href="<?php echo url('/settings'); ?>" class="theme-text-muted hover:opacity-90">
<i class="bi bi-gear-fill text-2xl"></i>
</a>
</div>
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>