first commit
916
app/static/css/style.css
Normal file
@@ -0,0 +1,916 @@
|
||||
@font-face {
|
||||
font-family: "InterLocal";
|
||||
src: url("../fonts/Inter_24pt-Regular.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterLocal";
|
||||
src: url("../fonts/Inter_24pt-Medium.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterLocal";
|
||||
src: url("../fonts/Inter_24pt-SemiBold.ttf") format("truetype");
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterLocal";
|
||||
src: url("../fonts/Inter_24pt-Bold.ttf") format("truetype");
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SpaceGroteskLocal";
|
||||
src: url("../fonts/SpaceGrotesk-Regular.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SpaceGroteskLocal";
|
||||
src: url("../fonts/SpaceGrotesk-SemiBold.ttf") format("truetype");
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SpaceGroteskLocal";
|
||||
src: url("../fonts/SpaceGrotesk-Bold.ttf") format("truetype");
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: #eef3ff;
|
||||
--bg-accent: #d8e6ff;
|
||||
--surface: rgba(255, 255, 255, 0.85);
|
||||
--surface-strong: #ffffff;
|
||||
--surface-soft: rgba(244, 248, 255, 0.95);
|
||||
--text: #223049;
|
||||
--muted: #64748b;
|
||||
--border: rgba(132, 152, 190, 0.24);
|
||||
--primary: #2563eb;
|
||||
--primary-strong: #1745c1;
|
||||
--secondary: #edf4ff;
|
||||
--success: #059669;
|
||||
--warning: #f59e0b;
|
||||
--danger: #e11d48;
|
||||
--shadow: 0 24px 60px rgba(52, 79, 131, 0.16);
|
||||
--radius-lg: 28px;
|
||||
--radius-md: 22px;
|
||||
--radius-sm: 16px;
|
||||
--font-body: "InterLocal", system-ui, sans-serif;
|
||||
--font-heading: "SpaceGroteskLocal", "InterLocal", sans-serif;
|
||||
--safe-bottom: max(24px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: var(--font-body);
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(181, 210, 255, 0.85), transparent 32%),
|
||||
radial-gradient(circle at top right, rgba(255, 221, 196, 0.48), transparent 32%),
|
||||
linear-gradient(180deg, #f8fbff 0%, #eef3ff 42%, #edf2ff 100%);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-family: var(--font-heading);
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
min-height: 100vh;
|
||||
padding: 24px 18px calc(100px + var(--safe-bottom));
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.topbar h1 {
|
||||
font-size: clamp(1.9rem, 4vw, 2.9rem);
|
||||
}
|
||||
|
||||
.topbar-user {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.panel,
|
||||
.hero-card,
|
||||
.task-card,
|
||||
.score-row,
|
||||
.sidebar-card,
|
||||
.calendar-day,
|
||||
.archive-row,
|
||||
.badge-setting-card,
|
||||
.list-row {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
backdrop-filter: blur(18px);
|
||||
box-shadow: var(--shadow);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.panel,
|
||||
.hero-card,
|
||||
.score-row,
|
||||
.list-row {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.hero-grid,
|
||||
.two-column {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(245, 249, 255, 0.86)),
|
||||
linear-gradient(135deg, rgba(37, 99, 235, 0.06), rgba(5, 150, 105, 0.03));
|
||||
}
|
||||
|
||||
.hero-card h2 {
|
||||
margin-bottom: 12px;
|
||||
font-size: clamp(1.8rem, 4vw, 2.7rem);
|
||||
}
|
||||
|
||||
.hero-card p {
|
||||
color: var(--muted);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.hero-card--brand {
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
margin-top: 22px;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.hero-stats div {
|
||||
padding: 16px;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid rgba(150, 173, 214, 0.18);
|
||||
}
|
||||
|
||||
.hero-stats strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.brand strong {
|
||||
display: block;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.brand span {
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.brand__logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.flash-stack {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.flash {
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--border);
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
.flash--success {
|
||||
border-color: rgba(5, 150, 105, 0.2);
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.flash--error {
|
||||
border-color: rgba(225, 29, 72, 0.2);
|
||||
color: #9f1239;
|
||||
}
|
||||
|
||||
.flash--info {
|
||||
color: var(--primary-strong);
|
||||
}
|
||||
|
||||
.progress-card {
|
||||
margin-top: 22px;
|
||||
padding: 18px;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(255, 255, 255, 0.74);
|
||||
border: 1px solid rgba(132, 152, 190, 0.2);
|
||||
}
|
||||
|
||||
.progress-card__top,
|
||||
.section-heading,
|
||||
.task-card__top,
|
||||
.score-row__head,
|
||||
.score-row__meta,
|
||||
.task-card__footer,
|
||||
.sidebar-card__row,
|
||||
.toolbar-actions,
|
||||
.archive-row,
|
||||
.list-row,
|
||||
.push-box__state,
|
||||
.form-actions,
|
||||
.field-inline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-top: 12px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(162, 182, 218, 0.24);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress span,
|
||||
.score-bar span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, #2563eb, #34d399);
|
||||
}
|
||||
|
||||
.stack,
|
||||
.task-grid,
|
||||
.scoreboard,
|
||||
.archive-list,
|
||||
.badge-settings {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.section-heading h2,
|
||||
.panel h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.section-heading__count,
|
||||
.point-pill,
|
||||
.reward-chip,
|
||||
.rank-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.section-heading__count,
|
||||
.point-pill {
|
||||
background: var(--secondary);
|
||||
color: var(--primary-strong);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 28px;
|
||||
border: 1px dashed rgba(132, 152, 190, 0.44);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.58);
|
||||
}
|
||||
|
||||
.task-card {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.task-card h3 {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.task-card--compact {
|
||||
opacity: 0.94;
|
||||
}
|
||||
|
||||
.chip-row,
|
||||
.badge-cloud {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
font-size: 0.83rem;
|
||||
}
|
||||
|
||||
.status-badge--open {
|
||||
background: #e8f0ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.status-badge--soon {
|
||||
background: #fff3d6;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.status-badge--overdue {
|
||||
background: #ffe3ea;
|
||||
color: #be123c;
|
||||
}
|
||||
|
||||
.status-badge--completed {
|
||||
background: #ddfbf1;
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.task-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.task-meta dt {
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.task-meta dd {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.task-assignee {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 10px 24px rgba(78, 100, 141, 0.14);
|
||||
}
|
||||
|
||||
.avatar--lg {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.done-hint,
|
||||
.muted,
|
||||
.inline-note,
|
||||
.sidebar-card p,
|
||||
.score-row__meta,
|
||||
.archive-row__right small,
|
||||
.calendar-task small {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.button,
|
||||
.button--ghost,
|
||||
.button--secondary,
|
||||
.icon-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
min-height: 48px;
|
||||
padding: 0 18px;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, var(--primary), #4f8cff);
|
||||
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.24);
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button--secondary:hover,
|
||||
.button--ghost:hover,
|
||||
.icon-button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button--secondary {
|
||||
background: var(--secondary);
|
||||
color: var(--primary-strong);
|
||||
}
|
||||
|
||||
.button--ghost {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(132, 152, 190, 0.3);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.button--wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
width: 48px;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.74);
|
||||
border: 1px solid rgba(132, 152, 190, 0.24);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-grid--two {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.field--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.field label,
|
||||
.checkbox span {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.field input,
|
||||
.field select,
|
||||
.field textarea {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgba(132, 152, 190, 0.3);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.field--compact input,
|
||||
.field--compact select {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.checkbox input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.checkbox--compact {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.filter-bar,
|
||||
.inline-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.panel--toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.segmented {
|
||||
display: inline-flex;
|
||||
padding: 4px;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.74);
|
||||
}
|
||||
|
||||
.segmented a {
|
||||
padding: 10px 16px;
|
||||
border-radius: 14px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.segmented .is-active {
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.calendar-grid__weekdays {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
min-height: 132px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.calendar-day strong {
|
||||
display: inline-flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.calendar-day--empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.calendar-day__tasks {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.calendar-task {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.calendar-task--open {
|
||||
border-left: 4px solid #2563eb;
|
||||
}
|
||||
|
||||
.calendar-task--soon {
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.calendar-task--overdue {
|
||||
border-left: 4px solid #e11d48;
|
||||
}
|
||||
|
||||
.calendar-task--completed {
|
||||
border-left: 4px solid #059669;
|
||||
}
|
||||
|
||||
.score-row {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.score-row--leader {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(246, 255, 249, 0.92)),
|
||||
linear-gradient(135deg, rgba(52, 211, 153, 0.08), rgba(37, 99, 235, 0.08));
|
||||
}
|
||||
|
||||
.score-row__person,
|
||||
.archive-row__left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.score-row__points strong {
|
||||
display: block;
|
||||
font-size: clamp(1.8rem, 4vw, 2.8rem);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.score-bar {
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(162, 182, 218, 0.22);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.reward-chip,
|
||||
.rank-badge {
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
color: var(--primary-strong);
|
||||
}
|
||||
|
||||
.badge-setting-card {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.push-box {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.push-box__state {
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.66);
|
||||
}
|
||||
|
||||
.push-box__state.is-disabled {
|
||||
border: 1px dashed rgba(225, 29, 72, 0.26);
|
||||
}
|
||||
|
||||
.push-box__state.is-ready {
|
||||
border: 1px solid rgba(5, 150, 105, 0.18);
|
||||
}
|
||||
|
||||
.auth-layout {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.auth-panel {
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
left: 14px;
|
||||
right: 14px;
|
||||
bottom: calc(10px + env(safe-area-inset-bottom));
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: 0 24px 44px rgba(58, 82, 128, 0.2);
|
||||
border: 1px solid rgba(132, 152, 190, 0.22);
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.bottom-nav__item,
|
||||
.nav-link {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 6px;
|
||||
color: var(--muted);
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
font-size: 0.73rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bottom-nav__item.is-active,
|
||||
.nav-link.is-active {
|
||||
color: var(--primary-strong);
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.nav-icon,
|
||||
.nav-icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.complete-dialog {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.complete-dialog::backdrop {
|
||||
background: rgba(18, 31, 56, 0.36);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.complete-dialog__surface {
|
||||
width: min(460px, calc(100vw - 24px));
|
||||
padding: 24px;
|
||||
border-radius: 28px;
|
||||
background: #fff;
|
||||
box-shadow: var(--shadow);
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.choice-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.text-link {
|
||||
color: var(--primary-strong);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media (min-width: 760px) {
|
||||
.page-shell {
|
||||
padding: 28px 28px 32px;
|
||||
}
|
||||
|
||||
.hero-grid,
|
||||
.two-column,
|
||||
.auth-layout {
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(280px, 0.95fr);
|
||||
}
|
||||
|
||||
.task-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.form-grid--two,
|
||||
.badge-settings {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.calendar-grid__weekdays {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.calendar-day--empty {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.app-shell {
|
||||
display: grid;
|
||||
grid-template-columns: 290px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
height: 100vh;
|
||||
padding: 28px 20px;
|
||||
border-right: 1px solid rgba(132, 152, 190, 0.2);
|
||||
background: rgba(248, 251, 255, 0.72);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
grid-template-columns: auto 1fr;
|
||||
justify-items: start;
|
||||
text-align: left;
|
||||
font-size: 0.94rem;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
padding: 36px 40px 48px;
|
||||
}
|
||||
|
||||
.topbar-user {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px 8px 18px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(132, 152, 190, 0.2);
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.task-grid,
|
||||
.scoreboard {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.form-grid--two {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
BIN
app/static/fonts/Inter_24pt-Bold.ttf
Normal file
BIN
app/static/fonts/Inter_24pt-Medium.ttf
Normal file
BIN
app/static/fonts/Inter_24pt-Regular.ttf
Normal file
BIN
app/static/fonts/Inter_24pt-SemiBold.ttf
Normal file
BIN
app/static/fonts/SpaceGrotesk-Bold.ttf
Normal file
BIN
app/static/fonts/SpaceGrotesk-Regular.ttf
Normal file
BIN
app/static/fonts/SpaceGrotesk-SemiBold.ttf
Normal file
1
app/static/icons/bell.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M240.4 40.2C243.1 17.4 257.8 0 288 0C318.2 0 332.9 17.4 335.6 40.2C376.9 48 409.9 66.3 434 93.1C466.3 129.1 480 177.5 480 227.9L480 265.2C546.5 299 576 362.5 576 431.9L576 479.9L384 479.9C384 503.9 377.5 528.2 360.8 546.8C343.7 565.9 318.6 575.9 288 575.9C257.4 575.9 232.4 565.9 215.2 546.8C198.5 528.3 192 504 192 480L0 480L0 432C0 362.6 29.5 299.2 96 265.3L96 228C96 177.5 109.7 129.2 142 93.2C166.1 66.4 199.1 48 240.4 40.3zM213.4 121.6C198.6 146.4 192 183 192 228L192 296.8L177.1 302.9C121.4 325.7 96 373.2 96 432L480 432C480 373.2 454.6 325.7 398.9 302.9L384 296.8L384 228C384 183 377.4 146.4 362.5 121.6C348.9 98.9 327 84 288 84C249 84 227.1 98.9 213.5 121.6zM271.9 518.6C275.8 525.2 280.4 528 287.9 528C295.4 528 300 525.2 303.9 518.6C308.6 510.7 311.9 497.6 311.9 480L263.9 480C263.9 497.6 267.2 510.7 271.9 518.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
app/static/icons/calendar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M192 0L192 48L384 48L384 0L432 0L432 48L576 48L576 432C576 485 533 528 480 528L96 528C43 528 0 485 0 432L0 48L144 48L144 0L192 0zM96 96L96 144L480 144L480 96L96 96zM96 192L96 432C96 458.5 117.5 480 144 480L432 480C458.5 480 480 458.5 480 432L480 192L96 192zM288 312C262.8 312 252 295.9 252 276C252 256.1 262.8 240 288 240C313.2 240 324 256.1 324 276C324 295.9 313.2 312 288 312zM432 276C432 295.9 421.2 312 396 312C370.8 312 360 295.9 360 276C360 256.1 370.8 240 396 240C421.2 240 432 256.1 432 276zM180 312C154.8 312 144 295.9 144 276C144 256.1 154.8 240 180 240C205.2 240 216 256.1 216 276C216 295.9 205.2 312 180 312zM324 396C324 415.9 313.2 432 288 432C262.8 432 252 415.9 252 396C252 376.1 262.8 360 288 360C313.2 360 324 376.1 324 396zM396 432C370.8 432 360 415.9 360 396C360 376.1 370.8 360 396 360C421.2 360 432 376.1 432 396C432 415.9 421.2 432 396 432zM216 396C216 415.9 205.2 432 180 432C154.8 432 144 415.9 144 396C144 376.1 154.8 360 180 360C205.2 360 216 376.1 216 396z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
app/static/icons/chart-bar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M408 528L408 24L552 24L552 456C552 509 509 552 456 552L408 552L408 528zM504 408L504 72L456 72L456 456C482.5 456 504 434.5 504 408zM216 528L216 144L360 144L360 552L216 552L216 528zM312 456L312 192L264 192L264 456L312 456zM168 528L168 552L120 552C67 552 24 509 24 456L24 264L168 264L168 528zM120 456L120 312L72 312L72 408C72 434.5 93.5 456 120 456z"/></svg>
|
||||
|
After Width: | Height: | Size: 614 B |
1
app/static/icons/check.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M215.5 352.1L488 102.3L521.9 136.3L233.9 448.3L215.6 468.1L198 447.6L54 279.6L87.6 245.5L215.5 352.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 369 B |
1
app/static/icons/gear.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M248.4 0L229.7 0L226 14.7C215.5 44.8 201.4 54.7 189.5 57.6C175.1 61.1 154.7 56.9 128.9 42.4L118.9 36.4C117.2 38.1 94.1 61.2 49.6 105.7L36.3 119L42.3 128.9C56.8 154.7 61 175.1 57.5 189.5C54.6 201.4 44.7 215.5 14.6 226L0 229.7L0 346.3L14.7 350C44.8 360.5 54.7 374.6 57.6 386.5C61.1 400.9 56.9 421.3 42.4 447.1L36.4 457C38.1 458.7 61.2 481.8 105.7 526.3L119 539.6L128.9 533.6C154.6 519.1 175.1 514.9 189.5 518.4C201.4 521.3 215.5 531.2 226 561.3L229.7 576L346.4 576L350.1 561.3C360.6 531.2 374.7 521.3 386.6 518.4C401 514.9 421.4 519.1 447.1 533.6L457.1 539.6L539.6 457.1L533.6 447.2C519.1 421.5 514.9 401 518.4 386.6C521.3 374.7 531.2 360.6 561.3 350.1L576 346.4L576 229.7L561.3 226C531.2 215.5 521.3 201.4 518.4 189.5C514.9 175.1 519.1 154.7 533.6 128.9L539.6 119C537.9 117.3 514.8 94.2 470.3 49.7L457 36.4L447 42.4C421.3 56.9 400.8 61.1 386.4 57.6C374.5 54.7 360.4 44.8 349.9 14.7L346.3 0L248.4 0zM251.3 91.4C257.1 79.9 262.1 65.4 266.9 48L309.1 48C313.9 65.3 318.9 79.9 324.7 91.4C332.5 106.8 343.9 121.1 362.8 125.7C379.8 129.8 396.7 124.3 411.2 117.7L458.5 165L458 166.2C451.4 181.1 445.9 198.5 450.8 215.7C456 234.3 470.8 245.1 485.9 252.3C497.2 257.7 511.4 262.3 528.1 266.9L528.1 309C510.7 313.8 496.1 318.8 484.7 324.5C469.3 332.2 454.9 343.6 450.3 362.5C446.1 379.6 451.8 396.6 458.4 411.1L412.3 457.2C411 456.5 409.7 455.9 408.5 455.2C394.6 448.2 376.9 440.7 359.2 445.3C339.5 450.4 329.5 467.4 323.4 482.2C318.5 494 313.9 509.4 309.1 527.9L267.2 527.9C262.4 509.4 257.9 494 252.9 482.2C246.7 467.4 236.8 450.5 217.1 445.3C199.4 440.7 181.7 448.2 167.8 455.2C166.6 455.8 165.3 456.5 164 457.2L118.9 412.1C119.6 410.8 120.3 409.4 121 408.1C128 394.2 135.6 376.5 131 358.8C125.9 339 108.8 329.1 94 323C82.2 318.1 66.7 313.6 48.2 308.7L48.2 266.8C66.7 262 82.2 257.5 94 252.5C108.7 246.4 125.8 236.5 131 216.7C135.6 199 128.1 181.3 121 167.4C120.3 166.1 119.7 164.8 118.9 163.4L165 117.3C179.5 123.9 196.4 129.4 213.4 125.3C232.2 120.7 243.7 106.4 251.5 91zM304 249.4C308.7 257.3 312 270.4 312 288C312 305.6 308.7 318.7 304 326.6C300.1 333.2 295.5 336 288 336C280.5 336 275.9 333.2 272 326.6C267.3 318.7 264 305.6 264 288C264 270.4 267.3 257.3 272 249.4C275.9 242.8 280.5 240 288 240C295.5 240 300.1 242.8 304 249.4zM288 192C257.4 192 232.4 202 215.2 221.1C198.5 239.7 192 264.1 192 288C192 311.9 198.5 336.3 215.2 354.9C232.4 374 257.4 384 288 384C318.6 384 343.6 374 360.8 354.9C377.5 336.3 384 312 384 288C384 264 377.5 239.7 360.8 221.1C343.6 202 318.6 192 288 192z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
1
app/static/icons/house.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M301.8 16.3L288 6.7L274.2 16.3L34.2 184.3L24 191.5L24 456C24 509 67 552 120 552L456 552C509 552 552 509 552 456L552 48L456 48L456 124.3L301.8 16.3zM72 297.9L288 81.9L504 297.9L504 456C504 482.5 482.5 504 456 504L408 504L408 360C408 320 393.2 289.2 369.3 268.6C346 248.6 316.1 240 288 240C259.9 240 229.9 248.6 206.7 268.6C182.8 289.2 168 320 168 360L168 504L120 504C93.5 504 72 482.5 72 456L72 297.9zM240 504L240 360C240 329.9 247.2 312.1 255.5 302.2C263.4 292.8 274.2 288 288 288C301.8 288 312.6 292.8 320.5 302.2C328.8 312.1 336 330 336 360L336 504L240 504z"/></svg>
|
||||
|
After Width: | Height: | Size: 827 B |
1
app/static/icons/list.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M552 120L552 145.9L526.2 143.9L214.2 119.9L214.2 72L526.2 48L552 46L552 119.9zM96 144C62.4 144 48 122.5 48 96C48 69.5 62.4 48 96 48C129.6 48 144 69.5 144 96C144 122.5 129.6 144 96 144zM96 336C62.4 336 48 314.5 48 288C48 261.5 62.4 240 96 240C129.6 240 144 261.5 144 288C144 314.5 129.6 336 96 336zM144 480C144 506.5 129.6 528 96 528C62.4 528 48 506.5 48 480C48 453.5 62.4 432 96 432C129.6 432 144 453.5 144 480zM552 337.9L526.2 335.9L214.2 311.9L214.2 264L526.2 240L552 238L552 337.8zM552 504L552 529.9L526.2 527.9L214.2 503.9L214.2 456L526.2 432L552 430L552 503.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 833 B |
1
app/static/icons/plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M506.1 264.1L327.2 248.8L311.9 69.9L264.1 70.2L250.9 250.8L70.3 264L70 311.9L248.9 327.2L264.2 506.1L312 505.8L325.2 325.2L505.8 312L506.1 264.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 413 B |
1
app/static/icons/trophy.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M96 0L480 0C533 0 576 43 576 96L576 108L576 108C574.7 165.7 541.6 218 489.9 243.9L465.4 256.1C458.5 281 449 304.1 436.6 324.3C409.1 369.2 367.7 399.7 312 406.5L312 431.9L324.2 431.9C362.6 431.9 392.9 440.8 415 459.2C437.2 477.7 447.8 502.9 452.4 528.9C454.3 539.9 457.1 555.6 460.7 575.9L115.5 575.9C119.1 555.6 121.9 539.9 123.8 528.9C128.4 502.9 139 477.7 161.2 459.2C183.3 440.8 213.6 431.9 252 431.9L264.2 431.9L264.2 406.5C208.6 399.7 167.1 369.1 139.6 324.3C127.2 304 117.7 281 110.8 256.1L86.2 243.9C34.4 218 1.3 165.7 0 108L0 108L0 96C0 43 43 0 96 0zM384 72L384 48L192 48L192 138.9C192 205 200.9 262.6 218.5 302.6C235.9 342.3 259.3 360.1 288 360.1C316.7 360.1 340.1 342.3 357.5 302.6C375.1 262.6 384 205.1 384 138.9L384 72zM528 104.5C528 86.5 513.4 72 495.5 72L480 72L480 137.6C480 157.6 479 177.2 476.8 196.3C508.4 176.8 528 142.1 528 104.5zM80.5 72C62.5 72 48 86.6 48 104.5C48 142.1 67.6 176.8 99.2 196.3C97 177.2 96 157.6 96 137.6L96 72L80.5 72zM257.9 480C233.5 480 220.1 486.6 212 494.7C204.7 502 199.3 512.8 196.1 528L380 528C376.8 512.8 371.4 502 364.1 494.7C356 486.6 342.6 480 318.2 480L257.9 480z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
app/static/icons/user.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M288 72C308.9 72 325.9 78.9 338 92.5C350.3 106.3 360 129.9 360 168C360 206.1 350.3 229.7 338 243.5C326 257 309 264 288.1 264L288 264C267.1 264 250.1 257.1 238 243.5C225.7 229.7 216 206.1 216 168C216 129.9 225.7 106.3 238 92.5C250.1 78.9 267.1 72 288 72zM187.1 277.4C187.7 277.9 188.2 278.4 188.8 278.9C163.7 287.1 140.6 298.8 120.6 314.2C75.4 349.1 48 401.6 48 469.4L48 552L528 552L528 469.4C528 401.5 500.6 349.1 455.4 314.2C435.4 298.8 412.3 287.1 387.2 278.9C387.8 278.4 388.3 277.9 388.9 277.4C416.7 252.5 432 215.4 432 168C432 120.6 416.6 83.6 388.9 58.6C361.6 34.1 325.2 24 288 24C250.8 24 214.4 34.1 187.1 58.6C159.4 83.6 144 120.6 144 168C144 215.4 159.4 252.4 187.1 277.4zM185 346.4C210.1 324.5 246 312.1 287.7 312.1L288.3 312.1C330 312.2 365.9 324.5 391 346.4C415.7 368 432 400.5 432 445.5L432 504.1L144 504.1L144 445.5C144 400.5 160.2 368 185 346.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
app/static/icons/users.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M200.5 134.2C208.8 144.1 216 162 216 192C216 222 208.8 239.9 200.5 249.8C192.6 259.2 181.8 264 168 264C154.2 264 143.4 259.2 135.5 249.8C127.2 239.9 120 222 120 192C120 162 127.2 144.1 135.5 134.2C143.4 124.8 154.2 120 168 120C181.8 120 192.6 124.8 200.5 134.2zM253.4 281.7C275.9 260.7 288 230.2 288 192C288 153 275.3 122 251.9 100.9C228.9 80.3 198.6 72 168 72C137.4 72 107.1 80.3 84.1 100.9C60.7 122 48 153 48 192C48 230.1 60.1 260.7 82.6 281.7C71.2 287.1 60.5 293.8 50.8 301.9C18.6 328.8 0 369 0 420.5L0 504L576 504L576 426.5C576 382.2 558.1 346.6 528.4 322.6C519.8 315.7 510.5 309.8 500.4 305.1C518.4 286.2 528 260 528 228.1C528 193.3 516.7 165.3 495.4 146.2C474.6 127.5 447.2 120.1 420 120.1C392.8 120.1 365.4 127.5 344.6 146.2C323.4 165.3 312 193.3 312 228.1C312 260 321.5 286.3 339.6 305.1C329.6 309.9 320.2 315.7 311.6 322.6C310.3 323.7 309 324.8 307.7 325.9C301.2 317 293.7 309 285.2 302C275.5 293.9 264.8 287.2 253.4 281.8zM504 456L336 456L336 426.4C336 394.5 346.2 372.4 360.5 358.3C374.9 344.1 395.5 336 420 336C444.5 336 465.1 344.1 479.5 358.3C493.8 372.4 504 394.5 504 426.4L504 455.9zM264 456L72 456L72 402.5C72 371.4 83 349.5 99.1 335.2C115.6 320.5 139.5 312 168 312C196.5 312 220.4 320.6 236.9 335.2C253 349.5 264 371.4 264 402.5L264 456zM443.6 178.9C450 186.9 456 201.9 456 228C456 254.1 450 269.1 443.6 277.1C437.8 284.5 430 288 420 288C410 288 402.2 284.5 396.4 277.1C390 269.1 384 254.1 384 228C384 201.9 390 186.9 396.4 178.9C402.2 171.5 410 168 420 168C430 168 437.8 171.5 443.6 178.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/static/images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
12
app/static/images/avatars/anna.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ffcf9c"/>
|
||||
<stop offset="100%" stop-color="#ff8ba7"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="44" fill="url(#g)"/>
|
||||
<circle cx="64" cy="50" r="24" fill="#fff" fill-opacity="0.92"/>
|
||||
<path d="M26 106c7-20 21-31 38-31s31 11 38 31" fill="#fff" fill-opacity="0.92"/>
|
||||
<text x="64" y="117" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#b4236c">A</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 594 B |
12
app/static/images/avatars/ben.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#93c5fd"/>
|
||||
<stop offset="100%" stop-color="#34d399"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="44" fill="url(#g)"/>
|
||||
<circle cx="64" cy="50" r="24" fill="#fff" fill-opacity="0.92"/>
|
||||
<path d="M26 106c7-20 21-31 38-31s31 11 38 31" fill="#fff" fill-opacity="0.92"/>
|
||||
<text x="64" y="117" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#0c6e6f">B</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 594 B |
11
app/static/images/avatars/default.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#dbeafe"/>
|
||||
<stop offset="100%" stop-color="#bfdbfe"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="44" fill="url(#g)"/>
|
||||
<circle cx="64" cy="46" r="22" fill="#fff" fill-opacity="0.94"/>
|
||||
<path d="M30 104c6-18 19-28 34-28s28 10 34 28" fill="#fff" fill-opacity="0.94"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 479 B |
11
app/static/images/favicon.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2563eb"/>
|
||||
<stop offset="100%" stop-color="#34d399"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="18" fill="url(#g)"/>
|
||||
<path d="M18 32.5 32 21l14 11.5V46H18z" fill="#fff"/>
|
||||
<path d="m42 16 2.5 5 5.5.8-4 3.8.9 5.4-4.9-2.8-4.9 2.8.9-5.4-4-3.8 5.5-.8z" fill="#f59e0b"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
20
app/static/images/logo.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Putzliga Logo</title>
|
||||
<desc id="desc">Ein rundes Signet mit Haus, Schild und Stern für Putzliga.</desc>
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2563eb"/>
|
||||
<stop offset="100%" stop-color="#34d399"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="card" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.96"/>
|
||||
<stop offset="100%" stop-color="#eef6ff" stop-opacity="0.92"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="16" y="16" width="224" height="224" rx="56" fill="url(#bg)"/>
|
||||
<circle cx="128" cy="128" r="82" fill="url(#card)"/>
|
||||
<path d="M82 127.5 128 88l46 39.5v43.5a8 8 0 0 1-8 8H90a8 8 0 0 1-8-8z" fill="#2563eb"/>
|
||||
<path d="M108 177v-34a20 20 0 0 1 40 0v34" fill="#9fd0ff"/>
|
||||
<path d="M128 58l59 50.5" stroke="#ffffff" stroke-width="13" stroke-linecap="round"/>
|
||||
<path d="m164 74 7 15 16 2-12 11 3 16-14-8-14 8 3-16-12-11 16-2z" fill="#f59e0b"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/static/images/pwa-badge.png
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
app/static/images/pwa-icon-192.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/static/images/pwa-icon-512.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
104
app/static/js/app.js
Normal file
@@ -0,0 +1,104 @@
|
||||
(function () {
|
||||
const dialog = document.getElementById("completeDialog");
|
||||
const dialogForm = document.getElementById("completeDialogForm");
|
||||
const dialogChoice = document.getElementById("completeDialogChoice");
|
||||
const dialogText = document.getElementById("completeDialogText");
|
||||
const closeButton = document.getElementById("completeDialogClose");
|
||||
|
||||
document.querySelectorAll("[data-complete-action]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (!dialog || !dialogForm || !dialogChoice || !dialogText) {
|
||||
return;
|
||||
}
|
||||
dialogForm.action = button.dataset.completeAction;
|
||||
dialogText.textContent = `Die Aufgabe "${button.dataset.completeTitle}" war ${button.dataset.completeAssigned} zugewiesen. Wer hat sie erledigt?`;
|
||||
dialog.showModal();
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-complete-choice]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
dialogChoice.value = button.dataset.completeChoice || "me";
|
||||
dialog.close();
|
||||
dialogForm.submit();
|
||||
});
|
||||
});
|
||||
|
||||
if (closeButton && dialog) {
|
||||
closeButton.addEventListener("click", () => dialog.close());
|
||||
}
|
||||
|
||||
const pushButton = document.getElementById("pushToggle");
|
||||
const pushHint = document.getElementById("pushHint");
|
||||
const vapidKey = document.body.dataset.pushKey;
|
||||
const isIos = /iphone|ipad|ipod/i.test(window.navigator.userAgent);
|
||||
const isStandalone =
|
||||
window.matchMedia("(display-mode: standalone)").matches ||
|
||||
window.navigator.standalone === true;
|
||||
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||
const raw = atob(base64);
|
||||
return Uint8Array.from([...raw].map((char) => char.charCodeAt(0)));
|
||||
}
|
||||
|
||||
async function postJSON(url, payload) {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function togglePush() {
|
||||
if (!("serviceWorker" in navigator) || !("PushManager" in window) || !pushButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.register("/service-worker.js");
|
||||
const existing = await registration.pushManager.getSubscription();
|
||||
|
||||
if (existing) {
|
||||
await postJSON("/settings/push/unsubscribe", { endpoint: existing.endpoint });
|
||||
await existing.unsubscribe();
|
||||
pushButton.dataset.subscribed = "0";
|
||||
pushButton.textContent = "Push aktivieren";
|
||||
return;
|
||||
}
|
||||
|
||||
const permission = await Notification.requestPermission();
|
||||
if (permission !== "granted") {
|
||||
return;
|
||||
}
|
||||
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(vapidKey),
|
||||
});
|
||||
await postJSON("/settings/push/subscribe", subscription.toJSON());
|
||||
pushButton.dataset.subscribed = "1";
|
||||
pushButton.textContent = "Push deaktivieren";
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("/service-worker.js").catch(() => {});
|
||||
}
|
||||
|
||||
if (pushButton && (!("serviceWorker" in navigator) || !("PushManager" in window))) {
|
||||
pushButton.disabled = true;
|
||||
if (pushHint) {
|
||||
pushHint.textContent = "Dieser Browser unterstützt Web-Push hier aktuell nicht.";
|
||||
}
|
||||
} else if (pushButton && isIos && !isStandalone) {
|
||||
pushButton.disabled = true;
|
||||
if (pushHint) {
|
||||
pushHint.textContent = "Auf iPhone/iPad funktioniert Web-Push erst nach „Zum Home-Bildschirm“ und Öffnen als Web-App.";
|
||||
}
|
||||
} else if (pushButton && vapidKey) {
|
||||
pushButton.addEventListener("click", () => {
|
||||
togglePush().catch((error) => console.error("Push toggle failed", error));
|
||||
});
|
||||
}
|
||||
})();
|
||||
25
app/static/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Putzliga",
|
||||
"short_name": "Putzliga",
|
||||
"description": "Spielerische Haushalts-App mit Aufgaben, Punkten und Monats-Highscore.",
|
||||
"id": "/",
|
||||
"start_url": "/my-tasks",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#eef3ff",
|
||||
"theme_color": "#f5f7ff",
|
||||
"lang": "de",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/images/pwa-icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/images/pwa-icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
71
app/static/service-worker.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const CACHE_NAME = "putzliga-shell-v1";
|
||||
const ASSETS = [
|
||||
"/my-tasks",
|
||||
"/static/css/style.css",
|
||||
"/static/js/app.js",
|
||||
"/static/images/logo.svg",
|
||||
"/static/images/pwa-icon-192.png",
|
||||
"/static/images/pwa-icon-512.png"
|
||||
];
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)));
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) =>
|
||||
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
|
||||
)
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.method !== "GET") return;
|
||||
event.respondWith(
|
||||
caches.match(event.request).then((cached) => {
|
||||
if (cached) return cached;
|
||||
return fetch(event.request)
|
||||
.then((response) => {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match("/my-tasks"));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("push", (event) => {
|
||||
const payload = event.data ? event.data.json() : {};
|
||||
const title = payload.title || "Putzliga";
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(title, {
|
||||
body: payload.body || "Es gibt Neuigkeiten in der Putzliga.",
|
||||
icon: payload.icon || "/static/images/pwa-icon-192.png",
|
||||
badge: payload.badge || "/static/images/pwa-badge.png",
|
||||
tag: payload.tag || "putzliga",
|
||||
data: { url: payload.url || "/my-tasks" }
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("notificationclick", (event) => {
|
||||
event.notification.close();
|
||||
const targetUrl = event.notification.data?.url || "/my-tasks";
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
|
||||
for (const client of clientList) {
|
||||
if ("focus" in client) {
|
||||
client.navigate(targetUrl);
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(targetUrl);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||