| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- <?php
- /**
- * Shared HTML fragments for the public site and the admin backoffice.
- * Admin pages all sit in admin/ and hardcode '../assets/...'. Public pages sit
- * in the document root except the gallery viewer in gallery/, so they prefix
- * their links with base() — '' or '../' depending on the page (see bootstrap).
- */
- declare(strict_types=1);
- function public_header(string $title, string $active = '', string $bodyClass = ''): void
- {
- $site = site_get();
- $name = $site['intro_title'] ?: config('site.name', 'Portfolio');
- $base = base();
- ?><!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title><?= e($title) ?></title>
- <link rel="stylesheet" href="<?= e($base) ?>assets/site.css">
- </head>
- <body class="<?= e($bodyClass) ?>">
- <header class="nav" id="nav">
- <a class="nav-brand" href="<?= e($base ?: './') ?>"><?= e($name) ?></a>
- <nav class="nav-links">
- <a href="<?= e($base ?: './') ?>" class="<?= $active === 'home' ? 'active' : '' ?>">Home</a>
- <a href="<?= e($base) ?>showreel.php" class="<?= $active === 'showreel' ? 'active' : '' ?>">Showreel</a>
- <a href="<?= e($base) ?>contact.php" class="<?= $active === 'contact' ? 'active' : '' ?>">Contact</a>
- </nav>
- </header>
- <?php
- }
- function public_footer(): void
- {
- $base = base();
- ?><footer class="footer">
- <nav class="footer-links">
- <a href="<?= e($base) ?>impressum.php">Impressum</a>
- <a href="<?= e($base) ?>datenschutz.php">Datenschutz</a>
- </nav>
- <span>© <?= date('Y') ?> <?= e(site_get()['intro_title'] ?: config('site.name', '')) ?></span>
- </footer>
- <script src="<?= e($base) ?>assets/site.js"></script>
- </body>
- </html>
- <?php
- }
- function admin_header(string $title, string $active = ''): void
- {
- ?><!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title><?= e($title) ?> · Admin</title>
- <link rel="stylesheet" href="../assets/site.css">
- </head>
- <body class="admin">
- <header class="admin-nav">
- <a class="nav-brand" href="index.php">Backoffice</a>
- <nav class="nav-links">
- <a href="frontpage.php" class="<?= $active === 'frontpage' ? 'active' : '' ?>">Front page</a>
- <a href="showreel.php" class="<?= $active === 'showreel' ? 'active' : '' ?>">Showreel</a>
- <a href="galleries.php" class="<?= $active === 'galleries' ? 'active' : '' ?>">Galleries</a>
- <a href="contact.php" class="<?= $active === 'contact' ? 'active' : '' ?>">Contact</a>
- <a href="settings.php" class="<?= $active === 'settings' ? 'active' : '' ?>">Settings</a>
- <a href="../" target="_blank" rel="noopener">View site ↗</a>
- <a href="logout.php">Log out</a>
- </nav>
- </header>
- <main class="admin-main">
- <?php
- }
- function admin_footer(): void
- {
- ?></main>
- </body>
- </html>
- <?php
- }
- /**
- * The "resize uploads to" control, shared by the gallery create and settings
- * forms. $current is the gallery's stored cap in pixels, or null for Original.
- *
- * The select carries pixel values, never names, so the handler
- * (parse_max_resolution) never has to map a label back to a number. The custom
- * input is revealed by a tiny delegated script emitted once per page —
- * assets/admin.js is the uploader and is not loaded on galleries.php.
- */
- function resolution_field(?int $current = null): void
- {
- $isPreset = $current !== null && in_array($current, RESOLUTION_PRESETS, true);
- $isCustom = $current !== null && !$isPreset;
- ?>
- <label for="res">Resize uploads to <span style="text-transform:none;letter-spacing:0">(longest edge)</span></label>
- <div class="res-field">
- <select id="res" name="max_resolution">
- <option value="" <?= $current === null ? 'selected' : '' ?>>Original — no resize</option>
- <?php foreach (RESOLUTION_PRESETS as $px): ?>
- <option value="<?= $px ?>" <?= $current === $px ? 'selected' : '' ?>><?= e(resolution_label($px)) ?></option>
- <?php endforeach; ?>
- <option value="custom" <?= $isCustom ? 'selected' : '' ?>>Custom…</option>
- </select>
- <input type="number" name="max_resolution_custom" placeholder="e.g. 3000"
- min="<?= RESOLUTION_MIN ?>" max="<?= RESOLUTION_MAX ?>"
- value="<?= $isCustom ? (int)$current : '' ?>" <?= $isCustom ? '' : 'hidden' ?>>
- </div>
- <p class="help">
- Images larger than this are downscaled in the browser before uploading,
- which also keeps them under the server's upload limit. The re-encode
- drops EXIF data (camera, lens, date, location) — choose Original to keep
- it. Files the browser cannot read, such as RAW, are always uploaded
- untouched.
- </p>
- <?php
- static $scriptDone = false;
- if ($scriptDone) {
- return;
- }
- $scriptDone = true;
- ?>
- <script>
- document.addEventListener('change', function (e) {
- var select = e.target.closest('.res-field select');
- if (!select) return;
- var custom = select.parentNode.querySelector('input[name="max_resolution_custom"]');
- custom.hidden = select.value !== 'custom';
- if (!custom.hidden) custom.focus();
- });
- </script>
- <?php
- }
- /** One-shot status message helpers (flash messages via session). */
- function flash_set(string $msg, string $kind = 'ok'): void
- {
- session_boot();
- $_SESSION['flash'] = ['msg' => $msg, 'kind' => $kind];
- }
- function flash_render(): void
- {
- session_boot();
- if (!empty($_SESSION['flash'])) {
- $f = $_SESSION['flash'];
- unset($_SESSION['flash']);
- echo '<div class="flash flash-' . e($f['kind']) . '">' . e($f['msg']) . '</div>';
- }
- }
|