Scaffold the mobile member app: PWA shell, push, and app-shell tokens
New `mobile` Django app mounted at /app/ -- the installed PWA for Member mode (M1-M7, see design_handoff_rosterchief_platform/README.md). Coach mode (C1-C6) is a later phase and has no routes yet. Foundation pieces: - assets/mobile.css: Tailwind v4 theme reusing management.css's design tokens (same per-club --tenant-* theming pattern), plus the ice/coach accent and mobile's 14px card radius. - PushSubscription model + pywebpush-based sender (mobile/services/push.py), wired to notifications.Notification via a post_save signal so the existing notification system gains a push channel without knowing about PWAs itself. - Per-club manifest.webmanifest + service worker (served at /app/sw.js) + a server-rendered fallback home-screen icon (club initials on secondary_color) for clubs without an uploaded logo -- confirmed with the user as the fallback, never a generic RosterChief mark. - App shell (base.html): navy header, person switcher (every child a signed-in parent manages, plus "Me"), bottom tab bar, safe-area insets. Vendored htmx + Alpine for the screens built on top of it. - Placeholder views/routes for all seven M1-M7 screens so the shell is fully wired end-to-end before each screen is built out individually. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
419
assets/mobile.css
Normal file
419
assets/mobile.css
Normal file
@@ -0,0 +1,419 @@
|
||||
/* RosterChief member app -- the installed PWA (see design_handoff_rosterchief_platform/
|
||||
* README.md, "Member mode (mobile, 402x874 reference)"). Its own stylesheet, not shared
|
||||
* with management.css/controlpanel.css/app.css: the component vocabulary (app header,
|
||||
* role switcher, bottom tab bar, hero card) has nothing in common with the desktop
|
||||
* surfaces' daisyUI-shaped one.
|
||||
*
|
||||
* Design tokens copied from management.css's own @theme (built against the same design
|
||||
* doc) rather than re-derived -- same colours, same self-hosted fonts (shared
|
||||
* static/fonts/ directory, one download for every stylesheet). Two differences:
|
||||
* - --radius-box is 14px here (mobile cards), not management's 0.75rem/12px (desktop
|
||||
* "rounded-xl" cards) -- see the design doc's own radius table.
|
||||
* - --color-ice/--color-ice-ink get their own names (management just calls the same
|
||||
* #14B8E8 hex "info", since it has no coach-mode concept) -- the design doc is
|
||||
* explicit that Coach mode uses ice "regardless of club", so it needs a name that
|
||||
* survives club theming rather than colliding with a generic status colour.
|
||||
*
|
||||
* Club theming (design doc, "Club theming"): mobile/templates/mobile/base.html sets
|
||||
* --tenant-club/--tenant-club-content from Club.secondary_color (app header active tab,
|
||||
* primary buttons, hero eyebrow) and --tenant-navy from Club.primary_color where the
|
||||
* club overrides the platform navy header -- same --tenant-* custom-property pattern
|
||||
* management/templates/management/base.html already uses, so both surfaces stay
|
||||
* consistent with each other and with the identity page's own live preview.
|
||||
*/
|
||||
@import "tailwindcss";
|
||||
|
||||
@source "../mobile";
|
||||
|
||||
@theme {
|
||||
--font-display: "Barlow Condensed", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-sans: "Barlow", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, monospace;
|
||||
|
||||
--color-ink: #0b1220;
|
||||
--color-navy: var(--tenant-navy, #101e36);
|
||||
--color-steel: #1b2b47;
|
||||
--color-hairline: #1e2b42;
|
||||
--color-paper: #f4f5f7;
|
||||
--color-line: #e3e6eb;
|
||||
--color-rule: #eef0f3;
|
||||
--color-edge: #d6dae1;
|
||||
--color-stroke: #c9cfd8;
|
||||
--color-muted: #6c7787;
|
||||
--color-dim: #8b95a4;
|
||||
--color-slate: #3a4658;
|
||||
--color-on-dark: #93a0b4;
|
||||
--color-on-dark-dim: #7c8aa0;
|
||||
--color-on-dark-faint: #5c6b85;
|
||||
|
||||
--color-club: var(--tenant-club, #e4002b);
|
||||
--color-club-dark: var(--tenant-club-dark, #b00021);
|
||||
--color-club-content: var(--tenant-club-content, #ffffff);
|
||||
|
||||
--color-ok: #14a05a;
|
||||
--color-ok-bg: #e6f6ee;
|
||||
--color-ok-border: #bfe7d3;
|
||||
--color-ok-text: #0c7a43;
|
||||
|
||||
--color-warn: #f0a22e;
|
||||
--color-warn-bg: #fff5e4;
|
||||
--color-warn-border: #f6e0b8;
|
||||
--color-warn-text: #9a6410;
|
||||
--color-warn-deep: #7a4e08;
|
||||
|
||||
--color-danger-bg: #fdecec;
|
||||
--color-danger-border: #f5c9ce;
|
||||
|
||||
--color-info: #14b8e8;
|
||||
--color-info-bg: #eaf7fc;
|
||||
--color-info-border: #c3e7f4;
|
||||
--color-info-text: #0a6f91;
|
||||
|
||||
/* Coach-mode accent -- never club-themed, see the file banner. */
|
||||
--color-ice: #14b8e8;
|
||||
--color-ice-ink: #04212c;
|
||||
|
||||
--color-row-sel: #fff7f8;
|
||||
--color-row-focus: #f4f9ff;
|
||||
--color-row-warn: #fffdf6;
|
||||
--color-subhead: #f8f9fa;
|
||||
|
||||
--radius-box: 0.875rem; /* 14px -- mobile card radius, see the file banner */
|
||||
}
|
||||
|
||||
/* --- fonts: identical set to management.css/controlpanel.css, same self-hosted files. --- */
|
||||
|
||||
@font-face {
|
||||
font-family: "Barlow";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("../fonts/barlow-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: "Barlow";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("../fonts/barlow-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: "Barlow";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("../fonts/barlow-latin-600-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: "Barlow";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("../fonts/barlow-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: "Barlow Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("../fonts/barlow-condensed-latin-600-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: "Barlow Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("../fonts/barlow-condensed-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: "Barlow Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("../fonts/barlow-condensed-latin-800-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: "IBM Plex Mono";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("../fonts/ibm-plex-mono-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: "IBM Plex Mono";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("../fonts/ibm-plex-mono-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;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
background: var(--color-paper);
|
||||
color: var(--color-slate);
|
||||
font-family: var(--font-sans);
|
||||
/* env(safe-area-inset-*) support -- design doc: "The app content starts below a
|
||||
54px status-bar inset and ends above a 26-30px home-indicator inset -- preserve
|
||||
those safe areas via env(safe-area-inset-*), not fixed padding." */
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* --- App shell -------------------------------------------------------------- */
|
||||
|
||||
.app-header {
|
||||
padding: max(env(safe-area-inset-top), 14px) 16px 14px;
|
||||
background: var(--color-navy);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.app-crest {
|
||||
width: 30px;
|
||||
height: 32px;
|
||||
background: var(--color-club);
|
||||
clip-path: polygon(50% 0, 100% 18%, 100% 62%, 50% 100%, 0 62%, 0 18%);
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Role switcher -- design doc "Component" table: full-width segmented pill,
|
||||
bg-steel track, active segment bg-white text-ink (member mode). Only rendered
|
||||
when the account holds >=1 staff role -- see mobile/templates/mobile/base.html. */
|
||||
.role-switcher {
|
||||
display: flex;
|
||||
background: var(--color-steel);
|
||||
border-radius: 999px;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.role-switcher-item {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 800;
|
||||
font-size: 0.875rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-on-dark);
|
||||
}
|
||||
|
||||
.role-switcher-item-active {
|
||||
background: #fff;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
/* Bottom tab bar -- design doc: "bg-white border-t border-line pt-2 pb-[26px]
|
||||
(member)... four equal items, 48px tall". pb uses the safe-area inset instead of
|
||||
the doc's fixed 26px, which was standing in for it. */
|
||||
.tab-bar {
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--color-line);
|
||||
padding: 8px 8px max(env(safe-area-inset-bottom), 26px);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab-bar-item {
|
||||
flex: 1;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
color: var(--color-dim);
|
||||
}
|
||||
|
||||
.tab-bar-item-active {
|
||||
color: var(--color-club);
|
||||
}
|
||||
|
||||
.tab-bar-label {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* --- Buttons ------------------------------------------------------------------ */
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
height: 46px; /* design doc: "height 46px mobile" */
|
||||
padding-inline: 1.25rem;
|
||||
border-radius: 0.5rem;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-club);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-dark {
|
||||
background: var(--color-ink);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-positive {
|
||||
background: var(--color-ok);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #fff;
|
||||
border: 1.5px solid var(--color-stroke);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.btn-ghost-on-dark {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* --- Status pills --------------------------------------------------------------- */
|
||||
|
||||
.pill {
|
||||
border-radius: 999px;
|
||||
padding: 5px 10px;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pill-ok {
|
||||
background: var(--color-ok-bg);
|
||||
color: var(--color-ok-text);
|
||||
border: 1px solid var(--color-ok-border);
|
||||
}
|
||||
|
||||
.pill-danger {
|
||||
background: var(--color-danger-bg);
|
||||
color: var(--color-club-dark);
|
||||
border: 1px solid var(--color-danger-border);
|
||||
}
|
||||
|
||||
.pill-warn {
|
||||
background: var(--color-warn-bg);
|
||||
color: var(--color-warn-text);
|
||||
border: 1px solid var(--color-warn-border);
|
||||
}
|
||||
|
||||
.pill-info {
|
||||
background: var(--color-info-bg);
|
||||
color: var(--color-info-text);
|
||||
border: 1px solid var(--color-info-border);
|
||||
}
|
||||
|
||||
.pill-neutral {
|
||||
background: var(--color-rule);
|
||||
color: #4a5566;
|
||||
border: 1px solid #dde1e7;
|
||||
}
|
||||
|
||||
/* --- Person switcher chips (M1, M2, M3 -- "a person switcher on anything per-member") --- */
|
||||
|
||||
.person-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #fff;
|
||||
border: 1.5px solid var(--color-line);
|
||||
border-radius: 999px;
|
||||
padding: 5px 12px 5px 5px;
|
||||
color: var(--color-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.person-chip-active {
|
||||
border-color: var(--color-ink);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.person-chip-avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 999px;
|
||||
background: var(--color-stroke);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 800;
|
||||
font-size: 0.875rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.person-chip-active .person-chip-avatar {
|
||||
background: var(--color-club);
|
||||
}
|
||||
|
||||
.person-chip-label {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 0.9375rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* --- Cards ---------------------------------------------------------------------- */
|
||||
|
||||
.m-card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: var(--radius-box);
|
||||
}
|
||||
|
||||
.m-card-dark {
|
||||
background: var(--color-ink);
|
||||
color: #fff;
|
||||
border-radius: var(--radius-box);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user