Replace the mobile nav's horizontal scroll strip (which wrapped into an unreadable jumble because daisyUI's .menu sets flex-wrap: wrap and .menu-horizontal never resets it) with a hamburger button that opens a proper left-side drawer, mirrored in controlpanel for the same bug. Move the theme toggle, "Management", and "Django admin" controls into that drawer below `lg`, leaving only the account icon in the navbar, so the club name has room to render in full instead of truncating to initials. Add a reusable `.table-cards` CSS pattern (app.css) that turns a list table into a stack of labelled cards below `md`, and apply it to every list/detail table in the management app -- members, families, teams, memberships, events, groups, locations, opponents, positions, referee levels/list, roles, sponsors, news, parent claims, team roster/staff, and the shared family-members table. Tables revert to normal desktop layout at `md` and up. Member list gets extra passes: search submits icon-only below `sm` so it fits next to the input, the Members/Guardians/Both tabs go full-width, and last/first name merge into one column. Action-button rows across every page stack full-width on mobile instead of wrapping mid-button (shared fix in base.html). Home dashboard's upcoming-events and news mini-tables become simple lists instead of overflowing tables. Also: brand truncates instead of overflowing on a long club name, a stray invalid xmlns attribute removed, a wrapping UUID badge hidden below `sm` on team detail. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
269 lines
11 KiB
CSS
269 lines
11 KiB
CSS
@import "tailwindcss";
|
|
|
|
/* Scan Django templates for utility classes (Tailwind's auto-detection doesn't
|
|
know about our template dirs). */
|
|
@source "../templates";
|
|
@source "../controlpanel";
|
|
@source "../billing";
|
|
@source "../management";
|
|
@source "../club";
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Fonts are self-hosted (files copied from the @fontsource packages into
|
|
static/fonts/, paths are relative to the built css at /static/css/app.css).
|
|
Google's CDN would leak every visitor's IP to a third party on page load,
|
|
which we don't want to inherit for an EU club platform.
|
|
|
|
Two subsets each: `latin` covers western europe, `latin-ext` carries the
|
|
polish/czech/turkish letters that turn up in member names. The unicode-range
|
|
means a browser only fetches latin-ext when a page actually uses those glyphs.
|
|
|
|
Ubuntu is static (it has no variable version); its real weights are 400/500/700,
|
|
so `font-semibold` (600) is synthesised up to 700 by the browser. JetBrains Mono
|
|
and Roboto are variable: one file covers the whole weight axis. */
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-400-normal.woff2") format("woff2");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-ext-400-normal.woff2") format("woff2");
|
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: italic;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-400-italic.woff2") format("woff2");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: italic;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-ext-400-italic.woff2") format("woff2");
|
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: normal;
|
|
font-weight: 500;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-500-normal.woff2") format("woff2");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: normal;
|
|
font-weight: 500;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-ext-500-normal.woff2") format("woff2");
|
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-700-normal.woff2") format("woff2");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Ubuntu";
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-display: swap;
|
|
src: url("../fonts/ubuntu-latin-ext-700-normal.woff2") format("woff2");
|
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "JetBrains Mono";
|
|
font-style: normal;
|
|
font-weight: 100 800;
|
|
font-display: swap;
|
|
src: url("../fonts/jetbrains-mono-latin-wght-normal.woff2") format("woff2-variations");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "JetBrains Mono";
|
|
font-style: normal;
|
|
font-weight: 100 800;
|
|
font-display: swap;
|
|
src: url("../fonts/jetbrains-mono-latin-ext-wght-normal.woff2") format("woff2-variations");
|
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Roboto";
|
|
font-style: normal;
|
|
font-weight: 100 900;
|
|
font-display: swap;
|
|
src: url("../fonts/roboto-latin-standard-normal.woff2") format("woff2-variations");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Roboto";
|
|
font-style: normal;
|
|
font-weight: 100 900;
|
|
font-display: swap;
|
|
src: url("../fonts/roboto-latin-ext-standard-normal.woff2") format("woff2-variations");
|
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
|
|
/* Tourney is a display face for jersey numbers and the like. Its variable file
|
|
carries two axes -- weight 100-900 and width 75-125 -- so font-stretch has to be
|
|
declared as a range too, otherwise the browser clamps to the default width and
|
|
`font-stretch: 125%` (a wide shirt number) silently does nothing. */
|
|
@font-face {
|
|
font-family: "Tourney";
|
|
font-style: normal;
|
|
font-weight: 100 900;
|
|
font-stretch: 75% 125%;
|
|
font-display: swap;
|
|
src: url("../fonts/tourney-latin-standard-normal.woff2") format("woff2-variations");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "Tourney";
|
|
font-style: normal;
|
|
font-weight: 100 900;
|
|
font-stretch: 75% 125%;
|
|
font-display: swap;
|
|
src: url("../fonts/tourney-latin-ext-standard-normal.woff2") format("woff2-variations");
|
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
}
|
|
|
|
/* Setting --font-sans / --font-mono changes the body and <code> defaults; every
|
|
--font-* also generates a utility, so --font-roboto gives us `font-roboto` to
|
|
opt into Roboto where we want it. */
|
|
@theme {
|
|
--font-sans: "Ubuntu", ui-sans-serif, system-ui, sans-serif;
|
|
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
|
--font-ubuntu: "Ubuntu", ui-sans-serif, system-ui, sans-serif;
|
|
--font-roboto: "Roboto", ui-sans-serif, system-ui, sans-serif;
|
|
--font-tourney: "Tourney", ui-sans-serif, system-ui, sans-serif;
|
|
}
|
|
|
|
/* The logo is a background image, not `content:` -- content-replacement on a real
|
|
element (rather than ::before/::after) isn't supported in Firefox.
|
|
|
|
Default = dark-ink logo, for a light background. The media query covers "auto",
|
|
where the toggle deliberately sets no data-theme at all; the attribute selectors
|
|
are more specific, so an explicit choice always beats the OS. */
|
|
.logo {
|
|
background-image: var(--logo-dark);
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
background-size: contain;
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
.logo {
|
|
background-image: var(--logo-light);
|
|
}
|
|
}
|
|
|
|
[data-theme="light"] .logo {
|
|
background-image: var(--logo-dark);
|
|
}
|
|
|
|
[data-theme="dark"] .logo {
|
|
background-image: var(--logo-light);
|
|
}
|
|
|
|
/* Club-uploaded logos are arbitrary raster files (often much smaller than the
|
|
badge they're displayed in), so the browser is upscaling them -- and the
|
|
default resampling some engines fall back to for that reads as pixelated.
|
|
Forcing the element onto its own GPU-composited layer makes Chrome/Safari
|
|
pick their higher-quality scaler; the image-rendering hints are a no-op
|
|
where unsupported, degrading gracefully to the browser default. Doesn't
|
|
apply to SVG logos, which scale losslessly regardless. */
|
|
.club-logo {
|
|
image-rendering: -webkit-optimize-contrast;
|
|
image-rendering: smooth;
|
|
image-rendering: high-quality;
|
|
backface-visibility: hidden;
|
|
transform: translateZ(0);
|
|
}
|
|
|
|
/* Below `md`, a `.table-cards` table reads as a stack of cards instead of a
|
|
horizontally-scrolling grid: each row becomes a bordered block, each cell
|
|
becomes a label/value line pulling its label from `data-label` (set on the
|
|
<td> in the template -- there's no way to read the matching <th> text from
|
|
pure CSS). A <td> with no `data-label` renders as plain full-width content
|
|
instead of a label/value row -- for an image cell, a heading-style link, or
|
|
an actions row that already lays itself out. At `md` and up this reverts to
|
|
an ordinary table with nothing left over from the mobile styling. */
|
|
@media (width < 48rem) {
|
|
.table-cards, .table-cards :is(thead, tbody, tr, th, td) {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
.table-cards thead {
|
|
display: none;
|
|
}
|
|
|
|
.table-cards tbody tr {
|
|
border: 1px solid var(--color-base-300);
|
|
border-radius: var(--radius-box);
|
|
padding: 0 0.75rem;
|
|
}
|
|
|
|
.table-cards tbody tr + tr {
|
|
margin-top: 0.75rem;
|
|
}
|
|
|
|
.table-cards td {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
padding: 0.6rem 0;
|
|
border-bottom: 1px solid var(--color-base-200);
|
|
}
|
|
|
|
.table-cards td:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.table-cards td[data-label]:before {
|
|
content: attr(data-label);
|
|
flex-shrink: 0;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
text-transform: uppercase;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.table-cards td:not([data-label]) {
|
|
justify-content: flex-start;
|
|
}
|
|
}
|