Reload the browser on template and static changes in dev
Adds django-browser-reload: runserver already restarts on Python changes, but the browser had to be refreshed by hand for every template or CSS edit. It also watches static/, so a Tailwind rebuild now refreshes the page on its own. Mounted only under DEBUG -- it injects a script into every HTML response and serves an open event stream, neither of which belongs in production; a test holds that line. Its endpoint is exempt from RequireMFAMiddleware, otherwise a not-yet-enrolled staff user has the stream redirected away and live reload dies on the MFA enrolment page, which is exactly a page we are restyling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,9 @@ from django.urls import reverse
|
||||
from club.models import ClubRole
|
||||
|
||||
#: Paths a not-yet-enrolled user must still reach (to enrol, or to log out).
|
||||
EXEMPT_PREFIXES = ("/accounts/", "/static/", "/media/")
|
||||
#: ``/__reload__/`` is django-browser-reload's event stream, which only exists
|
||||
#: under DEBUG — without it, live reload dies on the enrolment page itself.
|
||||
EXEMPT_PREFIXES = ("/accounts/", "/static/", "/media/", "/__reload__/")
|
||||
|
||||
ELEVATED_ROLES = (ClubRole.Roles.ADMIN, ClubRole.Roles.EDITOR)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ dependencies = [
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"coverage>=7.15.0",
|
||||
"django-browser-reload>=1.21.0",
|
||||
"ruff>=0.15.17",
|
||||
]
|
||||
|
||||
|
||||
@@ -81,6 +81,13 @@ MIDDLEWARE = [
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
# Reload the browser when templates, static files or Python change. Dev only: it
|
||||
# injects a script tag into every HTML response and serves an open event stream,
|
||||
# neither of which belongs in production.
|
||||
if DEBUG:
|
||||
INSTALLED_APPS += ["django_browser_reload"]
|
||||
MIDDLEWARE += ["django_browser_reload.middleware.BrowserReloadMiddleware"]
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"allauth.account.auth_backends.AuthenticationBackend",
|
||||
|
||||
30
rosterchief/tests.py
Normal file
30
rosterchief/tests.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import importlib
|
||||
|
||||
from django.test import SimpleTestCase, override_settings
|
||||
from django.urls import Resolver404, clear_url_caches, resolve
|
||||
|
||||
from . import urls
|
||||
|
||||
|
||||
class BrowserReloadUrlTests(SimpleTestCase):
|
||||
"""django-browser-reload serves an open event stream and injects a script into
|
||||
every HTML response, so it must never be mounted outside DEBUG."""
|
||||
|
||||
def reload_urlconf(self):
|
||||
importlib.reload(urls)
|
||||
clear_url_caches()
|
||||
|
||||
def tearDown(self):
|
||||
self.reload_urlconf() # restore the real (DEBUG=False) urlconf
|
||||
|
||||
def test_the_reload_endpoint_is_mounted_in_debug(self):
|
||||
with override_settings(DEBUG=True):
|
||||
self.reload_urlconf()
|
||||
|
||||
self.assertEqual(resolve("/__reload__/events/").view_name, "django_browser_reload:events")
|
||||
|
||||
def test_the_reload_endpoint_is_absent_without_debug(self):
|
||||
self.reload_urlconf()
|
||||
|
||||
with self.assertRaises(Resolver404):
|
||||
resolve("/__reload__/events/")
|
||||
@@ -7,6 +7,7 @@ second factors. ``RequireMFAMiddleware`` then blocks any staff user who has not
|
||||
enrolled.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.views.generic import RedirectView
|
||||
@@ -17,3 +18,6 @@ urlpatterns = [
|
||||
path("accounts/", include("allauth.urls")),
|
||||
path("controlpanel/", include("controlpanel.urls")),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [path("__reload__/", include("django_browser_reload.urls"))]
|
||||
|
||||
BIN
static/.DS_Store
vendored
Normal file
BIN
static/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -27,6 +27,10 @@
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--default-transition-duration: 150ms;
|
||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--default-font-family: var(--font-sans);
|
||||
--default-mono-font-family: var(--font-mono);
|
||||
}
|
||||
@@ -464,6 +468,26 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapse-plus {
|
||||
@layer daisyui.l1.l2 {
|
||||
> .collapse-title:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 0.5rem;
|
||||
width: 0.5rem;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition-property: all;
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
top: 0.9rem;
|
||||
inset-inline-end: 1.4rem;
|
||||
--tw-content: "+";
|
||||
content: var(--tw-content);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
@@ -917,6 +941,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapse-open {
|
||||
@layer daisyui.l1.l2 {
|
||||
grid-template-rows: max-content 1fr;
|
||||
> .collapse-content {
|
||||
--overflow-delay: 0.2s;
|
||||
overflow: revert-layer;
|
||||
content-visibility: visible;
|
||||
min-height: fit-content;
|
||||
padding-bottom: 1rem;
|
||||
@supports not (content-visibility: visible) {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapse {
|
||||
visibility: collapse;
|
||||
}
|
||||
@@ -982,6 +1021,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.toast {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: fixed;
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: calc(0.25rem * 4);
|
||||
top: auto;
|
||||
bottom: calc(0.25rem * 4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(0.25rem * 2);
|
||||
background-color: transparent;
|
||||
translate: var(--toast-x, 0) var(--toast-y, 0);
|
||||
width: max-content;
|
||||
max-width: calc(100vw - 2rem);
|
||||
& > * {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: toast 0.25s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.toggle {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
border: var(--border) solid currentColor;
|
||||
@@ -1318,6 +1378,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.aura {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
--aura-padding: 0.125rem;
|
||||
padding: var(--aura-padding);
|
||||
border-radius: calc(var(--aura-padding) + var(--aura-radius, var(--radius-box)));
|
||||
animation: aura var(--tw-duration, 6s) linear infinite;
|
||||
background-image: conic-gradient(from var(--aura-angle), transparent 225deg, currentColor);
|
||||
&:has( > .card, > .alert) {
|
||||
--aura-radius: var(--radius-box);
|
||||
}
|
||||
&:has( > .btn, > .input, > .select) {
|
||||
--aura-radius: var(--radius-field);
|
||||
}
|
||||
&:has( > .checkbox, > .toggle, > .badge) {
|
||||
--aura-radius: var(--radius-selector);
|
||||
}
|
||||
&:before, &:after {
|
||||
animation: inherit;
|
||||
background-color: inherit;
|
||||
background-image: inherit;
|
||||
border-radius: inherit;
|
||||
position: absolute;
|
||||
top: calc(1 / 2 * 100%);
|
||||
left: calc(1 / 2 * 100%);
|
||||
z-index: 0;
|
||||
display: block;
|
||||
opacity: 70%;
|
||||
filter: blur(0.25rem);
|
||||
translate: -50% -50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
}
|
||||
&:after {
|
||||
opacity: 30%;
|
||||
filter: blur(1rem);
|
||||
}
|
||||
& > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.select {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
@@ -1666,6 +1771,48 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.rating {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
--size: var(--size-selector, 0.25rem) * 6;
|
||||
input {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
}
|
||||
* {
|
||||
border-radius: 0;
|
||||
background-color: var(--color-base-content);
|
||||
opacity: 20%;
|
||||
width: calc(var(--size) * 1);
|
||||
height: calc(var(--size));
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: rating 0.25s ease-out;
|
||||
}
|
||||
}
|
||||
.rating-hidden {
|
||||
width: calc(0.25rem * 2);
|
||||
background-color: transparent;
|
||||
}
|
||||
:checked, [aria-checked="true"], [aria-current="true"], :has( ~ :checked, ~ [aria-checked="true"], ~ [aria-current="true"]) {
|
||||
opacity: 100%;
|
||||
}
|
||||
:focus-visible {
|
||||
scale: 1.1;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition: scale 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
:active:focus {
|
||||
animation: none;
|
||||
scale: 1.1;
|
||||
}
|
||||
}
|
||||
@layer daisyui.l1.l2 {
|
||||
--size: var(--size-selector, 0.25rem) * 6;
|
||||
}
|
||||
}
|
||||
.navbar {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: flex;
|
||||
@@ -1734,9 +1881,89 @@
|
||||
border-radius: var(--radius-box);
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
height: calc(0.25rem * 2);
|
||||
width: 100%;
|
||||
appearance: none;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-box);
|
||||
background-color: currentcolor;
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, currentcolor 20%, transparent);
|
||||
}
|
||||
color: var(--color-base-content);
|
||||
&:indeterminate {
|
||||
background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% );
|
||||
background-size: 200%;
|
||||
background-position-x: 15%;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: progress 5s ease-in-out infinite;
|
||||
}
|
||||
@supports (-moz-appearance: none) {
|
||||
&::-moz-progress-bar {
|
||||
background-color: transparent;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: progress 5s ease-in-out infinite;
|
||||
background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% );
|
||||
background-size: 200%;
|
||||
background-position-x: 15%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@supports (-moz-appearance: none) {
|
||||
&::-moz-progress-bar {
|
||||
border-radius: var(--radius-box);
|
||||
background-color: currentcolor;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition: inline-size 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
@supports (-webkit-appearance: none) {
|
||||
&::-webkit-progress-bar {
|
||||
border-radius: var(--radius-box);
|
||||
background-color: transparent;
|
||||
}
|
||||
&::-webkit-progress-value {
|
||||
border-radius: var(--radius-box);
|
||||
background-color: currentColor;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition: inline-size 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
.dropdown-right {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-h: right;
|
||||
--anchor-v: span-bottom;
|
||||
.dropdown-content {
|
||||
inset-inline-start: 100%;
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
transform-origin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown-left {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-h: left;
|
||||
--anchor-v: span-bottom;
|
||||
.dropdown-content {
|
||||
inset-inline-end: 100%;
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
transform-origin: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown-end {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-h: span-left;
|
||||
@@ -1931,6 +2158,26 @@
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
.tab-content {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
order: var(--tabcontent-order);
|
||||
display: none;
|
||||
border-color: transparent;
|
||||
--tabcontent-radius-ss: var(--radius-box);
|
||||
--tabcontent-radius-se: var(--radius-box);
|
||||
--tabcontent-radius-es: var(--radius-box);
|
||||
--tabcontent-radius-ee: var(--radius-box);
|
||||
--tabcontent-order: 1;
|
||||
width: 100%;
|
||||
height: calc(100% - var(--tab-height) + var(--border));
|
||||
margin: var(--tabcontent-margin);
|
||||
border-width: var(--border);
|
||||
border-start-start-radius: var(--tabcontent-radius-ss);
|
||||
border-start-end-radius: var(--tabcontent-radius-se);
|
||||
border-end-start-radius: var(--tabcontent-radius-es);
|
||||
border-end-end-radius: var(--tabcontent-radius-ee);
|
||||
}
|
||||
}
|
||||
.hero {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: grid;
|
||||
@@ -2328,6 +2575,46 @@
|
||||
.flex-none {
|
||||
flex: none;
|
||||
}
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.transform {
|
||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||
}
|
||||
.skeleton {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
border-radius: var(--radius-box);
|
||||
background-color: var(--color-base-300);
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
transition-duration: 15s;
|
||||
}
|
||||
will-change: background-position;
|
||||
background-image: linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% );
|
||||
background-size: 200% auto;
|
||||
background-position-x: -50%;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: skeleton 1.8s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
.aura-glow {
|
||||
@layer daisyui.l1.l2 {
|
||||
animation: none;
|
||||
background-image: radial-gradient(closest-corner at center, currentColor 0%, transparent 90%);
|
||||
&:before {
|
||||
animation: aura-glow var(--tw-duration, 6s) ease-out infinite;
|
||||
}
|
||||
&:after {
|
||||
animation: aura-glow-after var(--tw-duration, 6s) ease-out infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
.link {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
cursor: pointer;
|
||||
@@ -2349,6 +2636,9 @@
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -2435,6 +2725,9 @@
|
||||
.bg-base-200 {
|
||||
background-color: var(--color-base-200);
|
||||
}
|
||||
.bg-base-300 {
|
||||
background-color: var(--color-base-300);
|
||||
}
|
||||
.btn-link {
|
||||
@layer daisyui {
|
||||
text-decoration-line: underline;
|
||||
@@ -2553,6 +2846,9 @@
|
||||
--alert-color: var(--color-warning);
|
||||
}
|
||||
}
|
||||
.text-base-content {
|
||||
color: var(--color-base-content);
|
||||
}
|
||||
.text-base-content\/70 {
|
||||
color: var(--color-base-content);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -2587,6 +2883,9 @@
|
||||
text-decoration-line: none;
|
||||
}
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
.opacity-60 {
|
||||
opacity: 60%;
|
||||
}
|
||||
@@ -2601,9 +2900,26 @@
|
||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.outline {
|
||||
outline-style: var(--tw-outline-style);
|
||||
outline-width: 1px;
|
||||
}
|
||||
.filter {
|
||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||
}
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
.ease-in-out {
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
}
|
||||
.ease-out {
|
||||
--tw-ease: var(--ease-out);
|
||||
transition-timing-function: var(--ease-out);
|
||||
}
|
||||
.btn-error {
|
||||
@layer daisyui.l1.l2 {
|
||||
--btn-color: var(--color-error);
|
||||
@@ -2683,6 +2999,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
[data-theme="light"] .logo {
|
||||
content: var(--logo-dark);
|
||||
}
|
||||
[data-theme="dark"] .logo {
|
||||
content: var(--logo-light);
|
||||
}
|
||||
@layer base {
|
||||
:where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] {
|
||||
color-scheme: light;
|
||||
@@ -2969,6 +3291,26 @@
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@property --tw-rotate-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-rotate-y {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-rotate-z {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-skew-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-skew-y {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-space-y-reverse {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -3073,6 +3415,11 @@
|
||||
inherits: false;
|
||||
initial-value: 0 0 #0000;
|
||||
}
|
||||
@property --tw-outline-style {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@property --tw-blur {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -3126,9 +3473,18 @@
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-ease {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@layer properties {
|
||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
||||
*, ::before, ::after, ::backdrop {
|
||||
--tw-rotate-x: initial;
|
||||
--tw-rotate-y: initial;
|
||||
--tw-rotate-z: initial;
|
||||
--tw-skew-x: initial;
|
||||
--tw-skew-y: initial;
|
||||
--tw-space-y-reverse: 0;
|
||||
--tw-divide-y-reverse: 0;
|
||||
--tw-border-style: solid;
|
||||
@@ -3152,6 +3508,7 @@
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-outline-style: solid;
|
||||
--tw-blur: initial;
|
||||
--tw-brightness: initial;
|
||||
--tw-contrast: initial;
|
||||
@@ -3165,6 +3522,7 @@
|
||||
--tw-drop-shadow-color: initial;
|
||||
--tw-drop-shadow-alpha: 100%;
|
||||
--tw-drop-shadow-size: initial;
|
||||
--tw-ease: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
static/images/rosterchief-dark.svg
Normal file
6
static/images/rosterchief-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="200" height="200">
|
||||
<path d="M50 4 L90 18 V50 C90 76 72 92 50 98 C28 92 10 76 10 50 V18 Z" fill="#0F1A2E"></path>
|
||||
<rect x="27" y="30" width="46" height="8" rx="4" fill="#FFFFFF"></rect>
|
||||
<rect x="27" y="44" width="34" height="8" rx="4" fill="#FFFFFF"></rect>
|
||||
<path d="M27 80 L50 62 L73 80 L66 85 L50 72 L34 85 Z" fill="#FFFFFF"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
6
static/images/rosterchief-white.svg
Normal file
6
static/images/rosterchief-white.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="200" height="200">
|
||||
<path d="M50 4 L90 18 V50 C90 76 72 92 50 98 C28 92 10 76 10 50 V18 Z" fill="#FFFFFF"></path>
|
||||
<rect x="27" y="30" width="46" height="8" rx="4" fill="#0F1A2E"></rect>
|
||||
<rect x="27" y="44" width="34" height="8" rx="4" fill="#0F1A2E"></rect>
|
||||
<path d="M27 80 L50 62 L73 80 L66 85 L50 72 L34 85 Z" fill="#0F1A2E"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
@@ -8,5 +8,13 @@
|
||||
/* daisyUI: light is the default, dark applies automatically when the OS asks
|
||||
for it. An explicit data-theme on <html> (set by the toggle) overrides both. */
|
||||
@plugin "daisyui" {
|
||||
themes: light --default, dark --prefersdark;
|
||||
themes: light --default, dark --prefersdark;
|
||||
}
|
||||
|
||||
[data-theme="light"] .logo {
|
||||
content: var(--logo-dark);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .logo {
|
||||
content: var(--logo-light);
|
||||
}
|
||||
|
||||
33
templates/_controlpanel_base.html
Normal file
33
templates/_controlpanel_base.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% load static lucide %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>RosterChief - Control Panel</title>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const stored = localStorage.getItem('theme');
|
||||
if (stored) document.documentElement.setAttribute("data-theme", stored);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--logo-light: url("{% static "images/rosterchief-white.svg" %}");
|
||||
--logo-dark: url("{% static "images/rosterchief-black.svg" %}");
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/app.css' %}"/>
|
||||
|
||||
{% block extra %}{% endblock extra %}
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen bg-base-200">
|
||||
{% block main %}{% endblock main %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "_controlpanel_base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{% block head_title %}{% endblock head_title %} · RosterChief
|
||||
|
||||
15
uv.lock
generated
15
uv.lock
generated
@@ -213,6 +213,19 @@ mfa = [
|
||||
{ name = "qrcode" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-browser-reload"
|
||||
version = "1.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/79/ef/ab407c1a2f14e13c75a6419a2d124954aa0364f62580ad95a3d31dc6c73d/django_browser_reload-1.21.0.tar.gz", hash = "sha256:3335ad3d107eb657f623d1a8e680dfbcab8a83ae1f94df1895e069dddf5604ba", size = 15800, upload-time = "2025-09-22T17:00:35.199Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/61/1b4a8c589652859995bcab87682286443eb9fdf2d7fd584975b9ffc1db33/django_browser_reload-1.21.0-py3-none-any.whl", hash = "sha256:0b2a86ab460774fa9bb142a121c70e75a72f18109f51a4f6de409cd633d3a70d", size = 12852, upload-time = "2025-09-22T17:00:33.479Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-lucide"
|
||||
version = "1.3.1"
|
||||
@@ -379,6 +392,7 @@ dependencies = [
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "coverage" },
|
||||
{ name = "django-browser-reload" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
@@ -398,6 +412,7 @@ requires-dist = [
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "coverage", specifier = ">=7.15.0" },
|
||||
{ name = "django-browser-reload", specifier = ">=1.21.0" },
|
||||
{ name = "ruff", specifier = ">=0.15.17" },
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user