diff --git a/Cargo.toml b/Cargo.toml index e1d3db7..1029196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "web-petting" -version = "1.0.4" +version = "1.0.5" edition = "2024" default-run = "web-petting" diff --git a/src/public.rs b/src/public.rs index 36e995d..7d3009b 100644 --- a/src/public.rs +++ b/src/public.rs @@ -1046,6 +1046,7 @@ async fn serve_static(_request: Request, Path(filename): Path) -> cot::R }, }; let content_type = match ext { + "css" => "text/css; charset=utf-8", "png" => "image/png", "jpg" | "jpeg" => "image/jpeg", "webp" => "image/webp", diff --git a/static/admin.css b/static/admin.css new file mode 100644 index 0000000..fba1873 --- /dev/null +++ b/static/admin.css @@ -0,0 +1,708 @@ +:root { + --admin-accent: #7567e8; + --admin-accent-dark: #6255cf; + --admin-accent-soft: #efedff; + --admin-bg: #f6f5f9; + --admin-surface: #fff; + --admin-surface-soft: #fbfaff; + --admin-border: #e7e5ef; + --admin-border-soft: #eeecf3; + --admin-text: #302c49; + --admin-muted: #817c94; + --admin-danger: #b54e5b; + --admin-success: #177349; + color-scheme: light; +} + +* { box-sizing: border-box; } + +html { background: var(--admin-bg); } + +body { + min-height: 100vh; + background: var(--admin-bg); + color: var(--admin-text); + font-size: 15px; +} + +a { color: var(--admin-accent-dark); } + +/* Application shell */ +.admin-shell { padding-bottom: 4.35rem; } + +.top-header { + position: sticky; + top: 0; + z-index: 30; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + min-height: 3.5rem; + padding: .55rem 1rem; + background: rgba(255,255,255,.94); + border-bottom: 1px solid var(--admin-border); + box-shadow: 0 1px 8px rgba(42,34,80,.035); + backdrop-filter: blur(10px); +} + +.top-header .brand { + flex: 0 0 auto; + color: var(--admin-text); + font-size: 1.05rem; + font-weight: 800; + text-decoration: none; +} + +.brand-version { + color: #aaa5b6; + font-size: .66rem; + font-weight: 500; +} + +.top-header-right { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: .7rem; + font-size: .8rem; +} + +.top-header-right a { color: #777184; text-decoration: none; } +.top-header-right a:hover { color: var(--admin-accent-dark); } +.top-header-right .admin-name { color: #aaa5b6; } + +.desktop-nav { display: none; } + +.bottom-tabs { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 30; + display: flex; + height: 3.65rem; + overflow-x: auto; + background: rgba(255,255,255,.96); + border-top: 1px solid var(--admin-border); + box-shadow: 0 -4px 18px rgba(42,34,80,.06); + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + backdrop-filter: blur(10px); +} + +.bottom-tabs::-webkit-scrollbar { display: none; } + +.bottom-tabs a { + display: flex; + flex: 1 0 3.2rem; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 3.2rem; + padding: 0 .42rem; + color: #9993a8; + font-size: .59rem; + font-weight: 700; + text-decoration: none; + white-space: nowrap; +} + +.bottom-tabs a .tab-icon { font-size: 1.2rem; line-height: 1; } +.bottom-tabs a .tab-label { display: block; margin-top: .12rem; } +.bottom-tabs a.is-active { color: var(--admin-accent); } + +.main-content { + width: 100%; + max-width: 980px; + margin: 0 auto; + padding: 1.25rem 1rem 2rem; +} + +/* Typography and page chrome */ +.page-head { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: .8rem; + margin-bottom: 1.25rem; +} + +.page-head h1 { + margin: 0; + color: var(--admin-text); + font-size: 1.55rem; + font-weight: 800; + letter-spacing: -.02em; +} + +.page-head p, +.page-description { + margin: .25rem 0 0; + color: var(--admin-muted); + font-size: .86rem; + line-height: 1.45; +} + +.page-head-actions, +.toolbar, +.action-row { + display: flex; + align-items: center; + gap: .5rem; + flex-wrap: wrap; +} + +.section-title { + margin: 1.55rem 0 .75rem; + color: #514b69; + font-size: .82rem; + font-weight: 800; + letter-spacing: .04em; + text-transform: uppercase; +} +.section-inline-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: .75rem; } +.section-inline-head .label { margin: 0; } +.form-card hr { height: 1px; margin: 1rem 0; background: var(--admin-border-soft); border: 0; } + +.empty-state { + padding: 2rem 1rem; + color: var(--admin-muted); + text-align: center; + background: rgba(255,255,255,.65); + border: 1px dashed #dcd8e8; + border-radius: 12px; +} + +/* Buttons */ +.button { + border-radius: 8px; + box-shadow: none !important; + font-weight: 700; + transition: transform .12s, border-color .15s, background .15s; +} + +.button:hover { transform: translateY(-1px); } +.button:active { transform: none; } +.button.is-primary { background: var(--admin-accent); border-color: var(--admin-accent); } +.button.is-primary:not(.is-outlined):not(.is-light), +.button.is-link:not(.is-outlined):not(.is-light), +.button.is-info:not(.is-outlined):not(.is-light), +.button.is-success:not(.is-outlined):not(.is-light), +.button.is-danger:not(.is-outlined):not(.is-light) { color: #fff !important; } +.button.is-primary:hover { color: #fff !important; background: var(--admin-accent-dark); border-color: var(--admin-accent-dark); } +.button.is-primary.is-outlined { color: var(--admin-accent-dark) !important; background: transparent; border-color: #a49bea; } +.button.is-primary.is-outlined:hover { color: #fff !important; background: var(--admin-accent); border-color: var(--admin-accent); } +.button.is-light { background: #f3f1f8; color: #5f5971; } +.button[disabled] { color: #8d8798 !important; background: #eceaf0 !important; border-color: #e0dde7 !important; opacity: 1; transform: none; } +.button.is-small, +.btn-sm { height: auto !important; padding: .32rem .65rem !important; font-size: .75rem !important; } + +/* Reusable cards and sections */ +.admin-section, +.form-card { + overflow: hidden; + background: var(--admin-surface); + border: 1px solid var(--admin-border); + border-radius: 14px; + box-shadow: 0 2px 10px rgba(42,34,80,.035); +} + +.form-card { padding: 1.2rem; } + +.admin-section-head { + display: grid; + grid-template-columns: 42px minmax(0,1fr); + gap: .8rem; + padding: 1rem 1.15rem; + background: linear-gradient(180deg,#fff,var(--admin-surface-soft)); + border-bottom: 1px solid #eceaf2; +} + +.admin-section-icon { + display: flex; + align-items: center; + justify-content: center; + width: 42px; + height: 42px; + background: var(--admin-accent-soft); + border-radius: 11px; + font-size: 1.2rem; +} + +.admin-section-head h2 { + margin: 0; + color: var(--admin-text); + font-size: 1.05rem; + font-weight: 750; +} + +.admin-section-head p { + max-width: 680px; + margin: .2rem 0 0; + color: var(--admin-muted); + font-size: .82rem; + line-height: 1.45; +} + +.admin-section-body { padding: 1.15rem; } + +.admin-title-row { + display: flex; + align-items: center; + gap: .55rem; + flex-wrap: wrap; +} + +.status-pill { + display: inline-flex; + align-items: center; + padding: .12rem .48rem; + color: #777184; + background: #eeedf2; + border-radius: 99px; + font-size: .67rem; + font-weight: 700; +} + +.status-pill.is-enabled { color: var(--admin-success); background: #dcf7e8; } + +.admin-grid, +.form-grid { + display: grid; + grid-template-columns: repeat(2,minmax(0,1fr)); + gap: 1rem 1.1rem; +} +.form-grid > .field { margin: 0; } +.page-head .button.is-primary { min-height: 2.25rem; padding: .42rem .8rem !important; } + +.admin-field { min-width: 0; margin: 0 !important; } +.admin-field-wide { grid-column: 1 / -1; } + +.admin-subsection { + margin-top: 1.1rem; + padding-top: 1.1rem; + border-top: 1px solid var(--admin-border-soft); +} + +.admin-subsection h2, +.admin-subsection h3, +.form-card > h2 { + margin: 0 0 .75rem; + color: #514b69; + font-size: .82rem; + font-weight: 800; + letter-spacing: .04em; + text-transform: uppercase; +} + +.item-card { + display: block; + margin-bottom: .65rem; + padding: .9rem 1rem; + color: inherit; + background: var(--admin-surface); + border: 1px solid var(--admin-border); + border-radius: 12px; + box-shadow: 0 1px 6px rgba(42,34,80,.025); + transition: border-color .15s, box-shadow .15s, transform .15s; +} + +.item-card:hover { + border-color: #dcd8eb; + box-shadow: 0 5px 18px rgba(42,34,80,.055); +} + +.item-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: .75rem; + margin-bottom: .35rem; +} + +.item-card-header .name { + min-width: 0; + color: var(--admin-text); + font-size: .95rem; + font-weight: 750; +} + +.item-card-meta { + display: flex; + gap: .25rem 1rem; + flex-wrap: wrap; + color: var(--admin-muted); + font-size: .79rem; + line-height: 1.55; +} + +.item-card-meta a { color: inherit; text-decoration: none; } + +.item-card-actions { + display: flex; + gap: .4rem; + flex-wrap: wrap; + margin-top: .65rem; + padding-top: .6rem; + border-top: 1px solid var(--admin-border-soft); +} + +.item-card-actions form { margin: 0; } +.color-dot { display: inline-block; width: 11px; height: 11px; margin-right: 6px; border-radius: 50%; vertical-align: middle; } +.card-link { color: inherit; text-decoration: none; } +.card-note { margin-top: .35rem; color: var(--admin-muted); font-size: .78rem; line-height: 1.45; } +.feedback-card { border-left: 3px solid var(--admin-accent); text-decoration: none; } +.feedback-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: .3rem; } +.feedback-date { color: #9993a8; font-size: .75rem; white-space: nowrap; } +.feedback-text { color: #4a4570; font-size: .82rem; line-height: 1.5; } +.muted-detail { color: var(--admin-muted); font-weight: 450; } + +/* Statuses */ +.badge { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + padding: .16rem .58rem; + border-radius: 99px; + font-size: .69rem; + font-weight: 750; + white-space: nowrap; +} + +.badge-new, +.badge-visit-scheduled { color: #1e40af; background: #dbeafe; } +.badge-in_progress { color: #92400e; background: #fef3c7; } +.badge-converted, +.badge-active, +.badge-visit-completed { color: #065f46; background: #d1fae5; } +.badge-rejected { color: #991b1b; background: #fee2e2; } +.badge-archived, +.badge-visit-cancelled { color: #554f61; background: #ebe9ef; } + +/* Forms */ +.field:not(:last-child) { margin-bottom: 1rem; } +.field .label, +.admin-field .label { + margin-bottom: .38rem; + color: #514c66 !important; + font-size: .76rem; + font-weight: 750; +} + +input, +textarea, +select, +.input, +.textarea, +.select select { + color: var(--admin-text) !important; + background-color: #fff !important; + border-color: #dcd9e5 !important; + border-radius: 8px !important; + box-shadow: none !important; + font-size: .86rem; +} + +.input, +.select select { min-height: 2.45rem; } +.textarea { min-height: 76px; resize: vertical; } + +input:focus, +textarea:focus, +select:focus, +.input:focus, +.textarea:focus, +.select select:focus { + border-color: #7c6cff !important; + box-shadow: 0 0 0 2px rgba(124,108,255,.12) !important; +} + +input[readonly], +.input[readonly] { color: #716b81 !important; background: #f8f7fa !important; } + +input[type="file"].input { padding: .36rem .55rem; } +input[type="color"] { width: 3rem; height: 2.35rem; padding: 2px; cursor: pointer; } +input[type="checkbox"] { accent-color: var(--admin-accent); } + +.field-help { + margin-top: .35rem; + color: #8b869b; + font-size: .72rem; + line-height: 1.4; +} + +.secret-control { position: relative; } +.secret-control::after { position: absolute; top: 50%; right: .75rem; content: "πŸ”’"; transform: translateY(-50%); opacity: .48; font-size: .78rem; pointer-events: none; } +.secret-control .input { padding-right: 2.25rem; letter-spacing: .05em; } +.admin-form { display: flex; flex-direction: column; gap: 1rem; } +.admin-toggle-list { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: .7rem; margin-bottom: 1rem; } +.admin-toggle { position: relative; display: flex; align-items: flex-start; gap: .7rem; padding: .75rem .85rem; color: inherit !important; background: var(--admin-surface-soft); border: 1px solid #e8e5f2; border-radius: 10px; cursor: pointer; transition: border-color .15s,background .15s; } +.admin-toggle:hover { background: #f8f6ff; border-color: #dcd7ef; } +.admin-toggle > input { position: absolute; opacity: 0; pointer-events: none; } +.admin-toggle-track { flex: 0 0 auto; width: 38px; height: 22px; padding: 2px; background: #ccc8d6; border-radius: 99px; transition: .18s; } +.admin-toggle-track span { display: block; width: 18px; height: 18px; background: #fff; border-radius: 50%; box-shadow: 0 1px 4px rgba(0,0,0,.2); transition: .18s; } +.admin-toggle > input:checked + .admin-toggle-track { background: var(--admin-accent); } +.admin-toggle > input:checked + .admin-toggle-track span { transform: translateX(16px); } +.admin-toggle > input:focus-visible + .admin-toggle-track { outline: 2px solid #7c6cff; outline-offset: 2px; } +.admin-toggle strong { display: block; color: #3e3956; font-size: .82rem; line-height: 1.35; } +.admin-toggle small { display: block; margin-top: .15rem; color: #8a849c; font-size: .7rem; line-height: 1.35; } +.admin-toggle-compact { align-items: center; padding: .55rem .65rem; } + +.admin-check { position: relative; display: inline-flex; align-items: center; gap: .45rem; color: #716b81 !important; font-size: .72rem; line-height: 1.35; cursor: pointer; } +.admin-check > input { position: absolute; opacity: 0; pointer-events: none; } +.admin-check-box { display: inline-flex; flex: 0 0 auto; align-items: center; justify-content: center; width: 18px; height: 18px; color: transparent; background: #fff; border: 1.5px solid #cfcad9; border-radius: 5px; font-size: .68rem; font-weight: 900; transition: .15s; } +.admin-check > input:checked + .admin-check-box { color: #fff; background: var(--admin-accent); border-color: var(--admin-accent); } +.admin-check > input:focus-visible + .admin-check-box { outline: 2px solid #7c6cff; outline-offset: 2px; } +.admin-check-danger { color: #a04e58 !important; } +.admin-check-danger > input:checked + .admin-check-box { background: var(--admin-danger); border-color: var(--admin-danger); } + +.admin-note { margin: .9rem 0 1rem; padding: .75rem .9rem; border: 1px solid; border-radius: 9px; font-size: .76rem; line-height: 1.5; } +.admin-note p + p { margin-top: .35rem; } +.admin-note-info { color: #42627f; background: #f1f7ff; border-color: #d7e8fa; } +.admin-note-warning { color: #785f28; background: #fff8e8; border-color: #f1dfb4; } +.admin-note code { display: inline-block; margin: .2rem 0 0; padding: .12rem .35rem; color: inherit; background: rgba(255,255,255,.7); border-radius: 4px; user-select: all; } + +.admin-details { margin-top: 1rem; overflow: hidden; background: #fcfbff; border: 1px solid #e8e5f2; border-radius: 9px; } +.admin-details summary { display: flex; align-items: center; justify-content: space-between; padding: .7rem .85rem; color: #514b69; font-size: .78rem; font-weight: 700; cursor: pointer; } +.admin-details summary span { min-width: 24px; padding: .05rem .4rem; color: #6257bb; text-align: center; background: #ece9ff; border-radius: 99px; } +.admin-details-body { padding: 0 .85rem .85rem; } + +.sticky-actions { + position: sticky; + bottom: 4.3rem; + z-index: 10; + display: flex; + justify-content: flex-end; + padding: .65rem; + background: rgba(255,255,255,.92); + border: 1px solid rgba(222,218,238,.9); + border-radius: 12px; + box-shadow: 0 8px 28px rgba(46,38,86,.12); + backdrop-filter: blur(8px); +} +.sticky-actions .button { min-width: 150px; } + +.danger-zone { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #f1dfe2; } +.inline-control { display: flex; align-items: center; gap: .5rem; } +.inline-control > .input, +.inline-control > .select { flex: 1; min-width: 0; } +.color-control { display: flex; align-items: center; gap: .6rem; } +.portal-panel { margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px solid var(--admin-border-soft); } +.portal-qr { margin-top: .8rem; text-align: center; } +.portal-qr canvas { max-width: 180px; padding: .4rem; background: #fff; border: 1px solid var(--admin-border); border-radius: 10px; } +.inline-admin-form { margin-top: .7rem; padding-top: .7rem; border-top: 1px solid var(--admin-border-soft); } +.inline-admin-grid { display: grid; grid-template-columns: minmax(180px,1fr) minmax(180px,.8fr) auto; align-items: end; gap: .75rem; } +.inline-admin-grid .field { margin: 0; } +.testimonial-content { display: flex; align-items: flex-start; gap: .75rem; min-width: 0; } +.testimonial-avatar { flex: 0 0 auto; width: 48px; height: 48px; border-radius: 12px; object-fit: cover; } +.testimonial-text { color: #49435d; font-size: .88rem; line-height: 1.5; } +.testimonial-note { margin-top: .2rem; color: var(--admin-muted); font-size: .76rem; } + +/* Messages and tables */ +.notification { color: var(--admin-text) !important; border-radius: 10px; } +.table-wrap { overflow-x: auto; } +.table-wrap .table { margin: 0; background: transparent; font-size: .75rem; } +.table th { color: #696278 !important; font-size: .7rem; text-transform: uppercase; letter-spacing: .03em; } +.is-nowrap { white-space: nowrap; } +.seo-preview { min-height: 2rem; margin-top: .5rem; padding: .5rem .65rem; background: #fafafa; border: 1px solid #ebe9ef; border-radius: 7px; font-size: .78rem; line-height: 2; } + +.pagination-row, +.media-pagination { display: flex; justify-content: center; gap: .35rem; flex-wrap: wrap; margin-top: 1.25rem; } + +/* Media */ +.filter-bar { display: flex; align-items: center; gap: .6rem; margin-bottom: 1rem; padding: .65rem .75rem; background: #fff; border: 1px solid var(--admin-border); border-radius: 10px; } +.media-grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(200px,1fr)); gap: .8rem; } +.media-card { overflow: hidden; background: #fff; border: 1px solid var(--admin-border); border-radius: 12px; box-shadow: 0 1px 6px rgba(42,34,80,.025); transition: transform .15s, box-shadow .15s; } +.media-card:hover { transform: translateY(-2px); box-shadow: 0 7px 20px rgba(42,34,80,.08); } +.media-card img { display: block; width: 100%; height: 160px; object-fit: cover; } +.media-card .photo-thumb { width: 100%; height: 160px; } +.media-card .video-thumb { position: relative; width: 100%; height: 160px; background: #17151e; } +.media-card .video-play, +.visit-media-item .video-play { position: absolute; top: 50%; left: 50%; color: #fff; line-height: 1; text-shadow: 0 1px 5px #000; transform: translate(-50%,-50%); pointer-events: none; } +.media-card .video-play { font-size: 2.5rem; } +.media-info { padding: .7rem .8rem; } +.media-meta { display: flex; align-items: center; justify-content: space-between; gap: .5rem; color: var(--admin-text); font-size: .82rem; } +.media-caption { margin-top: .25rem; color: #746e82; font-size: .78rem; } + +.visit-media-grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(105px,1fr)); gap: .55rem; margin-bottom: 1rem; } +.visit-media-item { overflow: hidden; background: #faf9fc; border: 1px solid var(--admin-border); border-radius: 9px; } +.visit-media-item img, +.visit-media-item .photo-thumb, +.visit-media-item .video-thumb-sm { display: block; width: 100%; height: 82px; object-fit: cover; } +.visit-media-item .video-thumb-sm { position: relative; background: #17151e; } +.visit-media-item .video-play { font-size: 1.6rem; } +.visit-media-item .media-cap { padding: .25rem .4rem; overflow: hidden; color: var(--admin-muted); font-size: .69rem; text-overflow: ellipsis; white-space: nowrap; } +.visit-media-delete { padding: .25rem .4rem .4rem; } +.visit-media-delete .button { width: 100%; min-height: 1.65rem; font-size: .66rem; } + +/* Upload queue */ +.upload-context { margin-bottom: 1rem; padding: .7rem .8rem; color: #686276; background: var(--admin-surface-soft); border: 1px solid var(--admin-border-soft); border-radius: 9px; font-size: .82rem; } +.upload-queue { display: flex; flex-direction: column; gap: .4rem; margin-top: .65rem; } +.upload-queue:empty { display: none; } +.upload-queue-item { display: grid; grid-template-columns: 34px minmax(0,1fr); align-items: center; gap: .55rem; padding: .45rem .55rem; background: #f7f6ff; border-radius: 8px; } +.upload-queue-icon { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; background: #ebe8ff; border-radius: 7px; font-size: 1.05rem; } +.upload-queue-name { overflow: hidden; color: #494467; font-size: .78rem; text-overflow: ellipsis; white-space: nowrap; } +.upload-queue-state { color: #8a84a5; font-size: .68rem; } +.upload-queue-track { height: 3px; margin-top: .25rem; overflow: hidden; background: #dedbea; border-radius: 99px; } +.upload-queue-bar { width: 0; height: 100%; background: var(--admin-accent); transition: width .2s; } +.upload-progress { display: none; margin-bottom: 1rem; } +.upload-progress-head { display: flex; justify-content: space-between; margin-bottom: .3rem; color: #5f596d; font-size: .78rem; } +.upload-progress-track { height: 7px; overflow: hidden; background: #e8e6ed; border-radius: 99px; } +.upload-progress-bar { width: 0; height: 100%; background: linear-gradient(90deg,#6c63ff,#b06cff); border-radius: 99px; transition: width .2s; } +#uploadBar.is-processing, +.upload-queue-bar.is-processing { width: 35% !important; animation: upload-processing 1.15s ease-in-out infinite; } +@keyframes upload-processing { from { transform: translateX(-110%); } to { transform: translateX(300%); } } + +/* Schedule */ +#calendar-wrap { padding: .75rem; overflow: hidden; } +.fc { color: var(--admin-text); font-size: .82rem; } +.fc .fc-daygrid-day-number, +.fc .fc-col-header-cell-cushion, +.fc .fc-list-day-text, +.fc .fc-list-day-side-text, +.fc .fc-toolbar-title, +.fc th, +.fc td { color: var(--admin-text) !important; } +.fc .fc-toolbar { gap: .55rem; flex-wrap: nowrap; } +.fc .fc-toolbar-chunk { min-width: 0; } +.fc .fc-toolbar-chunk:first-child, +.fc .fc-toolbar-chunk:last-child { display: flex; align-items: center; } +.fc .fc-button-group { display: flex; flex-wrap: nowrap; gap: .35rem; } +.fc .fc-button-group > .fc-button { margin-left: 0 !important; border-radius: 7px !important; } +.fc .fc-toolbar-title { font-size: 1.05rem !important; white-space: nowrap; } +.fc .fc-button { padding: .28rem .52rem !important; color: #fff !important; background: var(--admin-accent) !important; border-color: var(--admin-accent) !important; border-radius: 7px !important; font-size: .76rem !important; } +.fc-event { padding: 2px 5px; border: none !important; border-radius: 5px; cursor: pointer; } +.fc-event.ev-completed .fc-event-title, +.fc-event.ev-completed .fc-list-event-title, +.fc-event.ev-cancelled .fc-event-title, +.fc-event.ev-cancelled .fc-list-event-title { text-decoration: line-through; opacity: .75; } +.fc .fc-day-today { background: #f0eeff !important; } +.fc .fc-day.day-weekend { background: #fdf9f6; } +.fc .fc-day-today.day-weekend { background: #f0eeff !important; } + +.time-row { display: flex; align-items: center; gap: .5rem; } +.time-block { display: flex; flex: 1; flex-direction: column; } +.time-lbl { margin-bottom: .2rem; color: var(--admin-muted); font-size: .72rem; } +.time-sep { padding-top: 1.2rem; color: #aaa5b6; font-size: 1.1rem; } +.cal-toolbar { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .55rem; } +.sched-cal { overflow: hidden; background: #faf9fc; border: 1px solid var(--admin-border); border-radius: 11px; } +.cal-nav { display: flex; align-items: center; justify-content: space-between; padding: .65rem .75rem; background: #fff; border-bottom: 1px solid var(--admin-border-soft); } +.cal-nav button { padding: .2rem .5rem; color: var(--admin-accent); background: none; border: 0; border-radius: 6px; font-size: 1.05rem; cursor: pointer; } +.cal-nav button:hover { background: #f0eeff; } +.cal-nav span { color: var(--admin-text); font-size: .95rem; font-weight: 750; } +.cal-grid { display: grid; grid-template-columns: repeat(7,1fr); } +.cal-wday { padding: .42rem 0; color: #aaa5b6; background: #faf9fc; font-size: .69rem; font-weight: 750; text-align: center; } +.cal-wday.is-weekend { color: #d98a91; } +.cal-day { position: relative; padding: .55rem .2rem; color: var(--admin-text); border-radius: 0; font-size: .86rem; text-align: center; cursor: pointer; transition: background .1s; user-select: none; } +.cal-day:hover { background: #f0eeff; } +.cal-day.is-empty { cursor: default; } +.cal-day.is-empty:hover { background: none; } +.cal-day.is-today { color: var(--admin-accent); font-weight: 750; } +.cal-day.is-selected { color: #fff !important; background: var(--admin-accent) !important; } +.cal-day.is-range-start { color: #fff !important; background: #a89cff !important; } +.cal-day.is-past { color: #ccc8d3; } +.day-row { display: flex; align-items: center; gap: .4rem; padding: .5rem 0; border-bottom: 1px solid #f2f0f5; } +.day-row:last-child { border-bottom: 0; } +.day-date { flex: 1; min-width: 0; overflow: hidden; font-size: .82rem; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; } +.day-times { display: flex; flex-shrink: 0; align-items: center; gap: .25rem; } +.day-times .time-sep { padding: 0; font-size: .78rem; } +.day-rm { flex-shrink: 0; padding: .15rem .25rem; color: #bbb6c5; background: none; border: 0; font-size: 1rem; line-height: 1; cursor: pointer; } +.day-rm:hover { color: var(--admin-danger); } +.time-badge-wrap { position: relative; display: inline-flex; align-items: center; justify-content: center; min-width: 3.6rem; padding: .15rem .5rem; color: #5b52d6; background: #ede9ff; border: 1.5px solid #c4beff; border-radius: 20px; font-size: .76rem; font-weight: 750; white-space: nowrap; cursor: pointer; } +.time-badge-wrap:focus-within { background: #f0eeff; border-color: var(--admin-accent); } +.time-badge-label { z-index: 1; pointer-events: none; } +.time-badge-input { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; margin: 0; opacity: 0; background: transparent; border: 0; font-size: 16px; cursor: pointer; } + +.status-picker { display: flex; gap: .5rem; } +.status-btn { display: flex; flex: 1; flex-direction: column; align-items: center; gap: .3rem; padding: .6rem .4rem; color: #777184; background: #f5f4f7; border: 2px solid transparent; border-radius: 10px; font-size: .79rem; font-weight: 650; line-height: 1.2; cursor: pointer; transition: all .15s; } +.status-btn-icon { font-size: 1.3rem; line-height: 1; } +.status-btn:hover { filter: brightness(.97); } +.status-btn-scheduled.is-active { color: #1e40af; background: #dbeafe; border-color: #3b82f6; } +.status-btn-completed.is-active { color: #15803d; background: #d1fae5; border-color: #22c55e; } +.status-btn-cancelled.is-active { color: #b91c1c; background: #fee2e2; border-color: #ef4444; } + +/* Dialogs */ +.visit-modal-bg, +.upload-modal-bg { position: fixed; inset: 0; z-index: 100; display: none; align-items: center; justify-content: center; padding: 1rem; background: rgba(31,27,48,.38); backdrop-filter: blur(3px); } +.visit-modal-bg.is-open, +.upload-modal-bg.is-open { display: flex; } +.visit-modal, +.upload-modal { width: 100%; max-width: 420px; padding: 1.25rem; background: #fff; border: 1px solid var(--admin-border); border-radius: 14px; box-shadow: 0 18px 50px rgba(31,27,48,.18); } +.visit-modal { max-width: 380px; } +.visit-modal h3 { margin: 0 0 .75rem; color: var(--admin-text); font-size: 1.05rem; font-weight: 750; } +.visit-modal .meta { margin-bottom: .75rem; color: var(--admin-muted); font-size: .82rem; line-height: 1.6; } +.modal-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: 1rem; } +.modal-head h3 { margin: 0; color: var(--admin-text); font-size: 1.05rem; font-weight: 750; } +.modal-close { padding: .25rem; color: var(--admin-muted); background: none; border: 0; font-size: 1.15rem; cursor: pointer; } + +/* Authentication screens */ +.auth-shell { display: flex; align-items: center; justify-content: center; padding: 1.5rem 0; } +.login-box { width: 100%; max-width: 410px; padding: 0 1rem; } +.auth-language { margin-bottom: .7rem; text-align: right; } +.auth-language a { color: var(--admin-muted); font-size: .75rem; } +.login-card { padding: 1.6rem 1.4rem; background: #fff; border: 1px solid var(--admin-border); border-radius: 16px; box-shadow: 0 12px 38px rgba(42,34,80,.08); } +.auth-head { margin-bottom: 1.1rem; text-align: center; } +.auth-icon { display: flex; align-items: center; justify-content: center; width: 48px; height: 48px; margin: 0 auto .65rem; background: var(--admin-accent-soft); border-radius: 13px; font-size: 1.55rem; } +.auth-head h1 { margin: 0; color: var(--admin-text); font-size: 1.25rem; font-weight: 800; } +.auth-head p { margin-top: .2rem; color: var(--admin-muted); font-size: .84rem; } +.auth-divider { height: 1px; margin: 1rem 0; background: var(--admin-border-soft); border: 0; } +.turnstile-wrap { margin-top: .75rem; } +.time-columns { margin-bottom: 0 !important; } + +@media (min-width: 1080px) { + .admin-shell { padding-bottom: 0; } + .bottom-tabs { display: none; } + .desktop-nav { display: flex; align-items: center; justify-content: center; gap: .2rem; } + .desktop-nav a { padding: .36rem .62rem; color: #625d70; border-radius: 7px; font-size: .78rem; font-weight: 650; text-decoration: none; transition: background .15s,color .15s; } + .desktop-nav a:hover { background: #f2f0f7; } + .desktop-nav a.is-active { color: #fff; background: var(--admin-accent); } + .sticky-actions { bottom: .75rem; } +} + +@media (max-width: 768px) { + .top-header .brand { font-size: .95rem; } + .brand-version, + .top-header-right .admin-name { display: none; } + .main-content { padding: 1rem .75rem 1.5rem; } + .page-head { align-items: flex-start; } + .page-head h1 { font-size: 1.35rem; } + .admin-grid, + .form-grid, + .admin-toggle-list { grid-template-columns: 1fr; } + .admin-field-wide { grid-column: auto; } + .admin-section-head { grid-template-columns: 36px minmax(0,1fr); padding: .9rem; } + .admin-section-icon { width: 36px; height: 36px; } + .admin-section-body, + .form-card { padding: .9rem; } + .sticky-actions .button { width: 100%; } + .fc .fc-toolbar { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: .55rem; font-size: .72rem; } + .fc .fc-toolbar-chunk:first-child { grid-column: 1; grid-row: 1; justify-self: start; } + .fc .fc-toolbar-chunk:nth-child(2) { grid-column: 2; grid-row: 1; justify-self: end; } + .fc .fc-toolbar-chunk:last-child { grid-column: 1 / -1; grid-row: 2; width: 100%; overflow-x: auto; } + .fc .fc-toolbar-chunk:last-child .fc-button-group { width: 100%; } + .fc .fc-toolbar-chunk:last-child .fc-button { flex: 1 0 auto; } + .fc .fc-toolbar-title { font-size: .92rem !important; } + .fc .fc-button { padding: .22rem .35rem !important; font-size: .68rem !important; } +} + +@media (max-width: 520px) { + .page-head { flex-direction: column; } + .page-head-actions { width: 100%; } + .page-head-actions .button { flex: 1; } + .item-card-header { align-items: flex-start; } + .media-grid { grid-template-columns: repeat(2,minmax(0,1fr)); gap: .55rem; } + .media-card img, + .media-card .photo-thumb, + .media-card .video-thumb { height: 122px; } + .media-meta { align-items: flex-start; flex-direction: column; gap: .1rem; } + .status-picker { flex-direction: column; } + .status-btn { flex-direction: row; justify-content: flex-start; } + .inline-control { align-items: stretch; flex-direction: column; } + .inline-admin-grid { grid-template-columns: 1fr; align-items: stretch; } +} + +@media (max-width: 400px) { + .bottom-tabs a .tab-label { display: none; } + .bottom-tabs a { min-width: 2.8rem; padding: 0 .3rem; } +} diff --git a/templates/admin/client_form.html b/templates/admin/client_form.html index 5d2ced2..008c193 100644 --- a/templates/admin/client_form.html +++ b/templates/admin/client_form.html @@ -5,12 +5,17 @@ {% block content %}
-

{{ title }}

+

{{ t.clients_title }}

-
-
-
+
+
+ +

{{ title }}

+
+
+ +
@@ -28,41 +33,42 @@
-
+
-
+
-
+
-
- +
+ {{ client_color }}
-
+
{% if is_edit %} -
+
-
- +
+
-
- +
+
+
-
+
{% if client_status == "active" %}
@@ -82,10 +88,12 @@
{% endif %} {% if client_status != "deleted" %} -
+
{% endif %} +
{% endif %} -
+
+
{% endblock %} diff --git a/templates/admin/clients.html b/templates/admin/clients.html index 5df4a88..d12c0d0 100644 --- a/templates/admin/clients.html +++ b/templates/admin/clients.html @@ -6,7 +6,7 @@ {% block content %}

{{ t.clients_title }}

-
+
{% if show_all %} {{ t.filter_show_active }} {% else %} @@ -17,19 +17,19 @@
{% if clients.is_empty() %} -

{{ t.clients_empty }}

+

{{ t.clients_empty }}

{% else %} {% for client in &clients %}
- - {{ client.name }} + + {{ client.name }} {{ t.client_status(&client.status) }}
{% if let Some(phone) = client.phone.as_deref() %} - πŸ“ž {{ phone }} + πŸ“ž {{ phone }} {% endif %} {% if let Some(email) = client.email.as_deref() %} βœ‰οΈ {{ email }} diff --git a/templates/admin/dashboard.html b/templates/admin/dashboard.html index 4bb8aa6..336aa2f 100644 --- a/templates/admin/dashboard.html +++ b/templates/admin/dashboard.html @@ -10,27 +10,27 @@
{% if today_visits.is_empty() %} -

{{ t.dashboard_no_visits }}

+

{{ t.dashboard_no_visits }}

{% else %} {% for tv in &today_visits %}
- - {{ tv.client_name }} + + {{ tv.client_name }} {{ t.visit_status(&tv.visit.status) }}
πŸ• {{ tv.visit.time_start }} β€” {{ tv.visit.time_end }} {% if !tv.client_phone.is_empty() %} - πŸ“ž {{ tv.client_phone }} + πŸ“ž {{ tv.client_phone }} {% endif %} {% if !tv.client_address.is_empty() %} πŸ“ {{ tv.client_address }} {% endif %}
{% if let Some(notes) = tv.visit.notes.as_deref() %} -
{{ notes }}
+
{{ notes }}
{% endif %}
{% if tv.visit.status == "scheduled" %} @@ -48,22 +48,22 @@ {% endif %} -

{{ t.dashboard_recent_feedbacks }}

+

{{ t.dashboard_recent_feedbacks }}

{% if feedbacks.is_empty() %} -

{{ t.dashboard_no_feedbacks }}

+

{{ t.dashboard_no_feedbacks }}

{% else %} {% for fb in &feedbacks %} - -
- {{ fb.client_name }} - {{ fb.visit_date }} + {% endfor %} {% if feedback_total_pages > 1 %} -
+
{% if feedback_page > 1 %} « {% endif %} diff --git a/templates/admin/layout.html b/templates/admin/layout.html index 3346681..fbfb2de 100644 --- a/templates/admin/layout.html +++ b/templates/admin/layout.html @@ -6,119 +6,13 @@ {{ t.nav_title }} β€” {% block title %}{% endblock %} - + - +
- 🐾 {{ t.nav_title }} v{{ t.app_version() }} + 🐾 {{ t.nav_title }} v{{ t.app_version() }}
{% if leads.is_empty() %} -

{{ t.leads_empty }}

+

{{ t.leads_empty }}

{% else %} {% for lead in &leads %}
@@ -24,7 +24,7 @@
{% if let Some(phone) = lead.phone.as_deref() %} - πŸ“ž {{ phone }} + πŸ“ž {{ phone }} {% endif %} {% if let Some(comment) = lead.comment.as_deref() %} πŸ’¬ {{ comment }} diff --git a/templates/admin/login.html b/templates/admin/login.html index 47352c8..54ef47f 100644 --- a/templates/admin/login.html +++ b/templates/admin/login.html @@ -6,31 +6,21 @@ {{ t.nav_title }} β€” {{ t.login_title }} + {% if !turnstile_site_key.is_empty() %} {% endif %} - - +