Added customizable colours of the public page with palettes and live preview. Fixes #14

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 21:54:24 +02:00
co-authored by Claude Opus 5.5
parent 1a1688d322
commit 9da18a67ac
12 changed files with 760 additions and 11 deletions
+172 -4
View File
@@ -1,10 +1,168 @@
/*
* Public booking page.
*
* Settings from the appearance table come as CSS variables in the style attribute
* (Appearance::banner_style in src/backend/data.rs), so nothing here is generated.
* The customer's appearance settings come as CSS variables in style attributes
* (Colors::style in src/backend/color.rs, Appearance::banner_style in src/backend/data.rs),
* so nothing here is generated. The rules are scoped by .public-page, which wraps the public
* route and the preview in the appearance settings; the admin portal keeps its own colours.
* They override the colours that the vendor template hard-codes, and they set everything
* that assets/css/dark.css sets, so the preview looks the same in both admin themes.
******************************************************************************/
.public-page {
--rz-primary: #696cff;
--rz-bg: #f5f5f9;
--rz-card: #ffffff;
--rz-text: #566a7f;
--rz-on-primary: #ffffff;
--rz-scheme: light;
/* Shades derived from the settings */
--rz-body: color-mix(in srgb, var(--rz-text) 89%, var(--rz-card));
--rz-muted: color-mix(in srgb, var(--rz-text) 50%, var(--rz-card));
--rz-border: color-mix(in srgb, var(--rz-text) 22%, var(--rz-card));
--rz-primary-hover: color-mix(in srgb, var(--rz-primary) 90%, var(--rz-on-primary));
--rz-primary-shadow: color-mix(in srgb, var(--rz-primary) 40%, transparent);
min-height: 100vh;
color: var(--rz-body);
background-color: var(--rz-bg);
color-scheme: var(--rz-scheme);
}
/* Preview in the appearance settings */
.public-page.public-preview {
min-height: 0;
border: 1px solid var(--rz-border);
border-radius: 0.5rem;
overflow: hidden;
}
/* Typography */
.public-page h1, .public-page h2, .public-page h3,
.public-page h4, .public-page h5, .public-page h6 {
color: var(--rz-text);
}
.public-page a {
color: var(--rz-primary);
}
.public-page a:hover {
color: var(--rz-primary-hover);
}
/* Cards and dialogs */
.public-page .card {
background-color: var(--rz-card);
border-color: var(--rz-border);
box-shadow: 0 2px 6px 0 rgba(67, 89, 113, 0.12);
}
.public-page .modal-content {
color: var(--rz-body);
background-color: var(--rz-card);
box-shadow: 0 2px 20px 0 rgba(67, 89, 113, 0.45);
}
/* Forms */
.public-page .form-label,
.public-page .form-check-label {
color: var(--rz-text);
}
.public-page .form-control {
color: var(--rz-body);
background-color: var(--rz-card);
border-color: var(--rz-border);
}
.public-page .form-control::placeholder {
color: var(--rz-muted);
}
.public-page .form-control:focus {
color: var(--rz-body);
background-color: var(--rz-card);
border-color: var(--rz-primary);
box-shadow: 0 0 0.25rem 0.05rem color-mix(in srgb, var(--rz-primary) 10%, transparent);
}
.public-page .form-check-input:not(:checked) {
background-color: var(--rz-card);
border-color: var(--rz-border);
}
.public-page .form-check-input:focus {
border-color: var(--rz-primary);
box-shadow: 0 2px 4px 0 var(--rz-primary-shadow);
}
.public-page .form-check-input:checked {
background-color: var(--rz-primary);
border-color: var(--rz-primary);
box-shadow: 0 2px 4px 0 var(--rz-primary-shadow);
}
/* Buttons */
.public-page .btn-primary {
color: var(--rz-on-primary);
background-color: var(--rz-primary);
border-color: var(--rz-primary);
box-shadow: 0 0.125rem 0.25rem 0 var(--rz-primary-shadow);
}
.public-page .btn-primary:hover,
.public-page .btn-primary:focus {
color: var(--rz-on-primary);
background-color: var(--rz-primary-hover);
border-color: var(--rz-primary-hover);
}
.public-page .btn-outline-secondary {
color: var(--rz-body);
border-color: var(--rz-muted);
}
.public-page .btn-outline-secondary:hover,
.public-page .btn-outline-secondary:focus {
color: var(--rz-card);
background-color: var(--rz-body);
border-color: var(--rz-body);
}
/* Time slots: a hidden checkbox followed by its label */
.public-page .btn-outline-primary {
color: var(--rz-primary);
border-color: var(--rz-primary);
background: transparent;
}
.public-page .btn-outline-primary:hover,
.public-page .btn-check:checked + .btn-outline-primary {
color: var(--rz-on-primary);
background-color: var(--rz-primary);
border-color: var(--rz-primary);
box-shadow: 0 0.125rem 0.25rem 0 var(--rz-primary-shadow);
}
.public-page .btn-check:focus + .btn-outline-primary {
border-color: var(--rz-primary-hover);
}
/* Captcha */
.public-page .leptos-captcha {
color: var(--rz-body);
}
/* Banner */
.public-banner {
height: 100px;
clip-path: inset(0 0 0 0);
@@ -14,7 +172,8 @@
color: white;
}
.public-banner-title {
/* Two classes, to win over the heading colour above */
.public-banner .public-banner-title {
font-size: x-large;
color: white;
text-shadow: -3px -3px 0 #000, 3px -3px 0 #000, -3px 3px 0 #000, 3px 3px 0 #000;
@@ -29,7 +188,16 @@
height: 200px;
}
.public-banner-title {
.public-banner .public-banner-title {
font-size: xxx-large;
}
}
/* The preview is narrow whatever the width of the window */
.public-preview .public-banner {
height: 100px;
}
.public-preview .public-banner .public-banner-title {
font-size: x-large;
}