partials.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. <?php
  2. /**
  3. * Shared HTML fragments for the public site and the admin backoffice.
  4. * Public pages sit in the document root and admin pages in admin/, so asset
  5. * paths differ: public pages use 'assets/...', admin pages use '../assets/...'.
  6. */
  7. declare(strict_types=1);
  8. function public_header(string $title, string $active = '', string $bodyClass = ''): void
  9. {
  10. $site = site_get();
  11. $name = $site['intro_title'] ?: config('site.name', 'Portfolio');
  12. ?><!DOCTYPE html>
  13. <html lang="en">
  14. <head>
  15. <meta charset="utf-8">
  16. <meta name="viewport" content="width=device-width, initial-scale=1">
  17. <title><?= e($title) ?></title>
  18. <link rel="stylesheet" href="assets/site.css">
  19. </head>
  20. <body class="<?= e($bodyClass) ?>">
  21. <header class="nav" id="nav">
  22. <a class="nav-brand" href="./"><?= e($name) ?></a>
  23. <nav class="nav-links">
  24. <a href="./" class="<?= $active === 'home' ? 'active' : '' ?>">Home</a>
  25. <a href="showreel.php" class="<?= $active === 'showreel' ? 'active' : '' ?>">Showreel</a>
  26. <a href="contact.php" class="<?= $active === 'contact' ? 'active' : '' ?>">Contact</a>
  27. </nav>
  28. </header>
  29. <?php
  30. }
  31. function public_footer(): void
  32. {
  33. ?><footer class="footer">
  34. <nav class="footer-links">
  35. <a href="impressum.php">Impressum</a>
  36. <a href="datenschutz.php">Datenschutz</a>
  37. </nav>
  38. <span>&copy; <?= date('Y') ?> <?= e(site_get()['intro_title'] ?: config('site.name', '')) ?></span>
  39. </footer>
  40. <script src="assets/site.js"></script>
  41. </body>
  42. </html>
  43. <?php
  44. }
  45. function admin_header(string $title, string $active = ''): void
  46. {
  47. ?><!DOCTYPE html>
  48. <html lang="en">
  49. <head>
  50. <meta charset="utf-8">
  51. <meta name="viewport" content="width=device-width, initial-scale=1">
  52. <title><?= e($title) ?> · Admin</title>
  53. <link rel="stylesheet" href="../assets/site.css">
  54. </head>
  55. <body class="admin">
  56. <header class="admin-nav">
  57. <a class="nav-brand" href="index.php">Backoffice</a>
  58. <nav class="nav-links">
  59. <a href="frontpage.php" class="<?= $active === 'frontpage' ? 'active' : '' ?>">Front page</a>
  60. <a href="showreel.php" class="<?= $active === 'showreel' ? 'active' : '' ?>">Showreel</a>
  61. <a href="galleries.php" class="<?= $active === 'galleries' ? 'active' : '' ?>">Galleries</a>
  62. <a href="contact.php" class="<?= $active === 'contact' ? 'active' : '' ?>">Contact</a>
  63. <a href="settings.php" class="<?= $active === 'settings' ? 'active' : '' ?>">Settings</a>
  64. <a href="../" target="_blank" rel="noopener">View site ↗</a>
  65. <a href="logout.php">Log out</a>
  66. </nav>
  67. </header>
  68. <main class="admin-main">
  69. <?php
  70. }
  71. function admin_footer(): void
  72. {
  73. ?></main>
  74. </body>
  75. </html>
  76. <?php
  77. }
  78. /**
  79. * The "resize uploads to" control, shared by the gallery create and settings
  80. * forms. $current is the gallery's stored cap in pixels, or null for Original.
  81. *
  82. * The select carries pixel values, never names, so the handler
  83. * (parse_max_resolution) never has to map a label back to a number. The custom
  84. * input is revealed by a tiny delegated script emitted once per page —
  85. * assets/admin.js is the uploader and is not loaded on galleries.php.
  86. */
  87. function resolution_field(?int $current = null): void
  88. {
  89. $isPreset = $current !== null && in_array($current, RESOLUTION_PRESETS, true);
  90. $isCustom = $current !== null && !$isPreset;
  91. ?>
  92. <label for="res">Resize uploads to <span style="text-transform:none;letter-spacing:0">(longest edge)</span></label>
  93. <div class="res-field">
  94. <select id="res" name="max_resolution">
  95. <option value="" <?= $current === null ? 'selected' : '' ?>>Original — no resize</option>
  96. <?php foreach (RESOLUTION_PRESETS as $px): ?>
  97. <option value="<?= $px ?>" <?= $current === $px ? 'selected' : '' ?>><?= e(resolution_label($px)) ?></option>
  98. <?php endforeach; ?>
  99. <option value="custom" <?= $isCustom ? 'selected' : '' ?>>Custom…</option>
  100. </select>
  101. <input type="number" name="max_resolution_custom" placeholder="e.g. 3000"
  102. min="<?= RESOLUTION_MIN ?>" max="<?= RESOLUTION_MAX ?>"
  103. value="<?= $isCustom ? (int)$current : '' ?>" <?= $isCustom ? '' : 'hidden' ?>>
  104. </div>
  105. <p class="help">
  106. Images larger than this are downscaled in the browser before uploading,
  107. which also keeps them under the server's upload limit. The re-encode
  108. drops EXIF data (camera, lens, date, location) — choose Original to keep
  109. it. Files the browser cannot read, such as RAW, are always uploaded
  110. untouched.
  111. </p>
  112. <?php
  113. static $scriptDone = false;
  114. if ($scriptDone) {
  115. return;
  116. }
  117. $scriptDone = true;
  118. ?>
  119. <script>
  120. document.addEventListener('change', function (e) {
  121. var select = e.target.closest('.res-field select');
  122. if (!select) return;
  123. var custom = select.parentNode.querySelector('input[name="max_resolution_custom"]');
  124. custom.hidden = select.value !== 'custom';
  125. if (!custom.hidden) custom.focus();
  126. });
  127. </script>
  128. <?php
  129. }
  130. /** One-shot status message helpers (flash messages via session). */
  131. function flash_set(string $msg, string $kind = 'ok'): void
  132. {
  133. session_boot();
  134. $_SESSION['flash'] = ['msg' => $msg, 'kind' => $kind];
  135. }
  136. function flash_render(): void
  137. {
  138. session_boot();
  139. if (!empty($_SESSION['flash'])) {
  140. $f = $_SESSION['flash'];
  141. unset($_SESSION['flash']);
  142. echo '<div class="flash flash-' . e($f['kind']) . '">' . e($f['msg']) . '</div>';
  143. }
  144. }