diff --git a/app/static/css/theme.css b/app/static/css/theme.css new file mode 100644 index 0000000..1cd1c52 --- /dev/null +++ b/app/static/css/theme.css @@ -0,0 +1,298 @@ +/* ════════════════════════════════════════════════════════════════════════ + Janitorial QC — Global UI Theme + ──────────────────────────────────────────────────────────────────────── + Purpose: unify every signed-in page onto one polished design system by + retuning Bootstrap's own components. UI/UX only — no markup/route/JS change. + + Design tokens (matching the app's premium pages: inspections/view, + templates/form_editor, etc.): + Font DM Sans + Accent #2563eb / hover #1d4ed8 / soft #eff6ff + Slate text #0f172a · heading #374151 · muted #64748b / #94a3b8 + border #e2e8f0 / #cbd5e1 · surface #f8fafc / #f1f5f9 · page #eef0f4 + Chrome navbar #1a1d23 (matches the dark card-headers on premium pages) + Green #16a34a Amber #f59e0b Danger #dc2626 + Shape radius .85rem cards / .5rem controls · soft shadows + + Load order: AFTER bootstrap.min.css, BEFORE page-level {% block extra_css %} + so any page's own styles still win. + ════════════════════════════════════════════════════════════════════════ */ + +/* ── 1. Design tokens (retune Bootstrap CSS variables) ───────────────────── */ +:root { + --bs-primary: #2563eb; + --bs-primary-rgb: 37, 99, 235; + --bs-success: #16a34a; + --bs-success-rgb: 22, 163, 74; + --bs-warning: #f59e0b; + --bs-warning-rgb: 245, 158, 11; + --bs-danger: #dc2626; + --bs-danger-rgb: 220, 38, 38; + + --bs-link-color: #2563eb; + --bs-link-color-rgb: 37, 99, 235; + --bs-link-hover-color: #1d4ed8; + --bs-link-hover-color-rgb: 29, 78, 216; + + --bs-body-color: #0f172a; + --bs-body-color-rgb: 15, 23, 42; + --bs-secondary-color: rgba(100, 116, 139, 1); + --bs-body-bg: #eef0f4; + --bs-border-color: #e2e8f0; + + --bs-border-radius: .55rem; + --bs-border-radius-sm: .4rem; + --bs-border-radius-lg: .85rem; + --bs-border-radius-xl: 1rem; + + --bs-body-font-family: 'DM Sans', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + + --jqc-ink: #0f172a; + --jqc-heading: #374151; + --jqc-muted: #64748b; + --jqc-faint: #94a3b8; + --jqc-border: #e2e8f0; + --jqc-border-2: #cbd5e1; + --jqc-surface: #f8fafc; + --jqc-surface-2: #f1f5f9; + --jqc-accent: #2563eb; + --jqc-accent-700: #1d4ed8; + --jqc-accent-50: #eff6ff; + --jqc-navbar: #1a1d23; + --jqc-shadow: 0 1px 2px rgba(15, 23, 42, .06), 0 1px 3px rgba(15, 23, 42, .05); + --jqc-shadow-md: 0 4px 24px rgba(15, 23, 42, .10); +} + +/* ── 2. Base typography ──────────────────────────────────────────────────── */ +body { + font-family: var(--bs-body-font-family); + background-color: var(--bs-body-bg); + color: var(--jqc-ink); + -webkit-font-smoothing: antialiased; +} +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + font-family: var(--bs-body-font-family); + color: var(--jqc-ink); + font-weight: 700; + letter-spacing: -.01em; +} +.text-muted { color: var(--jqc-muted) !important; } +a { text-underline-offset: 2px; } + +/* ── 3. Navbar — dark slate chrome (matches premium dark card-headers) ────── */ +.navbar.bg-primary { + background-color: var(--jqc-navbar) !important; + border-bottom: 1px solid rgba(255, 255, 255, .06); + box-shadow: 0 1px 0 rgba(15, 23, 42, .04); +} +.navbar .navbar-brand { + font-weight: 700; + letter-spacing: -.01em; +} +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, .80); + border-radius: 6px; + padding-inline: .7rem; + transition: background-color .15s, color .15s; +} +.navbar-dark .navbar-nav .nav-link:hover { color: #fff; } +.navbar-dark .navbar-nav .nav-link.active { + background-color: rgba(37, 99, 235, .22) !important; + box-shadow: inset 0 -2px 0 var(--jqc-accent) !important; + color: #fff !important; +} + +/* ── 4. Cards ────────────────────────────────────────────────────────────── */ +.card { + border: 1px solid var(--jqc-border); + border-radius: var(--bs-border-radius-lg); + box-shadow: var(--jqc-shadow); +} +.card.shadow-sm { box-shadow: var(--jqc-shadow) !important; } +.card.shadow, +.card.shadow-lg { box-shadow: var(--jqc-shadow-md) !important; } + +.card-header { + background-color: var(--jqc-surface); + border-bottom: 1px solid var(--jqc-border); + color: var(--jqc-heading); + font-weight: 600; + padding: .7rem 1rem; +} +.card-header.bg-light { background-color: var(--jqc-surface) !important; } +.card-header.bg-white { background-color: #fff !important; } + +/* Colored stat cards (dashboard) — flatten + soften corners */ +.card.text-white { border: none; } +.card.text-white .text-white-50 { color: rgba(255, 255, 255, .72) !important; } + +/* ── 5. Buttons ──────────────────────────────────────────────────────────── */ +.btn { + font-weight: 500; + border-radius: var(--bs-border-radius); + --bs-btn-focus-box-shadow: 0 0 0 .2rem rgba(37, 99, 235, .25); +} +.btn-primary { + --bs-btn-bg: var(--jqc-accent); + --bs-btn-border-color: var(--jqc-accent); + --bs-btn-hover-bg: var(--jqc-accent-700); + --bs-btn-hover-border-color: var(--jqc-accent-700); + --bs-btn-active-bg: var(--jqc-accent-700); + --bs-btn-active-border-color: var(--jqc-accent-700); + --bs-btn-disabled-bg: var(--jqc-accent); + --bs-btn-disabled-border-color: var(--jqc-accent); +} +.btn-outline-primary { + --bs-btn-color: var(--jqc-accent); + --bs-btn-border-color: var(--jqc-accent); + --bs-btn-hover-bg: var(--jqc-accent); + --bs-btn-hover-border-color: var(--jqc-accent); + --bs-btn-active-bg: var(--jqc-accent); + --bs-btn-active-border-color: var(--jqc-accent); +} +.btn-success { + --bs-btn-bg: var(--bs-success); --bs-btn-border-color: var(--bs-success); + --bs-btn-hover-bg: #15803d; --bs-btn-hover-border-color: #15803d; + --bs-btn-active-bg: #15803d; --bs-btn-active-border-color: #15803d; + --bs-btn-disabled-bg: var(--bs-success); --bs-btn-disabled-border-color: var(--bs-success); +} +.btn-danger { + --bs-btn-bg: var(--bs-danger); --bs-btn-border-color: var(--bs-danger); + --bs-btn-hover-bg: #b91c1c; --bs-btn-hover-border-color: #b91c1c; + --bs-btn-active-bg: #b91c1c; --bs-btn-active-border-color: #b91c1c; + --bs-btn-disabled-bg: var(--bs-danger); --bs-btn-disabled-border-color: var(--bs-danger); +} +.btn-warning { + --bs-btn-bg: var(--bs-warning); --bs-btn-border-color: var(--bs-warning); + --bs-btn-color: #422006; --bs-btn-hover-color: #422006; + --bs-btn-hover-bg: #d97706; --bs-btn-hover-border-color: #d97706; + --bs-btn-active-bg: #d97706; --bs-btn-active-border-color: #d97706; +} +.btn-outline-secondary { + --bs-btn-color: var(--jqc-muted); + --bs-btn-border-color: var(--jqc-border-2); + --bs-btn-hover-bg: var(--jqc-surface); + --bs-btn-hover-color: var(--jqc-accent); + --bs-btn-hover-border-color: var(--jqc-accent); + --bs-btn-active-bg: var(--jqc-surface-2); + --bs-btn-active-color: var(--jqc-ink); + --bs-btn-active-border-color: var(--jqc-border-2); +} +.btn-link { --bs-btn-color: var(--jqc-accent); --bs-btn-hover-color: var(--jqc-accent-700); text-decoration: none; } + +/* ── 6. Forms ────────────────────────────────────────────────────────────── */ +.form-label { + color: var(--jqc-heading); + font-weight: 500; + margin-bottom: .35rem; +} +.form-control, .form-select { + border-color: var(--jqc-border); + color: var(--jqc-ink); + border-radius: var(--bs-border-radius); +} +.form-control::placeholder { color: var(--jqc-faint); } +.form-control:focus, .form-select:focus { + border-color: var(--jqc-accent); + box-shadow: 0 0 0 .2rem rgba(37, 99, 235, .18); +} +.input-group-text { + background-color: var(--jqc-surface); + border-color: var(--jqc-border); + color: var(--jqc-muted); +} +.form-check-input:checked { + background-color: var(--jqc-accent); + border-color: var(--jqc-accent); +} +.form-check-input:focus { + border-color: var(--jqc-accent); + box-shadow: 0 0 0 .2rem rgba(37, 99, 235, .18); +} + +/* ── 7. Tables ───────────────────────────────────────────────────────────── */ +.table { + --bs-table-color: var(--jqc-ink); + --bs-table-border-color: var(--jqc-border); + border-color: var(--jqc-border); + margin-bottom: 0; +} +.table > thead th, +.table-light, .table-light > th, .table-light > td { + background-color: var(--jqc-surface-2); + color: var(--jqc-heading); + font-weight: 600; + border-bottom-color: var(--jqc-border); +} +.table > thead th { + text-transform: none; + font-size: .82rem; + letter-spacing: .01em; + padding-block: .6rem; +} +.table > tbody > tr { border-color: var(--jqc-border); } +.table-hover > tbody > tr:hover > * { + background-color: var(--jqc-surface); + --bs-table-bg-state: var(--jqc-surface); +} +/* Cards that wrap a flush table: clip corners so the table follows the radius */ +.card > .table-responsive:last-child, +.card > .card-body.p-0 > .table-responsive, +.card > .card-body.p-0 > .table { + border-bottom-left-radius: var(--bs-border-radius-lg); + border-bottom-right-radius: var(--bs-border-radius-lg); + overflow: hidden; +} + +/* ── 8. Badges ───────────────────────────────────────────────────────────── */ +.badge { + font-weight: 600; + letter-spacing: .01em; + border-radius: .4rem; + padding: .35em .6em; +} + +/* ── 9. Alerts ───────────────────────────────────────────────────────────── */ +.alert { + border-radius: var(--bs-border-radius); + border-width: 1px; +} + +/* ── 10. Dropdowns ───────────────────────────────────────────────────────── */ +.dropdown-menu { + border: 1px solid var(--jqc-border); + border-radius: var(--bs-border-radius); + box-shadow: var(--jqc-shadow-md); + padding: .35rem; + --bs-dropdown-link-active-bg: var(--jqc-accent); +} +.dropdown-item { + border-radius: var(--bs-border-radius-sm); + padding: .45rem .7rem; + color: var(--jqc-ink); +} +.dropdown-item:hover, .dropdown-item:focus { + background-color: var(--jqc-surface); + color: var(--jqc-accent); +} +.dropdown-divider { border-top-color: var(--jqc-border); } + +/* ── 11. Misc components ─────────────────────────────────────────────────── */ +.page-link { color: var(--jqc-accent); } +.page-item.active .page-link { + background-color: var(--jqc-accent); + border-color: var(--jqc-accent); +} +.nav-tabs .nav-link.active { color: var(--jqc-accent); } +.list-group-item-action:hover { background-color: var(--jqc-surface); } +.modal-content { border: 1px solid var(--jqc-border); border-radius: var(--bs-border-radius-lg); } +.spinner-border.text-primary { color: var(--jqc-accent) !important; } +hr { color: var(--jqc-border-2); } + +/* Notification dropdown (base.html) — align accent + hover */ +.notif-item.unread { border-left-color: var(--jqc-accent) !important; background-color: var(--jqc-accent-50) !important; } +.notif-item:hover { background-color: var(--jqc-surface) !important; } + +/* ── 12. Focus visibility (accessibility) ────────────────────────────────── */ +:focus-visible { outline: 2px solid rgba(37, 99, 235, .45); outline-offset: 1px; } \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index abeafb1..dd777c2 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -10,6 +10,8 @@