mirror of
https://github.com/tmdinosaurcenter/kiosk-guestbook.git
synced 2026-06-04 01:18:12 -06:00
617aa5f028
Adds FIELD_MAX constants and server-side length checks in the index route. Adds matching maxlength attributes on all form inputs so the browser enforces limits before submission.
177 lines
6.5 KiB
Plaintext
177 lines
6.5 KiB
Plaintext
<!doctype html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>${SITE_TITLE}</title>
|
|
|
|
<!-- Bootstrap CSS -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
<!-- Fonts -->
|
|
<link href="https://fonts.googleapis.com/css2?family=Vollkorn:wght@700&family=Open+Sans&display=swap" rel="stylesheet" />
|
|
<style>
|
|
body {
|
|
font-family: 'Open Sans', sans-serif;
|
|
}
|
|
|
|
h1, h2, h3, h4, h5, h6 {
|
|
font-family: 'Vollkorn', serif;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Scrolling marquee styles */
|
|
.scrolling-wrapper {
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
position: fixed;
|
|
bottom: 0;
|
|
width: 100%;
|
|
background-color: #f8f9fa;
|
|
border-top: 1px solid #dee2e6;
|
|
}
|
|
|
|
.scrolling-content {
|
|
display: inline-block;
|
|
padding: 10px;
|
|
font-size: 1.25rem;
|
|
animation: scroll-left linear infinite;
|
|
}
|
|
|
|
@keyframes scroll-left {
|
|
0% {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
100% {
|
|
transform: translateX(-50%);
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container mt-5 mb-5">
|
|
<header class="d-flex align-items-center mb-4">
|
|
<img src="${LOGO_URL}" alt="Logo" class="me-3" style="height: 50px;" />
|
|
<h1 class="h3 mb-0">${SITE_TITLE}</h1>
|
|
</header>
|
|
|
|
<!-- Brief instructions for the form -->
|
|
<div class="alert alert-info" role="alert">
|
|
Please fill in your details below. First name, last name, and location are required.
|
|
Providing your email is optional, but it helps us follow up if needed.
|
|
</div>
|
|
|
|
{% if error %}
|
|
<div class="alert alert-danger" role="alert">
|
|
{{ error }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form method="post" action="/" class="mb-4">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
|
<div class="mb-3">
|
|
<label for="first_name" class="form-label">First Name(s):</label>
|
|
<input type="text" class="form-control" id="first_name" name="first_name" maxlength="100" required />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="last_name" class="form-label">Last Name:</label>
|
|
<input type="text" class="form-control" id="last_name" name="last_name" maxlength="100" required />
|
|
</div>
|
|
|
|
<!-- Email + Newsletter Block (fully fixed) -->
|
|
<div class="mb-3">
|
|
<label for="email" class="form-label">Email (Optional):</label>
|
|
<input type="email" class="form-control" id="email" name="email" maxlength="254" />
|
|
|
|
<div class="form-check mt-2">
|
|
<input class="form-check-input" type="checkbox" name="newsletter_opt_in" id="newsletter_opt_in"
|
|
checked />
|
|
<label class="form-check-label" for="newsletter_opt_in">
|
|
Subscribe our newsletter
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="location" class="form-label">Location:</label>
|
|
<input type="text" class="form-control" id="location" name="location" maxlength="100" required />
|
|
</div>
|
|
|
|
<!-- Comment field hidden by default -->
|
|
<div class="mb-3" id="comment-field" style="display: none;">
|
|
<label for="comment" class="form-label">Comment (Optional):</label>
|
|
<textarea class="form-control" id="comment" name="comment" rows="3" maxlength="2000"></textarea>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary">Submit</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Scrolling Guest Entries at the Bottom -->
|
|
<!-- Content is duplicated so the loop is seamless: animate 0 → -50% -->
|
|
<div class="scrolling-wrapper">
|
|
<div class="scrolling-content">
|
|
{% for guest in guests %}
|
|
<span class="me-5">
|
|
<strong>{{ guest[0] }}</strong> from {{ guest[1] }}
|
|
</span>
|
|
{% endfor %}
|
|
{% for guest in guests %}
|
|
<span class="me-5">
|
|
<strong>{{ guest[0] }}</strong> from {{ guest[1] }}
|
|
</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Set scrolling speed to a fixed pixels-per-second rate -->
|
|
<script>
|
|
(function () {
|
|
const pixelsPerSecond = 80;
|
|
const content = document.querySelector(".scrolling-content");
|
|
|
|
function updateScrollSpeed() {
|
|
// Travel distance is half the total width (one copy of the list)
|
|
const oneCopyWidth = content.offsetWidth / 2;
|
|
content.style.animationDuration = (oneCopyWidth / pixelsPerSecond) + "s";
|
|
}
|
|
|
|
updateScrollSpeed();
|
|
window.addEventListener("resize", updateScrollSpeed);
|
|
})();
|
|
</script>
|
|
|
|
<!-- JavaScript to reveal the comment field -->
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const firstNameInput = document.getElementById("first_name");
|
|
const lastNameInput = document.getElementById("last_name");
|
|
const locationInput = document.getElementById("location");
|
|
const commentField = document.getElementById("comment-field");
|
|
|
|
function checkFields() {
|
|
if (
|
|
firstNameInput.value.trim().length >= 3 &&
|
|
lastNameInput.value.trim().length >= 3 &&
|
|
locationInput.value.trim().length >= 3
|
|
) {
|
|
commentField.style.display = "block";
|
|
} else {
|
|
commentField.style.display = "none";
|
|
}
|
|
}
|
|
|
|
firstNameInput.addEventListener("input", checkFields);
|
|
lastNameInput.addEventListener("input", checkFields);
|
|
locationInput.addEventListener("input", checkFields);
|
|
});
|
|
</script>
|
|
|
|
<!-- 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/bootstrap@5.1.3/dist/js/bootstrap.min.js"></script>
|
|
</body>
|
|
|
|
</html> |