feat: add PWA support and mobile admin card layout

All pages: manifest link, apple-mobile-web-app meta tags, theme-color,
viewport-fit=cover, overscroll-behavior:none, safe-area padding, 16px
input font-size to prevent iOS zoom, SW registration.

admin.html: card-per-entry layout on small screens (d-md-none) with
name, location, timestamp, newsletter status, email, comment, and
delete button. Desktop table unchanged (d-none d-md-block).
This commit is contained in:
2026-03-29 19:20:29 -06:00
parent 3057201102
commit 047f57513d
4 changed files with 177 additions and 44 deletions
+57 -1
View File
@@ -2,9 +2,22 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Guestbook Admin</title> <title>Guestbook Admin</title>
<!-- PWA -->
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/static/images/logo.png" />
<meta name="theme-color" content="#3a9cb8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<style>
html, body { overscroll-behavior: none; }
body { padding-bottom: env(safe-area-inset-bottom); }
input, select, textarea { font-size: 16px !important; }
</style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
<div class="container py-4"> <div class="container py-4">
@@ -19,6 +32,40 @@
</div> </div>
</div> </div>
<!-- Mobile card list (phones) -->
<div class="d-md-none">
{% for g in guests %}
<div class="card mb-2 shadow-sm">
<div class="card-body py-2 px-3">
<div class="d-flex justify-content-between align-items-start">
<div>
<strong>{{ g[1] }} {{ g[2] }}</strong>
<div class="text-muted small">{{ g[4] }} &middot; {{ g[7] | localtime }}</div>
<div class="small mt-1">Newsletter: {{ 'Yes' if g[6] else 'No' }}</div>
{% if g[3] %}
<div class="text-muted small">{{ g[3] }}</div>
{% endif %}
{% if g[5] %}
<div class="text-muted small fst-italic">{{ g[5] }}</div>
{% endif %}
</div>
{% if current_user.role != 'viewer' %}
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button type="submit" class="btn btn-danger btn-sm ms-2">Delete</button>
</form>
{% endif %}
</div>
</div>
</div>
{% else %}
<p class="text-muted text-center">No entries found.</p>
{% endfor %}
</div>
<!-- Desktop table (hidden on phones) -->
<div class="d-none d-md-block">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-bordered table-hover bg-white"> <table class="table table-bordered table-hover bg-white">
<thead class="table-dark"> <thead class="table-dark">
@@ -61,6 +108,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
{% if total_pages > 1 %} {% if total_pages > 1 %}
<nav> <nav>
@@ -80,5 +128,13 @@
</nav> </nav>
{% endif %} {% endif %}
</div> </div>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
});
}
</script>
</body> </body>
</html> </html>
+22 -1
View File
@@ -2,9 +2,22 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Guestbook Admin — Login</title> <title>Guestbook Admin — Login</title>
<!-- PWA -->
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/static/images/logo.png" />
<meta name="theme-color" content="#3a9cb8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<style>
html, body { overscroll-behavior: none; }
body { padding-bottom: env(safe-area-inset-bottom); }
input.form-control { font-size: 16px !important; }
</style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
<div class="container py-5" style="max-width: 400px;"> <div class="container py-5" style="max-width: 400px;">
@@ -31,5 +44,13 @@
</div> </div>
</div> </div>
</div> </div>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
});
}
</script>
</body> </body>
</html> </html>
+22 -1
View File
@@ -2,9 +2,22 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Guestbook Admin — Users</title> <title>Guestbook Admin — Users</title>
<!-- PWA -->
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/static/images/logo.png" />
<meta name="theme-color" content="#3a9cb8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<style>
html, body { overscroll-behavior: none; }
body { padding-bottom: env(safe-area-inset-bottom); }
input.form-control, select.form-select { font-size: 16px !important; }
</style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
<div class="container py-4" style="max-width: 700px;"> <div class="container py-4" style="max-width: 700px;">
@@ -76,5 +89,13 @@
Admins can view and delete entries. Viewers can only view. Admins can view and delete entries. Viewers can only view.
</p> </p>
</div> </div>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
});
}
</script>
</body> </body>
</html> </html>
+36 -1
View File
@@ -3,9 +3,17 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>${SITE_TITLE}</title> <title>${SITE_TITLE}</title>
<!-- PWA -->
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/static/images/logo.png" />
<meta name="theme-color" content="#3a9cb8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="${SITE_TITLE}" />
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<!-- Fonts --> <!-- Fonts -->
@@ -20,6 +28,23 @@
font-weight: 700; font-weight: 700;
} }
/* PWA / iOS kiosk */
html, body {
height: 100%;
overscroll-behavior: none;
}
body {
padding-bottom: env(safe-area-inset-bottom);
}
/* Prevent iOS auto-zoom on input focus (requires >= 16px) */
input.form-control,
textarea.form-control,
select.form-select {
font-size: 16px !important;
}
/* Scrolling marquee styles */ /* Scrolling marquee styles */
.scrolling-wrapper { .scrolling-wrapper {
overflow: hidden; overflow: hidden;
@@ -29,6 +54,7 @@
width: 100%; width: 100%;
background-color: #f8f9fa; background-color: #f8f9fa;
border-top: 1px solid #dee2e6; border-top: 1px solid #dee2e6;
padding-bottom: env(safe-area-inset-bottom);
} }
.scrolling-content { .scrolling-content {
@@ -172,6 +198,15 @@
<!-- Bootstrap JS (optional) --> <!-- Bootstrap JS (optional) -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"></script>
<!-- Service worker registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
});
}
</script>
</body> </body>
</html> </html>