From b3e2bd7a23bfd3d65e51de751909f04c8ef9f905 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 13:33:04 +0100 Subject: [PATCH 01/12] feat(docs): add comprehensive UI design system documentation --- docs/design-system.md | 180 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 docs/design-system.md diff --git a/docs/design-system.md b/docs/design-system.md new file mode 100644 index 0000000..96144fa --- /dev/null +++ b/docs/design-system.md @@ -0,0 +1,180 @@ +# StudyBuddy UI Design System (Athenify-Inspired) +# Athenify URL: https://athenify.io/ + +## Purpose + +This document defines the canonical UI/UX system for the StudyBuddy application. + +All templates, components, and pages MUST follow this system to ensure: +- Visual consistency +- Predictable structure +- Reusable components +- Scalable frontend development + +Codex or any code generation tool MUST treat this document as a contract. + +--- + +## Core Design Principles + +### 1. Layout Philosophy + +- Use a **dashboard-first SaaS layout** +- Structure: + - Sidebar (navigation) + - Topbar (context/actions) + - Main content area +- Content is always organised as: + **Page → Section → Card → Action** + +--- + +### 2. Spacing System + +Use a consistent **8px spacing scale**: + +| Token | Value | +|------|------| +| xs | 4px | +| sm | 8px | +| md | 16px | +| lg | 24px | +| xl | 32px | +| xxl | 48px | + +Rules: +- Never use arbitrary spacing +- Always apply spacing via CSS classes +- Prefer padding over margin for internal layout + +--- + +### 3. Typography + +- Font: system-ui stack (fast, neutral, modern) +- Hierarchy: + +| Element | Style | +|--------|------| +| Page Title | Large, bold | +| Section Title | Medium, semi-bold | +| Card Title | Small, bold | +| Body Text | Regular | +| Metadata | Small, muted | + +Rules: +- Maintain strong visual hierarchy +- Avoid excessive font sizes +- Use weight instead of colour for emphasis + +--- + +### 4. Colour System + +Neutral-first palette: + +- Background: very light grey +- Surface (cards): white +- Border: soft grey +- Text: near-black +- Accent: subtle (blue or indigo) + +Rules: +- Avoid high saturation +- Use colour sparingly for actions and highlights +- Maintain high readability + +--- + +### 5. Components + +#### Cards +- Rounded corners +- Soft border +- Internal padding (md or lg) +- Used for ALL grouped content + +#### Buttons +- Rounded +- Minimal styling +- Clear hover states + +#### Sidebar +- Fixed width +- Vertical navigation +- Active item clearly highlighted + +#### Sections +- Always have a title +- Contain 1+ cards +- Maintain vertical rhythm + +--- + +### 6. Layout Rules + +- Use grid or flex layouts +- Avoid deeply nested structures +- Maximum content width should be constrained +- Maintain whitespace around sections + +--- + +### 7. Reusability Requirements + +All UI must: +- Extend `/templates/base.html` +- Use shared styles from `/static/css/theme.css` +- Avoid duplication +- Be component-friendly + +--- + +## Codex Usage Contract + +When generating UI: + +### MUST + +- Follow this design system strictly +- Extend base template +- Use predefined spacing and component styles +- Produce semantic HTML + +### MUST NOT + +- Use inline styles +- Introduce new spacing scales +- Break layout hierarchy +- Invent new design patterns + +--- + +## Canonical Prompt (Use This for All UI Generation) + +Use Athenify.io as UI/UX inspiration. + +Design requirements: +- Clean, minimal dashboard aesthetic +- Soft neutral colour palette (light greys, subtle accent colour) +- Generous whitespace and padding +- Rounded components (cards, buttons) +- Clear hierarchy (title → section → card → action) +- Modern SaaS layout (sidebar + main content) +- Consistent spacing scale (8px grid) + +Tech constraints: +- Django templates + HTMX +- No inline styles +- Reusable components where possible + +Follow the project design system defined in: +- /docs/design-system.md +- /templates/base.html +- /static/css/theme.css + +Use Athenify-inspired design principles already defined there. + +Output: +- Production-ready template +- Semantic HTML structure \ No newline at end of file From 43bfe48d7cab5b1baf0fb251a676aa09a108ddd1 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 13:35:26 +0100 Subject: [PATCH 02/12] feat(template): restructure base HTML layout with sidebar and topbar components --- templates/base.html | 84 ++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/templates/base.html b/templates/base.html index e827177..7b7996b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,25 +1,61 @@ - - - - - + + + + + + {% block title %}StudyBuddy{% endblock %} - - -
- -
-
- {% block content %}{% endblock %} -
- - + + + + + + + + + + + + +
+ + + + + +
+ + +
+
+ {% block page_title %}Dashboard{% endblock %} +
+
+ {% block topbar_actions %}{% endblock %} +
+
+ + +
+ {% block content %} + + {% endblock %} +
+ +
+ +
+ + + \ No newline at end of file From 0bf869010e9135f03e37647b97bf1c86e00b4ffd Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 13:37:31 +0100 Subject: [PATCH 03/12] feat(styles): add base theme styles including layout, colors, and components --- static/css/theme.css | 137 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 static/css/theme.css diff --git a/static/css/theme.css b/static/css/theme.css new file mode 100644 index 0000000..d39a0dc --- /dev/null +++ b/static/css/theme.css @@ -0,0 +1,137 @@ +/* File: /static/css/theme.css */ + +/* ========================= + Root Variables +========================= */ +:root { + --color-bg: #f7f8fa; + --color-surface: #ffffff; + --color-border: #e5e7eb; + --color-text: #111827; + --color-muted: #6b7280; + --color-accent: #4f46e5; + + --radius: 12px; + + --space-xs: 4px; + --space-sm: 8px; + --space-md: 16px; + --space-lg: 24px; + --space-xl: 32px; +} + +/* ========================= + Base +========================= */ +body { + margin: 0; + font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; + background-color: var(--color-bg); + color: var(--color-text); +} + +/* ========================= + Layout +========================= */ +.app-layout { + display: flex; + min-height: 100vh; +} + +/* Sidebar */ +.sidebar { + width: 240px; + background: var(--color-surface); + border-right: 1px solid var(--color-border); + padding: var(--space-lg); +} + +.sidebar-header { + margin-bottom: var(--space-lg); +} + +.sidebar-nav .nav-item { + display: block; + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius); + text-decoration: none; + color: var(--color-text); + margin-bottom: var(--space-sm); +} + +.sidebar-nav .nav-item:hover { + background: var(--color-bg); +} + +.sidebar-nav .nav-item.active { + background: var(--color-accent); + color: white; +} + +/* Main */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +/* Topbar */ +.topbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-md) var(--space-lg); + background: var(--color-surface); + border-bottom: 1px solid var(--color-border); +} + +.topbar-title { + font-weight: 600; +} + +/* Content */ +.content-area { + padding: var(--space-lg); +} + +/* ========================= + Components +========================= */ + +/* Card */ +.card-ui { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + padding: var(--space-md); + margin-bottom: var(--space-lg); +} + +/* Section */ +.section { + margin-bottom: var(--space-xl); +} + +.section-title { + font-weight: 600; + margin-bottom: var(--space-md); +} + +/* Buttons */ +.btn-ui { + border-radius: var(--radius); + padding: var(--space-sm) var(--space-md); + border: 1px solid var(--color-border); + background: var(--color-surface); +} + +.btn-ui-primary { + background: var(--color-accent); + color: white; + border: none; +} + +/* Muted text */ +.text-muted-ui { + color: var(--color-muted); +} \ No newline at end of file From bbb08088801b905533ccccbfe3eaebfb76d7a145 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 13:55:27 +0100 Subject: [PATCH 04/12] feat(ui): enhance signup flow with improved layout and navigation, add user profile management features --- static/css/theme.css | 231 ++++++++++++++++++++++++++++- templates/base.html | 21 ++- templates/dashboard/index.html | 56 ++++++- templates/home.html | 32 +++- templates/registration/login.html | 29 +++- templates/registration/signup.html | 29 +++- templates/users/profile.html | 41 ++++- 7 files changed, 416 insertions(+), 23 deletions(-) diff --git a/static/css/theme.css b/static/css/theme.css index d39a0dc..918a1a7 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -18,6 +18,7 @@ --space-md: 16px; --space-lg: 24px; --space-xl: 32px; + --space-xxl: 48px; } /* ========================= @@ -30,6 +31,14 @@ body { color: var(--color-text); } +a { + color: var(--color-accent); +} + +a:hover { + color: var(--color-text); +} + /* ========================= Layout ========================= */ @@ -44,12 +53,24 @@ body { background: var(--color-surface); border-right: 1px solid var(--color-border); padding: var(--space-lg); + flex-shrink: 0; } .sidebar-header { margin-bottom: var(--space-lg); } +.sidebar-header h4 { + margin: 0; + font-size: 1.125rem; + font-weight: 700; +} + +.sidebar-header a { + color: var(--color-text); + text-decoration: none; +} + .sidebar-nav .nav-item { display: block; padding: var(--space-sm) var(--space-md); @@ -89,9 +110,17 @@ body { font-weight: 600; } +.topbar-actions { + display: flex; + align-items: center; + gap: var(--space-sm); +} + /* Content */ .content-area { padding: var(--space-lg); + width: 100%; + max-width: 1120px; } /* ========================= @@ -107,22 +136,137 @@ body { margin-bottom: var(--space-lg); } +.card-ui:last-child { + margin-bottom: 0; +} + +.card-ui p:last-child { + margin-bottom: 0; +} + +.card-title-ui { + margin-bottom: var(--space-sm); + font-size: 1rem; + font-weight: 700; +} + +.card-actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); + margin-top: var(--space-md); +} + /* Section */ .section { margin-bottom: var(--space-xl); } +.section:last-child { + margin-bottom: 0; +} + .section-title { font-weight: 600; margin-bottom: var(--space-md); } +.page-stack { + display: flex; + flex-direction: column; + gap: var(--space-xl); +} + +.page-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-lg); +} + +.page-title { + margin: 0 0 var(--space-sm); + font-size: 1.75rem; + font-weight: 700; +} + +.page-subtitle { + max-width: 680px; + margin: 0; + color: var(--color-muted); +} + +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: var(--space-lg); +} + +.detail-list { + display: grid; + gap: var(--space-sm); + margin: 0; +} + +.detail-row { + display: flex; + justify-content: space-between; + gap: var(--space-md); + padding: var(--space-sm) 0; + border-bottom: 1px solid var(--color-border); +} + +.detail-row:last-child { + border-bottom: 0; +} + +.detail-row dd { + margin-bottom: 0; + text-align: right; +} + +.metric-value { + display: block; + margin-bottom: var(--space-xs); + font-size: 1.5rem; + font-weight: 700; +} + +.action-list { + display: grid; + gap: var(--space-md); +} + +.action-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); + padding: var(--space-md); + border: 1px solid var(--color-border); + border-radius: var(--radius); + background: var(--color-bg); +} + /* Buttons */ .btn-ui { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); border-radius: var(--radius); padding: var(--space-sm) var(--space-md); border: 1px solid var(--color-border); background: var(--color-surface); + color: var(--color-text); + font-weight: 600; + line-height: 1.5; + text-decoration: none; +} + +.btn-ui:hover { + background: var(--color-bg); + color: var(--color-text); } .btn-ui-primary { @@ -131,7 +275,92 @@ body { border: none; } +.btn-ui-primary:hover { + background: #4338ca; + color: white; +} + +/* Forms */ +.form-ui { + display: grid; + gap: var(--space-md); +} + +.form-ui p { + display: grid; + gap: var(--space-xs); + margin: 0; +} + +.form-ui label { + font-weight: 600; +} + +.form-ui input, +.form-ui select, +.form-ui textarea { + width: 100%; + border: 1px solid var(--color-border); + border-radius: var(--radius); + padding: var(--space-sm) var(--space-md); + background: var(--color-surface); + color: var(--color-text); +} + +.form-ui input:focus, +.form-ui select:focus, +.form-ui textarea:focus { + border-color: var(--color-accent); + box-shadow: 0 0 0 3px rgb(79 70 229 / 12%); + outline: none; +} + +.form-ui .helptext, +.form-ui ul { + color: var(--color-muted); + font-size: 0.875rem; +} + +.form-ui ul { + margin: 0; + padding-left: var(--space-lg); +} + /* Muted text */ .text-muted-ui { color: var(--color-muted); -} \ No newline at end of file +} + +@media (max-width: 760px) { + .app-layout { + flex-direction: column; + } + + .sidebar { + width: 100%; + border-right: 0; + border-bottom: 1px solid var(--color-border); + } + + .sidebar-nav { + display: flex; + gap: var(--space-sm); + overflow-x: auto; + } + + .sidebar-nav .nav-item { + margin-bottom: 0; + white-space: nowrap; + } + + .topbar, + .page-header, + .action-item { + align-items: stretch; + flex-direction: column; + } + + .content-area { + padding: var(--space-md); + } +} diff --git a/templates/base.html b/templates/base.html index 7b7996b..0d17ab2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,3 +1,4 @@ +{% load static %} @@ -22,14 +23,13 @@ @@ -42,7 +42,14 @@

StudyBuddy

{% block page_title %}Dashboard{% endblock %}
- {% block topbar_actions %}{% endblock %} + {% block topbar_actions %} + {% if request.user.is_authenticated %} + Profile + {% else %} + Log in + Sign up + {% endif %} + {% endblock %}
@@ -58,4 +65,4 @@

StudyBuddy

- \ No newline at end of file + diff --git a/templates/dashboard/index.html b/templates/dashboard/index.html index d229ae0..ea4d272 100644 --- a/templates/dashboard/index.html +++ b/templates/dashboard/index.html @@ -1,7 +1,61 @@ {% extends "base.html" %} {% block title %}Dashboard | StudyBuddy{% endblock %} +{% block page_title %}Dashboard{% endblock %} {% block content %} -

Dashboard

+
+ + +
+

Today

+
+
+ 0 +

Sessions planned

+

Create a study session to start shaping the day.

+
+
+ 0 +

Tasks due

+

No upcoming study tasks have been added yet.

+
+
+ 0% +

Weekly progress

+

Progress will update as study work is completed.

+
+
+
+ +
+

Next Actions

+
+
+
+
+

Build your first study plan

+

Add a session with a subject, target outcome, and review time.

+
+ Add session +
+
+
+

Review your profile

+

Confirm your account details before personal planning settings are added.

+
+ Open profile +
+
+
+
+
{% endblock %} diff --git a/templates/home.html b/templates/home.html index 8b7ff34..e897186 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,7 +1,37 @@ {% extends "base.html" %} {% block title %}StudyBuddy{% endblock %} +{% block page_title %}Study Plan{% endblock %} {% block content %} -

StudyBuddy

+
+ + +
+

Planning Overview

+
+
+

Sessions

+

Plan study blocks around a clear workload, priority, and review rhythm.

+
+
+

Notes

+

Keep the supporting material for each study goal close to the work.

+
+
+

Progress

+

Review what is complete, what is blocked, and what needs attention next.

+
+
+
+
{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html index fca178d..ababb0a 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -1,12 +1,29 @@ {% extends "base.html" %} {% block title %}Log in | StudyBuddy{% endblock %} +{% block page_title %}Log in{% endblock %} {% block content %} -

Log in

-
- {% csrf_token %} - {{ form.as_p }} - -
+
+ + +
+

Account Access

+
+
+ {% csrf_token %} + {{ form.as_p }} +
+ + Create account +
+
+
+
+
{% endblock %} diff --git a/templates/registration/signup.html b/templates/registration/signup.html index b752749..330c391 100644 --- a/templates/registration/signup.html +++ b/templates/registration/signup.html @@ -1,12 +1,29 @@ {% extends "base.html" %} {% block title %}Sign up | StudyBuddy{% endblock %} +{% block page_title %}Sign up{% endblock %} {% block content %} -

Sign up

-
- {% csrf_token %} - {{ form.as_p }} - -
+
+ + +
+

New Account

+
+
+ {% csrf_token %} + {{ form.as_p }} +
+ + Log in +
+
+
+
+
{% endblock %} diff --git a/templates/users/profile.html b/templates/users/profile.html index a894908..3acf0d7 100644 --- a/templates/users/profile.html +++ b/templates/users/profile.html @@ -1,7 +1,46 @@ {% extends "base.html" %} {% block title %}Profile | StudyBuddy{% endblock %} +{% block page_title %}Profile{% endblock %} {% block content %} -

Profile

+
+ + +
+

Account Details

+
+
+
+
Display name
+
{{ request.user.display_name }}
+
+
+
Email
+
{{ request.user.email }}
+
+
+
Username
+
{{ request.user.username }}
+
+
+
+
+ +
+

Workspace

+
+

Study preferences

+

Personal scheduling and notification preferences will appear here as planning features are added.

+ +
+
+
{% endblock %} From ad95c4c48ea357b1451488075f6fc5b466c67d4e Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 14:16:08 +0100 Subject: [PATCH 05/12] refactor(ui): align StudyBuddy templates with Athenify design system --- docs/design-system.md | 250 ++++---- static/css/theme.css | 924 +++++++++++++++++++++++------ templates/base.html | 78 +-- templates/dashboard/index.html | 149 +++-- templates/home.html | 195 +++++- templates/registration/login.html | 44 +- templates/registration/signup.html | 44 +- templates/users/profile.html | 89 +-- 8 files changed, 1253 insertions(+), 520 deletions(-) diff --git a/docs/design-system.md b/docs/design-system.md index 96144fa..07636d0 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -1,180 +1,174 @@ -# StudyBuddy UI Design System (Athenify-Inspired) -# Athenify URL: https://athenify.io/ +# StudyBuddy UI Design System -## Purpose +Source inspiration: https://athenify.io/ -This document defines the canonical UI/UX system for the StudyBuddy application. +This document is the canonical UI contract for StudyBuddy templates. The goal is not to copy Athenify verbatim. The goal is to give StudyBuddy the same category feel: a polished study-product website with a bright marketing shell, a blue brand accent, dark rounded calls to action, and dashboard visuals that make study tracking feel motivating. -All templates, components, and pages MUST follow this system to ensure: -- Visual consistency -- Predictable structure -- Reusable components -- Scalable frontend development +## Visual Direction -Codex or any code generation tool MUST treat this document as a contract. +StudyBuddy should feel like: ---- +- A student-focused study tracker and planner. +- Clean, white, spacious, and product-led. +- More like a refined landing/product app than a generic admin panel. +- Built around visual dashboard previews, compact metric cards, progress pills, streaks, and study-session controls. -## Core Design Principles +Avoid a plain left-sidebar SaaS layout for the public shell. Athenify's first viewport uses a top navigation bar, centered hero content, large product imagery, soft shadows, and very little background color. -### 1. Layout Philosophy +## Layout Model -- Use a **dashboard-first SaaS layout** -- Structure: - - Sidebar (navigation) - - Topbar (context/actions) - - Main content area -- Content is always organised as: - **Page → Section → Card → Action** +All templates must extend `/templates/base.html`. ---- +The base template provides: -### 2. Spacing System +- White top navigation. +- Brand lockup on the left. +- Product/category navigation in the center. +- Authentication actions on the right. +- A constrained main content area. -Use a consistent **8px spacing scale**: +Page content follows: -| Token | Value | -|------|------| -| xs | 4px | -| sm | 8px | -| md | 16px | -| lg | 24px | -| xl | 32px | -| xxl | 48px | +`Page shell -> Hero or Page Header -> Section -> Card -> Action` + +Use the dashboard-app visual language inside cards and previews, not as the global frame. + +## Color System + +Use CSS variables from `/static/css/theme.css`. + +Primary colors: + +| Token | Purpose | +| --- | --- | +| `--color-page` | Overall page background | +| `--color-surface` | Cards, nav, panels | +| `--color-ink` | Main text | +| `--color-muted` | Supporting text | +| `--color-line` | Borders | +| `--color-blue` | Brand blue | +| `--color-dark` | Dark CTA and app sidebar | +| `--color-green` | Progress and success indicators | Rules: -- Never use arbitrary spacing -- Always apply spacing via CSS classes -- Prefer padding over margin for internal layout ---- +- Use white as the dominant page color. +- Use blue mainly for brand and small highlights. +- Use dark navy/black for primary CTAs. +- Use green only for progress, streaks, and positive study metrics. +- Do not use purple-first palettes or generic Bootstrap button colors. -### 3. Typography +## Typography -- Font: system-ui stack (fast, neutral, modern) -- Hierarchy: +Use the system UI stack. + +Hierarchy: | Element | Style | -|--------|------| -| Page Title | Large, bold | -| Section Title | Medium, semi-bold | -| Card Title | Small, bold | -| Body Text | Regular | +| --- | --- | +| Brand | Bold, blue, compact | +| Hero headline | Large, black, tight line-height | +| Page title | Strong, dashboard-like | +| Section eyebrow | Small, uppercase, muted blue/grey | +| Section title | Bold, medium-large | +| Card title | Small, bold | | Metadata | Small, muted | -Rules: -- Maintain strong visual hierarchy -- Avoid excessive font sizes -- Use weight instead of colour for emphasis - ---- +Hero copy should be direct and product-specific. Avoid generic filler. -### 4. Colour System +## Spacing -Neutral-first palette: +Use the 8px scale only: -- Background: very light grey -- Surface (cards): white -- Border: soft grey -- Text: near-black -- Accent: subtle (blue or indigo) +| Token | Value | +| --- | --- | +| xs | 4px | +| sm | 8px | +| md | 16px | +| lg | 24px | +| xl | 32px | +| xxl | 48px | +| xxxl | 72px | -Rules: -- Avoid high saturation -- Use colour sparingly for actions and highlights -- Maintain high readability +Sections need generous vertical space. Cards stay compact and information dense. ---- +## Components -### 5. Components +### Header -#### Cards -- Rounded corners -- Soft border -- Internal padding (md or lg) -- Used for ALL grouped content +- White background. +- Logo/brand left. +- Horizontal nav. +- Right-aligned login and dark pill CTA. +- Sticky positioning is allowed when it does not obscure content. -#### Buttons -- Rounded -- Minimal styling -- Clear hover states +### Buttons -#### Sidebar -- Fixed width -- Vertical navigation -- Active item clearly highlighted +- `btn-ui`: white button with soft border. +- `btn-ui-primary`: dark navy pill, used for primary action. +- `btn-ui-blue`: brand-blue secondary action. -#### Sections -- Always have a title -- Contain 1+ cards -- Maintain vertical rhythm +Buttons should be rounded pills, not rectangular admin buttons. ---- +### Cards -### 6. Layout Rules +- White surface. +- Soft grey border. +- 18-24px radius. +- Subtle shadow only for hero/product preview cards. +- Compact typography. -- Use grid or flex layouts -- Avoid deeply nested structures -- Maximum content width should be constrained -- Maintain whitespace around sections +### Product Mockups ---- +Athenify relies heavily on dashboard screenshots. StudyBuddy templates should use reusable HTML/CSS product mockups until real screenshots exist: -### 7. Reusability Requirements +- Browser/device frame. +- Dark app sidebar inside the mockup. +- Dashboard heading. +- Metric strip. +- Study streak row. +- Progress bars. +- Chart-like panels. -All UI must: -- Extend `/templates/base.html` -- Use shared styles from `/static/css/theme.css` -- Avoid duplication -- Be component-friendly +This is a required visual pattern for public-facing pages. ---- +### Sections -## Codex Usage Contract +Every major section must have either: -When generating UI: +- An eyebrow plus title, or +- A clear section title. -### MUST +Sections should not be floating cards. Cards belong inside sections. -- Follow this design system strictly -- Extend base template -- Use predefined spacing and component styles -- Produce semantic HTML +## Template Rules -### MUST NOT +Must: -- Use inline styles -- Introduce new spacing scales -- Break layout hierarchy -- Invent new design patterns +- Extend `base.html`. +- Use shared classes from `theme.css`. +- Use semantic HTML. +- Use card, section, button, metric, and mockup utilities. +- Keep all styling out of templates. ---- +Must not: -## Canonical Prompt (Use This for All UI Generation) +- Reintroduce a global sidebar shell. +- Use inline styles. +- Use arbitrary spacing. +- Use Bootstrap visual classes as the main design system. +- Use placeholder admin UI that lacks product context. -Use Athenify.io as UI/UX inspiration. +## Canonical Prompt -Design requirements: -- Clean, minimal dashboard aesthetic -- Soft neutral colour palette (light greys, subtle accent colour) -- Generous whitespace and padding -- Rounded components (cards, buttons) -- Clear hierarchy (title → section → card → action) -- Modern SaaS layout (sidebar + main content) -- Consistent spacing scale (8px grid) +When creating or refactoring StudyBuddy templates: -Tech constraints: -- Django templates + HTMX -- No inline styles -- Reusable components where possible +Use Athenify.io as visual inspiration: white marketing shell, blue brand, dark rounded CTA, generous whitespace, large dashboard preview, compact metric cards, green progress indicators, and student-focused study tracking content. -Follow the project design system defined in: -- /docs/design-system.md -- /templates/base.html -- /static/css/theme.css +Follow: -Use Athenify-inspired design principles already defined there. +- `/docs/design-system.md` +- `/templates/base.html` +- `/static/css/theme.css` -Output: -- Production-ready template -- Semantic HTML structure \ No newline at end of file +Output production-ready Django templates that extend the base template and use only shared design-system classes. diff --git a/static/css/theme.css b/static/css/theme.css index 918a1a7..8579a29 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -1,17 +1,26 @@ /* File: /static/css/theme.css */ -/* ========================= - Root Variables -========================= */ :root { - --color-bg: #f7f8fa; + --color-page: #ffffff; + --color-soft: #f7f9fc; --color-surface: #ffffff; - --color-border: #e5e7eb; - --color-text: #111827; - --color-muted: #6b7280; - --color-accent: #4f46e5; - - --radius: 12px; + --color-ink: #0f172a; + --color-muted: #65738a; + --color-line: #e6eaf0; + --color-blue: #2557d6; + --color-blue-soft: #edf4ff; + --color-dark: #101827; + --color-dark-2: #1d2537; + --color-green: #18b76b; + --color-green-soft: #eafaf2; + --color-gold: #d99a22; + --shadow-soft: 0 20px 60px rgb(15 23 42 / 10%); + --shadow-card: 0 10px 30px rgb(15 23 42 / 6%); + + --radius-sm: 10px; + --radius-md: 16px; + --radius-lg: 24px; + --radius-pill: 999px; --space-xs: 4px; --space-sm: 8px; @@ -19,268 +28,390 @@ --space-lg: 24px; --space-xl: 32px; --space-xxl: 48px; + --space-xxxl: 72px; +} + +* { + box-sizing: border-box; } -/* ========================= - Base -========================= */ body { margin: 0; - font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; - background-color: var(--color-bg); - color: var(--color-text); + background: var(--color-page); + color: var(--color-ink); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: 0; } a { - color: var(--color-accent); + color: inherit; + text-decoration: none; } a:hover { - color: var(--color-text); + color: var(--color-blue); } -/* ========================= - Layout -========================= */ -.app-layout { - display: flex; +p { + color: var(--color-muted); + line-height: 1.6; +} + +.site-shell { min-height: 100vh; + background: + radial-gradient(circle at 50% 120px, rgb(37 87 214 / 8%), transparent 280px), + linear-gradient(180deg, #fff 0%, #fff 62%, #f7f9fc 100%); } -/* Sidebar */ -.sidebar { - width: 240px; - background: var(--color-surface); - border-right: 1px solid var(--color-border); - padding: var(--space-lg); - flex-shrink: 0; +.site-header { + position: sticky; + top: 0; + z-index: 20; + border-bottom: 1px solid rgb(230 234 240 / 70%); + background: rgb(255 255 255 / 92%); + backdrop-filter: blur(18px); } -.sidebar-header { - margin-bottom: var(--space-lg); +.site-nav { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-xl); + width: min(100%, 1240px); + min-height: 76px; + margin: 0 auto; + padding: 0 var(--space-xl); } -.sidebar-header h4 { - margin: 0; - font-size: 1.125rem; - font-weight: 700; +.brand-link { + display: inline-flex; + align-items: center; + gap: 10px; + color: var(--color-blue); + font-size: 1.35rem; + font-weight: 800; + line-height: 1; +} + +.brand-mark { + position: relative; + width: 18px; + height: 18px; + border-radius: 60% 42% 60% 42%; + background: var(--color-blue); + transform: rotate(-28deg); +} + +.brand-mark::after { + position: absolute; + right: 3px; + bottom: 3px; + width: 8px; + height: 2px; + border-radius: var(--radius-pill); + background: #ffffff; + content: ""; +} + +.nav-links, +.nav-actions, +.card-actions { + display: flex; + align-items: center; + gap: var(--space-md); } -.sidebar-header a { - color: var(--color-text); - text-decoration: none; +.nav-links { + flex: 1; } -.sidebar-nav .nav-item { - display: block; - padding: var(--space-sm) var(--space-md); - border-radius: var(--radius); - text-decoration: none; - color: var(--color-text); - margin-bottom: var(--space-sm); +.nav-link-ui { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--color-ink); + font-weight: 700; + white-space: nowrap; } -.sidebar-nav .nav-item:hover { - background: var(--color-bg); +.nav-link-ui::after { + width: 8px; + height: 8px; + border-right: 2px solid var(--color-blue); + border-bottom: 2px solid var(--color-blue); + margin-top: -4px; + content: ""; + transform: rotate(45deg); } -.sidebar-nav .nav-item.active { - background: var(--color-accent); - color: white; +.nav-link-ui.active { + color: var(--color-blue); } -/* Main */ -.main-content { - flex: 1; - display: flex; - flex-direction: column; +.site-main { + width: 100%; } -/* Topbar */ -.topbar { - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--space-md) var(--space-lg); - background: var(--color-surface); - border-bottom: 1px solid var(--color-border); +.container-ui { + width: min(100%, 1240px); + margin: 0 auto; + padding-right: var(--space-xl); + padding-left: var(--space-xl); } -.topbar-title { - font-weight: 600; +.hero { + padding: var(--space-xxl) 0 var(--space-xxxl); + text-align: center; } -.topbar-actions { - display: flex; +.hero-content { + display: grid; + justify-items: center; + gap: var(--space-lg); + max-width: 920px; + margin: 0 auto; + text-align: center; +} + +.eyebrow { + display: inline-flex; align-items: center; gap: var(--space-sm); + color: var(--color-blue); + font-size: 0.82rem; + font-weight: 800; + letter-spacing: 0; + text-transform: uppercase; } -/* Content */ -.content-area { - padding: var(--space-lg); - width: 100%; - max-width: 1120px; +.hero-title { + max-width: 850px; + margin: 0; + color: var(--color-ink); + font-size: clamp(2.75rem, 6vw, 5.75rem); + font-weight: 850; + letter-spacing: 0; + line-height: 0.96; } -/* ========================= - Components -========================= */ +.hero-title .text-blue { + color: var(--color-blue); +} -/* Card */ -.card-ui { +.hero-subtitle { + max-width: 760px; + margin: 0; + color: var(--color-muted); + font-size: 1.14rem; +} + +.microcopy { + margin: 0; + color: var(--color-muted); + font-size: 0.92rem; +} + +.btn-ui { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + padding: 0 var(--space-lg); + border: 1px solid var(--color-line); + border-radius: var(--radius-pill); background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius); - padding: var(--space-md); - margin-bottom: var(--space-lg); + color: var(--color-ink); + font-weight: 800; + line-height: 1; + box-shadow: none; } -.card-ui:last-child { - margin-bottom: 0; +.btn-ui:hover { + border-color: #cfd7e3; + background: var(--color-soft); + color: var(--color-ink); } -.card-ui p:last-child { - margin-bottom: 0; +.btn-ui-primary { + border-color: var(--color-dark); + background: var(--color-dark); + color: #ffffff; } -.card-title-ui { - margin-bottom: var(--space-sm); - font-size: 1rem; - font-weight: 700; +.btn-ui-primary:hover { + border-color: var(--color-dark-2); + background: var(--color-dark-2); + color: #ffffff; } -.card-actions { - display: flex; - flex-wrap: wrap; - gap: var(--space-sm); - margin-top: var(--space-md); +.btn-ui-blue { + border-color: var(--color-blue); + background: var(--color-blue); + color: #ffffff; +} + +.btn-ui-blue:hover { + border-color: #1d49bd; + background: #1d49bd; + color: #ffffff; +} + +.btn-ui-ghost { + border-color: transparent; + background: transparent; } -/* Section */ .section { - margin-bottom: var(--space-xl); + padding: var(--space-xxxl) 0; } -.section:last-child { - margin-bottom: 0; +.section-tight { + padding: var(--space-xxl) 0; +} + +.section-header { + max-width: 760px; + margin-bottom: var(--space-xl); } .section-title { - font-weight: 600; - margin-bottom: var(--space-md); + margin: var(--space-sm) 0 0; + color: var(--color-ink); + font-size: clamp(2rem, 3vw, 3rem); + font-weight: 850; + letter-spacing: 0; + line-height: 1.04; +} + +.section-copy { + margin: var(--space-md) 0 0; + font-size: 1.03rem; } .page-stack { - display: flex; - flex-direction: column; - gap: var(--space-xl); + display: grid; + gap: var(--space-xxl); + padding: var(--space-xxl) 0 var(--space-xxxl); } .page-header { display: flex; - align-items: flex-start; + align-items: flex-end; justify-content: space-between; - gap: var(--space-lg); + gap: var(--space-xl); } .page-title { - margin: 0 0 var(--space-sm); - font-size: 1.75rem; - font-weight: 700; + margin: 0; + color: var(--color-ink); + font-size: clamp(2.25rem, 4vw, 4rem); + font-weight: 850; + letter-spacing: 0; + line-height: 1; } .page-subtitle { - max-width: 680px; - margin: 0; - color: var(--color-muted); + max-width: 720px; + margin: var(--space-md) 0 0; + font-size: 1.05rem; } .card-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-lg); } -.detail-list { - display: grid; - gap: var(--space-sm); - margin: 0; +.card-grid-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); } -.detail-row { - display: flex; - justify-content: space-between; - gap: var(--space-md); - padding: var(--space-sm) 0; - border-bottom: 1px solid var(--color-border); +.card-ui { + border: 1px solid var(--color-line); + border-radius: var(--radius-lg); + background: var(--color-surface); + padding: var(--space-lg); + box-shadow: 0 1px 0 rgb(15 23 42 / 3%); } -.detail-row:last-child { - border-bottom: 0; +.card-ui p:last-child { + margin-bottom: 0; } -.detail-row dd { - margin-bottom: 0; - text-align: right; +.card-title-ui { + margin: 0 0 var(--space-sm); + color: var(--color-ink); + font-size: 1rem; + font-weight: 850; +} + +.feature-card { + min-height: 190px; +} + +.feature-icon { + display: grid; + place-items: center; + width: 42px; + height: 42px; + margin-bottom: var(--space-md); + border-radius: 14px; + background: var(--color-blue-soft); + color: var(--color-blue); + font-weight: 900; } .metric-value { display: block; margin-bottom: var(--space-xs); - font-size: 1.5rem; + color: var(--color-ink); + font-size: 2rem; + font-weight: 850; + line-height: 1; +} + +.metric-label { + color: var(--color-muted); + font-size: 0.9rem; font-weight: 700; } -.action-list { +.detail-list { display: grid; - gap: var(--space-md); + gap: var(--space-sm); + margin: 0; } -.action-item { +.detail-row { display: flex; - align-items: center; justify-content: space-between; - gap: var(--space-md); - padding: var(--space-md); - border: 1px solid var(--color-border); - border-radius: var(--radius); - background: var(--color-bg); + gap: var(--space-lg); + padding: var(--space-md) 0; + border-bottom: 1px solid var(--color-line); } -/* Buttons */ -.btn-ui { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--space-sm); - border-radius: var(--radius); - padding: var(--space-sm) var(--space-md); - border: 1px solid var(--color-border); - background: var(--color-surface); - color: var(--color-text); - font-weight: 600; - line-height: 1.5; - text-decoration: none; +.detail-row:last-child { + border-bottom: 0; } -.btn-ui:hover { - background: var(--color-bg); - color: var(--color-text); +.detail-row dt { + color: var(--color-muted); + font-weight: 700; } -.btn-ui-primary { - background: var(--color-accent); - color: white; - border: none; +.detail-row dd { + margin: 0; + color: var(--color-ink); + font-weight: 800; + text-align: right; } -.btn-ui-primary:hover { - background: #4338ca; - color: white; +.form-card { + max-width: 560px; + margin: 0 auto; } -/* Forms */ .form-ui { display: grid; gap: var(--space-md); @@ -293,32 +424,39 @@ a:hover { } .form-ui label { - font-weight: 600; + color: var(--color-ink); + font-weight: 800; } .form-ui input, .form-ui select, .form-ui textarea { width: 100%; - border: 1px solid var(--color-border); - border-radius: var(--radius); - padding: var(--space-sm) var(--space-md); + min-height: 46px; + border: 1px solid var(--color-line); + border-radius: var(--radius-md); + padding: 0 var(--space-md); background: var(--color-surface); - color: var(--color-text); + color: var(--color-ink); +} + +.form-ui textarea { + min-height: 120px; + padding-top: var(--space-md); } .form-ui input:focus, .form-ui select:focus, .form-ui textarea:focus { - border-color: var(--color-accent); - box-shadow: 0 0 0 3px rgb(79 70 229 / 12%); + border-color: var(--color-blue); + box-shadow: 0 0 0 4px rgb(37 87 214 / 12%); outline: none; } .form-ui .helptext, .form-ui ul { color: var(--color-muted); - font-size: 0.875rem; + font-size: 0.86rem; } .form-ui ul { @@ -326,41 +464,449 @@ a:hover { padding-left: var(--space-lg); } -/* Muted text */ .text-muted-ui { color: var(--color-muted); } -@media (max-width: 760px) { - .app-layout { - flex-direction: column; +.hero-visual { + position: relative; + width: min(100%, 1060px); + margin: var(--space-xxl) auto 0; +} + +.product-frame { + overflow: hidden; + border: 1px solid #dfe5ee; + border-radius: 22px; + background: #ffffff; + box-shadow: var(--shadow-soft); +} + +.browser-bar { + display: flex; + align-items: center; + justify-content: space-between; + height: 38px; + padding: 0 var(--space-md); + background: #34373d; + color: #cbd5e1; + font-size: 0.75rem; +} + +.browser-dots { + display: flex; + gap: 6px; +} + +.browser-dots span { + width: 10px; + height: 10px; + border-radius: 50%; + background: #f36b5f; +} + +.browser-dots span:nth-child(2) { + background: #f5c451; +} + +.browser-dots span:nth-child(3) { + background: #65c96f; +} + +.mock-app { + display: grid; + grid-template-columns: 174px 1fr; + min-height: 520px; + background: #f8fafc; +} + +.mock-sidebar { + padding: var(--space-lg) var(--space-md); + background: linear-gradient(180deg, #192133 0%, #101827 100%); + color: #ffffff; +} + +.mock-brand { + margin-bottom: var(--space-lg); + font-weight: 850; +} + +.mock-menu { + display: grid; + gap: var(--space-sm); +} + +.mock-menu span { + display: block; + padding: 9px 12px; + border-radius: 10px; + color: rgb(255 255 255 / 70%); + font-size: 0.82rem; + font-weight: 750; +} + +.mock-menu span.active { + background: #071026; + color: #ffffff; +} + +.mock-dashboard { + padding: var(--space-lg); +} + +.mock-topline { + display: flex; + justify-content: space-between; + gap: var(--space-md); + margin-bottom: var(--space-lg); +} + +.mock-date { + color: var(--color-muted); + font-size: 0.82rem; + font-weight: 800; +} + +.mock-dashboard h2 { + margin: 0; + color: var(--color-ink); + font-size: 2rem; + font-weight: 900; + line-height: 1; +} + +.mock-pill-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); + margin-top: var(--space-sm); +} + +.mock-pill { + display: inline-flex; + align-items: center; + min-height: 28px; + padding: 0 12px; + border-radius: var(--radius-pill); + background: #f1f5f9; + color: var(--color-ink); + font-size: 0.78rem; + font-weight: 850; +} + +.mock-pill.dark { + background: var(--color-dark); + color: #ffffff; +} + +.mock-grid { + display: grid; + grid-template-columns: 1.08fr 1fr 1fr; + gap: var(--space-md); +} + +.mock-panel { + min-height: 170px; + border: 1px solid var(--color-line); + border-radius: 18px; + background: #ffffff; + padding: var(--space-md); + box-shadow: 0 10px 24px rgb(15 23 42 / 4%); +} + +.mock-panel.tall { + grid-row: span 2; + min-height: 356px; +} + +.mock-panel-title { + display: flex; + justify-content: space-between; + gap: var(--space-md); + margin-bottom: var(--space-md); + color: var(--color-ink); + font-size: 0.88rem; + font-weight: 900; +} + +.progress-line { + height: 8px; + overflow: hidden; + border-radius: var(--radius-pill); + background: #edf1f6; +} + +.progress-line span { + display: block; + width: 68%; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, #16a45f, #38d482); +} + +.card-ui:nth-child(2) .progress-line span, +.mock-panel:nth-child(3) .progress-line span { + width: 42%; +} + +.card-ui:nth-child(3) .progress-line span, +.mock-panel:nth-child(4) .progress-line span { + width: 26%; +} + +.streak-row { + display: flex; + justify-content: space-between; + gap: 7px; + margin: var(--space-md) 0; +} + +.streak-day { + display: grid; + place-items: center; + width: 32px; + height: 32px; + border: 3px solid #30c477; + border-radius: 50%; + color: #16a45f; + font-size: 0.78rem; + font-weight: 900; +} + +.chart-line { + position: relative; + height: 130px; + overflow: hidden; + border-radius: 16px; + background: + linear-gradient(180deg, rgb(37 129 245 / 18%), rgb(37 129 245 / 0%)), + linear-gradient(135deg, transparent 0 10%, rgb(37 129 245 / 18%) 10% 13%, transparent 13% 28%, rgb(37 129 245 / 25%) 28% 32%, transparent 32% 46%, rgb(37 129 245 / 32%) 46% 52%, transparent 52%); +} + +.chart-line::after { + position: absolute; + right: 8%; + bottom: 34%; + left: 7%; + height: 4px; + border-radius: var(--radius-pill); + background: var(--color-blue); + box-shadow: + 45px -18px 0 var(--color-blue), + 115px 20px 0 var(--color-blue), + 180px -22px 0 var(--color-blue), + 250px -8px 0 var(--color-blue); + content: ""; + transform: rotate(-8deg); +} + +.table-list { + display: grid; + gap: 12px; +} + +.table-row { + display: grid; + grid-template-columns: 1fr 76px; + gap: var(--space-md); + align-items: center; + color: var(--color-muted); + font-size: 0.82rem; + font-weight: 800; +} + +.donut { + width: 132px; + height: 132px; + margin: var(--space-md) auto 0; + border-radius: 50%; + background: conic-gradient(#0f7c3f 0 52%, #36b878 52% 76%, #91d9ae 76% 90%, #d7e7dd 90% 100%); + box-shadow: inset 0 0 0 34px #ffffff; +} + +.phone-frame { + position: absolute; + right: -10px; + bottom: -18px; + width: 210px; + border: 8px solid #111827; + border-radius: 36px; + background: #ffffff; + box-shadow: 0 22px 45px rgb(15 23 42 / 24%); +} + +.phone-content { + min-height: 340px; + padding: var(--space-md); +} + +.phone-notch { + width: 66px; + height: 18px; + margin: 0 auto var(--space-sm); + border-radius: 0 0 14px 14px; + background: #111827; +} + +.phone-content h3 { + margin: 0 0 var(--space-md); + font-size: 1.4rem; + font-weight: 900; +} + +.mini-bars { + display: grid; + gap: 8px; +} + +.mini-bars span { + display: block; + height: 8px; + border-radius: var(--radius-pill); + background: #e8eef5; +} + +.mini-bars span:nth-child(1) { + width: 78%; + background: var(--color-green); +} + +.mini-bars span:nth-child(2) { + width: 52%; +} + +.mini-bars span:nth-child(3) { + width: 68%; + background: #63c68f; +} + +.trust-row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: var(--space-md); + margin-top: var(--space-xl); + color: var(--color-muted); + font-size: 0.94rem; + font-weight: 750; +} + +.logo-pill { + padding: 8px 14px; + border: 1px solid var(--color-line); + border-radius: var(--radius-pill); + background: #ffffff; +} + +.split-section { + display: grid; + grid-template-columns: 0.95fr 1.05fr; + gap: var(--space-xxl); + align-items: center; +} + +.quote-card { + display: grid; + gap: var(--space-md); + padding: var(--space-xl); + border-radius: var(--radius-lg); + background: var(--color-dark); + color: #ffffff; +} + +.quote-card p, +.quote-card .text-muted-ui { + color: rgb(255 255 255 / 76%); +} + +.quote-card .section-title { + color: #ffffff; +} + +.medal-row { + display: flex; + gap: var(--space-lg); + margin-top: var(--space-md); +} + +.medal { + color: var(--color-gold); + font-size: 1.7rem; + font-weight: 900; +} + +@media (max-width: 980px) { + .site-nav { + flex-wrap: wrap; + gap: var(--space-md); + padding: var(--space-md); } - .sidebar { + .nav-links { + order: 3; width: 100%; - border-right: 0; - border-bottom: 1px solid var(--color-border); + overflow-x: auto; + padding-bottom: var(--space-xs); } - .sidebar-nav { - display: flex; - gap: var(--space-sm); - overflow-x: auto; + .card-grid, + .card-grid-2, + .split-section { + grid-template-columns: 1fr; } - .sidebar-nav .nav-item { - margin-bottom: 0; - white-space: nowrap; + .mock-app { + grid-template-columns: 1fr; } - .topbar, - .page-header, - .action-item { + .mock-sidebar { + display: none; + } + + .mock-grid { + grid-template-columns: 1fr; + } + + .phone-frame { + display: none; + } +} + +@media (max-width: 640px) { + .container-ui { + padding-right: var(--space-md); + padding-left: var(--space-md); + } + + .hero { + padding-top: var(--space-xl); + } + + .nav-actions { + width: 100%; + justify-content: stretch; + } + + .nav-actions .btn-ui { + flex: 1; + } + + .card-actions, + .page-header { align-items: stretch; flex-direction: column; } - .content-area { + .product-frame { + border-radius: 16px; + } + + .mock-dashboard { padding: var(--space-md); } + + .mock-topline { + flex-direction: column; + } } diff --git a/templates/base.html b/templates/base.html index 0d17ab2..b9bc07e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -7,62 +7,44 @@ {% block title %}StudyBuddy{% endblock %} - - - - - - -
- - - - - -
- - -
-
- {% block page_title %}Dashboard{% endblock %} -
-
- {% block topbar_actions %} - {% if request.user.is_authenticated %} - Profile - {% else %} - Log in - Sign up - {% endif %} - {% endblock %} -
+
+ - -
- {% block content %} - - {% endblock %} +
+ {% block content %}{% endblock %}
-
- -
- diff --git a/templates/dashboard/index.html b/templates/dashboard/index.html index ea4d272..3129180 100644 --- a/templates/dashboard/index.html +++ b/templates/dashboard/index.html @@ -1,61 +1,108 @@ {% extends "base.html" %} {% block title %}Dashboard | StudyBuddy{% endblock %} -{% block page_title %}Dashboard{% endblock %} {% block content %} -
- - -
-

Today

-
-
- 0 -

Sessions planned

-

Create a study session to start shaping the day.

-
-
- 0 -

Tasks due

-

No upcoming study tasks have been added yet.

-
-
- 0% -

Weekly progress

-

Progress will update as study work is completed.

-
-
-
- -
-

Next Actions

-
-
-
-
-

Build your first study plan

-

Add a session with a subject, target outcome, and review time.

+
+
+ + +
+
+
+ 0h + Today +
+
+
+ 0% + Weekly goal +
+
+
+ 0 + Sessions tracked +
+
+
+
+ +
+
+
+
StreaksStart today
+
+ MTWTF +
+
Study TrendThis week
+
+
MedalsComing soon
+
+ 0 + 0 + 0 +
+
+ +
+
GoalsDaily
+ 2h 0m + Suggested study goal +
+
+ +
+
Study Days0/365
+ 0% + Year progress +
+
+ +
+
CoursesNext
+
+
Create first course
+
Add a study goal
+
Track session
+
+
+ +
+
ActivitiesPlanned
+
+
+
+
+ +
+
+
+ 01 +

Build your first study plan

+

Add a subject, target study time, and the first session you want to complete.

+ - Add session -
-
-
-

Review your profile

-

Confirm your account details before personal planning settings are added.

+ +
+ 02 +

Review account settings

+

Confirm your StudyBuddy profile before personal planning preferences are added.

+ - Open profile -
+
-
-
+ +
{% endblock %} diff --git a/templates/home.html b/templates/home.html index e897186..e7578de 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,37 +1,184 @@ {% extends "base.html" %} -{% block title %}StudyBuddy{% endblock %} -{% block page_title %}Study Plan{% endblock %} +{% block title %}StudyBuddy | Study Tracker and Planner{% endblock %} {% block content %} -
- +
+ -
-

Planning Overview

+
+
+
+ What is StudyBuddy? +

Study planning, tracking, and motivation in one place.

+

StudyBuddy gives students a clear picture of what they planned, what they completed, and where their next focused session should go.

+
+
+
+ 01 +

Track Sessions

+

Log study time and keep a visible record of steady work.

+
+
+ 02 +

Plan Goals

+

Turn courses, exams, and deadlines into achievable study targets.

+
+
+
+
+ +
+
+
+ Study Guides +

From scattered effort to visible progress.

+
-
-

Sessions

-

Plan study blocks around a clear workload, priority, and review rhythm.

+
+ T +

Focus Timer

+

Use session blocks to stay in the zone and avoid vague study days.

-
-

Notes

-

Keep the supporting material for each study goal close to the work.

+
+ S +

Streaks

+

Build consistency with daily signals that make momentum obvious.

-
-

Progress

-

Review what is complete, what is blocked, and what needs attention next.

+
+ G +

Goal Progress

+

See how each course is moving against the study target you set.

-
- + +
+ +
+
+
+ Get started +

Start building your study dashboard today.

+

StudyBuddy is ready for account creation, authentication, and the first dashboard foundation. More planning features can build on this visual system.

+ +
+
+
{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html index ababb0a..e1d5ba2 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -1,29 +1,29 @@ {% extends "base.html" %} -{% block title %}Log in | StudyBuddy{% endblock %} -{% block page_title %}Log in{% endblock %} +{% block title %}Log In | StudyBuddy{% endblock %} {% block content %} -
- +
+
+
+ Welcome back +

Log In

+

Return to your StudyBuddy dashboard and continue tracking study momentum.

+
-
-

Account Access

-
-
- {% csrf_token %} - {{ form.as_p }} -
- - Create account -
-
-
-
+
+
+

Account Access

+
+ {% csrf_token %} + {{ form.as_p }} +
+ + Create Account +
+
+
+
+
{% endblock %} diff --git a/templates/registration/signup.html b/templates/registration/signup.html index 330c391..20e0ecd 100644 --- a/templates/registration/signup.html +++ b/templates/registration/signup.html @@ -1,29 +1,29 @@ {% extends "base.html" %} -{% block title %}Sign up | StudyBuddy{% endblock %} -{% block page_title %}Sign up{% endblock %} +{% block title %}Create Account | StudyBuddy{% endblock %} {% block content %} -
- +
+
+
+ Start tracking +

Create Account

+

Create your StudyBuddy account and start building a dashboard for study sessions, goals, and progress.

+
-
-

New Account

-
-
- {% csrf_token %} - {{ form.as_p }} -
- - Log in -
-
-
-
+
+
+

New Account

+
+ {% csrf_token %} + {{ form.as_p }} +
+ + Log In +
+
+
+
+
{% endblock %} diff --git a/templates/users/profile.html b/templates/users/profile.html index 3acf0d7..eb28f9d 100644 --- a/templates/users/profile.html +++ b/templates/users/profile.html @@ -1,46 +1,63 @@ {% extends "base.html" %} {% block title %}Profile | StudyBuddy{% endblock %} -{% block page_title %}Profile{% endblock %} {% block content %} -
- +
+
+ -
-

Account Details

-
-
-
-
Display name
-
{{ request.user.display_name }}
-
-
-
Email
-
{{ request.user.email }}
-
-
-
Username
-
{{ request.user.username }}
-
-
-
-
+
+
+
+

Account Details

+
+
+
Display name
+
{{ request.user.display_name }}
+
+
+
Email
+
{{ request.user.email }}
+
+
+
Username
+
{{ request.user.username }}
+
+
+
-
-

Workspace

-
-

Study preferences

-

Personal scheduling and notification preferences will appear here as planning features are added.

-
- Back to dashboard +
+

Study Workspace

+

Personal scheduling, course targets, and notification preferences will appear here as StudyBuddy grows.

+
+ Study goals + Sessions + Progress +
+
+
+
+ +
+
+ Next step +

Make your study effort measurable.

+

Head back to the dashboard to start shaping the first trackable study workflow.

+
- -
+
+
{% endblock %} From 4d9646ed675c1ad8cb8c6ea9ebd5578e77c98d4a Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 14:27:24 +0100 Subject: [PATCH 06/12] feat(gitignore): add staticfiles to .gitignore and remove unused __init__.py --- .gitignore | 2 ++ app/__init__.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 app/__init__.py diff --git a/.gitignore b/.gitignore index 7db2825..bcce168 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,8 @@ cover/ local_settings.py db.sqlite3 db.sqlite3-journal +staticfiles/ +app/staticfiles/ # Flask stuff: instance/ diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index 99463bb..0000000 --- a/app/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Application package for StudyBuddy domain apps.""" From 2494919623249f10409a140cfae3172eec7d89ef Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 14:30:11 +0100 Subject: [PATCH 07/12] feat(forms): enhance user signup form with email and username validation, and improve unique username generation --- apps/users/forms.py | 68 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/apps/users/forms.py b/apps/users/forms.py index 1f6ea3b..cf20e52 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -1,15 +1,71 @@ -"""Forms for StudyBuddy user accounts.""" +"""Forms for StudyBuddy user registration and account workflows.""" from __future__ import annotations +from django import forms +from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm -from apps.users.models import CustomUser +CustomUser = get_user_model() -class CustomUserCreationForm(UserCreationForm): - """Signup form for the custom user model.""" +class UserSignUpForm(UserCreationForm): + """Form used by visitors to create a StudyBuddy account.""" + + email = forms.EmailField( + label="Email address", + help_text="Use this email address when signing in.", + ) + username = forms.CharField( + required=False, + help_text="Optional. If left blank, StudyBuddy derives one from your email.", + ) + + class Meta: + """Form metadata for user signup.""" - class Meta(UserCreationForm.Meta): model = CustomUser - fields = ("email", "username") + fields = ("email", "username", "first_name", "last_name", "password1", "password2") + + def clean_email(self) -> str: + """Validate that the submitted email is unique.""" + email = self.cleaned_data["email"].strip().lower() + + if CustomUser.objects.filter(email__iexact=email).exists(): + raise forms.ValidationError("A user with this email already exists.") + + return email + + def clean_username(self) -> str: + """Validate an optional username only when the user supplies one.""" + username = self.cleaned_data.get("username", "").strip() + + if username and CustomUser.objects.filter(username__iexact=username).exists(): + raise forms.ValidationError("A user with this username already exists.") + + return username + + def save(self, commit: bool = True): + """Create a user with a safe username and email-first login.""" + user = super().save(commit=False) + user.email = self.cleaned_data["email"] + user.username = self.cleaned_data.get("username") or self._unique_username() + + if commit: + user.save() + self.save_m2m() + + return user + + def _unique_username(self) -> str: + """Build a unique username from the email local part.""" + email = self.cleaned_data["email"] + base_username = email.split("@", maxsplit=1)[0][:140] or "user" + candidate = base_username + suffix = 1 + + while CustomUser.objects.filter(username__iexact=candidate).exists(): + suffix += 1 + candidate = f"{base_username}-{suffix}" + + return candidate From d08e6c09c56659724a90daa2e1eeb27686cb0973 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 14:31:56 +0100 Subject: [PATCH 08/12] feat(signup): update signup view to use UserSignUpForm and improve user feedback --- apps/users/views.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/users/views.py b/apps/users/views.py index 8d863ab..610d6e7 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -1,29 +1,33 @@ -"""Views for StudyBuddy user accounts.""" +"""Views for StudyBuddy account workflows.""" from __future__ import annotations +from django.contrib import messages from django.contrib.auth import login from django.contrib.auth.decorators import login_required +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render -from apps.users.forms import CustomUserCreationForm +from apps.users.forms import UserSignUpForm -def signup(request): - """Register a new StudyBuddy user.""" +def signup(request: HttpRequest) -> HttpResponse: + """Register a new user and send them to the dashboard.""" if request.method == "POST": - form = CustomUserCreationForm(request.POST) + form = UserSignUpForm(request.POST) + if form.is_valid(): user = form.save() - login(request, user) + login(request, user, backend="django.contrib.auth.backends.ModelBackend") + messages.success(request, "Your StudyBuddy account is ready.") return redirect("dashboard:index") else: - form = CustomUserCreationForm() + form = UserSignUpForm() - return render(request, "registration/signup.html", {"form": form}) + return render(request, "users/signup.html", {"form": form}) @login_required -def profile(request): - """Render the signed-in user's profile page.""" +def profile(request: HttpRequest) -> HttpResponse: + """Render the authenticated user's profile.""" return render(request, "users/profile.html") From 45ed5091107449d3e3bc01312d187e54f665ee8f Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 14:33:58 +0100 Subject: [PATCH 09/12] feat(signup): add signup template for user account creation --- templates/users/signup.html | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 templates/users/signup.html diff --git a/templates/users/signup.html b/templates/users/signup.html new file mode 100644 index 0000000..b65651d --- /dev/null +++ b/templates/users/signup.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block title %}Create account | StudyBuddy{% endblock %} + +{% block content %} +
+
+
+

Create your StudyBuddy account

+

+ Start with a secure account. Study sessions, notes, metrics, and insights will attach + to this identity as the product grows. +

+ +
+ {% csrf_token %} + + {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
{{ field.errors }}
+ {% endif %} +
+ {% endfor %} + + +
+ +

+ Already have an account? + Log in. +

+
+
+
+{% endblock %} \ No newline at end of file From 35fecc943e1d58e82ac8783bafadbbaab7759070 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 14:35:25 +0100 Subject: [PATCH 10/12] feat(tests): add integration tests for user authentication views --- apps/users/tests/test_auth_views.py | 99 +++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 apps/users/tests/test_auth_views.py diff --git a/apps/users/tests/test_auth_views.py b/apps/users/tests/test_auth_views.py new file mode 100644 index 0000000..096791d --- /dev/null +++ b/apps/users/tests/test_auth_views.py @@ -0,0 +1,99 @@ +"""Integration tests for user authentication views.""" + +from __future__ import annotations + +import pytest +from django.contrib.auth import get_user_model +from django.urls import reverse + +from apps.users.factories import CustomUserFactory + +CustomUser = get_user_model() + + +@pytest.mark.django_db +def test_signup_page_renders(client): + """The signup page renders a registration form.""" + response = client.get(reverse("users:signup")) + + assert response.status_code == 200 + assert b"Create your StudyBuddy account" in response.content + + +@pytest.mark.django_db +def test_signup_creates_user_and_redirects_to_dashboard(client): + """A valid signup creates a user and sends them to the dashboard.""" + response = client.post( + reverse("users:signup"), + { + "email": "new.user@example.com", + "username": "", + "first_name": "New", + "last_name": "User", + "password1": "StrongPassword123!", + "password2": "StrongPassword123!", + }, + ) + + assert response.status_code == 302 + assert response["Location"] == reverse("dashboard:index") + assert CustomUser.objects.filter(email="new.user@example.com").exists() + + +@pytest.mark.django_db +def test_signup_rejects_duplicate_email(client): + """Signup rejects email addresses that already belong to another user.""" + CustomUserFactory(email="duplicate@example.com") + + response = client.post( + reverse("users:signup"), + { + "email": "duplicate@example.com", + "username": "duplicate-user", + "first_name": "Duplicate", + "last_name": "User", + "password1": "StrongPassword123!", + "password2": "StrongPassword123!", + }, + ) + + assert response.status_code == 200 + assert b"A user with this email already exists." in response.content + + +@pytest.mark.django_db +def test_login_with_email_redirects_to_dashboard(client): + """Users can log in with their email address and password.""" + user = CustomUserFactory(email="login@example.com") + + response = client.post( + reverse("users:login"), + { + "username": user.email, + "password": "password123", + }, + ) + + assert response.status_code == 302 + assert response["Location"] == reverse("dashboard:index") + + +@pytest.mark.django_db +def test_profile_requires_login(client): + """Anonymous users are redirected before viewing a profile.""" + response = client.get(reverse("users:profile")) + + assert response.status_code == 302 + assert response["Location"].startswith(f"{reverse('users:login')}?next=") + + +@pytest.mark.django_db +def test_authenticated_profile_shows_user_email(client): + """Authenticated users can view their account profile.""" + user = CustomUserFactory(email="profile@example.com") + client.force_login(user) + + response = client.get(reverse("users:profile")) + + assert response.status_code == 200 + assert b"profile@example.com" in response.content \ No newline at end of file From bed11db57e0759fcfeed2acd938621e56fe58b3c Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 14:49:56 +0100 Subject: [PATCH 11/12] feat(signup): enhance signup page with improved layout and error handling --- apps/users/forms.py | 9 +++- apps/users/tests/test_auth_views.py | 5 +- apps/users/views.py | 2 - static/css/theme.css | 11 +++++ templates/users/signup.html | 76 +++++++++++++++-------------- 5 files changed, 61 insertions(+), 42 deletions(-) diff --git a/apps/users/forms.py b/apps/users/forms.py index cf20e52..b1c4876 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -25,7 +25,14 @@ class Meta: """Form metadata for user signup.""" model = CustomUser - fields = ("email", "username", "first_name", "last_name", "password1", "password2") + fields = ( + "email", + "username", + "first_name", + "last_name", + "password1", + "password2", + ) def clean_email(self) -> str: """Validate that the submitted email is unique.""" diff --git a/apps/users/tests/test_auth_views.py b/apps/users/tests/test_auth_views.py index 096791d..6ad964d 100644 --- a/apps/users/tests/test_auth_views.py +++ b/apps/users/tests/test_auth_views.py @@ -17,7 +17,8 @@ def test_signup_page_renders(client): response = client.get(reverse("users:signup")) assert response.status_code == 200 - assert b"Create your StudyBuddy account" in response.content + assert b"Create Account" in response.content + assert b"New Account" in response.content @pytest.mark.django_db @@ -96,4 +97,4 @@ def test_authenticated_profile_shows_user_email(client): response = client.get(reverse("users:profile")) assert response.status_code == 200 - assert b"profile@example.com" in response.content \ No newline at end of file + assert b"profile@example.com" in response.content diff --git a/apps/users/views.py b/apps/users/views.py index 610d6e7..92b9ae9 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -2,7 +2,6 @@ from __future__ import annotations -from django.contrib import messages from django.contrib.auth import login from django.contrib.auth.decorators import login_required from django.http import HttpRequest, HttpResponse @@ -19,7 +18,6 @@ def signup(request: HttpRequest) -> HttpResponse: if form.is_valid(): user = form.save() login(request, user, backend="django.contrib.auth.backends.ModelBackend") - messages.success(request, "Your StudyBuddy account is ready.") return redirect("dashboard:index") else: form = UserSignUpForm() diff --git a/static/css/theme.css b/static/css/theme.css index 8579a29..d6782ef 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -464,6 +464,17 @@ p { padding-left: var(--space-lg); } +.form-errors { + color: #b42318; + font-size: 0.88rem; + font-weight: 700; +} + +.form-errors ul { + margin: 0; + padding-left: var(--space-lg); +} + .text-muted-ui { color: var(--color-muted); } diff --git a/templates/users/signup.html b/templates/users/signup.html index b65651d..d938b67 100644 --- a/templates/users/signup.html +++ b/templates/users/signup.html @@ -1,47 +1,49 @@ {% extends "base.html" %} -{% block title %}Create account | StudyBuddy{% endblock %} +{% block title %}Create Account | StudyBuddy{% endblock %} {% block content %} -
-
-
-

Create your StudyBuddy account

-

- Start with a secure account. Study sessions, notes, metrics, and insights will attach - to this identity as the product grows. -

+
+
+
+ Start tracking +

Create Account

+

Create your StudyBuddy account and start building a dashboard for study sessions, goals, and progress.

+
-
- {% csrf_token %} +
+
+

New Account

- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} + + {% csrf_token %} - {% for field in form %} -
- - {{ field }} - {% if field.help_text %} -
{{ field.help_text }}
- {% endif %} - {% if field.errors %} -
{{ field.errors }}
- {% endif %} -
- {% endfor %} + {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} - - + {% for field in form %} +

+ + {{ field }} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% if field.errors %} + {{ field.errors }} + {% endif %} +

+ {% endfor %} -

- Already have an account? - Log in. -

-
+
+ + Log In +
+ + +
-
-{% endblock %} \ No newline at end of file +
+{% endblock %} From 668804ef85e1485a4d37a43884414f36aa7ec0f6 Mon Sep 17 00:00:00 2001 From: adrian adewunmi Date: Wed, 6 May 2026 15:17:49 +0100 Subject: [PATCH 12/12] feat(signup): add tests for duplicate username handling and unique username generation --- apps/users/tests/test_auth_views.py | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/apps/users/tests/test_auth_views.py b/apps/users/tests/test_auth_views.py index 6ad964d..9e8fedc 100644 --- a/apps/users/tests/test_auth_views.py +++ b/apps/users/tests/test_auth_views.py @@ -7,6 +7,7 @@ from django.urls import reverse from apps.users.factories import CustomUserFactory +from apps.users.forms import UserSignUpForm CustomUser = get_user_model() @@ -62,6 +63,75 @@ def test_signup_rejects_duplicate_email(client): assert b"A user with this email already exists." in response.content +@pytest.mark.django_db +def test_signup_rejects_duplicate_username(client): + """Signup rejects explicitly supplied usernames already in use.""" + CustomUserFactory(username="existing-user") + + response = client.post( + reverse("users:signup"), + { + "email": "unique@example.com", + "username": "existing-user", + "first_name": "Unique", + "last_name": "User", + "password1": "StrongPassword123!", + "password2": "StrongPassword123!", + }, + ) + + assert response.status_code == 200 + assert b"A user with this username already exists." in response.content + + +@pytest.mark.django_db +def test_signup_generates_unique_username_when_email_local_part_exists(client): + """Blank usernames are derived from email and made unique.""" + CustomUserFactory(email="learner@example.com", username="learner") + + response = client.post( + reverse("users:signup"), + { + "email": "learner@studybuddy.test", + "username": "", + "first_name": "New", + "last_name": "Learner", + "password1": "StrongPassword123!", + "password2": "StrongPassword123!", + }, + ) + + assert response.status_code == 302 + user = CustomUser.objects.get(email="learner@studybuddy.test") + + assert user.username == "learner-2" + + +@pytest.mark.django_db +def test_signup_form_save_commit_false_builds_unsaved_user(): + """Signup form can build a valid custom user without persisting it.""" + form = UserSignUpForm( + data={ + "email": "draft@example.com", + "username": "", + "first_name": "Draft", + "last_name": "User", + "password1": "StrongPassword123!", + "password2": "StrongPassword123!", + } + ) + + assert form.is_valid() + + user = form.save(commit=False) + + assert user.pk is None + assert user.email == "draft@example.com" + assert user.username == "draft" + assert user.check_password("StrongPassword123!") + assert not CustomUser.objects.filter(email="draft@example.com").exists() + + @pytest.mark.django_db def test_login_with_email_redirects_to_dashboard(client): """Users can log in with their email address and password."""